diff --git a/core/01_core.js b/core/01_core.js index 0e823f54a8..1dfb88c99c 100644 --- a/core/01_core.js +++ b/core/01_core.js @@ -127,6 +127,18 @@ errorMap[className] = errorBuilder; } + function buildCustomError(className, message, code) { + const error = errorMap[className]?.(message); + // Strip buildCustomError() calls from stack trace + if (typeof error == "object") { + ErrorCaptureStackTrace(error, buildCustomError); + if (code) { + error.code = code; + } + } + return error; + } + function unwrapOpResult(res) { // .$err_class_name is a special key that should only exist on errors if (res?.$err_class_name) { @@ -168,7 +180,7 @@ } function opSync(opName, ...args) { - return unwrapOpResult(ops[opName](...args)); + return ops[opName](...args); } function refOp(promiseId) { @@ -229,6 +241,7 @@ metrics, registerErrorBuilder, registerErrorClass, + buildCustomError, opresolve, BadResource, BadResourcePrototype, diff --git a/core/error.rs b/core/error.rs index 664cb5c0be..bc8a3195ba 100644 --- a/core/error.rs +++ b/core/error.rs @@ -1,5 +1,7 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use crate::runtime::GetErrorClassFn; +use crate::runtime::JsRealm; use crate::runtime::JsRuntime; use crate::source_map::apply_source_map; use crate::source_map::get_source_line; @@ -91,6 +93,30 @@ pub fn get_custom_error_class(error: &Error) -> Option<&'static str> { error.downcast_ref::().map(|e| e.class) } +pub fn to_v8_error<'a>( + scope: &mut v8::HandleScope<'a>, + get_class: GetErrorClassFn, + error: &Error, +) -> v8::Local<'a, v8::Value> { + let cb = JsRealm::state_from_scope(scope) + .borrow() + .js_build_custom_error_cb + .clone() + .expect("Custom error builder must be set"); + let cb = cb.open(scope); + let this = v8::undefined(scope).into(); + let class = v8::String::new(scope, get_class(error)).unwrap(); + let message = v8::String::new(scope, &error.to_string()).unwrap(); + let mut args = vec![class.into(), message.into()]; + if let Some(code) = crate::error_codes::get_error_code(error) { + args.push(v8::String::new(scope, code).unwrap().into()); + } + let exception = cb + .call(scope, this, &args) + .expect("Custom error class must have a builder registered"); + exception +} + /// A `JsError` represents an exception coming from V8, with stack frames and /// line numbers. The deno_cli crate defines another `JsError` type, which wraps /// the one defined here, that adds source map support and colorful formatting. diff --git a/core/ops_builtin_v8.rs b/core/ops_builtin_v8.rs index f2d28346e4..b828f908d6 100644 --- a/core/ops_builtin_v8.rs +++ b/core/ops_builtin_v8.rs @@ -473,7 +473,8 @@ fn op_serialize( value_serializer.write_value(scope.get_current_context(), value.v8_value); if scope.has_caught() || scope.has_terminated() { scope.rethrow(); - Err(type_error("unreachable")) + // Dummy value, this result will be discarded because an error was thrown. + Ok(ZeroCopyBuf::empty()) } else if let Some(true) = ret { let vector = value_serializer.release(); Ok(vector.into()) diff --git a/core/runtime.rs b/core/runtime.rs index b13ed0b7ea..3f3caac5e5 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -150,6 +150,7 @@ pub type CompiledWasmModuleStore = CrossIsolateStore; #[derive(Default)] pub(crate) struct ContextState { js_recv_cb: Option>, + pub(crate) js_build_custom_error_cb: Option>, // TODO(andreubotella): Move the rest of Option> fields from // JsRuntimeState to this struct. pub(crate) unrefed_ops: HashSet, @@ -653,16 +654,25 @@ impl JsRuntime { /// Grabs a reference to core.js' opresolve & syncOpsCache() fn init_cbs(&mut self, realm: &JsRealm) { - let recv_cb = { + let (recv_cb, build_custom_error_cb) = { let scope = &mut realm.handle_scope(self.v8_isolate()); let recv_cb = Self::grab_global::(scope, "Deno.core.opresolve") .expect("Deno.core.opresolve is undefined in the realm"); - v8::Global::new(scope, recv_cb) + let recv_cb = v8::Global::new(scope, recv_cb); + let build_custom_error_cb = + Self::grab_global::(scope, "Deno.core.buildCustomError") + .expect("Deno.core.buildCustomError is undefined in the realm"); + let build_custom_error_cb = v8::Global::new(scope, build_custom_error_cb); + (recv_cb, build_custom_error_cb) }; // Put global handle in callback state let state = realm.state(self.v8_isolate()); state.borrow_mut().js_recv_cb.replace(recv_cb); + state + .borrow_mut() + .js_build_custom_error_cb + .replace(build_custom_error_cb); } /// Returns the runtime's op state, which can be used to maintain ops @@ -737,6 +747,9 @@ impl JsRuntime { let realm = JsRealm::new(context.clone()); let realm_state = realm.state(self.v8_isolate()); std::mem::take(&mut realm_state.borrow_mut().js_recv_cb); + std::mem::take( + &mut realm_state.borrow_mut().js_build_custom_error_cb, + ); context .open(self.v8_isolate()) .clear_all_slots(self.v8_isolate()); @@ -3424,7 +3437,7 @@ assertEquals(1, notify_return_value); Deno.core.opSync("op_set_promise_reject_callback", (type, promise, reason) => { Deno.core.opSync("op_promise_reject"); }); - + throw new Error('top level throw'); "#; diff --git a/ops/lib.rs b/ops/lib.rs index fdafca1659..42913160b4 100644 --- a/ops/lib.rs +++ b/ops/lib.rs @@ -399,8 +399,8 @@ fn codegen_sync_ret( #ok_block }, Err(err) => { - let err = #core::OpError::new(op_state.get_error_class_fn, err); - rv.set(#core::serde_v8::to_v8(scope, err).unwrap()); + let exception = #core::error::to_v8_error(scope, op_state.get_error_class_fn, &err); + scope.throw_exception(exception); }, }; }