adsl 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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