furnace-avm2 0.9.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +3 -1
  3. data/bin/furnace-avm2 +27 -13
  4. data/bin/furnace-avm2-decompiler +14 -4
  5. data/bin/furnace-avm2-shell +4 -0
  6. data/furnace-avm2.gemspec +1 -1
  7. data/lib/furnace-avm2.rb +1 -1
  8. data/lib/furnace-avm2/abc.rb +3 -0
  9. data/lib/furnace-avm2/abc/metadata/exception_info.rb +7 -2
  10. data/lib/furnace-avm2/abc/metadata/method_body_info.rb +4 -2
  11. data/lib/furnace-avm2/abc/metadata/script_info.rb +8 -3
  12. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add.rb +1 -1
  13. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add_i.rb +2 -1
  14. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal.rb +1 -1
  15. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal_i.rb +2 -1
  16. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement.rb +1 -1
  17. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement_i.rb +2 -1
  18. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_divide.rb +1 -1
  19. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_equals.rb +1 -1
  20. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterequals.rb +1 -1
  21. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterthan.rb +1 -1
  22. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal.rb +1 -1
  23. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal_i.rb +2 -1
  24. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment.rb +1 -1
  25. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment_i.rb +2 -1
  26. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessequals.rb +1 -1
  27. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessthan.rb +1 -1
  28. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_modulo.rb +1 -1
  29. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply.rb +1 -1
  30. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply_i.rb +2 -1
  31. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate.rb +1 -1
  32. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate_i.rb +2 -1
  33. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_not.rb +1 -1
  34. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_strictequals.rb +1 -1
  35. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract.rb +1 -1
  36. data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract_i.rb +2 -1
  37. data/lib/furnace-avm2/abc/opcodes/arithmetic_opcode.rb +5 -0
  38. data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvalue.rb +1 -1
  39. data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvoid.rb +1 -1
  40. data/lib/furnace-avm2/abc/opcodes/function_return_opcode.rb +4 -0
  41. data/lib/furnace-avm2/abc/opcodes/opcode.rb +1 -1
  42. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce.rb +1 -1
  43. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_a.rb +4 -1
  44. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_b.rb +3 -2
  45. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_s.rb +3 -2
  46. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_d.rb +3 -2
  47. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_i.rb +3 -2
  48. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_o.rb +3 -2
  49. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_s.rb +3 -2
  50. data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_u.rb +3 -2
  51. data/lib/furnace-avm2/abc/opcodes/type_conversion_opcode.rb +10 -0
  52. data/lib/furnace-avm2/abc/primitives/opcode_sequence.rb +4 -0
  53. data/lib/furnace-avm2/abc/primitives/record.rb +21 -1
  54. data/lib/furnace-avm2/source/declaration_tokens/package_token.rb +16 -8
  55. data/lib/furnace-avm2/source/declaration_tokens/script_token.rb +7 -0
  56. data/lib/furnace-avm2/source/declaration_tokens/slot_token.rb +12 -4
  57. data/lib/furnace-avm2/source/declaration_tokens/token_with_traits.rb +10 -9
  58. data/lib/furnace-avm2/source/decompiler.rb +363 -166
  59. data/lib/furnace-avm2/source/implementation_tokens/case_token.rb +20 -0
  60. data/lib/furnace-avm2/source/implementation_tokens/catch_filter_token.rb +7 -0
  61. data/lib/furnace-avm2/source/implementation_tokens/catch_token.rb +9 -0
  62. data/lib/furnace-avm2/source/implementation_tokens/closure_token.rb +2 -0
  63. data/lib/furnace-avm2/source/implementation_tokens/control_flow_token.rb +0 -1
  64. data/lib/furnace-avm2/source/implementation_tokens/do_while_token.rb +16 -0
  65. data/lib/furnace-avm2/source/implementation_tokens/else_token.rb +3 -1
  66. data/lib/furnace-avm2/source/implementation_tokens/finally_token.rb +11 -0
  67. data/lib/furnace-avm2/source/implementation_tokens/label_name_token.rb +12 -0
  68. data/lib/furnace-avm2/source/implementation_tokens/this_token.rb +9 -0
  69. data/lib/furnace-avm2/source/implementation_tokens/try_token.rb +11 -0
  70. data/lib/furnace-avm2/source/implementation_tokens/unary_operator_token.rb +2 -2
  71. data/lib/furnace-avm2/source/implementation_tokens/unary_post_operator_token.rb +2 -2
  72. data/lib/furnace-avm2/transform.rb +2 -0
  73. data/lib/furnace-avm2/transform/ast_build.rb +234 -92
  74. data/lib/furnace-avm2/transform/ast_normalize.rb +4 -13
  75. data/lib/furnace-avm2/transform/cfg_build.rb +74 -33
  76. data/lib/furnace-avm2/transform/cfg_reduce.rb +457 -75
  77. data/lib/furnace-avm2/transform/nf_normalize.rb +69 -40
  78. data/lib/furnace-avm2/transform/propagate_constants.rb +49 -0
  79. data/lib/furnace-avm2/transform/propagate_labels.rb +31 -0
  80. data/lib/furnace-avm2/version.rb +1 -1
  81. data/test/basic.as +111 -3
  82. data/test/switch.as +27 -0
  83. metadata +907 -313
  84. data/Gemfile.lock +0 -19
@@ -0,0 +1,20 @@
1
+ module Furnace::AVM2::Tokens
2
+ class CaseToken < Furnace::Code::SurroundedToken
3
+ def initialize(origin, value, options)
4
+ super(origin, [ value ], options)
5
+ @value = value
6
+ end
7
+
8
+ def text_before
9
+ if @value
10
+ "case "
11
+ else
12
+ "default"
13
+ end
14
+ end
15
+
16
+ def text_after
17
+ ":\n"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module Furnace::AVM2::Tokens
2
+ class CatchFilterToken < Furnace::Code::SeparatedToken
3
+ def text_between
4
+ ": "
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ require_relative 'control_flow_token'
2
+
3
+ module Furnace::AVM2::Tokens
4
+ class CatchToken < ControlFlowToken
5
+ def keyword
6
+ "catch"
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,7 @@
1
1
  module Furnace::AVM2::Tokens
2
2
  class ClosureToken < CalleeToken
3
+ include IsComplex
4
+
3
5
  def initialize(origin, body, options={})
4
6
  super(origin, [
5
7
  ClosureNameToken.new(origin, options)
@@ -1,6 +1,5 @@
1
1
  module Furnace::AVM2::Tokens
2
2
  class ControlFlowToken < Furnace::Code::NonterminalToken
3
-
4
3
  def initialize(origin, condition, body, options={})
5
4
  super(origin, [condition, body], options)
6
5
  @condition, @body = condition, body
@@ -0,0 +1,16 @@
1
+ module Furnace::AVM2::Tokens
2
+ class DoWhileToken < Furnace::Code::NonterminalToken
3
+ def initialize(origin, body, condition, options={})
4
+ super(origin, [body, condition], options)
5
+ @condition, @body = condition, body
6
+ end
7
+
8
+ def to_text
9
+ "do #{@body.to_text}while(#{@condition.to_text});\n"
10
+ end
11
+
12
+ def to_structure(options={})
13
+ structurize "do ... while(...)", options
14
+ end
15
+ end
16
+ end
@@ -10,8 +10,10 @@ module Furnace::AVM2::Tokens
10
10
  header = "else"
11
11
  if @body.is_a? ScopeToken
12
12
  "#{header} #{@body.to_text}"
13
- else
13
+ elsif @body
14
14
  "#{header}\n#{indent @body.to_text}"
15
+ else
16
+ "#{header} "
15
17
  end
16
18
  end
17
19
 
@@ -0,0 +1,11 @@
1
+ module Furnace::AVM2::Tokens
2
+ class FinallyToken < Furnace::Code::SeparatedToken
3
+ def initialize(origin, body, options={})
4
+ super(origin, [ body ], options)
5
+ end
6
+
7
+ def text_before
8
+ "finally "
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ module Furnace::AVM2::Tokens
2
+ class LabelNameToken < Furnace::Code::TerminalToken
3
+ def initialize(origin, name, options={})
4
+ super(origin, options)
5
+ @name = name
6
+ end
7
+
8
+ def to_text
9
+ @name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module Furnace::AVM2::Tokens
2
+ class ThisToken < Furnace::Code::TerminalToken
3
+ include IsSimple
4
+
5
+ def to_text
6
+ "this"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ module Furnace::AVM2::Tokens
2
+ class TryToken < Furnace::Code::SeparatedToken
3
+ def text_before
4
+ "try "
5
+ end
6
+
7
+ def text_between
8
+ " "
9
+ end
10
+ end
11
+ end
@@ -2,8 +2,8 @@ module Furnace::AVM2::Tokens
2
2
  class UnaryOperatorToken < Furnace::Code::SurroundedToken
3
3
  include IsSimple
4
4
 
5
- def initialize(origin, children, operator, options={})
6
- super(origin, children, options)
5
+ def initialize(origin, child, operator, options={})
6
+ super(origin, [ child ], options)
7
7
  @operator = operator
8
8
  end
9
9
 
@@ -2,8 +2,8 @@ module Furnace::AVM2::Tokens
2
2
  class UnaryPostOperatorToken < Furnace::Code::SurroundedToken
3
3
  include IsSimple
4
4
 
5
- def initialize(origin, children, operator, options={})
6
- super(origin, children, options)
5
+ def initialize(origin, child, operator, options={})
6
+ super(origin, [ child ], options)
7
7
  @operator = operator
8
8
  end
9
9
 
@@ -5,6 +5,8 @@ end
5
5
 
6
6
  require_relative "transform/ast_build"
7
7
  require_relative "transform/ast_normalize"
8
+ require_relative "transform/propagate_labels"
9
+ require_relative "transform/propagate_constants"
8
10
  require_relative "transform/cfg_build"
9
11
  require_relative "transform/cfg_reduce"
10
12
  require_relative "transform/nf_normalize"
@@ -7,7 +7,15 @@ module Furnace::AVM2
7
7
  :if_le, :if_lt, :if_ne, :if_nge, :if_ngt,
8
8
  :if_nle, :if_nlt, :if_strict_eq, :if_strict_ne, :if_true ]
9
9
 
10
- CONST_OPERATORS = [ :integer, :double, :string, :false, :true, :nan, :undefined, :null ]
10
+ PURE_OPERATORS = [ :integer, :double, :string, :false, :true, :nan, :undefined, :null,
11
+ :find_property_strict ]
12
+
13
+ PRE_POST_OPERATORS = [ :increment, :increment_i, :decrement, :decrement_i ]
14
+
15
+ SHORT_ASSIGN_OPERATORS = [ :add, :add_i, :subtract, :subtract_i, :multiply, :multiply_i,
16
+ :divide, :modulo,
17
+ :set_local, :set_local_0, :set_local_1, :set_local_2, :set_local_3,
18
+ :new_catch, :new_activation ]
11
19
 
12
20
  def initialize(options)
13
21
  @validate = options[:validate] || false
@@ -16,94 +24,196 @@ module Furnace::AVM2
16
24
  #@verbose = true
17
25
  end
18
26
 
19
- def transform(code, method)
20
- stack = []
21
- ast = AST::Node.new(:root)
27
+ def consume(count)
28
+ if count == 0
29
+ []
30
+ elsif count <= @stack.size
31
+ @stack.slice!(-count..-1)
32
+ else
33
+ raise "cannot consume #{count}: stack underflow with #{@stack.size}"
34
+ end
35
+ end
22
36
 
23
- dup = nil
24
- spurious = 0
37
+ def produce(what)
38
+ @stack.push what
39
+ end
25
40
 
26
- in_shortcut = false
27
- shortjump = []
28
- ternary = []
41
+ def emit(node)
42
+ if @verbose
43
+ puts "emitted:"
44
+ puts node.to_sexp(1)
45
+ end
29
46
 
30
- consume = lambda do |count|
31
- if count == 0
32
- []
33
- elsif count <= stack.size
34
- stack.slice!(-count..-1)
35
- else
36
- raise "cannot consume #{count}: stack underflow with #{stack.size}"
47
+ @ast.children.push node
48
+ end
49
+
50
+ def unemit
51
+ @ast.children.delete(-1)
52
+ end
53
+
54
+ def extend_complex_expr(valid_types, expected_depth=nil)
55
+ expr, current = consume(2)
56
+
57
+ if @validate
58
+ if !valid_types.include?(expr.type)
59
+ raise "invalid complex expr: #{expr.type} not in #{valid_types}"
60
+ elsif expected_depth && expr.children.count != expected_depth
61
+ raise "invalid complex expr: depth #{expr.children.count} != #{expected_depth}"
37
62
  end
38
63
  end
39
64
 
40
- produce = lambda do |what|
41
- stack.push what
42
- end
65
+ expr.children << current
66
+ produce(expr)
67
+ end
43
68
 
44
- emit = lambda do |node|
45
- if @verbose
46
- puts "emitted:"
47
- puts node.to_sexp(1)
69
+ def finalize_complex_expr(opcode, worklist, valid_types, expected_depth=nil, wrap_to=nil)
70
+ while worklist.last == opcode.offset
71
+ extend_complex_expr(valid_types, expected_depth)
72
+
73
+ if wrap_to
74
+ node, *prepend = *wrap_to
75
+
76
+ expr, = consume(1)
77
+ expr = AST::Node.new(node, [*prepend, expr])
78
+ produce(expr)
48
79
  end
49
80
 
50
- ast.children.push node
81
+ worklist.pop
51
82
  end
83
+ end
52
84
 
53
- extend_complex_expr = lambda do |valid_types, expected_depth=nil|
54
- expr, current = consume.(2)
85
+ def expand_conditionals
86
+ expressions = []
55
87
 
56
- if @validate
57
- if !valid_types.include?(expr.type)
58
- raise "invalid complex expr: #{expr.type} not in #{valid_types}"
59
- elsif expected_depth && expr.children.count != expected_depth
60
- raise "invalid complex expr: depth #{expr.children.count} != #{expected_depth}"
61
- end
62
- end
88
+ while @stack.any? && CONDITIONAL_OPERATORS.include?(@stack.last.type)
89
+ conditional, = consume(1)
90
+
91
+ jump_node = AST::Node.new(:jump_if, [
92
+ true,
93
+ conditional.metadata[:offset],
94
+ conditional
95
+ ], label: conditional.metadata[:label])
96
+
97
+ expressions.unshift jump_node
98
+ end
63
99
 
64
- expr.children << current
65
- produce.(expr)
100
+ expressions.each do |expr|
101
+ emit(expr)
66
102
  end
103
+ end
67
104
 
68
- finalize_complex_expr = lambda do |opcode, worklist, valid_types, expected_depth=nil, wrap_to=nil|
69
- while worklist.last == opcode.offset
70
- extend_complex_expr.(valid_types, expected_depth)
105
+ LocalIncDecInnerMatcher = AST::Matcher.new do
106
+ [ either[*PRE_POST_OPERATORS],
107
+ either[
108
+ [:convert, any,
109
+ [:get_local, any]],
110
+ [:get_local, any],
111
+ ]
112
+ ]
113
+ end
71
114
 
72
- if wrap_to
73
- node, *prepend = *wrap_to
115
+ def transform(code, body)
116
+ @stack = []
117
+ @ast = AST::Node.new(:root)
74
118
 
75
- expr, = consume.(1)
76
- expr = AST::Node.new(node, [*prepend, expr])
77
- produce.(expr)
78
- end
119
+ dup = nil
120
+ spurious = 0
79
121
 
80
- worklist.pop
81
- end
82
- end
122
+ in_shortcut = false
123
+ shortjump = []
124
+ ternary = []
83
125
 
84
- expand_conditionals = lambda do
85
- expressions = []
126
+ current_handler = nil
127
+ current_finally = nil
128
+
129
+ finallies = {}
130
+ body.exceptions.each_with_index do |exception, index|
131
+ first, second = exception, body.exceptions[index + 1]
132
+ next unless second
133
+
134
+ if first.from_offset == second.from_offset &&
135
+ second.to_offset > first.to_offset &&
136
+ first.target_offset > second.from_offset &&
137
+ first.target_offset < second.to_offset &&
138
+ first.to.is_a?(ABC::AS3Jump) &&
139
+ first.to.target.is_a?(ABC::AS3PushByte)
140
+ entry = first.to.target.next.target
141
+ epilogue = nil
142
+
143
+ cursor = entry.next
144
+ while cursor
145
+ if cursor.is_a?(ABC::AS3LookupSwitch) &&
146
+ cursor.body.default_offset == 8 &&
147
+ cursor.body.case_count == 0
148
+ epilogue = cursor
149
+ break
150
+ end
86
151
 
87
- while stack.any? && CONDITIONAL_OPERATORS.include?(stack.last.type)
88
- conditional, = consume.(1)
152
+ cursor = cursor.next
153
+ end
89
154
 
90
- jump_node = AST::Node.new(:jump_if, [ true, conditional.metadata[:offset], conditional ])
91
- expressions.unshift jump_node
155
+ raise "cannot find finally epilogue" if epilogue.nil?
156
+
157
+ finallies[first.to_offset] = {
158
+ first_catch: first,
159
+ second_catch: second,
160
+ skip_intervals: [ first.target_offset...second.target_offset,
161
+ (second.target_offset + 4)...entry.offset ],
162
+ begin_offset: first.to.offset,
163
+ entry_offset: entry.offset,
164
+ epilogue_offset: epilogue.offset,
165
+ end_offset: epilogue.offset + epilogue.byte_length,
166
+ }
92
167
  end
168
+ end
93
169
 
94
- ast.children.concat expressions
170
+ exceptions = {}
171
+ body.exceptions.each_with_index do |exception, index|
172
+ next if finallies.find { |k,f| f[:first_catch] == exception }
173
+ exceptions[exception.target_offset] = exception
95
174
  end
96
175
 
97
176
  code.each do |opcode|
98
177
  if @verbose
99
178
  puts "================================"
100
- puts "stack: #{stack.inspect}"
179
+ puts "stack: #{@stack.inspect}"
101
180
  puts "shortjump: #{shortjump.inspect} ternary: #{ternary.inspect}"
102
181
  puts "opcode: #{opcode.inspect}"
103
182
  end
104
183
 
105
- finalize_complex_expr.(opcode, ternary, CONDITIONAL_OPERATORS, nil, [:ternary_if, false])
106
- finalize_complex_expr.(opcode, shortjump, [ :and, :or ], 1)
184
+ finalize_complex_expr(opcode, ternary, CONDITIONAL_OPERATORS, nil, [:ternary_if, false])
185
+ finalize_complex_expr(opcode, shortjump, [ :and, :or ], 1)
186
+
187
+ if finallies.has_key? opcode.offset
188
+ current_finally = finallies[opcode.offset]
189
+ end
190
+
191
+ if current_finally
192
+ if current_finally[:begin_offset] == opcode.offset
193
+ puts "FINALLY: begin" if @verbose
194
+ emit(AST::Node.new(:jump, [ current_finally[:end_offset] ], label: opcode.offset))
195
+ next
196
+ elsif current_finally[:skip_intervals].find { |si| si.include? opcode.offset }
197
+ puts "FINALLY: skip" if @verbose
198
+ next
199
+ elsif current_finally[:epilogue_offset] == opcode.offset
200
+ puts "FINALLY: epilogue" if @verbose
201
+ emit(AST::Node.new(:nop, [], label: opcode.offset))
202
+ @stack.clear
203
+ next
204
+ end
205
+ end
206
+
207
+ if exceptions.has_key? opcode.offset
208
+ exception = exceptions[opcode.offset]
209
+
210
+ current_handler = exception
211
+ if exception.variable
212
+ produce(AST::Node.new(:exception_variable, [ exception.variable.to_astlet ]))
213
+ else
214
+ produce(AST::Node.new(:exception_variable))
215
+ end
216
+ end
107
217
 
108
218
  if dup == 1 && (opcode.is_a?(ABC::AS3CoerceB) ||
109
219
  opcode.is_a?(ABC::AS3IfTrue) || opcode.is_a?(ABC::AS3IfFalse))
@@ -129,21 +239,21 @@ module Furnace::AVM2
129
239
  end
130
240
 
131
241
  node = AST::Node.new(type, [], label: opcode.offset)
132
- node.children = consume.(1)
133
- produce.(node)
242
+ node.children = consume(1)
243
+ produce(node)
134
244
 
135
245
  shortjump.push opcode.target_offset
136
246
  elsif opcode.is_a?(ABC::AS3Swap)
137
- first, second = stack.pop, stack.pop
138
- stack.push first, second
247
+ first, second = @stack.pop, @stack.pop
248
+ @stack.push first, second
139
249
  elsif opcode.is_a?(ABC::AS3Dup)
140
- node = stack.last
250
+ node = @stack.last
141
251
 
142
- if CONST_OPERATORS.include?(node.type) ||
252
+ if PURE_OPERATORS.include?(node.type) ||
143
253
  (node.type == :get_local && node.children.first == 0)
144
254
  dup_node = node.dup
145
255
  dup_node.metadata[:label] = opcode.offset
146
- produce.(dup_node)
256
+ produce(dup_node)
147
257
  else
148
258
  dup ||= 0
149
259
  dup += 1
@@ -151,44 +261,68 @@ module Furnace::AVM2
151
261
  elsif opcode.is_a?(ABC::AS3Jump)
152
262
  if opcode.body.jump_offset == 0
153
263
  node = AST::Node.new(:nop, [], label: opcode.offset)
154
- emit.(node)
155
- elsif stack.any? && !CONDITIONAL_OPERATORS.include?(stack.last.type)
156
- extend_complex_expr.(CONDITIONAL_OPERATORS)
264
+ emit(node)
265
+ elsif opcode.target.is_a? ABC::AS3Throw
266
+ node = AST::Node.new(:throw, [ consume(1) ], label: opcode.offset)
267
+ emit(node)
268
+ elsif @stack.any? && !CONDITIONAL_OPERATORS.include?(@stack.last.type)
269
+ extend_complex_expr(CONDITIONAL_OPERATORS)
157
270
 
158
271
  ternary.push opcode.target_offset
159
272
  else
160
- expand_conditionals.()
273
+ expand_conditionals()
161
274
 
162
275
  node = AST::Node.new(opcode.ast_type, opcode.parameters, label: opcode.offset)
163
- emit.(node)
276
+ emit(node)
164
277
  end
165
278
  elsif opcode.is_a?(ABC::ControlTransferOpcode)
166
279
  node = AST::Node.new(opcode.ast_type, [], label: opcode.offset)
167
280
  node.metadata[:offset] = opcode.target_offset
168
- node.children = consume.(opcode.consumes)
169
- produce.(node)
170
- elsif opcode.is_a?(ABC::AS3Kill)
171
- # Ignore. SSA will handle that.
281
+ node.children = consume(opcode.consumes)
282
+ produce(node)
172
283
  else
284
+ node = AST::Node.new(opcode.ast_type, [], label: opcode.offset)
285
+
286
+ if dup == 1
287
+ top_opcode, = consume(1)
288
+ found = true
289
+
290
+ if PRE_POST_OPERATORS.include?(top_opcode.type)
291
+ top_opcode.update(:"pre_#{top_opcode.type}")
292
+ elsif PRE_POST_OPERATORS.include?(node.type)
293
+ node.update(:"post_#{node.type}")
294
+ elsif SHORT_ASSIGN_OPERATORS.include? top_opcode.type
295
+ # just push it through
296
+ else
297
+ found = false
298
+ end
299
+
300
+ if found
301
+ produce(AST::Node.new(:unemit))
302
+ dup = false
303
+ end
304
+
305
+ produce(top_opcode)
306
+ end
307
+
173
308
  if dup
174
309
  spurious += 1
175
310
 
176
- save_node = AST::Node.new(:set_local, [ -spurious, *consume.(1) ])
177
- emit.(save_node)
311
+ save_node = AST::Node.new(:set_local, [ -spurious, *consume(1) ])
312
+ expand_conditionals()
313
+ emit(save_node)
178
314
 
179
315
  (1 + dup).times do
180
316
  load_node = AST::Node.new(:get_local, [ -spurious ])
181
- produce.(load_node)
317
+ produce(load_node)
182
318
  end
183
319
 
184
320
  dup = false
185
321
  end
186
322
 
187
- node = AST::Node.new(opcode.ast_type, [], label: opcode.offset)
188
-
189
- parameters = consume.(opcode.consumes)
323
+ parameters = consume(opcode.consumes)
190
324
  if opcode.consumes_context
191
- context = opcode.context(consume.(opcode.consumes_context))
325
+ context = opcode.context(consume(opcode.consumes_context))
192
326
  end
193
327
 
194
328
  node.children.concat context if context
@@ -198,23 +332,31 @@ module Furnace::AVM2
198
332
  # All opcodes which produce 2 results--that is, dup and swap--
199
333
  # are already handled at the top.
200
334
  if opcode.produces == 0
201
- expand_conditionals.()
202
- emit.(node)
335
+ # This was a fallthrough assignment.
336
+ if @stack.any? && @stack.last.type == :unemit
337
+ consume(1) # dump unemit
338
+ produce(node)
339
+ next
340
+ end
341
+
342
+ expand_conditionals()
343
+
344
+ # Spec does not require stack to be empty upon encountering return.
345
+ # If there's something left, it should have been here.
346
+ if opcode.is_a? ABC::FunctionReturnOpcode
347
+ while @stack.any?
348
+ emit(*consume(1))
349
+ end
350
+ end
351
+
352
+ emit(node)
203
353
  elsif opcode.produces == 1
204
- produce.(node)
354
+ produce(node)
205
355
  end
206
356
  end
207
357
  end
208
358
 
209
- if @validate
210
- extracted_opcodes = []
211
- extract = lambda do |tree|
212
- extracted_opcodes.push tree.metadata[:label]
213
- tree.select { |c| c.is_a? Node }.each { |c| extract.(c) }
214
- end
215
- end
216
-
217
- ast.normalize_hierarchy!
359
+ [ @ast.normalize_hierarchy!, body, finallies.values ]
218
360
  end
219
361
  end
220
362
  end