avm1: Fix scope of function calls in SWFv5 vs. SWFv6+
Fix various scope behavior when executing a function: * For function calls in SWFv6+, functions are proper closures and close over the SWF version, base clip, and scope of their defining function. * Function calls in SWFv5 are not closures, and use the version, base clip, and scope of `this` when the function is called. Fixes #5502.
This commit is contained in:
parent
a453aa73af
commit
4904b2cc97
|
@ -224,9 +224,57 @@ impl<'gc> Executable<'gc> {
|
||||||
nf(activation, this, args)
|
nf(activation, this, args)
|
||||||
}
|
}
|
||||||
Executable::Action(af) => {
|
Executable::Action(af) => {
|
||||||
|
let this_obj = match this {
|
||||||
|
Value::Object(obj) => Some(obj),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let target = activation.target_clip_or_root();
|
||||||
|
let (swf_version, base_clip, parent_scope) = if activation.swf_version() >= 6 {
|
||||||
|
// Function calls in a v6+ SWF are proper closures, and "close" over the scope that defined the function:
|
||||||
|
// * Use the SWF version from the SWF that defined the function.
|
||||||
|
// * Use the base clip from when the function was defined.
|
||||||
|
// * Close over the scope from when the function was defined.
|
||||||
|
let base_clip = if !af.base_clip.removed() {
|
||||||
|
af.base_clip
|
||||||
|
} else {
|
||||||
|
this_obj
|
||||||
|
.and_then(|this| this.as_display_object())
|
||||||
|
.unwrap_or(target)
|
||||||
|
};
|
||||||
|
(af.swf_version(), base_clip, af.scope())
|
||||||
|
} else {
|
||||||
|
// Function calls in a v5 SWF are *not* closures, and will use the settings of
|
||||||
|
// `this`, regardless of the function's origin:
|
||||||
|
// * Use the SWF version of `this`.
|
||||||
|
// * Use the base clip of `this`.
|
||||||
|
// * Allocate a new scope using the given base clip. No previous scope is closed over.
|
||||||
|
let base_clip = this_obj
|
||||||
|
.and_then(|this| this.as_display_object())
|
||||||
|
.unwrap_or(target);
|
||||||
|
let swf_version = base_clip.swf_version();
|
||||||
|
let base_clip_obj = match base_clip.object() {
|
||||||
|
Value::Object(o) => o,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
// TODO: It would be nice to avoid these extra Scope allocs.
|
||||||
|
let scope = GcCell::allocate(
|
||||||
|
activation.context.gc_context,
|
||||||
|
Scope::new(
|
||||||
|
GcCell::allocate(
|
||||||
|
activation.context.gc_context,
|
||||||
|
Scope::from_global_object(activation.context.avm1.globals),
|
||||||
|
),
|
||||||
|
super::scope::ScopeClass::Target,
|
||||||
|
base_clip_obj,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
(swf_version, base_clip, scope)
|
||||||
|
};
|
||||||
|
|
||||||
let child_scope = GcCell::allocate(
|
let child_scope = GcCell::allocate(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Scope::new_local_scope(af.scope(), activation.context.gc_context),
|
Scope::new_local_scope(parent_scope, activation.context.gc_context),
|
||||||
);
|
);
|
||||||
|
|
||||||
let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
|
let arguments = if af.flags.contains(FunctionFlags::SUPPRESS_ARGUMENTS) {
|
||||||
|
@ -252,11 +300,6 @@ impl<'gc> Executable<'gc> {
|
||||||
Attribute::DONT_ENUM,
|
Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
|
|
||||||
let this_obj = match this {
|
|
||||||
Value::Object(obj) => Some(obj),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: `super` should only be defined if this was a method call (depth > 0?)
|
// TODO: `super` should only be defined if this was a method call (depth > 0?)
|
||||||
// `f[""]()` emits a CallMethod op, causing `this` to be undefined, but `super` is a function; what is it?
|
// `f[""]()` emits a CallMethod op, causing `this` to be undefined, but `super` is a function; what is it?
|
||||||
let super_object: Option<Object<'gc>> = this_obj.and_then(|this| {
|
let super_object: Option<Object<'gc>> = this_obj.and_then(|this| {
|
||||||
|
@ -267,19 +310,6 @@ impl<'gc> Executable<'gc> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let effective_version = if activation.swf_version() > 5 {
|
|
||||||
if !af.base_clip.removed() {
|
|
||||||
af.base_clip.swf_version()
|
|
||||||
} else {
|
|
||||||
af.swf_version()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this_obj
|
|
||||||
.and_then(|this| this.as_display_object())
|
|
||||||
.map(|dn| dn.swf_version())
|
|
||||||
.unwrap_or(activation.context.player_version)
|
|
||||||
};
|
|
||||||
|
|
||||||
let name = if cfg!(feature = "avm_debug") {
|
let name = if cfg!(feature = "avm_debug") {
|
||||||
let mut result = match af.name.map(ExecutionName::Dynamic).unwrap_or(name) {
|
let mut result = match af.name.map(ExecutionName::Dynamic).unwrap_or(name) {
|
||||||
ExecutionName::Static(n) => n.to_owned(),
|
ExecutionName::Static(n) => n.to_owned(),
|
||||||
|
@ -301,17 +331,10 @@ impl<'gc> Executable<'gc> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let max_recursion_depth = activation.context.avm1.max_recursion_depth();
|
let max_recursion_depth = activation.context.avm1.max_recursion_depth();
|
||||||
let base_clip = if effective_version > 5 && !af.base_clip.removed() {
|
|
||||||
af.base_clip
|
|
||||||
} else {
|
|
||||||
this_obj
|
|
||||||
.and_then(|this| this.as_display_object())
|
|
||||||
.unwrap_or_else(|| activation.base_clip())
|
|
||||||
};
|
|
||||||
let mut frame = Activation::from_action(
|
let mut frame = Activation::from_action(
|
||||||
activation.context.reborrow(),
|
activation.context.reborrow(),
|
||||||
activation.id.function(name, reason, max_recursion_depth)?,
|
activation.id.function(name, reason, max_recursion_depth)?,
|
||||||
effective_version,
|
swf_version,
|
||||||
child_scope,
|
child_scope,
|
||||||
af.constant_pool,
|
af.constant_pool,
|
||||||
base_clip,
|
base_clip,
|
||||||
|
|
Loading…
Reference in New Issue