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.
- checksums.yaml +4 -4
- data/Gemfile +2 -20
- data/README.md +14 -21
- data/bin/adsl-verify +8 -8
- data/lib/adsl.rb +3 -0
- data/lib/adsl/adsl.rb +3 -0
- data/lib/adsl/ds/data_store_spec.rb +339 -0
- data/lib/adsl/extract/instrumenter.rb +206 -0
- data/lib/adsl/extract/meta.rb +33 -0
- data/lib/adsl/extract/rails/action_block_builder.rb +233 -0
- data/lib/adsl/extract/rails/action_instrumenter.rb +400 -0
- data/lib/adsl/extract/rails/action_runner.rb +57 -0
- data/lib/adsl/extract/rails/active_record_metaclass_generator.rb +555 -0
- data/lib/adsl/extract/rails/callback_chain_simulator.rb +135 -0
- data/lib/adsl/extract/rails/invariant_extractor.rb +48 -0
- data/lib/adsl/extract/rails/invariant_instrumenter.rb +70 -0
- data/lib/adsl/extract/rails/other_meta.rb +57 -0
- data/lib/adsl/extract/rails/rails_extractor.rb +211 -0
- data/lib/adsl/extract/rails/rails_instrumentation_test_case.rb +34 -0
- data/lib/adsl/extract/rails/rails_special_gem_instrumentation.rb +120 -0
- data/lib/adsl/extract/rails/rails_test_helper.rb +140 -0
- data/lib/adsl/extract/sexp_utils.rb +54 -0
- data/lib/adsl/fol/first_order_logic.rb +261 -0
- data/lib/adsl/parser/adsl_parser.racc +159 -0
- data/lib/{parser → adsl/parser}/adsl_parser.rex +4 -4
- data/lib/{parser → adsl/parser}/adsl_parser.rex.rb +6 -6
- data/lib/adsl/parser/adsl_parser.tab.rb +1031 -0
- data/lib/adsl/parser/ast_nodes.rb +1410 -0
- data/lib/adsl/railtie.rb +67 -0
- data/lib/adsl/spass/bin.rb +230 -0
- data/lib/{spass → adsl/spass}/ruby_extensions.rb +0 -0
- data/lib/adsl/spass/spass_ds_extensions.rb +931 -0
- data/lib/adsl/spass/spass_translator.rb +393 -0
- data/lib/adsl/spass/util.rb +13 -0
- data/lib/adsl/util/csv_hash_formatter.rb +94 -0
- data/lib/adsl/util/general.rb +228 -0
- data/lib/adsl/util/test_helper.rb +71 -0
- data/lib/adsl/verification/formula_generators.rb +231 -0
- data/lib/adsl/verification/instrumentation_filter.rb +50 -0
- data/lib/adsl/verification/invariant.rb +19 -0
- data/lib/adsl/verification/rails_verification.rb +33 -0
- data/lib/adsl/verification/utils.rb +20 -0
- data/lib/adsl/verification/verification_case.rb +13 -0
- data/test/integration/rails/rails_branch_verification_test.rb +112 -0
- data/test/integration/rails/rails_verification_test.rb +253 -0
- data/test/integration/spass/basic_translation_test.rb +563 -0
- data/test/integration/spass/control_flow_translation_test.rb +421 -0
- data/test/unit/adsl/ds/data_store_spec_test.rb +54 -0
- data/test/unit/adsl/extract/instrumenter_test.rb +103 -0
- data/test/unit/adsl/extract/meta_test.rb +142 -0
- data/test/unit/adsl/extract/rails/action_block_builder_test.rb +178 -0
- data/test/unit/adsl/extract/rails/action_instrumenter_test.rb +68 -0
- data/test/unit/adsl/extract/rails/active_record_metaclass_generator_test.rb +336 -0
- data/test/unit/adsl/extract/rails/callback_chain_simulator_test.rb +76 -0
- data/test/unit/adsl/extract/rails/invariant_extractor_test.rb +92 -0
- data/test/unit/adsl/extract/rails/rails_extractor_test.rb +1380 -0
- data/test/unit/adsl/extract/rails/rails_test_helper_test.rb +25 -0
- data/test/unit/adsl/extract/sexp_utils_test.rb +100 -0
- data/test/unit/adsl/fol/first_order_logic_test.rb +227 -0
- data/test/unit/adsl/parser/action_parser_test.rb +1040 -0
- data/test/unit/adsl/parser/ast_nodes_test.rb +359 -0
- data/test/unit/adsl/parser/class_parser_test.rb +288 -0
- data/test/unit/adsl/parser/general_parser_test.rb +67 -0
- data/test/unit/adsl/parser/invariant_parser_test.rb +432 -0
- data/test/unit/adsl/parser/parser_util_test.rb +126 -0
- data/test/unit/adsl/spass/bin_test.rb +65 -0
- data/test/unit/adsl/spass/ruby_extensions_test.rb +39 -0
- data/test/unit/adsl/spass/spass_ds_extensions_test.rb +7 -0
- data/test/unit/adsl/spass/spass_translator_test.rb +342 -0
- data/test/unit/adsl/util/csv_hash_formatter_test.rb +68 -0
- data/test/unit/adsl/util/general_test.rb +303 -0
- data/test/unit/adsl/util/test_helper_test.rb +120 -0
- data/test/unit/adsl/verification/formula_generators_test.rb +200 -0
- data/test/unit/adsl/verification/instrumentation_filter_test.rb +39 -0
- data/test/unit/adsl/verification/utils_test.rb +39 -0
- data/test/unit/adsl/verification/verification_case_test.rb +8 -0
- metadata +229 -29
- data/lib/ds/data_store_spec.rb +0 -292
- data/lib/fol/first_order_logic.rb +0 -260
- data/lib/parser/adsl_ast.rb +0 -779
- data/lib/parser/adsl_parser.racc +0 -151
- data/lib/parser/adsl_parser.tab.rb +0 -976
- data/lib/parser/dsdl_parser.rex.rb +0 -196
- data/lib/parser/dsdl_parser.tab.rb +0 -976
- data/lib/spass/bin.rb +0 -164
- data/lib/spass/spass_ds_extensions.rb +0 -870
- data/lib/spass/spass_translator.rb +0 -388
- data/lib/spass/util.rb +0 -11
- data/lib/util/csv_hash_formatter.rb +0 -47
- data/lib/util/test_helper.rb +0 -33
- data/lib/util/util.rb +0 -114
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'adsl/parser/ast_nodes'
|
|
2
|
+
|
|
3
|
+
module ADSL
|
|
4
|
+
module Extract
|
|
5
|
+
module Rails
|
|
6
|
+
module CallbackChainSimulator
|
|
7
|
+
|
|
8
|
+
include ADSL::Parser
|
|
9
|
+
|
|
10
|
+
# returns true or false if the node will render or raise, or will not render or raise
|
|
11
|
+
# returns nil if the node may or may not render or raise
|
|
12
|
+
def halting_status_of(ast_node, is_action_body = false)
|
|
13
|
+
if ast_node.is_a?(ASTBlock)
|
|
14
|
+
sub_statuses = ast_node.statements.map do |stmt|
|
|
15
|
+
sub_rs = halting_status_of stmt, is_action_body
|
|
16
|
+
return true if sub_rs
|
|
17
|
+
sub_rs
|
|
18
|
+
end
|
|
19
|
+
return nil if sub_statuses.include? nil
|
|
20
|
+
return false
|
|
21
|
+
elsif ast_node.is_a?(ASTEither)
|
|
22
|
+
sub_statuses = ast_node.blocks.map{ |block| halting_status_of block, is_action_body }
|
|
23
|
+
return false if sub_statuses.uniq == [false]
|
|
24
|
+
return true if sub_statuses.uniq == [true]
|
|
25
|
+
return nil
|
|
26
|
+
elsif ast_node.is_a?(ASTDummyStmt) and [:render, :raise].include?(ast_node.type)
|
|
27
|
+
return false if ast_node.type == :render and is_action_body
|
|
28
|
+
return true
|
|
29
|
+
else
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# returns a hash of {:will_halt => block, :will_not_halt => block}
|
|
35
|
+
def split_into_paths_that_will_or_will_not_halt(block, is_action_body = false)
|
|
36
|
+
case halting_status_of block, is_action_body
|
|
37
|
+
when true; return { :will_halt => block, :will_not_halt => nil }
|
|
38
|
+
when false; return { :will_halt => nil, :will_not_halt => block }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
paths = { :will_halt => [], :will_not_halt => [] }
|
|
42
|
+
block.statements.each do |stmt|
|
|
43
|
+
if stmt.is_a?(ASTBlock) && halting_status_of(stmt, is_action_body).nil?
|
|
44
|
+
possibilities = split_into_paths_that_will_or_will_not_halt stmt, is_action_body
|
|
45
|
+
|
|
46
|
+
paths[:will_halt] << possibilities[:will_halt]
|
|
47
|
+
paths[:will_not_halt] << possibilities[:will_not_halt]
|
|
48
|
+
elsif stmt.is_a?(ASTEither) && halting_status_of(stmt, is_action_body).nil?
|
|
49
|
+
rendering_paths = []
|
|
50
|
+
not_rendering_paths = []
|
|
51
|
+
|
|
52
|
+
stmt.blocks.each do |subblock|
|
|
53
|
+
possibilities = split_into_paths_that_will_or_will_not_halt subblock, is_action_body
|
|
54
|
+
rendering_paths << possibilities[:will_halt] unless possibilities[:will_halt].nil?
|
|
55
|
+
not_rendering_paths << possibilities[:will_not_halt] unless possibilities[:will_not_halt].nil?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if rendering_paths.length == 1
|
|
59
|
+
paths[:will_halt] << rendering_paths.first
|
|
60
|
+
else
|
|
61
|
+
paths[:will_halt] << ASTEither.new(:blocks => rendering_paths)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
if not_rendering_paths.length == 1
|
|
65
|
+
paths[:will_not_halt] << not_rendering_paths.first
|
|
66
|
+
else
|
|
67
|
+
paths[:will_not_halt] << ASTEither.new(:blocks => not_rendering_paths)
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
paths[:will_halt] << stmt
|
|
71
|
+
paths[:will_not_halt] << stmt
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
paths[:will_halt] = ASTBlock.new(:statements => paths[:will_halt])
|
|
76
|
+
paths[:will_not_halt] = ASTBlock.new(:statements => paths[:will_not_halt])
|
|
77
|
+
|
|
78
|
+
paths
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def split_into_callbacks(root_block)
|
|
82
|
+
pairs = []
|
|
83
|
+
root_block.statements.reverse_each do |stmt|
|
|
84
|
+
if stmt.is_a? ADSL::Parser::ASTDummyStmt
|
|
85
|
+
pairs << [stmt.type, []]
|
|
86
|
+
else
|
|
87
|
+
pairs.last[1] << stmt
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
pairs.reverse!
|
|
91
|
+
pairs.length.times do |index|
|
|
92
|
+
stmts = pairs[index][1]
|
|
93
|
+
pairs[index][1] = (stmts.length == 1 ? stmts.first : ASTBlock.new(:statements => stmts.reverse))
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
pairs
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def interrupt_callback_chain_on_render(root_block, action_name)
|
|
100
|
+
callbacks = split_into_callbacks root_block
|
|
101
|
+
|
|
102
|
+
index = action_index = callbacks.index{ |callback_name, block| callback_name == action_name }
|
|
103
|
+
return if index.nil?
|
|
104
|
+
|
|
105
|
+
# skip the action and proceed to the most prior before block
|
|
106
|
+
until index < 0
|
|
107
|
+
block = callbacks[index][1]
|
|
108
|
+
render_halts = index != action_index
|
|
109
|
+
|
|
110
|
+
case halting_status_of block, !render_halts
|
|
111
|
+
when true
|
|
112
|
+
# will halt execution after this callback is done
|
|
113
|
+
callbacks = callbacks.first(index + 1)
|
|
114
|
+
when nil
|
|
115
|
+
# may render
|
|
116
|
+
paths = split_into_paths_that_will_or_will_not_halt block, !render_halts
|
|
117
|
+
what_happens_unless_renders = ASTBlock.new(:statements => callbacks[index+1..-1].map{ |c| c[1] })
|
|
118
|
+
callbacks = callbacks.first(index + 1)
|
|
119
|
+
callbacks.last[1] = ASTEither.new(:blocks => [
|
|
120
|
+
paths[:will_halt],
|
|
121
|
+
ASTBlock.new(:statements => [paths[:will_not_halt], *what_happens_unless_renders])
|
|
122
|
+
])
|
|
123
|
+
else
|
|
124
|
+
# doesn't render, all good!
|
|
125
|
+
end
|
|
126
|
+
index -= 1
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
root_block.statements = callbacks.map{ |name, block| block }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'adsl/verification/invariant'
|
|
2
|
+
require 'adsl/verification/formula_generators'
|
|
3
|
+
require 'adsl/extract/rails/invariant_instrumenter'
|
|
4
|
+
require 'adsl/verification/instrumentation_filter'
|
|
5
|
+
|
|
6
|
+
module ADSL
|
|
7
|
+
module Extract
|
|
8
|
+
module Rails
|
|
9
|
+
class InvariantExtractor
|
|
10
|
+
|
|
11
|
+
include ADSL::Verification
|
|
12
|
+
include ADSL::Verification::FormulaGenerators
|
|
13
|
+
include ADSL::Verification::InstrumentationFilterGenerators
|
|
14
|
+
|
|
15
|
+
attr_reader :invariants
|
|
16
|
+
|
|
17
|
+
def initialize(ar_class_names)
|
|
18
|
+
@ar_class_names = ar_class_names
|
|
19
|
+
@invariants = []
|
|
20
|
+
@builder = nil
|
|
21
|
+
@stack_level = 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def invariant(name = nil, builder)
|
|
25
|
+
@invariants << Invariant.new(:description => name, :formula => builder.adsl_ast)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def load_in_context(path)
|
|
29
|
+
file = File.open path, 'r'
|
|
30
|
+
ADSL::Extract::Rails::InvariantInstrumenter.new(@ar_class_names).instrument_and_execute_source self, file.read
|
|
31
|
+
ensure
|
|
32
|
+
file.close
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def extract(param)
|
|
36
|
+
if param.is_a? Array
|
|
37
|
+
param.each do |path|
|
|
38
|
+
load_in_context path
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
ADSL::Extract::Rails::InvariantInstrumenter.new(@ar_class_names).instrument_and_execute_source self, param
|
|
42
|
+
end
|
|
43
|
+
@invariants
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'adsl/extract/instrumenter'
|
|
2
|
+
require 'adsl/verification/formula_generators'
|
|
3
|
+
require 'adsl/extract/rails/active_record_metaclass_generator'
|
|
4
|
+
|
|
5
|
+
module ADSL
|
|
6
|
+
module Extract
|
|
7
|
+
module Rails
|
|
8
|
+
class InvariantInstrumenter < ADSL::Extract::Instrumenter
|
|
9
|
+
|
|
10
|
+
def instrument_and_execute_source object, source
|
|
11
|
+
instrumented_source = instrument_string source
|
|
12
|
+
exec_within do
|
|
13
|
+
object.instance_eval instrumented_source
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(ar_class_names, *params)
|
|
18
|
+
super *params
|
|
19
|
+
|
|
20
|
+
# if a block is passed to the invariant call, add that block to the last of the parameters instead
|
|
21
|
+
replace :iter do |sexp|
|
|
22
|
+
next sexp unless (
|
|
23
|
+
sexp[1].sexp_type == :call and
|
|
24
|
+
sexp[1][1].nil? and
|
|
25
|
+
sexp[1][2] == :invariant and
|
|
26
|
+
sexp[1].last.sexp_type == :call
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
params_for_invariant = sexp[1][3..-1]
|
|
30
|
+
s(:call, nil, :invariant,
|
|
31
|
+
*params_for_invariant[0..-2],
|
|
32
|
+
s(:iter, params_for_invariant.last, *sexp[2..-1])
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# replace and with self.and etc
|
|
37
|
+
[:and, :or].each do |operand|
|
|
38
|
+
replace operand do |sexp|
|
|
39
|
+
s(:if,
|
|
40
|
+
s(:call, nil, :respond_to?, s(:lit, operand)),
|
|
41
|
+
s(:call, s(:self), operand, *sexp.sexp_body),
|
|
42
|
+
s(operand, *sexp.sexp_body)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# replace not with self.not
|
|
48
|
+
replace :call do |sexp|
|
|
49
|
+
next sexp unless sexp[2] == :!
|
|
50
|
+
s(:if,
|
|
51
|
+
s(:call, nil, :respond_to?, s(:lit, :not)),
|
|
52
|
+
s(:call, s(:self), :not, sexp[1].dup),
|
|
53
|
+
sexp
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def should_instrument?(object, method_name)
|
|
59
|
+
return false unless super
|
|
60
|
+
|
|
61
|
+
klass = object.class != Class ? object.class : object
|
|
62
|
+
method = object.method method_name
|
|
63
|
+
|
|
64
|
+
klass.name.match(/^ADSL::.*$/).nil? && !(method.source_location[0] =~ /.*lib\/adsl\/.*/)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'active_support'
|
|
2
|
+
require 'adsl/parser/ast_nodes'
|
|
3
|
+
|
|
4
|
+
class NilClass
|
|
5
|
+
def adsl_ast
|
|
6
|
+
::ADSL::Parser::ASTEmptyObjset.new
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ADSL
|
|
11
|
+
module Extract
|
|
12
|
+
module Rails
|
|
13
|
+
|
|
14
|
+
class MetaUnknown
|
|
15
|
+
def method_missing(method, *args, &block)
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def respond_to?(method_name, *args, &block)
|
|
20
|
+
return true unless method_name == :adsl_ast
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
self.class.name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def adsl_ast
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class PartiallyUnknownHash < MetaUnknown
|
|
33
|
+
def initialize(options = {})
|
|
34
|
+
@options = options
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def [](arg)
|
|
38
|
+
@options[arg] || MetaUnknown.new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def []=(key, val)
|
|
42
|
+
@options[key] = val
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def method_missing(method, *args, &block)
|
|
46
|
+
return @options[method] if @options.include? method
|
|
47
|
+
if method.to_s =~ /^.*=$/
|
|
48
|
+
short_method = method.to_s[0..-2].to_sym
|
|
49
|
+
return @options[short_method] if @options.include? short_method
|
|
50
|
+
end
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
require 'adsl/extract/rails/action_instrumenter'
|
|
2
|
+
require 'adsl/extract/rails/invariant_extractor'
|
|
3
|
+
require 'adsl/extract/rails/callback_chain_simulator'
|
|
4
|
+
require 'adsl/extract/rails/rails_special_gem_instrumentation'
|
|
5
|
+
require 'adsl/extract/rails/other_meta'
|
|
6
|
+
require 'adsl/parser/ast_nodes'
|
|
7
|
+
require 'adsl/util/general'
|
|
8
|
+
require 'pathname'
|
|
9
|
+
|
|
10
|
+
module ADSL
|
|
11
|
+
module Extract
|
|
12
|
+
module Rails
|
|
13
|
+
class RailsExtractor
|
|
14
|
+
|
|
15
|
+
include ADSL::Extract::Rails::CallbackChainSimulator
|
|
16
|
+
include ADSL::Extract::Rails::RailsSpecialGemInstrumentation
|
|
17
|
+
|
|
18
|
+
attr_accessor :ar_classes, :actions, :invariants, :instrumentation_filters
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
21
|
+
options = Hash[
|
|
22
|
+
:ar_classes => default_activerecord_models,
|
|
23
|
+
:invariants => Dir['invariants/**/*_invs.rb'],
|
|
24
|
+
:instrumentation_filters => []
|
|
25
|
+
].merge options
|
|
26
|
+
|
|
27
|
+
@ar_classes = options[:ar_classes].map do |ar_class|
|
|
28
|
+
generator = ActiveRecordMetaclassGenerator.new ar_class
|
|
29
|
+
generator.generate_class
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
ar_class_names = @ar_classes.map(&:adsl_ast_class_name)
|
|
33
|
+
|
|
34
|
+
@invariant_extractor = ADSL::Extract::Rails::InvariantExtractor.new ar_class_names
|
|
35
|
+
@invariants = @invariant_extractor.extract(options[:invariants]).map{ |inv| inv.adsl_ast }
|
|
36
|
+
@instrumentation_filters = @invariant_extractor.instrumentation_filters
|
|
37
|
+
@instrumentation_filters += options[:instrumentation_filters]
|
|
38
|
+
|
|
39
|
+
@action_instrumenter = ADSL::Extract::Rails::ActionInstrumenter.new ar_class_names
|
|
40
|
+
@action_instrumenter.instrumentation_filters = @instrumentation_filters
|
|
41
|
+
@actions = []
|
|
42
|
+
all_routes.each do |route|
|
|
43
|
+
translation = action_to_adsl_ast(route)
|
|
44
|
+
@actions << translation unless translation.nil?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def all_routes
|
|
49
|
+
::Rails.application.routes.routes.map{ |route|
|
|
50
|
+
{
|
|
51
|
+
:request_method => request_method_for(route),
|
|
52
|
+
:url => url_for(route),
|
|
53
|
+
:controller => controller_of(route),
|
|
54
|
+
:action => action_of(route)
|
|
55
|
+
}
|
|
56
|
+
}.select{ |route|
|
|
57
|
+
!route[:action].nil? &&
|
|
58
|
+
!route[:controller].nil? &&
|
|
59
|
+
!route[:url].nil? &&
|
|
60
|
+
!route[:request_method].nil? &&
|
|
61
|
+
route[:controller].action_methods.include?(route[:action].to_s)
|
|
62
|
+
}.uniq{ |a|
|
|
63
|
+
[a[:controller], a[:action]]
|
|
64
|
+
}.sort{ |a, b| [a[:controller].to_s, a[:action]] <=> [b[:controller].to_s, b[:action]] }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def route_for(controller, action)
|
|
68
|
+
all_routes.select{ |a| a[:controller] == controller && a[:action] == action.to_sym}.first
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def action_name_for(route)
|
|
72
|
+
"#{route[:controller]}__#{route[:action]}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def callbacks(controller)
|
|
76
|
+
controller.respond_to?(:_process_action_callbacks) ? controller._process_action_callbacks : []
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def prepare_instrumentation(controller_class, action)
|
|
80
|
+
controller_class.class_eval <<-ruby, __FILE__, __LINE__ + 1
|
|
81
|
+
def default_render(*args); end
|
|
82
|
+
def verify_authenticity_token; end
|
|
83
|
+
def params
|
|
84
|
+
ADSL::Extract::Rails::PartiallyUnknownHash.new(
|
|
85
|
+
:controller => '#{ controller_class.controller_name }',
|
|
86
|
+
:action => '#{ action }'
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
ruby
|
|
90
|
+
|
|
91
|
+
instrument_gems controller_class, action
|
|
92
|
+
|
|
93
|
+
controller = controller_class.new
|
|
94
|
+
@action_instrumenter.instrument controller, action
|
|
95
|
+
callbacks(controller_class).each do |callback|
|
|
96
|
+
next if callback.filter.is_a?(String)
|
|
97
|
+
@action_instrumenter.instrument controller, callback.filter
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def action_to_adsl_ast(route)
|
|
102
|
+
instrumentation_allows = @instrumentation_filters.map do |f|
|
|
103
|
+
f.allow_instrumentation? route[:controller].new, route[:action]
|
|
104
|
+
end
|
|
105
|
+
return nil if instrumentation_allows.include? false
|
|
106
|
+
|
|
107
|
+
action_name = action_name_for route
|
|
108
|
+
potential_adsl_asts = @actions.select{ |action| action.name.text == action_name }
|
|
109
|
+
raise "Multiple actions with identical names" if potential_adsl_asts.length > 1
|
|
110
|
+
return potential_adsl_asts.first if potential_adsl_asts.length == 1
|
|
111
|
+
|
|
112
|
+
prepare_instrumentation route[:controller], route[:action]
|
|
113
|
+
|
|
114
|
+
session = ActionDispatch::Integration::Session.new(::Rails.application)
|
|
115
|
+
::Rails.application.config.action_dispatch.show_exceptions = false
|
|
116
|
+
|
|
117
|
+
block = @action_instrumenter.exec_within do
|
|
118
|
+
@action_instrumenter.exec_within do
|
|
119
|
+
request_method = route[:request_method].to_s.downcase.split('|').first
|
|
120
|
+
session.send request_method, route[:url]
|
|
121
|
+
end
|
|
122
|
+
@action_instrumenter.abb.root_lvl_adsl_ast
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
interrupt_callback_chain_on_render block, route[:action]
|
|
126
|
+
|
|
127
|
+
action = ADSL::Parser::ASTAction.new({
|
|
128
|
+
:name => ADSL::Parser::ASTIdent.new(:text => action_name),
|
|
129
|
+
:arg_cardinalities => [],
|
|
130
|
+
:arg_names => [],
|
|
131
|
+
:arg_types => [],
|
|
132
|
+
:block => block
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
action = action.optimize
|
|
136
|
+
action.prepend_global_variables_by_signatures /^at__.*/, /^atat__.*/
|
|
137
|
+
|
|
138
|
+
action
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def default_activerecord_models
|
|
142
|
+
models_dir = Rails.respond_to?(:root) ? Rails.root.join('app', 'models') : Pathname.new('app/models')
|
|
143
|
+
classes = Dir[models_dir.join '**', '*.rb'].map{ |path|
|
|
144
|
+
relative_path = /^#{Regexp.escape models_dir.to_s}\/(.*)\.rb$/.match(path)[1]
|
|
145
|
+
klass = nil
|
|
146
|
+
while klass.nil? && !relative_path.empty?
|
|
147
|
+
begin
|
|
148
|
+
klass = relative_path.camelize.constantize
|
|
149
|
+
rescue NameError, LoadError
|
|
150
|
+
end
|
|
151
|
+
if klass.nil?
|
|
152
|
+
relative_path = /^[^\/]+\/(.*)$/.match(relative_path)[1]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
raise "Could not find class corresponding to path #{path}" if klass.nil?
|
|
156
|
+
klass
|
|
157
|
+
}.select{ |klass| klass < ActiveRecord::Base }
|
|
158
|
+
until_no_change(classes) do |classes|
|
|
159
|
+
all = classes.dup
|
|
160
|
+
classes.each do |c|
|
|
161
|
+
all << c.superclass unless classes.include?(c.superclass) || c.superclass == ActiveRecord::Base
|
|
162
|
+
end
|
|
163
|
+
all
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def controller_of(route)
|
|
168
|
+
return nil unless route.defaults.include? :controller
|
|
169
|
+
possible_names = [
|
|
170
|
+
"#{route.defaults[:controller].pluralize}_controller".camelize,
|
|
171
|
+
"#{route.defaults[:controller]}_controller".camelize,
|
|
172
|
+
]
|
|
173
|
+
possible_names.each do |name|
|
|
174
|
+
begin
|
|
175
|
+
return name.constantize
|
|
176
|
+
rescue NameError
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
raise "No controller class found for #{route.defaults}; attempted class names are #{possible_names}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def action_of(route)
|
|
183
|
+
return nil unless route.defaults.include? :action
|
|
184
|
+
route.defaults[:action].to_sym
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def request_method_for(route)
|
|
188
|
+
method_s = route.verb.source.match(/^\^?(.*?)\$?$/)[1]
|
|
189
|
+
return nil if method_s.empty?
|
|
190
|
+
method_s.to_sym
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def url_for(route)
|
|
194
|
+
params = {}
|
|
195
|
+
route.required_parts.each do |part|
|
|
196
|
+
params[part] = 0
|
|
197
|
+
end
|
|
198
|
+
route.format(params)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def adsl_ast
|
|
202
|
+
ADSL::Parser::ASTSpec.new(
|
|
203
|
+
:classes => @ar_classes.map(&:adsl_ast),
|
|
204
|
+
:actions => @actions,
|
|
205
|
+
:invariants => @invariants
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|