diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/bindings.rs | 31 | ||||
-rw-r--r-- | core/error.rs | 160 | ||||
-rw-r--r-- | core/runtime.rs | 49 |
3 files changed, 149 insertions, 91 deletions
diff --git a/core/bindings.rs b/core/bindings.rs index 5e37232a1..46dcefe38 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -1,6 +1,7 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. use crate::error::is_instance_of_error; +use crate::error::JsError; use crate::modules::get_module_type_from_assertions; use crate::modules::parse_import_assertions; use crate::modules::validate_import_assertions; @@ -102,6 +103,12 @@ pub static EXTERNAL_REFERENCES: Lazy<v8::ExternalReferences> = v8::ExternalReference { function: abort_wasm_streaming.map_fn_to(), }, + v8::ExternalReference { + function: destructure_error.map_fn_to(), + }, + v8::ExternalReference { + function: terminate.map_fn_to(), + }, ]) }); @@ -228,6 +235,8 @@ pub fn initialize_context<'s>( set_wasm_streaming_callback, ); set_func(scope, core_val, "abortWasmStreaming", abort_wasm_streaming); + set_func(scope, core_val, "destructureError", destructure_error); + set_func(scope, core_val, "terminate", terminate); // Direct bindings on `window`. set_func(scope, global, "queueMicrotask", queue_microtask); @@ -1293,6 +1302,28 @@ fn queue_microtask( }; } +fn destructure_error( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, +) { + let js_error = JsError::from_v8_exception(scope, args.get(0)); + let object = serde_v8::to_v8(scope, js_error).unwrap(); + rv.set(object); +} + +fn terminate( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, +) { + let state_rc = JsRuntime::state(scope); + let mut state = state_rc.borrow_mut(); + state.explicit_terminate_exception = + Some(v8::Global::new(scope, args.get(0))); + scope.terminate_execution(); +} + fn create_host_object( scope: &mut v8::HandleScope, _args: v8::FunctionCallbackArguments, diff --git a/core/error.rs b/core/error.rs index 106845f04..1fc4a1af7 100644 --- a/core/error.rs +++ b/core/error.rs @@ -90,7 +90,8 @@ pub fn get_custom_error_class(error: &Error) -> Option<&'static str> { /// 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. -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] pub struct JsError { pub message: String, pub cause: Option<Box<JsError>>, @@ -103,7 +104,7 @@ pub struct JsError { pub stack: Option<String>, } -#[derive(Debug, PartialEq, Clone, serde::Deserialize)] +#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct JsStackFrame { pub type_name: Option<String>, @@ -190,84 +191,84 @@ impl JsError { let msg = v8::Exception::create_message(scope, exception); - 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) + 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 { - // 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, - ) + "Uncaught".to_string() }; - - Self { - message, - cause, - script_resource_name: msg - .get_script_resource_name(scope) - .and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) - .map(|v| v.to_rust_string_lossy(scope)), - source_line: msg - .get_source_line(scope) - .map(|v| v.to_rust_string_lossy(scope)), - line_number: msg.get_line_number(scope).and_then(|v| v.try_into().ok()), - start_column: msg.get_start_column().try_into().ok(), - end_column: msg.get_end_column().try_into().ok(), - frames, - stack, + 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![], + }; + Self { + message, + cause, + script_resource_name: msg + .get_script_resource_name(scope) + .and_then(|v| v8::Local::<v8::String>::try_from(v).ok()) + .map(|v| v.to_rust_string_lossy(scope)), + source_line: msg + .get_source_line(scope) + .map(|v| v.to_rust_string_lossy(scope)), + line_number: msg.get_line_number(scope).and_then(|v| v.try_into().ok()), + start_column: msg.get_start_column().try_into().ok(), + end_column: msg.get_end_column().try_into().ok(), + frames, + stack, + } + } else { + // The exception is not a JS Error object. + // Get the message given by V8::Exception::create_message(), and provide + // empty frames. + Self { + message: msg.get(scope).to_rust_string_lossy(scope), + cause: None, + script_resource_name: None, + source_line: None, + line_number: None, + start_column: None, + end_column: None, + frames: vec![], + stack: None, + } } } } @@ -337,6 +338,9 @@ pub(crate) fn is_instance_of_error<'s>( let mut maybe_prototype = value.to_object(scope).unwrap().get_prototype(scope); while let Some(prototype) = maybe_prototype { + if !prototype.is_object() { + return false; + } if prototype.strict_equals(error_prototype) { return true; } diff --git a/core/runtime.rs b/core/runtime.rs index 50f7399d4..7f7b6ead1 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -169,6 +169,10 @@ pub(crate) struct JsRuntimeState { pub(crate) op_ctxs: Box<[OpCtx]>, pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>, pub(crate) compiled_wasm_module_store: Option<CompiledWasmModuleStore>, + /// The error that was passed to an explicit `Deno.core.terminate` call. + /// It will be retrieved by `exception_to_err_result` and used as an error + /// instead of any other exceptions. + pub(crate) explicit_terminate_exception: Option<v8::Global<v8::Value>>, waker: AtomicWaker, } @@ -384,6 +388,7 @@ impl JsRuntime { op_state: op_state.clone(), op_ctxs, have_unpolled_ops: false, + explicit_terminate_exception: None, waker: AtomicWaker::new(), }))); @@ -1017,19 +1022,33 @@ pub(crate) fn exception_to_err_result<'s, T>( exception: v8::Local<v8::Value>, in_promise: bool, ) -> Result<T, Error> { + let state_rc = JsRuntime::state(scope); + let mut state = state_rc.borrow_mut(); + let is_terminating_exception = scope.is_execution_terminating(); let mut exception = exception; if is_terminating_exception { - // TerminateExecution was called. Cancel exception termination so that the + // TerminateExecution was called. Cancel isolate termination so that the // exception can be created.. scope.cancel_terminate_execution(); - // Maybe make a new exception object. - if exception.is_null_or_undefined() { - let message = v8::String::new(scope, "execution terminated").unwrap(); - exception = v8::Exception::error(scope, message); - } + // If the termination is the result of a `Deno.core.terminate` call, we want + // to use the exception that was passed to it rather than the exception that + // was passed to this function. + exception = state + .explicit_terminate_exception + .take() + .map(|exception| v8::Local::new(scope, exception)) + .unwrap_or_else(|| { + // Maybe make a new exception object. + if exception.is_null_or_undefined() { + let message = v8::String::new(scope, "execution terminated").unwrap(); + v8::Exception::error(scope, message) + } else { + exception + } + }); } let mut js_error = JsError::from_v8_exception(scope, exception); @@ -1039,9 +1058,6 @@ pub(crate) fn exception_to_err_result<'s, T>( js_error.message.trim_start_matches("Uncaught ") ); } - - let state_rc = JsRuntime::state(scope); - let state = state_rc.borrow(); let js_error = (state.js_error_create_fn)(js_error); if is_terminating_exception { @@ -1222,7 +1238,14 @@ impl JsRuntime { // Update status after evaluating. status = module.get_status(); - if let Some(value) = maybe_value { + let explicit_terminate_exception = + state_rc.borrow_mut().explicit_terminate_exception.take(); + if let Some(exception) = explicit_terminate_exception { + let exception = v8::Local::new(tc_scope, exception); + sender + .send(exception_to_err_result(tc_scope, exception, false)) + .expect("Failed to send module evaluation error."); + } else if let Some(value) = maybe_value { assert!( status == v8::ModuleStatus::Evaluated || status == v8::ModuleStatus::Errored @@ -3093,8 +3116,8 @@ assertEquals(1, notify_return_value); const a1b = a1.subarray(0, 3); const a2 = new Uint8Array([5,10,15]); const a2b = a2.subarray(0, 3); - - + + if (!(a1.length > 0 && a1b.length > 0)) { throw new Error("a1 & a1b should have a length"); } @@ -3116,7 +3139,7 @@ assertEquals(1, notify_return_value); if (a2.byteLength > 0 || a2b.byteLength > 0) { throw new Error("expecting a2 & a2b to be detached, a3 re-attached"); } - + const wmem = new WebAssembly.Memory({ initial: 1, maximum: 2 }); const w32 = new Uint32Array(wmem.buffer); w32[0] = 1; w32[1] = 2; w32[2] = 3; |