furnace 0.3.1 → 0.4.0.beta.1
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/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +1 -2
- data/{LICENSE → LICENSE.MIT} +14 -14
- data/Rakefile +1 -5
- data/furnace.gemspec +7 -3
- data/lib/furnace/ast/node.rb +14 -0
- data/lib/furnace/ast/processor.rb +7 -7
- data/lib/furnace/ssa/argument.rb +23 -0
- data/lib/furnace/ssa/basic_block.rb +117 -0
- data/lib/furnace/ssa/builder.rb +100 -0
- data/lib/furnace/ssa/constant.rb +43 -0
- data/lib/furnace/ssa/function.rb +191 -0
- data/lib/furnace/ssa/generic_instruction.rb +14 -0
- data/lib/furnace/ssa/generic_type.rb +16 -0
- data/lib/furnace/ssa/instruction.rb +92 -0
- data/lib/furnace/ssa/instruction_syntax.rb +109 -0
- data/lib/furnace/ssa/instructions/branch.rb +11 -0
- data/lib/furnace/ssa/instructions/phi.rb +63 -0
- data/lib/furnace/ssa/instructions/return.rb +11 -0
- data/lib/furnace/ssa/module.rb +52 -0
- data/lib/furnace/ssa/named_value.rb +25 -0
- data/lib/furnace/ssa/pretty_printer.rb +113 -0
- data/lib/furnace/ssa/terminator_instruction.rb +24 -0
- data/lib/furnace/ssa/type.rb +27 -0
- data/lib/furnace/ssa/types/basic_block.rb +11 -0
- data/lib/furnace/ssa/types/function.rb +11 -0
- data/lib/furnace/ssa/types/void.rb +15 -0
- data/lib/furnace/ssa/user.rb +84 -0
- data/lib/furnace/ssa/value.rb +62 -0
- data/lib/furnace/ssa.rb +66 -0
- data/lib/furnace/transform/iterative.rb +27 -0
- data/lib/furnace/transform/pipeline.rb +3 -3
- data/lib/furnace/version.rb +1 -1
- data/lib/furnace.rb +3 -3
- data/test/ast_test.rb +32 -3
- data/test/ssa_test.rb +1129 -0
- data/test/test_helper.rb +17 -28
- data/test/transform_test.rb +74 -0
- metadata +136 -58
- data/lib/furnace/cfg/algorithms.rb +0 -193
- data/lib/furnace/cfg/graph.rb +0 -99
- data/lib/furnace/cfg/node.rb +0 -78
- data/lib/furnace/cfg.rb +0 -7
- data/lib/furnace/transform/iterative_process.rb +0 -26
data/test/test_helper.rb
CHANGED
@@ -1,36 +1,25 @@
|
|
1
|
-
|
1
|
+
require 'bacon'
|
2
|
+
require 'bacon/colored_output'
|
2
3
|
|
3
|
-
|
4
|
-
|
4
|
+
class Should
|
5
|
+
def enumerate(iterator, array)
|
6
|
+
enum = @object.send(iterator)
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
def handle_specification(name)
|
9
|
-
puts spaces + name
|
10
|
-
yield
|
11
|
-
puts if Counter[:context_depth] == 1
|
8
|
+
satisfy("##{iterator} should return an Enumerator, returns #{enum.class}") do
|
9
|
+
enum.instance_of? Enumerator
|
12
10
|
end
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
error = yield
|
18
|
-
|
19
|
-
print error.empty? ? "\e[32m" : "\e[1;31m"
|
20
|
-
print " - #{description}"
|
21
|
-
puts error.empty? ? "\e[0m" : " [#{error}]\e[0m"
|
12
|
+
values = enum.to_a
|
13
|
+
satisfy("##{iterator} should yield #{array.inspect}, yields #{values.inspect}") do
|
14
|
+
(values - array).empty? && (array - values).empty?
|
22
15
|
end
|
16
|
+
end
|
17
|
+
end
|
23
18
|
|
24
|
-
|
25
|
-
|
26
|
-
puts "%d specifications (%d requirements), %d failures, %d errors" %
|
27
|
-
Counter.values_at(:specifications, :requirements, :failed, :errors)
|
28
|
-
end
|
19
|
+
require 'simplecov'
|
20
|
+
SimpleCov.start
|
29
21
|
|
30
|
-
|
31
|
-
" " * (Counter[:context_depth] - 1)
|
32
|
-
end
|
33
|
-
end
|
22
|
+
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
|
34
23
|
|
35
|
-
|
36
|
-
|
24
|
+
require 'furnace'
|
25
|
+
include Furnace
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe Transform do
|
4
|
+
class Context
|
5
|
+
attr_accessor :value
|
6
|
+
|
7
|
+
def initialize(value)
|
8
|
+
@value = value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Pass
|
13
|
+
attr_accessor :run_count
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@run_count = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def run(context)
|
20
|
+
@run_count += 1
|
21
|
+
|
22
|
+
if context.value > 0
|
23
|
+
context.value -= 1
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
before do
|
32
|
+
@context = Context.new(3)
|
33
|
+
@pass = Pass.new
|
34
|
+
end
|
35
|
+
|
36
|
+
describe Transform::Pipeline do
|
37
|
+
should 'run all stages' do
|
38
|
+
pipeline = Transform::Pipeline.new([ @pass ] * 4)
|
39
|
+
pipeline.run(@context)
|
40
|
+
|
41
|
+
@context.value.should == 0
|
42
|
+
@pass.run_count.should == 4
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe Transform::Iterative do
|
47
|
+
should 'run until nothing changes' do
|
48
|
+
pipeline = Transform::Iterative.new([ @pass ] * 2)
|
49
|
+
pipeline.run(@context)
|
50
|
+
|
51
|
+
# [
|
52
|
+
# 3 pass 2 -> true
|
53
|
+
# 2 pass 1 -> true
|
54
|
+
# ]
|
55
|
+
# [
|
56
|
+
# 1 pass 0 -> true
|
57
|
+
# 0 pass 0 -> false
|
58
|
+
# ]
|
59
|
+
# [
|
60
|
+
# 0 pass 0 -> false
|
61
|
+
# 0 pass 0 -> false
|
62
|
+
# ]
|
63
|
+
|
64
|
+
@context.value.should == 0
|
65
|
+
@pass.run_count.should == 6
|
66
|
+
end
|
67
|
+
|
68
|
+
should 'signal if any changes were made' do
|
69
|
+
pipeline = Transform::Iterative.new([ @pass ] * 3)
|
70
|
+
pipeline.run(@context).should == true
|
71
|
+
pipeline.run(@context).should == false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
CHANGED
@@ -1,92 +1,150 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: furnace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
4
|
+
prerelease: 6
|
5
|
+
version: 0.4.0.beta.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Peter Zotov
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
|
15
|
+
name: ansi
|
16
|
+
version_requirements: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: !binary |-
|
21
|
+
MA==
|
17
22
|
none: false
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
18
24
|
requirements:
|
19
|
-
- -
|
25
|
+
- - ">="
|
20
26
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
22
|
-
|
27
|
+
version: !binary |-
|
28
|
+
MA==
|
29
|
+
none: false
|
23
30
|
prerelease: false
|
31
|
+
type: :runtime
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rake
|
24
34
|
version_requirements: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - "~>"
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '10.0'
|
25
39
|
none: false
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
26
41
|
requirements:
|
27
|
-
- -
|
42
|
+
- - "~>"
|
28
43
|
- !ruby/object:Gem::Version
|
29
|
-
version: '0'
|
44
|
+
version: '10.0'
|
45
|
+
none: false
|
46
|
+
prerelease: false
|
47
|
+
type: :development
|
30
48
|
- !ruby/object:Gem::Dependency
|
31
49
|
name: bacon
|
32
|
-
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.2'
|
33
55
|
none: false
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
34
57
|
requirements:
|
35
|
-
- - ~>
|
58
|
+
- - "~>"
|
36
59
|
- !ruby/object:Gem::Version
|
37
|
-
version: '1.
|
38
|
-
|
60
|
+
version: '1.2'
|
61
|
+
none: false
|
39
62
|
prerelease: false
|
63
|
+
type: :development
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: bacon-colored_output
|
40
66
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
67
|
requirements:
|
43
|
-
- -
|
68
|
+
- - ">="
|
44
69
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
46
|
-
|
47
|
-
name: yard
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
70
|
+
version: !binary |-
|
71
|
+
MA==
|
49
72
|
none: false
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
50
74
|
requirements:
|
51
|
-
- -
|
75
|
+
- - ">="
|
52
76
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
54
|
-
|
77
|
+
version: !binary |-
|
78
|
+
MA==
|
79
|
+
none: false
|
55
80
|
prerelease: false
|
81
|
+
type: :development
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: simplecov
|
56
84
|
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: !binary |-
|
89
|
+
MA==
|
57
90
|
none: false
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
58
92
|
requirements:
|
59
|
-
- -
|
93
|
+
- - ">="
|
60
94
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
95
|
+
version: !binary |-
|
96
|
+
MA==
|
97
|
+
none: false
|
98
|
+
prerelease: false
|
99
|
+
type: :development
|
62
100
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
64
|
-
|
101
|
+
name: yard
|
102
|
+
version_requirements: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: !binary |-
|
107
|
+
MA==
|
65
108
|
none: false
|
109
|
+
requirement: !ruby/object:Gem::Requirement
|
66
110
|
requirements:
|
67
|
-
- -
|
111
|
+
- - ">="
|
68
112
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
70
|
-
|
113
|
+
version: !binary |-
|
114
|
+
MA==
|
115
|
+
none: false
|
71
116
|
prerelease: false
|
117
|
+
type: :development
|
118
|
+
- !ruby/object:Gem::Dependency
|
119
|
+
name: kramdown
|
72
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: !binary |-
|
125
|
+
MA==
|
73
126
|
none: false
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
74
128
|
requirements:
|
75
|
-
- -
|
129
|
+
- - ">="
|
76
130
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
78
|
-
|
79
|
-
|
131
|
+
version: !binary |-
|
132
|
+
MA==
|
133
|
+
none: false
|
134
|
+
prerelease: false
|
135
|
+
type: :development
|
136
|
+
description: Furnace is a static code analysis framework for dynamic languages, aimed at efficient type and behavior inference.
|
80
137
|
email:
|
81
138
|
- whitequark@whitequark.org
|
82
139
|
executables: []
|
83
140
|
extensions: []
|
84
141
|
extra_rdoc_files: []
|
85
142
|
files:
|
86
|
-
- .gitignore
|
87
|
-
- .
|
143
|
+
- ".gitignore"
|
144
|
+
- ".travis.yml"
|
145
|
+
- ".yardopts"
|
88
146
|
- Gemfile
|
89
|
-
- LICENSE
|
147
|
+
- LICENSE.MIT
|
90
148
|
- Rakefile
|
91
149
|
- furnace.gemspec
|
92
150
|
- lib/furnace.rb
|
@@ -94,45 +152,65 @@ files:
|
|
94
152
|
- lib/furnace/ast/node.rb
|
95
153
|
- lib/furnace/ast/processor.rb
|
96
154
|
- lib/furnace/ast/sexp.rb
|
97
|
-
- lib/furnace/cfg.rb
|
98
|
-
- lib/furnace/cfg/algorithms.rb
|
99
|
-
- lib/furnace/cfg/graph.rb
|
100
|
-
- lib/furnace/cfg/node.rb
|
101
155
|
- lib/furnace/graphviz.rb
|
102
|
-
- lib/furnace/
|
156
|
+
- lib/furnace/ssa.rb
|
157
|
+
- lib/furnace/ssa/argument.rb
|
158
|
+
- lib/furnace/ssa/basic_block.rb
|
159
|
+
- lib/furnace/ssa/builder.rb
|
160
|
+
- lib/furnace/ssa/constant.rb
|
161
|
+
- lib/furnace/ssa/function.rb
|
162
|
+
- lib/furnace/ssa/generic_instruction.rb
|
163
|
+
- lib/furnace/ssa/generic_type.rb
|
164
|
+
- lib/furnace/ssa/instruction.rb
|
165
|
+
- lib/furnace/ssa/instruction_syntax.rb
|
166
|
+
- lib/furnace/ssa/instructions/branch.rb
|
167
|
+
- lib/furnace/ssa/instructions/phi.rb
|
168
|
+
- lib/furnace/ssa/instructions/return.rb
|
169
|
+
- lib/furnace/ssa/module.rb
|
170
|
+
- lib/furnace/ssa/named_value.rb
|
171
|
+
- lib/furnace/ssa/pretty_printer.rb
|
172
|
+
- lib/furnace/ssa/terminator_instruction.rb
|
173
|
+
- lib/furnace/ssa/type.rb
|
174
|
+
- lib/furnace/ssa/types/basic_block.rb
|
175
|
+
- lib/furnace/ssa/types/function.rb
|
176
|
+
- lib/furnace/ssa/types/void.rb
|
177
|
+
- lib/furnace/ssa/user.rb
|
178
|
+
- lib/furnace/ssa/value.rb
|
179
|
+
- lib/furnace/transform/iterative.rb
|
103
180
|
- lib/furnace/transform/pipeline.rb
|
104
181
|
- lib/furnace/version.rb
|
105
182
|
- test/ast_test.rb
|
183
|
+
- test/ssa_test.rb
|
106
184
|
- test/test_helper.rb
|
185
|
+
- test/transform_test.rb
|
107
186
|
homepage: http://github.com/whitequark/furnace
|
108
187
|
licenses: []
|
109
|
-
post_install_message:
|
188
|
+
post_install_message:
|
110
189
|
rdoc_options: []
|
111
190
|
require_paths:
|
112
191
|
- lib
|
113
192
|
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
-
none: false
|
115
193
|
requirements:
|
116
|
-
- -
|
194
|
+
- - ">="
|
117
195
|
- !ruby/object:Gem::Version
|
118
|
-
version: '0'
|
119
196
|
segments:
|
120
197
|
- 0
|
121
|
-
hash:
|
122
|
-
|
198
|
+
hash: 2
|
199
|
+
version: !binary |-
|
200
|
+
MA==
|
123
201
|
none: false
|
202
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
203
|
requirements:
|
125
|
-
- - !
|
204
|
+
- - !binary |-
|
205
|
+
Pg==
|
126
206
|
- !ruby/object:Gem::Version
|
127
|
-
version:
|
128
|
-
|
129
|
-
- 0
|
130
|
-
hash: -2809790994543949274
|
207
|
+
version: 1.3.1
|
208
|
+
none: false
|
131
209
|
requirements: []
|
132
|
-
rubyforge_project:
|
133
|
-
rubygems_version: 1.8.
|
134
|
-
signing_key:
|
210
|
+
rubyforge_project:
|
211
|
+
rubygems_version: 1.8.24
|
212
|
+
signing_key:
|
135
213
|
specification_version: 3
|
136
214
|
summary: A static code analysis framework
|
137
215
|
test_files: []
|
138
|
-
has_rdoc:
|
216
|
+
has_rdoc:
|
@@ -1,193 +0,0 @@
|
|
1
|
-
module Furnace::CFG
|
2
|
-
module Algorithms
|
3
|
-
def eliminate_unreachable!
|
4
|
-
worklist = Set[ entry, exit ]
|
5
|
-
reachable = Set[]
|
6
|
-
|
7
|
-
while worklist.any?
|
8
|
-
node = worklist.first
|
9
|
-
worklist.delete node
|
10
|
-
reachable.add node
|
11
|
-
|
12
|
-
node.targets.each do |target|
|
13
|
-
unless reachable.include? target
|
14
|
-
worklist.add target
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
if node.exception
|
19
|
-
unless reachable.include? node.exception
|
20
|
-
worklist.add node.exception
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
@nodes.each do |node|
|
26
|
-
unless reachable.include? node
|
27
|
-
@nodes.delete node
|
28
|
-
yield node if block_given?
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
flush
|
33
|
-
end
|
34
|
-
|
35
|
-
def merge_redundant!
|
36
|
-
worklist = @nodes.dup
|
37
|
-
while worklist.any?
|
38
|
-
node = worklist.first
|
39
|
-
worklist.delete node
|
40
|
-
|
41
|
-
target = node.targets[0]
|
42
|
-
next if target == @exit
|
43
|
-
|
44
|
-
# Skip explicitly non-redundant nodes
|
45
|
-
if node.metadata[:keep]
|
46
|
-
next
|
47
|
-
end
|
48
|
-
|
49
|
-
if node.targets.uniq == [target] &&
|
50
|
-
target.sources.uniq == [node] &&
|
51
|
-
node.exception == target.exception
|
52
|
-
|
53
|
-
yield node, target if block_given?
|
54
|
-
|
55
|
-
node.insns.delete node.cti
|
56
|
-
@nodes.delete target
|
57
|
-
worklist.delete target
|
58
|
-
|
59
|
-
node.insns.concat target.insns
|
60
|
-
node.cti = target.cti
|
61
|
-
node.target_labels = target.target_labels
|
62
|
-
|
63
|
-
worklist.add node
|
64
|
-
|
65
|
-
flush
|
66
|
-
elsif node.targets.count == 1 &&
|
67
|
-
node.insns.empty?
|
68
|
-
|
69
|
-
target = node.targets.first
|
70
|
-
|
71
|
-
yield target, node if block_given?
|
72
|
-
|
73
|
-
node.sources.each do |source|
|
74
|
-
index = source.targets.index(node)
|
75
|
-
source.target_labels[index] = target.label
|
76
|
-
end
|
77
|
-
|
78
|
-
@nodes.delete node
|
79
|
-
worklist.delete node
|
80
|
-
|
81
|
-
if @entry == node
|
82
|
-
@entry = target
|
83
|
-
end
|
84
|
-
|
85
|
-
flush
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# Shamelessly stolen from
|
91
|
-
# http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf
|
92
|
-
def compute_generic_domination(start, forward)
|
93
|
-
# values of β will give rise to dom!
|
94
|
-
dom = { start => Set[start] }
|
95
|
-
|
96
|
-
@nodes.each do |node|
|
97
|
-
next if node == start
|
98
|
-
dom[node] = @nodes.dup
|
99
|
-
end
|
100
|
-
|
101
|
-
change = true
|
102
|
-
while change
|
103
|
-
change = false
|
104
|
-
@nodes.each do |node|
|
105
|
-
next if node == start
|
106
|
-
|
107
|
-
# Are we computing dominators or postdominators?
|
108
|
-
if forward
|
109
|
-
edges = node.sources + node.exception_sources
|
110
|
-
elsif node.exception.nil?
|
111
|
-
edges = node.targets
|
112
|
-
else
|
113
|
-
edges = node.targets + [ node.exception ]
|
114
|
-
end
|
115
|
-
|
116
|
-
# Key Idea [for dominators]
|
117
|
-
# If a node dominates all
|
118
|
-
# predecessors of node n, then it
|
119
|
-
# also dominates node n.
|
120
|
-
pred = edges.map do |source|
|
121
|
-
dom[source]
|
122
|
-
end.reduce(:&)
|
123
|
-
|
124
|
-
# An exception handler header node has no regular sources.
|
125
|
-
pred = [] if pred.nil?
|
126
|
-
|
127
|
-
current = Set[node].merge(pred)
|
128
|
-
if current != dom[node]
|
129
|
-
dom[node] = current
|
130
|
-
change = true
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
dom
|
136
|
-
end
|
137
|
-
|
138
|
-
def dominators
|
139
|
-
@dominators ||= compute_generic_domination(@entry, true)
|
140
|
-
end
|
141
|
-
|
142
|
-
def postdominators
|
143
|
-
@postdominators ||= compute_generic_domination(@exit, false)
|
144
|
-
end
|
145
|
-
|
146
|
-
# See also {#dominators} for references.
|
147
|
-
def identify_loops
|
148
|
-
loops = Hash.new { |h,k| h[k] = Set.new }
|
149
|
-
|
150
|
-
dom = dominators
|
151
|
-
|
152
|
-
@nodes.each do |node|
|
153
|
-
node.targets.each do |target|
|
154
|
-
# Back edges
|
155
|
-
# A back edge of a natural loop is one whose
|
156
|
-
# target dominates its source.
|
157
|
-
if dom[node].include? target
|
158
|
-
loops[target].add node
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# At this point, +loops+ contains a list of all nodes
|
164
|
-
# which have a back edge to the loop header. Expand
|
165
|
-
# it to the list of all nodes in the loop.
|
166
|
-
loops.each do |header, nodes|
|
167
|
-
# Natural loop
|
168
|
-
# The natural loop of a back edge (m→n), where
|
169
|
-
# n dominates m, is the set of nodes x such that n
|
170
|
-
# dominates x and there is a path from x to m not
|
171
|
-
# containing n.
|
172
|
-
pre_header = dom[header]
|
173
|
-
all_nodes = Set[header]
|
174
|
-
|
175
|
-
nodes.each do |node|
|
176
|
-
all_nodes.merge(dom[node] - pre_header)
|
177
|
-
end
|
178
|
-
|
179
|
-
nodes.replace all_nodes
|
180
|
-
end
|
181
|
-
|
182
|
-
loops.default = nil
|
183
|
-
loops
|
184
|
-
end
|
185
|
-
|
186
|
-
def flush
|
187
|
-
@dominators = nil
|
188
|
-
@postdominators = nil
|
189
|
-
|
190
|
-
super if defined?(super)
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
data/lib/furnace/cfg/graph.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
module Furnace::CFG
|
2
|
-
class Graph
|
3
|
-
include Algorithms
|
4
|
-
|
5
|
-
attr_reader :nodes
|
6
|
-
attr_accessor :entry, :exit
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@nodes = Set.new
|
10
|
-
|
11
|
-
@source_map = nil
|
12
|
-
@label_map = {}
|
13
|
-
end
|
14
|
-
|
15
|
-
def find_node(label)
|
16
|
-
if node = @label_map[label]
|
17
|
-
node
|
18
|
-
elsif node = @nodes.find { |n| n.label == label }
|
19
|
-
@label_map[label] = node
|
20
|
-
node
|
21
|
-
else
|
22
|
-
raise "Cannot find CFG node #{label}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def sources_for(node, find_exceptions=false)
|
27
|
-
unless @source_map
|
28
|
-
@source_map = Hash.new { |h, k| h[k] = [] }
|
29
|
-
@exception_source_map = Hash.new { |h, k| h[k] = [] }
|
30
|
-
|
31
|
-
@nodes.each do |node|
|
32
|
-
node.targets.each do |target|
|
33
|
-
@source_map[target] << node
|
34
|
-
end
|
35
|
-
|
36
|
-
@exception_source_map[node.exception] << node
|
37
|
-
end
|
38
|
-
|
39
|
-
@source_map.each do |node, sources|
|
40
|
-
sources.freeze
|
41
|
-
end
|
42
|
-
|
43
|
-
@exception_source_map.each do |node, sources|
|
44
|
-
sources.freeze
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
if find_exceptions
|
49
|
-
@exception_source_map[node]
|
50
|
-
else
|
51
|
-
@source_map[node]
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def flush
|
56
|
-
@source_map = nil
|
57
|
-
@label_map.clear
|
58
|
-
|
59
|
-
super if defined?(super)
|
60
|
-
end
|
61
|
-
|
62
|
-
def to_graphviz
|
63
|
-
Furnace::Graphviz.new do |graph|
|
64
|
-
@nodes.each do |node|
|
65
|
-
if node.label == nil
|
66
|
-
contents = "<exit>"
|
67
|
-
else
|
68
|
-
contents = "<#{node.label.inspect}>"
|
69
|
-
end
|
70
|
-
|
71
|
-
if node.metadata.any?
|
72
|
-
contents << "\n#{node.metadata.inspect}"
|
73
|
-
end
|
74
|
-
|
75
|
-
if node.insns.any?
|
76
|
-
contents << "\n#{node.insns.map(&:inspect).join("\n")}"
|
77
|
-
end
|
78
|
-
|
79
|
-
options = {}
|
80
|
-
if @entry == node
|
81
|
-
options.merge! color: 'green'
|
82
|
-
elsif @exit == node
|
83
|
-
options.merge! color: 'red'
|
84
|
-
end
|
85
|
-
|
86
|
-
graph.node node.label, contents, options
|
87
|
-
|
88
|
-
node.target_labels.each_with_index do |label, idx|
|
89
|
-
graph.edge node.label, label, "#{idx}"
|
90
|
-
end
|
91
|
-
|
92
|
-
if node.exception_label
|
93
|
-
graph.edge node.label, node.exception_label, "", color: 'orange'
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|