Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avm2: Add AS3 native initializers, abstract class metadata, remove separate super_init method entirely #18673

Merged
merged 7 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 64 additions & 48 deletions core/build_playerglobal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ const RUFFLE_METADATA_NAME: &str = "Ruffle";
// Indicates that we should generate a reference to an instance allocator
// method (used as a metadata key with `Ruffle` metadata)
const METADATA_INSTANCE_ALLOCATOR: &str = "InstanceAllocator";
// Indicates that we should generate a reference to a native initializer
// method (used as a metadata key with `Ruffle` metadata)
const METADATA_SUPER_INITIALIZER: &str = "SuperInitializer";
/// Indicates that we should generate a reference to a class call handler
/// method (used as a metadata key with `Ruffle` metadata)
const METADATA_CALL_HANDLER: &str = "CallHandler";
/// Indicates that we should generate a reference to a custom constructor
/// method (used as a metadata key with `Ruffle` metadata)
const METADATA_CUSTOM_CONSTRUCTOR: &str = "CustomConstructor";
/// Indicates that the class can't be directly instantiated (but its child classes might be).
/// Binds to an always-throwing allocator.
/// (This can also be used on final non-abstract classes that you just want to disable `new` for.
/// We just didn't find a better name for this concept than "abstract")
const METADATA_ABSTRACT: &str = "Abstract";
Lord-McSweeney marked this conversation as resolved.
Show resolved Hide resolved
// The name for metadata for namespace versioning- the Flex SDK doesn't
// strip versioning metadata, so we have to allow this metadata name
const API_METADATA_NAME: &str = "API";
Expand Down Expand Up @@ -366,7 +368,6 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn st
let none_tokens = quote! { None };
let mut rust_paths = vec![none_tokens.clone(); abc.methods.len()];
let mut rust_instance_allocators = vec![none_tokens.clone(); abc.classes.len()];
let mut rust_super_initializers = vec![none_tokens.clone(); abc.classes.len()];
let mut rust_call_handlers = vec![none_tokens.clone(); abc.classes.len()];
let mut rust_custom_constructors = vec![none_tokens; abc.classes.len()];

Expand Down Expand Up @@ -403,28 +404,69 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn st
rust_method_name_and_path(&abc, trait_, parent, method_prefix, "");
};

// Look for `[Ruffle(InstanceAllocator)]` metadata - if present,
// generate a reference to an allocator function in the native instance
// We support four kinds of native methods:
// instance methods, class methods, script methods ("freestanding") and initializers.
// We're going to insert them into an array indexed by `MethodId`,
// so it doesn't matter what order we visit them in.
for (i, instance) in abc.instances.iter().enumerate() {
// Look for native instance methods
for trait_ in &instance.traits {
check_trait(trait_, Some(instance.name));
}
// Look for native class methods (in the corresponding
// `Class` definition)
for trait_ in &abc.classes[i].traits {
check_trait(trait_, Some(instance.name));
}
}
// Look for freestanding methods
for script in &abc.scripts {
for trait_ in &script.traits {
check_trait(trait_, None);
}
}

// Look for `[Ruffle(InstanceAllocator)]` and similar metadata - if present,
// generate a reference to a function in the native instance
// allocators table.
let mut check_instance_allocator = |trait_: &Trait| {
let mut check_class = |trait_: &Trait| {
let class_id = if let TraitKind::Class { class, .. } = trait_.kind {
class.0
} else {
return;
};

let class_name_idx = abc.instances[class_id as usize].name.0;
let class_name_idx = abc.instances[class_id as usize].name;
let class_name = resolve_multiname_name(
&abc,
&abc.constant_pool.multinames[class_name_idx as usize - 1],
&abc.constant_pool.multinames[class_name_idx.0 as usize - 1],
);

let instance_allocator_method_name =
"::".to_string() + &flash_to_rust_path(&class_name) + "_allocator";
let super_init_method_name = "::super_init".to_string();
let init_method_name = "::".to_string() + &flash_to_rust_path(&class_name) + "_initializer";
let call_handler_method_name = "::call_handler".to_string();
let custom_constructor_method_name =
"::".to_string() + &flash_to_rust_path(&class_name) + "_constructor";

// Also support instance initializer - let's pretend it's a trait.
let init_method_idx = abc.instances[class_id as usize].init_method;
let init_method = &abc.methods[init_method_idx.0 as usize];
if init_method.flags.contains(MethodFlags::NATIVE) {
let init_trait = Trait {
name: class_name_idx,
kind: TraitKind::Method {
disp_id: 0, // unused
method: abc.classes[class_id as usize].init_method,
},
metadata: vec![], // unused
is_final: true, // unused
is_override: false, // unused
};
rust_paths[init_method_idx.0 as usize] =
rust_method_name_and_path(&abc, &init_trait, None, "", &init_method_name);
}

for metadata_idx in &trait_.metadata {
let metadata = &abc.metadata[metadata_idx.0 as usize];
let name =
Expand Down Expand Up @@ -457,15 +499,6 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn st
&instance_allocator_method_name,
);
}
(None, METADATA_SUPER_INITIALIZER) if !is_versioning => {
rust_super_initializers[class_id as usize] = rust_method_name_and_path(
&abc,
trait_,
None,
"",
&super_init_method_name,
);
}
(None, METADATA_CALL_HANDLER) if !is_versioning => {
rust_call_handlers[class_id as usize] = rust_method_name_and_path(
&abc,
Expand All @@ -484,34 +517,25 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn st
&custom_constructor_method_name,
);
}
(None, METADATA_ABSTRACT) if !is_versioning => {
rust_instance_allocators[class_id as usize] = {
let path = "crate::avm2::object::abstract_class_allocator";
let path_tokens = TokenStream::from_str(path).unwrap();
let flash_method_path = "unused".to_string();
quote! { Some((#flash_method_path, #path_tokens)) }
};
}
(None, _) if is_versioning => {}
_ => panic!("Unexpected metadata pair ({key:?}, {value})"),
}
}
}
};

// We support three kinds of native methods:
// instance methods, class methods, and freestanding functions.
// We're going to insert them into an array indexed by `MethodId`,
// so it doesn't matter what order we visit them in.
for (i, instance) in abc.instances.iter().enumerate() {
// Look for native instance methods
for trait_ in &instance.traits {
check_trait(trait_, Some(instance.name));
}
// Look for native class methods (in the corresponding
// `Class` definition)
for trait_ in &abc.classes[i].traits {
check_trait(trait_, Some(instance.name));
}
}

// Look for freestanding methods
// Handle classes
for script in &abc.scripts {
for trait_ in &script.traits {
check_trait(trait_, None);
check_instance_allocator(trait_);
check_class(trait_);
}
}
// Finally, generate the actual code.
Expand Down Expand Up @@ -543,22 +567,14 @@ fn write_native_table(data: &[u8], out_dir: &Path) -> Result<Vec<u8>, Box<dyn st
#(#rust_instance_allocators,)*
];

// This is very similar to `NATIVE_METHOD_TABLE`, but we have one entry per
// class, rather than per method. When an entry is `Some(fn_ptr)`, we use
// `fn_ptr` as the super initializer for the corresponding class when we
// load it into Ruffle.
pub const NATIVE_SUPER_INITIALIZER_TABLE: &[Option<(&'static str, crate::avm2::method::NativeMethodImpl)>] = &[
#(#rust_super_initializers,)*
];

// This is very similar to `NATIVE_SUPER_INITIALIZER_TABLE`.
// This is very similar to `NATIVE_INSTANCE_ALLOCATOR_TABLE`.
// When an entry is `Some(fn_ptr)`, we use `fn_ptr` as the native call
// handler for the corresponding class when we load it into Ruffle.
pub const NATIVE_CALL_HANDLER_TABLE: &[Option<(&'static str, crate::avm2::method::NativeMethodImpl)>] = &[
#(#rust_call_handlers,)*
];

// This is very similar to `NATIVE_SUPER_INITIALIZER_TABLE`.
// This is very similar to `NATIVE_INSTANCE_ALLOCATOR_TABLE`.
// When an entry is `Some(fn_ptr)`, we use `fn_ptr` as the native custom
// constructor for the corresponding class when we load it into Ruffle.
pub const NATIVE_CUSTOM_CONSTRUCTOR_TABLE: &[Option<(&'static str, crate::avm2::class::CustomConstructorFn)>] = &[
Expand Down
4 changes: 0 additions & 4 deletions core/src/avm2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,6 @@ pub struct Avm2<'gc> {
#[collect(require_static)]
native_instance_allocator_table: &'static [Option<(&'static str, AllocatorFn)>],

#[collect(require_static)]
native_super_initializer_table: &'static [Option<(&'static str, NativeMethodImpl)>],

#[collect(require_static)]
native_call_handler_table: &'static [Option<(&'static str, NativeMethodImpl)>],

Expand Down Expand Up @@ -223,7 +220,6 @@ impl<'gc> Avm2<'gc> {

native_method_table: Default::default(),
native_instance_allocator_table: Default::default(),
native_super_initializer_table: Default::default(),
native_call_handler_table: Default::default(),
native_custom_constructor_table: Default::default(),
broadcast_list: Default::default(),
Expand Down
2 changes: 1 addition & 1 deletion core/src/avm2/activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
.bound_superclass_object
.expect("Superclass object is required to run super_init");

bound_superclass_object.call_super_init(receiver.into(), args, self)
bound_superclass_object.call_init(receiver.into(), args, self)
}

/// Retrieve a local register.
Expand Down
59 changes: 0 additions & 59 deletions core/src/avm2/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,21 +139,6 @@ pub struct ClassData<'gc> {
/// Must be called each time a new class instance is constructed.
instance_init: Method<'gc>,

/// The super initializer for this class, called when super() is called for
/// a subclass.
///
/// This may be provided to allow natively-constructed classes to
/// initialize themselves in a different manner from user-constructed ones.
/// For example, the user-accessible constructor may error out (as it's not
/// a valid class to construct for users), but native code may still call
/// its constructor stack.
///
/// By default, a class's `super_init` will be initialized to the
/// same method as the regular one. You must specify a separate super
/// initializer to change initialization behavior based on what code is
/// constructing the class.
super_init: Method<'gc>,

/// Traits for a given class.
///
/// These are accessed as normal instance properties; they should not be
Expand Down Expand Up @@ -238,8 +223,6 @@ impl<'gc> Class<'gc> {
class_i_class: Class<'gc>,
mc: &Mutation<'gc>,
) -> Self {
let super_init = instance_init;

let instance_allocator = super_class
.map(|c| c.instance_allocator())
.unwrap_or(Allocator(scriptobject_allocator));
Expand All @@ -256,7 +239,6 @@ impl<'gc> Class<'gc> {
all_interfaces: Vec::new(),
instance_allocator,
instance_init,
super_init,
traits: Vec::new(),
vtable: VTable::empty(mc),
call_handler: None,
Expand Down Expand Up @@ -287,7 +269,6 @@ impl<'gc> Class<'gc> {
all_interfaces: Vec::new(),
instance_allocator: Allocator(scriptobject_allocator),
instance_init: class_init,
super_init: class_init,
traits: Vec::new(),
vtable: VTable::empty(mc),
call_handler: None,
Expand All @@ -311,8 +292,6 @@ impl<'gc> Class<'gc> {
instance_init: Method<'gc>,
mc: &Mutation<'gc>,
) -> Self {
let super_init = instance_init;

Class(GcCell::new(
mc,
ClassData {
Expand All @@ -325,7 +304,6 @@ impl<'gc> Class<'gc> {
all_interfaces: Vec::new(),
instance_allocator: Allocator(scriptobject_allocator),
instance_init,
super_init,
traits: Vec::new(),
vtable: VTable::empty(mc),
call_handler: None,
Expand Down Expand Up @@ -496,7 +474,6 @@ impl<'gc> Class<'gc> {
}

let instance_init = unit.load_method(abc_instance.init_method, false, activation)?;
let mut super_init = instance_init;
let class_init = unit.load_method(abc_class.init_method, false, activation)?;

let mut attributes = ClassAttributes::empty();
Expand All @@ -515,20 +492,6 @@ impl<'gc> Class<'gc> {
[class_index as usize]
.map(|(_name, ptr)| Allocator(ptr));

if let Some((name, table_native_init)) =
activation.avm2().native_super_initializer_table[class_index as usize]
{
let method = Method::from_builtin_and_params(
table_native_init,
name,
instance_init.signature().to_vec(),
instance_init.return_type(),
instance_init.is_variadic(),
activation.context.gc_context,
);
super_init = method;
}

if let Some((name, table_native_call_handler)) =
activation.avm2().native_call_handler_table[class_index as usize]
{
Expand Down Expand Up @@ -569,7 +532,6 @@ impl<'gc> Class<'gc> {
all_interfaces: Vec::new(),
instance_allocator,
instance_init,
super_init,
traits: Vec::new(),
vtable: VTable::empty(activation.context.gc_context),
call_handler,
Expand Down Expand Up @@ -603,7 +565,6 @@ impl<'gc> Class<'gc> {
all_interfaces: Vec::new(),
instance_allocator: Allocator(scriptobject_allocator),
instance_init: class_init,
super_init: class_init,
traits: Vec::new(),
vtable: VTable::empty(activation.context.gc_context),
call_handler: None,
Expand Down Expand Up @@ -861,11 +822,6 @@ impl<'gc> Class<'gc> {
"<Activation object constructor>",
activation.context.gc_context,
),
super_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
"<Activation object constructor>",
activation.context.gc_context,
),
traits,
vtable: VTable::empty(activation.context.gc_context),
call_handler: None,
Expand Down Expand Up @@ -903,11 +859,6 @@ impl<'gc> Class<'gc> {
"<Activation object class constructor>",
activation.context.gc_context,
),
super_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
"<Activation object class constructor>",
activation.context.gc_context,
),
traits: Vec::new(),
vtable: VTable::empty(activation.context.gc_context),
call_handler: None,
Expand Down Expand Up @@ -1285,16 +1236,6 @@ impl<'gc> Class<'gc> {
self.0.read().instance_init
}

/// Get this class's super() initializer.
pub fn super_init(self) -> Method<'gc> {
self.0.read().super_init
}

/// Set a super() initializer for this class.
pub fn set_super_init(self, mc: &Mutation<'gc>, new_super_init: Method<'gc>) {
self.0.write(mc).super_init = new_super_init;
}

/// Set a call handler for this class.
pub fn set_call_handler(self, mc: &Mutation<'gc>, new_call_handler: Method<'gc>) {
self.0.write(mc).call_handler = Some(new_call_handler);
Expand Down
1 change: 0 additions & 1 deletion core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,6 @@ fn load_playerglobal<'gc>(
) -> Result<(), Error<'gc>> {
activation.avm2().native_method_table = native::NATIVE_METHOD_TABLE;
activation.avm2().native_instance_allocator_table = native::NATIVE_INSTANCE_ALLOCATOR_TABLE;
activation.avm2().native_super_initializer_table = native::NATIVE_SUPER_INITIALIZER_TABLE;
activation.avm2().native_call_handler_table = native::NATIVE_CALL_HANDLER_TABLE;
activation.avm2().native_custom_constructor_table = native::NATIVE_CUSTOM_CONSTRUCTOR_TABLE;

Expand Down
Loading