diff --git a/cli/tests/error_022_stack_custom_error.ts b/cli/tests/error_022_stack_custom_error.ts index 6f3c1b0bdf..b957435038 100644 --- a/cli/tests/error_022_stack_custom_error.ts +++ b/cli/tests/error_022_stack_custom_error.ts @@ -1,10 +1,14 @@ class CustomError extends Error { - constructor(message: string) { - super(message); + constructor() { + super(); this.name = "CustomError"; } + + get message(): string { + return "custom error"; + } } -const error = new CustomError("custom error"); +const error = new CustomError(); console.log(error.stack); throw error; diff --git a/core/js_errors.rs b/core/js_errors.rs index 06a8d188f5..8429d731b4 100644 --- a/core/js_errors.rs +++ b/core/js_errors.rs @@ -73,148 +73,170 @@ impl JSError { let msg = v8::Exception::create_message(scope, exception); - let exception: Option> = - exception.clone().try_into().ok(); - let _ = exception.map(|e| get_property(scope, context, e, "stack")); + let (message, frames, formatted_frames) = if exception.is_native_error() { + // The exception is a JS Error object. + let exception: v8::Local = + exception.clone().try_into().unwrap(); - let maybe_call_sites = exception - .and_then(|e| get_property(scope, context, e, "__callSiteEvals")); - let maybe_call_sites: Option> = - maybe_call_sites.and_then(|a| a.try_into().ok()); + // Get the message by formatting error.name and error.message. + let name = get_property(scope, context, exception, "name") + .and_then(|m| m.to_string(scope)) + .map(|s| s.to_rust_string_lossy(scope)) + .unwrap_or_else(|| "undefined".to_string()); + let message_prop = get_property(scope, context, exception, "message") + .and_then(|m| m.to_string(scope)) + .map(|s| s.to_rust_string_lossy(scope)) + .unwrap_or_else(|| "undefined".to_string()); + let message = format!("Uncaught {}: {}", name, message_prop); - let (frames, formatted_frames) = if let Some(call_sites) = maybe_call_sites - { + // Access error.stack to ensure that prepareStackTrace() has been called. + // This should populate error.__callSiteEvals and error.__formattedFrames. + let _ = get_property(scope, context, exception, "stack"); + + // Read an array of structured frames from error.__callSiteEvals. + let frames_v8 = + get_property(scope, context, exception, "__callSiteEvals"); + let frames_v8: Option> = + frames_v8.and_then(|a| a.try_into().ok()); + + // Read an array of pre-formatted frames from error.__formattedFrames. + let formatted_frames_v8 = + get_property(scope, context, exception, "__formattedFrames"); + let formatted_frames_v8: Option> = + formatted_frames_v8.and_then(|a| a.try_into().ok()); + + // Convert them into Vec and Vec respectively. let mut frames: Vec = vec![]; let mut formatted_frames: Vec = vec![]; - - let formatted_frames_v8 = - get_property(scope, context, exception.unwrap(), "__formattedFrames"); - let formatted_frames_v8: v8::Local = formatted_frames_v8 - .and_then(|a| a.try_into().ok()) - .expect("__formattedFrames should be defined if __callSiteEvals is."); - - for i in 0..call_sites.length() { - let call_site: v8::Local = call_sites - .get_index(scope, context, i) - .unwrap() - .try_into() - .unwrap(); - let type_name: Option> = - get_property(scope, context, call_site, "typeName") - .unwrap() - .try_into() - .ok(); - let type_name = type_name.map(|s| s.to_rust_string_lossy(scope)); - let function_name: Option> = - get_property(scope, context, call_site, "functionName") - .unwrap() - .try_into() - .ok(); - let function_name = - function_name.map(|s| s.to_rust_string_lossy(scope)); - let method_name: Option> = - get_property(scope, context, call_site, "methodName") - .unwrap() - .try_into() - .ok(); - let method_name = method_name.map(|s| s.to_rust_string_lossy(scope)); - let file_name: Option> = - get_property(scope, context, call_site, "fileName") - .unwrap() - .try_into() - .ok(); - let file_name = file_name.map(|s| s.to_rust_string_lossy(scope)); - let line_number: Option> = - get_property(scope, context, call_site, "lineNumber") - .unwrap() - .try_into() - .ok(); - let line_number = line_number.map(|n| n.value()); - let column_number: Option> = - get_property(scope, context, call_site, "columnNumber") - .unwrap() - .try_into() - .ok(); - let column_number = column_number.map(|n| n.value()); - let eval_origin: Option> = - get_property(scope, context, call_site, "evalOrigin") - .unwrap() - .try_into() - .ok(); - let eval_origin = eval_origin.map(|s| s.to_rust_string_lossy(scope)); - let is_top_level: Option> = - get_property(scope, context, call_site, "isTopLevel") - .unwrap() - .try_into() - .ok(); - let is_top_level = is_top_level.map(|b| b.is_true()); - let is_eval: v8::Local = - get_property(scope, context, call_site, "isEval") + if let (Some(frames_v8), Some(formatted_frames_v8)) = + (frames_v8, formatted_frames_v8) + { + for i in 0..frames_v8.length() { + let call_site: v8::Local = frames_v8 + .get_index(scope, context, i) .unwrap() .try_into() .unwrap(); - let is_eval = is_eval.is_true(); - let is_native: v8::Local = - get_property(scope, context, call_site, "isNative") + let type_name: Option> = + get_property(scope, context, call_site, "typeName") + .unwrap() + .try_into() + .ok(); + let type_name = type_name.map(|s| s.to_rust_string_lossy(scope)); + let function_name: Option> = + get_property(scope, context, call_site, "functionName") + .unwrap() + .try_into() + .ok(); + let function_name = + function_name.map(|s| s.to_rust_string_lossy(scope)); + let method_name: Option> = + get_property(scope, context, call_site, "methodName") + .unwrap() + .try_into() + .ok(); + let method_name = method_name.map(|s| s.to_rust_string_lossy(scope)); + let file_name: Option> = + get_property(scope, context, call_site, "fileName") + .unwrap() + .try_into() + .ok(); + let file_name = file_name.map(|s| s.to_rust_string_lossy(scope)); + let line_number: Option> = + get_property(scope, context, call_site, "lineNumber") + .unwrap() + .try_into() + .ok(); + let line_number = line_number.map(|n| n.value()); + let column_number: Option> = + get_property(scope, context, call_site, "columnNumber") + .unwrap() + .try_into() + .ok(); + let column_number = column_number.map(|n| n.value()); + let eval_origin: Option> = + get_property(scope, context, call_site, "evalOrigin") + .unwrap() + .try_into() + .ok(); + let eval_origin = eval_origin.map(|s| s.to_rust_string_lossy(scope)); + let is_top_level: Option> = + get_property(scope, context, call_site, "isTopLevel") + .unwrap() + .try_into() + .ok(); + let is_top_level = is_top_level.map(|b| b.is_true()); + let is_eval: v8::Local = + get_property(scope, context, call_site, "isEval") + .unwrap() + .try_into() + .unwrap(); + let is_eval = is_eval.is_true(); + let is_native: v8::Local = + get_property(scope, context, call_site, "isNative") + .unwrap() + .try_into() + .unwrap(); + let is_native = is_native.is_true(); + let is_constructor: v8::Local = + get_property(scope, context, call_site, "isConstructor") + .unwrap() + .try_into() + .unwrap(); + let is_constructor = is_constructor.is_true(); + let is_async: v8::Local = + get_property(scope, context, call_site, "isAsync") + .unwrap() + .try_into() + .unwrap(); + let is_async = is_async.is_true(); + let is_promise_all: v8::Local = + get_property(scope, context, call_site, "isPromiseAll") + .unwrap() + .try_into() + .unwrap(); + let is_promise_all = is_promise_all.is_true(); + let promise_index: Option> = + get_property(scope, context, call_site, "columnNumber") + .unwrap() + .try_into() + .ok(); + let promise_index = promise_index.map(|n| n.value()); + frames.push(JSStackFrame { + type_name, + function_name, + method_name, + file_name, + line_number, + column_number, + eval_origin, + is_top_level, + is_eval, + is_native, + is_constructor, + is_async, + is_promise_all, + promise_index, + }); + let formatted_frame: v8::Local = formatted_frames_v8 + .get_index(scope, context, i) .unwrap() .try_into() .unwrap(); - let is_native = is_native.is_true(); - let is_constructor: v8::Local = - get_property(scope, context, call_site, "isConstructor") - .unwrap() - .try_into() - .unwrap(); - let is_constructor = is_constructor.is_true(); - let is_async: v8::Local = - get_property(scope, context, call_site, "isAsync") - .unwrap() - .try_into() - .unwrap(); - let is_async = is_async.is_true(); - let is_promise_all: v8::Local = - get_property(scope, context, call_site, "isPromiseAll") - .unwrap() - .try_into() - .unwrap(); - let is_promise_all = is_promise_all.is_true(); - let promise_index: Option> = - get_property(scope, context, call_site, "columnNumber") - .unwrap() - .try_into() - .ok(); - let promise_index = promise_index.map(|n| n.value()); - frames.push(JSStackFrame { - type_name, - function_name, - method_name, - file_name, - line_number, - column_number, - eval_origin, - is_top_level, - is_eval, - is_native, - is_constructor, - is_async, - is_promise_all, - promise_index, - }); - let formatted_frame: v8::Local = formatted_frames_v8 - .get_index(scope, context, i) - .unwrap() - .try_into() - .unwrap(); - let formatted_frame = formatted_frame.to_rust_string_lossy(scope); - formatted_frames.push(formatted_frame) + let formatted_frame = formatted_frame.to_rust_string_lossy(scope); + formatted_frames.push(formatted_frame) + } } - (frames, formatted_frames) + (message, frames, formatted_frames) } else { - (vec![], 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![], vec![]) }; Self { - message: msg.get(scope).to_rust_string_lossy(scope), + message, script_resource_name: msg .get_script_resource_name(scope) .and_then(|v| v8::Local::::try_from(v).ok())