furnace 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/lib/furnace/cfg/graph.rb +46 -13
- data/lib/furnace/cfg/node.rb +17 -7
- data/lib/furnace/graphviz.rb +10 -3
- data/lib/furnace/transform.rb +0 -1
- data/lib/furnace/version.rb +1 -1
- metadata +3 -12
- data/lib/furnace/anf/edge.rb +0 -6
- data/lib/furnace/anf/graph.rb +0 -60
- data/lib/furnace/anf/if_node.rb +0 -17
- data/lib/furnace/anf/in_node.rb +0 -17
- data/lib/furnace/anf/let_node.rb +0 -37
- data/lib/furnace/anf/node.rb +0 -31
- data/lib/furnace/anf/return_node.rb +0 -17
- data/lib/furnace/transform/generic/anf_build.rb +0 -153
data/.gitignore
CHANGED
data/lib/furnace/cfg/graph.rb
CHANGED
@@ -5,10 +5,16 @@ module Furnace::CFG
|
|
5
5
|
|
6
6
|
def initialize
|
7
7
|
@nodes = Set.new
|
8
|
+
|
9
|
+
@source_map = nil
|
10
|
+
@label_map = {}
|
8
11
|
end
|
9
12
|
|
10
13
|
def find_node(label)
|
11
|
-
if node = @
|
14
|
+
if node = @label_map[label]
|
15
|
+
node
|
16
|
+
elsif node = @nodes.find { |n| n.label == label }
|
17
|
+
@label_map[label] = node
|
12
18
|
node
|
13
19
|
else
|
14
20
|
raise "Cannot find CFG node #{label}"
|
@@ -16,19 +22,31 @@ module Furnace::CFG
|
|
16
22
|
end
|
17
23
|
|
18
24
|
def eliminate_unreachable!
|
19
|
-
|
20
|
-
|
21
|
-
node = worklist.first
|
22
|
-
worklist.delete node
|
25
|
+
queue = [entry]
|
26
|
+
reachable = Set[]
|
23
27
|
|
24
|
-
|
28
|
+
while queue.any?
|
29
|
+
node = queue.shift
|
30
|
+
reachable.add node
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
32
|
+
node.targets.each do |target|
|
33
|
+
unless reachable.include? target
|
34
|
+
queue.push target
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if node.exception
|
39
|
+
unless reachable.include? node.exception
|
40
|
+
queue.push node.exception
|
41
|
+
end
|
30
42
|
end
|
31
43
|
end
|
44
|
+
|
45
|
+
@nodes.each do |node|
|
46
|
+
@nodes.delete node unless reachable.include? node
|
47
|
+
end
|
48
|
+
|
49
|
+
flush
|
32
50
|
end
|
33
51
|
|
34
52
|
def merge_redundant!
|
@@ -41,7 +59,9 @@ module Furnace::CFG
|
|
41
59
|
next if target == @exit
|
42
60
|
|
43
61
|
if node.targets.count == 1 &&
|
44
|
-
target.sources.count == 1
|
62
|
+
target.sources.count == 1 &&
|
63
|
+
node.exception == target.exception
|
64
|
+
|
45
65
|
node.insns.delete node.cti
|
46
66
|
@nodes.delete node
|
47
67
|
@nodes.delete target
|
@@ -101,6 +121,9 @@ module Furnace::CFG
|
|
101
121
|
dom[source]
|
102
122
|
end.reduce(:&)
|
103
123
|
|
124
|
+
# An exception handler header node has no regular sources.
|
125
|
+
pred = [] if pred.nil?
|
126
|
+
|
104
127
|
current = Set[node].merge(pred)
|
105
128
|
if current != dom[node]
|
106
129
|
dom[node] = current
|
@@ -150,10 +173,11 @@ module Furnace::CFG
|
|
150
173
|
nodes.replace all_nodes
|
151
174
|
end
|
152
175
|
|
176
|
+
loops.default = nil
|
153
177
|
loops
|
154
178
|
end
|
155
179
|
|
156
|
-
def
|
180
|
+
def sources_for(node)
|
157
181
|
unless @source_map
|
158
182
|
@source_map = Hash.new { |h, k| h[k] = [] }
|
159
183
|
|
@@ -162,13 +186,18 @@ module Furnace::CFG
|
|
162
186
|
@source_map[target] << node
|
163
187
|
end
|
164
188
|
end
|
189
|
+
|
190
|
+
@source_map.each do |node, sources|
|
191
|
+
sources.freeze
|
192
|
+
end
|
165
193
|
end
|
166
194
|
|
167
|
-
@source_map
|
195
|
+
@source_map[node]
|
168
196
|
end
|
169
197
|
|
170
198
|
def flush
|
171
199
|
@source_map = nil
|
200
|
+
@label_map.clear
|
172
201
|
end
|
173
202
|
|
174
203
|
def to_graphviz
|
@@ -192,6 +221,10 @@ module Furnace::CFG
|
|
192
221
|
node.target_labels.each_with_index do |label, idx|
|
193
222
|
graph.edge node.label, label, "#{idx}"
|
194
223
|
end
|
224
|
+
|
225
|
+
if node.exception_label
|
226
|
+
graph.edge node.label, node.exception_label, "Exc", color: 'orange'
|
227
|
+
end
|
195
228
|
end
|
196
229
|
end
|
197
230
|
end
|
data/lib/furnace/cfg/node.rb
CHANGED
@@ -2,17 +2,23 @@ module Furnace::CFG
|
|
2
2
|
class Node
|
3
3
|
attr_reader :cfg, :label
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
alias :cti :control_transfer_instruction
|
5
|
+
attr_accessor :target_labels, :exception_label
|
6
|
+
attr_accessor :instructions, :control_transfer_instruction
|
8
7
|
|
9
|
-
|
8
|
+
alias :insns :instructions
|
9
|
+
alias :insns= :instructions=
|
10
|
+
alias :cti :control_transfer_instruction
|
11
|
+
alias :cti= :control_transfer_instruction=
|
12
|
+
|
13
|
+
def initialize(cfg, label=nil, insns=[], cti=nil,
|
14
|
+
target_labels=[], exception_label=nil)
|
10
15
|
@cfg, @label = cfg, label
|
11
16
|
|
12
17
|
@instructions = insns
|
13
18
|
@control_transfer_instruction = cti
|
14
19
|
|
15
|
-
@target_labels
|
20
|
+
@target_labels = target_labels
|
21
|
+
@exception_label = exception_label
|
16
22
|
end
|
17
23
|
|
18
24
|
def target_labels
|
@@ -22,7 +28,7 @@ module Furnace::CFG
|
|
22
28
|
def targets
|
23
29
|
@target_labels.map do |label|
|
24
30
|
@cfg.find_node label
|
25
|
-
end
|
31
|
+
end.freeze
|
26
32
|
end
|
27
33
|
|
28
34
|
def source_labels
|
@@ -30,7 +36,11 @@ module Furnace::CFG
|
|
30
36
|
end
|
31
37
|
|
32
38
|
def sources
|
33
|
-
@cfg.
|
39
|
+
@cfg.sources_for(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def exception
|
43
|
+
@cfg.find_node @exception_label if @exception_label
|
34
44
|
end
|
35
45
|
|
36
46
|
def exits?
|
data/lib/furnace/graphviz.rb
CHANGED
@@ -29,14 +29,21 @@ class Furnace::Graphviz
|
|
29
29
|
label: label
|
30
30
|
})
|
31
31
|
|
32
|
-
@code << %Q{#{name.inspect}
|
32
|
+
@code << %Q{#{name.inspect} #{graphviz_options(options)};\n}
|
33
33
|
end
|
34
34
|
|
35
|
-
def edge(from, to, label="")
|
36
|
-
|
35
|
+
def edge(from, to, label="", options={})
|
36
|
+
options = options.merge({
|
37
|
+
label: label.inspect
|
38
|
+
})
|
39
|
+
@code << %Q{#{from.inspect} -> #{to.inspect} #{graphviz_options(options)};\n}
|
37
40
|
end
|
38
41
|
|
39
42
|
def to_s
|
40
43
|
@code
|
41
44
|
end
|
45
|
+
|
46
|
+
def graphviz_options(options)
|
47
|
+
"[#{options.map { |k,v| "#{k}=#{v}" }.join(",")}]"
|
48
|
+
end
|
42
49
|
end
|
data/lib/furnace/transform.rb
CHANGED
@@ -9,6 +9,5 @@ require "furnace/transform/rubinius/ast_normalize"
|
|
9
9
|
require "furnace/transform/generic/label_normalize"
|
10
10
|
require "furnace/transform/generic/cfg_build"
|
11
11
|
require "furnace/transform/generic/cfg_normalize"
|
12
|
-
require "furnace/transform/generic/anf_build"
|
13
12
|
|
14
13
|
require "furnace/transform/optimizing/fold_constants"
|
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.1.
|
4
|
+
version: 0.1.2
|
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-05-06 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.
|
@@ -28,13 +28,6 @@ files:
|
|
28
28
|
- bin/furnace
|
29
29
|
- furnace.gemspec
|
30
30
|
- lib/furnace.rb
|
31
|
-
- lib/furnace/anf/edge.rb
|
32
|
-
- lib/furnace/anf/graph.rb
|
33
|
-
- lib/furnace/anf/if_node.rb
|
34
|
-
- lib/furnace/anf/in_node.rb
|
35
|
-
- lib/furnace/anf/let_node.rb
|
36
|
-
- lib/furnace/anf/node.rb
|
37
|
-
- lib/furnace/anf/return_node.rb
|
38
31
|
- lib/furnace/ast.rb
|
39
32
|
- lib/furnace/ast/matcher.rb
|
40
33
|
- lib/furnace/ast/matcher/dsl.rb
|
@@ -55,7 +48,6 @@ files:
|
|
55
48
|
- lib/furnace/code/token.rb
|
56
49
|
- lib/furnace/graphviz.rb
|
57
50
|
- lib/furnace/transform.rb
|
58
|
-
- lib/furnace/transform/generic/anf_build.rb
|
59
51
|
- lib/furnace/transform/generic/cfg_build.rb
|
60
52
|
- lib/furnace/transform/generic/cfg_normalize.rb
|
61
53
|
- lib/furnace/transform/generic/label_normalize.rb
|
@@ -84,9 +76,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
76
|
version: '0'
|
85
77
|
requirements: []
|
86
78
|
rubyforge_project:
|
87
|
-
rubygems_version: 1.8.
|
79
|
+
rubygems_version: 1.8.23
|
88
80
|
signing_key:
|
89
81
|
specification_version: 3
|
90
82
|
summary: A static code analysis framework
|
91
83
|
test_files: []
|
92
|
-
has_rdoc:
|
data/lib/furnace/anf/edge.rb
DELETED
data/lib/furnace/anf/graph.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
module Furnace
|
2
|
-
module ANF
|
3
|
-
class Graph
|
4
|
-
attr_reader :nodes, :edges
|
5
|
-
attr_accessor :root
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@root = nil
|
9
|
-
@nodes = Set.new
|
10
|
-
@edges = Set.new
|
11
|
-
end
|
12
|
-
|
13
|
-
def find(label)
|
14
|
-
@nodes.find { |node| node.label == label }
|
15
|
-
end
|
16
|
-
|
17
|
-
def eliminate_dead_code
|
18
|
-
live_set = search
|
19
|
-
@nodes &= live_set
|
20
|
-
end
|
21
|
-
|
22
|
-
def search
|
23
|
-
seen_set = Set.new
|
24
|
-
work_set = Set.new
|
25
|
-
|
26
|
-
work_set.add @root
|
27
|
-
|
28
|
-
while work_set.any?
|
29
|
-
node = work_set.first
|
30
|
-
work_set.delete node
|
31
|
-
seen_set.add node
|
32
|
-
|
33
|
-
yield node if block_given?
|
34
|
-
|
35
|
-
node.leaving_edges.map(&:target).each do |target|
|
36
|
-
work_set.add target unless seen_set.include? target
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
seen_set
|
41
|
-
end
|
42
|
-
|
43
|
-
def to_graphviz
|
44
|
-
Graphviz.new do |graph|
|
45
|
-
@nodes.each do |node|
|
46
|
-
graph.node node.object_id, node.to_human_readable
|
47
|
-
|
48
|
-
case node
|
49
|
-
when ANF::IfNode
|
50
|
-
graph.edge node.object_id, node.leaving_edge(true).target.object_id, "true"
|
51
|
-
graph.edge node.object_id, node.leaving_edge(false).target.object_id, "false"
|
52
|
-
when ANF::LetNode, ANF::InNode
|
53
|
-
graph.edge node.object_id, node.leaving_edge.target.object_id
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
data/lib/furnace/anf/if_node.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
module Furnace
|
2
|
-
module ANF
|
3
|
-
class IfNode < Node
|
4
|
-
attr_reader :condition
|
5
|
-
|
6
|
-
def initialize(graph, condition)
|
7
|
-
super(graph)
|
8
|
-
|
9
|
-
@condition = condition
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_human_readable
|
13
|
-
"if\n#{humanize @condition}"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/furnace/anf/in_node.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
module Furnace
|
2
|
-
module ANF
|
3
|
-
class InNode < Node
|
4
|
-
attr_reader :expressions
|
5
|
-
|
6
|
-
def initialize(graph, expressions)
|
7
|
-
super(graph)
|
8
|
-
|
9
|
-
@expressions = expressions
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_human_readable
|
13
|
-
"in\n#{@expressions.map { |e| "#{e.to_sexp(1)}" }.join "\n"}"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
data/lib/furnace/anf/let_node.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
module Furnace
|
2
|
-
module ANF
|
3
|
-
class LetNode < Node
|
4
|
-
attr_reader :arguments
|
5
|
-
|
6
|
-
def initialize(graph, arguments)
|
7
|
-
super(graph)
|
8
|
-
|
9
|
-
@arguments = arguments
|
10
|
-
end
|
11
|
-
|
12
|
-
def try_eliminate
|
13
|
-
if identity?
|
14
|
-
entering_edges.each do |edge|
|
15
|
-
edge.target = leaving_edge.target
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def identity?
|
21
|
-
@arguments.reduce(true) { |r, (k, v)| r && (v === k) }
|
22
|
-
end
|
23
|
-
|
24
|
-
def try_propagate
|
25
|
-
end
|
26
|
-
|
27
|
-
def static?(node)
|
28
|
-
[ NilClass, TrueClass, FalseClass, Fixnum, Symbol,
|
29
|
-
AST::LocalVariable, AST::InstanceVariable ].include? node.class
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_human_readable
|
33
|
-
"let\n#{@arguments.map { |k, v| " #{k} = #{humanize v}" }.join "\n"}"
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/lib/furnace/anf/node.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
module Furnace
|
2
|
-
module ANF
|
3
|
-
class Node
|
4
|
-
attr_reader :graph
|
5
|
-
|
6
|
-
def initialize(graph)
|
7
|
-
@graph = graph
|
8
|
-
end
|
9
|
-
|
10
|
-
def leaving_edges
|
11
|
-
@graph.edges.select { |edge| edge.source == self }
|
12
|
-
end
|
13
|
-
|
14
|
-
def leaving_edge(param=nil)
|
15
|
-
@graph.edges.find { |edge| edge.source == self && edge.param == param }
|
16
|
-
end
|
17
|
-
|
18
|
-
def entering_edges
|
19
|
-
@graph.edges.select { |edge| edge.target == self }
|
20
|
-
end
|
21
|
-
|
22
|
-
def humanize(node)
|
23
|
-
if node.is_a? AST::Node
|
24
|
-
node.to_sexp
|
25
|
-
else
|
26
|
-
node.inspect
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
@@ -1,153 +0,0 @@
|
|
1
|
-
module Furnace
|
2
|
-
module Transform
|
3
|
-
module Generic
|
4
|
-
class ANFBuild
|
5
|
-
include AST::Visitor
|
6
|
-
|
7
|
-
def transform(cfg, method)
|
8
|
-
@method_locals = method.local_names
|
9
|
-
|
10
|
-
@last_label = -1
|
11
|
-
@anf_nodes = Hash.new { |k,v| v }
|
12
|
-
@anf_edges = []
|
13
|
-
|
14
|
-
@anf = ANF::Graph.new
|
15
|
-
|
16
|
-
cfg.nodes.each do |node|
|
17
|
-
@locals = {}
|
18
|
-
@node = node
|
19
|
-
|
20
|
-
@last_anf_node = nil
|
21
|
-
|
22
|
-
# At this point we can have no more than two edges.
|
23
|
-
@default_edge = node.default_leaving_edge
|
24
|
-
@other_edge = node.leaving_edge(node.operations.last.metadata[:label])
|
25
|
-
|
26
|
-
# Transform the AST for each node to ANF, removing redundant root nodes
|
27
|
-
# in the process.
|
28
|
-
node.operations.delete_if do |operation|
|
29
|
-
visit operation
|
30
|
-
|
31
|
-
operation.type == :remove
|
32
|
-
end
|
33
|
-
|
34
|
-
# If there were no nodes created, fallback to default CFG edge.
|
35
|
-
@last_anf_node ||= @default_edge.target_label
|
36
|
-
|
37
|
-
# If some operations were done just for side effect, add an InNode.
|
38
|
-
if node.operations.any?
|
39
|
-
anf_in = ANF::InNode.new(@anf, node.operations)
|
40
|
-
@anf.nodes.add anf_in
|
41
|
-
|
42
|
-
@anf_edges << [ anf_in, @last_anf_node ]
|
43
|
-
@last_anf_node = anf_in
|
44
|
-
end
|
45
|
-
|
46
|
-
# If any locals were rebound, add a LetNode.
|
47
|
-
if @locals.any? || node.operations.any?
|
48
|
-
anf_let = ANF::LetNode.new(@anf, passed_locals)
|
49
|
-
@anf.nodes.add anf_let
|
50
|
-
|
51
|
-
@anf_edges << [ anf_let, @last_anf_node ]
|
52
|
-
@last_anf_node = anf_let
|
53
|
-
end
|
54
|
-
|
55
|
-
@anf_nodes[node.label] = @last_anf_node
|
56
|
-
end
|
57
|
-
|
58
|
-
# The root is a CFG node with label (ip) 0.
|
59
|
-
@anf.root = @anf_nodes[0]
|
60
|
-
|
61
|
-
@anf_edges.each do |(source_label, target_label, param)|
|
62
|
-
@anf.edges.add ANF::Edge.new(@anf_nodes[source_label],
|
63
|
-
@anf_nodes[target_label],
|
64
|
-
param)
|
65
|
-
end
|
66
|
-
|
67
|
-
[ @anf, method ]
|
68
|
-
end
|
69
|
-
|
70
|
-
def passed_locals
|
71
|
-
map = @method_locals.map do |name|
|
72
|
-
# Is the name rebound?
|
73
|
-
if @locals.include?(name)
|
74
|
-
[ name, @locals[name] ]
|
75
|
-
# Is it the middle of function?
|
76
|
-
elsif @node.entering_edges.any?
|
77
|
-
[ name, AST::LocalVariable.new(name) ]
|
78
|
-
# Locals default to nil.
|
79
|
-
else
|
80
|
-
[ name, nil ]
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
Hash[*map.flatten]
|
85
|
-
end
|
86
|
-
|
87
|
-
# (set-lvar :var value)
|
88
|
-
def on_set_lvar(ast_node)
|
89
|
-
@locals[ast_node.children.first] = ast_node.children.last
|
90
|
-
|
91
|
-
ast_node.update(:remove)
|
92
|
-
end
|
93
|
-
|
94
|
-
# (jump-if compare_to condition)
|
95
|
-
def on_jump_if(ast_node)
|
96
|
-
if ast_node.children.first == true
|
97
|
-
true_edge, false_edge = @other_edge, @default_edge
|
98
|
-
else
|
99
|
-
true_edge, false_edge = @default_edge, @other_edge
|
100
|
-
end
|
101
|
-
|
102
|
-
true_node = ANF::LetNode.new(@anf, passed_locals)
|
103
|
-
false_node = ANF::LetNode.new(@anf, passed_locals)
|
104
|
-
@last_anf_node = ANF::IfNode.new(@anf, ast_node.children.last)
|
105
|
-
|
106
|
-
@anf.nodes.merge [ true_node, false_node, @last_anf_node ]
|
107
|
-
|
108
|
-
@anf_edges << [ @last_anf_node, true_node, true ] <<
|
109
|
-
[ true_node, true_edge.target_label ]
|
110
|
-
@anf_edges << [ @last_anf_node, false_node, false ] <<
|
111
|
-
[ false_node, false_edge.target_label ]
|
112
|
-
|
113
|
-
ast_node.update(:remove)
|
114
|
-
end
|
115
|
-
|
116
|
-
# (return expression)
|
117
|
-
def on_return(ast_node)
|
118
|
-
@last_anf_node = ANF::ReturnNode.new(@anf, ast_node.children.last)
|
119
|
-
|
120
|
-
@anf.nodes.add @last_anf_node
|
121
|
-
|
122
|
-
ast_node.update(:remove)
|
123
|
-
end
|
124
|
-
|
125
|
-
# (get-lvar :x) -> %x
|
126
|
-
def on_get_lvar(node)
|
127
|
-
node.update(:expand, AST::LocalVariable.new(node.children.first))
|
128
|
-
end
|
129
|
-
|
130
|
-
# AST node labels do not make sense.
|
131
|
-
def on_any(ast_node)
|
132
|
-
ast_node.metadata.delete :label
|
133
|
-
end
|
134
|
-
|
135
|
-
def expand_node(node)
|
136
|
-
node.update(:expand)
|
137
|
-
end
|
138
|
-
|
139
|
-
# Immediates do not have to carry metadata anymore.
|
140
|
-
alias :on_true :expand_node
|
141
|
-
alias :on_false :expand_node
|
142
|
-
alias :on_nil :expand_node
|
143
|
-
alias :on_fixnum :expand_node
|
144
|
-
alias :on_literal :expand_node
|
145
|
-
|
146
|
-
# We have a near infinite supply of small, unoccupied labels.
|
147
|
-
def make_label
|
148
|
-
@last_label -= 1
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|