Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions core/src/avm2/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1678,8 +1678,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
let multiname = multiname.fill_with_runtime_params(self)?;
let source = self.pop_stack().null_check(self, Some(&multiname))?;

let ctor = source.get_property(&multiname, self)?;
let constructed_object = ctor.construct(self, args)?;
let constructed_object = source.construct_prop(self, &multiname, args)?;

self.push_stack(constructed_object);

Expand Down
37 changes: 37 additions & 0 deletions core/src/avm2/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,16 @@ pub fn make_error_1006<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc>
))
}

#[inline(never)]
#[cold]
pub fn make_error_1007<'gc>(activation: &mut Activation<'_, 'gc>) -> Error<'gc> {
make_error!(type_error(
activation,
"Error #1007: Instantiation attempted on a non-constructor.",
1007,
))
}

#[inline(never)]
#[cold]
pub fn make_error_1010<'gc>(
Expand Down Expand Up @@ -569,6 +579,23 @@ pub fn make_error_1063<'gc>(
))
}

#[inline(never)]
#[cold]
pub fn make_error_1064<'gc>(
activation: &mut Activation<'_, 'gc>,
method: Method<'gc>,
) -> Error<'gc> {
let mut function_name = WString::new();

display_function(&mut function_name, method);

make_error!(type_error(
activation,
&format!("Error #1064: Cannot call method {function_name} as constructor.",),
1064,
))
}

#[inline(never)]
#[cold]
pub fn make_error_1065<'gc>(
Expand Down Expand Up @@ -702,6 +729,16 @@ pub fn make_error_1112<'gc>(activation: &mut Activation<'_, 'gc>, arg_count: usi
))
}

#[inline(never)]
#[cold]
pub fn make_error_1115<'gc>(activation: &mut Activation<'_, 'gc>, name: &str) -> Error<'gc> {
make_error!(type_error(
activation,
&format!("Error #1115: {name} is not a constructor."),
1115,
))
}

#[inline(never)]
#[cold]
pub fn make_error_1117<'gc>(
Expand Down
8 changes: 2 additions & 6 deletions core/src/avm2/globals/class.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! `Class` builtin/prototype

use crate::avm2::activation::Activation;
use crate::avm2::error::type_error;
use crate::avm2::error::make_error_1115;
use crate::avm2::object::{ClassObject, Object};
use crate::avm2::value::Value;
use crate::avm2::Error;
Expand All @@ -10,11 +10,7 @@ pub fn class_allocator<'gc>(
_class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
Err(Error::avm_error(type_error(
activation,
"Error #1115: Class$ is not a constructor.",
1115,
)?))
Err(make_error_1115(activation, "Class$"))
}

pub fn get_prototype<'gc>(
Expand Down
8 changes: 2 additions & 6 deletions core/src/avm2/globals/vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::avm2::activation::Activation;
use crate::avm2::class::{Class, ClassAttributes};
use crate::avm2::error::{make_error_1034, make_error_1112, type_error};
use crate::avm2::error::{make_error_1007, make_error_1034, make_error_1112};
use crate::avm2::function::FunctionArgs;
use crate::avm2::globals::array::{
compare_numeric_slow, compare_string_case_insensitive, compare_string_case_sensitive,
Expand All @@ -22,11 +22,7 @@ pub fn vector_allocator<'gc>(
_class: ClassObject<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
return Err(Error::avm_error(type_error(
activation,
"Error #1007: Instantiation attempted on a non-constructor.",
1007,
)?));
Err(make_error_1007(activation))
}

/// Implements `Vector`'s instance constructor.
Expand Down
8 changes: 8 additions & 0 deletions core/src/avm2/object/function_object.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Function object impl

use crate::avm2::activation::Activation;
use crate::avm2::error::make_error_1064;
use crate::avm2::function::{BoundMethod, FunctionArgs};
use crate::avm2::method::Method;
use crate::avm2::object::script_object::{ScriptObject, ScriptObjectData};
Expand Down Expand Up @@ -103,6 +104,13 @@ impl<'gc> FunctionObject<'gc> {
activation: &mut Activation<'_, 'gc>,
arguments: FunctionArgs<'_, 'gc>,
) -> Result<Object<'gc>, Error<'gc>> {
let method = self.0.exec.as_method();
if method.bound_class().is_some() {
// If the Method is class-bound, attempting to construct it throws
// an error
return Err(make_error_1064(activation, method));
}

let object_class = activation.avm2().classes().object;

let prototype = if let Some(proto) = self.prototype() {
Expand Down
10 changes: 5 additions & 5 deletions core/src/avm2/optimizer/type_aware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1744,17 +1744,17 @@ fn abstract_interpret_ops<'gc>(
match vtable.get_trait(&multiname) {
Some(Property::Slot { slot_id })
| Some(Property::ConstSlot { slot_id }) => {
optimize_op_to!(Op::ConstructSlot {
index: slot_id,
num_args
});

let mut value_class =
vtable.slot_class(slot_id).expect("Slot should exist");
let resolved_value_class = value_class.get_class(activation)?;

if let Some(slot_class) = resolved_value_class {
if let Some(instance_class) = slot_class.i_class() {
optimize_op_to!(Op::ConstructSlot {
index: slot_id,
num_args
});

// ConstructProp on a c_class will construct its i_class
stack_push_done = true;
stack.push_class_not_null(activation, instance_class)?;
Expand Down
105 changes: 91 additions & 14 deletions core/src/avm2/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

use crate::avm2::activation::Activation;
use crate::avm2::error::{self};
use crate::avm2::error::{make_error_1006, make_error_1034, make_error_1050, type_error};
use crate::avm2::error::{
make_error_1006, make_error_1007, make_error_1034, make_error_1050, make_error_1064,
make_error_1115,
};
use crate::avm2::function::{exec, FunctionArgs};
use crate::avm2::object::{NamespaceObject, Object, TObject};
use crate::avm2::property::Property;
Expand Down Expand Up @@ -1350,6 +1353,92 @@
}
}

pub fn construct_prop(
&self,
activation: &mut Activation<'_, 'gc>,
multiname: &Multiname<'gc>,
arguments: FunctionArgs<'_, 'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
let vtable = self.vtable(activation);

match vtable.get_trait(multiname) {
Some(Property::Slot { slot_id }) | Some(Property::ConstSlot { slot_id }) => {
// Only objects can have slots
let object = self.as_object().unwrap();

let value = object.get_slot(slot_id);

// If the value is a `Function` or `Class`, it's constructible
let is_constructible = value.as_object().is_some_and(|o| {
o.as_class_object().is_some() || o.as_function_object().is_some()
});

// This check might seem redundant, as `Value::construct` will
// throw an error anyway if the value isn't constructible.
// However, in avmplus, attempting to construct a
// non-constructible value using `constructprop` in interpreter
// mode in SWFv9/v10 throws a different error, error #1115 (see
// below). So here, we have to manually check for
// `Function`/`Class` and throw error 1115 or 1007 (depending on
// the SWF version) if the value is neither of them.
if is_constructible {
value.construct(activation, arguments)
} else {
// Error 1115 is only thrown in SWFv9/v10 in interpreter-mode code
if activation.context.root_swf.version() < 11 && activation.is_interpreter() {
Err(make_error_1115(activation, "value"))
} else {
Err(make_error_1007(activation))
}
}
}
Some(Property::Method { disp_id }) => {
// Attempting to `construct_prop` a method always throws error 1064

let method = vtable.get_method(disp_id).expect("Method should exist");
Err(make_error_1064(activation, method))
}
Some(Property::Virtual { get: Some(get), .. }) => {
let value = self.call_method_with_args(get, FunctionArgs::empty(), activation)?;

value.construct(activation, arguments)
}
Some(Property::Virtual { get: None, .. }) => {
let instance_class = self.instance_class(activation);

Check warning on line 1407 in core/src/avm2/value.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1407)

Err(error::make_reference_error(
activation,
error::ReferenceErrorCode::ReadFromWriteOnly,
multiname,
instance_class,
))

Check warning on line 1414 in core/src/avm2/value.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered lines (1409–1414)
}
None => {
let value = if let Some(object) = self.as_object() {
object.get_property_local(multiname, activation)?
} else {
// Unlike `Value::get_property`, error messages report that
// the read failed on the `Object` class, not the
// `instance_class` of this `Value`.
let object_class = activation.avm2().class_defs().object;
let proto = self.proto(activation);

let dynamic_lookup = crate::avm2::object::get_dynamic_property(
activation,
multiname,
None,
proto,
object_class,
)?;

Check warning on line 1432 in core/src/avm2/value.rs

View workflow job for this annotation

GitHub Actions / Coverage Report

Coverage

Uncovered line (1432)

dynamic_lookup.unwrap_or(Value::Undefined)
};

value.construct(activation, arguments)
}
}
}

/// Returns true if the value has one or more traits of a given name.
///
/// This method will panic if called on null or undefined.
Expand Down Expand Up @@ -1424,19 +1513,7 @@
Some(Object::FunctionObject(function_object)) => {
function_object.construct(activation, args).map(Into::into)
}
_ => {
let error = if activation.context.root_swf.version() < 11 {
type_error(activation, "Error #1115: value is not a constructor.", 1115)
} else {
type_error(
activation,
"Error #1007: Instantiation attempted on a non-constructor.",
1007,
)
};

Err(Error::avm_error(error?))
}
_ => Err(make_error_1007(activation)),
}
}

Expand Down
8 changes: 8 additions & 0 deletions tests/tests/swfs/avm2/construct_errors_swf10/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
1007
[object TypeError]
1007
[object TypeError]
1007
[object TypeError]
1007
[object TypeError]
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/construct_errors_swf10/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
47 changes: 47 additions & 0 deletions tests/tests/swfs/avm2/constructprop_dynamic_primitive/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package {
import flash.display.MovieClip;

public class Test extends MovieClip {
public function Test() {
super();

Number.prototype.abc = 99;
Number.prototype.def = new Function();
Number.prototype.ghi = null;

var n:Number = 19;

try {
new (n.abc)();
trace("no error");
} catch(e:Error) {
trace(Object.prototype.toString.call(e));
trace(e.errorID);
}

try {
new (n.def)();
trace("no error");
} catch(e:Error) {
trace(Object.prototype.toString.call(e));
trace(e.errorID);
}

try {
new (n.ghi)();
trace("no error");
} catch(e:Error) {
trace(Object.prototype.toString.call(e));
trace(e.errorID);
}

try {
new (n.jkl)();
trace("no error");
} catch(e:Error) {
trace(Object.prototype.toString.call(e));
trace(e.errorID);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[object TypeError]
1007
no error
[object TypeError]
1007
[object TypeError]
1007
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
19 changes: 19 additions & 0 deletions tests/tests/swfs/avm2/constructprop_method/Test.as
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package {
import flash.display.MovieClip;

public class Test extends MovieClip {
public function Test() {
super();

try {
new (this.abc)();
} catch(e:Error) {
trace(Object.prototype.toString.call(e));
trace(e.errorID);
}
}

public function abc():void {
}
}
}
2 changes: 2 additions & 0 deletions tests/tests/swfs/avm2/constructprop_method/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[object TypeError]
1064
Binary file added tests/tests/swfs/avm2/constructprop_method/test.swf
Binary file not shown.
1 change: 1 addition & 0 deletions tests/tests/swfs/avm2/constructprop_method/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
num_frames = 1
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
num_ticks = 1
known_failure = true
Loading