avm2: `PrimitiveObject::from_primitive` should now select it's own class and construct it.

This also includes protections on `String` to prevent it from overwriting itself. All user-constructed primitive objects start out boxing `Value::Undefined` and future constructor implementations should check what's already been boxed before overwriting it with user arguments.
This commit is contained in:
David Wendt 2021-05-31 21:53:59 -04:00
parent 261cb40a22
commit c167912f52
7 changed files with 107 additions and 49 deletions

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
Err("Boolean constructor is a stub.".into()) Err("Boolean constructor is a stub.".into())
} }
/// Implements `Boolean`'s native instance initializer.
pub fn native_instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
activation.super_init(this, args)?;
}
Ok(Value::Undefined)
}
/// Implements `Boolean`'s class initializer. /// Implements `Boolean`'s class initializer.
pub fn class_init<'gc>( pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
@ -39,6 +52,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
let mut write = class.write(mc); let mut write = class.write(mc);
write.set_instance_deriver(primitive_deriver); write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class class
} }

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
Err("int constructor is a stub.".into()) Err("int constructor is a stub.".into())
} }
/// Implements `int`'s native instance initializer.
pub fn native_instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
activation.super_init(this, args)?;
}
Ok(Value::Undefined)
}
/// Implements `int`'s class initializer. /// Implements `int`'s class initializer.
pub fn class_init<'gc>( pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
@ -39,6 +52,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
let mut write = class.write(mc); let mut write = class.write(mc);
write.set_instance_deriver(primitive_deriver); write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class class
} }

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
Err("Number constructor is a stub.".into()) Err("Number constructor is a stub.".into())
} }
/// Implements `Number`'s native instance initializer.
pub fn native_instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
activation.super_init(this, args)?;
}
Ok(Value::Undefined)
}
/// Implements `Number`'s class initializer. /// Implements `Number`'s class initializer.
pub fn class_init<'gc>( pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
@ -39,6 +52,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
let mut write = class.write(mc); let mut write = class.write(mc);
write.set_instance_deriver(primitive_deriver); write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class class
} }

View File

@ -21,11 +21,13 @@ pub fn instance_init<'gc>(
activation.super_init(this, &[])?; activation.super_init(this, &[])?;
if let Some(mut value) = this.as_primitive_mut(activation.context.gc_context) { if let Some(mut value) = this.as_primitive_mut(activation.context.gc_context) {
*value = args if !matches!(*value, Value::String(_)) {
.get(0) *value = args
.unwrap_or(&Value::String("".into())) .get(0)
.coerce_to_string(activation)? .unwrap_or(&Value::String("".into()))
.into(); .coerce_to_string(activation)?
.into();
}
} }
} }

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
Err("uint constructor is a stub.".into()) Err("uint constructor is a stub.".into())
} }
/// Implements `uint`'s native instance initializer.
pub fn native_instance_init<'gc>(
activation: &mut Activation<'_, 'gc, '_>,
this: Option<Object<'gc>>,
args: &[Value<'gc>],
) -> Result<Value<'gc>, Error> {
if let Some(this) = this {
activation.super_init(this, args)?;
}
Ok(Value::Undefined)
}
/// Implements `uint`'s class initializer. /// Implements `uint`'s class initializer.
pub fn class_init<'gc>( pub fn class_init<'gc>(
_activation: &mut Activation<'_, 'gc, '_>, _activation: &mut Activation<'_, 'gc, '_>,
@ -39,6 +52,7 @@ pub fn create_class<'gc>(mc: MutationContext<'gc, '_>) -> GcCell<'gc, Class<'gc>
let mut write = class.write(mc); let mut write = class.write(mc);
write.set_instance_deriver(primitive_deriver); write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class class
} }

View File

@ -21,7 +21,16 @@ pub fn primitive_deriver<'gc>(
proto: Object<'gc>, proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>, activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
PrimitiveObject::derive(constr, proto, activation.context.gc_context) let base = ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
Ok(PrimitiveObject(GcCell::allocate(
activation.context.gc_context,
PrimitiveObjectData {
base,
primitive: Value::Undefined,
},
))
.into())
} }
/// An Object which represents a primitive value of some other kind. /// An Object which represents a primitive value of some other kind.
@ -41,43 +50,51 @@ pub struct PrimitiveObjectData<'gc> {
impl<'gc> PrimitiveObject<'gc> { impl<'gc> PrimitiveObject<'gc> {
/// Box a primitive into an object. /// Box a primitive into an object.
///
/// This function will yield an error if `primitive` is `Undefined`, `Null`,
/// or an object already.
pub fn from_primitive( pub fn from_primitive(
primitive: Value<'gc>, primitive: Value<'gc>,
constr: Object<'gc>, activation: &mut Activation<'_, 'gc, '_>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> { ) -> Result<Object<'gc>, Error> {
if !primitive.is_primitive() { if !primitive.is_primitive() {
return Err("Attempted to box an object as a primitive".into()); return Err("Attempted to box an object as a primitive".into());
} }
let base = if matches!(primitive, Value::Undefined) {
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr)); return Err("Cannot box an undefined value".into());
} else if matches!(primitive, Value::Null) {
return Err("Cannot box a null value".into());
}
Ok(PrimitiveObject(GcCell::allocate( let proto = match primitive {
mc, Value::Bool(_) => activation.avm2().prototypes().boolean,
Value::Number(_) => activation.avm2().prototypes().number,
Value::Unsigned(_) => activation.avm2().prototypes().uint,
Value::Integer(_) => activation.avm2().prototypes().int,
Value::String(_) => activation.avm2().prototypes().string,
_ => unreachable!(),
};
let constr = match primitive {
Value::Bool(_) => activation.avm2().constructors().boolean,
Value::Number(_) => activation.avm2().constructors().number,
Value::Unsigned(_) => activation.avm2().constructors().uint,
Value::Integer(_) => activation.avm2().constructors().int,
Value::String(_) => activation.avm2().constructors().string,
_ => unreachable!(),
};
let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
let this = PrimitiveObject(GcCell::allocate(
activation.context.gc_context,
PrimitiveObjectData { base, primitive }, PrimitiveObjectData { base, primitive },
)) ))
.into()) .into();
}
/// Construct a primitive subclass. constr.call_native_initializer(Some(this), &[], activation, Some(constr))?;
pub fn derive(
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
) -> Result<Object<'gc>, Error> {
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(PrimitiveObject(GcCell::allocate( Ok(this)
mc,
PrimitiveObjectData {
base,
primitive: Value::Undefined,
},
))
.into())
} }
} }

View File

@ -565,24 +565,7 @@ impl<'gc> Value<'gc> {
_ => {} _ => {}
}; };
let proto = match self { PrimitiveObject::from_primitive(self.clone(), activation)
Value::Bool(_) => activation.avm2().prototypes().boolean,
Value::Number(_) => activation.avm2().prototypes().number,
Value::Unsigned(_) => activation.avm2().prototypes().uint,
Value::Integer(_) => activation.avm2().prototypes().int,
Value::String(_) => activation.avm2().prototypes().string,
_ => unreachable!(),
};
let constr = match self {
Value::Bool(_) => activation.avm2().constructors().boolean,
Value::Number(_) => activation.avm2().constructors().number,
Value::Unsigned(_) => activation.avm2().constructors().uint,
Value::Integer(_) => activation.avm2().constructors().int,
Value::String(_) => activation.avm2().constructors().string,
_ => unreachable!(),
};
PrimitiveObject::from_primitive(self.clone(), constr, proto, activation.context.gc_context)
} }
/// Determine if two values are abstractly equal to each other. /// Determine if two values are abstractly equal to each other.