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.
- data/.gitignore +1 -0
- data/Gemfile +3 -1
- data/bin/furnace-avm2 +27 -13
- data/bin/furnace-avm2-decompiler +14 -4
- data/bin/furnace-avm2-shell +4 -0
- data/furnace-avm2.gemspec +1 -1
- data/lib/furnace-avm2.rb +1 -1
- data/lib/furnace-avm2/abc.rb +3 -0
- data/lib/furnace-avm2/abc/metadata/exception_info.rb +7 -2
- data/lib/furnace-avm2/abc/metadata/method_body_info.rb +4 -2
- data/lib/furnace-avm2/abc/metadata/script_info.rb +8 -3
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_add_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_declocal_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_decrement_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_divide.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_equals.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterequals.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_greaterthan.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_inclocal_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_increment_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessequals.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_lessthan.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_modulo.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_multiply_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_negate_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_not.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_strictequals.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic/as3_subtract_i.rb +2 -1
- data/lib/furnace-avm2/abc/opcodes/arithmetic_opcode.rb +5 -0
- data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvalue.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/function_return/as3_returnvoid.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/function_return_opcode.rb +4 -0
- data/lib/furnace-avm2/abc/opcodes/opcode.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce.rb +1 -1
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_a.rb +4 -1
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_b.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_coerce_s.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_d.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_i.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_o.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_s.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion/as3_convert_u.rb +3 -2
- data/lib/furnace-avm2/abc/opcodes/type_conversion_opcode.rb +10 -0
- data/lib/furnace-avm2/abc/primitives/opcode_sequence.rb +4 -0
- data/lib/furnace-avm2/abc/primitives/record.rb +21 -1
- data/lib/furnace-avm2/source/declaration_tokens/package_token.rb +16 -8
- data/lib/furnace-avm2/source/declaration_tokens/script_token.rb +7 -0
- data/lib/furnace-avm2/source/declaration_tokens/slot_token.rb +12 -4
- data/lib/furnace-avm2/source/declaration_tokens/token_with_traits.rb +10 -9
- data/lib/furnace-avm2/source/decompiler.rb +363 -166
- data/lib/furnace-avm2/source/implementation_tokens/case_token.rb +20 -0
- data/lib/furnace-avm2/source/implementation_tokens/catch_filter_token.rb +7 -0
- data/lib/furnace-avm2/source/implementation_tokens/catch_token.rb +9 -0
- data/lib/furnace-avm2/source/implementation_tokens/closure_token.rb +2 -0
- data/lib/furnace-avm2/source/implementation_tokens/control_flow_token.rb +0 -1
- data/lib/furnace-avm2/source/implementation_tokens/do_while_token.rb +16 -0
- data/lib/furnace-avm2/source/implementation_tokens/else_token.rb +3 -1
- data/lib/furnace-avm2/source/implementation_tokens/finally_token.rb +11 -0
- data/lib/furnace-avm2/source/implementation_tokens/label_name_token.rb +12 -0
- data/lib/furnace-avm2/source/implementation_tokens/this_token.rb +9 -0
- data/lib/furnace-avm2/source/implementation_tokens/try_token.rb +11 -0
- data/lib/furnace-avm2/source/implementation_tokens/unary_operator_token.rb +2 -2
- data/lib/furnace-avm2/source/implementation_tokens/unary_post_operator_token.rb +2 -2
- data/lib/furnace-avm2/transform.rb +2 -0
- data/lib/furnace-avm2/transform/ast_build.rb +234 -92
- data/lib/furnace-avm2/transform/ast_normalize.rb +4 -13
- data/lib/furnace-avm2/transform/cfg_build.rb +74 -33
- data/lib/furnace-avm2/transform/cfg_reduce.rb +457 -75
- data/lib/furnace-avm2/transform/nf_normalize.rb +69 -40
- data/lib/furnace-avm2/transform/propagate_constants.rb +49 -0
- data/lib/furnace-avm2/transform/propagate_labels.rb +31 -0
- data/lib/furnace-avm2/version.rb +1 -1
- data/test/basic.as +111 -3
- data/test/switch.as +27 -0
- metadata +907 -313
- data/Gemfile.lock +0 -19
@@ -122,7 +122,7 @@ module Furnace::AVM2::ABC
|
|
122
122
|
end
|
123
123
|
|
124
124
|
def disassemble
|
125
|
-
" #{offset.to_s.rjust(4, "0")} #{self.class.mnemonic.rjust(20, " ")} #{disassemble_parameters}
|
125
|
+
" #{offset.to_s.rjust(4, "0")} #{self.class.mnemonic.rjust(20, " ")} #{disassemble_parameters}"
|
126
126
|
end
|
127
127
|
alias :inspect :disassemble
|
128
128
|
end
|
@@ -37,7 +37,27 @@ module Furnace::AVM2::ABC
|
|
37
37
|
elsif index = pool.index(value)
|
38
38
|
send(:"#{field}=", index + 1)
|
39
39
|
else
|
40
|
-
|
40
|
+
format_entry = ConstPoolInfo.format.find { |f| f[1] == array }
|
41
|
+
if format_entry.nil?
|
42
|
+
raise "cpool setter: [BUG] no such cpool field"
|
43
|
+
end
|
44
|
+
|
45
|
+
klass = nil
|
46
|
+
case format_entry[2][:type]
|
47
|
+
when :vstring; klass = String
|
48
|
+
when :nested; klass = format_entry[2][:class]
|
49
|
+
end
|
50
|
+
|
51
|
+
if klass.nil?
|
52
|
+
raise "cpool setter: [BUG] no checker for type #{format_entry[2][:type]}"
|
53
|
+
end
|
54
|
+
|
55
|
+
if value.class != klass
|
56
|
+
raise "cpool setter: trying to set wrong kind of value (#{value.class} instead of #{klass})"
|
57
|
+
end
|
58
|
+
|
59
|
+
pool.push value
|
60
|
+
send(:"#{field}=", pool.index(value) + 1)
|
41
61
|
end
|
42
62
|
end
|
43
63
|
end
|
@@ -11,19 +11,27 @@ module Furnace::AVM2::Tokens
|
|
11
11
|
ns.name !~ /^[A-Za-z0-9_$.]+$/
|
12
12
|
}
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
case options[:package_type]
|
15
|
+
when :class, :interface
|
16
|
+
content = ClassToken.new(origin, options)
|
17
|
+
when :script
|
18
|
+
content = ScriptToken.new(origin, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
scope = nil
|
22
|
+
if content.children.any?
|
23
|
+
scope = ScopeToken.new(origin, [
|
17
24
|
*import_ns.map { |ns|
|
18
25
|
ImportToken.new(origin, ns.name, options)
|
19
26
|
},
|
20
27
|
(Furnace::Code::NewlineToken.new(origin, options) if import_ns.any?),
|
21
|
-
|
22
|
-
when :class; ClassToken.new(origin, options)
|
23
|
-
when :interface; ClassToken.new(origin, options)
|
24
|
-
when :script; ScriptToken.new(origin, options)
|
25
|
-
end)
|
28
|
+
content
|
26
29
|
], options)
|
30
|
+
end
|
31
|
+
|
32
|
+
super(origin, [
|
33
|
+
(PackageNameToken.new(origin, options[:package_name].ns.name, options) if options[:package_name]),
|
34
|
+
scope,
|
27
35
|
], options)
|
28
36
|
end
|
29
37
|
end
|
@@ -12,6 +12,13 @@ module Furnace::AVM2::Tokens
|
|
12
12
|
*transform_traits(origin, options.merge(static: false)),
|
13
13
|
(global_code if global_code.children.any?)
|
14
14
|
], options)
|
15
|
+
|
16
|
+
if options[:debug_funids] && global_code.children.any?
|
17
|
+
@children.unshift \
|
18
|
+
CommentToken.new(origin,
|
19
|
+
"Function ##{origin.initializer_idx}",
|
20
|
+
options)
|
21
|
+
end
|
15
22
|
end
|
16
23
|
end
|
17
24
|
end
|
@@ -10,10 +10,18 @@ module Furnace::AVM2::Tokens
|
|
10
10
|
], options) if origin.type)
|
11
11
|
], options)
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
],
|
13
|
+
value = nil
|
14
|
+
|
15
|
+
if options[:property_values]
|
16
|
+
*, value = options[:property_values].find { |k,v| k == origin.name.to_astlet }
|
17
|
+
end
|
18
|
+
|
19
|
+
if value.nil? && origin.printable_value
|
20
|
+
value = ImmediateToken.new(origin, origin.printable_value, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
if value
|
24
|
+
@children << InitializationToken.new(origin, [ value ], @options)
|
17
25
|
end
|
18
26
|
end
|
19
27
|
|
@@ -7,21 +7,22 @@ module Furnace::AVM2::Tokens
|
|
7
7
|
[:Class, :Slot, :Const].include? trait.kind
|
8
8
|
end
|
9
9
|
|
10
|
+
if options[:environment] == :class && options[:static]
|
11
|
+
if origin.initializer_body
|
12
|
+
properties = Furnace::AVM2::Decompiler.new(
|
13
|
+
origin.initializer_body, options).decompose_static_initializer
|
14
|
+
options = options.merge(property_values: properties)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
10
18
|
tokens += vars.map { |trait| transform_trait trait, options }
|
11
19
|
|
12
20
|
if tokens.any?
|
13
21
|
tokens << Furnace::Code::NewlineToken.new(origin, options)
|
14
22
|
end
|
15
23
|
|
16
|
-
if options[:environment] == :class
|
17
|
-
|
18
|
-
tokens << CommentToken.new(origin, "Static initializer", options)
|
19
|
-
tokens << CommentToken.new(origin,
|
20
|
-
ConstructorToken.new(origin, options.merge(commented: true)),
|
21
|
-
options)
|
22
|
-
else
|
23
|
-
tokens << ConstructorToken.new(origin, options)
|
24
|
-
end
|
24
|
+
if options[:environment] == :class && !options[:static]
|
25
|
+
tokens << ConstructorToken.new(origin, options)
|
25
26
|
end
|
26
27
|
|
27
28
|
tokens += methods.map { |trait| transform_trait trait, options }
|
@@ -17,55 +17,24 @@ module Furnace::AVM2
|
|
17
17
|
@closure = @options.delete(:closure)
|
18
18
|
end
|
19
19
|
|
20
|
-
ActivationPrologue = Matcher.new do
|
21
|
-
[:begin,
|
22
|
-
[:push_scope,
|
23
|
-
[:get_local, 0]],
|
24
|
-
[:set_local, -1,
|
25
|
-
[:new_activation]],
|
26
|
-
[:set_local, capture(:activation_local),
|
27
|
-
[:get_local, -1]],
|
28
|
-
[:push_scope,
|
29
|
-
[:get_local, -1]],
|
30
|
-
skip
|
31
|
-
]
|
32
|
-
end
|
33
|
-
|
34
|
-
RegularPrologue = Matcher.new do
|
35
|
-
[:begin,
|
36
|
-
[:push_scope,
|
37
|
-
[:get_local, 0]],
|
38
|
-
skip
|
39
|
-
]
|
40
|
-
end
|
41
|
-
|
42
20
|
def decompile
|
43
21
|
begin
|
44
22
|
@locals = Set.new([0]) + (1..@method.param_count).to_a
|
45
23
|
@scopes = []
|
24
|
+
@metascopes = []
|
46
25
|
|
47
|
-
@
|
48
|
-
|
49
|
-
if captures = ActivationPrologue.match(@nf)
|
50
|
-
@closure_slots = {}
|
51
|
-
@body.slot_traits.each do |trait|
|
52
|
-
@closure_slots[trait.idx] = trait
|
53
|
-
end
|
26
|
+
@catch_scopes = {}
|
54
27
|
|
55
|
-
|
28
|
+
@closure_locals = Set.new
|
56
29
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
@nf.children.slice! 0...1
|
61
|
-
else
|
62
|
-
# No prologue; probably a closure.
|
63
|
-
end
|
30
|
+
@closure_slots = {}
|
31
|
+
@body.slot_traits.each do |trait|
|
32
|
+
@closure_slots[trait.idx] = trait
|
64
33
|
end
|
65
34
|
|
66
|
-
@global_slots = @options[:global_slots]
|
35
|
+
@global_slots = @options[:global_slots] || {}
|
67
36
|
|
68
|
-
stmt_block @
|
37
|
+
stmt_block @body.code_to_nf,
|
69
38
|
function: !@options[:global_code],
|
70
39
|
closure: @closure
|
71
40
|
|
@@ -97,19 +66,50 @@ module Furnace::AVM2
|
|
97
66
|
end
|
98
67
|
end
|
99
68
|
|
69
|
+
StaticProperty = Matcher.new do
|
70
|
+
[ either[:set_property, :init_property],
|
71
|
+
[:find_property, capture(:property)],
|
72
|
+
backref(:property),
|
73
|
+
capture(:value)
|
74
|
+
]
|
75
|
+
end
|
76
|
+
|
77
|
+
def decompose_static_initializer
|
78
|
+
properties = {}
|
79
|
+
|
80
|
+
StaticProperty.find_all(@body.code_to_nf.children) do |match, captures|
|
81
|
+
begin
|
82
|
+
token = handle_expression(captures[:value])
|
83
|
+
rescue ExpressionNotRecognized => e
|
84
|
+
token = token(CommentToken, "Unrecognized static initializer:\n#{e.opcode.inspect}")
|
85
|
+
end
|
86
|
+
|
87
|
+
properties[captures[:property]] = token
|
88
|
+
end
|
89
|
+
|
90
|
+
properties
|
91
|
+
end
|
92
|
+
|
100
93
|
# Statements
|
101
94
|
|
102
95
|
def stmt_block(block, options={})
|
103
96
|
nodes = []
|
97
|
+
last_index = 0
|
98
|
+
|
99
|
+
block.children.each_with_index do |opcode, index|
|
100
|
+
last_index = index
|
104
101
|
|
105
|
-
block.children.each do |opcode|
|
106
102
|
if respond_to?(:"stmt_#{opcode.type}")
|
107
103
|
send :"stmt_#{opcode.type}", opcode, nodes
|
108
104
|
else
|
109
105
|
catch(:skip) do
|
110
|
-
|
106
|
+
@collected_vars = []
|
107
|
+
stmt = token(StatementToken, [
|
111
108
|
handle_expression(opcode)
|
112
109
|
])
|
110
|
+
|
111
|
+
nodes.concat @collected_vars
|
112
|
+
nodes.push stmt
|
113
113
|
end
|
114
114
|
end
|
115
115
|
end
|
@@ -121,9 +121,9 @@ module Furnace::AVM2
|
|
121
121
|
"Expression recognizer failed at:\n" +
|
122
122
|
"#{e.opcode.inspect}\n"
|
123
123
|
|
124
|
-
|
125
|
-
|
126
|
-
|
124
|
+
comment << "\nRest of the code in this block:\n"
|
125
|
+
block.children[last_index..-1].each do |opcode|
|
126
|
+
comment << "#{opcode.inspect}\n"
|
127
127
|
end
|
128
128
|
|
129
129
|
nodes << CommentToken.new(@body, comment, @options)
|
@@ -134,13 +134,28 @@ module Furnace::AVM2
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
137
|
+
def stmt_begin(opcode, nodes)
|
138
|
+
nodes << stmt_block(opcode)
|
139
|
+
end
|
140
|
+
|
137
141
|
def stmt_if(opcode, nodes)
|
138
142
|
condition, if_true, if_false = opcode.children
|
139
143
|
|
140
144
|
nodes << token(IfToken, handle_expression(condition),
|
141
145
|
stmt_block(if_true, continuation: !if_false.nil?))
|
142
|
-
|
143
|
-
|
146
|
+
|
147
|
+
if if_false
|
148
|
+
first_child = if_false.children.first
|
149
|
+
if if_false.children.count == 1 &&
|
150
|
+
first_child.type == :if
|
151
|
+
nodes << token(ElseToken,
|
152
|
+
nil)
|
153
|
+
stmt_if(first_child, nodes)
|
154
|
+
else
|
155
|
+
nodes << token(ElseToken,
|
156
|
+
stmt_block(if_false))
|
157
|
+
end
|
158
|
+
end
|
144
159
|
end
|
145
160
|
|
146
161
|
def stmt_label(opcode, nodes)
|
@@ -152,10 +167,19 @@ module Furnace::AVM2
|
|
152
167
|
def stmt_while(opcode, nodes)
|
153
168
|
condition, body = opcode.children
|
154
169
|
|
155
|
-
nodes << token(WhileToken,
|
170
|
+
nodes << token(WhileToken,
|
171
|
+
handle_expression(condition),
|
156
172
|
stmt_block(body))
|
157
173
|
end
|
158
174
|
|
175
|
+
def stmt_do_while(opcode, nodes)
|
176
|
+
condition, body = opcode.children
|
177
|
+
|
178
|
+
nodes << token(DoWhileToken,
|
179
|
+
stmt_block(body, continuation: true),
|
180
|
+
handle_expression(condition))
|
181
|
+
end
|
182
|
+
|
159
183
|
def stmt_for(opcode, nodes)
|
160
184
|
value_reg, value_type, object_reg, body = opcode.children
|
161
185
|
|
@@ -167,10 +191,10 @@ module Furnace::AVM2
|
|
167
191
|
klass = ForEachToken
|
168
192
|
end
|
169
193
|
|
170
|
-
if @
|
194
|
+
if @activation_local
|
171
195
|
name = token(VariableNameToken, @closure_slots[value_reg].name.name)
|
172
196
|
else
|
173
|
-
name =
|
197
|
+
name = local_token(value_reg)
|
174
198
|
end
|
175
199
|
|
176
200
|
nodes << token(klass,
|
@@ -179,7 +203,7 @@ module Furnace::AVM2
|
|
179
203
|
name,
|
180
204
|
type_token(value_type)
|
181
205
|
]),
|
182
|
-
|
206
|
+
local_token(object_reg),
|
183
207
|
]),
|
184
208
|
stmt_block(body))
|
185
209
|
end
|
@@ -187,11 +211,17 @@ module Furnace::AVM2
|
|
187
211
|
alias :stmt_for_each_in :stmt_for
|
188
212
|
|
189
213
|
def stmt_break(opcode, nodes)
|
190
|
-
|
214
|
+
label, = opcode.children
|
215
|
+
nodes << token(BreakToken, [
|
216
|
+
(token(LabelNameToken, label) if label)
|
217
|
+
])
|
191
218
|
end
|
192
219
|
|
193
220
|
def stmt_continue(opcode, nodes)
|
194
|
-
|
221
|
+
label, = opcode.children
|
222
|
+
nodes << token(ContinueToken, [
|
223
|
+
(token(LabelNameToken, label) if label)
|
224
|
+
])
|
195
225
|
end
|
196
226
|
|
197
227
|
def stmt_throw(opcode, nodes)
|
@@ -204,31 +234,127 @@ module Furnace::AVM2
|
|
204
234
|
alias :stmt_return_value :stmt_return
|
205
235
|
alias :stmt_return_void :stmt_return
|
206
236
|
|
237
|
+
def stmt_try(opcode, nodes)
|
238
|
+
body, *handlers = opcode.children
|
239
|
+
|
240
|
+
nodes << token(TryToken, [
|
241
|
+
stmt_block(body, continuation: true),
|
242
|
+
])
|
243
|
+
|
244
|
+
handlers.each_with_index do |handler, index|
|
245
|
+
block = within_meta_scope do
|
246
|
+
stmt_block(handler.children.last, continuation: index < handlers.size - 1)
|
247
|
+
end
|
248
|
+
|
249
|
+
if handler.type == :catch
|
250
|
+
type, variable, = handler.children
|
251
|
+
|
252
|
+
if type
|
253
|
+
filter_node = token(CatchFilterToken, [
|
254
|
+
token(MultinameToken, variable.metadata[:origin]),
|
255
|
+
token(MultinameToken, type.metadata[:origin])
|
256
|
+
])
|
257
|
+
else
|
258
|
+
filter_node = token(MultinameToken, variable.metadata[:origin])
|
259
|
+
end
|
260
|
+
|
261
|
+
nodes << token(CatchToken, filter_node, block)
|
262
|
+
elsif handler.type == :finally
|
263
|
+
nodes << token(FinallyToken, block)
|
264
|
+
else
|
265
|
+
raise "unknown handler type #{handler.type}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def stmt_switch(opcode, nodes)
|
271
|
+
condition, body = opcode.children
|
272
|
+
|
273
|
+
nodes << token(SwitchToken,
|
274
|
+
handle_expression(condition),
|
275
|
+
stmt_block(body))
|
276
|
+
end
|
277
|
+
|
278
|
+
def stmt_default(opcode, nodes)
|
279
|
+
nodes << token(CaseToken, nil)
|
280
|
+
end
|
281
|
+
|
282
|
+
def stmt_case(opcode, nodes)
|
283
|
+
value, = opcode.children
|
284
|
+
nodes << token(CaseToken, handle_expression(value))
|
285
|
+
end
|
286
|
+
|
287
|
+
def within_meta_scope
|
288
|
+
@metascopes.push @scopes
|
289
|
+
@scopes = []
|
290
|
+
|
291
|
+
yield
|
292
|
+
ensure
|
293
|
+
@scopes = @metascopes.pop
|
294
|
+
end
|
295
|
+
|
296
|
+
KnownPushScopeMatcher = AST::Matcher.new do
|
297
|
+
[:push_scope,
|
298
|
+
either[
|
299
|
+
[:get_local, capture(:get_local)],
|
300
|
+
[:set_local, capture(:set_activation_local),
|
301
|
+
[:new_activation]]
|
302
|
+
]
|
303
|
+
]
|
304
|
+
end
|
305
|
+
|
207
306
|
def stmt_push_scope(opcode, nodes)
|
208
307
|
if @options[:global_code]
|
209
308
|
@scopes.push opcode.children.first
|
309
|
+
elsif captures = KnownPushScopeMatcher.match(opcode)
|
310
|
+
if captures[:get_local] == 0
|
311
|
+
@scopes << :this
|
312
|
+
elsif !@activation_local.nil? &&
|
313
|
+
captures[:get_local] == @activation_local
|
314
|
+
@scopes << :activation
|
315
|
+
elsif @catch_scopes.include? captures[:get_local]
|
316
|
+
@scopes << @catch_scopes[captures[:get_local]]
|
317
|
+
elsif captures[:set_activation_local]
|
318
|
+
if @activation_local
|
319
|
+
raise "more than one activation per function is not supported"
|
320
|
+
end
|
321
|
+
|
322
|
+
@scopes << :activation
|
323
|
+
@activation_local = captures[:set_activation_local]
|
324
|
+
else
|
325
|
+
raise "abnormal matched pushscope in nonglobal code: #{captures.inspect}"
|
326
|
+
end
|
210
327
|
else
|
211
|
-
raise "pushscope in nonglobal code"
|
328
|
+
raise "abnormal pushscope in nonglobal code"
|
212
329
|
end
|
213
330
|
end
|
214
331
|
|
215
332
|
def stmt_pop_scope(opcode, nodes)
|
216
333
|
if @options[:global_code]
|
217
334
|
@scopes.pop
|
335
|
+
elsif @scopes.any?
|
336
|
+
@scopes.pop
|
218
337
|
else
|
219
|
-
raise "popscope
|
338
|
+
raise "popscope with empty stack"
|
220
339
|
end
|
221
340
|
end
|
222
341
|
|
223
342
|
# Expressions
|
224
343
|
|
225
344
|
def handle_expression(opcode)
|
226
|
-
expression(opcode)
|
345
|
+
expression(opcode, true)
|
227
346
|
rescue ExpressionNotRecognized => e
|
228
347
|
raise ExpressionNotRecognized.new(opcode, e.opcode)
|
229
348
|
end
|
230
349
|
|
231
|
-
def expression(opcode)
|
350
|
+
def expression(opcode, toplevel=false)
|
351
|
+
if toplevel
|
352
|
+
handler = :"expr_#{opcode.type}_toplevel"
|
353
|
+
if respond_to?(handler) && node = send(handler, opcode)
|
354
|
+
return node
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
232
358
|
handler = :"expr_#{opcode.type}"
|
233
359
|
if respond_to?(handler) && node = send(handler, opcode)
|
234
360
|
node
|
@@ -287,59 +413,63 @@ module Furnace::AVM2
|
|
287
413
|
|
288
414
|
## Locals
|
289
415
|
|
290
|
-
def
|
416
|
+
def local_token(index)
|
291
417
|
if index < 0
|
292
|
-
"sp#{-index}"
|
418
|
+
token(VariableNameToken, "sp#{-index}")
|
293
419
|
elsif index == 0
|
294
420
|
if @options[:static]
|
295
|
-
@options[:instance].name.name
|
421
|
+
token(VariableNameToken, @options[:instance].name.name)
|
296
422
|
else
|
297
|
-
|
423
|
+
token(ThisToken)
|
298
424
|
end
|
299
425
|
elsif index <= @method.param_count
|
300
426
|
if @method.has_param_names?
|
301
|
-
@method.param_names[index - 1]
|
427
|
+
token(VariableNameToken, @method.param_names[index - 1])
|
302
428
|
else
|
303
|
-
"param#{index - 1}"
|
429
|
+
token(VariableNameToken, "param#{index - 1}")
|
304
430
|
end
|
305
431
|
else
|
306
|
-
"local#{index - @method.param_count - 1}"
|
432
|
+
token(VariableNameToken, "local#{index - @method.param_count - 1}")
|
307
433
|
end
|
308
434
|
end
|
309
435
|
|
310
436
|
def expr_get_local(opcode)
|
311
437
|
index, = opcode.children
|
312
|
-
|
438
|
+
local_token(index)
|
313
439
|
end
|
314
440
|
|
315
|
-
|
316
|
-
[:get_slot,
|
317
|
-
capture(:index),
|
318
|
-
[:get_scope_object, 1]]
|
319
|
-
end
|
320
|
-
|
321
|
-
GlobalGetSlot = Matcher.new do
|
441
|
+
GetSlot = Matcher.new do
|
322
442
|
[:get_slot,
|
323
443
|
capture(:index),
|
324
444
|
either[
|
325
|
-
[:
|
326
|
-
[:
|
327
|
-
]
|
445
|
+
[:get_scope_object, capture(:scope_pos)],
|
446
|
+
[:get_global_scope]
|
447
|
+
]
|
448
|
+
]
|
328
449
|
end
|
329
450
|
|
330
451
|
def expr_get_slot(opcode)
|
331
|
-
if
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
452
|
+
if captures = GetSlot.match(opcode)
|
453
|
+
scope = @scopes[captures[:scope_pos] || 0]
|
454
|
+
|
455
|
+
if scope.is_a? Hash
|
456
|
+
# treat as an inline scope, probably from an eh
|
457
|
+
if scope[captures[:index]]
|
458
|
+
var = scope[captures[:index]]
|
459
|
+
token(VariableNameToken, var.metadata[:origin].name)
|
460
|
+
end
|
461
|
+
elsif @closure_slots && scope == :activation
|
462
|
+
# treat as a local variable
|
463
|
+
slot = @closure_slots[captures[:index]]
|
464
|
+
token(VariableNameToken, slot.name.name)
|
465
|
+
elsif scope == :this
|
466
|
+
# treat as a global property
|
467
|
+
if slot = @global_slots[captures[:index]]
|
468
|
+
get_name(nil, slot.name.to_astlet)
|
469
|
+
else
|
470
|
+
token(PropertyNameToken,
|
471
|
+
"$__GLOBAL_#{captures[:index]}")
|
472
|
+
end
|
343
473
|
end
|
344
474
|
end
|
345
475
|
end
|
@@ -351,7 +481,7 @@ module Furnace::AVM2
|
|
351
481
|
:string => 'String',
|
352
482
|
:double => 'Number',
|
353
483
|
:object => 'Object',
|
354
|
-
:
|
484
|
+
:boolean => 'Boolean',
|
355
485
|
:true => 'Boolean',
|
356
486
|
:false => 'Boolean',
|
357
487
|
}
|
@@ -372,24 +502,38 @@ module Furnace::AVM2
|
|
372
502
|
[:coerce, [:q, "XML"], any]
|
373
503
|
end
|
374
504
|
|
375
|
-
def expr_set_var(
|
505
|
+
def expr_set_var(var, value, type, declare, toplevel)
|
376
506
|
if declare
|
377
|
-
|
378
|
-
token(
|
379
|
-
|
507
|
+
declaration =
|
508
|
+
token(LocalVariableToken, [
|
509
|
+
var,
|
510
|
+
type
|
511
|
+
])
|
512
|
+
end
|
513
|
+
|
514
|
+
if declare && toplevel
|
515
|
+
declaration.children <<
|
380
516
|
token(InitializationToken, [
|
381
517
|
expr(value)
|
382
518
|
])
|
383
|
-
|
519
|
+
|
520
|
+
declaration
|
384
521
|
else
|
522
|
+
if declare
|
523
|
+
@collected_vars <<
|
524
|
+
token(StatementToken, [
|
525
|
+
declaration
|
526
|
+
])
|
527
|
+
end
|
528
|
+
|
385
529
|
token(AssignmentToken, [
|
386
|
-
|
387
|
-
expr(value)
|
530
|
+
var,
|
531
|
+
parenthesize(expr(value))
|
388
532
|
])
|
389
533
|
end
|
390
534
|
end
|
391
535
|
|
392
|
-
def expr_set_local(opcode)
|
536
|
+
def expr_set_local(opcode, toplevel=false)
|
393
537
|
index, value = opcode.children
|
394
538
|
if IMMEDIATE_TYPE_MAP.include?(value.type)
|
395
539
|
type = token(TypeToken, [
|
@@ -406,83 +550,109 @@ module Furnace::AVM2
|
|
406
550
|
value = value.children.last
|
407
551
|
end
|
408
552
|
|
409
|
-
expr_set_var(
|
553
|
+
expr_set_var(local_token(index), value, type, !@locals.include?(index), toplevel)
|
410
554
|
ensure
|
411
555
|
@locals.add index if index
|
412
556
|
end
|
413
557
|
|
414
|
-
|
415
|
-
|
416
|
-
capture(:index),
|
417
|
-
[:get_scope_object, 1],
|
418
|
-
capture(:value)]
|
558
|
+
def expr_set_local_toplevel(opcode)
|
559
|
+
expr_set_local(opcode, true)
|
419
560
|
end
|
420
561
|
|
421
|
-
|
562
|
+
SetSlot = Matcher.new do
|
422
563
|
[:set_slot,
|
423
564
|
capture(:index),
|
424
565
|
either[
|
425
|
-
[:
|
426
|
-
[:
|
566
|
+
[:get_scope_object, capture(:scope_pos)],
|
567
|
+
[:get_global_scope],
|
568
|
+
[:push_scope,
|
569
|
+
[:set_local, capture(:catch_local),
|
570
|
+
[:new_catch, capture(:catch_id)]]]
|
427
571
|
],
|
428
|
-
capture(:value)
|
572
|
+
capture(:value)
|
573
|
+
]
|
429
574
|
end
|
430
575
|
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
index, value = captures.values_at(:index, :value)
|
435
|
-
slot = @closure_slots[index]
|
576
|
+
ExceptionVariable = Matcher.new do
|
577
|
+
[:exception_variable, capture(:variable)]
|
578
|
+
end
|
436
579
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
@closure_locals.add index
|
580
|
+
def expr_set_slot(opcode, toplevel=false)
|
581
|
+
if captures = SetSlot.match(opcode)
|
582
|
+
scope = @scopes[captures[:scope_pos] || 0]
|
441
583
|
|
442
|
-
|
443
|
-
|
444
|
-
# treat as a global property
|
445
|
-
index, value = captures.values_at(:index, :value)
|
584
|
+
if captures[:catch_id]
|
585
|
+
stmt = nil
|
446
586
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
token(PropertyNameToken, "$__GLOBAL_#{index}")
|
452
|
-
end
|
587
|
+
unless ExceptionVariable.match captures[:value], captures
|
588
|
+
stmt = token(SupplementaryCommentToken,
|
589
|
+
"Non-matching catch_id and catch id", [])
|
590
|
+
end
|
453
591
|
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
592
|
+
scope = {
|
593
|
+
captures[:index] => captures[:variable]
|
594
|
+
}
|
595
|
+
|
596
|
+
@catch_scopes[captures[:catch_local]] = scope
|
597
|
+
@scopes << scope
|
598
|
+
|
599
|
+
throw :skip unless stmt
|
600
|
+
stmt
|
601
|
+
elsif @closure_slots && scope == :activation
|
602
|
+
# treat as a local variable
|
603
|
+
index, value = captures.values_at(:index, :value)
|
604
|
+
slot = @closure_slots[index]
|
605
|
+
|
606
|
+
type = type_token(slot.type.to_astlet) if slot.type
|
607
|
+
expr = expr_set_var(token(VariableNameToken, slot.name.name),
|
608
|
+
value, type,
|
609
|
+
!@closure_locals.include?(index), toplevel)
|
610
|
+
@closure_locals.add index
|
611
|
+
|
612
|
+
expr
|
613
|
+
elsif scope == :this
|
614
|
+
# treat as a global property
|
615
|
+
index, value = captures.values_at(:index, :value)
|
616
|
+
|
617
|
+
if slot = @global_slots[index]
|
618
|
+
name = get_name(nil, slot.name.to_astlet)
|
619
|
+
else
|
620
|
+
name = token(PropertyNameToken, "$__GLOBAL_#{index}")
|
621
|
+
end
|
622
|
+
|
623
|
+
token(AssignmentToken, [
|
624
|
+
name,
|
625
|
+
parenthesize(expr(value))
|
626
|
+
])
|
627
|
+
end
|
458
628
|
end
|
459
629
|
end
|
460
630
|
|
631
|
+
def expr_set_slot_toplevel(opcode)
|
632
|
+
expr_set_slot(opcode, true)
|
633
|
+
end
|
634
|
+
|
461
635
|
## Arithmetics
|
462
636
|
|
463
637
|
INPLACE_OPERATOR_MAP = {
|
464
638
|
:inc_local => :"++",
|
465
|
-
:inc_local_i => :"++",
|
466
639
|
:dec_local => :"--",
|
467
|
-
:dec_local_i => :"--",
|
468
640
|
}
|
469
641
|
|
470
642
|
def expr_inplace_arithmetic(opcode)
|
471
|
-
|
472
|
-
|
473
|
-
|
643
|
+
index, = opcode.children
|
644
|
+
|
645
|
+
token(UnaryPostOperatorToken,
|
646
|
+
local_token(index),
|
647
|
+
INPLACE_OPERATOR_MAP[opcode.type])
|
474
648
|
end
|
475
649
|
|
476
|
-
alias :expr_inc_local
|
477
|
-
alias :
|
478
|
-
alias :expr_dec_local :expr_inplace_arithmetic
|
479
|
-
alias :expr_dec_local_i :expr_inplace_arithmetic
|
650
|
+
alias :expr_inc_local :expr_inplace_arithmetic
|
651
|
+
alias :expr_dec_local :expr_inplace_arithmetic
|
480
652
|
|
481
653
|
PSEUDO_OPERATOR_MAP = {
|
482
654
|
:increment => [:"+", 1],
|
483
|
-
:increment_i => [:"+", 1],
|
484
655
|
:decrement => [:"-", 1],
|
485
|
-
:decrement_i => [:"-", 1],
|
486
656
|
}
|
487
657
|
|
488
658
|
def expr_pseudo_arithmetic(opcode)
|
@@ -497,25 +667,38 @@ module Furnace::AVM2
|
|
497
667
|
end
|
498
668
|
|
499
669
|
alias :expr_increment :expr_pseudo_arithmetic
|
500
|
-
alias :expr_increment_i :expr_pseudo_arithmetic
|
501
670
|
alias :expr_decrement :expr_pseudo_arithmetic
|
502
|
-
|
671
|
+
|
672
|
+
def expr_prepost_incdec_local(opcode)
|
673
|
+
index, = opcode.children
|
674
|
+
lvar = local_token(index)
|
675
|
+
|
676
|
+
if opcode.type == :post_increment_local
|
677
|
+
token(UnaryPostOperatorToken, lvar, "++")
|
678
|
+
elsif opcode.type == :post_decrement_local
|
679
|
+
token(UnaryPostOperatorToken, lvar, "--")
|
680
|
+
elsif opcode.type == :pre_increment_local
|
681
|
+
token(UnaryOperatorToken, lvar, "++")
|
682
|
+
elsif opcode.type == :pre_decrement_local
|
683
|
+
token(UnaryOperatorToken, lvar, "--")
|
684
|
+
end
|
685
|
+
end
|
686
|
+
alias :expr_post_increment_local :expr_prepost_incdec_local
|
687
|
+
alias :expr_post_decrement_local :expr_prepost_incdec_local
|
688
|
+
alias :expr_pre_increment_local :expr_prepost_incdec_local
|
689
|
+
alias :expr_pre_decrement_local :expr_prepost_incdec_local
|
503
690
|
|
504
691
|
OPERATOR_MAP = {
|
505
692
|
:and => :"&&",
|
506
693
|
:or => :"||",
|
507
694
|
|
508
695
|
:add => :"+",
|
509
|
-
:add_i => :"+",
|
510
696
|
:subtract => :"-",
|
511
|
-
:subtract_i => :"-",
|
512
697
|
:multiply => :"*",
|
513
|
-
:multiply_i => :"*",
|
514
698
|
:divide => :"/",
|
515
699
|
:modulo => :"%",
|
516
700
|
:multiply => :"*",
|
517
701
|
:negate => :"-",
|
518
|
-
:negate_i => :"-",
|
519
702
|
|
520
703
|
:! => :!,
|
521
704
|
:> => :>,
|
@@ -541,7 +724,7 @@ module Furnace::AVM2
|
|
541
724
|
insides = parenthesize_each(exprs(opcode.children))
|
542
725
|
|
543
726
|
if insides.count == 1
|
544
|
-
token(UnaryOperatorToken, insides, OPERATOR_MAP[opcode.type])
|
727
|
+
token(UnaryOperatorToken, insides.first, OPERATOR_MAP[opcode.type])
|
545
728
|
elsif insides.count == 2
|
546
729
|
token(BinaryOperatorToken, insides, OPERATOR_MAP[opcode.type])
|
547
730
|
else
|
@@ -554,11 +737,8 @@ module Furnace::AVM2
|
|
554
737
|
alias :expr_or :expr_arithmetic
|
555
738
|
|
556
739
|
alias :expr_add :expr_arithmetic
|
557
|
-
alias :expr_add_i :expr_arithmetic
|
558
740
|
alias :expr_subtract :expr_arithmetic
|
559
|
-
alias :expr_subtract_i :expr_arithmetic
|
560
741
|
alias :expr_multiply :expr_arithmetic
|
561
|
-
alias :expr_multiply_i :expr_arithmetic
|
562
742
|
alias :expr_divide :expr_arithmetic
|
563
743
|
alias :expr_modulo :expr_arithmetic
|
564
744
|
alias :expr_negate :expr_arithmetic
|
@@ -601,11 +781,14 @@ module Furnace::AVM2
|
|
601
781
|
],
|
602
782
|
[
|
603
783
|
[:find_property_strict,
|
604
|
-
[:m, [:set,
|
784
|
+
[:m, [:set, any], any]],
|
605
785
|
capture(:multiname)
|
606
786
|
],
|
607
787
|
[
|
608
|
-
[
|
788
|
+
either[
|
789
|
+
[:get_scope_object, 0],
|
790
|
+
[:get_global_scope]
|
791
|
+
],
|
609
792
|
capture(:multiname)
|
610
793
|
],
|
611
794
|
],
|
@@ -644,13 +827,13 @@ module Furnace::AVM2
|
|
644
827
|
if captures = PropertyGlobal.match(opcode)
|
645
828
|
token(AssignmentToken, [
|
646
829
|
get_name(nil, captures[:multiname]),
|
647
|
-
expr(*captures[:arguments])
|
830
|
+
parenthesize(expr(*captures[:arguments]))
|
648
831
|
])
|
649
832
|
else
|
650
833
|
subject, multiname, value, = opcode.children
|
651
834
|
token(AssignmentToken, [
|
652
835
|
get_name(expr(subject), multiname),
|
653
|
-
expr(value)
|
836
|
+
parenthesize(expr(value))
|
654
837
|
])
|
655
838
|
end
|
656
839
|
end
|
@@ -661,7 +844,7 @@ module Furnace::AVM2
|
|
661
844
|
|
662
845
|
stmt = token(AssignmentToken, [
|
663
846
|
get_name(token(SuperToken), multiname),
|
664
|
-
expr(value)
|
847
|
+
parenthesize(expr(value))
|
665
848
|
])
|
666
849
|
|
667
850
|
unless This.match subject
|
@@ -673,28 +856,27 @@ module Furnace::AVM2
|
|
673
856
|
stmt
|
674
857
|
end
|
675
858
|
|
676
|
-
def
|
677
|
-
subject, multiname, = opcode.children
|
678
|
-
token(DeleteToken, [get_name(expr(subject), multiname)])
|
679
|
-
end
|
680
|
-
|
681
|
-
def expr_do_property(opcode, klass)
|
859
|
+
def expr_do_property(opcode, klass, has_args)
|
682
860
|
if captures = PropertyGlobal.match(opcode)
|
683
861
|
token(klass, [
|
684
862
|
get_name(nil, captures[:multiname]),
|
685
|
-
token(ArgumentsToken, exprs(captures[:arguments]))
|
863
|
+
(token(ArgumentsToken, exprs(captures[:arguments])) if has_args)
|
686
864
|
])
|
687
865
|
else
|
688
866
|
subject, multiname, *args = opcode.children
|
689
867
|
token(klass, [
|
690
868
|
get_name(expr(subject), multiname),
|
691
|
-
token(ArgumentsToken, exprs(args))
|
869
|
+
(token(ArgumentsToken, exprs(args)) if has_args)
|
692
870
|
])
|
693
871
|
end
|
694
872
|
end
|
695
873
|
|
874
|
+
def expr_delete_property(opcode)
|
875
|
+
expr_do_property(opcode, DeleteToken, false)
|
876
|
+
end
|
877
|
+
|
696
878
|
def expr_call_property(opcode)
|
697
|
-
expr_do_property(opcode, CallToken)
|
879
|
+
expr_do_property(opcode, CallToken, true)
|
698
880
|
end
|
699
881
|
alias :expr_call_property_lex :expr_call_property
|
700
882
|
|
@@ -711,7 +893,7 @@ module Furnace::AVM2
|
|
711
893
|
## Object creation
|
712
894
|
|
713
895
|
def expr_construct_property(opcode)
|
714
|
-
expr_do_property(opcode, NewToken)
|
896
|
+
expr_do_property(opcode, NewToken, true)
|
715
897
|
end
|
716
898
|
|
717
899
|
def expr_construct(opcode)
|
@@ -761,7 +943,7 @@ module Furnace::AVM2
|
|
761
943
|
token(CallToken, [
|
762
944
|
subject_token,
|
763
945
|
token(ArgumentsToken, [
|
764
|
-
|
946
|
+
local_token(0),
|
765
947
|
*exprs(args)
|
766
948
|
])
|
767
949
|
])
|
@@ -880,6 +1062,11 @@ module Furnace::AVM2
|
|
880
1062
|
xml_expr(node.children.first)
|
881
1063
|
end
|
882
1064
|
|
1065
|
+
def expr_check_filter(node)
|
1066
|
+
content, = node.children
|
1067
|
+
expr(content)
|
1068
|
+
end
|
1069
|
+
|
883
1070
|
private
|
884
1071
|
|
885
1072
|
def token(klass, *args)
|
@@ -910,14 +1097,24 @@ module Furnace::AVM2
|
|
910
1097
|
parenthesize(subject),
|
911
1098
|
expr(multiname.children.last)
|
912
1099
|
])
|
1100
|
+
elsif @scopes[0] == :this
|
1101
|
+
token(IndexToken, [
|
1102
|
+
local_token(0),
|
1103
|
+
expr(multiname.children.last)
|
1104
|
+
])
|
913
1105
|
else
|
914
|
-
token(CommentToken, "%%type #{origin} with no subject")
|
1106
|
+
token(CommentToken, "%%type #{origin} with no subject and non-this global-scope")
|
915
1107
|
end
|
916
1108
|
when :RTQName, :RTQNameA
|
917
1109
|
token(RTNameToken, [
|
918
1110
|
parenthesize(expr(multiname.children.first)),
|
919
1111
|
token(PropertyNameToken, origin.name)
|
920
1112
|
])
|
1113
|
+
when :RTQNameL, :RTQNameLA
|
1114
|
+
token(RTNameToken, [
|
1115
|
+
parenthesize(expr(multiname.children.first)),
|
1116
|
+
parenthesize(expr(multiname.children.last))
|
1117
|
+
])
|
921
1118
|
else
|
922
1119
|
token(CommentToken, "%%type #{origin}")
|
923
1120
|
end
|