furnace 0.2.5 → 0.2.6
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/ast/node.rb +23 -29
- data/lib/furnace/ast/strict_visitor.rb +20 -0
- data/lib/furnace/ast/visitor.rb +0 -2
- data/lib/furnace/ast.rb +1 -1
- data/lib/furnace/cfg/graph.rb +37 -23
- data/lib/furnace/cfg/node.rb +8 -4
- data/lib/furnace/graphviz.rb +2 -2
- data/lib/furnace/transform/iterative_process.rb +26 -0
- data/lib/furnace/transform/pipeline.rb +2 -0
- data/lib/furnace/version.rb +1 -1
- data/lib/furnace.rb +1 -0
- metadata +5 -3
- data/lib/furnace/ast/symbolic_node.rb +0 -34
data/lib/furnace/ast/node.rb
CHANGED
@@ -1,25 +1,11 @@
|
|
1
1
|
module Furnace::AST
|
2
2
|
class Node
|
3
|
-
attr_accessor :type, :
|
3
|
+
attr_accessor :type, :children, :metadata
|
4
4
|
|
5
5
|
def initialize(type, children=[], metadata={})
|
6
6
|
@type, @children, @metadata = type.to_sym, children, metadata
|
7
7
|
end
|
8
8
|
|
9
|
-
def normalize_hierarchy!
|
10
|
-
@children.each do |child|
|
11
|
-
if child.respond_to? :parent=
|
12
|
-
child.parent = self
|
13
|
-
end
|
14
|
-
|
15
|
-
if child.respond_to? :normalize_hierarchy!
|
16
|
-
child.normalize_hierarchy!
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
self
|
21
|
-
end
|
22
|
-
|
23
9
|
def update(type, children=nil, metadata={})
|
24
10
|
@type = type
|
25
11
|
@children = children || @children
|
@@ -52,18 +38,6 @@ module Furnace::AST
|
|
52
38
|
end
|
53
39
|
end
|
54
40
|
|
55
|
-
def index
|
56
|
-
parent.children.find_index(self)
|
57
|
-
end
|
58
|
-
|
59
|
-
def next
|
60
|
-
parent.children[index + 1]
|
61
|
-
end
|
62
|
-
|
63
|
-
def prev
|
64
|
-
parent.children[index - 1]
|
65
|
-
end
|
66
|
-
|
67
41
|
def to_s
|
68
42
|
"(#{fancy_type} ...)"
|
69
43
|
end
|
@@ -71,6 +45,12 @@ module Furnace::AST
|
|
71
45
|
def to_sexp(indent=0)
|
72
46
|
str = "#{" " * indent}(#{fancy_type}"
|
73
47
|
|
48
|
+
if @metadata[:ellipsis]
|
49
|
+
str << " <omitted>)"
|
50
|
+
|
51
|
+
return str
|
52
|
+
end
|
53
|
+
|
74
54
|
children.each do |child|
|
75
55
|
if (!children[0].is_a?(Node) && child.is_a?(Node)) ||
|
76
56
|
(children[0].is_a?(Node) && child.is_a?(Node) &&
|
@@ -96,10 +76,24 @@ module Furnace::AST
|
|
96
76
|
|
97
77
|
def fancy_type
|
98
78
|
dasherized = @type.to_s.gsub('_', '-')
|
79
|
+
|
80
|
+
if @metadata.any?
|
81
|
+
metainfo = @metadata.dup
|
82
|
+
metainfo.delete :label
|
83
|
+
metainfo.delete :origin
|
84
|
+
metainfo.delete :ellipsis
|
85
|
+
|
86
|
+
if metainfo.any?
|
87
|
+
metainfo = "#{metainfo.inspect}:"
|
88
|
+
else
|
89
|
+
metainfo = nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
99
93
|
if @metadata[:label]
|
100
|
-
"#{@metadata[:label]}:#{dasherized}"
|
94
|
+
"#{@metadata[:label]}:#{metainfo}#{dasherized}"
|
101
95
|
else
|
102
|
-
dasherized
|
96
|
+
"#{metainfo}#{dasherized}"
|
103
97
|
end
|
104
98
|
end
|
105
99
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Furnace::AST
|
2
|
+
module StrictVisitor
|
3
|
+
def visit(node)
|
4
|
+
# Invoke a specific handler
|
5
|
+
on_handler = :"on_#{node.type}"
|
6
|
+
if respond_to? on_handler
|
7
|
+
new_node = send on_handler, node
|
8
|
+
node = new_node if new_node
|
9
|
+
end
|
10
|
+
|
11
|
+
node
|
12
|
+
end
|
13
|
+
|
14
|
+
def visit_all(nodes)
|
15
|
+
nodes.map do |node|
|
16
|
+
visit node
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/furnace/ast/visitor.rb
CHANGED
data/lib/furnace/ast.rb
CHANGED
data/lib/furnace/cfg/graph.rb
CHANGED
@@ -22,28 +22,32 @@ module Furnace::CFG
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def eliminate_unreachable!
|
25
|
-
|
25
|
+
worklist = Set[ entry, exit ]
|
26
26
|
reachable = Set[]
|
27
27
|
|
28
|
-
while
|
29
|
-
node =
|
28
|
+
while worklist.any?
|
29
|
+
node = worklist.first
|
30
|
+
worklist.delete node
|
30
31
|
reachable.add node
|
31
32
|
|
32
33
|
node.targets.each do |target|
|
33
34
|
unless reachable.include? target
|
34
|
-
|
35
|
+
worklist.add target
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
38
39
|
if node.exception
|
39
40
|
unless reachable.include? node.exception
|
40
|
-
|
41
|
+
worklist.add node.exception
|
41
42
|
end
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
45
46
|
@nodes.each do |node|
|
46
|
-
|
47
|
+
unless reachable.include? node
|
48
|
+
@nodes.delete node
|
49
|
+
yield node if block_given?
|
50
|
+
end
|
47
51
|
end
|
48
52
|
|
49
53
|
flush
|
@@ -59,42 +63,41 @@ module Furnace::CFG
|
|
59
63
|
next if target == @exit
|
60
64
|
|
61
65
|
# Skip explicitly non-redundant nodes
|
62
|
-
if node.
|
66
|
+
if node.metadata[:keep]
|
63
67
|
next
|
64
68
|
end
|
65
69
|
|
66
|
-
if node.targets.
|
67
|
-
target.sources.
|
70
|
+
if node.targets.uniq == [target] &&
|
71
|
+
target.sources.uniq == [node] &&
|
68
72
|
node.exception == target.exception
|
69
73
|
|
74
|
+
yield node, target if block_given?
|
75
|
+
|
70
76
|
node.insns.delete node.cti
|
71
|
-
@nodes.delete node
|
72
77
|
@nodes.delete target
|
78
|
+
worklist.delete target
|
73
79
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
target.cti,
|
78
|
-
target.target_labels,
|
79
|
-
target.exception_label)
|
80
|
-
@nodes.add new_node
|
81
|
-
worklist.add new_node
|
80
|
+
node.insns.concat target.insns
|
81
|
+
node.cti = target.cti
|
82
|
+
node.target_labels = target.target_labels
|
82
83
|
|
83
|
-
|
84
|
-
@entry = new_node
|
85
|
-
end
|
84
|
+
worklist.add node
|
86
85
|
|
87
86
|
flush
|
88
87
|
elsif node.targets.count == 1 &&
|
89
88
|
node.insns.empty?
|
89
|
+
|
90
90
|
target = node.targets.first
|
91
91
|
|
92
|
+
yield target, node if block_given?
|
93
|
+
|
92
94
|
node.sources.each do |source|
|
93
95
|
index = source.targets.index(node)
|
94
96
|
source.target_labels[index] = target.label
|
95
97
|
end
|
96
98
|
|
97
99
|
@nodes.delete node
|
100
|
+
worklist.delete node
|
98
101
|
|
99
102
|
if @entry == node
|
100
103
|
@entry = target
|
@@ -231,6 +234,9 @@ module Furnace::CFG
|
|
231
234
|
def flush
|
232
235
|
@source_map = nil
|
233
236
|
@label_map.clear
|
237
|
+
|
238
|
+
@dominators = nil
|
239
|
+
@postdominators = nil
|
234
240
|
end
|
235
241
|
|
236
242
|
def to_graphviz
|
@@ -239,7 +245,15 @@ module Furnace::CFG
|
|
239
245
|
if node.label == nil
|
240
246
|
contents = "<exit>"
|
241
247
|
else
|
242
|
-
contents = "<#{node.label.inspect}
|
248
|
+
contents = "<#{node.label.inspect}>"
|
249
|
+
end
|
250
|
+
|
251
|
+
if node.metadata.any?
|
252
|
+
contents << "\n#{node.metadata.inspect}"
|
253
|
+
end
|
254
|
+
|
255
|
+
if node.insns.any?
|
256
|
+
contents << "\n#{node.insns.map(&:inspect).join("\n")}"
|
243
257
|
end
|
244
258
|
|
245
259
|
options = {}
|
@@ -256,7 +270,7 @@ module Furnace::CFG
|
|
256
270
|
end
|
257
271
|
|
258
272
|
if node.exception_label
|
259
|
-
graph.edge node.label, node.exception_label, "
|
273
|
+
graph.edge node.label, node.exception_label, "", color: 'orange'
|
260
274
|
end
|
261
275
|
end
|
262
276
|
end
|
data/lib/furnace/cfg/node.rb
CHANGED
@@ -5,14 +5,18 @@ module Furnace::CFG
|
|
5
5
|
attr_accessor :target_labels, :exception_label
|
6
6
|
attr_accessor :instructions, :control_transfer_instruction
|
7
7
|
|
8
|
+
attr_accessor :metadata
|
9
|
+
|
8
10
|
alias :insns :instructions
|
9
11
|
alias :insns= :instructions=
|
10
12
|
alias :cti :control_transfer_instruction
|
11
13
|
alias :cti= :control_transfer_instruction=
|
12
14
|
|
13
15
|
def initialize(cfg, label=nil, insns=[], cti=nil,
|
14
|
-
target_labels=[], exception_label=nil
|
16
|
+
target_labels=[], exception_label=nil,
|
17
|
+
metadata={})
|
15
18
|
@cfg, @label = cfg, label
|
19
|
+
@metadata = metadata
|
16
20
|
|
17
21
|
@instructions = insns
|
18
22
|
@control_transfer_instruction = cti
|
@@ -64,10 +68,10 @@ module Furnace::CFG
|
|
64
68
|
"<#{@label}:#{@instructions.join ", "}>"
|
65
69
|
elsif @label
|
66
70
|
"<#{@label}>"
|
67
|
-
elsif @
|
68
|
-
"<!unlabeled>"
|
71
|
+
elsif @instructions
|
72
|
+
"<!unlabeled:#{@instructions.join ", "}>"
|
69
73
|
else
|
70
|
-
"<!exit>"
|
74
|
+
"<!exit#{metadata}>"
|
71
75
|
end
|
72
76
|
end
|
73
77
|
end
|
data/lib/furnace/graphviz.rb
CHANGED
@@ -29,14 +29,14 @@ class Furnace::Graphviz
|
|
29
29
|
label: label
|
30
30
|
})
|
31
31
|
|
32
|
-
@code << %Q{#{name.inspect} #{graphviz_options(options)};\n}
|
32
|
+
@code << %Q{"#{name.inspect}" #{graphviz_options(options)};\n}
|
33
33
|
end
|
34
34
|
|
35
35
|
def edge(from, to, label="", options={})
|
36
36
|
options = options.merge({
|
37
37
|
label: label.inspect
|
38
38
|
})
|
39
|
-
@code << %Q{#{from.inspect} -> #{to.inspect} #{graphviz_options(options)};\n}
|
39
|
+
@code << %Q{"#{from.inspect}" -> "#{to.inspect}" #{graphviz_options(options)};\n}
|
40
40
|
end
|
41
41
|
|
42
42
|
def to_s
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Furnace
|
2
|
+
module Transform
|
3
|
+
class IterativeProcess
|
4
|
+
def initialize(stages)
|
5
|
+
@stages = stages
|
6
|
+
end
|
7
|
+
|
8
|
+
def transform(*sequence)
|
9
|
+
loop do
|
10
|
+
changed = false
|
11
|
+
|
12
|
+
@stages.each do |stage|
|
13
|
+
return sequence if stage.nil?
|
14
|
+
|
15
|
+
if new_sequence = stage.transform(*sequence)
|
16
|
+
changed = true
|
17
|
+
sequence = new_sequence
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
return sequence unless changed
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/furnace/version.rb
CHANGED
data/lib/furnace.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.2.
|
4
|
+
version: 0.2.6
|
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-
|
12
|
+
date: 2012-09-30 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.
|
@@ -30,7 +30,7 @@ files:
|
|
30
30
|
- lib/furnace/ast/matcher/dsl.rb
|
31
31
|
- lib/furnace/ast/matcher/special.rb
|
32
32
|
- lib/furnace/ast/node.rb
|
33
|
-
- lib/furnace/ast/
|
33
|
+
- lib/furnace/ast/strict_visitor.rb
|
34
34
|
- lib/furnace/ast/visitor.rb
|
35
35
|
- lib/furnace/cfg.rb
|
36
36
|
- lib/furnace/cfg/graph.rb
|
@@ -43,6 +43,7 @@ files:
|
|
43
43
|
- lib/furnace/code/terminal_token.rb
|
44
44
|
- lib/furnace/code/token.rb
|
45
45
|
- lib/furnace/graphviz.rb
|
46
|
+
- lib/furnace/transform/iterative_process.rb
|
46
47
|
- lib/furnace/transform/pipeline.rb
|
47
48
|
- lib/furnace/version.rb
|
48
49
|
homepage: http://github.com/whitequark/furnace
|
@@ -70,3 +71,4 @@ signing_key:
|
|
70
71
|
specification_version: 3
|
71
72
|
summary: A static code analysis framework
|
72
73
|
test_files: []
|
74
|
+
has_rdoc:
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module Furnace::AST
|
2
|
-
class SymbolicNode
|
3
|
-
def initialize(name)
|
4
|
-
@name = name.to_sym
|
5
|
-
end
|
6
|
-
|
7
|
-
def to_sym
|
8
|
-
@name
|
9
|
-
end
|
10
|
-
|
11
|
-
def ===(name)
|
12
|
-
@name == name.to_sym
|
13
|
-
end
|
14
|
-
|
15
|
-
def inspect
|
16
|
-
@name.to_s
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
class MethodName < SymbolicNode
|
21
|
-
def inspect
|
22
|
-
".#{@name}"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
class LocalVariable < SymbolicNode
|
27
|
-
def inspect
|
28
|
-
"%#{@name}"
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class Constant < SymbolicNode
|
33
|
-
end
|
34
|
-
end
|