diff options
author | Bert Belder <bertbelder@gmail.com> | 2020-09-14 18:48:57 +0200 |
---|---|---|
committer | Bert Belder <bertbelder@gmail.com> | 2020-09-15 01:50:52 +0200 |
commit | f5b40c918c7d602827622d167728a3e7bae87d9d (patch) | |
tree | fb51722e043f4d6bce64a2c7e897cce4ead06c82 /core/errors.rs | |
parent | 3da20d19a14ab6838897d281f1b11e49d68bd1a7 (diff) |
refactor: use the 'anyhow' crate instead of 'ErrBox' (#7476)
Diffstat (limited to 'core/errors.rs')
-rw-r--r-- | core/errors.rs | 466 |
1 files changed, 0 insertions, 466 deletions
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<T> 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<dyn AnyError>), -} - -impl ErrBox { - pub fn new( - class: &'static str, - message: impl Into<Cow<'static, str>>, - ) -> Self { - Self::Simple { - class, - message: message.into(), - } - } - - pub fn bad_resource(message: impl Into<Cow<'static, str>>) -> Self { - Self::new("BadResource", message) - } - - pub fn bad_resource_id() -> Self { - Self::new("BadResource", "Bad resource ID") - } - - pub fn error(message: impl Into<Cow<'static, str>>) -> 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<Cow<'static, str>>) -> Self { - Self::new("TypeError", message) - } - - pub fn last_os_error() -> Self { - Self::from(io::Error::last_os_error()) - } - - pub fn downcast<T: AnyError>(self) -> Result<T, Self> { - match self { - Self::Boxed(error) if Any::type_id(&*error) == TypeId::of::<T>() => { - 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<T: AnyError>(&self) -> Option<&T> { - match self { - Self::Boxed(error) if Any::type_id(&**error) == TypeId::of::<T>() => { - 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<T: AnyError> From<T> for ErrBox { - fn from(error: T) -> Self { - Self::Boxed(Box::new(error)) - } -} - -impl From<Box<dyn AnyError>> for ErrBox { - fn from(boxed: Box<dyn AnyError>) -> 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<String>, - pub script_resource_name: Option<String>, - pub line_number: Option<i64>, - pub start_column: Option<i64>, // 0-based - pub end_column: Option<i64>, // 0-based - pub frames: Vec<JsStackFrame>, - pub formatted_frames: Vec<String>, -} - -#[derive(Debug, PartialEq, Clone)] -pub struct JsStackFrame { - pub type_name: Option<String>, - pub function_name: Option<String>, - pub method_name: Option<String>, - pub file_name: Option<String>, - pub line_number: Option<i64>, - pub column_number: Option<i64>, - pub eval_origin: Option<String>, - pub is_top_level: Option<bool>, - 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<i64>, -} - -fn get_property<'a>( - scope: &mut v8::HandleScope<'a>, - object: v8::Local<v8::Object>, - key: &str, -) -> Option<v8::Local<'a, v8::Value>> { - 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<v8::Value>, - ) -> 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<v8::Object> = - 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<v8::Local<v8::Array>> = - 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<v8::Local<v8::Array>> = - formatted_frames_v8.and_then(|a| a.try_into().ok()); - - // Convert them into Vec<JSStack> and Vec<String> respectively. - let mut frames: Vec<JsStackFrame> = vec![]; - let mut formatted_frames: Vec<String> = 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<v8::Object> = - frames_v8.get_index(scope, i).unwrap().try_into().unwrap(); - let type_name: Option<v8::Local<v8::String>> = - 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<v8::Local<v8::String>> = - 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<v8::Local<v8::String>> = - 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<v8::Local<v8::String>> = - 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<v8::Local<v8::Integer>> = - get_property(scope, call_site, "lineNumber") - .unwrap() - .try_into() - .ok(); - let line_number = line_number.map(|n| n.value()); - let column_number: Option<v8::Local<v8::Integer>> = - get_property(scope, call_site, "columnNumber") - .unwrap() - .try_into() - .ok(); - let column_number = column_number.map(|n| n.value()); - let eval_origin: Option<v8::Local<v8::String>> = - 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<v8::Local<v8::Boolean>> = - 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<v8::Boolean> = - get_property(scope, call_site, "isEval") - .unwrap() - .try_into() - .unwrap(); - let is_eval = is_eval.is_true(); - let is_native: v8::Local<v8::Boolean> = - get_property(scope, call_site, "isNative") - .unwrap() - .try_into() - .unwrap(); - let is_native = is_native.is_true(); - let is_constructor: v8::Local<v8::Boolean> = - get_property(scope, call_site, "isConstructor") - .unwrap() - .try_into() - .unwrap(); - let is_constructor = is_constructor.is_true(); - let is_async: v8::Local<v8::Boolean> = - get_property(scope, call_site, "isAsync") - .unwrap() - .try_into() - .unwrap(); - let is_async = is_async.is_true(); - let is_promise_all: v8::Local<v8::Boolean> = - get_property(scope, call_site, "isPromiseAll") - .unwrap() - .try_into() - .unwrap(); - let is_promise_all = is_promise_all.is_true(); - let promise_index: Option<v8::Local<v8::Integer>> = - 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<v8::String> = 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::<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, - 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<v8::Value>, -) -> 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<T>. -pub struct ErrWithV8Handle { - err: ErrBox, - handle: v8::Global<v8::Value>, -} - -impl ErrWithV8Handle { - pub fn new( - scope: &mut v8::Isolate, - err: ErrBox, - handle: v8::Local<v8::Value>, - ) -> 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"); - } -} |