diff options
Diffstat (limited to 'core/error.rs')
-rw-r--r-- | core/error.rs | 113 |
1 files changed, 71 insertions, 42 deletions
diff --git a/core/error.rs b/core/error.rs index 332bc5c51..dd8d95d45 100644 --- a/core/error.rs +++ b/core/error.rs @@ -2,6 +2,7 @@ use anyhow::Error; use std::borrow::Cow; +use std::collections::HashSet; use std::fmt; use std::fmt::Debug; use std::fmt::Display; @@ -92,6 +93,7 @@ pub fn get_custom_error_class(error: &Error) -> Option<&'static str> { #[derive(Debug, PartialEq, Clone)] pub struct JsError { pub message: String, + pub cause: Option<Box<JsError>>, pub source_line: Option<String>, pub script_resource_name: Option<String>, pub line_number: Option<i64>, @@ -174,59 +176,86 @@ impl JsError { scope: &mut v8::HandleScope, exception: v8::Local<v8::Value>, ) -> Self { + Self::inner_from_v8_exception(scope, exception, Default::default()) + } + + fn inner_from_v8_exception<'a>( + scope: &'a mut v8::HandleScope, + exception: v8::Local<'a, v8::Value>, + mut seen: HashSet<v8::Local<'a, v8::Value>>, + ) -> Self { // Create a new HandleScope because we're creating a lot of new local // handles below. let scope = &mut v8::HandleScope::new(scope); let msg = v8::Exception::create_message(scope, exception); - let (message, frames, stack) = if is_instance_of_error(scope, exception) { - // The exception is a JS Error object. - let exception: v8::Local<v8::Object> = exception.try_into().unwrap(); - - let e: NativeJsError = - serde_v8::from_v8(scope, exception.into()).unwrap(); - // Get the message by formatting error.name and error.message. - let name = e.name.unwrap_or_else(|| "Error".to_string()); - let message_prop = e.message.unwrap_or_else(|| "".to_string()); - let message = if !name.is_empty() && !message_prop.is_empty() { - format!("Uncaught {}: {}", name, message_prop) - } else if !name.is_empty() { - format!("Uncaught {}", name) - } else if !message_prop.is_empty() { - format!("Uncaught {}", message_prop) + let (message, frames, stack, cause) = + if is_instance_of_error(scope, exception) { + // The exception is a JS Error object. + let exception: v8::Local<v8::Object> = exception.try_into().unwrap(); + let cause = get_property(scope, exception, "cause"); + let e: NativeJsError = + serde_v8::from_v8(scope, exception.into()).unwrap(); + // Get the message by formatting error.name and error.message. + let name = e.name.unwrap_or_else(|| "Error".to_string()); + let message_prop = e.message.unwrap_or_else(|| "".to_string()); + let message = if !name.is_empty() && !message_prop.is_empty() { + format!("Uncaught {}: {}", name, message_prop) + } else if !name.is_empty() { + format!("Uncaught {}", name) + } else if !message_prop.is_empty() { + format!("Uncaught {}", message_prop) + } else { + "Uncaught".to_string() + }; + let cause = cause.and_then(|cause| { + if cause.is_undefined() || seen.contains(&cause) { + None + } else { + seen.insert(cause); + Some(Box::new(JsError::inner_from_v8_exception( + scope, cause, seen, + ))) + } + }); + + // Access error.stack to ensure that prepareStackTrace() has been called. + // This should populate error.__callSiteEvals. + let stack = get_property(scope, exception, "stack"); + let stack: Option<v8::Local<v8::String>> = + stack.and_then(|s| s.try_into().ok()); + let stack = stack.map(|s| s.to_rust_string_lossy(scope)); + + // Read an array of structured frames from error.__callSiteEvals. + let frames_v8 = get_property(scope, exception, "__callSiteEvals"); + // Ignore non-array values + let frames_v8: Option<v8::Local<v8::Array>> = + frames_v8.and_then(|a| a.try_into().ok()); + + // Convert them into Vec<JsStackFrame> + let frames: Vec<JsStackFrame> = match frames_v8 { + Some(frames_v8) => { + serde_v8::from_v8(scope, frames_v8.into()).unwrap() + } + None => vec![], + }; + (message, frames, stack, cause) } else { - "Uncaught".to_string() - }; - - // Access error.stack to ensure that prepareStackTrace() has been called. - // This should populate error.__callSiteEvals. - let stack = get_property(scope, exception, "stack"); - let stack: Option<v8::Local<v8::String>> = - stack.and_then(|s| s.try_into().ok()); - let stack = stack.map(|s| s.to_rust_string_lossy(scope)); - - // Read an array of structured frames from error.__callSiteEvals. - let frames_v8 = get_property(scope, exception, "__callSiteEvals"); - // Ignore non-array values - let frames_v8: Option<v8::Local<v8::Array>> = - frames_v8.and_then(|a| a.try_into().ok()); - - // Convert them into Vec<JsStackFrame> - let frames: Vec<JsStackFrame> = match frames_v8 { - Some(frames_v8) => serde_v8::from_v8(scope, frames_v8.into()).unwrap(), - None => vec![], + // The exception is not a JS Error object. + // Get the message given by V8::Exception::create_message(), and provide + // empty frames. + ( + msg.get(scope).to_rust_string_lossy(scope), + vec![], + None, + None, + ) }; - (message, frames, stack) - } else { - // The exception is not a JS Error object. - // Get the message given by V8::Exception::create_message(), and provide - // empty frames. - (msg.get(scope).to_rust_string_lossy(scope), vec![], None) - }; Self { message, + cause, script_resource_name: msg .get_script_resource_name(scope) .and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) |