Skip to content

avm2: Use return type of methods in optimizer #20257

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

Open
wants to merge 43 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c70cb8e
avm2: Make getter functions fields for URLLoader
Apr 20, 2025
fc0e585
avm2: Make fields getter/setter functions for MouseEvent
Apr 20, 2025
1fb6f57
avm2: Minor fixes in VideoTextureEvent, Video, and Telemetry classes
Apr 20, 2025
87cd082
avm2: Make fields getter/setter functions for Gradient*Filter
Apr 20, 2025
6286868
avm2: Make fields getter/setter functions for DRMReturnVoucher*Event
Apr 20, 2025
c84e0d9
avm2: Make `text` field into an accessor for TextEvent
Apr 20, 2025
4407f6a
avm2: Remove incorrect overrides of getter functions in TextLine
Apr 20, 2025
9a77685
avm2: Use correct method signatures for Microphone
Apr 20, 2025
c62d6c4
avm2: Make x and y fields getter/setter pairs for Stage3D
Apr 20, 2025
984364f
avm2: Make Mutex.isSupported a getter
Apr 20, 2025
60750c5
avm2: Make fields getter/setter functions for GraphicsGradientFill
Apr 20, 2025
575bd85
avm2: Make describeType(QName) match FP's output
Apr 20, 2025
8c02717
avm2: Make fields getter/setter for SampleDataEvent
Apr 20, 2025
236f518
avm2: Make describeType output for URLVariables and AVPauseAtPeriodEn…
Apr 20, 2025
c01a486
avm2: Make `activating` field a getter/setter for ActivityEvent
Apr 20, 2025
34b7390
avm2: Make describeType output for UncaughtErrorEvent and VideoStream…
Apr 20, 2025
4e10ab1
avm2: Make fields getter/setter functions for DRMAuthenticationComple…
Apr 20, 2025
da33263
avm2: Make `info` field an accessor for NetStatusEvent
Apr 20, 2025
06cd77a
chore: Format AS3 filter classes
Apr 20, 2025
84a971d
avm2: Make `enableErrorChecking` a field for Context3D
Apr 20, 2025
4734a4d
avm2: Fix versioning and use accessors for NativeMenuItem properties
Apr 20, 2025
b4615e6
avm2: Make fields getter/setter functions for ContextMenuItem
Apr 20, 2025
ec7eb52
avm2: Make fields getter/setter functions for AVHTTPStatusEvent
Apr 20, 2025
a0b49e9
avm2: Make `changeList` field a getter/setter for SyncEvent
Apr 20, 2025
dbf9458
avm2: Make fields getter/setter functions for StatusEvent
Apr 20, 2025
02b76d8
avm2: Make fields getter/setter functions for ProgressEvent
Apr 20, 2025
c4ccb62
avm2: Make `serverURL` field an accessor for DRMLicenseRequestEvent
Apr 20, 2025
6954215
avm2: Use accessors instead of fields for HTTPStatusEvent
Apr 20, 2025
58dd4a6
avm2: Make `culling` field an accessor for GraphicsTrianglePath
Apr 20, 2025
e7a710a
avm2: Make `relatedObject` field an accessor for SoftKeyboardEvent
Apr 20, 2025
d58fb11
avm2: Use accessors instead of fields for FocusEvent properties
Apr 20, 2025
eedf4ee
avm2: Use accessors instead of fields for NetConnection properties
Apr 20, 2025
040975c
avm2: Use accessors instead of fields for AccelerometerEvent properties
Apr 20, 2025
b2f45ec
avm2: Use accessors instead of fields for GestureEvent properties
Apr 20, 2025
1f3ddf6
avm2: Use accessors instead of fields for DRMAuthenticationErrorEvent…
Apr 20, 2025
71999d8
avm2: Use accessors instead of fields for TouchEvent properties
Apr 20, 2025
5247ced
chore: Format TouchEvent.as
Apr 20, 2025
89d7cec
avm2: Coerce return value of native methods
Apr 20, 2025
0a92420
avm2: Fix incorrect method return values/types for some native methods
Apr 21, 2025
101933a
avm2: Fully validate all classes using user-provided traits
Apr 22, 2025
1218754
avm2: Verify that overrides of methods have the same return type
Apr 22, 2025
f93fbc7
avm2: Return type of Transform.matrix3D should be *, not void
Apr 22, 2025
0506f76
avm2: Use type information from return type of getters and methods in…
Apr 22, 2025
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
92 changes: 92 additions & 0 deletions core/src/avm2/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,98 @@ impl<'gc> Class<'gc> {
Ok(())
}

/// Like validate_class, but instead validates the method signatures of
/// all methods, getters, and setters in the class. This should be called
/// at ClassObject construction time, after all classes are loaded.
pub fn validate_signatures(
self,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
let read = self.0.read();

let superclass = read.super_class;

if let Some(superclass) = superclass {
for instance_trait in read.traits.iter() {
let is_protected = read.protected_namespace.is_some_and(|prot| {
prot.exact_version_match(instance_trait.name().namespace())
});

let mut current_superclass = Some(superclass);
let mut found_match = false;

while let Some(superclass) = current_superclass {
for supertrait in &*superclass.traits() {
let super_name = supertrait.name();
let my_name = instance_trait.name();

let names_match = super_name.local_name() == my_name.local_name()
&& (super_name.namespace().matches_ns(my_name.namespace())
Copy link
Collaborator

@adrian17 adrian17 May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do it during (or after) init_vtable? It already contains the "check trait kind" logic. If we did it during init_vtable, we could directly compare method_table[*disp_id as usize] just before setting the override. If we did it after init_vtable, we could lookup the name in resolved_traits instead. Both feel faster and more correct than quadratic loop with name comparisons.

Actually...

I was just checking what FP does and turns out it does both: the trait kind check early and signature check late - and it can be observed.

The "is it legal to override trait kind A with B" and "overriding methods without override is illegal" checks are done early during initial class loading, preparing the main name->property hashmap; FP does this eagerly, while we do init_vtable late - so in FP the class can fail these even if it's never used.

(see Traits::getOverride and where it's used. It does do a lookup on parent by name just like we do match resolved_traits.get() in init_vtable.)

Meanwhile the overridden methods' signatures are compared late, like in newclass. So if the class exists and has a mismatched signature but is never used/referred to, there won't be any VerifyErrors.

(see TraitsBindings::checkOverride, used in VTable::resolveSignatures(self), which is directly called from MethodEnv::newclass (and a dozen other places). It also doesn't concern itself with names anymore, it just compares vtable.methods[dispid] vs parent_vtable.methods[dispid]).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying we need to do it exactly like FP; simply moving the entire logic during/after init_vtable will just prevent some quadratic loops and should be conceptually simpler overall.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And even if the verification didn't move and stayed before init_vtable, couldn't we still lookup the superclass's fields by name without a loop, just like init_vtable does: resolved_traits.get(trait_data.name())?

|| (is_protected
&& superclass.protected_namespace().is_some_and(|prot| {
prot.exact_version_match(super_name.namespace())
})));
if names_match {
match (supertrait.kind(), instance_trait.kind()) {
(TraitKind::Getter { .. }, TraitKind::Setter { .. }) => continue,
(TraitKind::Setter { .. }, TraitKind::Getter { .. }) => continue,

(_, TraitKind::Const { .. })
| (_, TraitKind::Slot { .. })
| (_, TraitKind::Class { .. }) => {
found_match = true;
}
(TraitKind::Getter { .. }, TraitKind::Getter { .. })
| (TraitKind::Setter { .. }, TraitKind::Setter { .. })
| (TraitKind::Method { .. }, TraitKind::Method { .. }) => {
found_match = true;

let instance_method = instance_trait.as_method().unwrap();
if !instance_method.is_info_resolved() {
instance_method.resolve_info(activation)?;
}

let super_method = supertrait.as_method().unwrap();
if !super_method.is_info_resolved() {
super_method.resolve_info(activation)?;
}

// Methods must have same return type
let instance_return_type =
instance_method.resolved_return_type();
let super_return_type = super_method.resolved_return_type();

if instance_return_type != super_return_type {
return Err(make_error_1053(
activation,
instance_trait.name().local_name(),
read.name.to_qualified_name_err_message(
activation.context.gc_context,
),
));
}
}
_ => unreachable!("Other trait combinations are invalid"),
}

break;
}
}

// The signature is already validated so we don't need to
// check further.
if found_match {
break;
}

current_superclass = superclass.super_class();
}
}
}

Ok(())
}

pub fn init_vtable(self, context: &mut UpdateContext<'gc>) -> Result<(), Error<'gc>> {
let read = self.0.read();

Expand Down
1 change: 1 addition & 0 deletions core/src/avm2/globals/global_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub fn create_class<'gc>(
class.set_attributes(mc, ClassAttributes::FINAL);

class.validate_class(activation, true)?;
class.validate_signatures(activation)?;
class
.init_vtable(activation.context)
.expect("Native class's vtable should initialize");
Expand Down
12 changes: 7 additions & 5 deletions core/src/avm2/object/class_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,19 +301,21 @@ impl<'gc> ClassObject<'gc> {
self.base().set_proto(gc_context, proto);
}

/// Run the class's initializer method.
/// Validate signatures of the class and run the class's initializer method.
pub fn run_class_initializer(
self,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
let class = self.inner_class_definition();
let c_class = class.c_class().expect("ClassObject stores an i_class");

class.validate_signatures(activation)?;
c_class.validate_signatures(activation)?;

let self_value: Value<'gc> = self.into();
let class_classobject = activation.avm2().classes().class;

let scope = self.0.class_scope;
let c_class = self
.inner_class_definition()
.c_class()
.expect("ClassObject stores an i_class");

let Some(class_initializer) = c_class.instance_init() else {
unreachable!("c_classes should always have initializer methods")
Expand Down