From f5b40c918c7d602827622d167728a3e7bae87d9d Mon Sep 17 00:00:00 2001 From: Bert Belder Date: Mon, 14 Sep 2020 18:48:57 +0200 Subject: refactor: use the 'anyhow' crate instead of 'ErrBox' (#7476) --- core/Cargo.toml | 3 +- core/bindings.rs | 8 +- core/error.rs | 439 +++++++++++++++++++++++++++++++++ core/errors.rs | 466 ----------------------------------- core/examples/http_bench_json_ops.rs | 23 +- core/lib.rs | 5 +- core/modules.rs | 28 ++- core/ops.rs | 15 +- core/runtime.rs | 92 ++++--- 9 files changed, 532 insertions(+), 547 deletions(-) create mode 100644 core/error.rs delete mode 100644 core/errors.rs (limited to 'core') diff --git a/core/Cargo.toml b/core/Cargo.toml index 83482e46a..d9b1243fc 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -13,8 +13,10 @@ repository = "https://github.com/denoland/deno" path = "lib.rs" [dependencies] +anyhow = "1.0.32" downcast-rs = "1.2.0" futures = "0.3.5" +indexmap = "1.6.0" lazy_static = "1.4.0" libc = "0.2.77" log = "0.4.11" @@ -22,7 +24,6 @@ rusty_v8 = "0.10.0" serde_json = { version = "1.0.57", features = ["preserve_order"] } smallvec = "1.4.2" url = "2.1.1" -indexmap = "1.6.0" [[example]] name = "http_bench_bin_ops" diff --git a/core/bindings.rs b/core/bindings.rs index 265906990..03212f356 100644 --- a/core/bindings.rs +++ b/core/bindings.rs @@ -1,7 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -use crate::ErrBox; -use crate::JsError; +use crate::error::AnyError; +use crate::error::JsError; use crate::JsRuntime; use crate::JsRuntimeState; use crate::Op; @@ -396,8 +396,8 @@ fn send<'s>( let state = state_rc.borrow_mut(); let op_id = match v8::Local::::try_from(args.get(0)) - .map_err(ErrBox::from) - .and_then(|l| OpId::try_from(l.value()).map_err(ErrBox::from)) + .map_err(AnyError::from) + .and_then(|l| OpId::try_from(l.value()).map_err(AnyError::from)) { Ok(op_id) => op_id, Err(err) => { diff --git a/core/error.rs b/core/error.rs new file mode 100644 index 000000000..198f7baee --- /dev/null +++ b/core/error.rs @@ -0,0 +1,439 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +use rusty_v8 as v8; +use std::borrow::Cow; +use std::convert::TryFrom; +use std::convert::TryInto; +use std::error::Error; +use std::fmt; +use std::fmt::Debug; +use std::fmt::Display; +use std::fmt::Formatter; +use std::io; + +/// A generic wrapper that can encapsulate any concrete error type. +pub type AnyError = anyhow::Error; + +/// Creates a new error with a caller-specified error class name and message. +pub fn custom_error( + class: &'static str, + message: impl Into>, +) -> AnyError { + CustomError { + class, + message: message.into(), + } + .into() +} + +pub fn generic_error(message: impl Into>) -> AnyError { + custom_error("Error", message) +} + +pub fn type_error(message: impl Into>) -> AnyError { + custom_error("TypeError", message) +} + +pub fn uri_error(message: impl Into>) -> AnyError { + custom_error("URIError", message) +} + +pub fn last_os_error() -> AnyError { + io::Error::last_os_error().into() +} + +pub fn bad_resource(message: impl Into>) -> AnyError { + custom_error("BadResource", message) +} + +pub fn bad_resource_id() -> AnyError { + custom_error("BadResource", "Bad resource ID") +} + +pub fn not_supported() -> AnyError { + custom_error("NotSupported", "The operation is supported") +} + +pub fn resource_unavailable() -> AnyError { + custom_error( + "Busy", + "Resource is unavailable because it is in use by a promise", + ) +} + +/// A simple error type that lets the creator specify both the error message and +/// the error class name. This type is private; externally it only ever appears +/// wrapped in an `AnyError`. To retrieve the error class name from a wrapped +/// `CustomError`, use the function `get_custom_error_class()`. +#[derive(Debug)] +struct CustomError { + class: &'static str, + message: Cow<'static, str>, +} + +impl Display for CustomError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(&self.message) + } +} + +impl Error for CustomError {} + +/// If this error was crated with `custom_error()`, return the specified error +/// class name. In all other cases this function returns `None`. +pub fn get_custom_error_class(error: &AnyError) -> Option<&'static str> { + error.downcast_ref::().map(|e| e.class) +} + +/// 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)] +pub struct JsError { + pub message: String, + pub source_line: Option, + pub script_resource_name: Option, + pub line_number: Option, + pub start_column: Option, // 0-based + pub end_column: Option, // 0-based + pub frames: Vec, + pub formatted_frames: Vec, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct JsStackFrame { + pub type_name: Option, + pub function_name: Option, + pub method_name: Option, + pub file_name: Option, + pub line_number: Option, + pub column_number: Option, + pub eval_origin: Option, + pub is_top_level: Option, + pub is_eval: bool, + pub is_native: bool, + pub is_constructor: bool, + pub is_async: bool, + pub is_promise_all: bool, + pub promise_index: Option, +} + +fn get_property<'a>( + scope: &mut v8::HandleScope<'a>, + object: v8::Local, + key: &str, +) -> Option> { + let key = v8::String::new(scope, key).unwrap(); + object.get(scope, key.into()) +} + +impl JsError { + pub(crate) fn create(js_error: Self) -> AnyError { + js_error.into() + } + + pub fn from_v8_exception( + scope: &mut v8::HandleScope, + exception: v8::Local, + ) -> Self { + // Create a new HandleScope because we're creating a lot of new local + // handles below. + let scope = &mut v8::HandleScope::new(scope); + + let msg = v8::Exception::create_message(scope, exception); + + let (message, frames, formatted_frames) = if exception.is_native_error() { + // The exception is a JS Error object. + let exception: v8::Local = + exception.clone().try_into().unwrap(); + + // Get the message by formatting error.name and error.message. + let name = get_property(scope, exception, "name") + .and_then(|m| m.to_string(scope)) + .map(|s| s.to_rust_string_lossy(scope)) + .unwrap_or_else(|| "undefined".to_string()); + let message_prop = get_property(scope, exception, "message") + .and_then(|m| m.to_string(scope)) + .map(|s| s.to_rust_string_lossy(scope)) + .unwrap_or_else(|| "undefined".to_string()); + let message = format!("Uncaught {}: {}", name, message_prop); + + // Access error.stack to ensure that prepareStackTrace() has been called. + // This should populate error.__callSiteEvals and error.__formattedFrames. + let _ = get_property(scope, exception, "stack"); + + // Read an array of structured frames from error.__callSiteEvals. + let frames_v8 = get_property(scope, exception, "__callSiteEvals"); + let frames_v8: Option> = + frames_v8.and_then(|a| a.try_into().ok()); + + // Read an array of pre-formatted frames from error.__formattedFrames. + let formatted_frames_v8 = + get_property(scope, exception, "__formattedFrames"); + let formatted_frames_v8: Option> = + formatted_frames_v8.and_then(|a| a.try_into().ok()); + + // Convert them into Vec and Vec respectively. + let mut frames: Vec = vec![]; + let mut formatted_frames: Vec = vec![]; + if let (Some(frames_v8), Some(formatted_frames_v8)) = + (frames_v8, formatted_frames_v8) + { + for i in 0..frames_v8.length() { + let call_site: v8::Local = + frames_v8.get_index(scope, i).unwrap().try_into().unwrap(); + let type_name: Option> = + get_property(scope, call_site, "typeName") + .unwrap() + .try_into() + .ok(); + let type_name = type_name.map(|s| s.to_rust_string_lossy(scope)); + let function_name: Option> = + get_property(scope, call_site, "functionName") + .unwrap() + .try_into() + .ok(); + let function_name = + function_name.map(|s| s.to_rust_string_lossy(scope)); + let method_name: Option> = + get_property(scope, call_site, "methodName") + .unwrap() + .try_into() + .ok(); + let method_name = method_name.map(|s| s.to_rust_string_lossy(scope)); + let file_name: Option> = + get_property(scope, call_site, "fileName") + .unwrap() + .try_into() + .ok(); + let file_name = file_name.map(|s| s.to_rust_string_lossy(scope)); + let line_number: Option> = + get_property(scope, call_site, "lineNumber") + .unwrap() + .try_into() + .ok(); + let line_number = line_number.map(|n| n.value()); + let column_number: Option> = + get_property(scope, call_site, "columnNumber") + .unwrap() + .try_into() + .ok(); + let column_number = column_number.map(|n| n.value()); + let eval_origin: Option> = + get_property(scope, call_site, "evalOrigin") + .unwrap() + .try_into() + .ok(); + let eval_origin = eval_origin.map(|s| s.to_rust_string_lossy(scope)); + let is_top_level: Option> = + get_property(scope, call_site, "isTopLevel") + .unwrap() + .try_into() + .ok(); + let is_top_level = is_top_level.map(|b| b.is_true()); + let is_eval: v8::Local = + get_property(scope, call_site, "isEval") + .unwrap() + .try_into() + .unwrap(); + let is_eval = is_eval.is_true(); + let is_native: v8::Local = + get_property(scope, call_site, "isNative") + .unwrap() + .try_into() + .unwrap(); + let is_native = is_native.is_true(); + let is_constructor: v8::Local = + get_property(scope, call_site, "isConstructor") + .unwrap() + .try_into() + .unwrap(); + let is_constructor = is_constructor.is_true(); + let is_async: v8::Local = + get_property(scope, call_site, "isAsync") + .unwrap() + .try_into() + .unwrap(); + let is_async = is_async.is_true(); + let is_promise_all: v8::Local = + get_property(scope, call_site, "isPromiseAll") + .unwrap() + .try_into() + .unwrap(); + let is_promise_all = is_promise_all.is_true(); + let promise_index: Option> = + get_property(scope, call_site, "columnNumber") + .unwrap() + .try_into() + .ok(); + let promise_index = promise_index.map(|n| n.value()); + frames.push(JsStackFrame { + type_name, + function_name, + method_name, + file_name, + line_number, + column_number, + eval_origin, + is_top_level, + is_eval, + is_native, + is_constructor, + is_async, + is_promise_all, + promise_index, + }); + let formatted_frame: v8::Local = formatted_frames_v8 + .get_index(scope, i) + .unwrap() + .try_into() + .unwrap(); + let formatted_frame = formatted_frame.to_rust_string_lossy(scope); + formatted_frames.push(formatted_frame) + } + } + (message, frames, formatted_frames) + } 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![], vec![]) + }; + + Self { + message, + script_resource_name: msg + .get_script_resource_name(scope) + .and_then(|v| v8::Local::::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, + formatted_frames, + } + } +} + +impl Error for JsError {} + +fn format_source_loc( + file_name: &str, + line_number: i64, + column_number: i64, +) -> String { + let line_number = line_number; + let column_number = column_number; + format!("{}:{}:{}", file_name, line_number, column_number) +} + +impl Display for JsError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + if let Some(script_resource_name) = &self.script_resource_name { + if self.line_number.is_some() && self.start_column.is_some() { + assert!(self.line_number.is_some()); + assert!(self.start_column.is_some()); + let source_loc = format_source_loc( + script_resource_name, + self.line_number.unwrap(), + self.start_column.unwrap(), + ); + write!(f, "{}", source_loc)?; + } + if self.source_line.is_some() { + let source_line = self.source_line.as_ref().unwrap(); + write!(f, "\n{}\n", source_line)?; + let mut s = String::new(); + for i in 0..self.end_column.unwrap() { + if i >= self.start_column.unwrap() { + s.push('^'); + } else if source_line.chars().nth(i as usize).unwrap() == '\t' { + s.push('\t'); + } else { + s.push(' '); + } + } + writeln!(f, "{}", s)?; + } + } + + write!(f, "{}", self.message)?; + + for formatted_frame in &self.formatted_frames { + // TODO: Strip ANSI color from formatted_frame. + write!(f, "\n at {}", formatted_frame)?; + } + Ok(()) + } +} + +pub(crate) fn attach_handle_to_error( + scope: &mut v8::Isolate, + err: AnyError, + handle: v8::Local, +) -> AnyError { + // TODO(bartomieju): this is a special case... + ErrWithV8Handle::new(scope, err, handle).into() +} + +// TODO(piscisaureus): rusty_v8 should implement the Error trait on +// values of type v8::Global. +pub struct ErrWithV8Handle { + err: AnyError, + handle: v8::Global, +} + +impl ErrWithV8Handle { + pub fn new( + scope: &mut v8::Isolate, + err: AnyError, + handle: v8::Local, + ) -> Self { + let handle = v8::Global::new(scope, handle); + Self { err, handle } + } + + pub fn get_handle<'s>( + &self, + scope: &mut v8::HandleScope<'s>, + ) -> v8::Local<'s, v8::Value> { + v8::Local::new(scope, &self.handle) + } +} + +unsafe impl Send for ErrWithV8Handle {} +unsafe impl Sync for ErrWithV8Handle {} + +impl Error for ErrWithV8Handle {} + +impl Display for ErrWithV8Handle { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + ::fmt(&self.err, f) + } +} + +impl Debug for ErrWithV8Handle { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + ::fmt(self, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bad_resource() { + let err = bad_resource("Resource has been closed"); + assert_eq!(err.to_string(), "Resource has been closed"); + } + + #[test] + fn test_bad_resource_id() { + let err = bad_resource_id(); + assert_eq!(err.to_string(), "Bad resource ID"); + } +} diff --git a/core/errors.rs b/core/errors.rs deleted file mode 100644 index 3e42fb937..000000000 --- a/core/errors.rs +++ /dev/null @@ -1,466 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -use rusty_v8 as v8; -use std::any::Any; -use std::any::TypeId; -use std::borrow::Cow; -use std::convert::TryFrom; -use std::convert::TryInto; -use std::error::Error; -use std::fmt; -use std::io; - -// The Send and Sync traits are required because deno is multithreaded and we -// need to be able to handle errors across threads. -pub trait AnyError: Any + Error + Send + Sync + 'static {} -impl AnyError for T where T: Any + Error + Send + Sync + Sized + 'static {} - -#[derive(Debug)] -pub enum ErrBox { - Simple { - class: &'static str, - message: Cow<'static, str>, - }, - Boxed(Box), -} - -impl ErrBox { - pub fn new( - class: &'static str, - message: impl Into>, - ) -> Self { - Self::Simple { - class, - message: message.into(), - } - } - - pub fn bad_resource(message: impl Into>) -> Self { - Self::new("BadResource", message) - } - - pub fn bad_resource_id() -> Self { - Self::new("BadResource", "Bad resource ID") - } - - pub fn error(message: impl Into>) -> Self { - Self::new("Error", message) - } - - pub fn not_supported() -> Self { - Self::new("NotSupported", "The operation is supported") - } - - pub fn resource_unavailable() -> Self { - Self::new( - "Busy", - "Resource is unavailable because it is in use by a promise", - ) - } - - pub fn type_error(message: impl Into>) -> Self { - Self::new("TypeError", message) - } - - pub fn last_os_error() -> Self { - Self::from(io::Error::last_os_error()) - } - - pub fn downcast(self) -> Result { - match self { - Self::Boxed(error) if Any::type_id(&*error) == TypeId::of::() => { - let error = Box::into_raw(error) as *mut T; - let error = unsafe { Box::from_raw(error) }; - Ok(*error) - } - other => Err(other), - } - } - - pub fn downcast_ref(&self) -> Option<&T> { - match self { - Self::Boxed(error) if Any::type_id(&**error) == TypeId::of::() => { - let error = &**error as *const dyn AnyError as *const T; - let error = unsafe { &*error }; - Some(error) - } - _ => None, - } - } -} - -impl fmt::Display for ErrBox { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Simple { message, .. } => f.write_str(message), - Self::Boxed(error) => error.fmt(f), - } - } -} - -impl From for ErrBox { - fn from(error: T) -> Self { - Self::Boxed(Box::new(error)) - } -} - -impl From> for ErrBox { - fn from(boxed: Box) -> Self { - Self::Boxed(boxed) - } -} - -/// 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)] -pub struct JsError { - pub message: String, - pub source_line: Option, - pub script_resource_name: Option, - pub line_number: Option, - pub start_column: Option, // 0-based - pub end_column: Option, // 0-based - pub frames: Vec, - pub formatted_frames: Vec, -} - -#[derive(Debug, PartialEq, Clone)] -pub struct JsStackFrame { - pub type_name: Option, - pub function_name: Option, - pub method_name: Option, - pub file_name: Option, - pub line_number: Option, - pub column_number: Option, - pub eval_origin: Option, - pub is_top_level: Option, - pub is_eval: bool, - pub is_native: bool, - pub is_constructor: bool, - pub is_async: bool, - pub is_promise_all: bool, - pub promise_index: Option, -} - -fn get_property<'a>( - scope: &mut v8::HandleScope<'a>, - object: v8::Local, - key: &str, -) -> Option> { - let key = v8::String::new(scope, key).unwrap(); - object.get(scope, key.into()) -} - -impl JsError { - pub(crate) fn create(js_error: Self) -> ErrBox { - js_error.into() - } - - pub fn from_v8_exception( - scope: &mut v8::HandleScope, - exception: v8::Local, - ) -> Self { - // Create a new HandleScope because we're creating a lot of new local - // handles below. - let scope = &mut v8::HandleScope::new(scope); - - let msg = v8::Exception::create_message(scope, exception); - - let (message, frames, formatted_frames) = if exception.is_native_error() { - // The exception is a JS Error object. - let exception: v8::Local = - exception.clone().try_into().unwrap(); - - // Get the message by formatting error.name and error.message. - let name = get_property(scope, exception, "name") - .and_then(|m| m.to_string(scope)) - .map(|s| s.to_rust_string_lossy(scope)) - .unwrap_or_else(|| "undefined".to_string()); - let message_prop = get_property(scope, exception, "message") - .and_then(|m| m.to_string(scope)) - .map(|s| s.to_rust_string_lossy(scope)) - .unwrap_or_else(|| "undefined".to_string()); - let message = format!("Uncaught {}: {}", name, message_prop); - - // Access error.stack to ensure that prepareStackTrace() has been called. - // This should populate error.__callSiteEvals and error.__formattedFrames. - let _ = get_property(scope, exception, "stack"); - - // Read an array of structured frames from error.__callSiteEvals. - let frames_v8 = get_property(scope, exception, "__callSiteEvals"); - let frames_v8: Option> = - frames_v8.and_then(|a| a.try_into().ok()); - - // Read an array of pre-formatted frames from error.__formattedFrames. - let formatted_frames_v8 = - get_property(scope, exception, "__formattedFrames"); - let formatted_frames_v8: Option> = - formatted_frames_v8.and_then(|a| a.try_into().ok()); - - // Convert them into Vec and Vec respectively. - let mut frames: Vec = vec![]; - let mut formatted_frames: Vec = vec![]; - if let (Some(frames_v8), Some(formatted_frames_v8)) = - (frames_v8, formatted_frames_v8) - { - for i in 0..frames_v8.length() { - let call_site: v8::Local = - frames_v8.get_index(scope, i).unwrap().try_into().unwrap(); - let type_name: Option> = - get_property(scope, call_site, "typeName") - .unwrap() - .try_into() - .ok(); - let type_name = type_name.map(|s| s.to_rust_string_lossy(scope)); - let function_name: Option> = - get_property(scope, call_site, "functionName") - .unwrap() - .try_into() - .ok(); - let function_name = - function_name.map(|s| s.to_rust_string_lossy(scope)); - let method_name: Option> = - get_property(scope, call_site, "methodName") - .unwrap() - .try_into() - .ok(); - let method_name = method_name.map(|s| s.to_rust_string_lossy(scope)); - let file_name: Option> = - get_property(scope, call_site, "fileName") - .unwrap() - .try_into() - .ok(); - let file_name = file_name.map(|s| s.to_rust_string_lossy(scope)); - let line_number: Option> = - get_property(scope, call_site, "lineNumber") - .unwrap() - .try_into() - .ok(); - let line_number = line_number.map(|n| n.value()); - let column_number: Option> = - get_property(scope, call_site, "columnNumber") - .unwrap() - .try_into() - .ok(); - let column_number = column_number.map(|n| n.value()); - let eval_origin: Option> = - get_property(scope, call_site, "evalOrigin") - .unwrap() - .try_into() - .ok(); - let eval_origin = eval_origin.map(|s| s.to_rust_string_lossy(scope)); - let is_top_level: Option> = - get_property(scope, call_site, "isTopLevel") - .unwrap() - .try_into() - .ok(); - let is_top_level = is_top_level.map(|b| b.is_true()); - let is_eval: v8::Local = - get_property(scope, call_site, "isEval") - .unwrap() - .try_into() - .unwrap(); - let is_eval = is_eval.is_true(); - let is_native: v8::Local = - get_property(scope, call_site, "isNative") - .unwrap() - .try_into() - .unwrap(); - let is_native = is_native.is_true(); - let is_constructor: v8::Local = - get_property(scope, call_site, "isConstructor") - .unwrap() - .try_into() - .unwrap(); - let is_constructor = is_constructor.is_true(); - let is_async: v8::Local = - get_property(scope, call_site, "isAsync") - .unwrap() - .try_into() - .unwrap(); - let is_async = is_async.is_true(); - let is_promise_all: v8::Local = - get_property(scope, call_site, "isPromiseAll") - .unwrap() - .try_into() - .unwrap(); - let is_promise_all = is_promise_all.is_true(); - let promise_index: Option> = - get_property(scope, call_site, "columnNumber") - .unwrap() - .try_into() - .ok(); - let promise_index = promise_index.map(|n| n.value()); - frames.push(JsStackFrame { - type_name, - function_name, - method_name, - file_name, - line_number, - column_number, - eval_origin, - is_top_level, - is_eval, - is_native, - is_constructor, - is_async, - is_promise_all, - promise_index, - }); - let formatted_frame: v8::Local = formatted_frames_v8 - .get_index(scope, i) - .unwrap() - .try_into() - .unwrap(); - let formatted_frame = formatted_frame.to_rust_string_lossy(scope); - formatted_frames.push(formatted_frame) - } - } - (message, frames, formatted_frames) - } 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![], vec![]) - }; - - Self { - message, - script_resource_name: msg - .get_script_resource_name(scope) - .and_then(|v| v8::Local::::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, - formatted_frames, - } - } -} - -impl Error for JsError {} - -fn format_source_loc( - file_name: &str, - line_number: i64, - column_number: i64, -) -> String { - let line_number = line_number; - let column_number = column_number; - format!("{}:{}:{}", file_name, line_number, column_number) -} - -impl fmt::Display for JsError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(script_resource_name) = &self.script_resource_name { - if self.line_number.is_some() && self.start_column.is_some() { - assert!(self.line_number.is_some()); - assert!(self.start_column.is_some()); - let source_loc = format_source_loc( - script_resource_name, - self.line_number.unwrap(), - self.start_column.unwrap(), - ); - write!(f, "{}", source_loc)?; - } - if self.source_line.is_some() { - let source_line = self.source_line.as_ref().unwrap(); - write!(f, "\n{}\n", source_line)?; - let mut s = String::new(); - for i in 0..self.end_column.unwrap() { - if i >= self.start_column.unwrap() { - s.push('^'); - } else if source_line.chars().nth(i as usize).unwrap() == '\t' { - s.push('\t'); - } else { - s.push(' '); - } - } - writeln!(f, "{}", s)?; - } - } - - write!(f, "{}", self.message)?; - - for formatted_frame in &self.formatted_frames { - // TODO: Strip ANSI color from formatted_frame. - write!(f, "\n at {}", formatted_frame)?; - } - Ok(()) - } -} - -pub(crate) fn attach_handle_to_error( - scope: &mut v8::Isolate, - err: ErrBox, - handle: v8::Local, -) -> ErrBox { - // TODO(bartomieju): this is a special case... - ErrWithV8Handle::new(scope, err, handle).into() -} - -// TODO(piscisaureus): rusty_v8 should implement the Error trait on -// values of type v8::Global. -pub struct ErrWithV8Handle { - err: ErrBox, - handle: v8::Global, -} - -impl ErrWithV8Handle { - pub fn new( - scope: &mut v8::Isolate, - err: ErrBox, - handle: v8::Local, - ) -> Self { - let handle = v8::Global::new(scope, handle); - Self { err, handle } - } - - pub fn get_handle<'s>( - &self, - scope: &mut v8::HandleScope<'s>, - ) -> v8::Local<'s, v8::Value> { - v8::Local::new(scope, &self.handle) - } -} - -unsafe impl Send for ErrWithV8Handle {} -unsafe impl Sync for ErrWithV8Handle {} - -impl Error for ErrWithV8Handle {} - -impl fmt::Display for ErrWithV8Handle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.err.fmt(f) - } -} - -impl fmt::Debug for ErrWithV8Handle { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.err.fmt(f) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bad_resource() { - let err = ErrBox::bad_resource("Resource has been closed"); - assert!(matches!(err, ErrBox::Simple { class: "BadResource", .. })); - assert_eq!(err.to_string(), "Resource has been closed"); - } - - #[test] - fn test_bad_resource_id() { - let err = ErrBox::bad_resource_id(); - assert!(matches!(err, ErrBox::Simple { class: "BadResource", .. })); - assert_eq!(err.to_string(), "Bad resource ID"); - } -} diff --git a/core/examples/http_bench_json_ops.rs b/core/examples/http_bench_json_ops.rs index a7db450e4..a61a0ad63 100644 --- a/core/examples/http_bench_json_ops.rs +++ b/core/examples/http_bench_json_ops.rs @@ -1,9 +1,10 @@ #[macro_use] extern crate log; +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; use deno_core::js_check; use deno_core::BufVec; -use deno_core::ErrBox; use deno_core::JsRuntime; use deno_core::OpState; use deno_core::ZeroCopyBuf; @@ -53,7 +54,7 @@ fn op_listen( state: &mut OpState, _args: Value, _bufs: &mut [ZeroCopyBuf], -) -> Result { +) -> Result { debug!("listen"); let addr = "127.0.0.1:4544".parse::().unwrap(); let std_listener = std::net::TcpListener::bind(&addr)?; @@ -66,7 +67,7 @@ fn op_close( state: &mut OpState, args: Value, _buf: &mut [ZeroCopyBuf], -) -> Result { +) -> Result { let rid: u32 = args .get("rid") .unwrap() @@ -79,14 +80,14 @@ fn op_close( .resource_table .close(rid) .map(|_| serde_json::json!(())) - .ok_or_else(ErrBox::bad_resource_id) + .ok_or_else(bad_resource_id) } fn op_accept( state: Rc>, args: Value, _bufs: BufVec, -) -> impl Future> { +) -> impl Future> { let rid: u32 = args .get("rid") .unwrap() @@ -101,7 +102,7 @@ fn op_accept( let listener = resource_table .get_mut::(rid) - .ok_or_else(ErrBox::bad_resource_id)?; + .ok_or_else(bad_resource_id)?; listener.poll_accept(cx)?.map(|(stream, _addr)| { let rid = resource_table.add("tcpStream", Box::new(stream)); Ok(serde_json::json!({ "rid": rid })) @@ -113,7 +114,7 @@ fn op_read( state: Rc>, args: Value, mut bufs: BufVec, -) -> impl Future> { +) -> impl Future> { assert_eq!(bufs.len(), 1, "Invalid number of arguments"); let rid: u32 = args @@ -125,12 +126,12 @@ fn op_read( .unwrap(); debug!("read rid={}", rid); - poll_fn(move |cx| -> Poll> { + poll_fn(move |cx| -> Poll> { let resource_table = &mut state.borrow_mut().resource_table; let stream = resource_table .get_mut::(rid) - .ok_or_else(ErrBox::bad_resource_id)?; + .ok_or_else(bad_resource_id)?; Pin::new(stream) .poll_read(cx, &mut bufs[0])? .map(|nread| Ok(serde_json::json!({ "nread": nread }))) @@ -141,7 +142,7 @@ fn op_write( state: Rc>, args: Value, bufs: BufVec, -) -> impl Future> { +) -> impl Future> { assert_eq!(bufs.len(), 1, "Invalid number of arguments"); let rid: u32 = args @@ -158,7 +159,7 @@ fn op_write( let stream = resource_table .get_mut::(rid) - .ok_or_else(ErrBox::bad_resource_id)?; + .ok_or_else(bad_resource_id)?; Pin::new(stream) .poll_write(cx, &bufs[0])? .map(|nwritten| Ok(serde_json::json!({ "nwritten": nwritten }))) diff --git a/core/lib.rs b/core/lib.rs index bb54f5133..ba7fdcd21 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -9,7 +9,7 @@ extern crate lazy_static; extern crate log; mod bindings; -mod errors; +pub mod error; mod flags; mod gotham_state; mod module_specifier; @@ -24,9 +24,6 @@ mod zero_copy_buf; pub use rusty_v8 as v8; -pub use crate::errors::AnyError; -pub use crate::errors::ErrBox; -pub use crate::errors::JsError; pub use crate::flags::v8_set_flags; pub use crate::module_specifier::ModuleResolutionError; pub use crate::module_specifier::ModuleSpecifier; diff --git a/core/modules.rs b/core/modules.rs index eae2a8d9f..e02d5c52e 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -2,8 +2,9 @@ use rusty_v8 as v8; +use crate::error::generic_error; +use crate::error::AnyError; use crate::module_specifier::ModuleSpecifier; -use crate::ErrBox; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::Stream; @@ -48,8 +49,9 @@ pub struct ModuleSource { } pub type PrepareLoadFuture = - dyn Future)>; -pub type ModuleSourceFuture = dyn Future>; + dyn Future)>; +pub type ModuleSourceFuture = + dyn Future>; pub trait ModuleLoader { /// Returns an absolute URL. @@ -64,7 +66,7 @@ pub trait ModuleLoader { specifier: &str, referrer: &str, _is_main: bool, - ) -> Result; + ) -> Result; /// Given ModuleSpecifier, load its source code. /// @@ -91,7 +93,7 @@ pub trait ModuleLoader { _module_specifier: &ModuleSpecifier, _maybe_referrer: Option, _is_dyn_import: bool, - ) -> Pin>>> { + ) -> Pin>>> { async { Ok(()) }.boxed_local() } } @@ -106,8 +108,8 @@ impl ModuleLoader for NoopModuleLoader { _specifier: &str, _referrer: &str, _is_main: bool, - ) -> Result { - Err(ErrBox::error("Module loading is not supported")) + ) -> Result { + Err(generic_error("Module loading is not supported")) } fn load( @@ -116,7 +118,7 @@ impl ModuleLoader for NoopModuleLoader { _maybe_referrer: Option, _is_dyn_import: bool, ) -> Pin> { - async { Err(ErrBox::error("Module loading is not supported")) } + async { Err(generic_error("Module loading is not supported")) } .boxed_local() } } @@ -188,7 +190,7 @@ impl RecursiveModuleLoad { } } - pub async fn prepare(self) -> (ModuleLoadId, Result) { + pub async fn prepare(self) -> (ModuleLoadId, Result) { let (module_specifier, maybe_referrer) = match self.state { LoadState::ResolveMain(ref specifier, _) => { let spec = match self.loader.resolve(specifier, ".", true) { @@ -223,7 +225,7 @@ impl RecursiveModuleLoad { } } - fn add_root(&mut self) -> Result<(), ErrBox> { + fn add_root(&mut self) -> Result<(), AnyError> { let module_specifier = match self.state { LoadState::ResolveMain(ref specifier, _) => { self.loader.resolve(specifier, ".", true)? @@ -273,7 +275,7 @@ impl RecursiveModuleLoad { } impl Stream for RecursiveModuleLoad { - type Item = Result; + type Item = Result; fn poll_next( self: Pin<&mut Self>, @@ -518,7 +520,7 @@ mod tests { } impl Future for DelayedSourceCodeFuture { - type Output = Result; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let inner = self.get_mut(); @@ -549,7 +551,7 @@ mod tests { specifier: &str, referrer: &str, _is_root: bool, - ) -> Result { + ) -> Result { let referrer = if referrer == "." { "file:///" } else { diff --git a/core/ops.rs b/core/ops.rs index 7af4949a1..95be85168 100644 --- a/core/ops.rs +++ b/core/ops.rs @@ -1,8 +1,9 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +use crate::error::type_error; +use crate::error::AnyError; use crate::gotham_state::GothamState; use crate::BufVec; -use crate::ErrBox; use crate::ZeroCopyBuf; use futures::Future; use indexmap::IndexMap; @@ -150,12 +151,12 @@ fn op_table() { pub fn json_op_sync(op_fn: F) -> Box where - F: Fn(&mut OpState, Value, &mut [ZeroCopyBuf]) -> Result + F: Fn(&mut OpState, Value, &mut [ZeroCopyBuf]) -> Result + 'static, { Box::new(move |state: Rc>, mut bufs: BufVec| -> Op { let result = serde_json::from_slice(&bufs[0]) - .map_err(crate::ErrBox::from) + .map_err(AnyError::from) .and_then(|args| op_fn(&mut state.borrow_mut(), args, &mut bufs[1..])); let buf = json_serialize_op_result(None, result, state.borrow().get_error_class_fn); @@ -166,15 +167,15 @@ where pub fn json_op_async(op_fn: F) -> Box where F: Fn(Rc>, Value, BufVec) -> R + 'static, - R: Future> + 'static, + R: Future> + 'static, { let try_dispatch_op = - move |state: Rc>, bufs: BufVec| -> Result { + move |state: Rc>, bufs: BufVec| -> Result { let args: Value = serde_json::from_slice(&bufs[0])?; let promise_id = args .get("promiseId") .and_then(Value::as_u64) - .ok_or_else(|| ErrBox::type_error("missing or invalid `promiseId`"))?; + .ok_or_else(|| type_error("missing or invalid `promiseId`"))?; let bufs = bufs[1..].into(); use crate::futures::FutureExt; let fut = op_fn(state.clone(), args, bufs).map(move |result| { @@ -201,7 +202,7 @@ where fn json_serialize_op_result( promise_id: Option, - result: Result, + result: Result, get_error_class_fn: crate::runtime::GetErrorClassFn, ) -> Box<[u8]> { let value = match result { diff --git a/core/runtime.rs b/core/runtime.rs index 045613224..9ecbe580b 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -3,8 +3,10 @@ use rusty_v8 as v8; use crate::bindings; -use crate::errors::attach_handle_to_error; -use crate::errors::ErrWithV8Handle; +use crate::error::attach_handle_to_error; +use crate::error::AnyError; +use crate::error::ErrWithV8Handle; +use crate::error::JsError; use crate::futures::FutureExt; use crate::module_specifier::ModuleSpecifier; use crate::modules::LoadState; @@ -20,8 +22,6 @@ use crate::ops::*; use crate::shared_queue::SharedQueue; use crate::shared_queue::RECOMMENDED_SIZE; use crate::BufVec; -use crate::ErrBox; -use crate::JsError; use crate::OpState; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; @@ -52,9 +52,10 @@ pub enum Snapshot { Boxed(Box<[u8]>), } -type JsErrorCreateFn = dyn Fn(JsError) -> ErrBox; +type JsErrorCreateFn = dyn Fn(JsError) -> AnyError; -pub type GetErrorClassFn = &'static dyn for<'e> Fn(&'e ErrBox) -> &'static str; +pub type GetErrorClassFn = + &'static dyn for<'e> Fn(&'e AnyError) -> &'static str; /// Objects that need to live as long as the isolate #[derive(Default)] @@ -329,14 +330,15 @@ impl JsRuntime { /// Executes traditional JavaScript code (traditional = not ES modules) /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is JsError, however it may be a - /// different type if JsRuntime::set_js_error_create_fn() has been used. + /// `AnyError` can be downcast to a type that exposes additional information + /// about the V8 exception. By default this type is `JsError`, however it may + /// be a different type if `JsRuntime::set_js_error_create_fn()` has been + /// used. pub fn execute( &mut self, js_filename: &str, js_source: &str, - ) -> Result<(), ErrBox> { + ) -> Result<(), AnyError> { self.shared_init(); let state_rc = Self::state(self); @@ -376,9 +378,10 @@ impl JsRuntime { /// Takes a snapshot. The isolate should have been created with will_snapshot /// set to true. /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is JsError, however it may be a - /// different type if JsRuntime::set_js_error_create_fn() has been used. + /// `AnyError` can be downcast to a type that exposes additional information + /// about the V8 exception. By default this type is `JsError`, however it may + /// be a different type if `JsRuntime::set_js_error_create_fn()` has been + /// used. pub fn snapshot(&mut self) -> v8::StartupData { assert!(self.snapshot_creator.is_some()); let state = Self::state(self); @@ -466,7 +469,7 @@ where } impl Future for JsRuntime { - type Output = Result<(), ErrBox>; + type Output = Result<(), AnyError>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { let runtime = self.get_mut(); @@ -590,7 +593,7 @@ impl JsRuntimeState { /// is set to JsError::create. pub fn set_js_error_create_fn( &mut self, - f: impl Fn(JsError) -> ErrBox + 'static, + f: impl Fn(JsError) -> AnyError + 'static, ) { self.js_error_create_fn = Box::new(f); } @@ -633,7 +636,7 @@ impl JsRuntimeState { fn async_op_response<'s>( scope: &mut v8::HandleScope<'s>, maybe_buf: Option<(OpId, Box<[u8]>)>, -) -> Result<(), ErrBox> { +) -> Result<(), AnyError> { let context = scope.get_current_context(); let global: v8::Local = context.global(scope).into(); let js_recv_cb = JsRuntime::state(scope) @@ -662,7 +665,9 @@ fn async_op_response<'s>( } } -fn drain_macrotasks<'s>(scope: &mut v8::HandleScope<'s>) -> Result<(), ErrBox> { +fn drain_macrotasks<'s>( + scope: &mut v8::HandleScope<'s>, +) -> Result<(), AnyError> { let context = scope.get_current_context(); let global: v8::Local = context.global(scope).into(); @@ -699,7 +704,7 @@ fn drain_macrotasks<'s>(scope: &mut v8::HandleScope<'s>) -> Result<(), ErrBox> { pub(crate) fn exception_to_err_result<'s, T>( scope: &mut v8::HandleScope<'s>, exception: v8::Local, -) -> Result { +) -> Result { // TODO(piscisaureus): in rusty_v8, `is_execution_terminating()` should // also be implemented on `struct Isolate`. let is_terminating_exception = @@ -738,7 +743,7 @@ pub(crate) fn exception_to_err_result<'s, T>( fn check_promise_exceptions<'s>( scope: &mut v8::HandleScope<'s>, -) -> Result<(), ErrBox> { +) -> Result<(), AnyError> { let state_rc = JsRuntime::state(scope); let mut state = state_rc.borrow_mut(); @@ -752,7 +757,7 @@ fn check_promise_exceptions<'s>( } } -pub fn js_check(r: Result) -> T { +pub fn js_check(r: Result) -> T { if let Err(e) = r { panic!(e.to_string()); } @@ -782,7 +787,7 @@ impl JsRuntime { main: bool, name: &str, source: &str, - ) -> Result { + ) -> Result { let state_rc = Self::state(self); let scope = &mut v8::HandleScope::with_context( &mut **self, @@ -831,10 +836,11 @@ impl JsRuntime { /// Instantiates a ES module /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is JsError, however it may be a - /// different type if JsRuntime::set_js_error_create_fn() has been used. - fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + /// `AnyError` can be downcast to a type that exposes additional information + /// about the V8 exception. By default this type is `JsError`, however it may + /// be a different type if `JsRuntime::set_js_error_create_fn()` has been + /// used. + fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), AnyError> { let state_rc = Self::state(self); let state = state_rc.borrow(); let scope = &mut v8::HandleScope::with_context( @@ -867,10 +873,11 @@ impl JsRuntime { /// Evaluates an already instantiated ES module. /// - /// ErrBox can be downcast to a type that exposes additional information about - /// the V8 exception. By default this type is JsError, however it may be a - /// different type if JsRuntime::set_js_error_create_fn() has been used. - pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> { + /// `AnyError` can be downcast to a type that exposes additional information + /// about the V8 exception. By default this type is `JsError`, however it may + /// be a different type if `JsRuntime::set_js_error_create_fn()` has been + /// used. + pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), AnyError> { self.shared_init(); let state_rc = Self::state(self); @@ -939,8 +946,8 @@ impl JsRuntime { fn dyn_import_error( &mut self, id: ModuleLoadId, - err: ErrBox, - ) -> Result<(), ErrBox> { + err: AnyError, + ) -> Result<(), AnyError> { let state_rc = Self::state(self); let scope = &mut v8::HandleScope::with_context( @@ -973,7 +980,7 @@ impl JsRuntime { &mut self, id: ModuleLoadId, mod_id: ModuleId, - ) -> Result<(), ErrBox> { + ) -> Result<(), AnyError> { let state_rc = Self::state(self); debug!("dyn_import_done {} {:?}", id, mod_id); @@ -1010,7 +1017,7 @@ impl JsRuntime { fn prepare_dyn_imports( &mut self, cx: &mut Context, - ) -> Poll> { + ) -> Poll> { let state_rc = Self::state(self); loop { @@ -1041,7 +1048,10 @@ impl JsRuntime { } } - fn poll_dyn_imports(&mut self, cx: &mut Context) -> Poll> { + fn poll_dyn_imports( + &mut self, + cx: &mut Context, + ) -> Poll> { let state_rc = Self::state(self); loop { let poll_result = { @@ -1100,7 +1110,7 @@ impl JsRuntime { &mut self, info: ModuleSource, load: &mut RecursiveModuleLoad, - ) -> Result<(), ErrBox> { + ) -> Result<(), AnyError> { let ModuleSource { code, module_url_specified, @@ -1189,7 +1199,7 @@ impl JsRuntime { &mut self, specifier: &ModuleSpecifier, code: Option, - ) -> Result { + ) -> Result { self.shared_init(); let loader = { let state_rc = Self::state(self); @@ -1870,7 +1880,7 @@ pub mod tests { specifier: &str, referrer: &str, _is_main: bool, - ) -> Result { + ) -> Result { self.count.fetch_add(1, Ordering::Relaxed); assert_eq!(specifier, "./b.js"); assert_eq!(referrer, "file:///a.js"); @@ -1979,7 +1989,7 @@ pub mod tests { specifier: &str, referrer: &str, _is_main: bool, - ) -> Result { + ) -> Result { self.count.fetch_add(1, Ordering::Relaxed); assert_eq!(specifier, "/foo.js"); assert_eq!(referrer, "file:///dyn_import2.js"); @@ -2038,7 +2048,7 @@ pub mod tests { specifier: &str, referrer: &str, _is_main: bool, - ) -> Result { + ) -> Result { let c = self.resolve_count.fetch_add(1, Ordering::Relaxed); assert!(c < 4); assert_eq!(specifier, "./b.js"); @@ -2068,7 +2078,7 @@ pub mod tests { _module_specifier: &ModuleSpecifier, _maybe_referrer: Option, _is_dyn_import: bool, - ) -> Pin>>> { + ) -> Pin>>> { self.prepare_load_count.fetch_add(1, Ordering::Relaxed); async { Ok(()) }.boxed_local() } @@ -2160,7 +2170,7 @@ pub mod tests { specifier: &str, referrer: &str, _is_main: bool, - ) -> Result { + ) -> Result { assert_eq!(specifier, "file:///main.js"); assert_eq!(referrer, "."); let s = ModuleSpecifier::resolve_import(specifier, referrer).unwrap(); -- cgit v1.2.3