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.
@@ -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