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 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