avm2: Implement object-space overrides for `getproperty`, `setproperty`, `deleteproperty`, and `in`.

This commit is contained in:
David Wendt 2021-09-24 22:52:38 -04:00 committed by kmeisthax
parent 925604f730
commit dbe9dffe0e
3 changed files with 154 additions and 20 deletions

View File

@ -1232,8 +1232,39 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
method: Gc<'gc, BytecodeMethod<'gc>>,
index: Index<AbcMultiname>,
) -> Result<FrameControl<'gc>, Error> {
let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?;
let txunit = method.translation_unit();
let abc = txunit.abc();
let abc_multiname = Multiname::resolve_multiname_index(&abc, index.clone())?;
let (multiname, object) = if matches!(
abc_multiname,
AbcMultiname::MultinameL { .. } | AbcMultiname::MultinameLA { .. }
) {
// `MultinameL` is the only form of multiname that allows fast-path
// or alternate-path lookups based on the local name *value*,
// rather than it's string representation.
let name_value = self.context.avm2.pop();
let object = self.context.avm2.pop().coerce_to_object(self)?;
if !name_value.is_boxed_primitive() {
if let Some(dictionary) = object.as_dictionary_object() {
let value =
dictionary.get_property_by_object(name_value.coerce_to_object(self)?);
self.context.avm2.push(value);
return Ok(FrameControl::Continue);
}
}
(
Multiname::from_multiname_late(txunit, abc_multiname, name_value, self)?,
object,
)
} else {
let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?;
(multiname, object)
};
let value = object.get_property(object, &multiname, self)?;
self.context.avm2.push(value);
@ -1247,8 +1278,41 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
index: Index<AbcMultiname>,
) -> Result<FrameControl<'gc>, Error> {
let value = self.context.avm2.pop();
let multiname = self.pool_multiname(method, index)?;
let mut object = self.context.avm2.pop().coerce_to_object(self)?;
let txunit = method.translation_unit();
let abc = txunit.abc();
let abc_multiname = Multiname::resolve_multiname_index(&abc, index.clone())?;
let (multiname, mut object) = if matches!(
abc_multiname,
AbcMultiname::MultinameL { .. } | AbcMultiname::MultinameLA { .. }
) {
// `MultinameL` is the only form of multiname that allows fast-path
// or alternate-path lookups based on the local name *value*,
// rather than it's string representation.
let name_value = self.context.avm2.pop();
let object = self.context.avm2.pop().coerce_to_object(self)?;
if !name_value.is_boxed_primitive() {
if let Some(dictionary) = object.as_dictionary_object() {
dictionary.set_property_by_object(
name_value.coerce_to_object(self)?,
value,
self.context.gc_context,
);
return Ok(FrameControl::Continue);
}
}
(
Multiname::from_multiname_late(txunit, abc_multiname, name_value, self)?,
object,
)
} else {
let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?;
(multiname, object)
};
object.set_property(object, &multiname, value, self)?;
@ -1274,8 +1338,41 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
method: Gc<'gc, BytecodeMethod<'gc>>,
index: Index<AbcMultiname>,
) -> Result<FrameControl<'gc>, Error> {
let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?;
let txunit = method.translation_unit();
let abc = txunit.abc();
let abc_multiname = Multiname::resolve_multiname_index(&abc, index.clone())?;
let (multiname, object) = if matches!(
abc_multiname,
AbcMultiname::MultinameL { .. } | AbcMultiname::MultinameLA { .. }
) {
// `MultinameL` is the only form of multiname that allows fast-path
// or alternate-path lookups based on the local name *value*,
// rather than it's string representation.
let name_value = self.context.avm2.pop();
let object = self.context.avm2.pop().coerce_to_object(self)?;
if !name_value.is_boxed_primitive() {
if let Some(dictionary) = object.as_dictionary_object() {
dictionary.delete_property_by_object(
name_value.coerce_to_object(self)?,
self.context.gc_context,
);
self.context.avm2.push(true);
return Ok(FrameControl::Continue);
}
}
(
Multiname::from_multiname_late(txunit, abc_multiname, name_value, self)?,
object,
)
} else {
let multiname = self.pool_multiname(method, index)?;
let object = self.context.avm2.pop().coerce_to_object(self)?;
(multiname, object)
};
if let Some(name) = object.resolve_multiname(&multiname)? {
self.context
@ -1347,8 +1444,20 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn op_in(&mut self) -> Result<FrameControl<'gc>, Error> {
let obj = self.context.avm2.pop().coerce_to_object(self)?;
let name = self.context.avm2.pop().coerce_to_string(self)?;
let name_value = self.context.avm2.pop();
if let Some(dictionary) = obj.as_dictionary_object() {
if !name_value.is_boxed_primitive() {
let obj_key = name_value.coerce_to_object(self)?;
self.context
.avm2
.push(dictionary.has_property_by_object(obj_key));
return Ok(FrameControl::Continue);
}
}
let name = name_value.coerce_to_string(self)?;
let qname = QName::new(Namespace::public(), name);
let has_prop = obj.has_property(&qname)?;

View File

@ -2,6 +2,7 @@
use crate::avm2::activation::Activation;
use crate::avm2::script::TranslationUnit;
use crate::avm2::value::Value;
use crate::avm2::Error;
use crate::string::AvmString;
use gc_arena::{Collect, MutationContext};
@ -285,6 +286,31 @@ impl<'gc> Multiname<'gc> {
Ok(result)
}
/// Assemble a multiname from an ABC `MultinameL` and the late-bound name.
///
/// Intended for use by code that wants to inspect the late-bound name's
/// value first before using standard namespace lookup.
pub fn from_multiname_late(
translation_unit: TranslationUnit<'gc>,
abc_multiname: &AbcMultiname,
name: Value<'gc>,
activation: &mut Activation<'_, 'gc, '_>,
) -> Result<Self, Error> {
match abc_multiname {
AbcMultiname::MultinameL { namespace_set }
| AbcMultiname::MultinameLA { namespace_set } => Ok(Self {
ns: Self::abc_namespace_set(
translation_unit,
namespace_set.clone(),
activation.context.gc_context,
)?,
name: Some(name.coerce_to_string(activation)?),
params: Vec::new(),
}),
_ => Err("Cannot assemble early-bound multinames using from_multiname_late".into()),
}
}
/// Resolve an ABC multiname's parameters and yields an AVM multiname with
/// those parameters filled in.
///
@ -345,18 +371,9 @@ impl<'gc> Multiname<'gc> {
name: translation_unit.pool_string_option(name.0, activation.context.gc_context)?,
params: Vec::new(),
},
AbcMultiname::MultinameL { namespace_set }
| AbcMultiname::MultinameLA { namespace_set } => {
let name = activation.avm2().pop().coerce_to_string(activation)?;
Self {
ns: Self::abc_namespace_set(
translation_unit,
namespace_set.clone(),
activation.context.gc_context,
)?,
name: Some(name),
params: Vec::new(),
}
AbcMultiname::MultinameL { .. } | AbcMultiname::MultinameLA { .. } => {
let name = activation.avm2().pop();
Self::from_multiname_late(translation_unit, abc_multiname, name, activation)?
}
AbcMultiname::TypeName { .. } => {
return Err("Recursive TypeNames are not supported!".into())

View File

@ -250,7 +250,7 @@ impl<'gc> Value<'gc> {
}
}
/// Yields `true` if the given value is a primitive value.
/// Yields `true` if the given value is an unboxed primitive value.
///
/// Note: Boxed primitive values are not considered primitive - it is
/// expected that their `toString`/`valueOf` handlers have already had a
@ -259,6 +259,14 @@ impl<'gc> Value<'gc> {
!matches!(self, Value::Object(_))
}
/// Yields `true` if the given value is a primitive value, boxed or no.
pub fn is_boxed_primitive(&self) -> bool {
match self {
Value::Object(o) => o.as_primitive().is_some(),
_ => true,
}
}
/// Coerce the value to a boolean.
///
/// Boolean coercion happens according to the rules specified in the ES4