rdg 0.0.1 → 0.0.2
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/.gitignore +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +2 -0
- data/bin/ast +1 -1
- data/bin/cfg +10 -0
- data/lib/rdg/cfg.rb +76 -0
- data/lib/rdg/control/analyser.rb +44 -0
- data/lib/rdg/control/begin.rb +19 -0
- data/lib/rdg/control/def.rb +15 -0
- data/lib/rdg/control/if.rb +28 -0
- data/lib/rdg/control/none.rb +11 -0
- data/lib/rdg/control/return.rb +14 -0
- data/lib/rdg/control/while.rb +24 -0
- data/lib/rdg/tree/ast.rb +41 -8
- data/lib/rdg/version.rb +1 -1
- data/spec/integration/cfg/if_spec.rb +69 -0
- data/spec/integration/cfg/methods_spec.rb +39 -0
- data/spec/integration/cfg/sequence_spec.rb +40 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/matchers/contain_matcher.rb +22 -0
- data/spec/support/matchers/flow_between_matcher.rb +18 -0
- data/spec/unit/control/analyser_spec.rb +61 -0
- data/spec/unit/control/begin_spec.rb +25 -0
- data/spec/unit/control/def_spec.rb +28 -0
- data/spec/unit/control/if_spec.rb +75 -0
- data/spec/unit/control/return_spec.rb +27 -0
- data/spec/unit/control/while_spec.rb +25 -0
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af60440c383c82705747bf477f53b83e1cad635f
|
4
|
+
data.tar.gz: e815153ed63da6c542a6da44175131ccce12e613
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3cf79483cbd687128ea25905ee796f50582f89c5e4d0d7d23a22cf12e22371f4de81d15a0c8ed01ee1d25335410ebdef64abd0253a776f95d574437f799f5d4
|
7
|
+
data.tar.gz: 2695d85dd9d28e27c51e91872ee595cf4febc8a16c09246ab08a5c4cb9d187eb9f10dd3cec6e5be22310e2416e0b5cdd6124267c67bcee9a50c619b0b40a031b
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.rubocop.yml
CHANGED
data/bin/ast
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
require_relative "../lib/rdg/tree/ast"
|
3
3
|
|
4
4
|
if ARGV.size == 1
|
5
|
-
RDG::Tree::AST.from_path(ARGV[0]).write_to_graphic_file('png', ARGV[0])
|
5
|
+
RDG::Tree::AST.from_path(ARGV[0]).write_to_graphic_file('png', ARGV[0] + ".ast")
|
6
6
|
else
|
7
7
|
puts "ast: expected 1 argument but received #{ARGV.size}"
|
8
8
|
puts "Usage:"
|
data/bin/cfg
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require_relative "../lib/rdg/cfg"
|
3
|
+
|
4
|
+
if ARGV.size == 1
|
5
|
+
RDG::CFG.from_path(ARGV[0]).write_to_graphic_file('png', ARGV[0] + ".cfg")
|
6
|
+
else
|
7
|
+
puts "cfg: expected 1 argument but received #{ARGV.size}"
|
8
|
+
puts "Usage:"
|
9
|
+
puts " cfg path/to/source.rb"
|
10
|
+
end
|
data/lib/rdg/cfg.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require_relative "tree/ast"
|
2
|
+
require_relative "control/def"
|
3
|
+
require_relative "control/begin"
|
4
|
+
require_relative "control/if"
|
5
|
+
require_relative "control/while"
|
6
|
+
require_relative "control/return"
|
7
|
+
require_relative "control/none"
|
8
|
+
|
9
|
+
module RDG
|
10
|
+
class CFG
|
11
|
+
def self.from_path(path)
|
12
|
+
new(Tree::AST.from_path(path))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_source(source)
|
16
|
+
new(Tree::AST.from_source(source))
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(ast)
|
20
|
+
@graph = BiDiDirectedAdjacencyGraph.new
|
21
|
+
analyse(ast)
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_to_graphic_file(format = 'png', filename = "cfg")
|
25
|
+
@graph.write_to_graphic_file(format, filename)
|
26
|
+
end
|
27
|
+
|
28
|
+
def vertices
|
29
|
+
@graph.each_vertex.to_a
|
30
|
+
end
|
31
|
+
|
32
|
+
def successors(v)
|
33
|
+
@graph.each_adjacent(v).to_a
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_edge?(u, v)
|
37
|
+
@graph.has_edge?(u, v)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def analyse(ast)
|
43
|
+
state = {}
|
44
|
+
ast.pre_order_iterator.select(&:compound?).each do |ast_node|
|
45
|
+
analyser_for(ast_node.type).new(ast_node, @graph, state).analyse
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def analyser_for(ast_node_type)
|
50
|
+
case ast_node_type
|
51
|
+
when :def
|
52
|
+
Control::Def
|
53
|
+
when :begin, :kwbegin
|
54
|
+
Control::Begin
|
55
|
+
when :if
|
56
|
+
Control::If
|
57
|
+
when :while
|
58
|
+
Control::While
|
59
|
+
when :return
|
60
|
+
Control::Return
|
61
|
+
else
|
62
|
+
Control::None
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class BiDiDirectedAdjacencyGraph < ::RGL::DirectedAdjacencyGraph
|
67
|
+
def each_predecessor(vertex, &block)
|
68
|
+
each_vertex.select { |v| each_adjacent(v).include?(vertex) }.each(&block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def each_successor(vertex, &block)
|
72
|
+
each_adjacent(vertex, &block) if has_vertex?(vertex)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RDG
|
2
|
+
module Control
|
3
|
+
class Analyser
|
4
|
+
def initialize(ast_node, graph, state)
|
5
|
+
@ast_node, @graph, @state = ast_node, graph, state
|
6
|
+
end
|
7
|
+
|
8
|
+
def analyse
|
9
|
+
add_internal_flow_edges
|
10
|
+
propogate_incoming_flow
|
11
|
+
propogate_outgoing_flow
|
12
|
+
remove_non_flow_vertices
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def children
|
18
|
+
@ast_node.children
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_internal_flow_edges
|
22
|
+
internal_flow_edges.each { |s, t| @graph.add_edge(s, t) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def propogate_incoming_flow
|
26
|
+
@graph.each_predecessor(@ast_node) do |predecessor|
|
27
|
+
@graph.remove_edge(predecessor, @ast_node)
|
28
|
+
start_nodes.each { |n| @graph.add_edge(predecessor, n) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def propogate_outgoing_flow
|
33
|
+
@graph.each_successor(@ast_node) do |successor|
|
34
|
+
@graph.remove_edge(@ast_node, successor)
|
35
|
+
end_nodes.each { |n| @graph.add_edge(n, successor) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def remove_non_flow_vertices
|
40
|
+
@graph.remove_vertex(@ast_node)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative "analyser"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
class Begin < Analyser
|
6
|
+
def internal_flow_edges
|
7
|
+
children.each_cons(2).to_a
|
8
|
+
end
|
9
|
+
|
10
|
+
def start_nodes
|
11
|
+
children.first(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
def end_nodes
|
15
|
+
children.last(1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module RDG
|
2
|
+
module Control
|
3
|
+
class Def
|
4
|
+
def initialize(ast_node, graph, state)
|
5
|
+
@graph, @state = graph, state
|
6
|
+
@name, @args, @body = ast_node.children
|
7
|
+
end
|
8
|
+
|
9
|
+
def analyse
|
10
|
+
@state[:current_method] = @name
|
11
|
+
@graph.add_edge(@name, @body)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative "analyser"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
class If < Analyser
|
6
|
+
def initialize(ast_node, graph, state)
|
7
|
+
super(ast_node, graph, state)
|
8
|
+
@predicate, *@consequences = children.reject(&:empty?)
|
9
|
+
end
|
10
|
+
|
11
|
+
def internal_flow_edges
|
12
|
+
@consequences.map { |consequence| [@predicate, consequence] }
|
13
|
+
end
|
14
|
+
|
15
|
+
def start_nodes
|
16
|
+
[@predicate]
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_nodes
|
20
|
+
if @consequences.size == 1
|
21
|
+
[@predicate, @consequences.first]
|
22
|
+
else
|
23
|
+
@consequences
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RDG
|
2
|
+
module Control
|
3
|
+
class Return
|
4
|
+
def initialize(ast_node, graph, state)
|
5
|
+
@graph, @ast_node, @state = graph, ast_node, state
|
6
|
+
end
|
7
|
+
|
8
|
+
def analyse
|
9
|
+
return unless @state.key?(:current_method)
|
10
|
+
@graph.each_successor(@ast_node) { |s| @graph.remove_edge(@ast_node, s) }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "analyser"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
class While < Analyser
|
6
|
+
def initialize(ast_node, graph, state)
|
7
|
+
super(ast_node, graph, state)
|
8
|
+
@predicate, @body = children
|
9
|
+
end
|
10
|
+
|
11
|
+
def internal_flow_edges
|
12
|
+
[[@predicate, @body], [@body, @predicate]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def start_nodes
|
16
|
+
[@predicate]
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_nodes
|
20
|
+
[@body]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/rdg/tree/ast.rb
CHANGED
@@ -20,6 +20,10 @@ module RDG
|
|
20
20
|
import(ast)
|
21
21
|
end
|
22
22
|
|
23
|
+
def root
|
24
|
+
@graph.each_vertex.first
|
25
|
+
end
|
26
|
+
|
23
27
|
def pre_order_iterator
|
24
28
|
RGL::PreOrderIterator.new(@graph)
|
25
29
|
end
|
@@ -35,30 +39,59 @@ module RDG
|
|
35
39
|
private
|
36
40
|
|
37
41
|
def import(ast)
|
38
|
-
Node.new(ast).tap do |current_node|
|
42
|
+
Node.new(ast, @graph).tap do |current_node|
|
39
43
|
@graph.add_vertex(current_node)
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
if ast.respond_to?(:children)
|
46
|
+
ast.children.each do |child|
|
47
|
+
@graph.add_edge(current_node, import(child))
|
48
|
+
end
|
43
49
|
end
|
44
50
|
end
|
45
51
|
end
|
46
52
|
|
47
53
|
class Node
|
48
|
-
|
54
|
+
attr_accessor :wrapped
|
55
|
+
|
56
|
+
def initialize(wrapped, graph)
|
49
57
|
@wrapped = wrapped
|
58
|
+
@graph = graph
|
59
|
+
end
|
60
|
+
|
61
|
+
def compound?
|
62
|
+
wrapped.is_a?(Parser::AST::Node)
|
63
|
+
end
|
64
|
+
|
65
|
+
def scalar?
|
66
|
+
!compound?
|
67
|
+
end
|
68
|
+
|
69
|
+
def empty?
|
70
|
+
wrapped.nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def type
|
74
|
+
compound? ? wrapped.type : nil
|
50
75
|
end
|
51
76
|
|
52
77
|
def children
|
53
|
-
|
54
|
-
|
78
|
+
@graph.each_adjacent(self).to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
def ==(other)
|
82
|
+
if scalar?
|
83
|
+
wrapped == other.wrapped
|
55
84
|
else
|
56
|
-
|
85
|
+
type == other.type && children == other.children
|
57
86
|
end
|
58
87
|
end
|
59
88
|
|
89
|
+
def inspect
|
90
|
+
to_s
|
91
|
+
end
|
92
|
+
|
60
93
|
def to_s
|
61
|
-
|
94
|
+
wrapped.to_s
|
62
95
|
end
|
63
96
|
end
|
64
97
|
end
|
data/lib/rdg/version.rb
CHANGED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "rdg/cfg"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
describe CFG do
|
5
|
+
context "for simple if expressions" do
|
6
|
+
it "should show control flowing from predicate to successor (skipping consequence)" do
|
7
|
+
cfg = CFG.from_source("a = 1; if 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
|
+
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
|
+
expect(cfg).to flow_between("b == 1", "c = 1")
|
22
|
+
expect(cfg).to flow_between("c = 1", "z = 1")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should show control flowing into predicate and through longer consequence" do
|
26
|
+
cfg = CFG.from_source("a = 1; if b == 1 then; c = 1; d = 1; e = 1; end; z = 1")
|
27
|
+
|
28
|
+
expect(cfg).to contain("a = 1", "b == 1", "c = 1", "d = 1", "e = 1", "z = 1")
|
29
|
+
|
30
|
+
expect(cfg).to flow_between("a = 1", "b == 1")
|
31
|
+
expect(cfg).to flow_between("b == 1", "c = 1")
|
32
|
+
expect(cfg).to flow_between("c = 1", "d = 1")
|
33
|
+
expect(cfg).to flow_between("d = 1", "e = 1")
|
34
|
+
expect(cfg).to flow_between("e = 1", "z = 1")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "for if expressions with several consequences" do
|
39
|
+
it "should show control flowing from predicate to (and out of) all consequences" do
|
40
|
+
cfg = CFG.from_source("a = 1; if b == 1 then; c = 1; else; d = 1; end; z = 1")
|
41
|
+
|
42
|
+
expect(cfg).to contain("a = 1", "b == 1", "c = 1", "d = 1", "z = 1")
|
43
|
+
|
44
|
+
expect(cfg).to flow_between("a = 1", "b == 1")
|
45
|
+
expect(cfg).to flow_between("b == 1", "c = 1")
|
46
|
+
expect(cfg).to flow_between("b == 1", "d = 1")
|
47
|
+
expect(cfg).to flow_between("c = 1", "z = 1")
|
48
|
+
expect(cfg).to flow_between("d = 1", "z = 1")
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should show control flowing over each condition and its consequences" do
|
52
|
+
cfg = CFG.from_source("a = 1;" \
|
53
|
+
"if b == 1 then; c = 1; elsif b == 2; d = 1; else; e = 1; end;" \
|
54
|
+
"z = 1")
|
55
|
+
|
56
|
+
expect(cfg).to contain("a = 1", "b == 1", "c = 1", "b == 2", "d = 1", "e = 1", "z = 1")
|
57
|
+
|
58
|
+
expect(cfg).to flow_between("a = 1", "b == 1")
|
59
|
+
expect(cfg).to flow_between("b == 1", "c = 1")
|
60
|
+
expect(cfg).to flow_between("b == 1", "b == 2")
|
61
|
+
expect(cfg).to flow_between("b == 2", "d = 1")
|
62
|
+
expect(cfg).to flow_between("b == 2", "e = 1")
|
63
|
+
expect(cfg).to flow_between("c = 1", "z = 1")
|
64
|
+
expect(cfg).to flow_between("d = 1", "z = 1")
|
65
|
+
expect(cfg).to flow_between("e = 1", "z = 1")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "rdg/cfg"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
describe CFG do
|
5
|
+
context "for a simple method" do
|
6
|
+
it "should show control flowing from the method name to the body" do
|
7
|
+
cfg = CFG.from_source("def foo; a = 1; a = 2; end")
|
8
|
+
name, first, _ = cfg.vertices
|
9
|
+
|
10
|
+
expect(cfg.vertices.size).to eq(3)
|
11
|
+
expect(cfg.has_edge?(name, first)).to be_truthy
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "for a method containing an early return" do
|
16
|
+
before(:all) do
|
17
|
+
@cfg = CFG.from_source("def foo; a = 1; if a == 2; return; end; a = 2; end")
|
18
|
+
@name, @first, _, _, @ret = @cfg.vertices
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should show control flowing from the method name to the body" do
|
22
|
+
expect(@cfg.has_edge?(@name, @first)).to be_truthy
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not show control flowing from return to successor" do
|
26
|
+
expect(@cfg.has_edge?(@ret, @second)).to be_falsey
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "return from no method" do
|
31
|
+
it "should do nothing" do
|
32
|
+
cfg = CFG.from_source("return")
|
33
|
+
|
34
|
+
puts cfg.vertices
|
35
|
+
expect(cfg.vertices.empty?).to be_truthy
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "rdg/cfg"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
describe CFG do
|
5
|
+
context "for a sequence of statements" do
|
6
|
+
xit "should show no control flow for a lone element" do
|
7
|
+
cfg = CFG.from_source("a = 1")
|
8
|
+
|
9
|
+
expect(cfg).to contain("a = 1")
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should show control flowing between children in order" do
|
13
|
+
cfg = CFG.from_source("a = 1; b = 2; c = 3")
|
14
|
+
|
15
|
+
expect(cfg).to contain("a = 1", "b = 2", "c = 3")
|
16
|
+
|
17
|
+
expect(cfg).to flow_between("a = 1", "b = 2")
|
18
|
+
expect(cfg).to flow_between("b = 2", "c = 3")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should show control flowing into the first child" do
|
22
|
+
cfg = CFG.from_source("a = 1; begin; b = 1; b = 2; end")
|
23
|
+
|
24
|
+
expect(cfg).to contain("a = 1", "b = 1", "b = 2")
|
25
|
+
|
26
|
+
expect(cfg).to flow_between("a = 1", "b = 1")
|
27
|
+
expect(cfg).to flow_between("b = 1", "b = 2")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should show control flowing out of the last child" do
|
31
|
+
cfg = CFG.from_source("begin; b = 1; b = 2; end; b = 3")
|
32
|
+
|
33
|
+
expect(cfg).to contain("b = 1", "b = 2", "b = 3")
|
34
|
+
|
35
|
+
expect(cfg).to flow_between("b = 1", "b = 2")
|
36
|
+
expect(cfg).to flow_between("b = 2", "b = 3")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "codeclimate-test-reporter"
|
2
2
|
CodeClimate::TestReporter.start
|
3
3
|
|
4
|
+
Dir["./spec/support/**/*.rb"].each { |f| require f }
|
5
|
+
|
4
6
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
5
7
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
6
8
|
# Require this file using `require "spec_helper"` to ensure that it is only
|
@@ -8,7 +10,6 @@ CodeClimate::TestReporter.start
|
|
8
10
|
#
|
9
11
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
10
12
|
RSpec.configure do |config|
|
11
|
-
config.treat_symbols_as_metadata_keys_with_true_values = true
|
12
13
|
config.run_all_when_everything_filtered = true
|
13
14
|
config.filter_run :focus
|
14
15
|
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec::Matchers.define :contain do |*sources|
|
2
|
+
match do |graph|
|
3
|
+
expected = expected_vertices(sources)
|
4
|
+
actual = graph.vertices
|
5
|
+
|
6
|
+
equivalent_ignoring_order(actual, expected)
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |graph|
|
10
|
+
"expected graph to contain the vertices #{expected_vertices(sources)} " \
|
11
|
+
"but got #{graph.vertices}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def expected_vertices(sources)
|
15
|
+
sources.map { |s| RDG::Tree::AST.from_source(s).root }
|
16
|
+
end
|
17
|
+
|
18
|
+
def equivalent_ignoring_order(first_array, second_array)
|
19
|
+
first_array.size == second_array.size &&
|
20
|
+
first_array.all? { |e| second_array.include?(e) }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
RSpec::Matchers.define :flow_between do |source_of_start, source_of_end|
|
2
|
+
match do |graph|
|
3
|
+
graph.has_edge?(
|
4
|
+
find_vertex(graph, source_of_start),
|
5
|
+
find_vertex(graph, source_of_end)
|
6
|
+
)
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message do |graph|
|
10
|
+
"expected to find an edge from #{source_of_start} to #{source_of_end} " \
|
11
|
+
"but from #{source_of_start} the only edges are: " \
|
12
|
+
"#{graph.successors(find_vertex(graph, source_of_start))}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_vertex(graph, source)
|
16
|
+
graph.vertices.detect { |v| v == RDG::Tree::AST.from_source(source).root }
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "rdg/control/analyser"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe Analyser do
|
6
|
+
let(:ast) { double("ast") }
|
7
|
+
let(:graph) { spy("graph") }
|
8
|
+
|
9
|
+
subject do
|
10
|
+
class DummyAnalyser < Analyser
|
11
|
+
def internal_flow_edges
|
12
|
+
[[:s1, :e1], [:s2, :e2]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def start_nodes
|
16
|
+
[:s1, :s2]
|
17
|
+
end
|
18
|
+
|
19
|
+
def end_nodes
|
20
|
+
[:e1, :e2]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
DummyAnalyser.new(ast, graph, nil)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should add a CFG edge for every internal flow edge" do
|
28
|
+
subject.analyse
|
29
|
+
|
30
|
+
expect(graph).to have_received(:add_edge).with(:s1, :e1)
|
31
|
+
expect(graph).to have_received(:add_edge).with(:s2, :e2)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should move any incoming edges to start nodes" do
|
35
|
+
allow(graph).to receive(:each_predecessor).and_yield(:predecessor)
|
36
|
+
|
37
|
+
subject.analyse
|
38
|
+
|
39
|
+
expect(graph).to have_received(:remove_edge).with(:predecessor, ast)
|
40
|
+
expect(graph).to have_received(:add_edge).with(:predecessor, :s1)
|
41
|
+
expect(graph).to have_received(:add_edge).with(:predecessor, :s2)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should move any outgoing edges to end nodes" do
|
45
|
+
allow(graph).to receive(:each_successor).and_yield(:successor)
|
46
|
+
|
47
|
+
subject.analyse
|
48
|
+
|
49
|
+
expect(graph).to have_received(:remove_edge).with(ast, :successor)
|
50
|
+
expect(graph).to have_received(:add_edge).with(:e1, :successor)
|
51
|
+
expect(graph).to have_received(:add_edge).with(:e2, :successor)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should remove the AST node from the CFG" do
|
55
|
+
subject.analyse
|
56
|
+
|
57
|
+
expect(graph).to have_received(:remove_vertex).with(ast)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rdg/control/begin"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
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
|
11
|
+
|
12
|
+
it "should have control flow start at the first child" do
|
13
|
+
expect(subject.start_nodes).to eq([1])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have control flow end at the last child" do
|
17
|
+
expect(subject.end_nodes).to eq([3])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have control flow edges between each pair of children" do
|
21
|
+
expect(subject.internal_flow_edges).to eq([[1, 2], [2, 3]])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "rdg/control/def"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe Def do
|
6
|
+
let(:state) { Hash.new }
|
7
|
+
let(:graph) { spy("graph") }
|
8
|
+
subject do
|
9
|
+
ast = double("ast")
|
10
|
+
allow(ast).to receive(:children) { [:name, :args, :body] }
|
11
|
+
Def.new(ast, graph, state)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should have control flow from name to body" do
|
15
|
+
subject.analyse
|
16
|
+
|
17
|
+
expect(graph).to have_received(:add_edge).with(:name, :body)
|
18
|
+
expect(graph).to have_received(:add_edge).once
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have updated state with name node" do
|
22
|
+
subject.analyse
|
23
|
+
|
24
|
+
expect(state[:current_method]).to eq(:name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "rdg/control/if"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe If do
|
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
|
12
|
+
|
13
|
+
it "should have control flow start at the predicate" do
|
14
|
+
expect(subject.start_nodes).to eq([:predicate])
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have control flow end at the predicate and the consequence" do
|
18
|
+
expect(subject.end_nodes).to eq([:predicate, :consequence])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have control flow edge between predicate and consequence" do
|
22
|
+
expect(subject.internal_flow_edges).to eq([[:predicate, :consequence]])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
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)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have control flow start at the predicate" do
|
34
|
+
expect(subject.start_nodes).to eq([:predicate])
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should have control flow end at the consequences" do
|
38
|
+
expect(subject.end_nodes).to eq([:consequence, :alternative])
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should have control flow edge between predicate and consequences" do
|
42
|
+
expect(subject.internal_flow_edges).to eq([
|
43
|
+
[:predicate, :consequence],
|
44
|
+
[:predicate, :alternative]
|
45
|
+
])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
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
|
55
|
+
|
56
|
+
it "should have control flow start at the predicate" do
|
57
|
+
expect(subject.start_nodes).to eq([:predicate])
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should have control flow end at the consequences" do
|
61
|
+
expect(subject.end_nodes).to eq([:consequence, :a1, :a2, :a3])
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should have control flow edge between predicate and all consequences" do
|
65
|
+
expect(subject.internal_flow_edges).to eq([
|
66
|
+
[:predicate, :consequence],
|
67
|
+
[:predicate, :a1],
|
68
|
+
[:predicate, :a2],
|
69
|
+
[:predicate, :a3]
|
70
|
+
])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "rdg/control/return"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe Return do
|
6
|
+
let(:graph) { spy("graph") }
|
7
|
+
let(:state) { { current_method: :do_important_stuff } }
|
8
|
+
subject { Return.new(:return, graph, state) }
|
9
|
+
|
10
|
+
it "should remove existing edges out of return node" do
|
11
|
+
allow(graph).to receive(:each_successor).and_yield(:successor)
|
12
|
+
|
13
|
+
subject.analyse
|
14
|
+
|
15
|
+
expect(graph).to have_received(:remove_edge).with(:return, :successor)
|
16
|
+
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
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rdg/control/while"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Control
|
5
|
+
describe While do
|
6
|
+
subject do
|
7
|
+
ast = double("ast")
|
8
|
+
allow(ast).to receive(:children) { [:predicate, :body] }
|
9
|
+
While.new(ast, nil, nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should have control flow start at the predicate" do
|
13
|
+
expect(subject.start_nodes).to eq([:predicate])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should have control flow end at the body" do
|
17
|
+
expect(subject.end_nodes).to eq([:body])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have control flow edges between predicate and body, and vice-versa" do
|
21
|
+
expect(subject.internal_flow_edges).to eq([[:predicate, :body], [:body, :predicate]])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rdg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Louis Rose
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -114,6 +114,7 @@ email:
|
|
114
114
|
- louis.rose@york.ac.uk
|
115
115
|
executables:
|
116
116
|
- ast
|
117
|
+
- cfg
|
117
118
|
extensions: []
|
118
119
|
extra_rdoc_files: []
|
119
120
|
files:
|
@@ -126,12 +127,32 @@ files:
|
|
126
127
|
- README.md
|
127
128
|
- Rakefile
|
128
129
|
- bin/ast
|
130
|
+
- bin/cfg
|
131
|
+
- lib/rdg/cfg.rb
|
132
|
+
- lib/rdg/control/analyser.rb
|
133
|
+
- lib/rdg/control/begin.rb
|
134
|
+
- lib/rdg/control/def.rb
|
135
|
+
- lib/rdg/control/if.rb
|
136
|
+
- lib/rdg/control/none.rb
|
137
|
+
- lib/rdg/control/return.rb
|
138
|
+
- lib/rdg/control/while.rb
|
129
139
|
- lib/rdg/tree/ast.rb
|
130
140
|
- lib/rdg/tree/rgl/post_order_iterator.rb
|
131
141
|
- lib/rdg/tree/rgl/pre_order_iterator.rb
|
132
142
|
- lib/rdg/version.rb
|
133
143
|
- rdg.gemspec
|
144
|
+
- spec/integration/cfg/if_spec.rb
|
145
|
+
- spec/integration/cfg/methods_spec.rb
|
146
|
+
- spec/integration/cfg/sequence_spec.rb
|
134
147
|
- spec/spec_helper.rb
|
148
|
+
- spec/support/matchers/contain_matcher.rb
|
149
|
+
- spec/support/matchers/flow_between_matcher.rb
|
150
|
+
- spec/unit/control/analyser_spec.rb
|
151
|
+
- spec/unit/control/begin_spec.rb
|
152
|
+
- spec/unit/control/def_spec.rb
|
153
|
+
- spec/unit/control/if_spec.rb
|
154
|
+
- spec/unit/control/return_spec.rb
|
155
|
+
- spec/unit/control/while_spec.rb
|
135
156
|
- spec/unit/tree/ast_spec.rb
|
136
157
|
- spec/unit/tree/rgl/post_order_iterator_spec.rb
|
137
158
|
- spec/unit/tree/rgl/pre_order_iterator_spec.rb
|
@@ -160,7 +181,18 @@ signing_key:
|
|
160
181
|
specification_version: 4
|
161
182
|
summary: Dependency analysis for Ruby programs
|
162
183
|
test_files:
|
184
|
+
- spec/integration/cfg/if_spec.rb
|
185
|
+
- spec/integration/cfg/methods_spec.rb
|
186
|
+
- spec/integration/cfg/sequence_spec.rb
|
163
187
|
- spec/spec_helper.rb
|
188
|
+
- spec/support/matchers/contain_matcher.rb
|
189
|
+
- spec/support/matchers/flow_between_matcher.rb
|
190
|
+
- spec/unit/control/analyser_spec.rb
|
191
|
+
- spec/unit/control/begin_spec.rb
|
192
|
+
- spec/unit/control/def_spec.rb
|
193
|
+
- spec/unit/control/if_spec.rb
|
194
|
+
- spec/unit/control/return_spec.rb
|
195
|
+
- spec/unit/control/while_spec.rb
|
164
196
|
- spec/unit/tree/ast_spec.rb
|
165
197
|
- spec/unit/tree/rgl/post_order_iterator_spec.rb
|
166
198
|
- spec/unit/tree/rgl/pre_order_iterator_spec.rb
|