rdg 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +2 -2
  5. data/Gemfile +1 -1
  6. data/RELEASES.md +10 -0
  7. data/TODO.md +68 -0
  8. data/bin/ast +5 -4
  9. data/bin/cfg +5 -4
  10. data/lib/rdg/analysis/analyser.rb +30 -0
  11. data/lib/rdg/analysis/composite.rb +23 -0
  12. data/lib/rdg/analysis/context.rb +17 -0
  13. data/lib/rdg/analysis/equivalences.rb +25 -0
  14. data/lib/rdg/analysis/propagater.rb +51 -0
  15. data/lib/rdg/analysis/registry.rb +35 -0
  16. data/lib/rdg/cfg.rb +12 -41
  17. data/lib/rdg/control/begin.rb +6 -4
  18. data/lib/rdg/control/break.rb +19 -0
  19. data/lib/rdg/control/case.rb +25 -0
  20. data/lib/rdg/control/{while.rb → conditional_loop.rb} +8 -7
  21. data/lib/rdg/control/def.rb +21 -7
  22. data/lib/rdg/control/ensure.rb +30 -0
  23. data/lib/rdg/control/for.rb +29 -0
  24. data/lib/rdg/control/handler.rb +25 -0
  25. data/lib/rdg/control/if.rb +7 -6
  26. data/lib/rdg/control/jump.rb +33 -0
  27. data/lib/rdg/control/jump_to_start.rb +11 -0
  28. data/lib/rdg/control/next.rb +9 -0
  29. data/lib/rdg/control/none.rb +5 -3
  30. data/lib/rdg/control/redo.rb +9 -0
  31. data/lib/rdg/control/rescue.rb +31 -0
  32. data/lib/rdg/control/rescue_body.rb +29 -0
  33. data/lib/rdg/control/retry.rb +13 -0
  34. data/lib/rdg/control/return.rb +5 -6
  35. data/lib/rdg/control/when.rb +27 -0
  36. data/lib/rdg/graph/bidirected_adjacency_graph.rb +19 -0
  37. data/lib/rdg/graph/rgl/allow_duplicates.rb +45 -0
  38. data/lib/rdg/tree/ast.rb +15 -4
  39. data/lib/rdg/version.rb +1 -1
  40. data/rdg.gemspec +3 -2
  41. data/spec/integration/cfg/conditionals/case_spec.rb +66 -0
  42. data/spec/integration/cfg/{if_spec.rb → conditionals/if_spec.rb} +24 -9
  43. data/spec/integration/cfg/conditionals/unless_spec.rb +53 -0
  44. data/spec/integration/cfg/exceptions_spec.rb +131 -0
  45. data/spec/integration/cfg/loops/loop_control_spec.rb +90 -0
  46. data/spec/integration/cfg/loops/loop_spec.rb +70 -0
  47. data/spec/integration/cfg/sequence_spec.rb +7 -1
  48. data/spec/support/doubles/fake_ast.rb +15 -0
  49. data/spec/support/matchers/flow_between_matcher.rb +1 -1
  50. data/spec/unit/analysis/composite_spec.rb +47 -0
  51. data/spec/unit/analysis/equivalences_spec.rb +29 -0
  52. data/spec/unit/{control/analyser_spec.rb → analysis/propagater_spec.rb} +23 -12
  53. data/spec/unit/analysis/registry_spec.rb +61 -0
  54. data/spec/unit/control/begin_spec.rb +3 -6
  55. data/spec/unit/control/break_spec.rb +26 -0
  56. data/spec/unit/control/case_spec.rb +66 -0
  57. data/spec/unit/control/conditional_loop_spec.rb +22 -0
  58. data/spec/unit/control/ensure_spec.rb +33 -0
  59. data/spec/unit/control/for_spec.rb +26 -0
  60. data/spec/unit/control/handler_spec.rb +27 -0
  61. data/spec/unit/control/if_spec.rb +26 -18
  62. data/spec/unit/control/jump_spec.rb +43 -0
  63. data/spec/unit/control/jump_to_start_spec.rb +22 -0
  64. data/spec/unit/control/rescue_body_spec.rb +26 -0
  65. data/spec/unit/control/rescue_spec.rb +62 -0
  66. data/spec/unit/control/return_spec.rb +1 -10
  67. data/spec/unit/control/when_spec.rb +59 -0
  68. data/spec/unit/graph/bidirected_adjacency_graph_spec.rb +49 -0
  69. metadata +91 -18
  70. data/lib/rdg/control/analyser.rb +0 -44
  71. data/spec/integration/cfg/methods_spec.rb +0 -39
  72. data/spec/unit/control/def_spec.rb +0 -28
  73. data/spec/unit/control/while_spec.rb +0 -25
@@ -0,0 +1,33 @@
1
+ require "rdg/control/ensure"
2
+
3
+ module RDG
4
+ module Control
5
+ describe Ensure do
6
+ let(:registry) { spy('registry') }
7
+ let(:context) { Analysis::Context.new(spy('graph'), spy('equivalences'), registry) }
8
+
9
+ let(:main_ast) { FakeAst.new(:if) }
10
+ let(:ensure_ast) { FakeAst.new(:send) }
11
+ let(:ast) { FakeAst.new(:rescue, children: [main_ast, ensure_ast]) }
12
+ subject { Ensure.new(ast, context) }
13
+
14
+ it "should have control flow start at the main block" do
15
+ expect(subject.start_node).to eq(main_ast)
16
+ end
17
+
18
+ it "should have control flow end at the ensure block" do
19
+ expect(subject.end_nodes).to eq([ensure_ast])
20
+ end
21
+
22
+ it "should have control flow from main block to ensure block" do
23
+ expect(subject.internal_flow_edges).to eq([[main_ast, ensure_ast]])
24
+ end
25
+
26
+ it "should prepend Handler so that edges back to main_ast are created for ensure block" do
27
+ subject.analyse
28
+
29
+ expect(registry).to have_received(:prepend_for).with(ensure_ast, Handler)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ require "rdg/control/for"
2
+
3
+ module RDG
4
+ module Control
5
+ describe For do
6
+ let(:ast) { FakeAst.new(:for, children: [:iterator, :iterable, :body]) }
7
+ subject { For.new(ast) }
8
+
9
+ it "should have control flow start at the iterable object" do
10
+ expect(subject.start_node).to eq(:iterable)
11
+ end
12
+
13
+ it "should have control flow end at the iterable object" do
14
+ expect(subject.end_nodes).to eq(%i(iterable))
15
+ end
16
+
17
+ it "should have control flow edges between iterable and body, and vice-versa" do
18
+ expect(subject.internal_flow_edges).to eq([%i(iterable body), %i(body iterable)])
19
+ end
20
+
21
+ it "should have control flow nodes only for iterable and body, and not for iterator" do
22
+ expect(subject.nodes).to eq(%i(iterable body))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ require "rdg/control/handler"
2
+
3
+ module RDG
4
+ module Control
5
+ describe Handler do
6
+ let(:graph) { spy("graph") }
7
+ let(:equivalences) { spy("equivalences") }
8
+ let(:context) { Analysis::Context.new(graph, equivalences) }
9
+
10
+ let(:block_ast) { FakeAst.new(:rescue, children: [:rescuable]) }
11
+ let(:ast) { FakeAst.new(:resbody, ancestors: [block_ast]) }
12
+
13
+ subject { Handler.new(ast, context) }
14
+
15
+ it "should add edges from main to handler" do
16
+ allow(equivalences).to receive(:all).with(:rescuable).and_return(
17
+ %i(first_rescuable second_rescuable)
18
+ )
19
+
20
+ subject.analyse
21
+
22
+ expect(graph).to have_received(:add_edge).with(:first_rescuable, ast)
23
+ expect(graph).to have_received(:add_edge).with(:second_rescuable, ast)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -4,14 +4,11 @@ module RDG
4
4
  module Control
5
5
  describe If do
6
6
  context "without any alternatives" do
7
- subject do
8
- ast = double("ast")
9
- allow(ast).to receive(:children) { [:predicate, :consequence, :""] }
10
- If.new(ast, nil, nil)
11
- end
7
+ let(:ast) { FakeAst.new(:if, children: [:predicate, :consequence, :""]) }
8
+ subject { If.new(ast) }
12
9
 
13
10
  it "should have control flow start at the predicate" do
14
- expect(subject.start_nodes).to eq([:predicate])
11
+ expect(subject.start_node).to eq(:predicate)
15
12
  end
16
13
 
17
14
  it "should have control flow end at the predicate and the consequence" do
@@ -23,15 +20,29 @@ module RDG
23
20
  end
24
21
  end
25
22
 
26
- context "with one alternative" do
27
- subject do
28
- ast = double("ast")
29
- allow(ast).to receive(:children) { [:predicate, :consequence, :alternative] }
30
- If.new(ast, nil, nil)
23
+ context "without any consequence (i.e., the `parser` gem's representation of unless)" do
24
+ let(:ast) { FakeAst.new(:if, children: [:predicate, :"", :alternative]) }
25
+ subject { If.new(ast) }
26
+
27
+ it "should have control flow start at the predicate" do
28
+ expect(subject.start_node).to eq(:predicate)
29
+ end
30
+
31
+ it "should have control flow end at the predicate and the alternative" do
32
+ expect(subject.end_nodes).to eq([:predicate, :alternative])
31
33
  end
32
34
 
35
+ it "should have control flow edge between predicate and alternative" do
36
+ expect(subject.internal_flow_edges).to eq([[:predicate, :alternative]])
37
+ end
38
+ end
39
+
40
+ context "with one alternative" do
41
+ let(:ast) { FakeAst.new(:if, children: [:predicate, :consequence, :alternative]) }
42
+ subject { If.new(ast) }
43
+
33
44
  it "should have control flow start at the predicate" do
34
- expect(subject.start_nodes).to eq([:predicate])
45
+ expect(subject.start_node).to eq(:predicate)
35
46
  end
36
47
 
37
48
  it "should have control flow end at the consequences" do
@@ -47,14 +58,11 @@ module RDG
47
58
  end
48
59
 
49
60
  context "with several alternatives" do
50
- subject do
51
- ast = double("ast")
52
- allow(ast).to receive(:children) { [:predicate, :consequence, :a1, :a2, :a3] }
53
- If.new(ast, nil, nil)
54
- end
61
+ let(:ast) { FakeAst.new(:if, children: [:predicate, :consequence, :a1, :a2, :a3]) }
62
+ subject { If.new(ast) }
55
63
 
56
64
  it "should have control flow start at the predicate" do
57
- expect(subject.start_nodes).to eq([:predicate])
65
+ expect(subject.start_node).to eq(:predicate)
58
66
  end
59
67
 
60
68
  it "should have control flow end at the consequences" do
@@ -0,0 +1,43 @@
1
+ require "rdg/control/jump"
2
+
3
+ module RDG
4
+ module Control
5
+ describe Jump do
6
+ let(:graph) { spy("graph") }
7
+ let(:block_ast) { FakeAst.new(:while) }
8
+ let(:ast) { FakeAst.new(:thing, ancestors: [block_ast]) }
9
+
10
+ subject do
11
+ class DummyJump < Jump
12
+ def new_successors
13
+ %i(first_new_successor second_new_successor)
14
+ end
15
+ end
16
+
17
+ DummyJump.new(ast, graph)
18
+ end
19
+
20
+ it "should remove any edges to existing successors" do
21
+ allow(graph).to receive(:each_successor).and_yield(:successor)
22
+ subject.analyse
23
+
24
+ expect(graph).to have_received(:remove_edge).with(ast, :successor)
25
+ end
26
+
27
+ it "should add an edge from jump to each new successor" do
28
+ subject.analyse
29
+
30
+ expect(graph).to have_received(:add_edge).with(ast, :first_new_successor)
31
+ expect(graph).to have_received(:add_edge).with(ast, :second_new_successor)
32
+ end
33
+
34
+ it "should do nothing when not contained within a block" do
35
+ ast.ancestors = []
36
+ subject.analyse
37
+
38
+ expect(graph).not_to have_received(:add_edge)
39
+ expect(graph).not_to have_received(:remove_edge)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,22 @@
1
+ require "rdg/control/jump_to_start"
2
+
3
+ module RDG
4
+ module Control
5
+ describe JumpToStart do
6
+ let(:graph) { spy("graph") }
7
+ let(:equivalences) { spy("equivalences") }
8
+ let(:context) { Analysis::Context.new(graph, equivalences) }
9
+
10
+ let(:block_ast) { FakeAst.new(:while, children: [:jumpable]) }
11
+ let(:ast) { FakeAst.new(:thing, ancestors: [block_ast]) }
12
+
13
+ subject { JumpToStart.new(ast, context) }
14
+
15
+ it "should use equivalents of block's first child as new successors" do
16
+ allow(equivalences).to receive(:first).with(:jumpable).and_return(:new_successor)
17
+
18
+ expect(subject.new_successors).to eq([:new_successor])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ require "rdg/control/rescue_body"
2
+
3
+ module RDG
4
+ module Control
5
+ describe RescueBody do
6
+ let(:ast) { FakeAst.new(:resbody, children: [:ts, :e, 1, 2, 3]) }
7
+ subject { RescueBody.new(ast) }
8
+
9
+ it "should have control flow start at the first child" do
10
+ expect(subject.start_node).to eq(1)
11
+ end
12
+
13
+ it "should have control flow end at the last child" do
14
+ expect(subject.end_nodes).to eq([3])
15
+ end
16
+
17
+ it "should have control flow edges between each pair of children" do
18
+ expect(subject.internal_flow_edges).to eq([[1, 2], [2, 3]])
19
+ end
20
+
21
+ it "should have control flow nodes only for statements, and not for ex types or variable" do
22
+ expect(subject.nodes).to eq([1, 2, 3])
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ require "rdg/control/rescue"
2
+
3
+ module RDG
4
+ module Control
5
+ describe Rescue do
6
+ let(:registry) { spy('registry') }
7
+ let(:context) { Analysis::Context.new(spy('graph'), spy('equivalences'), registry) }
8
+
9
+ let(:main_ast) { FakeAst.new(:if) }
10
+ let(:handler1) { FakeAst.new(:send) }
11
+ let(:handler2) { FakeAst.new(:raise) }
12
+
13
+ context "rescue without an else" do
14
+ let(:ast) { FakeAst.new(:rescue, children: [main_ast, handler1, handler2, :""]) }
15
+ subject { Rescue.new(ast, context) }
16
+
17
+ it "should have control flow start at the main block" do
18
+ expect(subject.start_node).to eq(main_ast)
19
+ end
20
+
21
+ it "should have control flow end at the main block and each fallback" do
22
+ expect(subject.end_nodes).to eq([main_ast, handler1, handler2])
23
+ end
24
+
25
+ it "should have no control flow edges (yet)" do
26
+ expect(subject.internal_flow_edges).to eq([])
27
+ end
28
+
29
+ it "should prepend Handler so that edges back to main_ast are created for each handler" do
30
+ subject.analyse
31
+
32
+ expect(registry).to have_received(:prepend_for).with(handler1, Handler)
33
+ expect(registry).to have_received(:prepend_for).with(handler2, Handler)
34
+ end
35
+ end
36
+
37
+ context "rescue with an else" do
38
+ let(:ast) { FakeAst.new(:rescue, children: [main_ast, handler1, handler2, :else]) }
39
+ subject { Rescue.new(ast, context) }
40
+
41
+ it "should have control flow start at the main block" do
42
+ expect(subject.start_node).to eq(main_ast)
43
+ end
44
+
45
+ it "should have control flow end at each fallback and the else" do
46
+ expect(subject.end_nodes).to eq([handler1, handler2, :else])
47
+ end
48
+
49
+ it "should have control flow from the main block to the else and nothing more (yet)" do
50
+ expect(subject.internal_flow_edges).to eq([[main_ast, :else]])
51
+ end
52
+
53
+ it "should prepend Handler so that edges back to main_ast are created for each handler" do
54
+ subject.analyse
55
+
56
+ expect(registry).to have_received(:prepend_for).with(handler1, Handler)
57
+ expect(registry).to have_received(:prepend_for).with(handler2, Handler)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -4,8 +4,7 @@ module RDG
4
4
  module Control
5
5
  describe Return do
6
6
  let(:graph) { spy("graph") }
7
- let(:state) { { current_method: :do_important_stuff } }
8
- subject { Return.new(:return, graph, state) }
7
+ subject { Return.new(:return, graph) }
9
8
 
10
9
  it "should remove existing edges out of return node" do
11
10
  allow(graph).to receive(:each_successor).and_yield(:successor)
@@ -14,14 +13,6 @@ module RDG
14
13
 
15
14
  expect(graph).to have_received(:remove_edge).with(:return, :successor)
16
15
  end
17
-
18
- it "should do nothing if there is no current method" do
19
- state.delete(:current_method)
20
- subject.analyse
21
-
22
- expect(graph).not_to have_received(:add_edge)
23
- expect(graph).not_to have_received(:remove_edge)
24
- end
25
16
  end
26
17
  end
27
18
  end
@@ -0,0 +1,59 @@
1
+ require "rdg/control/when"
2
+
3
+ module RDG
4
+ module Control
5
+ describe When do
6
+ let(:ast) { FakeAst.new(:when, children: [:test, :action]) }
7
+ let(:graph) { spy("graph") }
8
+ subject { When.new(ast, graph) }
9
+
10
+ context "two successors" do
11
+ before(:each) do
12
+ allow(graph).to receive(:each_successor).with(ast) { [:next_when, :successor] }
13
+ end
14
+
15
+ it "should have control flow start at the expression" do
16
+ expect(subject.start_node).to eq(:test)
17
+ end
18
+
19
+ it "should have control flow edge between test and action" do
20
+ expect(subject.internal_flow_edges).to eq([[:test, :action]])
21
+ end
22
+
23
+ it "should propagate outgoing flow from test to next when" do
24
+ subject.propogate_outgoing_flow
25
+ expect(graph).to have_received(:add_edge).with(:test, :next_when)
26
+ end
27
+
28
+ it "should propagate outgoing flow from action to successor" do
29
+ subject.propogate_outgoing_flow
30
+ expect(graph).to have_received(:add_edge).with(:action, :successor)
31
+ end
32
+ end
33
+
34
+ context "one successor" do
35
+ before(:each) do
36
+ allow(graph).to receive(:each_successor).with(ast) { [:successor] }
37
+ end
38
+
39
+ it "should have control flow start at the expression" do
40
+ expect(subject.start_node).to eq(:test)
41
+ end
42
+
43
+ it "should have control flow edge between test and action" do
44
+ expect(subject.internal_flow_edges).to eq([[:test, :action]])
45
+ end
46
+
47
+ it "should propagate outgoing flow from test to successor" do
48
+ subject.propogate_outgoing_flow
49
+ expect(graph).to have_received(:add_edge).with(:test, :successor)
50
+ end
51
+
52
+ it "should propagate outgoing flow from action to successor" do
53
+ subject.propogate_outgoing_flow
54
+ expect(graph).to have_received(:add_edge).with(:action, :successor)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,49 @@
1
+ require "rdg/graph/bidirected_adjacency_graph"
2
+
3
+ module RDG
4
+ module Graph
5
+ describe BidirectedAdjacencyGraph do
6
+ subject do
7
+ BidirectedAdjacencyGraph[
8
+ :f, :b, # F
9
+ :b, :a, # / \
10
+ :b, :d, # B G
11
+ :d, :c, # / \ / \
12
+ :d, :e, # A D I
13
+ :f, :g, # / \ /
14
+ :g, :i, # C E H
15
+ :i, :h, #
16
+ :g, :d # NB: edges are directed downwards
17
+ ]
18
+ end
19
+
20
+ context "successors" do
21
+ it "should yield a list of successors" do
22
+ expect { |block| subject.each_successor(:b, &block) }.to yield_successive_args(:a, :d)
23
+ end
24
+
25
+ it "should not yield for a leaf" do
26
+ expect { |block| subject.each_successor(:c, &block) }.not_to yield_control
27
+ end
28
+
29
+ it "should not yield for an unknown node" do
30
+ expect { |block| subject.each_successor(:unknown, &block) }.not_to yield_control
31
+ end
32
+ end
33
+
34
+ context "predecessors" do
35
+ it "should yield a list of predecessors" do
36
+ expect { |block| subject.each_predecessor(:d, &block) }.to yield_successive_args(:b, :g)
37
+ end
38
+
39
+ it "should not yield for a root" do
40
+ expect { |block| subject.each_predecessor(:f, &block) }.not_to yield_control
41
+ end
42
+
43
+ it "should not yield for an unknown node" do
44
+ expect { |block| subject.each_predecessor(:unknown, &block) }.not_to yield_control
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end