Skip to content

Commit b94f9f3

Browse files
committed
avm2: Coerce function return value to declared return type
This can actually affect runtime behavior - if the return type is declared as 'int', then an instance of a custom class will get coerced to 0 when returned by the function. 'Plants vs Zombies Demo' relies on this - it has a function which incorrectly returns an object instead of an array index, but the value gets silently coerced to 0 under Flash Player.
1 parent 5d2477f commit b94f9f3

File tree

9 files changed

+114
-5
lines changed

9 files changed

+114
-5
lines changed

core/src/avm2/activation.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -1048,7 +1048,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
10481048
Op::CallSuperVoid { index, num_args } => {
10491049
self.op_call_super_void(method, index, num_args)
10501050
}
1051-
Op::ReturnValue => self.op_return_value(),
1051+
Op::ReturnValue => self.op_return_value(method),
10521052
Op::ReturnVoid => self.op_return_void(),
10531053
Op::GetProperty { index } => self.op_get_property(method, index),
10541054
Op::SetProperty { index } => self.op_set_property(method, index),
@@ -1498,10 +1498,13 @@ impl<'a, 'gc> Activation<'a, 'gc> {
14981498
Ok(FrameControl::Continue)
14991499
}
15001500

1501-
fn op_return_value(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
1501+
fn op_return_value(
1502+
&mut self,
1503+
method: Gc<'gc, BytecodeMethod<'gc>>,
1504+
) -> Result<FrameControl<'gc>, Error<'gc>> {
15021505
let return_value = self.pop_stack();
1503-
1504-
Ok(FrameControl::Return(return_value))
1506+
let coerced = return_value.coerce_to_type_name(self, &method.return_type)?;
1507+
Ok(FrameControl::Return(coerced))
15051508
}
15061509

15071510
fn op_return_void(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {

core/src/avm2/value.rs

+15-1
Original file line numberDiff line numberDiff line change
@@ -1049,9 +1049,23 @@ impl<'gc> Value<'gc> {
10491049
.name()
10501050
.to_qualified_name_err_message(activation.context.gc_context);
10511051

1052+
let debug_str = match self {
1053+
Value::Object(obj) if obj.as_primitive().is_none() => {
1054+
// Flash prints the class name (ignoring the toString() impl on the object),
1055+
// followed by something that looks like an address (it varies between executions).
1056+
// For now, we just set the "address" to all zeroes, on the off chance that some
1057+
// application is trying to parse the error message.
1058+
format!(
1059+
"{}@00000000000",
1060+
obj.instance_of_class_name(activation.context.gc_context)
1061+
)
1062+
}
1063+
_ => self.coerce_to_debug_string(activation)?.to_string(),
1064+
};
1065+
10521066
Err(Error::AvmError(type_error(
10531067
activation,
1054-
&format!("Error #1034: Type Coercion failed: cannot convert {self:?} to {name}."),
1068+
&format!("Error #1034: Type Coercion failed: cannot convert {debug_str} to {name}.",),
10551069
1034,
10561070
)?))
10571071
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package {
2+
public class MyClass {
3+
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package {
2+
public class MyOtherClass {
3+
public function toString():String {
4+
return "Custom toString";
5+
}
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package {
2+
public class Test {
3+
public function Test() {
4+
5+
}
6+
}
7+
}
8+
9+
function returnInt(param: *):int {
10+
return param;
11+
}
12+
13+
function returnBool(param: *):Boolean {
14+
return param;
15+
}
16+
17+
function returnNumber(param: *):Number {
18+
return param;
19+
}
20+
21+
function returnString(param: *):String {
22+
return param;
23+
}
24+
25+
function returnMyClass(param: *):MyClass {
26+
return param;
27+
}
28+
29+
for each (var val in [1.0, true, false, null, undefined, "Hello", new MyClass(), new MyOtherClass()]) {
30+
trace("returnInt(" + val + ") = " + returnInt(val));
31+
trace("returnBool(" + val + ") = " + returnBool(val));
32+
trace("returnNumber(" + val + ") = " + returnNumber(val));
33+
trace("returnString(" + val + ") = " + returnString(val));
34+
try {
35+
trace("returnMyClass(" + val + ") = " + returnMyClass(val));
36+
} catch (e) {
37+
trace("returnMyClass(" + val + ") threw error: " + e);
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
returnInt(1) = 1
2+
returnBool(1) = true
3+
returnNumber(1) = 1
4+
returnString(1) = 1
5+
returnMyClass(1) threw error: TypeError: Error #1034: Type Coercion failed: cannot convert 1 to MyClass.
6+
returnInt(true) = 1
7+
returnBool(true) = true
8+
returnNumber(true) = 1
9+
returnString(true) = true
10+
returnMyClass(true) threw error: TypeError: Error #1034: Type Coercion failed: cannot convert true to MyClass.
11+
returnInt(false) = 0
12+
returnBool(false) = false
13+
returnNumber(false) = 0
14+
returnString(false) = false
15+
returnMyClass(false) threw error: TypeError: Error #1034: Type Coercion failed: cannot convert false to MyClass.
16+
returnInt(null) = 0
17+
returnBool(null) = false
18+
returnNumber(null) = 0
19+
returnString(null) = null
20+
returnMyClass(null) = null
21+
returnInt(undefined) = 0
22+
returnBool(undefined) = false
23+
returnNumber(undefined) = NaN
24+
returnString(undefined) = null
25+
returnMyClass(undefined) = null
26+
returnInt(Hello) = 0
27+
returnBool(Hello) = true
28+
returnNumber(Hello) = NaN
29+
returnString(Hello) = Hello
30+
returnMyClass(Hello) threw error: TypeError: Error #1034: Type Coercion failed: cannot convert "Hello" to MyClass.
31+
returnInt([object MyClass]) = 0
32+
returnBool([object MyClass]) = true
33+
returnNumber([object MyClass]) = NaN
34+
returnString([object MyClass]) = [object MyClass]
35+
returnMyClass([object MyClass]) = [object MyClass]
36+
returnInt(Custom toString) = 0
37+
returnBool(Custom toString) = true
38+
returnNumber(Custom toString) = NaN
39+
returnString(Custom toString) = Custom toString
40+
returnMyClass(Custom toString) threw error: TypeError: Error #1034: Type Coercion failed: cannot convert MyOtherClass@00000000000 to MyClass.
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
num_frames = 1

0 commit comments

Comments
 (0)