furnace 0.0.8 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,10 @@
1
1
  module Furnace::CFG
2
2
  class Graph
3
- attr_reader :nodes, :edges
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 expand(label, operation)
22
- @pending_label ||= label
23
- @pending_operations << operation
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 transfer(targets)
27
- return unless @pending_label
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
- @nodes << Node.new(self, @pending_label, @pending_operations)
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
- targets.each do |operation, target|
32
- @edges << Edge.new(self, operation, @pending_label, target)
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
- @pending_label = nil
36
- @pending_operations = []
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
- def to_graphviz
40
- Furnace::Graphviz.new do |graph|
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
- graph.node node.label, node.operations.map(&:inspect).join("\n")
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
- @edges.each do |edge|
46
- if edge.source_operation.nil?
47
- label = "~"
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
- label = edge.source_operation
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.edge edge.source_label, edge.target_label, label
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
@@ -1,34 +1,53 @@
1
1
  module Furnace::CFG
2
2
  class Node
3
- attr_reader :label, :operations
3
+ attr_reader :cfg, :label
4
4
 
5
- def initialize(cfg, label, operations)
6
- @cfg, @label, @operations = cfg, label, operations
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 entering_edges
10
- @cfg.edges.select { |e| e.target == self }
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 leaving_edges
14
- @cfg.edges.select { |e| e.source == self }
28
+ def source_labels
29
+ sources.map &:label
15
30
  end
16
31
 
17
- def leaving_edge(source)
18
- leaving_edges.find { |e| e.source_operation == source }
32
+ def sources
33
+ @cfg.source_map[self]
19
34
  end
20
35
 
21
- def default_leaving_edge
22
- leaving_edge(nil)
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}:#{@operations.map(&:inspect).join ", "}>"
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
@@ -2,6 +2,5 @@ require "set"
2
2
 
3
3
  require "furnace/base"
4
4
 
5
- require "furnace/cfg/edge"
6
5
  require "furnace/cfg/graph"
7
6
  require "furnace/cfg/node"
@@ -1,6 +1,6 @@
1
1
  module Furnace
2
2
  module Code
3
- class SeparatedToken < NonterminalToken
3
+ class SeparatedToken < SurroundedToken
4
4
  def text_between
5
5
  ""
6
6
  end
@@ -34,7 +34,7 @@ module Furnace
34
34
 
35
35
  protected
36
36
 
37
- def indent(code, options)
37
+ def indent(code, options=@options)
38
38
  unless code.empty?
39
39
  code.to_s.gsub(/^/, (options[:indent_with] || ' ') * (options[:level] || 1))
40
40
  else
@@ -1,23 +1,35 @@
1
1
  require 'furnace/base'
2
2
 
3
- module Furnace::Graphviz
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!("&", "&amp;")
16
16
  content.gsub!(">", "&gt;")
17
17
  content.gsub!("<", "&lt;")
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
- @code << %Q{#{name.inspect} [shape=box,label=<<table border="0">#{content}</table>>];\n}
21
+ if content.empty?
22
+ label = "<&lt;empty&gt;>"
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="")
@@ -1,3 +1,3 @@
1
1
  module Furnace
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0"
3
3
  end
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.8
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-06 00:00:00.000000000 Z
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:
@@ -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