rdg 0.0.2 → 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 (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