avm2: Implement object space enumeration.

This required making enumerants into `Value`s, rather than `QName`s.
This commit is contained in:
David Wendt 2021-09-25 01:01:09 -04:00 committed by kmeisthax
parent dbe9dffe0e
commit c299f63784
7 changed files with 105 additions and 23 deletions

View File

@ -2491,9 +2491,7 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let cur_index = self.context.avm2.pop().coerce_to_number(self)?;
let object = self.context.avm2.pop().coerce_to_object(self)?;
let name = object
.get_enumerant_name(cur_index as u32)
.map(|n| n.local_name().into());
let name = object.get_enumerant_name(cur_index as u32);
self.context.avm2.push(name.unwrap_or(Value::Undefined));
@ -2506,7 +2504,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
let name = object.get_enumerant_name(cur_index as u32);
let value = if let Some(name) = name {
object.get_property(object, &name.into(), self)?
let name = name.coerce_to_string(self)?;
object.get_property(object, &QName::dynamic_name(name).into(), self)?
} else {
Value::Undefined
};

View File

@ -558,7 +558,7 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
/// Objects are responsible for maintaining a consistently ordered and
/// indexed list of enumerable names which can be queried by this
/// mechanism.
fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
fn get_enumerant_name(&self, index: u32) -> Option<Value<'gc>> {
let base = self.base();
base.get_enumerant_name(index)

View File

@ -110,4 +110,21 @@ impl<'gc> TObject<'gc> for DictionaryObject<'gc> {
fn as_dictionary_object(self) -> Option<DictionaryObject<'gc>> {
Some(self)
}
fn get_enumerant_name(&self, index: u32) -> Option<Value<'gc>> {
let read = self.0.read();
let last_enumerant = read.base.get_last_enumerant();
if index < last_enumerant {
read.base.get_enumerant_name(index)
} else {
let object_space_index = index.saturating_sub(last_enumerant);
read.object_space
.keys()
.nth(object_space_index as usize)
.cloned()
.map(|v| v.into())
}
}
}

View File

@ -397,7 +397,7 @@ impl<'gc> ScriptObjectData<'gc> {
self.proto = Some(proto)
}
pub fn get_enumerant_name(&self, index: u32) -> Option<QName<'gc>> {
pub fn get_enumerant_name(&self, index: u32) -> Option<Value<'gc>> {
// NOTE: AVM2 object enumeration is one of the weakest parts of an
// otherwise well-designed VM. Notably, because of the way they
// implemented `hasnext` and `hasnext2`, all enumerants start from ONE.
@ -406,7 +406,10 @@ impl<'gc> ScriptObjectData<'gc> {
// sentinel.
let true_index = (index as usize).checked_sub(1)?;
self.enumerants.get(true_index).cloned()
self.enumerants
.get(true_index)
.cloned()
.map(|q| q.local_name().into())
}
pub fn property_is_enumerable(&self, name: &QName<'gc>) -> bool {
@ -441,6 +444,14 @@ impl<'gc> ScriptObjectData<'gc> {
Ok(())
}
/// Get the end of (standard) enumerant space.
///
/// Intended for objects that need to extend enumerant space. The index
/// returned is guaranteed to be unused by the base enumerant list.
pub fn get_last_enumerant(&self) -> u32 {
(self.enumerants.len() as u32).saturating_add(1)
}
/// Install a method into the object.
pub fn install_method(
&mut self,

View File

@ -76,7 +76,65 @@ a["false"] = "stringy false";
trace('///a[a] = a');
a[a] = a;
trace("/// for (var k in a) { ... }");
var has_key2 = false;
var has_key3 = false;
var has_key4 = false;
trace("/// (enumerating object keys...)");
for (var k in a) {
trace(k);
if (k === key2) {
has_key2 = true;
} else if (k === key3) {
has_key3 = true;
} else if (k === key4) {
has_key4 = true;
}
}
if (has_key2) {
trace("/// (Found key2!)");
}
if (has_key3) {
trace("/// (Found key3!)");
}
if (has_key4) {
trace("/// (Found key4!)");
}
trace("///a.setPropertyIsEnumerable(key2, false);");
a.setPropertyIsEnumerable(key2, false);
trace("///a.setPropertyIsEnumerable(key3, false);");
a.setPropertyIsEnumerable(key3, false);
trace("///a.setPropertyIsEnumerable(key4, false);");
a.setPropertyIsEnumerable(key4, false);
has_key2 = false;
has_key3 = false;
has_key4 = false;
trace("/// (enumerating object keys...)");
for (var k in a) {
if (k === key2) {
has_key2 = true;
} else if (k === key3) {
has_key3 = true;
} else if (k === key4) {
has_key4 = true;
}
}
if (has_key2) {
trace("/// (Found key2!)");
}
if (has_key3) {
trace("/// (Found key3!)");
}
if (has_key4) {
trace("/// (Found key4!)");
}

View File

@ -24,17 +24,14 @@
///a[false] = "false"
///a["false"] = "stringy false"
///a[a] = a
/// for (var k in a) { ... }
13
false
key
1.123
[object Dictionary]
[object Test]
undefined
key4
null
[object Test]
key3
true
key4
/// (enumerating object keys...)
/// (Found key2!)
/// (Found key3!)
/// (Found key4!)
///a.setPropertyIsEnumerable(key2, false);
///a.setPropertyIsEnumerable(key3, false);
///a.setPropertyIsEnumerable(key4, false);
/// (enumerating object keys...)
/// (Found key2!)
/// (Found key3!)
/// (Found key4!)