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,45 @@
1
+ require "rgl/dot"
2
+
3
+ # This module provides a workaround for RGL issue #15:
4
+ # https://github.com/monora/rgl/issues/15
5
+ #
6
+ # Once the fix appears in an RGL release, this module
7
+ # and any includes of this module can be safely removed.
8
+
9
+ # rubocop:disable all
10
+ module RDG
11
+ module RGL
12
+ module AllowDuplicates
13
+ def to_dot_graph(params = {})
14
+ params['name'] ||= self.class.name.gsub(/:/, '_')
15
+ fontsize = params['fontsize'] ? params['fontsize'] : '8'
16
+ graph = (directed? ? ::RGL::DOT::Digraph : ::RGL::DOT::Graph).new(params)
17
+ edge_class = directed? ? ::RGL::DOT::DirectedEdge : ::RGL::DOT::Edge
18
+
19
+ each_vertex do |v|
20
+ graph << ::RGL::DOT::Node.new(
21
+ 'name' => v.object_id,
22
+ 'fontsize' => fontsize,
23
+ 'label' => vertex_label(v)
24
+ )
25
+ end
26
+
27
+ each_edge do |u, v|
28
+ graph << edge_class.new(
29
+ 'from' => u.object_id,
30
+ 'to' => v.object_id,
31
+ 'fontsize' => fontsize
32
+ )
33
+ end
34
+
35
+ graph
36
+ end
37
+
38
+ # Returns a label for vertex v. Default is v.to_s
39
+ def vertex_label(v)
40
+ v.to_s
41
+ end
42
+ end
43
+ end
44
+ end
45
+ # rubocop:enable all
@@ -1,6 +1,5 @@
1
- require "rgl/adjacency"
2
- require "rgl/dot"
3
1
  require "parser/current"
2
+ require_relative "../graph/bidirected_adjacency_graph"
4
3
  require_relative "rgl/pre_order_iterator"
5
4
  require_relative "rgl/post_order_iterator"
6
5
 
@@ -16,7 +15,7 @@ module RDG
16
15
  end
17
16
 
18
17
  def initialize(ast)
19
- @graph = ::RGL::DirectedAdjacencyGraph.new
18
+ @graph = Graph::BidirectedAdjacencyGraph.new
20
19
  import(ast)
21
20
  end
22
21
 
@@ -75,7 +74,19 @@ module RDG
75
74
  end
76
75
 
77
76
  def children
78
- @graph.each_adjacent(self).to_a
77
+ @graph.each_successor(self).to_a
78
+ end
79
+
80
+ def parent
81
+ @graph.each_predecessor(self).first
82
+ end
83
+
84
+ def ancestors
85
+ parent.nil? ? [] : parent.ancestors.unshift(parent)
86
+ end
87
+
88
+ def siblings
89
+ parent.nil? ? [] : parent.children
79
90
  end
80
91
 
81
92
  def ==(other)
@@ -1,3 +1,3 @@
1
1
  module RDG
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -18,10 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_runtime_dependency "parser", "~> 2.2.0.2"
21
+ spec.add_runtime_dependency "parser", "~> 2.2.2.0"
22
22
  spec.add_runtime_dependency "rgl", "~> 0.5.0"
23
+ spec.add_runtime_dependency "require_all", "~> 1.3.2"
23
24
 
24
- spec.add_development_dependency "bundler", "~> 1.8.0"
25
+ spec.add_development_dependency "bundler", "~> 1.9.0"
25
26
  spec.add_development_dependency "rake", "~> 10.4.2"
26
27
  spec.add_development_dependency "rspec", "~> 3.2.0"
27
28
  spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4.6"
@@ -0,0 +1,66 @@
1
+ require "rdg/cfg"
2
+
3
+ module RDG
4
+ describe CFG do
5
+ context "for simple case expressions" do
6
+ it "should show control flowing to expression, to test and then through and around action" do
7
+ cfg = CFG.from_source("a = 1; case b; when 2; c = 1; end; z = 1")
8
+
9
+ expect(cfg).to contain("a = 1", "b", "2", "c = 1", "z = 1")
10
+
11
+ expect(cfg).to flow_between("a = 1", "b")
12
+ expect(cfg).to flow_between("b", "2")
13
+ expect(cfg).to flow_between("2", "z = 1")
14
+ expect(cfg).to flow_between("2", "c = 1")
15
+ expect(cfg).to flow_between("c = 1", "z = 1")
16
+ end
17
+
18
+ it "should show control flowing through longer action" do
19
+ cfg = CFG.from_source("a = 1; case b; when 2; c = 1; d = 1; e = 1; end; z = 1")
20
+
21
+ expect(cfg).to contain("a = 1", "b", "2", "c = 1", "d = 1", "e = 1", "z = 1")
22
+
23
+ expect(cfg).to flow_between("a = 1", "b")
24
+ expect(cfg).to flow_between("b", "2")
25
+ expect(cfg).to flow_between("2", "z = 1")
26
+ expect(cfg).to flow_between("2", "c = 1")
27
+ expect(cfg).to flow_between("c = 1", "d = 1")
28
+ expect(cfg).to flow_between("d = 1", "e = 1")
29
+ expect(cfg).to flow_between("e = 1", "z = 1")
30
+ end
31
+ end
32
+
33
+ context "for case expressions with several actions" do
34
+ it "should show control flowing from test to action and to alternative" do
35
+ cfg = CFG.from_source("a = 1; case b; when 2; c = 1; else; d = 1; end; z = 1")
36
+
37
+ expect(cfg).to contain("a = 1", "b", "2", "c = 1", "d = 1", "z = 1")
38
+
39
+ expect(cfg).to flow_between("a = 1", "b")
40
+ expect(cfg).to flow_between("b", "2")
41
+ expect(cfg).to flow_between("2", "c = 1")
42
+ expect(cfg).to flow_between("2", "d = 1")
43
+ expect(cfg).to flow_between("c = 1", "z = 1")
44
+ expect(cfg).to flow_between("d = 1", "z = 1")
45
+ end
46
+
47
+ it "should show control flowing from test to action and to next test" do
48
+ cfg = CFG.from_source("a = 1;" \
49
+ "case b; when 2; c = 1; when 3; d = 1; else; e = 1; end;" \
50
+ "z = 1")
51
+
52
+ expect(cfg).to contain("a = 1", "b", "2", "c = 1", "3", "d = 1", "e = 1", "z = 1")
53
+
54
+ expect(cfg).to flow_between("a = 1", "b")
55
+ expect(cfg).to flow_between("b", "2")
56
+ expect(cfg).to flow_between("2", "c = 1")
57
+ expect(cfg).to flow_between("2", "3")
58
+ expect(cfg).to flow_between("3", "d = 1")
59
+ expect(cfg).to flow_between("3", "e = 1")
60
+ expect(cfg).to flow_between("c = 1", "z = 1")
61
+ expect(cfg).to flow_between("d = 1", "z = 1")
62
+ expect(cfg).to flow_between("e = 1", "z = 1")
63
+ end
64
+ end
65
+ end
66
+ end
@@ -3,21 +3,13 @@ require "rdg/cfg"
3
3
  module RDG
4
4
  describe CFG do
5
5
  context "for simple if expressions" do
6
- it "should show control flowing from predicate to successor (skipping consequence)" do
6
+ it "should show control flowing from predicate through and around consequence" do
7
7
  cfg = CFG.from_source("a = 1; if b == 1 then; c = 1; end; z = 1")
8
8
 
9
9
  expect(cfg).to contain("a = 1", "b == 1", "c = 1", "z = 1")
10
10
 
11
11
  expect(cfg).to flow_between("a = 1", "b == 1")
12
12
  expect(cfg).to flow_between("b == 1", "z = 1")
13
- end
14
-
15
- it "should show control flowing into predicate and through consequence" do
16
- cfg = CFG.from_source("a = 1; if b == 1 then; c = 1; end; z = 1")
17
-
18
- expect(cfg).to contain("a = 1", "b == 1", "c = 1", "z = 1")
19
-
20
- expect(cfg).to flow_between("a = 1", "b == 1")
21
13
  expect(cfg).to flow_between("b == 1", "c = 1")
22
14
  expect(cfg).to flow_between("c = 1", "z = 1")
23
15
  end
@@ -33,6 +25,17 @@ module RDG
33
25
  expect(cfg).to flow_between("d = 1", "e = 1")
34
26
  expect(cfg).to flow_between("e = 1", "z = 1")
35
27
  end
28
+
29
+ it "should show control flowing through and around predicate of modifier if" do
30
+ cfg = CFG.from_source("a = 1; c = 1 if b == 1; z = 1")
31
+
32
+ expect(cfg).to contain("a = 1", "b == 1", "c = 1", "z = 1")
33
+
34
+ expect(cfg).to flow_between("a = 1", "b == 1")
35
+ expect(cfg).to flow_between("b == 1", "z = 1")
36
+ expect(cfg).to flow_between("b == 1", "c = 1")
37
+ expect(cfg).to flow_between("c = 1", "z = 1")
38
+ end
36
39
  end
37
40
 
38
41
  context "for if expressions with several consequences" do
@@ -64,6 +67,18 @@ module RDG
64
67
  expect(cfg).to flow_between("d = 1", "z = 1")
65
68
  expect(cfg).to flow_between("e = 1", "z = 1")
66
69
  end
70
+
71
+ it "should show control flowing from predicate to both consequences of ternary" do
72
+ cfg = CFG.from_source("a = 1; b == 1 ? c = 1 : d = 1; z = 1")
73
+
74
+ expect(cfg).to contain("a = 1", "b == 1", "c = 1", "d = 1", "z = 1")
75
+
76
+ expect(cfg).to flow_between("a = 1", "b == 1")
77
+ expect(cfg).to flow_between("b == 1", "c = 1")
78
+ expect(cfg).to flow_between("b == 1", "d = 1")
79
+ expect(cfg).to flow_between("c = 1", "z = 1")
80
+ expect(cfg).to flow_between("d = 1", "z = 1")
81
+ end
67
82
  end
68
83
  end
69
84
  end
@@ -0,0 +1,53 @@
1
+ require "rdg/cfg"
2
+
3
+ module RDG
4
+ describe CFG do
5
+ context "for unless expressions" do
6
+ it "should show control flowing from predicate through and around consequence" do
7
+ cfg = CFG.from_source("a = 1; unless b == 1 then; c = 1; end; z = 1")
8
+
9
+ expect(cfg).to contain("a = 1", "b == 1", "c = 1", "z = 1")
10
+
11
+ expect(cfg).to flow_between("a = 1", "b == 1")
12
+ expect(cfg).to flow_between("b == 1", "z = 1")
13
+ expect(cfg).to flow_between("b == 1", "c = 1")
14
+ expect(cfg).to flow_between("c = 1", "z = 1")
15
+ end
16
+
17
+ it "should show control flowing into predicate and through longer consequence" do
18
+ cfg = CFG.from_source("a = 1; unless b == 1 then; c = 1; d = 1; e = 1; end; z = 1")
19
+
20
+ expect(cfg).to contain("a = 1", "b == 1", "c = 1", "d = 1", "e = 1", "z = 1")
21
+
22
+ expect(cfg).to flow_between("a = 1", "b == 1")
23
+ expect(cfg).to flow_between("b == 1", "c = 1")
24
+ expect(cfg).to flow_between("c = 1", "d = 1")
25
+ expect(cfg).to flow_between("d = 1", "e = 1")
26
+ expect(cfg).to flow_between("e = 1", "z = 1")
27
+ end
28
+
29
+ it "should show control flowing through and around predicate of modifier unless" do
30
+ cfg = CFG.from_source("a = 1; c = 1 unless b == 1; z = 1")
31
+
32
+ expect(cfg).to contain("a = 1", "b == 1", "c = 1", "z = 1")
33
+
34
+ expect(cfg).to flow_between("a = 1", "b == 1")
35
+ expect(cfg).to flow_between("b == 1", "z = 1")
36
+ expect(cfg).to flow_between("b == 1", "c = 1")
37
+ expect(cfg).to flow_between("c = 1", "z = 1")
38
+ end
39
+
40
+ it "should show control flowing from predicate to (and out of) all consequences" do
41
+ cfg = CFG.from_source("a = 1; unless b == 1 then; c = 1; else; d = 1; end; z = 1")
42
+
43
+ expect(cfg).to contain("a = 1", "b == 1", "c = 1", "d = 1", "z = 1")
44
+
45
+ expect(cfg).to flow_between("a = 1", "b == 1")
46
+ expect(cfg).to flow_between("b == 1", "c = 1")
47
+ expect(cfg).to flow_between("b == 1", "d = 1")
48
+ expect(cfg).to flow_between("c = 1", "z = 1")
49
+ expect(cfg).to flow_between("d = 1", "z = 1")
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,131 @@
1
+ require "rdg/cfg"
2
+
3
+ module RDG
4
+ describe CFG do
5
+ ["begin"].each do |block_type|
6
+ context "for a #{block_type} block with exception handlers" do
7
+ it "should show control flow from every child to the start of every handler" do
8
+ cfg = CFG.from_source(
9
+ "#{block_type}; a = 1; b = 1; rescue Err; y = 1; rescue; z = 1; end"
10
+ )
11
+
12
+ expect(cfg).to contain("a = 1", "b = 1", "y = 1", "z = 1")
13
+
14
+ expect(cfg).to flow_between("a = 1", "y = 1")
15
+ expect(cfg).to flow_between("b = 1", "y = 1")
16
+ expect(cfg).to flow_between("a = 1", "z = 1")
17
+ expect(cfg).to flow_between("b = 1", "z = 1")
18
+ end
19
+
20
+ it "should show control flowing into the first child" do
21
+ cfg = CFG.from_source(
22
+ "a = 1; #{block_type}; b = 1; rescue; z = 1; end"
23
+ )
24
+
25
+ expect(cfg).to contain("a = 1", "b = 1", "z = 1")
26
+
27
+ expect(cfg).to flow_between("a = 1", "b = 1")
28
+ end
29
+
30
+ it "should show control flowing out of the last child and the handlers to successor" do
31
+ cfg = CFG.from_source(
32
+ "#{block_type}; a = 1; b = 1; rescue Err; w = 1; x = 1; rescue; y = 1; end; z = 1"
33
+ )
34
+
35
+ expect(cfg).to contain("a = 1", "b = 1", "w = 1", "x = 1", "y = 1", "z = 1")
36
+
37
+ expect(cfg).to flow_between("b = 1", "z = 1")
38
+ expect(cfg).to flow_between("x = 1", "z = 1")
39
+ expect(cfg).to flow_between("y = 1", "z = 1")
40
+ end
41
+
42
+ it "should show control flowing out from out of last child through an else" do
43
+ cfg = CFG.from_source(
44
+ "#{block_type}; a = 1; b = 1; rescue Err; w = 1; x = 1; else; y = 1; end; z = 1"
45
+ )
46
+
47
+ expect(cfg).to contain("a = 1", "b = 1", "x = 1", "w = 1", "y = 1", "z = 1")
48
+
49
+ expect(cfg).to flow_between("b = 1", "y = 1")
50
+ expect(cfg).to flow_between("y = 1", "z = 1")
51
+
52
+ expect(cfg).not_to flow_between("b = 1", "z = 1")
53
+ end
54
+ end
55
+
56
+ context "for a #{block_type} with an ensure" do
57
+ it "should show control flow from every child to the start of an ensure" do
58
+ cfg = CFG.from_source(
59
+ "#{block_type}; a = 1; b = 1; ensure; z = 1; end; final = 1"
60
+ )
61
+
62
+ expect(cfg).to contain("a = 1", "b = 1", "z = 1", "final = 1")
63
+
64
+ expect(cfg).to flow_between("a = 1", "z = 1")
65
+ expect(cfg).to flow_between("b = 1", "z = 1")
66
+
67
+ expect(cfg).to flow_between("z = 1", "final = 1")
68
+ expect(cfg).not_to flow_between("b = 1", "final = 1")
69
+ end
70
+
71
+ it "should show control flow from every child incl. rescues to the start of an ensure" do
72
+ cfg = CFG.from_source(
73
+ "#{block_type}; a = 1; b = 1;" \
74
+ "rescue Err; v = 1;" \
75
+ "rescue; w = 1; y = 1;" \
76
+ "ensure; z = 1; end;" \
77
+ "final = 1"
78
+ )
79
+
80
+ expect(cfg).to contain("a = 1", "b = 1", "v = 1", "w = 1", "y = 1", "z = 1", "final = 1")
81
+
82
+ expect(cfg).to flow_between("a = 1", "z = 1")
83
+ expect(cfg).to flow_between("b = 1", "z = 1")
84
+ expect(cfg).to flow_between("w = 1", "z = 1")
85
+ expect(cfg).to flow_between("y = 1", "z = 1")
86
+
87
+ expect(cfg).to flow_between("z = 1", "final = 1")
88
+ expect(cfg).not_to flow_between("b = 1", "final = 1")
89
+ expect(cfg).not_to flow_between("v = 1", "final = 1")
90
+ expect(cfg).not_to flow_between("y = 1", "final = 1")
91
+ end
92
+
93
+ it "should show control flow from last child to ensure via else" do
94
+ cfg = CFG.from_source(
95
+ "#{block_type}; a = 1; b = 1;" \
96
+ "rescue; v = 1;" \
97
+ "else; w = 1; y = 1;" \
98
+ "ensure; z = 1; end;" \
99
+ "final = 1"
100
+ )
101
+
102
+ expect(cfg).to contain("a = 1", "b = 1", "v = 1", "w = 1", "y = 1", "z = 1", "final = 1")
103
+
104
+ expect(cfg).to flow_between("b = 1", "w = 1")
105
+ expect(cfg).to flow_between("y = 1", "z = 1")
106
+ expect(cfg).to flow_between("z = 1", "final = 1")
107
+
108
+ # b = 1 could raise, not be rescued and flow directly to ensure
109
+ expect(cfg).to flow_between("b = 1", "z = 1")
110
+ expect(cfg).not_to flow_between("b = 1", "final = 1")
111
+ end
112
+ end
113
+
114
+ context "retrying" do
115
+ it "should return control to the start of the #{block_type}" do
116
+ cfg = CFG.from_source(
117
+ "#{block_type}; a = 1; rescue; retry; end; z = 1"
118
+ )
119
+
120
+ expect(cfg).to contain("a = 1", "retry", "z = 1")
121
+
122
+ expect(cfg).to flow_between("a = 1", "z = 1")
123
+ expect(cfg).to flow_between("a = 1", "retry")
124
+ expect(cfg).to flow_between("retry", "a = 1")
125
+
126
+ expect(cfg).not_to flow_between("retry", "z = 1")
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,90 @@
1
+ require "rdg/cfg"
2
+
3
+ module RDG
4
+ describe CFG do
5
+ context "for simple loop control expressions" do
6
+ it "should show control flowing from break directly to successor" do
7
+ cfg = CFG.from_source("a = 1; while true do; a += 1; break; b += 1; end; z = 1")
8
+
9
+ expect(cfg).to contain("a = 1", "true", "a += 1", "break", "b += 1", "z = 1")
10
+
11
+ expect(cfg).to flow_between("a = 1", "true")
12
+ expect(cfg).to flow_between("true", "a += 1")
13
+ expect(cfg).to flow_between("true", "z = 1")
14
+ expect(cfg).to flow_between("a += 1", "break")
15
+ expect(cfg).to flow_between("break", "z = 1")
16
+ expect(cfg).to flow_between("b += 1", "true")
17
+
18
+ expect(cfg).not_to flow_between("break", "a += 1")
19
+ end
20
+
21
+ it "should show control flowing from nested break only to successor" do
22
+ cfg = CFG.from_source("a = 1; while true do; a += 1; break if false; b += 1; end; z = 1")
23
+
24
+ expect(cfg).to contain("a = 1", "true", "a += 1", "break", "false", "b += 1", "z = 1")
25
+
26
+ expect(cfg).to flow_between("a = 1", "true")
27
+ expect(cfg).to flow_between("true", "a += 1")
28
+ expect(cfg).to flow_between("true", "z = 1")
29
+ expect(cfg).to flow_between("a += 1", "false")
30
+ expect(cfg).to flow_between("false", "break")
31
+ expect(cfg).to flow_between("false", "b += 1")
32
+ expect(cfg).to flow_between("break", "z = 1")
33
+ expect(cfg).to flow_between("b += 1", "true")
34
+
35
+ expect(cfg).not_to flow_between("break", "a += 1")
36
+ end
37
+
38
+ %w(next redo).each do |kind|
39
+ it "should show control flowing from #{kind} directly to test" do
40
+ cfg = CFG.from_source("a = 1; while true do; a += 1; #{kind}; b += 1; end; z = 1")
41
+
42
+ expect(cfg).to contain("a = 1", "true", "a += 1", "#{kind}", "b += 1", "z = 1")
43
+
44
+ expect(cfg).to flow_between("a = 1", "true")
45
+ expect(cfg).to flow_between("true", "a += 1")
46
+ expect(cfg).to flow_between("true", "z = 1")
47
+ expect(cfg).to flow_between("a += 1", "#{kind}")
48
+ expect(cfg).to flow_between("#{kind}", "true")
49
+ expect(cfg).to flow_between("b += 1", "true")
50
+ end
51
+ end
52
+ end
53
+
54
+ context "for nested loop control expressions" do
55
+ it "should show control flowing from break to inner loop's successor" do
56
+ cfg = CFG.from_source("while false do;" \
57
+ "a = 1; while true do; a += 1; break; b += 1; end; z = 1;" \
58
+ "end;" \
59
+ "final = 1")
60
+
61
+ expect(cfg).to flow_between("break", "z = 1")
62
+ expect(cfg).not_to flow_between("break", "final = 1")
63
+ end
64
+
65
+ %w(next redo).each do |kind|
66
+ it "should show control flowing from #{kind} to inner loop's test" do
67
+ cfg = CFG.from_source("while false do;" \
68
+ "a = 1; while true do; a += 1; #{kind}; b += 1; end; z = 1;" \
69
+ "end;" \
70
+ "final = 1")
71
+
72
+ expect(cfg).to flow_between("#{kind}", "true")
73
+ expect(cfg).not_to flow_between("#{kind}", "false")
74
+ end
75
+ end
76
+ end
77
+
78
+ context "for complex loop test" do
79
+ %w(next redo).each do |kind|
80
+ it "should show control flow from #{kind} to children of inner loop's test" do
81
+ cfg = CFG.from_source("a = 1;" \
82
+ "while 42 ? :foo : 'bar' do; a += 1; #{kind}; b += 1; end;" \
83
+ "z = 1;")
84
+
85
+ expect(cfg).to flow_between("#{kind}", "42")
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end