diff options
author | Nayeem Rahman <nayeemrmn99@gmail.com> | 2022-06-06 19:26:57 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-06 20:26:57 +0200 |
commit | e3eae662f3d753141571bd132ccb199f95c745ea (patch) | |
tree | e0cdad78f409c9b221b909d0d7dfcee286e6325f /core | |
parent | 1081659be176a59512a7e9e3dc93e13046a26aec (diff) |
fix: Format non-error exceptions (#14604)
This commit adds "Deno.core.setFormatExceptionCallback" which
can be used to provide custom formatting for errors. It is useful
in cases when user throws something that is non-Error (eg.
a string, plain object, etc).
Diffstat (limited to 'core')
-rw-r--r-- | core/bindings.rs | 30 | ||||
-rw-r--r-- | core/error.rs | 38 | ||||
-rw-r--r-- | core/runtime.rs | 2 |
3 files changed, 60 insertions, 10 deletions
diff --git a/core/bindings.rs b/core/bindings.rs index 6fb0153a1..48e9a4561 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -55,6 +55,9 @@ pub static EXTERNAL_REFERENCES: Lazy<v8::ExternalReferences> = function: set_uncaught_exception_callback.map_fn_to(), }, v8::ExternalReference { + function: set_format_exception_callback.map_fn_to(), + }, + v8::ExternalReference { function: run_microtasks.map_fn_to(), }, v8::ExternalReference { @@ -214,6 +217,12 @@ pub fn initialize_context<'s>( "setUncaughtExceptionCallback", set_uncaught_exception_callback, ); + set_func( + scope, + core_val, + "setFormatExceptionCallback", + set_format_exception_callback, + ); set_func(scope, core_val, "runMicrotasks", run_microtasks); set_func(scope, core_val, "hasTickScheduled", has_tick_scheduled); set_func( @@ -646,6 +655,27 @@ fn set_uncaught_exception_callback( } } +/// Set a callback which formats exception messages as stored in +/// `JsError::exception_message`. The callback is passed the error value and +/// should return a string or `null`. If no callback is set or the callback +/// returns `null`, the built-in default formatting will be used. +fn set_format_exception_callback( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + if let Ok(new) = arg0_to_cb(scope, args) { + if let Some(old) = JsRuntime::state(scope) + .borrow_mut() + .js_format_exception_cb + .replace(new) + { + let old = v8::Local::new(scope, old); + rv.set(old.into()); + } + } +} + fn arg0_to_cb( scope: &mut v8::HandleScope, args: v8::FunctionCallbackArguments, diff --git a/core/error.rs b/core/error.rs index 2ba053802..c3bf6e861 100644 --- a/core/error.rs +++ b/core/error.rs @@ -191,6 +191,20 @@ impl JsError { let msg = v8::Exception::create_message(scope, exception); + let mut exception_message = None; + let state_rc = JsRuntime::state(scope); + let state = state_rc.borrow(); + if let Some(format_exception_cb) = &state.js_format_exception_cb { + let format_exception_cb = format_exception_cb.open(scope); + let this = v8::undefined(scope).into(); + let formatted = format_exception_cb.call(scope, this, &[exception]); + if let Some(formatted) = formatted { + if formatted.is_string() { + exception_message = Some(formatted.to_rust_string_lossy(scope)); + } + } + } + if is_instance_of_error(scope, exception) { // The exception is a JS Error object. let exception: v8::Local<v8::Object> = exception.try_into().unwrap(); @@ -200,15 +214,17 @@ impl JsError { // Get the message by formatting error.name and error.message. let name = e.name.clone().unwrap_or_else(|| "Error".to_string()); let message_prop = e.message.clone().unwrap_or_else(|| "".to_string()); - let exception_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 exception_message = exception_message.unwrap_or_else(|| { + 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 @@ -334,13 +350,15 @@ impl JsError { aggregated, } } else { + let exception_message = exception_message + .unwrap_or_else(|| msg.get(scope).to_rust_string_lossy(scope)); // The exception is not a JS Error object. // Get the message given by V8::Exception::create_message(), and provide // empty frames. Self { name: None, message: None, - exception_message: msg.get(scope).to_rust_string_lossy(scope), + exception_message, cause: None, source_line: None, source_line_frame_index: None, diff --git a/core/runtime.rs b/core/runtime.rs index 3578b216a..66d28226e 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -152,6 +152,7 @@ pub(crate) struct JsRuntimeState { pub(crate) js_nexttick_cbs: Vec<v8::Global<v8::Function>>, pub(crate) js_promise_reject_cb: Option<v8::Global<v8::Function>>, pub(crate) js_uncaught_exception_cb: Option<v8::Global<v8::Function>>, + pub(crate) js_format_exception_cb: Option<v8::Global<v8::Function>>, pub(crate) has_tick_scheduled: bool, pub(crate) js_wasm_streaming_cb: Option<v8::Global<v8::Function>>, pub(crate) pending_promise_exceptions: @@ -386,6 +387,7 @@ impl JsRuntime { js_nexttick_cbs: vec![], js_promise_reject_cb: None, js_uncaught_exception_cb: None, + js_format_exception_cb: None, has_tick_scheduled: false, js_wasm_streaming_cb: None, source_map_getter: options.source_map_getter, |