avm1: Refactor Executable::exec
- reduce rightwards drift by exiting early - outline some code into separate methods on Avm1Function
This commit is contained in:
parent
58b4342355
commit
aa1e53e0e3
|
@ -143,6 +143,81 @@ impl<'gc> Avm1Function<'gc> {
|
||||||
pub fn register_count(&self) -> u8 {
|
pub fn register_count(&self) -> u8 {
|
||||||
self.register_count
|
self.register_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug_string_for_call(&self, name: ExecutionName<'gc>, args: &[Value<'gc>]) -> String {
|
||||||
|
let mut result = match self.name.map(ExecutionName::Dynamic).unwrap_or(name) {
|
||||||
|
ExecutionName::Static(n) => n.to_owned(),
|
||||||
|
ExecutionName::Dynamic(n) => n.to_utf8_lossy().into_owned(),
|
||||||
|
};
|
||||||
|
result.push('(');
|
||||||
|
for i in 0..args.len() {
|
||||||
|
result.push_str(args.get(i).unwrap().type_of());
|
||||||
|
if i < args.len() - 1 {
|
||||||
|
result.push_str(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(')');
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_this(&self, frame: &mut Activation<'_, 'gc, '_>, this: Value<'gc>, preload_r: &mut u8) {
|
||||||
|
if self.flags.contains(FunctionFlags::PRELOAD_THIS) {
|
||||||
|
//TODO: What happens if you specify both suppress and
|
||||||
|
//preload for this?
|
||||||
|
frame.set_local_register(*preload_r, this);
|
||||||
|
*preload_r += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_arguments(&self, frame: &mut Activation<'_, 'gc, '_>, arguments: Value<'gc>, preload_r: &mut u8) {
|
||||||
|
if self.flags.contains(FunctionFlags::PRELOAD_ARGUMENTS) {
|
||||||
|
//TODO: What happens if you specify both suppress and
|
||||||
|
//preload for arguments?
|
||||||
|
frame.set_local_register(*preload_r, arguments);
|
||||||
|
*preload_r += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_super(&self, frame: &mut Activation<'_, 'gc, '_>, super_object: Option<Object<'gc>>, preload_r: &mut u8) {
|
||||||
|
if let Some(super_object) = super_object {
|
||||||
|
if self.flags.contains(FunctionFlags::PRELOAD_SUPER) {
|
||||||
|
frame.set_local_register(*preload_r, super_object.into());
|
||||||
|
//TODO: What happens if you specify both suppress and
|
||||||
|
//preload for super?
|
||||||
|
*preload_r += 1;
|
||||||
|
} else {
|
||||||
|
frame.force_define_local("super".into(), super_object.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_root(&self, frame: &mut Activation<'_, 'gc, '_>, preload_r: &mut u8) {
|
||||||
|
if self.flags.contains(FunctionFlags::PRELOAD_ROOT) {
|
||||||
|
let root = self.base_clip.avm1_root().object();
|
||||||
|
frame.set_local_register(*preload_r, root);
|
||||||
|
*preload_r += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_parent(&self, frame: &mut Activation<'_, 'gc, '_>, preload_r: &mut u8) {
|
||||||
|
if self.flags.contains(FunctionFlags::PRELOAD_PARENT) {
|
||||||
|
// If _parent is undefined (because this is a root timeline), it actually does not get pushed,
|
||||||
|
// and _global ends up incorrectly taking _parent's register.
|
||||||
|
// See test for more info.
|
||||||
|
if let Some(parent) = self.base_clip.avm1_parent() {
|
||||||
|
frame.set_local_register(*preload_r, parent.object());
|
||||||
|
*preload_r += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_global(&self, frame: &mut Activation<'_, 'gc, '_>, preload_r: &mut u8) {
|
||||||
|
if self.flags.contains(FunctionFlags::PRELOAD_GLOBAL) {
|
||||||
|
let global = frame.context.avm1.global_object();
|
||||||
|
frame.set_local_register(*preload_r, global);
|
||||||
|
*preload_r += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Collect)]
|
#[derive(Debug, Clone, Collect)]
|
||||||
|
@ -218,200 +293,147 @@ impl<'gc> Executable<'gc> {
|
||||||
reason: ExecutionReason,
|
reason: ExecutionReason,
|
||||||
callee: Object<'gc>,
|
callee: Object<'gc>,
|
||||||
) -> Result<Value<'gc>, Error<'gc>> {
|
) -> Result<Value<'gc>, Error<'gc>> {
|
||||||
match self {
|
let af = match self {
|
||||||
Executable::Native(nf) => {
|
Executable::Native(nf) => {
|
||||||
// TODO: Change NativeFunction to accept `this: Value`.
|
// TODO: Change NativeFunction to accept `this: Value`.
|
||||||
let this = this.coerce_to_object(activation);
|
let this = this.coerce_to_object(activation);
|
||||||
nf(activation, this, args)
|
return nf(activation, this, args);
|
||||||
}
|
}
|
||||||
Executable::Action(af) => {
|
Executable::Action(af) => af,
|
||||||
let this_obj = match this {
|
};
|
||||||
Value::Object(obj) => Some(obj),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let target = activation.target_clip_or_root();
|
let this_obj = match this {
|
||||||
let is_closure = activation.swf_version() >= 6;
|
Value::Object(obj) => Some(obj),
|
||||||
let base_clip = if (is_closure || reason == ExecutionReason::Special)
|
_ => None,
|
||||||
&& !af.base_clip.removed()
|
};
|
||||||
{
|
|
||||||
af.base_clip
|
let target = activation.target_clip_or_root();
|
||||||
} else {
|
let is_closure = activation.swf_version() >= 6;
|
||||||
this_obj
|
let base_clip = if (is_closure || reason == ExecutionReason::Special)
|
||||||
.and_then(|this| this.as_display_object())
|
&& !af.base_clip.removed()
|
||||||
.unwrap_or(target)
|
{
|
||||||
};
|
af.base_clip
|
||||||
let (swf_version, parent_scope) = if is_closure {
|
} else {
|
||||||
// Function calls in a v6+ SWF are proper closures, and "close" over the scope that defined the function:
|
this_obj
|
||||||
// * Use the SWF version from the SWF that defined the function.
|
.and_then(|this| this.as_display_object())
|
||||||
// * Use the base clip from when the function was defined.
|
.unwrap_or(target)
|
||||||
// * Close over the scope from when the function was defined.
|
};
|
||||||
(af.swf_version(), af.scope())
|
let (swf_version, parent_scope) = if is_closure {
|
||||||
} else {
|
// Function calls in a v6+ SWF are proper closures, and "close" over the scope that defined the function:
|
||||||
// Function calls in a v5 SWF are *not* closures, and will use the settings of
|
// * Use the SWF version from the SWF that defined the function.
|
||||||
// `this`, regardless of the function's origin:
|
// * Use the base clip from when the function was defined.
|
||||||
// * Use the SWF version of `this`.
|
// * Close over the scope from when the function was defined.
|
||||||
// * Use the base clip of `this`.
|
(af.swf_version(), af.scope())
|
||||||
// * Allocate a new scope using the given base clip. No previous scope is closed over.
|
} else {
|
||||||
let swf_version = base_clip.swf_version();
|
// Function calls in a v5 SWF are *not* closures, and will use the settings of
|
||||||
let base_clip_obj = match base_clip.object() {
|
// `this`, regardless of the function's origin:
|
||||||
Value::Object(o) => o,
|
// * Use the SWF version of `this`.
|
||||||
_ => unreachable!(),
|
// * Use the base clip of `this`.
|
||||||
};
|
// * Allocate a new scope using the given base clip. No previous scope is closed over.
|
||||||
// TODO: It would be nice to avoid these extra Scope allocs.
|
let swf_version = base_clip.swf_version();
|
||||||
let scope = GcCell::allocate(
|
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,
|
activation.context.gc_context,
|
||||||
Scope::new(
|
Scope::from_global_object(activation.context.avm1.globals),
|
||||||
GcCell::allocate(
|
),
|
||||||
activation.context.gc_context,
|
super::scope::ScopeClass::Target,
|
||||||
Scope::from_global_object(activation.context.avm1.globals),
|
base_clip_obj,
|
||||||
),
|
),
|
||||||
super::scope::ScopeClass::Target,
|
);
|
||||||
base_clip_obj,
|
(swf_version, scope)
|
||||||
),
|
};
|
||||||
);
|
|
||||||
(swf_version, scope)
|
|
||||||
};
|
|
||||||
|
|
||||||
let child_scope = GcCell::allocate(
|
let child_scope = GcCell::allocate(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
Scope::new_local_scope(parent_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) {
|
||||||
ArrayObject::empty(activation)
|
ArrayObject::empty(activation)
|
||||||
} else {
|
} else {
|
||||||
ArrayObject::new(
|
ArrayObject::new(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
activation.context.avm1.prototypes().array,
|
activation.context.avm1.prototypes().array,
|
||||||
args.iter().cloned(),
|
args.iter().cloned(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
arguments.define_value(
|
arguments.define_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
"callee",
|
"callee",
|
||||||
callee.into(),
|
callee.into(),
|
||||||
Attribute::DONT_ENUM,
|
Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
// The caller is the previous callee.
|
// The caller is the previous callee.
|
||||||
arguments.define_value(
|
arguments.define_value(
|
||||||
activation.context.gc_context,
|
activation.context.gc_context,
|
||||||
"caller",
|
"caller",
|
||||||
activation.callee.map(Value::from).unwrap_or(Value::Null),
|
activation.callee.map(Value::from).unwrap_or(Value::Null),
|
||||||
Attribute::DONT_ENUM,
|
Attribute::DONT_ENUM,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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| {
|
||||||
if !af.flags.contains(FunctionFlags::SUPPRESS_SUPER) {
|
if !af.flags.contains(FunctionFlags::SUPPRESS_SUPER) {
|
||||||
Some(SuperObject::new(activation, this, depth).into())
|
Some(SuperObject::new(activation, this, depth).into())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
Cow::Owned(af.debug_string_for_call(name, args))
|
||||||
ExecutionName::Static(n) => n.to_owned(),
|
} else {
|
||||||
ExecutionName::Dynamic(n) => n.to_utf8_lossy().into_owned(),
|
Cow::Borrowed("[Anonymous]")
|
||||||
};
|
};
|
||||||
|
|
||||||
result.push('(');
|
let max_recursion_depth = activation.context.avm1.max_recursion_depth();
|
||||||
for i in 0..args.len() {
|
let mut frame = Activation::from_action(
|
||||||
result.push_str(args.get(i).unwrap().type_of());
|
activation.context.reborrow(),
|
||||||
if i < args.len() - 1 {
|
activation.id.function(name, reason, max_recursion_depth)?,
|
||||||
result.push_str(", ");
|
swf_version,
|
||||||
}
|
child_scope,
|
||||||
}
|
af.constant_pool,
|
||||||
result.push(')');
|
base_clip,
|
||||||
|
this,
|
||||||
|
Some(callee),
|
||||||
|
Some(arguments.into()),
|
||||||
|
);
|
||||||
|
|
||||||
Cow::Owned(result)
|
frame.allocate_local_registers(af.register_count(), frame.context.gc_context);
|
||||||
} else {
|
|
||||||
Cow::Borrowed("[Anonymous]")
|
|
||||||
};
|
|
||||||
|
|
||||||
let max_recursion_depth = activation.context.avm1.max_recursion_depth();
|
let mut preload_r = 1;
|
||||||
let mut frame = Activation::from_action(
|
af.load_this(&mut frame, this, &mut preload_r);
|
||||||
activation.context.reborrow(),
|
af.load_arguments(&mut frame, arguments.into(), &mut preload_r);
|
||||||
activation.id.function(name, reason, max_recursion_depth)?,
|
af.load_super(&mut frame, super_object, &mut preload_r);
|
||||||
swf_version,
|
af.load_root(&mut frame, &mut preload_r);
|
||||||
child_scope,
|
af.load_parent(&mut frame, &mut preload_r);
|
||||||
af.constant_pool,
|
af.load_global(&mut frame, &mut preload_r);
|
||||||
base_clip,
|
|
||||||
this,
|
|
||||||
Some(callee),
|
|
||||||
Some(arguments.into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
frame.allocate_local_registers(af.register_count(), frame.context.gc_context);
|
// Any unassigned args are set to undefined to prevent assignments from leaking to the parent scope (#2166)
|
||||||
|
let args_iter = args
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.chain(std::iter::repeat(Value::Undefined));
|
||||||
|
|
||||||
let mut preload_r = 1;
|
//TODO: What happens if the argument registers clash with the
|
||||||
|
//preloaded registers? What gets done last?
|
||||||
if af.flags.contains(FunctionFlags::PRELOAD_THIS) {
|
for (param, value) in af.params.iter().zip(args_iter) {
|
||||||
//TODO: What happens if you specify both suppress and
|
if let Some(register) = param.register {
|
||||||
//preload for this?
|
frame.set_local_register(register.get(), value);
|
||||||
frame.set_local_register(preload_r, this);
|
} else {
|
||||||
preload_r += 1;
|
frame.force_define_local(param.name, value);
|
||||||
}
|
|
||||||
|
|
||||||
if af.flags.contains(FunctionFlags::PRELOAD_ARGUMENTS) {
|
|
||||||
//TODO: What happens if you specify both suppress and
|
|
||||||
//preload for arguments?
|
|
||||||
frame.set_local_register(preload_r, arguments.into());
|
|
||||||
preload_r += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(super_object) = super_object {
|
|
||||||
if af.flags.contains(FunctionFlags::PRELOAD_SUPER) {
|
|
||||||
frame.set_local_register(preload_r, super_object.into());
|
|
||||||
//TODO: What happens if you specify both suppress and
|
|
||||||
//preload for super?
|
|
||||||
preload_r += 1;
|
|
||||||
} else {
|
|
||||||
frame.force_define_local("super".into(), super_object.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if af.flags.contains(FunctionFlags::PRELOAD_ROOT) {
|
|
||||||
frame.set_local_register(preload_r, af.base_clip.avm1_root().object());
|
|
||||||
preload_r += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if af.flags.contains(FunctionFlags::PRELOAD_PARENT) {
|
|
||||||
// If _parent is undefined (because this is a root timeline), it actually does not get pushed,
|
|
||||||
// and _global ends up incorrectly taking _parent's register.
|
|
||||||
// See test for more info.
|
|
||||||
if let Some(parent) = af.base_clip.avm1_parent() {
|
|
||||||
frame.set_local_register(preload_r, parent.object());
|
|
||||||
preload_r += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if af.flags.contains(FunctionFlags::PRELOAD_GLOBAL) {
|
|
||||||
let global = frame.context.avm1.global_object();
|
|
||||||
frame.set_local_register(preload_r, global);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Any unassigned args are set to undefined to prevent assignments from leaking to the parent scope (#2166)
|
|
||||||
let args_iter = args
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.chain(std::iter::repeat(Value::Undefined));
|
|
||||||
|
|
||||||
//TODO: What happens if the argument registers clash with the
|
|
||||||
//preloaded registers? What gets done last?
|
|
||||||
for (param, value) in af.params.iter().zip(args_iter) {
|
|
||||||
if let Some(register) = param.register {
|
|
||||||
frame.set_local_register(register.get(), value);
|
|
||||||
} else {
|
|
||||||
frame.force_define_local(param.name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(frame.run_actions(af.data.clone())?.value())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(frame.run_actions(af.data.clone())?.value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue