summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/bindings.rs31
-rw-r--r--core/error.rs160
-rw-r--r--core/runtime.rs49
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;