avm2: Include class name in ScriptObject debug (#7512)
* avm2: Include class name in ScriptObject debug Currently, the `ScriptObject` debug impl is almost useless - while you determine if two printed objects are the same by comparing the pointer value, you'll have no idea what kind of object it actually is. This PR now formats the `ScriptObject` output as a struct, printing a (fake) "class" field containing the class name. Before/after: ``` [ERROR ruffle_core::avm2::activation] AVM2 error: Cannot coerce Object(ScriptObject(ScriptObject(GcCell(Gc { ptr: 0x55f863936db8 })))) to an QName { ns: Private("Test.as$38"), name: "Second" } [ERROR ruffle_core::avm2::activation] AVM2 error: Cannot coerce Object(ScriptObject(ScriptObject { class: "Object", ptr: 0x55ee0ad161e0 })) to an QName { ns: Private("Test.as$38"), name: "Second" } ``` Getting access to the class name from a `Debug` impl is tricky: Developers can (and should be able to) insert logging statements whereever they want, so any `GcCell` may be mutably borrowed. Panics in debug impls are extremely frustrating to deal with, so I've ensured that we only use `try_borrow` at each step. If any of the attempted borrows fail, we print out an error message in the "class_name" field, but we're still able to print the rest of the `ScriptObject`. Additionally, we have no access to a `MutationContext`, so we cannot allocate a new `AvmString`. To get around this, I've created a new method `QName::to_qualified_name_no_mc`, which uses an `Either` to return a `WString` instead of allocating an `AvmString`. This is more cumbersome to work with than the nrmal `QName::to_qualified_name`, so we'll only want to use it when we have no other choice.
This commit is contained in:
parent
2383e6850f
commit
2f8dde86af
|
@ -4,8 +4,10 @@ use crate::avm2::activation::Activation;
|
||||||
use crate::avm2::script::TranslationUnit;
|
use crate::avm2::script::TranslationUnit;
|
||||||
use crate::avm2::value::Value;
|
use crate::avm2::value::Value;
|
||||||
use crate::avm2::Error;
|
use crate::avm2::Error;
|
||||||
|
use crate::either::Either;
|
||||||
use crate::string::{AvmString, WStr, WString};
|
use crate::string::{AvmString, WStr, WString};
|
||||||
use gc_arena::{Collect, MutationContext};
|
use gc_arena::{Collect, MutationContext};
|
||||||
|
use std::fmt::Debug;
|
||||||
use swf::avm2::types::{
|
use swf::avm2::types::{
|
||||||
AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace,
|
AbcFile, Index, Multiname as AbcMultiname, Namespace as AbcNamespace,
|
||||||
NamespaceSet as AbcNamespaceSet,
|
NamespaceSet as AbcNamespaceSet,
|
||||||
|
@ -135,7 +137,7 @@ impl<'gc> Namespace<'gc> {
|
||||||
/// `QName`. All other forms of names and multinames are either versions of
|
/// `QName`. All other forms of names and multinames are either versions of
|
||||||
/// `QName` with unspecified parameters, or multiple names to be checked in
|
/// `QName` with unspecified parameters, or multiple names to be checked in
|
||||||
/// order.
|
/// order.
|
||||||
#[derive(Clone, Copy, Collect, Debug, Hash)]
|
#[derive(Clone, Copy, Collect, Hash)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct QName<'gc> {
|
pub struct QName<'gc> {
|
||||||
ns: Namespace<'gc>,
|
ns: Namespace<'gc>,
|
||||||
|
@ -222,14 +224,32 @@ impl<'gc> QName<'gc> {
|
||||||
|
|
||||||
/// Converts this `QName` to a fully qualified name.
|
/// Converts this `QName` to a fully qualified name.
|
||||||
pub fn to_qualified_name(self, mc: MutationContext<'gc, '_>) -> AvmString<'gc> {
|
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<AvmString<'gc>, WString> {
|
||||||
let uri = self.namespace().as_uri();
|
let uri = self.namespace().as_uri();
|
||||||
let name = self.local_name();
|
let name = self.local_name();
|
||||||
uri.is_empty().then_some(name).unwrap_or_else(|| {
|
if uri.is_empty() {
|
||||||
let mut buf = WString::from(uri.as_wstr());
|
Either::Left(name)
|
||||||
buf.push_str(WStr::from_units(b"::"));
|
} else {
|
||||||
buf.push_str(&name);
|
Either::Right({
|
||||||
AvmString::new(mc, buf)
|
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> {
|
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
|
/// A `Multiname` consists of a name which could be resolved in one or more
|
||||||
/// potential namespaces.
|
/// potential namespaces.
|
||||||
///
|
///
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::avm2::TranslationUnit;
|
||||||
use crate::string::AvmString;
|
use crate::string::AvmString;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use gc_arena::{Collect, GcCell, MutationContext};
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
use std::cell::{Ref, RefMut};
|
use std::cell::{BorrowError, Ref, RefMut};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
/// An Object which can be called to execute its function code.
|
/// An Object which can be called to execute its function code.
|
||||||
|
@ -706,6 +706,15 @@ impl<'gc> ClassObject<'gc> {
|
||||||
self.0.read().class_vtable
|
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<GcCell<'gc, Class<'gc>>, BorrowError> {
|
||||||
|
self.0.try_read().map(|c| c.class)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner_class_definition(self) -> GcCell<'gc, Class<'gc>> {
|
pub fn inner_class_definition(self) -> GcCell<'gc, Class<'gc>> {
|
||||||
self.0.read().class
|
self.0.read().class
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub fn scriptobject_allocator<'gc>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default implementation of `avm2::Object`.
|
/// Default implementation of `avm2::Object`.
|
||||||
#[derive(Clone, Collect, Debug, Copy)]
|
#[derive(Clone, Collect, Copy)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
pub struct ScriptObject<'gc>(GcCell<'gc, ScriptObjectData<'gc>>);
|
||||||
|
|
||||||
|
@ -429,3 +429,35 @@ impl<'gc> ScriptObjectData<'gc> {
|
||||||
self.vtable = Some(vtable);
|
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", &"<None>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.field("ptr", &self.0.as_ptr());
|
||||||
|
fmt.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub enum Either<A, B> {
|
||||||
|
Left(A),
|
||||||
|
Right(B),
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ pub mod context;
|
||||||
pub mod context_menu;
|
pub mod context_menu;
|
||||||
mod drawing;
|
mod drawing;
|
||||||
mod ecma_conversions;
|
mod ecma_conversions;
|
||||||
|
pub(crate) mod either;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod focus_tracker;
|
pub mod focus_tracker;
|
||||||
mod font;
|
mod font;
|
||||||
|
|
Loading…
Reference in New Issue