diff --git a/core/src/avm2.rs b/core/src/avm2.rs index bf0ddeb8a..e3dbe8454 100644 --- a/core/src/avm2.rs +++ b/core/src/avm2.rs @@ -452,7 +452,7 @@ impl<'gc> Avm2<'gc> { } /// Pushes an executable on the call stack - pub fn push_call(&self, mc: MutationContext<'gc, '_>, calling: Executable<'gc>) { + pub fn push_call(&self, mc: MutationContext<'gc, '_>, calling: &Executable<'gc>) { self.call_stack.write(mc).push(calling) } diff --git a/core/src/avm2/call_stack.rs b/core/src/avm2/call_stack.rs index 4f295c9af..088236274 100644 --- a/core/src/avm2/call_stack.rs +++ b/core/src/avm2/call_stack.rs @@ -1,4 +1,6 @@ -use crate::avm2::function::Executable; +use crate::avm2::function::{display_function, Executable}; +use crate::avm2::method::Method; +use crate::avm2::object::ClassObject; use crate::string::WString; use gc_arena::Collect; @@ -8,7 +10,10 @@ use super::script::Script; #[collect(no_drop)] pub enum CallNode<'gc> { GlobalInit(Script<'gc>), - Method(Executable<'gc>), + Method { + method: Method<'gc>, + superclass: Option>, + }, } #[derive(Collect, Clone)] @@ -22,8 +27,11 @@ impl<'gc> CallStack<'gc> { Self { stack: Vec::new() } } - pub fn push(&mut self, exec: Executable<'gc>) { - self.stack.push(CallNode::Method(exec)) + pub fn push(&mut self, exec: &Executable<'gc>) { + self.stack.push(CallNode::Method { + method: exec.as_method(), + superclass: exec.bound_superclass(), + }) } pub fn push_global_init(&mut self, script: Script<'gc>) { @@ -54,7 +62,9 @@ impl<'gc> CallStack<'gc> { // added by Ruffle output.push_utf8(&format!("global$init() [TU={}]", name)); } - CallNode::Method(exec) => exec.write_full_name(output), + CallNode::Method { method, superclass } => { + display_function(output, method, *superclass) + } } } } diff --git a/core/src/avm2/function.rs b/core/src/avm2/function.rs index 7d5171a73..03742e5b7 100644 --- a/core/src/avm2/function.rs +++ b/core/src/avm2/function.rs @@ -141,7 +141,7 @@ impl<'gc> Executable<'gc> { activation .context .avm2 - .push_call(activation.context.gc_context, self.clone()); + .push_call(activation.context.gc_context, self); method(&mut activation, receiver, &arguments) } Executable::Action(bm) => { @@ -167,7 +167,7 @@ impl<'gc> Executable<'gc> { activation .context .avm2 - .push_call(activation.context.gc_context, self.clone()); + .push_call(activation.context.gc_context, self); activation.run_actions(bm.method) } }; @@ -189,90 +189,15 @@ impl<'gc> Executable<'gc> { } } - pub fn write_full_name(&self, output: &mut WString) { - let class_def = self.bound_superclass().map(|superclass| { - let class_def = superclass.inner_class_definition(); - let name = class_def.read().name().to_qualified_name_no_mc(); - output.push_str(&name); - class_def - }); + pub fn as_method(&self) -> Method<'gc> { match self { - Executable::Native(NativeExecutable { method, .. }) => { - output.push_char('/'); - output.push_utf8(method.name) - } - Executable::Action(BytecodeExecutable { method, .. }) => { - // NOTE: The name of a bytecode method refers to the name of the trait that contains the method, - // rather than the name of the method itself. - if let Some(class_def) = class_def { - if class_def - .read() - .class_init() - .into_bytecode() - .map(|b| Gc::ptr_eq(b, *method)) - .unwrap_or(false) - { - output.push_utf8("$cinit"); - } else if !class_def - .read() - .instance_init() - .into_bytecode() - .map(|b| Gc::ptr_eq(b, *method)) - .unwrap_or(false) - { - // TODO: Ideally, the declaring trait of this executable should already be attached here, that way - // we can avoid needing to lookup the trait like this. - let class_def = class_def.read(); - let mut method_trait = None; - // First search instance traits for the method - for t in class_def.instance_traits() { - if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) { - if Gc::ptr_eq(b, *method) { - method_trait = Some(t); - break; - } - } - } - if method_trait.is_none() { - // If we can't find it in instance traits, search class traits instead - for t in class_def.class_traits() { - if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) - { - if Gc::ptr_eq(b, *method) { - // Class traits always start with $ - output.push_char('$'); - method_trait = Some(t); - break; - } - } - } - } - if let Some(method_trait) = method_trait { - output.push_char('/'); - match method_trait.kind() { - TraitKind::Setter { .. } => output.push_utf8("set "), - TraitKind::Getter { .. } => output.push_utf8("get "), - _ => (), - } - if method_trait.name().namespace().is_namespace() { - output.push_str(&method_trait.name().to_qualified_name_no_mc()); - } else { - output.push_str(&method_trait.name().local_name()); - } - } - // TODO: What happens if we can't find the trait? - } - // We purposely do nothing for instance initializers - } else if method.is_function && !method.method_name().is_empty() { - output.push_utf8("Function/"); - output.push_utf8(method.method_name()); - } else { - output.push_utf8("MethodInfo-"); - output.push_utf8(&method.abc_method.to_string()); - } - } + Executable::Native(nm) => Method::Native(nm.method), + Executable::Action(bm) => Method::Bytecode(bm.method), } - output.push_utf8("()"); + } + + pub fn write_full_name(&self, output: &mut WString) { + display_function(output, &self.as_method(), self.bound_superclass()); } pub fn num_parameters(&self) -> usize { @@ -300,3 +225,92 @@ impl<'gc> fmt::Debug for Executable<'gc> { } } } + +pub fn display_function<'gc>( + output: &mut WString, + method: &Method<'gc>, + superclass: Option>, +) { + let class_def = superclass.map(|superclass| { + let class_def = superclass.inner_class_definition(); + let name = class_def.read().name().to_qualified_name_no_mc(); + output.push_str(&name); + class_def + }); + match method { + Method::Native(method) => { + output.push_char('/'); + output.push_utf8(method.name) + } + Method::Bytecode(method) => { + // NOTE: The name of a bytecode method refers to the name of the trait that contains the method, + // rather than the name of the method itself. + if let Some(class_def) = class_def { + if class_def + .read() + .class_init() + .into_bytecode() + .map(|b| Gc::ptr_eq(b, *method)) + .unwrap_or(false) + { + output.push_utf8("$cinit"); + } else if !class_def + .read() + .instance_init() + .into_bytecode() + .map(|b| Gc::ptr_eq(b, *method)) + .unwrap_or(false) + { + // TODO: Ideally, the declaring trait of this executable should already be attached here, that way + // we can avoid needing to lookup the trait like this. + let class_def = class_def.read(); + let mut method_trait = None; + // First search instance traits for the method + for t in class_def.instance_traits() { + if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) { + if Gc::ptr_eq(b, *method) { + method_trait = Some(t); + break; + } + } + } + if method_trait.is_none() { + // If we can't find it in instance traits, search class traits instead + for t in class_def.class_traits() { + if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) { + if Gc::ptr_eq(b, *method) { + // Class traits always start with $ + output.push_char('$'); + method_trait = Some(t); + break; + } + } + } + } + if let Some(method_trait) = method_trait { + output.push_char('/'); + match method_trait.kind() { + TraitKind::Setter { .. } => output.push_utf8("set "), + TraitKind::Getter { .. } => output.push_utf8("get "), + _ => (), + } + if method_trait.name().namespace().is_namespace() { + output.push_str(&method_trait.name().to_qualified_name_no_mc()); + } else { + output.push_str(&method_trait.name().local_name()); + } + } + // TODO: What happens if we can't find the trait? + } + // We purposely do nothing for instance initializers + } else if method.is_function && !method.method_name().is_empty() { + output.push_utf8("Function/"); + output.push_utf8(method.method_name()); + } else { + output.push_utf8("MethodInfo-"); + output.push_utf8(&method.abc_method.to_string()); + } + } + } + output.push_utf8("()"); +}