mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
fix(repl): don't terminate on unhandled error events (#15548)
This commit is contained in:
parent
658d2cdff2
commit
a74b2ecf37
8 changed files with 76 additions and 44 deletions
|
@ -844,3 +844,20 @@ fn pty_tab_handler() {
|
||||||
assert_not_contains!(output, "atob");
|
assert_not_contains!(output, "atob");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn repl_report_error() {
|
||||||
|
let (out, err) = util::run_and_collect_output(
|
||||||
|
true,
|
||||||
|
"repl",
|
||||||
|
Some(vec![
|
||||||
|
r#"console.log(1); reportError(new Error("foo")); console.log(2);"#,
|
||||||
|
]),
|
||||||
|
Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO(nayeemrmn): The REPL should report event errors and rejections.
|
||||||
|
assert_contains!(out, "1\n2\nundefined\n");
|
||||||
|
assert!(err.is_empty());
|
||||||
|
}
|
||||||
|
|
|
@ -314,7 +314,6 @@
|
||||||
error,
|
error,
|
||||||
) => ops.op_abort_wasm_streaming(rid, error),
|
) => ops.op_abort_wasm_streaming(rid, error),
|
||||||
destructureError: (error) => ops.op_destructure_error(error),
|
destructureError: (error) => ops.op_destructure_error(error),
|
||||||
terminate: (exception) => ops.op_terminate(exception),
|
|
||||||
opNames: () => ops.op_op_names(),
|
opNames: () => ops.op_op_names(),
|
||||||
eventLoopHasMoreWork: () => ops.op_event_loop_has_more_work(),
|
eventLoopHasMoreWork: () => ops.op_event_loop_has_more_work(),
|
||||||
setPromiseRejectCallback: (fn) => ops.op_set_promise_reject_callback(fn),
|
setPromiseRejectCallback: (fn) => ops.op_set_promise_reject_callback(fn),
|
||||||
|
|
|
@ -150,7 +150,7 @@ impl JsRuntimeInspector {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
isolate: &mut v8::OwnedIsolate,
|
isolate: &mut v8::OwnedIsolate,
|
||||||
context: v8::Global<v8::Context>,
|
context: v8::Global<v8::Context>,
|
||||||
) -> Box<Self> {
|
) -> Rc<RefCell<Self>> {
|
||||||
let scope = &mut v8::HandleScope::new(isolate);
|
let scope = &mut v8::HandleScope::new(isolate);
|
||||||
|
|
||||||
let (new_session_tx, new_session_rx) =
|
let (new_session_tx, new_session_rx) =
|
||||||
|
@ -162,7 +162,7 @@ impl JsRuntimeInspector {
|
||||||
let waker = InspectorWaker::new(scope.thread_safe_handle());
|
let waker = InspectorWaker::new(scope.thread_safe_handle());
|
||||||
|
|
||||||
// Create JsRuntimeInspector instance.
|
// Create JsRuntimeInspector instance.
|
||||||
let mut self_ = Box::new(Self {
|
let self__ = Rc::new(RefCell::new(Self {
|
||||||
v8_inspector_client,
|
v8_inspector_client,
|
||||||
v8_inspector: Default::default(),
|
v8_inspector: Default::default(),
|
||||||
sessions: RefCell::new(SessionContainer::temporary_placeholder()),
|
sessions: RefCell::new(SessionContainer::temporary_placeholder()),
|
||||||
|
@ -170,7 +170,8 @@ impl JsRuntimeInspector {
|
||||||
flags: Default::default(),
|
flags: Default::default(),
|
||||||
waker,
|
waker,
|
||||||
deregister_tx: None,
|
deregister_tx: None,
|
||||||
});
|
}));
|
||||||
|
let mut self_ = self__.borrow_mut();
|
||||||
self_.v8_inspector = Rc::new(RefCell::new(
|
self_.v8_inspector = Rc::new(RefCell::new(
|
||||||
v8::inspector::V8Inspector::create(scope, &mut *self_).into(),
|
v8::inspector::V8Inspector::create(scope, &mut *self_).into(),
|
||||||
));
|
));
|
||||||
|
@ -192,8 +193,9 @@ impl JsRuntimeInspector {
|
||||||
// Poll the session handler so we will get notified whenever there is
|
// Poll the session handler so we will get notified whenever there is
|
||||||
// new incoming debugger activity.
|
// new incoming debugger activity.
|
||||||
let _ = self_.poll_sessions(None).unwrap();
|
let _ = self_.poll_sessions(None).unwrap();
|
||||||
|
drop(self_);
|
||||||
|
|
||||||
self_
|
self__
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_active_sessions(&self) -> bool {
|
pub fn has_active_sessions(&self) -> bool {
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub(crate) fn init_builtins_v8() -> Vec<OpDecl> {
|
||||||
op_set_wasm_streaming_callback::decl(),
|
op_set_wasm_streaming_callback::decl(),
|
||||||
op_abort_wasm_streaming::decl(),
|
op_abort_wasm_streaming::decl(),
|
||||||
op_destructure_error::decl(),
|
op_destructure_error::decl(),
|
||||||
op_terminate::decl(),
|
op_dispatch_exception::decl(),
|
||||||
op_op_names::decl(),
|
op_op_names::decl(),
|
||||||
op_apply_source_map::decl(),
|
op_apply_source_map::decl(),
|
||||||
op_set_format_exception_callback::decl(),
|
op_set_format_exception_callback::decl(),
|
||||||
|
@ -718,13 +718,27 @@ fn op_destructure_error(
|
||||||
JsError::from_v8_exception(scope, error.v8_value)
|
JsError::from_v8_exception(scope, error.v8_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Effectively throw an uncatchable error. This will terminate runtime
|
||||||
|
/// execution before any more JS code can run, except in the REPL where it
|
||||||
|
/// should just output the error to the console.
|
||||||
#[op(v8)]
|
#[op(v8)]
|
||||||
fn op_terminate(scope: &mut v8::HandleScope, exception: serde_v8::Value) {
|
fn op_dispatch_exception(
|
||||||
|
scope: &mut v8::HandleScope,
|
||||||
|
exception: serde_v8::Value,
|
||||||
|
) {
|
||||||
let state_rc = JsRuntime::state(scope);
|
let state_rc = JsRuntime::state(scope);
|
||||||
let mut state = state_rc.borrow_mut();
|
let mut state = state_rc.borrow_mut();
|
||||||
state.explicit_terminate_exception =
|
state
|
||||||
Some(v8::Global::new(scope, exception.v8_value));
|
.dispatched_exceptions
|
||||||
scope.terminate_execution();
|
.push_front(v8::Global::new(scope, exception.v8_value));
|
||||||
|
// Only terminate execution if there are no inspector sessions.
|
||||||
|
match state.inspector().try_borrow() {
|
||||||
|
Ok(inspector) if !inspector.has_active_sessions() => {
|
||||||
|
scope.terminate_execution();
|
||||||
|
}
|
||||||
|
// If the inspector is borrowed at this time, assume an inspector is active.
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op(v8)]
|
#[op(v8)]
|
||||||
|
|
|
@ -36,6 +36,7 @@ use std::any::Any;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::mem::forget;
|
use std::mem::forget;
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
@ -80,9 +81,6 @@ pub struct JsRuntime {
|
||||||
// This is an Option<OwnedIsolate> instead of just OwnedIsolate to workaround
|
// This is an Option<OwnedIsolate> instead of just OwnedIsolate to workaround
|
||||||
// a safety issue with SnapshotCreator. See JsRuntime::drop.
|
// a safety issue with SnapshotCreator. See JsRuntime::drop.
|
||||||
v8_isolate: Option<v8::OwnedIsolate>,
|
v8_isolate: Option<v8::OwnedIsolate>,
|
||||||
// This is an Option<Box<JsRuntimeInspector> instead of just Box<JsRuntimeInspector>
|
|
||||||
// to workaround a safety issue. See JsRuntime::drop.
|
|
||||||
inspector: Option<Box<JsRuntimeInspector>>,
|
|
||||||
snapshot_creator: Option<v8::SnapshotCreator>,
|
snapshot_creator: Option<v8::SnapshotCreator>,
|
||||||
has_snapshotted: bool,
|
has_snapshotted: bool,
|
||||||
built_from_snapshot: bool,
|
built_from_snapshot: bool,
|
||||||
|
@ -182,19 +180,18 @@ pub(crate) struct JsRuntimeState {
|
||||||
pub(crate) op_ctxs: Box<[OpCtx]>,
|
pub(crate) op_ctxs: Box<[OpCtx]>,
|
||||||
pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>,
|
pub(crate) shared_array_buffer_store: Option<SharedArrayBufferStore>,
|
||||||
pub(crate) compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
|
pub(crate) compiled_wasm_module_store: Option<CompiledWasmModuleStore>,
|
||||||
/// The error that was passed to an explicit `Deno.core.terminate` call.
|
/// The error that was passed to an `op_dispatch_exception` call.
|
||||||
/// It will be retrieved by `exception_to_err_result` and used as an error
|
/// It will be retrieved by `exception_to_err_result` and used as an error
|
||||||
/// instead of any other exceptions.
|
/// instead of any other exceptions.
|
||||||
pub(crate) explicit_terminate_exception: Option<v8::Global<v8::Value>>,
|
// TODO(nayeemrmn): This is polled in `exception_to_err_result()` which is
|
||||||
|
// flimsy. Try to poll it similarly to `pending_promise_exceptions`.
|
||||||
|
pub(crate) dispatched_exceptions: VecDeque<v8::Global<v8::Value>>,
|
||||||
|
inspector: Option<Rc<RefCell<JsRuntimeInspector>>>,
|
||||||
waker: AtomicWaker,
|
waker: AtomicWaker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for JsRuntime {
|
impl Drop for JsRuntime {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// The Isolate object must outlive the Inspector object, but this is
|
|
||||||
// currently not enforced by the type system.
|
|
||||||
self.inspector.take();
|
|
||||||
|
|
||||||
if let Some(creator) = self.snapshot_creator.take() {
|
if let Some(creator) = self.snapshot_creator.take() {
|
||||||
// TODO(ry): in rusty_v8, `SnapShotCreator::get_owned_isolate()` returns
|
// TODO(ry): in rusty_v8, `SnapShotCreator::get_owned_isolate()` returns
|
||||||
// a `struct OwnedIsolate` which is not actually owned, hence the need
|
// a `struct OwnedIsolate` which is not actually owned, hence the need
|
||||||
|
@ -421,7 +418,8 @@ impl JsRuntime {
|
||||||
op_state: op_state.clone(),
|
op_state: op_state.clone(),
|
||||||
op_ctxs,
|
op_ctxs,
|
||||||
have_unpolled_ops: false,
|
have_unpolled_ops: false,
|
||||||
explicit_terminate_exception: None,
|
dispatched_exceptions: Default::default(),
|
||||||
|
inspector: Some(inspector),
|
||||||
waker: AtomicWaker::new(),
|
waker: AtomicWaker::new(),
|
||||||
})));
|
})));
|
||||||
|
|
||||||
|
@ -434,7 +432,6 @@ impl JsRuntime {
|
||||||
|
|
||||||
let mut js_runtime = Self {
|
let mut js_runtime = Self {
|
||||||
v8_isolate: Some(isolate),
|
v8_isolate: Some(isolate),
|
||||||
inspector: Some(inspector),
|
|
||||||
snapshot_creator: maybe_snapshot_creator,
|
snapshot_creator: maybe_snapshot_creator,
|
||||||
has_snapshotted: false,
|
has_snapshotted: false,
|
||||||
built_from_snapshot: has_startup_snapshot,
|
built_from_snapshot: has_startup_snapshot,
|
||||||
|
@ -466,8 +463,8 @@ impl JsRuntime {
|
||||||
self.v8_isolate.as_mut().unwrap()
|
self.v8_isolate.as_mut().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inspector(&mut self) -> &mut Box<JsRuntimeInspector> {
|
pub fn inspector(&mut self) -> Rc<RefCell<JsRuntimeInspector>> {
|
||||||
self.inspector.as_mut().unwrap()
|
Self::state(self.v8_isolate()).borrow().inspector()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn global_realm(&mut self) -> JsRealm {
|
pub fn global_realm(&mut self) -> JsRealm {
|
||||||
|
@ -740,7 +737,7 @@ impl JsRuntime {
|
||||||
|
|
||||||
state.borrow_mut().global_realm.take();
|
state.borrow_mut().global_realm.take();
|
||||||
|
|
||||||
self.inspector.take();
|
state.borrow_mut().inspector.take();
|
||||||
|
|
||||||
// Overwrite existing ModuleMap to drop v8::Global handles
|
// Overwrite existing ModuleMap to drop v8::Global handles
|
||||||
self
|
self
|
||||||
|
@ -941,8 +938,7 @@ impl JsRuntime {
|
||||||
wait_for_inspector: bool,
|
wait_for_inspector: bool,
|
||||||
) -> Poll<Result<(), Error>> {
|
) -> Poll<Result<(), Error>> {
|
||||||
// We always poll the inspector first
|
// We always poll the inspector first
|
||||||
let _ = self.inspector().poll_unpin(cx);
|
let _ = self.inspector().borrow_mut().poll_unpin(cx);
|
||||||
|
|
||||||
let state_rc = Self::state(self.v8_isolate());
|
let state_rc = Self::state(self.v8_isolate());
|
||||||
let module_map_rc = Self::module_map(self.v8_isolate());
|
let module_map_rc = Self::module_map(self.v8_isolate());
|
||||||
{
|
{
|
||||||
|
@ -1003,11 +999,8 @@ impl JsRuntime {
|
||||||
self.evaluate_pending_module();
|
self.evaluate_pending_module();
|
||||||
|
|
||||||
let pending_state = Self::event_loop_pending_state(self.v8_isolate());
|
let pending_state = Self::event_loop_pending_state(self.v8_isolate());
|
||||||
let inspector_has_active_sessions = self
|
let inspector_has_active_sessions =
|
||||||
.inspector
|
self.inspector().borrow_mut().has_active_sessions();
|
||||||
.as_ref()
|
|
||||||
.map(|i| i.has_active_sessions())
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
if !pending_state.is_pending() && !maybe_scheduling {
|
if !pending_state.is_pending() && !maybe_scheduling {
|
||||||
if wait_for_inspector && inspector_has_active_sessions {
|
if wait_for_inspector && inspector_has_active_sessions {
|
||||||
|
@ -1144,6 +1137,10 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsRuntimeState {
|
impl JsRuntimeState {
|
||||||
|
pub(crate) fn inspector(&self) -> Rc<RefCell<JsRuntimeInspector>> {
|
||||||
|
self.inspector.as_ref().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Called by `bindings::host_import_module_dynamically_callback`
|
/// Called by `bindings::host_import_module_dynamically_callback`
|
||||||
/// after initiating new dynamic import load.
|
/// after initiating new dynamic import load.
|
||||||
pub fn notify_new_dynamic_import(&mut self) {
|
pub fn notify_new_dynamic_import(&mut self) {
|
||||||
|
@ -1167,17 +1164,17 @@ pub(crate) fn exception_to_err_result<'s, T>(
|
||||||
scope.cancel_terminate_execution();
|
scope.cancel_terminate_execution();
|
||||||
let mut exception = exception;
|
let mut exception = exception;
|
||||||
{
|
{
|
||||||
// If the termination is the result of a `Deno.core.terminate` call, we want
|
// If termination is the result of a `op_dispatch_exception` call, we want
|
||||||
// to use the exception that was passed to it rather than the exception that
|
// to use the exception that was passed to it rather than the exception that
|
||||||
// was passed to this function.
|
// was passed to this function.
|
||||||
let mut state = state_rc.borrow_mut();
|
let mut state = state_rc.borrow_mut();
|
||||||
exception = state
|
exception = state
|
||||||
.explicit_terminate_exception
|
.dispatched_exceptions
|
||||||
.take()
|
.pop_back()
|
||||||
.map(|exception| v8::Local::new(scope, exception))
|
.map(|exception| v8::Local::new(scope, exception))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
// Maybe make a new exception object.
|
// Maybe make a new exception object.
|
||||||
if exception.is_null_or_undefined() {
|
if was_terminating_execution && exception.is_null_or_undefined() {
|
||||||
let message = v8::String::new(scope, "execution terminated").unwrap();
|
let message = v8::String::new(scope, "execution terminated").unwrap();
|
||||||
v8::Exception::error(scope, message)
|
v8::Exception::error(scope, message)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1385,10 +1382,11 @@ impl JsRuntime {
|
||||||
// Update status after evaluating.
|
// Update status after evaluating.
|
||||||
status = module.get_status();
|
status = module.get_status();
|
||||||
|
|
||||||
let explicit_terminate_exception =
|
let has_dispatched_exception =
|
||||||
state_rc.borrow_mut().explicit_terminate_exception.take();
|
!state_rc.borrow_mut().dispatched_exceptions.is_empty();
|
||||||
if let Some(exception) = explicit_terminate_exception {
|
if has_dispatched_exception {
|
||||||
let exception = v8::Local::new(tc_scope, exception);
|
// This will be overrided in `exception_to_err_result()`.
|
||||||
|
let exception = v8::undefined(tc_scope).into();
|
||||||
let pending_mod_evaluate = {
|
let pending_mod_evaluate = {
|
||||||
let mut state = state_rc.borrow_mut();
|
let mut state = state_rc.borrow_mut();
|
||||||
state.pending_mod_evaluate.take().unwrap()
|
state.pending_mod_evaluate.take().unwrap()
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
((window) => {
|
((window) => {
|
||||||
const core = window.Deno.core;
|
const core = window.Deno.core;
|
||||||
|
const ops = core.ops;
|
||||||
const webidl = window.__bootstrap.webidl;
|
const webidl = window.__bootstrap.webidl;
|
||||||
const { DOMException } = window.__bootstrap.domException;
|
const { DOMException } = window.__bootstrap.domException;
|
||||||
const consoleInternal = window.__bootstrap.console;
|
const consoleInternal = window.__bootstrap.console;
|
||||||
|
@ -1451,7 +1452,7 @@
|
||||||
});
|
});
|
||||||
// Avoid recursing `reportException()` via error handlers more than once.
|
// Avoid recursing `reportException()` via error handlers more than once.
|
||||||
if (reportExceptionStackedCalls > 1 || window.dispatchEvent(event)) {
|
if (reportExceptionStackedCalls > 1 || window.dispatchEvent(event)) {
|
||||||
core.terminate(error);
|
ops.op_dispatch_exception(error);
|
||||||
}
|
}
|
||||||
reportExceptionStackedCalls--;
|
reportExceptionStackedCalls--;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,8 @@ impl InspectorServer {
|
||||||
js_runtime: &mut JsRuntime,
|
js_runtime: &mut JsRuntime,
|
||||||
should_break_on_first_statement: bool,
|
should_break_on_first_statement: bool,
|
||||||
) {
|
) {
|
||||||
let inspector = js_runtime.inspector();
|
let inspector_rc = js_runtime.inspector();
|
||||||
|
let mut inspector = inspector_rc.borrow_mut();
|
||||||
let session_sender = inspector.get_session_sender();
|
let session_sender = inspector.get_session_sender();
|
||||||
let deregister_rx = inspector.add_deregister_handler();
|
let deregister_rx = inspector.add_deregister_handler();
|
||||||
let info = InspectorInfo::new(
|
let info = InspectorInfo::new(
|
||||||
|
|
|
@ -294,6 +294,7 @@ impl MainWorker {
|
||||||
self
|
self
|
||||||
.js_runtime
|
.js_runtime
|
||||||
.inspector()
|
.inspector()
|
||||||
|
.borrow_mut()
|
||||||
.wait_for_session_and_break_on_next_statement()
|
.wait_for_session_and_break_on_next_statement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,8 +302,7 @@ impl MainWorker {
|
||||||
/// Create new inspector session. This function panics if Worker
|
/// Create new inspector session. This function panics if Worker
|
||||||
/// was not configured to create inspector.
|
/// was not configured to create inspector.
|
||||||
pub async fn create_inspector_session(&mut self) -> LocalInspectorSession {
|
pub async fn create_inspector_session(&mut self) -> LocalInspectorSession {
|
||||||
let inspector = self.js_runtime.inspector();
|
self.js_runtime.inspector().borrow().create_local_session()
|
||||||
inspector.create_local_session()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_event_loop(
|
pub fn poll_event_loop(
|
||||||
|
|
Loading…
Reference in a new issue