2020-01-02 15:13:47 -05:00
|
|
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
2019-02-26 17:36:05 -05:00
|
|
|
|
|
|
|
// Note that source_map_mappings requires 0-indexed line and column numbers but
|
|
|
|
// V8 Exceptions are 1-indexed.
|
|
|
|
|
|
|
|
// TODO: This currently only applies to uncaught exceptions. It would be nice to
|
|
|
|
// also have source maps for situations like this:
|
|
|
|
// const err = new Error("Boo!");
|
|
|
|
// console.log(err.stack);
|
|
|
|
// It would require calling into Rust from Error.prototype.prepareStackTrace.
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
use crate::ErrBox;
|
|
|
|
use rusty_v8 as v8;
|
|
|
|
use std::convert::TryFrom;
|
|
|
|
use std::convert::TryInto;
|
2019-07-10 18:53:48 -04:00
|
|
|
use std::error::Error;
|
2019-02-26 17:36:05 -05:00
|
|
|
use std::fmt;
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
/// 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.
|
2019-04-01 15:09:59 -04:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
2020-03-02 17:20:16 -05:00
|
|
|
pub struct JSError {
|
2019-02-26 17:36:05 -05:00
|
|
|
pub message: String,
|
|
|
|
pub source_line: Option<String>,
|
|
|
|
pub script_resource_name: Option<String>,
|
|
|
|
pub line_number: Option<i64>,
|
|
|
|
pub start_column: Option<i64>,
|
|
|
|
pub end_column: Option<i64>,
|
2020-03-02 17:20:16 -05:00
|
|
|
pub frames: Vec<JSStackFrame>,
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2019-07-10 18:53:48 -04:00
|
|
|
#[derive(Debug, PartialEq, Clone)]
|
2020-03-02 17:20:16 -05:00
|
|
|
pub struct JSStackFrame {
|
|
|
|
pub line_number: i64, // zero indexed
|
|
|
|
pub column: i64, // zero indexed
|
|
|
|
pub script_name: String,
|
|
|
|
pub function_name: String,
|
|
|
|
pub is_eval: bool,
|
|
|
|
pub is_constructor: bool,
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
impl JSError {
|
|
|
|
pub(crate) fn create(js_error: Self) -> ErrBox {
|
|
|
|
ErrBox::from(js_error)
|
2019-04-04 05:33:32 -04:00
|
|
|
}
|
2019-02-26 17:36:05 -05:00
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
pub fn from_v8_exception(
|
|
|
|
scope: &mut impl v8::InIsolate,
|
|
|
|
exception: v8::Local<v8::Value>,
|
|
|
|
) -> Self {
|
|
|
|
// Create a new HandleScope because we're creating a lot of new local
|
|
|
|
// handles below.
|
|
|
|
let mut hs = v8::HandleScope::new(scope);
|
|
|
|
let scope = hs.enter();
|
|
|
|
let context = scope.get_current_context().unwrap();
|
|
|
|
|
|
|
|
let msg = v8::Exception::create_message(scope, exception);
|
|
|
|
|
|
|
|
Self {
|
|
|
|
message: msg.get(scope).to_rust_string_lossy(scope),
|
|
|
|
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, context)
|
|
|
|
.map(|v| v.to_rust_string_lossy(scope)),
|
|
|
|
line_number: msg.get_line_number(context).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: msg
|
|
|
|
.get_stack_trace(scope)
|
|
|
|
.map(|stack_trace| {
|
|
|
|
(0..stack_trace.get_frame_count())
|
|
|
|
.map(|i| {
|
|
|
|
let frame = stack_trace.get_frame(scope, i).unwrap();
|
|
|
|
JSStackFrame {
|
|
|
|
line_number: frame
|
|
|
|
.get_line_number()
|
|
|
|
.checked_sub(1)
|
|
|
|
.and_then(|v| v.try_into().ok())
|
|
|
|
.unwrap(),
|
|
|
|
column: frame
|
|
|
|
.get_column()
|
|
|
|
.checked_sub(1)
|
|
|
|
.and_then(|v| v.try_into().ok())
|
|
|
|
.unwrap(),
|
|
|
|
script_name: frame
|
|
|
|
.get_script_name_or_source_url(scope)
|
|
|
|
.map(|v| v.to_rust_string_lossy(scope))
|
|
|
|
.unwrap_or_else(|| "<unknown>".to_owned()),
|
|
|
|
function_name: frame
|
|
|
|
.get_function_name(scope)
|
|
|
|
.map(|v| v.to_rust_string_lossy(scope))
|
|
|
|
.unwrap_or_else(|| "".to_owned()),
|
|
|
|
is_constructor: frame.is_constructor(),
|
|
|
|
is_eval: frame.is_eval(),
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
})
|
|
|
|
.unwrap_or_else(Vec::<_>::new),
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
impl Error for JSError {}
|
2019-07-10 18:53:48 -04:00
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
fn format_source_loc(
|
|
|
|
script_name: &str,
|
|
|
|
line_number: i64,
|
|
|
|
column: i64,
|
|
|
|
) -> String {
|
2019-07-10 18:53:48 -04:00
|
|
|
// TODO match this style with how typescript displays errors.
|
2020-03-02 17:20:16 -05:00
|
|
|
let line_number = line_number + 1;
|
2019-07-10 18:53:48 -04:00
|
|
|
let column = column + 1;
|
2020-03-02 17:20:16 -05:00
|
|
|
format!("{}:{}:{}", script_name, line_number, column)
|
2019-07-10 18:53:48 -04:00
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
fn format_stack_frame(frame: &JSStackFrame) -> String {
|
2019-07-10 18:53:48 -04:00
|
|
|
// Note when we print to string, we change from 0-indexed to 1-indexed.
|
|
|
|
let source_loc =
|
2020-03-02 17:20:16 -05:00
|
|
|
format_source_loc(&frame.script_name, frame.line_number, frame.column);
|
2019-07-10 18:53:48 -04:00
|
|
|
|
|
|
|
if !frame.function_name.is_empty() {
|
|
|
|
format!(" at {} ({})", frame.function_name, source_loc)
|
|
|
|
} else if frame.is_eval {
|
|
|
|
format!(" at eval ({})", source_loc)
|
|
|
|
} else {
|
|
|
|
format!(" at {}", source_loc)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
impl fmt::Display for JSError {
|
2019-07-10 18:53:48 -04:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2020-03-02 17:20:16 -05:00
|
|
|
if self.script_resource_name.is_some() {
|
|
|
|
let script_resource_name = self.script_resource_name.as_ref().unwrap();
|
|
|
|
if self.line_number.is_some() && self.start_column.is_some() {
|
|
|
|
assert!(self.line_number.is_some());
|
|
|
|
assert!(self.start_column.is_some());
|
2019-07-10 18:53:48 -04:00
|
|
|
let source_loc = format_source_loc(
|
|
|
|
script_resource_name,
|
2020-03-02 17:20:16 -05:00
|
|
|
self.line_number.unwrap() - 1,
|
|
|
|
self.start_column.unwrap() - 1,
|
2019-07-10 18:53:48 -04:00
|
|
|
);
|
|
|
|
write!(f, "{}", source_loc)?;
|
|
|
|
}
|
2020-03-02 17:20:16 -05:00
|
|
|
if self.source_line.is_some() {
|
|
|
|
write!(f, "\n{}\n", self.source_line.as_ref().unwrap())?;
|
2019-07-10 18:53:48 -04:00
|
|
|
let mut s = String::new();
|
2020-03-02 17:20:16 -05:00
|
|
|
for i in 0..self.end_column.unwrap() {
|
|
|
|
if i >= self.start_column.unwrap() {
|
2019-07-10 18:53:48 -04:00
|
|
|
s.push('^');
|
|
|
|
} else {
|
|
|
|
s.push(' ');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
writeln!(f, "{}", s)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
write!(f, "{}", self.message)?;
|
2019-07-10 18:53:48 -04:00
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
for frame in &self.frames {
|
2019-07-10 18:53:48 -04:00
|
|
|
write!(f, "\n{}", format_stack_frame(frame))?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-26 17:36:05 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2020-03-02 17:20:16 -05:00
|
|
|
#[test]
|
|
|
|
fn js_error_to_string() {
|
|
|
|
let js_error = JSError {
|
2019-02-26 17:36:05 -05:00
|
|
|
message: "Error: foo bar".to_string(),
|
|
|
|
source_line: None,
|
|
|
|
script_resource_name: None,
|
|
|
|
line_number: None,
|
|
|
|
start_column: None,
|
|
|
|
end_column: None,
|
|
|
|
frames: vec![
|
2020-03-02 17:20:16 -05:00
|
|
|
JSStackFrame {
|
|
|
|
line_number: 4,
|
2019-02-26 17:36:05 -05:00
|
|
|
column: 16,
|
|
|
|
script_name: "foo_bar.ts".to_string(),
|
|
|
|
function_name: "foo".to_string(),
|
|
|
|
is_eval: false,
|
|
|
|
is_constructor: false,
|
|
|
|
},
|
2020-03-02 17:20:16 -05:00
|
|
|
JSStackFrame {
|
|
|
|
line_number: 5,
|
2019-02-26 17:36:05 -05:00
|
|
|
column: 20,
|
|
|
|
script_name: "bar_baz.ts".to_string(),
|
|
|
|
function_name: "qat".to_string(),
|
|
|
|
is_eval: false,
|
|
|
|
is_constructor: false,
|
|
|
|
},
|
2020-03-02 17:20:16 -05:00
|
|
|
JSStackFrame {
|
|
|
|
line_number: 1,
|
2019-02-26 17:36:05 -05:00
|
|
|
column: 1,
|
|
|
|
script_name: "deno_main.js".to_string(),
|
|
|
|
function_name: "".to_string(),
|
|
|
|
is_eval: false,
|
|
|
|
is_constructor: false,
|
|
|
|
},
|
|
|
|
],
|
2020-03-02 17:20:16 -05:00
|
|
|
};
|
|
|
|
let actual = js_error.to_string();
|
2019-02-26 17:36:05 -05:00
|
|
|
let expected = "Error: foo bar\n at foo (foo_bar.ts:5:17)\n at qat (bar_baz.ts:6:21)\n at deno_main.js:2:2";
|
2020-03-02 17:20:16 -05:00
|
|
|
assert_eq!(actual, expected);
|
2019-02-26 17:36:05 -05:00
|
|
|
}
|
|
|
|
}
|