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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -2
- data/Gemfile +1 -1
- data/RELEASES.md +10 -0
- data/TODO.md +68 -0
- data/bin/ast +5 -4
- data/bin/cfg +5 -4
- data/lib/rdg/analysis/analyser.rb +30 -0
- data/lib/rdg/analysis/composite.rb +23 -0
- data/lib/rdg/analysis/context.rb +17 -0
- data/lib/rdg/analysis/equivalences.rb +25 -0
- data/lib/rdg/analysis/propagater.rb +51 -0
- data/lib/rdg/analysis/registry.rb +35 -0
- data/lib/rdg/cfg.rb +12 -41
- data/lib/rdg/control/begin.rb +6 -4
- data/lib/rdg/control/break.rb +19 -0
- data/lib/rdg/control/case.rb +25 -0
- data/lib/rdg/control/{while.rb → conditional_loop.rb} +8 -7
- data/lib/rdg/control/def.rb +21 -7
- data/lib/rdg/control/ensure.rb +30 -0
- data/lib/rdg/control/for.rb +29 -0
- data/lib/rdg/control/handler.rb +25 -0
- data/lib/rdg/control/if.rb +7 -6
- data/lib/rdg/control/jump.rb +33 -0
- data/lib/rdg/control/jump_to_start.rb +11 -0
- data/lib/rdg/control/next.rb +9 -0
- data/lib/rdg/control/none.rb +5 -3
- data/lib/rdg/control/redo.rb +9 -0
- data/lib/rdg/control/rescue.rb +31 -0
- data/lib/rdg/control/rescue_body.rb +29 -0
- data/lib/rdg/control/retry.rb +13 -0
- data/lib/rdg/control/return.rb +5 -6
- data/lib/rdg/control/when.rb +27 -0
- data/lib/rdg/graph/bidirected_adjacency_graph.rb +19 -0
- data/lib/rdg/graph/rgl/allow_duplicates.rb +45 -0
- data/lib/rdg/tree/ast.rb +15 -4
- data/lib/rdg/version.rb +1 -1
- data/rdg.gemspec +3 -2
- data/spec/integration/cfg/conditionals/case_spec.rb +66 -0
- data/spec/integration/cfg/{if_spec.rb → conditionals/if_spec.rb} +24 -9
- data/spec/integration/cfg/conditionals/unless_spec.rb +53 -0
- data/spec/integration/cfg/exceptions_spec.rb +131 -0
- data/spec/integration/cfg/loops/loop_control_spec.rb +90 -0
- data/spec/integration/cfg/loops/loop_spec.rb +70 -0
- data/spec/integration/cfg/sequence_spec.rb +7 -1
- data/spec/support/doubles/fake_ast.rb +15 -0
- data/spec/support/matchers/flow_between_matcher.rb +1 -1
- data/spec/unit/analysis/composite_spec.rb +47 -0
- data/spec/unit/analysis/equivalences_spec.rb +29 -0
- data/spec/unit/{control/analyser_spec.rb → analysis/propagater_spec.rb} +23 -12
- data/spec/unit/analysis/registry_spec.rb +61 -0
- data/spec/unit/control/begin_spec.rb +3 -6
- data/spec/unit/control/break_spec.rb +26 -0
- data/spec/unit/control/case_spec.rb +66 -0
- data/spec/unit/control/conditional_loop_spec.rb +22 -0
- data/spec/unit/control/ensure_spec.rb +33 -0
- data/spec/unit/control/for_spec.rb +26 -0
- data/spec/unit/control/handler_spec.rb +27 -0
- data/spec/unit/control/if_spec.rb +26 -18
- data/spec/unit/control/jump_spec.rb +43 -0
- data/spec/unit/control/jump_to_start_spec.rb +22 -0
- data/spec/unit/control/rescue_body_spec.rb +26 -0
- data/spec/unit/control/rescue_spec.rb +62 -0
- data/spec/unit/control/return_spec.rb +1 -10
- data/spec/unit/control/when_spec.rb +59 -0
- data/spec/unit/graph/bidirected_adjacency_graph_spec.rb +49 -0
- metadata +91 -18
- data/lib/rdg/control/analyser.rb +0 -44
- data/spec/integration/cfg/methods_spec.rb +0 -39
- data/spec/unit/control/def_spec.rb +0 -28
- data/spec/unit/control/while_spec.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3267ca615757cdfe8bb5552efafc08f53ce883c7
|
4
|
+
data.tar.gz: e91b39981bfb05cb0737e372c05d26f18cedf738
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdd71c1d0de2ce1f8f6223177a690fe5f91d70b0a23bbd469af41b4a236ff0a4636ff85d6eae3fd60219f570f9dd036d728d07fe78c3a348aa3fbad7ba13bbd2
|
7
|
+
data.tar.gz: 819759b62d7dd041329595424b654b8772310ac086ad732664bb1bcb64c3a5f50d39f767bf5d17fbffdbd24a428e49863e85947f4abe2c19744ee02f02157ad7
|
data/.rubocop.yml
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.2
|
data/.travis.yml
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.2.
|
3
|
+
- 2.2.2
|
4
4
|
before_install:
|
5
5
|
- gem update bundler
|
6
6
|
addons:
|
7
7
|
code_climate:
|
8
|
-
repo_token: 437cae623819bbf323bd0dbc9e2b857149c9f725841f3825ca55e09f564d8e0a
|
8
|
+
repo_token: 437cae623819bbf323bd0dbc9e2b857149c9f725841f3825ca55e09f564d8e0a
|
data/Gemfile
CHANGED
data/RELEASES.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Release History
|
2
|
+
|
3
|
+
## v0.1.0 (April 2015)
|
4
|
+
* Complete implementation of intra-procedural control flow graphs.
|
5
|
+
|
6
|
+
## v0.0.2 (March 2015)
|
7
|
+
* Provide initial partial implementation of intra-procedural control flow graphs.
|
8
|
+
|
9
|
+
## v0.0.1 (February 2015)
|
10
|
+
* Provide basic support for importing abstract syntax trees and iterating over them.
|
data/TODO.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
## Control Flow Graphs
|
2
|
+
|
3
|
+
### Basics
|
4
|
+
- [x] `begin` ... `end`
|
5
|
+
- [x] `def` ... `end`
|
6
|
+
|
7
|
+
### Conditionals
|
8
|
+
|
9
|
+
#### `if` expressions
|
10
|
+
- [x] no `else`
|
11
|
+
- [x] `else`
|
12
|
+
- [x] `elsif`
|
13
|
+
- [x] ternary `a ? b : c`
|
14
|
+
- [x] expression modifier `if`
|
15
|
+
|
16
|
+
#### `unless` expressions
|
17
|
+
- [x] no else
|
18
|
+
- [x] else
|
19
|
+
- [x] expression modifier `unless`
|
20
|
+
|
21
|
+
#### `case` expressions
|
22
|
+
- [x] single when
|
23
|
+
- [x] else
|
24
|
+
- [x] several when
|
25
|
+
|
26
|
+
### Loops
|
27
|
+
|
28
|
+
#### Looping expressions
|
29
|
+
- [x] `while`
|
30
|
+
- [x] `until`
|
31
|
+
- [x] `for`
|
32
|
+
- [x] `while` and `until` as modifiers
|
33
|
+
|
34
|
+
#### Skipping
|
35
|
+
- [x] `break`
|
36
|
+
- [x] `next`
|
37
|
+
- [x] `redo`
|
38
|
+
|
39
|
+
### Exceptions
|
40
|
+
- [x] `begin` / `rescue`
|
41
|
+
- [x] def / `rescue`
|
42
|
+
- [x] multiple `rescues`
|
43
|
+
- [x] `retry`
|
44
|
+
- [x] `ensure` part
|
45
|
+
- [x] `ensure` part with rescues
|
46
|
+
- [x] `ensure` part with rescues and else
|
47
|
+
- [x] `else` part
|
48
|
+
|
49
|
+
Exception control flow might be neater if there was some notion of hierarchy in the CFG. Right now, every statement within the rescuable block has a control flow edge to each of the exception handlers. It would be neater to have a single control flow edge from some kind of "parent" node which contains each of the statements in the rescuable block.
|
50
|
+
|
51
|
+
### Blocks
|
52
|
+
|
53
|
+
Not too sure how to approach this yet.
|
54
|
+
|
55
|
+
It might be reasonable to assume that any method that takes a block could cause control to flow into that block. If we have the source for that method, we could also check for yield or a call before adding this control flow edge.
|
56
|
+
|
57
|
+
No matter what, blocks support the same skipping constructs as loops:
|
58
|
+
|
59
|
+
- [ ] `break`
|
60
|
+
- [ ] `next`
|
61
|
+
- [ ] `redo`
|
62
|
+
|
63
|
+
## Inter-method Control Flow Graphs
|
64
|
+
|
65
|
+
- [ ] `return` expressions
|
66
|
+
- [ ] direct method invocations
|
67
|
+
- [ ] method invocations via `send` ?
|
68
|
+
- [ ] ensure control flow edges from a method invocation to a block only added when callee yields to block
|
data/bin/ast
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require_relative "../lib/rdg/tree/ast"
|
3
3
|
|
4
|
-
if ARGV.size == 1
|
5
|
-
|
4
|
+
if ARGV.size == 1 || ARGV.size == 2
|
5
|
+
image_format = ARGV.size == 2 ? ARGV[1] : 'pdf'
|
6
|
+
RDG::Tree::AST.from_path(ARGV[0]).write_to_graphic_file(image_format, ARGV[0] + ".ast")
|
6
7
|
else
|
7
|
-
puts "ast: expected 1
|
8
|
+
puts "ast: expected 1 or 2 arguments but received #{ARGV.size}"
|
8
9
|
puts "Usage:"
|
9
|
-
puts " ast path/to/source.rb"
|
10
|
+
puts " ast path/to/source.rb [image_format]"
|
10
11
|
end
|
data/bin/cfg
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require_relative "../lib/rdg/cfg"
|
3
3
|
|
4
|
-
if ARGV.size == 1
|
5
|
-
|
4
|
+
if ARGV.size == 1 || ARGV.size == 2
|
5
|
+
image_format = ARGV.size == 2 ? ARGV[1] : 'pdf'
|
6
|
+
RDG::CFG.from_path(ARGV[0]).write_to_graphic_file(image_format, ARGV[0] + ".cfg")
|
6
7
|
else
|
7
|
-
puts "cfg: expected 1
|
8
|
+
puts "cfg: expected 1 or 2 arguments but received #{ARGV.size}"
|
8
9
|
puts "Usage:"
|
9
|
-
puts " cfg path/to/source.rb"
|
10
|
+
puts " cfg path/to/source.rb [image_format]"
|
10
11
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative "context"
|
2
|
+
require_relative "registry"
|
3
|
+
|
4
|
+
module RDG
|
5
|
+
module Analysis
|
6
|
+
class Analyser
|
7
|
+
extend Forwardable
|
8
|
+
def_delegators :@context, :graph, :equivalences, :registry
|
9
|
+
|
10
|
+
def self.register_analyser(*types)
|
11
|
+
Registry.register_by_type(self, *types)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.register_default_analyser
|
15
|
+
Registry.register_default(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(ast_node, context = Context.new)
|
19
|
+
@ast_node, @context = ast_node, context
|
20
|
+
prepare
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def prepare
|
26
|
+
# do nothing
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "analyser"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Analysis
|
5
|
+
class Composite < Analyser
|
6
|
+
def self.compose(*ts)
|
7
|
+
Class.new(Composite) do
|
8
|
+
define_method :types do
|
9
|
+
ts
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(ast_node, context = Context.new)
|
15
|
+
@delegates = types.map { |t| t.new(ast_node, context) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def analyse
|
19
|
+
@delegates.each(&:analyse)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "../graph/bidirected_adjacency_graph"
|
2
|
+
require_relative "equivalences"
|
3
|
+
require_relative "registry"
|
4
|
+
|
5
|
+
module RDG
|
6
|
+
module Analysis
|
7
|
+
class Context
|
8
|
+
attr_reader :graph, :equivalences, :registry
|
9
|
+
|
10
|
+
def initialize(graph = nil, equivalences = nil, registry = nil)
|
11
|
+
@graph = graph || Graph::BidirectedAdjacencyGraph.new
|
12
|
+
@equivalences = equivalences || Equivalences.new
|
13
|
+
@registry = registry || Registry.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RDG
|
2
|
+
module Analysis
|
3
|
+
class Equivalences
|
4
|
+
def initialize
|
5
|
+
@equivalences = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def add(original, results)
|
9
|
+
@equivalences[original] = results
|
10
|
+
end
|
11
|
+
|
12
|
+
def all(original)
|
13
|
+
if @equivalences.key?(original)
|
14
|
+
@equivalences[original].map { |e| all(e) }.flatten
|
15
|
+
else
|
16
|
+
[original]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def first(original)
|
21
|
+
all(original).first
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative "analyser"
|
2
|
+
|
3
|
+
module RDG
|
4
|
+
module Analysis
|
5
|
+
class Propagater < Analyser
|
6
|
+
def analyse
|
7
|
+
add_internal_flow_edges
|
8
|
+
propogate_incoming_flow
|
9
|
+
propogate_outgoing_flow
|
10
|
+
remove_non_flow_vertices
|
11
|
+
add_equivalences
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def children
|
17
|
+
@ast_node.children
|
18
|
+
end
|
19
|
+
|
20
|
+
def nodes
|
21
|
+
children.reject(&:empty?)
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_internal_flow_edges
|
25
|
+
internal_flow_edges.each { |s, t| graph.add_edge(s, t) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def propogate_incoming_flow
|
29
|
+
graph.each_predecessor(@ast_node) do |predecessor|
|
30
|
+
graph.remove_edge(predecessor, @ast_node)
|
31
|
+
graph.add_edge(predecessor, start_node)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def propogate_outgoing_flow
|
36
|
+
graph.each_successor(@ast_node) do |successor|
|
37
|
+
graph.remove_edge(@ast_node, successor)
|
38
|
+
end_nodes.each { |n| graph.add_edge(n, successor) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove_non_flow_vertices
|
43
|
+
graph.remove_vertex(@ast_node)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_equivalences
|
47
|
+
equivalences.add(@ast_node, nodes)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module RDG
|
2
|
+
module Analysis
|
3
|
+
class Registry
|
4
|
+
def self.register_by_type(analyser, *types)
|
5
|
+
types.each { |type| by_type[type] = analyser }
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.register_default(analyser)
|
9
|
+
by_type.default = analyser
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.clear
|
13
|
+
by_type.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def analyser_for(ast_node, context)
|
17
|
+
by_node[ast_node].new(ast_node, context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepend_for(ast_node, analyser)
|
21
|
+
by_node[ast_node] = Composite.compose(analyser, by_node[ast_node])
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def self.by_type
|
27
|
+
@by_type ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def by_node
|
31
|
+
@by_node ||= Hash.new { |h, node| h[node] = Registry.by_type[node.type] }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/rdg/cfg.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
|
+
require "require_all"
|
2
|
+
|
1
3
|
require_relative "tree/ast"
|
2
|
-
|
3
|
-
|
4
|
-
require_relative "control/if"
|
5
|
-
require_relative "control/while"
|
6
|
-
require_relative "control/return"
|
7
|
-
require_relative "control/none"
|
4
|
+
require_rel "analysis/*"
|
5
|
+
require_rel "control/*"
|
8
6
|
|
9
7
|
module RDG
|
10
8
|
class CFG
|
@@ -17,59 +15,32 @@ module RDG
|
|
17
15
|
end
|
18
16
|
|
19
17
|
def initialize(ast)
|
20
|
-
@
|
18
|
+
@context = Analysis::Context.new
|
19
|
+
@context.graph.add_vertex(ast.root)
|
21
20
|
analyse(ast)
|
22
21
|
end
|
23
22
|
|
24
23
|
def write_to_graphic_file(format = 'png', filename = "cfg")
|
25
|
-
@graph.write_to_graphic_file(format, filename)
|
24
|
+
@context.graph.write_to_graphic_file(format, filename)
|
26
25
|
end
|
27
26
|
|
28
27
|
def vertices
|
29
|
-
@graph.each_vertex.to_a
|
28
|
+
@context.graph.each_vertex.to_a
|
30
29
|
end
|
31
30
|
|
32
31
|
def successors(v)
|
33
|
-
@graph.each_adjacent(v).to_a
|
32
|
+
@context.graph.each_adjacent(v).to_a
|
34
33
|
end
|
35
34
|
|
36
|
-
def
|
37
|
-
@graph.has_edge?(u, v)
|
35
|
+
def edge?(u, v)
|
36
|
+
@context.graph.has_edge?(u, v)
|
38
37
|
end
|
39
38
|
|
40
39
|
private
|
41
40
|
|
42
41
|
def analyse(ast)
|
43
|
-
state = {}
|
44
42
|
ast.pre_order_iterator.select(&:compound?).each do |ast_node|
|
45
|
-
|
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)
|
43
|
+
@context.registry.analyser_for(ast_node, @context).analyse
|
73
44
|
end
|
74
45
|
end
|
75
46
|
end
|
data/lib/rdg/control/begin.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
|
1
|
+
require "rdg/analysis/propagater"
|
2
2
|
|
3
3
|
module RDG
|
4
4
|
module Control
|
5
|
-
class Begin <
|
5
|
+
class Begin < Analysis::Propagater
|
6
|
+
register_analyser :begin, :kwbegin
|
7
|
+
|
6
8
|
def internal_flow_edges
|
7
9
|
children.each_cons(2).to_a
|
8
10
|
end
|
9
11
|
|
10
|
-
def
|
11
|
-
children.first
|
12
|
+
def start_node
|
13
|
+
children.first
|
12
14
|
end
|
13
15
|
|
14
16
|
def end_nodes
|