adsl 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -20
  3. data/README.md +14 -21
  4. data/bin/adsl-verify +8 -8
  5. data/lib/adsl.rb +3 -0
  6. data/lib/adsl/adsl.rb +3 -0
  7. data/lib/adsl/ds/data_store_spec.rb +339 -0
  8. data/lib/adsl/extract/instrumenter.rb +206 -0
  9. data/lib/adsl/extract/meta.rb +33 -0
  10. data/lib/adsl/extract/rails/action_block_builder.rb +233 -0
  11. data/lib/adsl/extract/rails/action_instrumenter.rb +400 -0
  12. data/lib/adsl/extract/rails/action_runner.rb +57 -0
  13. data/lib/adsl/extract/rails/active_record_metaclass_generator.rb +555 -0
  14. data/lib/adsl/extract/rails/callback_chain_simulator.rb +135 -0
  15. data/lib/adsl/extract/rails/invariant_extractor.rb +48 -0
  16. data/lib/adsl/extract/rails/invariant_instrumenter.rb +70 -0
  17. data/lib/adsl/extract/rails/other_meta.rb +57 -0
  18. data/lib/adsl/extract/rails/rails_extractor.rb +211 -0
  19. data/lib/adsl/extract/rails/rails_instrumentation_test_case.rb +34 -0
  20. data/lib/adsl/extract/rails/rails_special_gem_instrumentation.rb +120 -0
  21. data/lib/adsl/extract/rails/rails_test_helper.rb +140 -0
  22. data/lib/adsl/extract/sexp_utils.rb +54 -0
  23. data/lib/adsl/fol/first_order_logic.rb +261 -0
  24. data/lib/adsl/parser/adsl_parser.racc +159 -0
  25. data/lib/{parser → adsl/parser}/adsl_parser.rex +4 -4
  26. data/lib/{parser → adsl/parser}/adsl_parser.rex.rb +6 -6
  27. data/lib/adsl/parser/adsl_parser.tab.rb +1031 -0
  28. data/lib/adsl/parser/ast_nodes.rb +1410 -0
  29. data/lib/adsl/railtie.rb +67 -0
  30. data/lib/adsl/spass/bin.rb +230 -0
  31. data/lib/{spass → adsl/spass}/ruby_extensions.rb +0 -0
  32. data/lib/adsl/spass/spass_ds_extensions.rb +931 -0
  33. data/lib/adsl/spass/spass_translator.rb +393 -0
  34. data/lib/adsl/spass/util.rb +13 -0
  35. data/lib/adsl/util/csv_hash_formatter.rb +94 -0
  36. data/lib/adsl/util/general.rb +228 -0
  37. data/lib/adsl/util/test_helper.rb +71 -0
  38. data/lib/adsl/verification/formula_generators.rb +231 -0
  39. data/lib/adsl/verification/instrumentation_filter.rb +50 -0
  40. data/lib/adsl/verification/invariant.rb +19 -0
  41. data/lib/adsl/verification/rails_verification.rb +33 -0
  42. data/lib/adsl/verification/utils.rb +20 -0
  43. data/lib/adsl/verification/verification_case.rb +13 -0
  44. data/test/integration/rails/rails_branch_verification_test.rb +112 -0
  45. data/test/integration/rails/rails_verification_test.rb +253 -0
  46. data/test/integration/spass/basic_translation_test.rb +563 -0
  47. data/test/integration/spass/control_flow_translation_test.rb +421 -0
  48. data/test/unit/adsl/ds/data_store_spec_test.rb +54 -0
  49. data/test/unit/adsl/extract/instrumenter_test.rb +103 -0
  50. data/test/unit/adsl/extract/meta_test.rb +142 -0
  51. data/test/unit/adsl/extract/rails/action_block_builder_test.rb +178 -0
  52. data/test/unit/adsl/extract/rails/action_instrumenter_test.rb +68 -0
  53. data/test/unit/adsl/extract/rails/active_record_metaclass_generator_test.rb +336 -0
  54. data/test/unit/adsl/extract/rails/callback_chain_simulator_test.rb +76 -0
  55. data/test/unit/adsl/extract/rails/invariant_extractor_test.rb +92 -0
  56. data/test/unit/adsl/extract/rails/rails_extractor_test.rb +1380 -0
  57. data/test/unit/adsl/extract/rails/rails_test_helper_test.rb +25 -0
  58. data/test/unit/adsl/extract/sexp_utils_test.rb +100 -0
  59. data/test/unit/adsl/fol/first_order_logic_test.rb +227 -0
  60. data/test/unit/adsl/parser/action_parser_test.rb +1040 -0
  61. data/test/unit/adsl/parser/ast_nodes_test.rb +359 -0
  62. data/test/unit/adsl/parser/class_parser_test.rb +288 -0
  63. data/test/unit/adsl/parser/general_parser_test.rb +67 -0
  64. data/test/unit/adsl/parser/invariant_parser_test.rb +432 -0
  65. data/test/unit/adsl/parser/parser_util_test.rb +126 -0
  66. data/test/unit/adsl/spass/bin_test.rb +65 -0
  67. data/test/unit/adsl/spass/ruby_extensions_test.rb +39 -0
  68. data/test/unit/adsl/spass/spass_ds_extensions_test.rb +7 -0
  69. data/test/unit/adsl/spass/spass_translator_test.rb +342 -0
  70. data/test/unit/adsl/util/csv_hash_formatter_test.rb +68 -0
  71. data/test/unit/adsl/util/general_test.rb +303 -0
  72. data/test/unit/adsl/util/test_helper_test.rb +120 -0
  73. data/test/unit/adsl/verification/formula_generators_test.rb +200 -0
  74. data/test/unit/adsl/verification/instrumentation_filter_test.rb +39 -0
  75. data/test/unit/adsl/verification/utils_test.rb +39 -0
  76. data/test/unit/adsl/verification/verification_case_test.rb +8 -0
  77. metadata +229 -29
  78. data/lib/ds/data_store_spec.rb +0 -292
  79. data/lib/fol/first_order_logic.rb +0 -260
  80. data/lib/parser/adsl_ast.rb +0 -779
  81. data/lib/parser/adsl_parser.racc +0 -151
  82. data/lib/parser/adsl_parser.tab.rb +0 -976
  83. data/lib/parser/dsdl_parser.rex.rb +0 -196
  84. data/lib/parser/dsdl_parser.tab.rb +0 -976
  85. data/lib/spass/bin.rb +0 -164
  86. data/lib/spass/spass_ds_extensions.rb +0 -870
  87. data/lib/spass/spass_translator.rb +0 -388
  88. data/lib/spass/util.rb +0 -11
  89. data/lib/util/csv_hash_formatter.rb +0 -47
  90. data/lib/util/test_helper.rb +0 -33
  91. data/lib/util/util.rb +0 -114
@@ -0,0 +1,206 @@
1
+ require 'backports'
2
+ require 'sexp_processor'
3
+ require 'ruby_parser'
4
+ require 'method_source'
5
+ require 'ruby2ruby'
6
+ require 'adsl/extract/sexp_utils'
7
+ require 'adsl/extract/meta'
8
+
9
+ module Kernel
10
+ def ins_call(object, method_name, *args, &block)
11
+ ::ADSL::Extract::Instrumenter.get_instance.execute_instrumented object, method_name, *args, &block
12
+ end
13
+ end
14
+
15
+ module ADSL
16
+ module Extract
17
+ class Instrumenter
18
+
19
+ attr_reader :stack_depth, :method_locals_stack
20
+ attr_accessor :instrumentation_filters
21
+
22
+ @instance = nil
23
+ @method_locals_stack = []
24
+
25
+ def self.get_instance()
26
+ @instance
27
+ end
28
+
29
+ def ruby_parser
30
+ RUBY_VERSION >= '2' ? Ruby19Parser.new : RubyParser.for_current_ruby
31
+ end
32
+
33
+ def self.instrumented()
34
+ # a dummy method injected into the AST
35
+ end
36
+
37
+ def method_locals
38
+ @method_locals_stack.last
39
+ end
40
+
41
+ def previous_locals
42
+ @method_locals_stack[-2]
43
+ end
44
+
45
+ def root_locals
46
+ @method_locals_stack.first
47
+ end
48
+
49
+ def exec_within
50
+ Instrumenter.instance_variable_set(:@instance, self) if @stack_depth == 0
51
+ @stack_depth += 1
52
+ @method_locals_stack << create_locals if respond_to? :create_locals
53
+
54
+ return yield(self)
55
+ ensure
56
+ @stack_depth -= 1
57
+ @method_locals_stack.pop
58
+ Instrumenter.instance_variable_set(:@instance, nil) if @stack_depth == 0
59
+ end
60
+
61
+ def mark_sexp_instrumented(sexp)
62
+ raise 'Already instrumented' if sexp_instrumented? sexp
63
+
64
+ first_stmt = sexp[3]
65
+
66
+ if first_stmt[0] != :call or
67
+ first_stmt[1] != Instrumenter.to_sexp or
68
+ first_stmt[2] != :instrumented
69
+ new_stmt = s(:call, Instrumenter.to_sexp, :instrumented)
70
+ sexp.insert 3, new_stmt
71
+ end
72
+ sexp
73
+ end
74
+
75
+ def sexp_instrumented?(sexp)
76
+ first_stmt = sexp[3]
77
+ return (first_stmt[0] == :call and
78
+ first_stmt[1] == Instrumenter.to_sexp and
79
+ first_stmt[2] == :instrumented)
80
+ rescue MethodSource::SourceNotFoundError
81
+ return
82
+ end
83
+
84
+ def initialize(instrument_domain = Dir.pwd)
85
+ @instrument_domain = instrument_domain
86
+ @replacers = []
87
+ @stack_depth = 0
88
+ @method_locals_stack = []
89
+
90
+ # mark the instrumentation
91
+ replace :defn, :defs do |sexp|
92
+ mark_sexp_instrumented sexp
93
+ end
94
+
95
+ # make sure the instrumentation propagates through calls
96
+ replace :call do |sexp|
97
+ # expected format: s(:call, object, method_name, *args)
98
+ # replaced with Extract::Instrumenter.e(instrumenter_id, object, method_name, *args)
99
+ original_object = sexp.sexp_body[0] || s(:self)
100
+ original_method_name = sexp.sexp_body[1]
101
+ original_args = sexp.sexp_body[2..-1]
102
+
103
+ next sexp if [s(:self), nil].include? original_object and Kernel.respond_to? original_method_name
104
+
105
+ s(:call, nil, :ins_call, original_object, s(:lit, original_method_name), *original_args)
106
+ end
107
+ end
108
+
109
+ def should_instrument?(object, method_name)
110
+ return false if object.is_a?(Fixnum) or object.is_a?(Symbol)
111
+
112
+ method = object.singleton_class.instance_method method_name
113
+
114
+ return false if method.source_location.nil?
115
+ return false if method.owner == Kernel
116
+ return false if @instrument_domain && !(method.source_location.first =~ /^#{@instrument_domain}.*$/)
117
+
118
+ (instrumentation_filters || []).each do |filter|
119
+ return false unless filter.allow_instrumentation? object, method_name
120
+ end
121
+
122
+ source = method.source
123
+ sexp = ruby_parser.process source
124
+ !sexp_instrumented? sexp
125
+ rescue MethodSource::SourceNotFoundError
126
+ # sometimes this happens because the method_source gem bugs out with evals etc
127
+ return false
128
+ rescue NameError => e
129
+ # ghost method with no available source
130
+ return false
131
+ end
132
+
133
+ def replace(*types, &block)
134
+ options = types.last.is_a?(Hash) ? types.pop : {}
135
+ @replacers << [types, block, options]
136
+ end
137
+
138
+ def with_replace(*types, replacer)
139
+ replace *types, replacer
140
+ yield
141
+ ensure
142
+ @replacers.pop
143
+ end
144
+
145
+ def execute_instrumented(object, method_name, *args, &block)
146
+ self.exec_within do
147
+ instrument object, method_name
148
+ return object.send method_name, *args, &block
149
+ end
150
+ end
151
+
152
+ def convert_root_defs_into_defn(sexp)
153
+ sexp.sexp_type == :defs ? s(:defn, *sexp[2..-1]) : sexp
154
+ end
155
+
156
+ def instrument_string(source)
157
+ sexp = ruby_parser.process source
158
+ unless sexp.nil?
159
+ instrumented_sexp = instrument_sexp sexp
160
+ new_code = Ruby2Ruby.new.process instrumented_sexp
161
+ else
162
+ source
163
+ end
164
+ end
165
+
166
+ def instrument(object, method_name)
167
+ if should_instrument? object, method_name
168
+ begin
169
+ # this is complex because I want to avoid using .method on non-class objects
170
+ # because they might implement method themselves
171
+ method = object.singleton_class.instance_method method_name
172
+
173
+ source = method.source
174
+
175
+ # Ruby 2.0.0 support is in development as of writing this
176
+ sexp = ruby_parser.process source
177
+
178
+ unless sexp.nil?
179
+ sexp = convert_root_defs_into_defn sexp
180
+
181
+ instrumented_sexp = instrument_sexp sexp
182
+
183
+ new_code = Ruby2Ruby.new.process instrumented_sexp
184
+
185
+ object.replace_method method_name, new_code
186
+
187
+ new_code
188
+ else
189
+ source
190
+ end
191
+ rescue MethodSource::SourceNotFoundError
192
+ end
193
+ end
194
+ end
195
+
196
+ def instrument_sexp(sexp)
197
+ return nil if sexp.nil?
198
+ @replacers.reverse_each do |types, block, options|
199
+ sexp = sexp.block_replace *types, options, &block
200
+ end
201
+ sexp
202
+ end
203
+
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'backports'
3
+
4
+ class Object
5
+ def replace_method(method_name, source = nil, &block)
6
+ raise "Object #{self} of class #{self.class} does not respond to #{method_name}" unless self.respond_to? method_name, true
7
+
8
+ im = self.singleton_class.instance_method(method_name)
9
+
10
+ aliases = []
11
+ self.singleton_class.instance_methods.each do |other_name|
12
+ next if other_name == method_name
13
+ other = self.singleton_class.instance_method other_name
14
+ aliases << [other_name, other] if other == im
15
+ end
16
+
17
+ owner = im.owner
18
+
19
+ unless source.nil?
20
+ owner.class_eval source
21
+ else
22
+ owner.send :define_method, method_name, &block
23
+ end
24
+
25
+ aliases.each do |other_name, other|
26
+ other.owner.class_exec do
27
+ alias_method other_name, method_name
28
+ end
29
+ end
30
+
31
+ true
32
+ end
33
+ end
@@ -0,0 +1,233 @@
1
+ require 'adsl/extract/rails/other_meta'
2
+ require 'adsl/parser/ast_nodes'
3
+
4
+ module ADSL
5
+ module Extract
6
+ module Rails
7
+
8
+ class ActionBlockBuilder
9
+
10
+ attr_accessor :root_paths, :stmt_frames, :branch_choices
11
+
12
+ def initialize
13
+ @root_paths = []
14
+ @stmt_frames = [[]]
15
+ @branch_choices = []
16
+ @return_values = []
17
+ @has_returned_or_raised = false
18
+ end
19
+
20
+ def push_frame; @stmt_frames << []; end
21
+ def pop_frame; @stmt_frames.pop; end
22
+
23
+ def in_stmt_frame(*args)
24
+ push_frame
25
+ yield *args
26
+ ensure
27
+ return pop_frame
28
+ end
29
+
30
+ def included_already?(where, what)
31
+ return where.map{ |e| e.equal? what }.include?(true) ||
32
+ (
33
+ where.last.is_a?(ADSL::Parser::ASTObjsetStmt) &&
34
+ what.is_a?(ADSL::Parser::ASTObjsetStmt) &&
35
+ where.last.objset == what.objset
36
+ )
37
+ end
38
+
39
+ def append_stmt(stmt, options = {})
40
+ return stmt if @has_returned_or_raised && !options[:ignore_has_returned]
41
+ return stmt if included_already? @stmt_frames.last, stmt
42
+ @stmt_frames.last << stmt
43
+ stmt
44
+ end
45
+ alias_method :<<, :append_stmt
46
+
47
+ def branch_choice(if_id)
48
+ @branch_choices.each do |iter_if_id, choice|
49
+ return choice if iter_if_id == if_id
50
+ end
51
+ @branch_choices << [if_id, true]
52
+ true
53
+ end
54
+
55
+ def has_more_executions?
56
+ @branch_choices.each do |if_id, choice|
57
+ return true if choice == true
58
+ end
59
+ false
60
+ end
61
+
62
+ def increment_branch_choice
63
+ @branch_choices.pop while !@branch_choices.empty? && @branch_choices.last[1] == false
64
+ @branch_choices.last[1] = false unless @branch_choices.empty?
65
+ end
66
+
67
+ def reset
68
+ unless has_more_executions?
69
+ @root_paths = []
70
+ @branch_choices = []
71
+ @return_values = []
72
+ end
73
+ @has_returned_or_raised = false
74
+ @stmt_frames = [[]]
75
+ end
76
+
77
+ def explore_all_choices
78
+ while true
79
+ begin
80
+ reset
81
+ increment_branch_choice
82
+
83
+ return_value = yield
84
+
85
+ do_return return_value unless @has_returned_or_raised
86
+ rescue Exception => e
87
+ #puts "Exception: #{e}"
88
+ #puts e.backtrace.first 20
89
+ #do_raise unless @has_returned_or_raised
90
+ ensure
91
+ return common_return_value unless has_more_executions?
92
+ end
93
+ end
94
+ end
95
+
96
+ def common_supertype_of_objsets(values)
97
+ return false if values.empty?
98
+ values.each do |value|
99
+ return false if value.is_a?(MetaUnknown) || !value.respond_to?(:adsl_ast)
100
+ end
101
+ adsl_asts = values.reject{ |v| v.nil? }.map(&:adsl_ast)
102
+ adsl_asts = adsl_asts.map{ |v| v.is_a?(ADSL::Parser::ASTObjsetStmt) ? v.objset : v }
103
+ adsl_asts.each do |adsl_ast|
104
+ return false unless adsl_ast.class.is_objset?
105
+ # side effects should trigger only if the selection is chosen;
106
+ # but the translation does not do this
107
+ return false if adsl_ast.objset_has_side_effects?
108
+ end
109
+
110
+ common_supertype = nil
111
+ values.each do |value|
112
+ next if value.nil?
113
+ if common_supertype.nil?
114
+ common_supertype = value.class
115
+ elsif value.class <= common_supertype
116
+ # all is fine
117
+ elsif common_supertype <= value.class
118
+ common_supertype = value.class
119
+ else
120
+ return false
121
+ end
122
+ end
123
+
124
+ common_supertype
125
+ end
126
+
127
+ def common_supertype_of_objset_arrays(values)
128
+ return false if values.empty?
129
+
130
+ values.each do |value|
131
+ return false unless value.is_a? Array
132
+ end
133
+
134
+ return_value = []
135
+ highest_length = values.map(&:length).max
136
+ highest_length.times do |index|
137
+ ct = compatible_types(values.map{ |v| v[index] })
138
+ return false unless ct
139
+ return_value << ct
140
+ end
141
+ return_value
142
+ end
143
+
144
+ def common_return_value
145
+ uniq = @return_values.dup
146
+ # avoid include? because it uses :== and metaobjects override the == operator
147
+ if uniq.map(&:nil?).include? true
148
+ uniq.delete_if{ |e| e.nil? }
149
+ uniq << nil
150
+ end
151
+
152
+ if uniq.length == 1
153
+ uniq.first
154
+ elsif ct = common_supertype_of_objsets(uniq)
155
+ objsets = uniq.map(&:adsl_ast).map{ |r| r.is_a?(ADSL::Parser::ASTObjsetStmt) ? r.objset : r }
156
+ ct.new(:adsl_ast => ADSL::Parser::ASTOneOfObjset.new(:objsets => objsets))
157
+ elsif ct = common_supertype_of_objset_arrays(uniq)
158
+ highest_length = uniq.map(&:length).max
159
+ combined_objsets = []
160
+ highest_length.times do |index|
161
+ objsets = uniq.map{|u| u[index]}.map(&:adsl_ast).map{ |r| r.is_a?(ADSL::Parser::ASTObjsetStmt) ? r.objset : r }
162
+ combined_objsets << ct[index].new(:objset => ADSL::Parser::ASTOneOfObjset.new(:objsets => objsets))
163
+ end
164
+ combined_objsets
165
+ else
166
+ # append all return values to root paths
167
+ # cause they won't be returned and handled by the caller
168
+ # if an array is returned, assume the 'return 1, 2, 3' syntax
169
+
170
+ @return_values.length.times do |index|
171
+ Array.wrap(@return_values[index]).flatten.each do |ret_value|
172
+ stmt = ADSL::Extract::Rails::ActionInstrumenter.extract_stmt_from_expr ret_value
173
+ @root_paths[index] << stmt unless (
174
+ stmt.nil? ||
175
+ !stmt.class.is_statement? ||
176
+ included_already?(@root_paths[index], stmt)
177
+ )
178
+ end
179
+ end
180
+
181
+ ADSL::Extract::Rails::MetaUnknown.new
182
+ end
183
+ end
184
+
185
+ def all_stmts_so_far
186
+ stmts = []
187
+ @stmt_frames.each do |frame|
188
+ stmts += frame
189
+ end
190
+ stmts
191
+ end
192
+
193
+ def do_return(return_value = nil)
194
+ unless @has_returned_or_raised
195
+ @root_paths << all_stmts_so_far
196
+ @return_values << return_value
197
+ @has_returned_or_raised = true
198
+ end
199
+ return_value
200
+ end
201
+
202
+ def do_raise(*args)
203
+ unless @has_returned_or_raised
204
+ # appending nothing to root paths
205
+ @root_paths << [::ADSL::Parser::ASTDummyStmt.new(:type => :raise)]
206
+ @return_values << nil
207
+ @has_returned_or_raised = true
208
+ end
209
+ return *args
210
+ end
211
+
212
+ def adsl_ast
213
+ blocks = @root_paths.map do |root_path|
214
+ ::ADSL::Parser::ASTBlock.new :statements => root_path
215
+ end
216
+
217
+ if blocks.empty?
218
+ ::ADSL::Parser::ASTBlock.new :statements => []
219
+ elsif blocks.length == 1
220
+ blocks.first
221
+ else
222
+ ::ADSL::Parser::ASTBlock.new :statements => [::ADSL::Parser::ASTEither.new(:blocks => blocks)]
223
+ end
224
+ end
225
+
226
+ def root_lvl_adsl_ast
227
+ ::ADSL::Parser::ASTBlock.new :statements => @stmt_frames.first
228
+ end
229
+ end
230
+
231
+ end
232
+ end
233
+ end