0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

fix(repl): print unhandled rejections and event errors (#18878)

Fixes #8858.
Fixes #8869.

```
$ target/debug/deno
Deno 1.32.5
exit using ctrl+d, ctrl+c, or close()
REPL is running with all permissions allowed.
To specify permissions, run `deno repl` with allow flags.
> Promise.reject(new Error("bar"));
Promise { <rejected> Error: bar
    at <anonymous>:2:16 }
Uncaught (in promise) Error: bar
    at <anonymous>:2:16
> reportError(new Error("baz"));
undefined
Uncaught Error: baz
    at <anonymous>:2:13
>
This commit is contained in:
Nayeem Rahman 2023-04-27 22:36:49 +01:00 committed by GitHub
parent 6cd62ea5e9
commit 504482dadd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 9 deletions

View file

@ -783,14 +783,42 @@ fn pty_tab_handler() {
});
}
#[test]
fn repl_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console.log(1);");
console.expect_all(&["1", "undefined"]);
console.write_line(r#"throw new Error("foo");"#);
console.expect("Uncaught Error: foo");
console.expect(" at <anonymous>");
console.write_line("console.log(2);");
console.expect("2");
});
}
#[test]
fn repl_reject() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console.log(1);");
console.expect_all(&["1", "undefined"]);
console.write_line(r#"Promise.reject(new Error("foo"));"#);
console.expect("Promise { <rejected> Error: foo");
console.expect("Uncaught (in promise) Error: foo");
console.expect(" at <anonymous>");
console.write_line("console.log(2);");
console.expect("2");
});
}
#[test]
fn repl_report_error() {
util::with_pty(&["repl"], |mut console| {
console.write_line("console.log(1);");
console.expect_all(&["1", "undefined"]);
// TODO(nayeemrmn): The REPL should report event errors and rejections.
console.write_line(r#"reportError(new Error("foo"));"#);
console.expect("undefined");
console.expect("Uncaught Error: foo");
console.expect(" at <anonymous>");
console.write_line("console.log(2);");
console.expect("2");
});

View file

@ -7,6 +7,7 @@ use crate::colors;
use crate::file_fetcher::FileFetcher;
use crate::proc_state::ProcState;
use deno_core::error::AnyError;
use deno_core::futures::StreamExt;
use deno_runtime::permissions::Permissions;
use deno_runtime::permissions::PermissionsContainer;
use rustyline::error::ReadlineError;
@ -30,8 +31,11 @@ async fn read_line_and_poll(
message_handler: &mut RustylineSyncMessageHandler,
editor: ReplEditor,
) -> Result<String, ReadlineError> {
#![allow(clippy::await_holding_refcell_ref)]
let mut line_fut = tokio::task::spawn_blocking(move || editor.readline());
let mut poll_worker = true;
let notifications_rc = repl_session.notifications.clone();
let mut notifications = notifications_rc.borrow_mut();
loop {
tokio::select! {
@ -57,7 +61,20 @@ async fn read_line_and_poll(
}
poll_worker = true;
},
}
message = notifications.next() => {
if let Some(message) = message {
let method = message.get("method").unwrap().as_str().unwrap();
if method == "Runtime.exceptionThrown" {
let params = message.get("params").unwrap().as_object().unwrap();
let exception_details = params.get("exceptionDetails").unwrap().as_object().unwrap();
let text = exception_details.get("text").unwrap().as_str().unwrap();
let exception = exception_details.get("exception").unwrap().as_object().unwrap();
let description = exception.get("description").unwrap().as_str().unwrap();
println!("{text} {description}");
}
}
}
_ = repl_session.run_event_loop(), if poll_worker => {
poll_worker = false;
}

View file

@ -1,5 +1,7 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
use crate::args::CliOptions;
@ -128,12 +130,9 @@ pub struct ReplSession {
session: LocalInspectorSession,
pub context_id: u64,
pub language_server: ReplLanguageServer,
pub notifications: Rc<RefCell<UnboundedReceiver<Value>>>,
has_initialized_node_runtime: bool,
referrer: ModuleSpecifier,
// FIXME(bartlomieju): this field should be used to listen
// for "exceptionThrown" notifications
#[allow(dead_code)]
notification_rx: UnboundedReceiver<Value>,
}
impl ReplSession {
@ -193,7 +192,7 @@ impl ReplSession {
language_server,
has_initialized_node_runtime: false,
referrer,
notification_rx,
notifications: Rc::new(RefCell::new(notification_rx)),
};
// inject prelude

View file

@ -231,6 +231,35 @@ impl JsRuntimeInspector {
.context_destroyed(context);
}
pub fn exception_thrown(
&self,
scope: &mut HandleScope,
exception: v8::Local<'_, v8::Value>,
in_promise: bool,
) {
let context = scope.get_current_context();
let message = v8::Exception::create_message(scope, exception);
let stack_trace = message.get_stack_trace(scope).unwrap();
let mut v8_inspector_ref = self.v8_inspector.borrow_mut();
let v8_inspector = v8_inspector_ref.as_mut().unwrap();
let stack_trace = v8_inspector.create_stack_trace(stack_trace);
v8_inspector.exception_thrown(
context,
if in_promise {
v8::inspector::StringView::from("Uncaught (in promise)".as_bytes())
} else {
v8::inspector::StringView::from("Uncaught".as_bytes())
},
exception,
v8::inspector::StringView::from("".as_bytes()),
v8::inspector::StringView::from("".as_bytes()),
0,
0,
stack_trace,
0,
);
}
pub fn has_active_sessions(&self) -> bool {
self.sessions.borrow().has_active_sessions()
}

View file

@ -715,8 +715,7 @@ fn op_dispatch_exception(
let mut state = state_rc.borrow_mut();
if let Some(inspector) = &state.inspector {
let inspector = inspector.borrow();
// TODO(nayeemrmn): Send exception message to inspector sessions here.
inspector.exception_thrown(scope, exception.v8_value, false);
// This indicates that the op is being called from a REPL. Skip termination.
if inspector.is_dispatching_message() {
return;

View file

@ -4,6 +4,7 @@ use crate::bindings;
use crate::modules::ModuleCode;
use crate::ops::OpCtx;
use crate::runtime::exception_to_err_result;
use crate::JsRuntime;
use anyhow::Error;
use std::cell::RefCell;
use std::collections::HashMap;
@ -288,6 +289,15 @@ impl<'s> JsRealmLocal<'s> {
drop(context_state);
let exception = v8::Local::new(scope, handle);
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
if let Some(inspector) = &state.inspector {
let inspector = inspector.borrow();
inspector.exception_thrown(scope, exception, true);
if inspector.has_blocking_sessions() {
return Ok(());
}
}
exception_to_err_result(scope, exception, true)
}
}