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())
}
/// 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.
pub fn class_init<'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);
write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class
}

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
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.
pub fn class_init<'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);
write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class
}

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
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.
pub fn class_init<'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);
write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class
}

View File

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

View File

@ -18,6 +18,19 @@ pub fn instance_init<'gc>(
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.
pub fn class_init<'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);
write.set_instance_deriver(primitive_deriver);
write.set_native_instance_init(Method::from_builtin(native_instance_init));
class
}

View File

@ -21,7 +21,16 @@ pub fn primitive_deriver<'gc>(
proto: Object<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
) -> 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.
@ -41,43 +50,51 @@ pub struct PrimitiveObjectData<'gc> {
impl<'gc> PrimitiveObject<'gc> {
/// 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(
primitive: Value<'gc>,
constr: Object<'gc>,
base_proto: Object<'gc>,
mc: MutationContext<'gc, '_>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Object<'gc>, Error> {
if !primitive.is_primitive() {
return Err("Attempted to box an object as a primitive".into());
}
let base =
ScriptObjectData::base_new(Some(base_proto), ScriptObjectClass::ClassInstance(constr));
Ok(PrimitiveObject(GcCell::allocate(
mc,
PrimitiveObjectData { base, primitive },
))
.into())
if matches!(primitive, Value::Undefined) {
return Err("Cannot box an undefined value".into());
} else if matches!(primitive, Value::Null) {
return Err("Cannot box a null value".into());
}
/// Construct a primitive subclass.
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));
let proto = match primitive {
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!(),
};
Ok(PrimitiveObject(GcCell::allocate(
mc,
PrimitiveObjectData {
base,
primitive: Value::Undefined,
},
let base =
ScriptObjectData::base_new(Some(proto), ScriptObjectClass::ClassInstance(constr));
let this = PrimitiveObject(GcCell::allocate(
activation.context.gc_context,
PrimitiveObjectData { base, primitive },
))
.into())
.into();
constr.call_native_initializer(Some(this), &[], activation, Some(constr))?;
Ok(this)
}
}

View File

@ -565,24 +565,7 @@ impl<'gc> Value<'gc> {
_ => {}
};
let proto = match self {
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)
PrimitiveObject::from_primitive(self.clone(), activation)
}
/// Determine if two values are abstractly equal to each other.