avm2: Export Vector classes in public __AS3__.vec namespace (#9879)

Previously, the Vector$ classes were only exported in the internal 'AS3.vec' namespace, which is used by older ActionScript code. However, newer ActionScript code can also access these classes through the public 'AS3.vec' namespace, via 'getDefintionByName'.

We now export these classes in both namespaces. In the public 'AS3.vec' namespace, they are exported like 'Vector.' instead of 'Vector$uint'
This commit is contained in:
Aaron Hill 2023-03-07 16:22:24 -06:00 committed by GitHub
parent 2fca22bd4a
commit b8f0de8171
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 166 additions and 49 deletions

View File

@ -19,6 +19,8 @@ use swf::avm2::types::{
Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody, Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody,
}; };
use super::string::AvmString;
bitflags! { bitflags! {
/// All possible attributes for a given class. /// All possible attributes for a given class.
pub struct ClassAttributes: u8 { pub struct ClassAttributes: u8 {
@ -218,6 +220,26 @@ impl<'gc> Class<'gc> {
new_class.class_init = new_class.specialized_class_init.clone(); new_class.class_init = new_class.specialized_class_init.clone();
new_class.class_initializer_called = false; new_class.class_initializer_called = false;
if params.len() > 1 {
panic!(
"More than one type parameter is unsupported: {:?}",
self.name()
);
}
// FIXME - we should store a `Multiname` instead of a `QName`, and use the
// `params` field. For now, this is good enough to get tests passing
let name_with_params = format!(
"{}.<{}>",
new_class.name.local_name(),
params[0].read().name().to_qualified_name(mc)
);
new_class.name = QName::new(
new_class.name.namespace(),
AvmString::new_utf8(mc, name_with_params),
);
GcCell::allocate(mc, new_class) GcCell::allocate(mc, new_class)
} }

View File

@ -11,6 +11,7 @@ use crate::avm2::QName;
use gc_arena::{Collect, GcCell, MutationContext}; use gc_arena::{Collect, GcCell, MutationContext};
use super::class::Class; use super::class::Class;
use super::string::AvmString;
/// Represents a set of scripts and movies that share traits across different /// Represents a set of scripts and movies that share traits across different
/// script-global scopes. /// script-global scopes.
@ -180,6 +181,43 @@ impl<'gc> Domain<'gc> {
globals.get_property(&name.into(), activation) globals.get_property(&name.into(), activation)
} }
/// Retrieve a value from this domain, with special handling for 'Vector.<SomeType>'.
/// This is used by `getQualifiedClassName, ApplicationDomain.getDefinition, and ApplicationDomain.hasDefinition`.
pub fn get_defined_value_handling_vector(
self,
activation: &mut Activation<'_, 'gc>,
mut name: QName<'gc>,
) -> Result<Value<'gc>, Error<'gc>> {
// Special-case lookups of `Vector.<SomeType>` - these get internally converted
// to a lookup of `Vector,` a lookup of `SomeType`, and `vector_class.apply(some_type_class)`
let mut type_name = None;
if (name.namespace() == activation.avm2().vector_public_namespace
|| name.namespace() == activation.avm2().vector_internal_namespace)
&& (name.local_name().starts_with(b"Vector.<".as_slice())
&& name.local_name().ends_with(b">".as_slice()))
{
let local_name = name.local_name();
type_name = Some(AvmString::new(
activation.context.gc_context,
&local_name["Vector.<".len()..(local_name.len() - 1)],
));
name = QName::new(name.namespace(), "Vector");
}
let res = self.get_defined_value(activation, name);
if let Some(type_name) = type_name {
let type_qname = QName::from_qualified_name(type_name, activation);
let type_class = self.get_defined_value(activation, type_qname)?;
if let Ok(res) = res {
let class = res.as_object().ok_or_else(|| {
Error::RustError(format!("Vector type {:?} was not an object", res).into())
})?;
return class.apply(activation, &[type_class]).map(|obj| obj.into());
}
}
res
}
/// Export a definition from a script into the current application domain. /// Export a definition from a script into the current application domain.
/// ///
/// This does nothing if the definition already exists. /// This does nothing if the definition already exists.

View File

@ -72,23 +72,7 @@ pub fn get_definition<'gc>(
.unwrap_or_else(|| "".into()) .unwrap_or_else(|| "".into())
.coerce_to_string(activation)?; .coerce_to_string(activation)?;
let name = QName::from_qualified_name(name, activation); let name = QName::from_qualified_name(name, activation);
let (qname, mut defined_script) = match appdomain.get_defining_script(&name.into())? { return appdomain.get_defined_value_handling_vector(activation, name);
Some(data) => data,
None => {
return Err(Error::AvmError(crate::avm2::error::reference_error(
activation,
&format!(
"Error #1065: Variable {} is not defined.",
name.local_name()
),
1065,
)?))
}
};
let globals = defined_script.globals(&mut activation.context)?;
let definition = globals.get_property(&qname.into(), activation)?;
return Ok(definition);
} }
Ok(Value::Undefined) Ok(Value::Undefined)
@ -109,7 +93,10 @@ pub fn has_definition<'gc>(
let qname = QName::from_qualified_name(name, activation); let qname = QName::from_qualified_name(name, activation);
return Ok(appdomain.has_definition(qname).into()); return Ok(appdomain
.get_defined_value_handling_vector(activation, qname)
.is_ok()
.into());
} }
Ok(Value::Undefined) Ok(Value::Undefined)

View File

@ -260,7 +260,7 @@ pub fn get_definition_by_name<'gc>(
.unwrap_or(&Value::Undefined) .unwrap_or(&Value::Undefined)
.coerce_to_string(activation)?; .coerce_to_string(activation)?;
let qname = QName::from_qualified_name(name, activation); let qname = QName::from_qualified_name(name, activation);
appdomain.get_defined_value(activation, qname) appdomain.get_defined_value_handling_vector(activation, qname)
} }
// Implements `flash.utils.describeType` // Implements `flash.utils.describeType`

View File

@ -111,66 +111,66 @@ pub fn class_init<'gc>(
let class_class = activation.avm2().classes().class; let class_class = activation.avm2().classes().class;
let int_class = activation.avm2().classes().int; let int_class = activation.avm2().classes().int;
let int_vector_class = this.apply(activation, &[int_class.into()])?; let int_vector_class = this.apply(activation, &[int_class.into()])?;
let int_vector_name = QName::new(vector_internal_namespace, "Vector$int"); let int_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$int");
int_vector_class
.inner_class_definition()
.write(activation.context.gc_context)
.set_name(int_vector_name);
globals.install_const_late( globals.install_const_late(
activation.context.gc_context, activation.context.gc_context,
int_vector_name, int_vector_name_legacy,
int_vector_class.into(), int_vector_class.into(),
class_class, class_class,
); );
domain.export_definition(int_vector_name, script, activation.context.gc_context); domain.export_definition(
int_vector_name_legacy,
script,
activation.context.gc_context,
);
let uint_class = activation.avm2().classes().uint; let uint_class = activation.avm2().classes().uint;
let uint_vector_class = this.apply(activation, &[uint_class.into()])?; let uint_vector_class = this.apply(activation, &[uint_class.into()])?;
let uint_vector_name = QName::new(vector_internal_namespace, "Vector$uint"); let uint_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$uint");
uint_vector_class
.inner_class_definition()
.write(activation.context.gc_context)
.set_name(uint_vector_name);
globals.install_const_late( globals.install_const_late(
activation.context.gc_context, activation.context.gc_context,
uint_vector_name, uint_vector_name_legacy,
uint_vector_class.into(), uint_vector_class.into(),
class_class, class_class,
); );
domain.export_definition(uint_vector_name, script, activation.context.gc_context); domain.export_definition(
uint_vector_name_legacy,
script,
activation.context.gc_context,
);
let number_class = activation.avm2().classes().number; let number_class = activation.avm2().classes().number;
let number_vector_class = this.apply(activation, &[number_class.into()])?; let number_vector_class = this.apply(activation, &[number_class.into()])?;
let number_vector_name = QName::new(vector_internal_namespace, "Vector$double"); let number_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$double");
number_vector_class
.inner_class_definition()
.write(activation.context.gc_context)
.set_name(number_vector_name);
globals.install_const_late( globals.install_const_late(
activation.context.gc_context, activation.context.gc_context,
number_vector_name, number_vector_name_legacy,
number_vector_class.into(), number_vector_class.into(),
class_class, class_class,
); );
domain.export_definition(number_vector_name, script, activation.context.gc_context); domain.export_definition(
number_vector_name_legacy,
script,
activation.context.gc_context,
);
let object_vector_class = this.apply(activation, &[Value::Null])?; let plain_vector_class = this.apply(activation, &[Value::Null])?;
let object_vector_name = QName::new(vector_internal_namespace, "Vector$object"); let object_vector_name_legacy = QName::new(vector_internal_namespace, "Vector$object");
object_vector_class
.inner_class_definition()
.write(activation.context.gc_context)
.set_name(object_vector_name);
globals.install_const_late( globals.install_const_late(
activation.context.gc_context, activation.context.gc_context,
object_vector_name, object_vector_name_legacy,
object_vector_class.into(), plain_vector_class.into(),
class_class, class_class,
); );
domain.export_definition(object_vector_name, script, activation.context.gc_context); domain.export_definition(
object_vector_name_legacy,
script,
activation.context.gc_context,
);
} }
Ok(Value::Undefined) Ok(Value::Undefined)

View File

@ -0,0 +1,48 @@
package {
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;
import flash.system.ApplicationDomain;
public class Test {
public function Test() {
var vec;
var name;
vec = new Vector.<int>([1, 2]);
name = getQualifiedClassName(vec);
trace("Vector.<int> name: " + name);
trace("Vector.<int>: " + getDefinitionByName(name));
trace("ApplicationDomain.hasDefinition Vector.<int>: " + ApplicationDomain.currentDomain.hasDefinition(name));
trace("ApplicationDomain.getDefinition Vector.<int>: " + ApplicationDomain.currentDomain.getDefinition(name));
vec = new Vector.<uint>([1, 2]);
name = getQualifiedClassName(vec);
trace("Vector.<uint> name: " + name);
trace("Vector.<uint>: " + getDefinitionByName(name));
trace("ApplicationDomain.hasDefinition Vector.<uint>: " + ApplicationDomain.currentDomain.hasDefinition(name));
trace("ApplicationDomain.getDefinition Vector.<uint>: " + ApplicationDomain.currentDomain.getDefinition(name));
vec = new Vector.<Number>([1, 2]);
name = getQualifiedClassName(vec);
trace("Vector.<Number> name: " + name);
trace("Vector.<Number>: " + getDefinitionByName(name));
trace("ApplicationDomain.hasDefinition Vector.<Number>: " + ApplicationDomain.currentDomain.hasDefinition(name));
trace("ApplicationDomain.getDefinition Vector.<Number>: " + ApplicationDomain.currentDomain.getDefinition(name));
trace("Early lookup: " + ApplicationDomain.currentDomain.hasDefinition("__AS3__.vec::Vector.<Test>"));
vec = new Vector.<Object>([1, 2]);
name = getQualifiedClassName(vec);
trace("Vector.<Object> name: " + name);
trace("Vector.<Object>: " + getDefinitionByName(name));
trace("ApplicationDomain.hasDefinition Vector.<Object>: " + ApplicationDomain.currentDomain.hasDefinition(name));
trace("ApplicationDomain.getDefinition Vector.<Object>: " + ApplicationDomain.currentDomain.getDefinition(name));
vec = new Vector.<Test>([]);
name = getQualifiedClassName(vec);
trace("Vector.<Test> name: " + name);
trace("Vector.<Test>: " + getDefinitionByName(name));
trace("ApplicationDomain.hasDefinition Vector.<Test>: " + ApplicationDomain.currentDomain.hasDefinition(name));
trace("ApplicationDomain.getDefinition Vector.<Test>: " + ApplicationDomain.currentDomain.getDefinition(name));
}
}
}

View File

@ -0,0 +1,21 @@
Vector.<int> name: __AS3__.vec::Vector.<int>
Vector.<int>: [class Vector.<int>]
ApplicationDomain.hasDefinition Vector.<int>: true
ApplicationDomain.getDefinition Vector.<int>: [class Vector.<int>]
Vector.<uint> name: __AS3__.vec::Vector.<uint>
Vector.<uint>: [class Vector.<uint>]
ApplicationDomain.hasDefinition Vector.<uint>: true
ApplicationDomain.getDefinition Vector.<uint>: [class Vector.<uint>]
Vector.<Number> name: __AS3__.vec::Vector.<Number>
Vector.<Number>: [class Vector.<Number>]
ApplicationDomain.hasDefinition Vector.<Number>: true
ApplicationDomain.getDefinition Vector.<Number>: [class Vector.<Number>]
Early lookup: true
Vector.<Object> name: __AS3__.vec::Vector.<Object>
Vector.<Object>: [class Vector.<Object>]
ApplicationDomain.hasDefinition Vector.<Object>: true
ApplicationDomain.getDefinition Vector.<Object>: [class Vector.<Object>]
Vector.<Test> name: __AS3__.vec::Vector.<Test>
Vector.<Test>: [class Vector.<Test>]
ApplicationDomain.hasDefinition Vector.<Test>: true
ApplicationDomain.getDefinition Vector.<Test>: [class Vector.<Test>]

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
num_frames = 1