summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2022-06-06 19:26:57 +0100
committerGitHub <noreply@github.com>2022-06-06 20:26:57 +0200
commite3eae662f3d753141571bd132ccb199f95c745ea (patch)
treee0cdad78f409c9b221b909d0d7dfcee286e6325f /core
parent1081659be176a59512a7e9e3dc93e13046a26aec (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.rs30
-rw-r--r--core/error.rs38
-rw-r--r--core/runtime.rs2
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,