diff --git a/core/src/avm2/names.rs b/core/src/avm2/names.rs index 64ff388d7..50965f627 100644 --- a/core/src/avm2/names.rs +++ b/core/src/avm2/names.rs @@ -4,8 +4,10 @@ use crate::avm2::activation::Activation; use crate::avm2::script::TranslationUnit; use crate::avm2::value::Value; use crate::avm2::Error; +use crate::either::Either; use crate::string::{AvmString, WStr, WString}; use gc_arena::{Collect, MutationContext}; +use std::fmt::Debug; use swf::avm2::types::{ AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace, NamespaceSet as AbcNamespaceSet, @@ -135,7 +137,7 @@ impl<'gc> Namespace<'gc> { /// `QName`. All other forms of names and multinames are either versions of /// `QName` with unspecified parameters, or multiple names to be checked in /// order. -#[derive(Clone, Copy, Collect, Debug, Hash)] +#[derive(Clone, Copy, Collect, Hash)] #[collect(no_drop)] pub struct QName<'gc> { ns: Namespace<'gc>, @@ -222,14 +224,32 @@ impl<'gc> QName<'gc> { /// Converts this `QName` to a fully qualified name. pub fn to_qualified_name(self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> { + match self.to_qualified_name_no_mc() { + Either::Left(avm_string) => avm_string, + Either::Right(wstring) => AvmString::new(mc, wstring), + } + } + + /// Like `to_qualified_name`, but avoids the need for a `MutationContext` + /// by returning `Either::Right(wstring)` when it would otherwise + /// be necessary to allocate a new `AvmString`. + /// + /// This method is intended for contexts like `Debug` impls where + /// a `MutationContext` is not available. Normally, you should + /// use `to_qualified_name` + pub fn to_qualified_name_no_mc(self) -> Either, WString> { let uri = self.namespace().as_uri(); let name = self.local_name(); - uri.is_empty().then_some(name).unwrap_or_else(|| { - let mut buf = WString::from(uri.as_wstr()); - buf.push_str(WStr::from_units(b"::")); - buf.push_str(&name); - AvmString::new(mc, buf) - }) + if uri.is_empty() { + Either::Left(name) + } else { + Either::Right({ + let mut buf = WString::from(uri.as_wstr()); + buf.push_str(WStr::from_units(b"::")); + buf.push_str(&name); + buf + }) + } } pub fn local_name(&self) -> AvmString<'gc> { @@ -264,6 +284,15 @@ impl<'gc> QName<'gc> { } } +impl<'gc> Debug for QName<'gc> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self.to_qualified_name_no_mc() { + Either::Left(name) => write!(f, "{}", name), + Either::Right(name) => write!(f, "{}", name), + } + } +} + /// A `Multiname` consists of a name which could be resolved in one or more /// potential namespaces. /// diff --git a/core/src/avm2/object/class_object.rs b/core/src/avm2/object/class_object.rs index ce9b835f8..96119f9e8 100644 --- a/core/src/avm2/object/class_object.rs +++ b/core/src/avm2/object/class_object.rs @@ -17,7 +17,7 @@ use crate::avm2::TranslationUnit; use crate::string::AvmString; use fnv::FnvHashMap; use gc_arena::{Collect, GcCell, MutationContext}; -use std::cell::{Ref, RefMut}; +use std::cell::{BorrowError, Ref, RefMut}; use std::hash::{Hash, Hasher}; /// An Object which can be called to execute its function code. @@ -706,6 +706,15 @@ impl<'gc> ClassObject<'gc> { self.0.read().class_vtable } + /// Like `inner_class_definition`, but returns an `Err(BorrowError)` instead of panicking + /// if our `GcCell` is already mutably borrowed. This is useful + /// in contexts where panicking would be extremely undesirable, + /// and there's a fallback if we cannot obtain the `Class` + /// (such as `Debug` impls), + pub fn try_inner_class_definition(&self) -> Result>, BorrowError> { + self.0.try_read().map(|c| c.class) + } + pub fn inner_class_definition(self) -> GcCell<'gc, Class<'gc>> { self.0.read().class } diff --git a/core/src/avm2/object/script_object.rs b/core/src/avm2/object/script_object.rs index 9a5cb603a..9c9ac3878 100644 --- a/core/src/avm2/object/script_object.rs +++ b/core/src/avm2/object/script_object.rs @@ -24,7 +24,7 @@ pub fn scriptobject_allocator<'gc>( } /// Default implementation of `avm2::Object`. -#[derive(Clone, Collect, Debug, Copy)] +#[derive(Clone, Collect, Copy)] #[collect(no_drop)] pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>); @@ -429,3 +429,35 @@ impl<'gc> ScriptObjectData<'gc> { self.vtable = Some(vtable); } } + +impl<'gc> Debug for ScriptObject<'gc> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let mut fmt = f.debug_struct("ScriptObject"); + + let class_name = self + .0 + .try_read() + .map(|obj| obj.instance_of()) + .transpose() + .map(|class_obj| { + class_obj + .and_then(|class_obj| class_obj.try_inner_class_definition()) + .and_then(|class| class.try_read().map(|c| c.name())) + }); + + match class_name { + Some(Ok(class_name)) => { + fmt.field("class", &class_name); + } + Some(Err(err)) => { + fmt.field("class", &err); + } + None => { + fmt.field("class", &""); + } + } + + fmt.field("ptr", &self.0.as_ptr()); + fmt.finish() + } +} diff --git a/core/src/either.rs b/core/src/either.rs new file mode 100644 index 000000000..428c74154 --- /dev/null +++ b/core/src/either.rs @@ -0,0 +1,4 @@ +pub enum Either { + Left(A), + Right(B), +} diff --git a/core/src/lib.rs b/core/src/lib.rs index e47c09caf..1d6a8f030 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -23,6 +23,7 @@ pub mod context; pub mod context_menu; mod drawing; mod ecma_conversions; +pub(crate) mod either; pub mod events; pub mod focus_tracker; mod font;