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,70 @@
1
+ require "rdg/cfg"
2
+
3
+ module RDG
4
+ describe CFG do
5
+ %w(while until).each do |kind|
6
+ context "for simple #{kind} expressions" do
7
+ it "should show control flowing between test and body, and out to successor" do
8
+ cfg = CFG.from_source("a = 1; #{kind} true do; a += 1; end; z = 1")
9
+
10
+ expect(cfg).to contain("a = 1", "true", "a += 1", "z = 1")
11
+
12
+ expect(cfg).to flow_between("a = 1", "true")
13
+ expect(cfg).to flow_between("true", "a += 1")
14
+ expect(cfg).to flow_between("true", "z = 1")
15
+ expect(cfg).to flow_between("a += 1", "true")
16
+ end
17
+
18
+ it "should show control flowing through longer body" do
19
+ cfg = CFG.from_source("a = 1; #{kind} true do; a += 1; b += 1; c += 1; end; z = 1")
20
+
21
+ expect(cfg).to contain("a = 1", "true", "a += 1", "b += 1", "c += 1", "z = 1")
22
+
23
+ expect(cfg).to flow_between("a = 1", "true")
24
+ expect(cfg).to flow_between("true", "a += 1")
25
+ expect(cfg).to flow_between("true", "z = 1")
26
+ expect(cfg).to flow_between("a += 1", "b += 1")
27
+ expect(cfg).to flow_between("b += 1", "c += 1")
28
+ expect(cfg).to flow_between("c += 1", "true")
29
+ end
30
+
31
+ it "should show control flowing between test and body for modifier #{kind}" do
32
+ cfg = CFG.from_source("a = 1; b += 1 #{kind} true; z = 1")
33
+
34
+ expect(cfg).to contain("a = 1", "b += 1", "true", "z = 1")
35
+
36
+ expect(cfg).to flow_between("a = 1", "true")
37
+ expect(cfg).to flow_between("true", "b +=1")
38
+ expect(cfg).to flow_between("true", "z = 1")
39
+ expect(cfg).to flow_between("b += 1", "true")
40
+ end
41
+ end
42
+ end
43
+
44
+ context "for simple for expressions" do
45
+ it "should show control flowing between test and body, and out to successor" do
46
+ cfg = CFG.from_source("a = 1; for i in [1,2,3] do; a += 1; end; z = 1")
47
+
48
+ expect(cfg).to contain("a = 1", "[1,2,3]", "a += 1", "z = 1")
49
+
50
+ expect(cfg).to flow_between("a = 1", "[1,2,3]")
51
+ expect(cfg).to flow_between("[1,2,3]", "a += 1")
52
+ expect(cfg).to flow_between("[1,2,3]", "z = 1")
53
+ expect(cfg).to flow_between("a += 1", "[1,2,3]")
54
+ end
55
+
56
+ it "should show control flowing through longer body" do
57
+ cfg = CFG.from_source("a = 1; for i in [1,2,3] do; a += 1; b += 1; c += 1; end; z = 1")
58
+
59
+ expect(cfg).to contain("a = 1", "[1,2,3]", "a += 1", "b += 1", "c += 1", "z = 1")
60
+
61
+ expect(cfg).to flow_between("a = 1", "[1,2,3]")
62
+ expect(cfg).to flow_between("[1,2,3]", "a += 1")
63
+ expect(cfg).to flow_between("[1,2,3]", "z = 1")
64
+ expect(cfg).to flow_between("a += 1", "b += 1")
65
+ expect(cfg).to flow_between("b += 1", "c += 1")
66
+ expect(cfg).to flow_between("c += 1", "[1,2,3]")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -3,7 +3,13 @@ require "rdg/cfg"
3
3
  module RDG
4
4
  describe CFG do
5
5
  context "for a sequence of statements" do
6
- xit "should show no control flow for a lone element" do
6
+ it "should show no control flow for a lone integer" do
7
+ cfg = CFG.from_source("1")
8
+
9
+ expect(cfg).to contain("1")
10
+ end
11
+
12
+ it "should show no control flow for a lone assignment" do
7
13
  cfg = CFG.from_source("a = 1")
8
14
 
9
15
  expect(cfg).to contain("a = 1")
@@ -0,0 +1,15 @@
1
+ class FakeAst
2
+ attr_accessor :type, :children, :ancestors, :siblings
3
+
4
+ def initialize(type, children: [], ancestors: [], siblings: [])
5
+ @type, @children, @ancestors, @siblings = type, children, ancestors, siblings
6
+ end
7
+
8
+ def empty?
9
+ false
10
+ end
11
+
12
+ def parent
13
+ ancestors.first
14
+ end
15
+ end
@@ -1,6 +1,6 @@
1
1
  RSpec::Matchers.define :flow_between do |source_of_start, source_of_end|
2
2
  match do |graph|
3
- graph.has_edge?(
3
+ graph.edge?(
4
4
  find_vertex(graph, source_of_start),
5
5
  find_vertex(graph, source_of_end)
6
6
  )
@@ -0,0 +1,47 @@
1
+ require "rdg/analysis/composite"
2
+
3
+ module RDG
4
+ module Analysis
5
+ describe Composite do
6
+ subject { Composite.compose(FirstAnalyser, SecondAnalyser).new(:node, :context) }
7
+
8
+ it "delegates analysis to each composed analyser in turn" do
9
+ subject.analyse
10
+
11
+ expect(FirstAnalyser.called).to be_truthy
12
+ expect(SecondAnalyser.called).to be_truthy
13
+ end
14
+
15
+ it "makes node available to each composed analyser" do
16
+ subject.analyse
17
+
18
+ expect(FirstAnalyser.node).to eq(:node)
19
+ expect(SecondAnalyser.node).to eq(:node)
20
+ end
21
+
22
+ it "makes context available to each composed analyser" do
23
+ subject.analyse
24
+
25
+ expect(FirstAnalyser.context).to eq(:context)
26
+ expect(SecondAnalyser.context).to eq(:context)
27
+ end
28
+ end
29
+
30
+ class FakeAnalyser
31
+ def initialize(node, context)
32
+ self.class.node, self.class.context = node, context
33
+ end
34
+
35
+ def analyse
36
+ self.class.called = true
37
+ end
38
+
39
+ class << self
40
+ attr_accessor :called, :node, :context
41
+ end
42
+ end
43
+
44
+ class FirstAnalyser < FakeAnalyser; end
45
+ class SecondAnalyser < FakeAnalyser; end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ require "rdg/analysis/equivalences"
2
+
3
+ module RDG
4
+ module Analysis
5
+ describe Equivalences do
6
+ let(:ast) { FakeAst.new(:begin, children: [1, 2, 3]) }
7
+
8
+ context "all" do
9
+ it "returns direct equivalents" do
10
+ subject.add(:if, [:predicate, :consequence])
11
+
12
+ expect(subject.all(:if)).to eq([:predicate, :consequence])
13
+ end
14
+
15
+ it "returns indirect equivalents transitively" do
16
+ subject.add(:if, [:predicate, :consequence])
17
+ subject.add(:predicate, [:send, :float])
18
+ subject.add(:consequence, [:return])
19
+
20
+ expect(subject.all(:if)).to eq([:send, :float, :return])
21
+ end
22
+
23
+ it "returns the original when there is no equivalent" do
24
+ expect(subject.first(:if)).to eq(:if)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,27 +1,33 @@
1
- require "rdg/control/analyser"
1
+ require "rdg/analysis/propagater"
2
2
 
3
3
  module RDG
4
- module Control
5
- describe Analyser do
6
- let(:ast) { double("ast") }
4
+ module Analysis
5
+ describe Propagater do
6
+ let(:ast) { FakeAst.new(:some_type) }
7
7
  let(:graph) { spy("graph") }
8
+ let(:equivalences) { spy("equivalences") }
9
+ let(:context) { Context.new(graph, equivalences) }
8
10
 
9
11
  subject do
10
- class DummyAnalyser < Analyser
12
+ class DummyPropagater < Propagater
11
13
  def internal_flow_edges
12
- [[:s1, :e1], [:s2, :e2]]
14
+ [%i(s1 e1), %i(s2 e2)]
13
15
  end
14
16
 
15
- def start_nodes
16
- [:s1, :s2]
17
+ def start_node
18
+ :s1
17
19
  end
18
20
 
19
21
  def end_nodes
20
- [:e1, :e2]
22
+ %i(e1 e2)
23
+ end
24
+
25
+ def nodes
26
+ %i(s1 s2 e1 e2)
21
27
  end
22
28
  end
23
29
 
24
- DummyAnalyser.new(ast, graph, nil)
30
+ DummyPropagater.new(ast, context)
25
31
  end
26
32
 
27
33
  it "should add a CFG edge for every internal flow edge" do
@@ -31,14 +37,13 @@ module RDG
31
37
  expect(graph).to have_received(:add_edge).with(:s2, :e2)
32
38
  end
33
39
 
34
- it "should move any incoming edges to start nodes" do
40
+ it "should move any incoming edges to start node" do
35
41
  allow(graph).to receive(:each_predecessor).and_yield(:predecessor)
36
42
 
37
43
  subject.analyse
38
44
 
39
45
  expect(graph).to have_received(:remove_edge).with(:predecessor, ast)
40
46
  expect(graph).to have_received(:add_edge).with(:predecessor, :s1)
41
- expect(graph).to have_received(:add_edge).with(:predecessor, :s2)
42
47
  end
43
48
 
44
49
  it "should move any outgoing edges to end nodes" do
@@ -56,6 +61,12 @@ module RDG
56
61
 
57
62
  expect(graph).to have_received(:remove_vertex).with(ast)
58
63
  end
64
+
65
+ it "should add equivalences" do
66
+ subject.analyse
67
+
68
+ expect(equivalences).to have_received(:add).with(ast, %i(s1 s2 e1 e2))
69
+ end
59
70
  end
60
71
  end
61
72
  end
@@ -0,0 +1,61 @@
1
+ require "rdg/analysis/registry"
2
+
3
+ module RDG
4
+ module Analysis
5
+ describe Registry do
6
+ let(:ast) { FakeAst.new(:if) }
7
+
8
+ before(:each) do
9
+ Registry.clear
10
+ end
11
+
12
+ it "should allow analysers to be added by type" do
13
+ Registry.register_by_type FakeIfAnalyser, :if
14
+ analyser = subject.analyser_for(ast, :context)
15
+
16
+ expect(analyser.class).to eq(FakeIfAnalyser)
17
+ end
18
+
19
+ it "should select the correct analyser based on the AST's type" do
20
+ Registry.register_by_type FakeIfAnalyser, :if
21
+ Registry.register_by_type FakeForAnalyser, :for
22
+ analyser = subject.analyser_for(ast, :context)
23
+
24
+ expect(analyser.class).to eq(FakeIfAnalyser)
25
+ end
26
+
27
+ it "should select the default analyser if the AST's type is not recognised" do
28
+ Registry.register_by_type FakeForAnalyser, :for
29
+ Registry.register_default FakeDefaultAnalyser
30
+ analyser = subject.analyser_for(ast, :context)
31
+
32
+ expect(analyser.class).to eq(FakeDefaultAnalyser)
33
+ end
34
+
35
+ context "customisation" do
36
+ before(:each) do
37
+ Registry.register_by_type FakeIfAnalyser, :if
38
+ subject.prepend_for(ast, FakeExtraAnalyser)
39
+ end
40
+
41
+ it "should allow an additional handler to be prepended for a specific AST node" do
42
+ analyser = subject.analyser_for(ast, :context)
43
+
44
+ expect(analyser.class.superclass).to eq(Composite)
45
+ expect(analyser.types).to eq([FakeExtraAnalyser, FakeIfAnalyser])
46
+ end
47
+
48
+ it "should not run a prepended analyser for a different AST node of the same type" do
49
+ analyser = subject.analyser_for(FakeAst.new(:if), :context)
50
+
51
+ expect(analyser.class).to eq(FakeIfAnalyser)
52
+ end
53
+ end
54
+
55
+ class FakeDefaultAnalyser < Analyser; end
56
+ class FakeIfAnalyser < Analyser; end
57
+ class FakeForAnalyser < Analyser; end
58
+ class FakeExtraAnalyser < Analyser; end
59
+ end
60
+ end
61
+ end
@@ -3,14 +3,11 @@ require "rdg/control/begin"
3
3
  module RDG
4
4
  module Control
5
5
  describe Begin do
6
- subject do
7
- ast = double("ast")
8
- allow(ast).to receive(:children) { [1, 2, 3] }
9
- Begin.new(ast, nil, nil)
10
- end
6
+ let(:ast) { FakeAst.new(:begin, children: [1, 2, 3]) }
7
+ subject { Begin.new(ast) }
11
8
 
12
9
  it "should have control flow start at the first child" do
13
- expect(subject.start_nodes).to eq([1])
10
+ expect(subject.start_node).to eq(1)
14
11
  end
15
12
 
16
13
  it "should have control flow end at the last child" do
@@ -0,0 +1,26 @@
1
+ require "rdg/control/break"
2
+
3
+ module RDG
4
+ module Control
5
+ describe Break do
6
+ let(:graph) { spy("graph") }
7
+ let(:equivalences) { spy("equivalences") }
8
+ let(:context) { Analysis::Context.new(graph, equivalences) }
9
+
10
+ let(:inside_ast) { FakeAst.new(:send, ancestors: [loop_ast]) }
11
+ let(:outside_ast) { FakeAst.new(:send) }
12
+
13
+ let(:loop_ast) { FakeAst.new(:while, children: [:abstract_test]) }
14
+ let(:ast) { FakeAst.new(:thing, ancestors: [loop_ast]) }
15
+
16
+ subject { Break.new(ast, context) }
17
+
18
+ it "should use successor of test that are outside the loop as new successors" do
19
+ allow(equivalences).to receive(:first).with(:abstract_test).and_return(:test)
20
+ allow(graph).to receive(:each_successor).with(:test).and_return([inside_ast, outside_ast])
21
+
22
+ expect(subject.new_successors).to eq([outside_ast])
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,66 @@
1
+ require "rdg/control/case"
2
+
3
+ module RDG
4
+ module Control
5
+ describe Case do
6
+ context "sole (when) part" do
7
+ let(:ast) { FakeAst.new(:case, children: [:expression, :when, :""]) }
8
+ subject { Case.new(ast) }
9
+
10
+ it "should have control flow start at the expression" do
11
+ expect(subject.start_node).to eq(:expression)
12
+ end
13
+
14
+ it "should have control flow end at the when part" do
15
+ expect(subject.end_nodes).to eq([:when])
16
+ end
17
+
18
+ it "should have control flow edge between expression and when" do
19
+ expect(subject.internal_flow_edges).to eq([[:expression, :when]])
20
+ end
21
+ end
22
+
23
+ context "with else part" do
24
+ let(:ast) { FakeAst.new(:case, children: [:expression, :when, :alt]) }
25
+ subject { Case.new(ast) }
26
+
27
+ it "should have control flow start at the expression" do
28
+ expect(subject.start_node).to eq(:expression)
29
+ end
30
+
31
+ it "should have control flow end at the when and alternative" do
32
+ expect(subject.end_nodes).to eq([:when, :alt])
33
+ end
34
+
35
+ it "should have control flow edge from expression to when and then to alternative" do
36
+ expect(subject.internal_flow_edges).to eq([
37
+ [:expression, :when],
38
+ [:when, :alt]
39
+ ])
40
+ end
41
+ end
42
+
43
+ context "with several alternatives" do
44
+ let(:ast) { FakeAst.new(:case, children: [:expression, :when1, :when2, :when3, :alt]) }
45
+ subject { Case.new(ast) }
46
+
47
+ it "should have control flow start at the expression" do
48
+ expect(subject.start_node).to eq(:expression)
49
+ end
50
+
51
+ it "should have control flow end at the whens and alternative" do
52
+ expect(subject.end_nodes).to eq([:when1, :when2, :when3, :alt])
53
+ end
54
+
55
+ it "should have control flow edge from expression through whens to alternative" do
56
+ expect(subject.internal_flow_edges).to eq([
57
+ [:expression, :when1],
58
+ [:when1, :when2],
59
+ [:when2, :when3],
60
+ [:when3, :alt]
61
+ ])
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,22 @@
1
+ require "rdg/control/conditional_loop"
2
+
3
+ module RDG
4
+ module Control
5
+ describe ConditionalLoop do
6
+ let(:ast) { FakeAst.new(:while, children: [:predicate, :body]) }
7
+ subject { ConditionalLoop.new(ast) }
8
+
9
+ it "should have control flow start at the predicate" do
10
+ expect(subject.start_node).to eq(:predicate)
11
+ end
12
+
13
+ it "should have control flow end at the predicate" do
14
+ expect(subject.end_nodes).to eq([:predicate])
15
+ end
16
+
17
+ it "should have control flow edges between predicate and body, and vice-versa" do
18
+ expect(subject.internal_flow_edges).to eq([[:predicate, :body], [:body, :predicate]])
19
+ end
20
+ end
21
+ end
22
+ end