avm2: Add `error_backtrace` feature to capture RustError backtrace

This off-by-default feature is useful when debugging a RustError
(e.g. `TypeError: null is not an Object`) that wasn't handled
by a caller like `coerce_to_type`
This commit is contained in:
Aaron Hill 2024-07-02 09:38:08 -04:00
parent fe08638d26
commit 1517e4037c
No known key found for this signature in database
GPG Key ID: B4087E510E98B164
3 changed files with 57 additions and 9 deletions

View File

@ -91,6 +91,7 @@ jpegxr = ["dep:jpegxr", "lzma"]
default_font = []
test_only_as3 = []
serde = ["serde/derive"]
error_backtrace = []
[build-dependencies]
build_playerglobal = { path = "build_playerglobal" }

View File

@ -4,7 +4,6 @@ use crate::avm2::object::TObject;
use crate::avm2::{Activation, AvmString, Class, Multiname, Value};
use std::borrow::Cow;
use std::fmt::Debug;
use std::mem::size_of;
use super::function::display_function;
use super::method::Method;
@ -20,7 +19,54 @@ pub enum Error<'gc> {
/// An internal VM error. This cannot be caught by ActionScript code -
/// it will either be logged by Ruffle, or cause the player to
/// stop executing.
RustError(Box<dyn std::error::Error>),
RustError(RustError),
}
#[derive(Debug, thiserror::Error)]
pub struct RustError {
#[source]
source: Box<dyn std::error::Error>,
#[cfg(feature = "error_backtrace")]
backtrace: Option<std::backtrace::Backtrace>,
}
impl std::fmt::Display for RustError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.source)?;
#[cfg(feature = "error_backtrace")]
if let Some(backtrace) = &self.backtrace {
write!(f, "\n{}", backtrace)?;
}
Ok(())
}
}
impl RustError {
pub fn new(source: Box<dyn std::error::Error>) -> Self {
RustError {
source,
#[cfg(feature = "error_backtrace")]
backtrace: Some(std::backtrace::Backtrace::capture()),
}
}
}
impl From<Box<dyn std::error::Error>> for RustError {
fn from(source: Box<dyn std::error::Error>) -> Self {
RustError::new(source)
}
}
impl From<&str> for RustError {
fn from(source: &str) -> Self {
RustError::new(source.into())
}
}
impl From<String> for RustError {
fn from(source: String) -> Self {
RustError::new(source.into())
}
}
impl<'gc> Debug for Error<'gc> {
@ -37,17 +83,17 @@ impl<'gc> Debug for Error<'gc> {
match self {
Error::AvmError(error) => write!(f, "AvmError({:?})", error),
Error::RustError(error) => write!(f, "RustError({:?})", error),
Error::RustError(error) => write!(f, "RustError({})", error),
}
}
}
// This type is used very frequently, so make sure it doesn't unexpectedly grow.
#[cfg(target_family = "wasm")]
const _: () = assert!(size_of::<Result<Value<'_>, Error<'_>>>() == 24);
#[cfg(all(target_family = "wasm", not(feature = "error_backtrace")))]
const _: () = assert!(std::mem::size_of::<Result<Value<'_>, Error<'_>>>() == 24);
#[cfg(target_pointer_width = "64")]
const _: () = assert!(size_of::<Result<Value<'_>, Error<'_>>>() == 32);
#[cfg(all(target_pointer_width = "64", not(feature = "error_backtrace")))]
const _: () = assert!(std::mem::size_of::<Result<Value<'_>, Error<'_>>>() == 32);
#[inline(never)]
#[cold]
@ -813,7 +859,7 @@ impl<'gc, 'a> From<&'a str> for Error<'gc> {
impl<'gc, 'a> From<Cow<'a, str>> for Error<'gc> {
fn from(val: Cow<'a, str>) -> Error<'gc> {
Error::RustError(val.into())
Error::RustError((*val).into())
}
}
@ -825,6 +871,6 @@ impl<'gc> From<String> for Error<'gc> {
impl<'gc> From<ruffle_render::error::Error> for Error<'gc> {
fn from(val: ruffle_render::error::Error) -> Error<'gc> {
Error::RustError(val.into())
Error::RustError(RustError::new(val.into()))
}
}

View File

@ -63,6 +63,7 @@ jpegxr = ["ruffle_core/jpegxr"]
# core features
avm_debug = ["ruffle_core/avm_debug"]
error_backtrace = ["ruffle_core/error_backtrace"]
lzma = ["ruffle_core/lzma"]
software_video = ["ruffle_video_software"]
external_video = ["ruffle_video_external"]