furnace 0.0.8 → 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.
- data/lib/furnace/cfg/graph.rb +165 -23
- data/lib/furnace/cfg/node.rb +33 -14
- data/lib/furnace/cfg.rb +0 -1
- data/lib/furnace/code/separated_token.rb +1 -1
- data/lib/furnace/code/token.rb +1 -1
- data/lib/furnace/graphviz.rb +16 -4
- data/lib/furnace/version.rb +1 -1
- metadata +3 -3
- data/lib/furnace/cfg/edge.rb +0 -34
data/lib/furnace/cfg/graph.rb
CHANGED
@@ -1,13 +1,10 @@
|
|
1
1
|
module Furnace::CFG
|
2
2
|
class Graph
|
3
|
-
attr_reader
|
3
|
+
attr_reader :nodes
|
4
|
+
attr_accessor :entry, :exit
|
4
5
|
|
5
6
|
def initialize
|
6
7
|
@nodes = Set.new
|
7
|
-
@edges = Set.new
|
8
|
-
|
9
|
-
@pending_label = nil
|
10
|
-
@pending_operations = []
|
11
8
|
end
|
12
9
|
|
13
10
|
def find_node(label)
|
@@ -18,38 +15,183 @@ module Furnace::CFG
|
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
18
|
+
def eliminate_unreachable!
|
19
|
+
worklist = @nodes.dup
|
20
|
+
while worklist.any?
|
21
|
+
node = worklist.first
|
22
|
+
worklist.delete node
|
23
|
+
|
24
|
+
next if node == @entry
|
25
|
+
|
26
|
+
if node.sources.count == 0 ||
|
27
|
+
node.sources == [node]
|
28
|
+
@nodes.delete node
|
29
|
+
flush
|
30
|
+
end
|
31
|
+
end
|
24
32
|
end
|
25
33
|
|
26
|
-
def
|
27
|
-
|
34
|
+
def merge_redundant!
|
35
|
+
worklist = @nodes.dup
|
36
|
+
while worklist.any?
|
37
|
+
node = worklist.first
|
38
|
+
worklist.delete node
|
39
|
+
|
40
|
+
target = node.targets[0]
|
41
|
+
next if target == @exit
|
42
|
+
|
43
|
+
if node.targets.count == 1 &&
|
44
|
+
target.sources.count == 1
|
45
|
+
node.insns.delete node.cti
|
46
|
+
@nodes.delete node
|
47
|
+
@nodes.delete target
|
28
48
|
|
29
|
-
|
49
|
+
new_node = Node.new(self,
|
50
|
+
node.label,
|
51
|
+
node.insns + target.insns,
|
52
|
+
target.cti,
|
53
|
+
target.target_labels)
|
54
|
+
@nodes.add new_node
|
55
|
+
worklist.add new_node
|
30
56
|
|
31
|
-
|
32
|
-
|
57
|
+
if @entry == node
|
58
|
+
@entry = new_node
|
59
|
+
end
|
60
|
+
|
61
|
+
flush
|
62
|
+
elsif node.targets.count == 1 &&
|
63
|
+
node.insns.empty?
|
64
|
+
target = node.targets.first
|
65
|
+
|
66
|
+
node.sources.each do |source|
|
67
|
+
index = source.targets.index(node)
|
68
|
+
source.target_labels[index] = target.label
|
69
|
+
end
|
70
|
+
|
71
|
+
@nodes.delete node
|
72
|
+
|
73
|
+
flush
|
74
|
+
end
|
33
75
|
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Shamelessly stolen from
|
79
|
+
# http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf
|
80
|
+
def dominators
|
81
|
+
unless @dominators
|
82
|
+
# values of β will give rise to dom!
|
83
|
+
dom = { @entry => Set[@entry] }
|
84
|
+
|
85
|
+
@nodes.each do |node|
|
86
|
+
next if node == @entry
|
87
|
+
dom[node] = @nodes.dup
|
88
|
+
end
|
89
|
+
|
90
|
+
change = true
|
91
|
+
while change
|
92
|
+
change = false
|
93
|
+
@nodes.each do |node|
|
94
|
+
next if node == @entry
|
95
|
+
|
96
|
+
# Key Idea
|
97
|
+
# If a node dominates all
|
98
|
+
# predecessors of node n, then it
|
99
|
+
# also dominates node n.
|
100
|
+
pred = node.sources.map do |source|
|
101
|
+
dom[source]
|
102
|
+
end.reduce(:&)
|
34
103
|
|
35
|
-
|
36
|
-
|
104
|
+
current = Set[node].merge(pred)
|
105
|
+
if current != dom[node]
|
106
|
+
dom[node] = current
|
107
|
+
change = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
@dominators = dom
|
113
|
+
end
|
114
|
+
|
115
|
+
@dominators
|
37
116
|
end
|
38
117
|
|
39
|
-
|
40
|
-
|
118
|
+
# See also {#dominators} for references.
|
119
|
+
def identify_loops
|
120
|
+
loops = Hash.new { |h,k| h[k] = Set.new }
|
121
|
+
|
122
|
+
dom = dominators
|
123
|
+
@nodes.each do |node|
|
124
|
+
node.targets.each do |target|
|
125
|
+
# Back edges
|
126
|
+
# A back edge of a natural loop is one whose
|
127
|
+
# target dominates its source.
|
128
|
+
if dom[node].include? target
|
129
|
+
loops[target].add node
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# At this point, +loops+ contains a list of all nodes
|
135
|
+
# which have a back edge to the loop header. Expand
|
136
|
+
# it to the list of all nodes in the loop.
|
137
|
+
loops.each do |header, nodes|
|
138
|
+
# Natural loop
|
139
|
+
# The natural loop of a back edge (m→n), where
|
140
|
+
# n dominates m, is the set of nodes x such that n
|
141
|
+
# dominates x and there is a path from x to m not
|
142
|
+
# containing n.
|
143
|
+
pre_header = dom[header]
|
144
|
+
all_nodes = Set[header]
|
145
|
+
|
146
|
+
nodes.each do |node|
|
147
|
+
all_nodes.merge(dom[node] - pre_header)
|
148
|
+
end
|
149
|
+
|
150
|
+
nodes.replace all_nodes
|
151
|
+
end
|
152
|
+
|
153
|
+
loops
|
154
|
+
end
|
155
|
+
|
156
|
+
def source_map
|
157
|
+
unless @source_map
|
158
|
+
@source_map = Hash.new { |h, k| h[k] = [] }
|
159
|
+
|
41
160
|
@nodes.each do |node|
|
42
|
-
|
161
|
+
node.targets.each do |target|
|
162
|
+
@source_map[target] << node
|
163
|
+
end
|
43
164
|
end
|
165
|
+
end
|
166
|
+
|
167
|
+
@source_map
|
168
|
+
end
|
44
169
|
|
45
|
-
|
46
|
-
|
47
|
-
|
170
|
+
def flush
|
171
|
+
@source_map = nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def to_graphviz
|
175
|
+
Furnace::Graphviz.new do |graph|
|
176
|
+
@nodes.each do |node|
|
177
|
+
if node.label == nil
|
178
|
+
contents = "<exit>"
|
48
179
|
else
|
49
|
-
|
180
|
+
contents = "<#{node.label.inspect}>\n#{node.insns.map(&:inspect).join("\n")}"
|
181
|
+
end
|
182
|
+
|
183
|
+
options = {}
|
184
|
+
if @entry == node
|
185
|
+
options.merge! color: 'green'
|
186
|
+
elsif @exit == node
|
187
|
+
options.merge! color: 'red'
|
50
188
|
end
|
51
189
|
|
52
|
-
graph.
|
190
|
+
graph.node node.label, contents, options
|
191
|
+
|
192
|
+
node.target_labels.each_with_index do |label, idx|
|
193
|
+
graph.edge node.label, label, "#{idx}"
|
194
|
+
end
|
53
195
|
end
|
54
196
|
end
|
55
197
|
end
|
data/lib/furnace/cfg/node.rb
CHANGED
@@ -1,34 +1,53 @@
|
|
1
1
|
module Furnace::CFG
|
2
2
|
class Node
|
3
|
-
attr_reader
|
3
|
+
attr_reader :cfg, :label
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
attr_reader :instructions, :control_transfer_instruction
|
6
|
+
alias :insns :instructions
|
7
|
+
alias :cti :control_transfer_instruction
|
8
|
+
|
9
|
+
def initialize(cfg, label=nil, insns=[], cti=nil, target_labels=[])
|
10
|
+
@cfg, @label = cfg, label
|
11
|
+
|
12
|
+
@instructions = insns
|
13
|
+
@control_transfer_instruction = cti
|
14
|
+
|
15
|
+
@target_labels = target_labels
|
7
16
|
end
|
8
17
|
|
9
|
-
def
|
10
|
-
@
|
18
|
+
def target_labels
|
19
|
+
@target_labels
|
20
|
+
end
|
21
|
+
|
22
|
+
def targets
|
23
|
+
@target_labels.map do |label|
|
24
|
+
@cfg.find_node label
|
25
|
+
end
|
11
26
|
end
|
12
27
|
|
13
|
-
def
|
14
|
-
|
28
|
+
def source_labels
|
29
|
+
sources.map &:label
|
15
30
|
end
|
16
31
|
|
17
|
-
def
|
18
|
-
|
32
|
+
def sources
|
33
|
+
@cfg.source_map[self]
|
19
34
|
end
|
20
35
|
|
21
|
-
def
|
22
|
-
|
36
|
+
def exits?
|
37
|
+
targets == [@cfg.exit]
|
23
38
|
end
|
24
39
|
|
25
40
|
def ==(other)
|
26
|
-
self.label == other.label
|
41
|
+
other.is_a?(Node) && self.label == other.label
|
27
42
|
end
|
28
43
|
|
29
44
|
def inspect
|
30
|
-
if @label
|
31
|
-
"<#{@label}:#{@
|
45
|
+
if @label && @instructions
|
46
|
+
"<#{@label}:#{@instructions.join ", "}>"
|
47
|
+
elsif @label
|
48
|
+
"<#{@label}>"
|
49
|
+
elsif @insns
|
50
|
+
"<!unlabeled>"
|
32
51
|
else
|
33
52
|
"<!exit>"
|
34
53
|
end
|
data/lib/furnace/cfg.rb
CHANGED
data/lib/furnace/code/token.rb
CHANGED
data/lib/furnace/graphviz.rb
CHANGED
@@ -1,23 +1,35 @@
|
|
1
1
|
require 'furnace/base'
|
2
2
|
|
3
|
-
|
3
|
+
class Furnace::Graphviz
|
4
4
|
def initialize
|
5
5
|
@code = "digraph {\n"
|
6
6
|
@code << "node [labeljust=l,nojustify=true,fontname=monospace];"
|
7
|
-
@code << "rankdir=TB
|
7
|
+
@code << "rankdir=TB;\n"
|
8
8
|
|
9
9
|
yield self
|
10
10
|
|
11
11
|
@code << "}"
|
12
12
|
end
|
13
13
|
|
14
|
-
def node(name, content)
|
14
|
+
def node(name, content, options={})
|
15
15
|
content.gsub!("&", "&")
|
16
16
|
content.gsub!(">", ">")
|
17
17
|
content.gsub!("<", "<")
|
18
|
+
content.gsub!(/\*\*(.+?)\*\*/, '<b>\1</b>')
|
18
19
|
content = content.lines.map { |l| %Q{<tr><td align="left">#{l}</td></tr>} }.join
|
19
20
|
|
20
|
-
|
21
|
+
if content.empty?
|
22
|
+
label = "<<empty>>"
|
23
|
+
else
|
24
|
+
label = "<<table border=\"0\">#{content}</table>>"
|
25
|
+
end
|
26
|
+
|
27
|
+
options = options.merge({
|
28
|
+
shape: 'box',
|
29
|
+
label: label
|
30
|
+
})
|
31
|
+
|
32
|
+
@code << %Q{#{name.inspect} [#{options.map { |k,v| "#{k}=#{v}" }.join(",")}];\n}
|
21
33
|
end
|
22
34
|
|
23
35
|
def edge(from, to, label="")
|
data/lib/furnace/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: furnace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-11 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Furnace is a static code analysis framework for dynamic languages, aimed
|
15
15
|
at efficient type and behavior inference.
|
@@ -44,7 +44,6 @@ files:
|
|
44
44
|
- lib/furnace/ast/visitor.rb
|
45
45
|
- lib/furnace/base.rb
|
46
46
|
- lib/furnace/cfg.rb
|
47
|
-
- lib/furnace/cfg/edge.rb
|
48
47
|
- lib/furnace/cfg/graph.rb
|
49
48
|
- lib/furnace/cfg/node.rb
|
50
49
|
- lib/furnace/code.rb
|
@@ -90,3 +89,4 @@ signing_key:
|
|
90
89
|
specification_version: 3
|
91
90
|
summary: A static code analysis framework
|
92
91
|
test_files: []
|
92
|
+
has_rdoc:
|
data/lib/furnace/cfg/edge.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
module Furnace::CFG
|
2
|
-
class Edge
|
3
|
-
attr_accessor :source_operation, :source_label, :target_label
|
4
|
-
|
5
|
-
def initialize(cfg, source_operation, source_label, target_label)
|
6
|
-
@cfg, @source_operation, @source_label, @target_label =
|
7
|
-
cfg, source_operation, source_label, target_label
|
8
|
-
end
|
9
|
-
|
10
|
-
def source
|
11
|
-
@cfg.find_node(@source_label)
|
12
|
-
end
|
13
|
-
|
14
|
-
def target
|
15
|
-
@cfg.find_node(@target_label) if @target_label
|
16
|
-
end
|
17
|
-
|
18
|
-
def source=(node)
|
19
|
-
@source_label = node.label
|
20
|
-
end
|
21
|
-
|
22
|
-
def target=(node)
|
23
|
-
if node
|
24
|
-
@target_label = node.label
|
25
|
-
else
|
26
|
-
@target_label = nil
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def inspect
|
31
|
-
"<#{@source_label.inspect} -> #{@target_label.inspect}>"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|