rdg 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b4773e86f8072218af847f45bb3fd7144b17280d
4
- data.tar.gz: bc71864a750e80a115446322711ace2d9a36684c
3
+ metadata.gz: af60440c383c82705747bf477f53b83e1cad635f
4
+ data.tar.gz: e815153ed63da6c542a6da44175131ccce12e613
5
5
  SHA512:
6
- metadata.gz: d6194df5a3f39aab8a8d3819e2ec310a24611c53fa15ffc947edf7fec96aa02fa8b5bbad142b84bb228011b1bc65086ddf9e55ac527ed752f9e08cc571073225
7
- data.tar.gz: 35e161070ace8d798444f3c857328fde3c5eb785989d7182e1b06a56b0c6c124c3190e3cb6b7e3183ed4cc30d1e39507023d8d5d25ea7512346780d5c05a814a
6
+ metadata.gz: a3cf79483cbd687128ea25905ee796f50582f89c5e4d0d7d23a22cf12e22371f4de81d15a0c8ed01ee1d25335410ebdef64abd0253a776f95d574437f799f5d4
7
+ data.tar.gz: 2695d85dd9d28e27c51e91872ee595cf4febc8a16c09246ab08a5c4cb9d187eb9f10dd3cec6e5be22310e2416e0b5cdd6124267c67bcee9a50c619b0b40a031b
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ sample*
19
+ target*
data/.rspec CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
2
  --format progress
3
+ --require spec_helper
data/.rubocop.yml CHANGED
@@ -4,6 +4,8 @@ AllCops:
4
4
  - Rakefile
5
5
  Exclude:
6
6
  - vendor/*
7
+ - sample.rb
8
+ - target.rb
7
9
 
8
10
  StringLiterals:
9
11
  Enabled: false
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,11 @@
1
+ module RDG
2
+ module Control
3
+ class None
4
+ def initialize(_ast_node, _graph, _state)
5
+ end
6
+
7
+ def analyse
8
+ end
9
+ end
10
+ end
11
+ 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
- current_node.children.each do |child|
42
- @graph.add_edge(current_node, import(child))
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
- def initialize(wrapped)
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
- if @wrapped.is_a?(Parser::AST::Node)
54
- @wrapped.children
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
- @wrapped.to_s
94
+ wrapped.to_s
62
95
  end
63
96
  end
64
97
  end
data/lib/rdg/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module RDG
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -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.1
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-02-12 00:00:00.000000000 Z
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