mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
Do not convert exceptions to JSON and back (#4214)
This commit is contained in:
parent
3fcbf8789e
commit
eafd40feab
14 changed files with 280 additions and 764 deletions
|
@ -3,11 +3,12 @@
|
|||
use crate::colors;
|
||||
use crate::source_maps::apply_source_map;
|
||||
use crate::source_maps::SourceMapGetter;
|
||||
use deno_core;
|
||||
use deno_core::ErrBox;
|
||||
use deno_core::StackFrame;
|
||||
use deno_core::V8Exception;
|
||||
use deno_core::JSStackFrame;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A trait which specifies parts of a diagnostic like item needs to be able to
|
||||
/// generate to conform its display to other diagnostic like items
|
||||
|
@ -21,37 +22,37 @@ pub trait DisplayFormatter {
|
|||
|
||||
fn format_source_name(
|
||||
script_name: String,
|
||||
line: i64,
|
||||
line_number: i64,
|
||||
column: i64,
|
||||
is_internal: bool,
|
||||
) -> String {
|
||||
let line = line + 1;
|
||||
let line_number = line_number + 1;
|
||||
let column = column + 1;
|
||||
if is_internal {
|
||||
format!("{}:{}:{}", script_name, line, column)
|
||||
format!("{}:{}:{}", script_name, line_number, column)
|
||||
} else {
|
||||
let script_name_c = colors::cyan(script_name);
|
||||
let line_c = colors::yellow(line.to_string());
|
||||
let line_c = colors::yellow(line_number.to_string());
|
||||
let column_c = colors::yellow(column.to_string());
|
||||
format!("{}:{}:{}", script_name_c, line_c, column_c)
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats optional source, line and column into a single string.
|
||||
/// Formats optional source, line number and column into a single string.
|
||||
pub fn format_maybe_source_name(
|
||||
script_name: Option<String>,
|
||||
line: Option<i64>,
|
||||
line_number: Option<i64>,
|
||||
column: Option<i64>,
|
||||
) -> String {
|
||||
if script_name.is_none() {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
assert!(line.is_some());
|
||||
assert!(line_number.is_some());
|
||||
assert!(column.is_some());
|
||||
format_source_name(
|
||||
script_name.unwrap(),
|
||||
line.unwrap(),
|
||||
line_number.unwrap(),
|
||||
column.unwrap(),
|
||||
false,
|
||||
)
|
||||
|
@ -80,11 +81,11 @@ pub fn format_maybe_source_line(
|
|||
|
||||
assert!(start_column.is_some());
|
||||
assert!(end_column.is_some());
|
||||
let line = (1 + line_number.unwrap()).to_string();
|
||||
let line_color = colors::black_on_white(line.to_string());
|
||||
let line_len = line.len();
|
||||
let line_number = (1 + line_number.unwrap()).to_string();
|
||||
let line_color = colors::black_on_white(line_number.to_string());
|
||||
let line_number_len = line_number.len();
|
||||
let line_padding =
|
||||
colors::black_on_white(format!("{:indent$}", "", indent = line_len))
|
||||
colors::black_on_white(format!("{:indent$}", "", indent = line_number_len))
|
||||
.to_string();
|
||||
let mut s = String::new();
|
||||
let start_column = start_column.unwrap();
|
||||
|
@ -124,7 +125,7 @@ pub fn format_error_message(msg: String) -> String {
|
|||
format!("{} {}", preamble, msg)
|
||||
}
|
||||
|
||||
fn format_stack_frame(frame: &StackFrame, is_internal_frame: bool) -> String {
|
||||
fn format_stack_frame(frame: &JSStackFrame, is_internal_frame: bool) -> String {
|
||||
// Note when we print to string, we change from 0-indexed to 1-indexed.
|
||||
let function_name = if is_internal_frame {
|
||||
colors::italic_bold_gray(frame.function_name.clone()).to_string()
|
||||
|
@ -133,7 +134,7 @@ fn format_stack_frame(frame: &StackFrame, is_internal_frame: bool) -> String {
|
|||
};
|
||||
let mut source_loc = format_source_name(
|
||||
frame.script_name.clone(),
|
||||
frame.line,
|
||||
frame.line_number,
|
||||
frame.column,
|
||||
is_internal_frame,
|
||||
);
|
||||
|
@ -160,37 +161,25 @@ fn format_stack_frame(frame: &StackFrame, is_internal_frame: bool) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Wrapper around V8Exception which provides color to_string.
|
||||
/// Wrapper around deno_core::JSError which provides color to_string.
|
||||
#[derive(Debug)]
|
||||
pub struct JSError(V8Exception);
|
||||
pub struct JSError(deno_core::JSError);
|
||||
|
||||
impl JSError {
|
||||
pub fn new(v8_exception: V8Exception) -> Self {
|
||||
Self(v8_exception)
|
||||
}
|
||||
|
||||
pub fn from_json(
|
||||
json_str: &str,
|
||||
pub fn create(
|
||||
core_js_error: deno_core::JSError,
|
||||
source_map_getter: &impl SourceMapGetter,
|
||||
) -> ErrBox {
|
||||
let unmapped_exception = V8Exception::from_json(json_str).unwrap();
|
||||
Self::from_v8_exception(unmapped_exception, source_map_getter)
|
||||
}
|
||||
|
||||
pub fn from_v8_exception(
|
||||
unmapped_exception: V8Exception,
|
||||
source_map_getter: &impl SourceMapGetter,
|
||||
) -> ErrBox {
|
||||
let mapped_exception =
|
||||
apply_source_map(&unmapped_exception, source_map_getter);
|
||||
let js_error = Self(mapped_exception);
|
||||
let core_js_error = apply_source_map(&core_js_error, source_map_getter);
|
||||
let js_error = Self(core_js_error);
|
||||
ErrBox::from(js_error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<V8Exception> for JSError {
|
||||
fn into(self) -> V8Exception {
|
||||
self.0
|
||||
impl Deref for JSError {
|
||||
type Target = deno_core::JSError;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -264,53 +253,46 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::colors::strip_ansi_codes;
|
||||
|
||||
fn error1() -> V8Exception {
|
||||
V8Exception {
|
||||
#[test]
|
||||
fn js_error_to_string() {
|
||||
let core_js_error = deno_core::JSError {
|
||||
message: "Error: foo bar".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![
|
||||
StackFrame {
|
||||
line: 4,
|
||||
JSStackFrame {
|
||||
line_number: 4,
|
||||
column: 16,
|
||||
script_name: "foo_bar.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 5,
|
||||
JSStackFrame {
|
||||
line_number: 5,
|
||||
column: 20,
|
||||
script_name: "bar_baz.ts".to_string(),
|
||||
function_name: "qat".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 1,
|
||||
JSStackFrame {
|
||||
line_number: 1,
|
||||
column: 1,
|
||||
script_name: "deno_main.js".to_string(),
|
||||
function_name: "".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_error_to_string() {
|
||||
let e = error1();
|
||||
assert_eq!("error: 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", strip_ansi_codes(&JSError(e).to_string()));
|
||||
};
|
||||
let formatted_error = JSError(core_js_error).to_string();
|
||||
let actual = strip_ansi_codes(&formatted_error);
|
||||
let expected = "error: 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";
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
import { DiagnosticItem } from "./diagnostics.ts";
|
||||
import { sendSync } from "./dispatch_json.ts";
|
||||
|
||||
// TODO(bartlomieju): move to `repl.ts`?
|
||||
export function formatError(errString: string): string {
|
||||
const res = sendSync("op_format_error", { error: errString });
|
||||
return res.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an array of diagnostic items and return them as a single string.
|
||||
* @param items An array of diagnostic items to format
|
||||
|
|
|
@ -96,7 +96,7 @@ declare global {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
evalContext(code: string): [any, EvalErrorInfo | null];
|
||||
|
||||
errorToJSON: (e: Error) => string;
|
||||
formatError: (e: Error) => string;
|
||||
}
|
||||
|
||||
// Only `var` variables show up in the `globalThis` type when doing a global
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { close } from "./files.ts";
|
||||
import { exit } from "./os.ts";
|
||||
import { core } from "./core.ts";
|
||||
import { formatError } from "./format_error.ts";
|
||||
import { stringifyArgs } from "./console.ts";
|
||||
import { sendSync, sendAsync } from "./dispatch_json.ts";
|
||||
|
||||
|
@ -89,9 +88,7 @@ function evaluate(code: string): boolean {
|
|||
} else {
|
||||
lastThrownError = errInfo.thrown;
|
||||
if (errInfo.isNativeError) {
|
||||
const formattedError = formatError(
|
||||
core.errorToJSON(errInfo.thrown as Error)
|
||||
);
|
||||
const formattedError = core.formatError(errInfo.thrown as Error);
|
||||
replError(formattedError);
|
||||
} else {
|
||||
replError("Thrown:", errInfo.thrown);
|
||||
|
@ -162,7 +159,7 @@ export async function replLoop(): Promise<void> {
|
|||
if (err.message !== "Interrupted") {
|
||||
// e.g. this happens when we have deno.close(3).
|
||||
// We want to display the problem.
|
||||
const formattedError = formatError(core.errorToJSON(err));
|
||||
const formattedError = core.formatError(err);
|
||||
replError(formattedError);
|
||||
}
|
||||
// Quit REPL anyways.
|
||||
|
@ -184,7 +181,7 @@ export async function replLoop(): Promise<void> {
|
|||
} else {
|
||||
// e.g. this happens when we have deno.close(3).
|
||||
// We want to display the problem.
|
||||
const formattedError = formatError(core.errorToJSON(err));
|
||||
const formattedError = core.formatError(err);
|
||||
replError(formattedError);
|
||||
quitRepl(1);
|
||||
}
|
||||
|
|
|
@ -3,19 +3,16 @@
|
|||
//! There are many types of errors in Deno:
|
||||
//! - ErrBox: a generic boxed object. This is the super type of all
|
||||
//! errors handled in Rust.
|
||||
//! - JSError: exceptions thrown from V8 into Rust. Usually a user exception.
|
||||
//! These are basically a big JSON structure which holds information about
|
||||
//! line numbers. We use this to pretty-print stack traces. These are
|
||||
//! never passed back into the runtime.
|
||||
//! - JSError: a container for the error message and stack trace for exceptions
|
||||
//! thrown in JavaScript code. We use this to pretty-print stack traces.
|
||||
//! - OpError: these are errors that happen during ops, which are passed
|
||||
//! back into the runtime, where an exception object is created and thrown.
|
||||
//! OpErrors have an integer code associated with them - access this via the `kind` field.
|
||||
//! OpErrors have an integer code associated with them - access this via the
|
||||
//! `kind` field.
|
||||
//! - Diagnostic: these are errors that originate in TypeScript's compiler.
|
||||
//! They're similar to JSError, in that they have line numbers.
|
||||
//! But Diagnostics are compile-time type errors, whereas JSErrors are runtime exceptions.
|
||||
//!
|
||||
//! TODO:
|
||||
//! - rename/merge JSError with V8Exception?
|
||||
//! But Diagnostics are compile-time type errors, whereas JSErrors are runtime
|
||||
//! exceptions.
|
||||
|
||||
use crate::import_map::ImportMapError;
|
||||
use deno_core::ErrBox;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
use super::dispatch_json::{Deserialize, JsonOp, Value};
|
||||
use crate::diagnostics::Diagnostic;
|
||||
use crate::fmt_errors::JSError;
|
||||
use crate::op_error::OpError;
|
||||
use crate::source_maps::get_orig_position;
|
||||
use crate::source_maps::CachedMaps;
|
||||
|
@ -14,32 +13,12 @@ pub fn init(i: &mut Isolate, s: &State) {
|
|||
"op_apply_source_map",
|
||||
s.stateful_json_op(op_apply_source_map),
|
||||
);
|
||||
i.register_op("op_format_error", s.stateful_json_op(op_format_error));
|
||||
i.register_op(
|
||||
"op_format_diagnostic",
|
||||
s.stateful_json_op(op_format_diagnostic),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FormatErrorArgs {
|
||||
error: String,
|
||||
}
|
||||
|
||||
fn op_format_error(
|
||||
state: &State,
|
||||
args: Value,
|
||||
_zero_copy: Option<ZeroCopyBuf>,
|
||||
) -> Result<JsonOp, OpError> {
|
||||
let args: FormatErrorArgs = serde_json::from_value(args)?;
|
||||
let error =
|
||||
JSError::from_json(&args.error, &state.borrow().global_state.ts_compiler);
|
||||
|
||||
Ok(JsonOp::Sync(json!({
|
||||
"error": error.to_string(),
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ApplySourceMap {
|
||||
filename: String,
|
||||
|
|
|
@ -215,15 +215,14 @@ fn serialize_worker_event(event: WorkerEvent) -> Value {
|
|||
}
|
||||
});
|
||||
|
||||
if let Ok(err) = error.downcast::<JSError>() {
|
||||
let exception: V8Exception = err.into();
|
||||
if let Ok(js_error) = error.downcast::<JSError>() {
|
||||
serialized_error = json!({
|
||||
"type": "error",
|
||||
"error": {
|
||||
"message": exception.message,
|
||||
"fileName": exception.script_resource_name,
|
||||
"lineNumber": exception.line_number,
|
||||
"columnNumber": exception.start_column,
|
||||
"message": js_error.message,
|
||||
"fileName": js_error.script_resource_name,
|
||||
"lineNumber": js_error.line_number,
|
||||
"columnNumber": js_error.start_column,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
//! This mod provides functions to remap a deno_core::V8Exception based on a source map
|
||||
use deno_core::StackFrame;
|
||||
use deno_core::V8Exception;
|
||||
//! This mod provides functions to remap a deno_core::deno_core::JSError based on a source map
|
||||
use deno_core;
|
||||
use deno_core::JSStackFrame;
|
||||
use serde_json;
|
||||
use source_map_mappings::parse_mappings;
|
||||
use source_map_mappings::Bias;
|
||||
|
@ -12,7 +12,11 @@ use std::str;
|
|||
pub trait SourceMapGetter {
|
||||
/// Returns the raw source map file.
|
||||
fn get_source_map(&self, script_name: &str) -> Option<Vec<u8>>;
|
||||
fn get_source_line(&self, script_name: &str, line: usize) -> Option<String>;
|
||||
fn get_source_line(
|
||||
&self,
|
||||
script_name: &str,
|
||||
line_number: usize,
|
||||
) -> Option<String>;
|
||||
}
|
||||
|
||||
/// Cached filename lookups. The key can be None if a previous lookup failed to
|
||||
|
@ -82,36 +86,36 @@ fn builtin_source_map(script_name: &str) -> Option<Vec<u8>> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Apply a source map to a V8Exception, returning a V8Exception where the filenames,
|
||||
/// the lines and the columns point to their original source location, not their
|
||||
/// transpiled location if applicable.
|
||||
/// Apply a source map to a deno_core::JSError, returning a JSError where file
|
||||
/// names and line/column numbers point to the location in the original source,
|
||||
/// rather than the transpiled source code.
|
||||
pub fn apply_source_map<G: SourceMapGetter>(
|
||||
v8_exception: &V8Exception,
|
||||
js_error: &deno_core::JSError,
|
||||
getter: &G,
|
||||
) -> V8Exception {
|
||||
) -> deno_core::JSError {
|
||||
let mut mappings_map: CachedMaps = HashMap::new();
|
||||
|
||||
let mut frames = Vec::<StackFrame>::new();
|
||||
for frame in &v8_exception.frames {
|
||||
let mut frames = Vec::<JSStackFrame>::new();
|
||||
for frame in &js_error.frames {
|
||||
let f = frame_apply_source_map(&frame, &mut mappings_map, getter);
|
||||
frames.push(f);
|
||||
}
|
||||
|
||||
let (script_resource_name, line_number, start_column) =
|
||||
get_maybe_orig_position(
|
||||
v8_exception.script_resource_name.clone(),
|
||||
v8_exception.line_number,
|
||||
v8_exception.start_column,
|
||||
js_error.script_resource_name.clone(),
|
||||
js_error.line_number,
|
||||
js_error.start_column,
|
||||
&mut mappings_map,
|
||||
getter,
|
||||
);
|
||||
// It is better to just move end_column to be the same distance away from
|
||||
// start column because sometimes the code point is not available in the
|
||||
// source file map.
|
||||
let end_column = match v8_exception.end_column {
|
||||
let end_column = match js_error.end_column {
|
||||
Some(ec) => {
|
||||
if let Some(sc) = start_column {
|
||||
Some(ec - (v8_exception.start_column.unwrap() - sc))
|
||||
Some(ec - (js_error.start_column.unwrap() - sc))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -122,74 +126,67 @@ pub fn apply_source_map<G: SourceMapGetter>(
|
|||
// will go fetch it from the getter
|
||||
let source_line = match line_number {
|
||||
Some(ln)
|
||||
if v8_exception.source_line.is_some()
|
||||
&& script_resource_name.is_some() =>
|
||||
if js_error.source_line.is_some() && script_resource_name.is_some() =>
|
||||
{
|
||||
getter.get_source_line(
|
||||
&v8_exception.script_resource_name.clone().unwrap(),
|
||||
&js_error.script_resource_name.clone().unwrap(),
|
||||
ln as usize,
|
||||
)
|
||||
}
|
||||
_ => v8_exception.source_line.clone(),
|
||||
_ => js_error.source_line.clone(),
|
||||
};
|
||||
|
||||
V8Exception {
|
||||
message: v8_exception.message.clone(),
|
||||
frames,
|
||||
error_level: v8_exception.error_level,
|
||||
deno_core::JSError {
|
||||
message: js_error.message.clone(),
|
||||
source_line,
|
||||
script_resource_name,
|
||||
line_number,
|
||||
start_column,
|
||||
end_column,
|
||||
// These are difficult to map to their original position and they are not
|
||||
// currently used in any output, so we don't remap them.
|
||||
start_position: v8_exception.start_position,
|
||||
end_position: v8_exception.end_position,
|
||||
frames,
|
||||
}
|
||||
}
|
||||
|
||||
fn frame_apply_source_map<G: SourceMapGetter>(
|
||||
frame: &StackFrame,
|
||||
frame: &JSStackFrame,
|
||||
mappings_map: &mut CachedMaps,
|
||||
getter: &G,
|
||||
) -> StackFrame {
|
||||
let (script_name, line, column) = get_orig_position(
|
||||
) -> JSStackFrame {
|
||||
let (script_name, line_number, column) = get_orig_position(
|
||||
frame.script_name.to_string(),
|
||||
frame.line,
|
||||
frame.line_number,
|
||||
frame.column,
|
||||
mappings_map,
|
||||
getter,
|
||||
);
|
||||
|
||||
StackFrame {
|
||||
JSStackFrame {
|
||||
script_name,
|
||||
function_name: frame.function_name.clone(),
|
||||
line,
|
||||
line_number,
|
||||
column,
|
||||
is_eval: frame.is_eval,
|
||||
is_constructor: frame.is_constructor,
|
||||
is_wasm: frame.is_wasm,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_maybe_orig_position<G: SourceMapGetter>(
|
||||
script_name: Option<String>,
|
||||
line: Option<i64>,
|
||||
line_number: Option<i64>,
|
||||
column: Option<i64>,
|
||||
mappings_map: &mut CachedMaps,
|
||||
getter: &G,
|
||||
) -> (Option<String>, Option<i64>, Option<i64>) {
|
||||
match (script_name, line, column) {
|
||||
match (script_name, line_number, column) {
|
||||
(Some(script_name_v), Some(line_v), Some(column_v)) => {
|
||||
let (script_name, line, column) = get_orig_position(
|
||||
let (script_name, line_number, column) = get_orig_position(
|
||||
script_name_v,
|
||||
line_v - 1,
|
||||
column_v,
|
||||
mappings_map,
|
||||
getter,
|
||||
);
|
||||
(Some(script_name), Some(line), Some(column))
|
||||
(Some(script_name), Some(line_number), Some(column))
|
||||
}
|
||||
_ => (None, None, None),
|
||||
}
|
||||
|
@ -197,18 +194,18 @@ fn get_maybe_orig_position<G: SourceMapGetter>(
|
|||
|
||||
pub fn get_orig_position<G: SourceMapGetter>(
|
||||
script_name: String,
|
||||
line: i64,
|
||||
line_number: i64,
|
||||
column: i64,
|
||||
mappings_map: &mut CachedMaps,
|
||||
getter: &G,
|
||||
) -> (String, i64, i64) {
|
||||
let maybe_sm = get_mappings(&script_name, mappings_map, getter);
|
||||
let default_pos = (script_name, line, column);
|
||||
let default_pos = (script_name, line_number, column);
|
||||
|
||||
match maybe_sm {
|
||||
None => default_pos,
|
||||
Some(sm) => match sm.mappings.original_location_for(
|
||||
line as u32,
|
||||
line_number as u32,
|
||||
column as u32,
|
||||
Bias::default(),
|
||||
) {
|
||||
|
@ -274,7 +271,7 @@ mod tests {
|
|||
fn get_source_line(
|
||||
&self,
|
||||
script_name: &str,
|
||||
line: usize,
|
||||
line_number: usize,
|
||||
) -> Option<String> {
|
||||
let s = match script_name {
|
||||
"foo_bar.ts" => vec![
|
||||
|
@ -286,99 +283,83 @@ mod tests {
|
|||
],
|
||||
_ => return None,
|
||||
};
|
||||
if s.len() > line {
|
||||
Some(s[line].to_string())
|
||||
if s.len() > line_number {
|
||||
Some(s[line_number].to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn error1() -> V8Exception {
|
||||
V8Exception {
|
||||
#[test]
|
||||
fn apply_source_map_1() {
|
||||
let core_js_error = deno_core::JSError {
|
||||
message: "Error: foo bar".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![
|
||||
StackFrame {
|
||||
line: 4,
|
||||
JSStackFrame {
|
||||
line_number: 4,
|
||||
column: 16,
|
||||
script_name: "foo_bar.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 5,
|
||||
JSStackFrame {
|
||||
line_number: 5,
|
||||
column: 20,
|
||||
script_name: "bar_baz.ts".to_string(),
|
||||
function_name: "qat".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 1,
|
||||
JSStackFrame {
|
||||
line_number: 1,
|
||||
column: 1,
|
||||
script_name: "deno_main.js".to_string(),
|
||||
function_name: "".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_exception_apply_source_map_1() {
|
||||
let e = error1();
|
||||
};
|
||||
let getter = MockSourceMapGetter {};
|
||||
let actual = apply_source_map(&e, &getter);
|
||||
let expected = V8Exception {
|
||||
let actual = apply_source_map(&core_js_error, &getter);
|
||||
let expected = deno_core::JSError {
|
||||
message: "Error: foo bar".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![
|
||||
StackFrame {
|
||||
line: 5,
|
||||
JSStackFrame {
|
||||
line_number: 5,
|
||||
column: 12,
|
||||
script_name: "foo_bar.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 4,
|
||||
JSStackFrame {
|
||||
line_number: 4,
|
||||
column: 14,
|
||||
script_name: "bar_baz.ts".to_string(),
|
||||
function_name: "qat".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 1,
|
||||
JSStackFrame {
|
||||
line_number: 1,
|
||||
column: 1,
|
||||
script_name: "deno_main.js".to_string(),
|
||||
function_name: "".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -386,25 +367,21 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn v8_exception_apply_source_map_2() {
|
||||
let e = V8Exception {
|
||||
fn apply_source_map_2() {
|
||||
let e = deno_core::JSError {
|
||||
message: "TypeError: baz".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![StackFrame {
|
||||
line: 11,
|
||||
frames: vec![JSStackFrame {
|
||||
line_number: 11,
|
||||
column: 12,
|
||||
script_name: "CLI_SNAPSHOT.js".to_string(),
|
||||
function_name: "setLogDebug".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
}],
|
||||
};
|
||||
let getter = MockSourceMapGetter {};
|
||||
|
@ -416,15 +393,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn v8_exception_apply_source_map_line() {
|
||||
let e = V8Exception {
|
||||
fn apply_source_map_line() {
|
||||
let e = deno_core::JSError {
|
||||
message: "TypeError: baz".to_string(),
|
||||
source_line: Some("foo".to_string()),
|
||||
script_resource_name: Some("foo_bar.ts".to_string()),
|
||||
line_number: Some(4),
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: Some(16),
|
||||
end_column: None,
|
||||
frames: vec![],
|
||||
|
|
|
@ -105,8 +105,8 @@ impl Worker {
|
|||
let mut isolate = deno_core::EsIsolate::new(loader, startup_data, false);
|
||||
|
||||
let global_state_ = state.borrow().global_state.clone();
|
||||
isolate.set_js_error_create(move |v8_exception| {
|
||||
JSError::from_v8_exception(v8_exception, &global_state_.ts_compiler)
|
||||
isolate.set_js_error_create_fn(move |core_js_error| {
|
||||
JSError::create(core_js_error, &global_state_.ts_compiler)
|
||||
});
|
||||
|
||||
let (internal_channels, external_channels) = create_channels();
|
||||
|
|
208
core/bindings.rs
208
core/bindings.rs
|
@ -3,6 +3,7 @@
|
|||
use crate::es_isolate::EsIsolate;
|
||||
use crate::isolate::Isolate;
|
||||
use crate::isolate::ZeroCopyBuf;
|
||||
use crate::js_errors::JSError;
|
||||
|
||||
use rusty_v8 as v8;
|
||||
use v8::MapFnTo;
|
||||
|
@ -26,7 +27,7 @@ lazy_static! {
|
|||
function: eval_context.map_fn_to()
|
||||
},
|
||||
v8::ExternalReference {
|
||||
function: error_to_json.map_fn_to()
|
||||
function: format_error.map_fn_to()
|
||||
},
|
||||
v8::ExternalReference {
|
||||
getter: shared_getter.map_fn_to()
|
||||
|
@ -146,13 +147,13 @@ pub fn initialize_context<'s>(
|
|||
eval_context_val.into(),
|
||||
);
|
||||
|
||||
let mut error_to_json_tmpl = v8::FunctionTemplate::new(scope, error_to_json);
|
||||
let error_to_json_val =
|
||||
error_to_json_tmpl.get_function(scope, context).unwrap();
|
||||
let mut format_error_tmpl = v8::FunctionTemplate::new(scope, format_error);
|
||||
let format_error_val =
|
||||
format_error_tmpl.get_function(scope, context).unwrap();
|
||||
core_val.set(
|
||||
context,
|
||||
v8::String::new(scope, "errorToJSON").unwrap().into(),
|
||||
error_to_json_val.into(),
|
||||
v8::String::new(scope, "formatError").unwrap().into(),
|
||||
format_error_val.into(),
|
||||
);
|
||||
|
||||
core_val.set_accessor(
|
||||
|
@ -536,20 +537,18 @@ fn eval_context(
|
|||
rv.set(output.into());
|
||||
}
|
||||
|
||||
fn error_to_json(
|
||||
fn format_error(
|
||||
scope: v8::FunctionCallbackScope,
|
||||
args: v8::FunctionCallbackArguments,
|
||||
mut rv: v8::ReturnValue,
|
||||
) {
|
||||
let deno_isolate: &mut Isolate =
|
||||
unsafe { &mut *(scope.isolate().get_data(0) as *mut Isolate) };
|
||||
let context = deno_isolate.global_context.get(scope).unwrap();
|
||||
|
||||
let message = v8::Exception::create_message(scope, args.get(0));
|
||||
let json_obj = encode_message_as_object(scope, message);
|
||||
let json_string = v8::json::stringify(context, json_obj.into()).unwrap();
|
||||
|
||||
rv.set(json_string.into());
|
||||
let e = JSError::from_v8_exception(scope, args.get(0));
|
||||
let e = (deno_isolate.js_error_create_fn)(e);
|
||||
let e = e.to_string();
|
||||
let e = v8::String::new(scope, &e).unwrap();
|
||||
rv.set(e.into())
|
||||
}
|
||||
|
||||
fn queue_microtask(
|
||||
|
@ -638,184 +637,3 @@ pub fn module_resolve_callback<'s>(
|
|||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn encode_message_as_object<'a>(
|
||||
s: &mut impl v8::ToLocal<'a>,
|
||||
message: v8::Local<v8::Message>,
|
||||
) -> v8::Local<'a, v8::Object> {
|
||||
let context = s.get_current_context().unwrap();
|
||||
let json_obj = v8::Object::new(s);
|
||||
|
||||
let exception_str = message.get(s);
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "message").unwrap().into(),
|
||||
exception_str.into(),
|
||||
);
|
||||
|
||||
let script_resource_name = message
|
||||
.get_script_resource_name(s)
|
||||
.expect("Missing ScriptResourceName");
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "scriptResourceName").unwrap().into(),
|
||||
script_resource_name,
|
||||
);
|
||||
|
||||
let source_line = message
|
||||
.get_source_line(s, context)
|
||||
.expect("Missing SourceLine");
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "sourceLine").unwrap().into(),
|
||||
source_line.into(),
|
||||
);
|
||||
|
||||
let line_number = message
|
||||
.get_line_number(context)
|
||||
.expect("Missing LineNumber");
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "lineNumber").unwrap().into(),
|
||||
v8::Integer::new(s, line_number as i32).into(),
|
||||
);
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "startPosition").unwrap().into(),
|
||||
v8::Integer::new(s, message.get_start_position() as i32).into(),
|
||||
);
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "endPosition").unwrap().into(),
|
||||
v8::Integer::new(s, message.get_end_position() as i32).into(),
|
||||
);
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "errorLevel").unwrap().into(),
|
||||
v8::Integer::new(s, message.error_level() as i32).into(),
|
||||
);
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "startColumn").unwrap().into(),
|
||||
v8::Integer::new(s, message.get_start_column() as i32).into(),
|
||||
);
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "endColumn").unwrap().into(),
|
||||
v8::Integer::new(s, message.get_end_column() as i32).into(),
|
||||
);
|
||||
|
||||
let is_shared_cross_origin =
|
||||
v8::Boolean::new(s, message.is_shared_cross_origin());
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "isSharedCrossOrigin").unwrap().into(),
|
||||
is_shared_cross_origin.into(),
|
||||
);
|
||||
|
||||
let is_opaque = v8::Boolean::new(s, message.is_opaque());
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "isOpaque").unwrap().into(),
|
||||
is_opaque.into(),
|
||||
);
|
||||
|
||||
let frames = if let Some(stack_trace) = message.get_stack_trace(s) {
|
||||
let count = stack_trace.get_frame_count() as i32;
|
||||
let frames = v8::Array::new(s, count);
|
||||
|
||||
for i in 0..count {
|
||||
let frame = stack_trace
|
||||
.get_frame(s, i as usize)
|
||||
.expect("No frame found");
|
||||
let frame_obj = v8::Object::new(s);
|
||||
frames.set(context, v8::Integer::new(s, i).into(), frame_obj.into());
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "line").unwrap().into(),
|
||||
v8::Integer::new(s, frame.get_line_number() as i32).into(),
|
||||
);
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "column").unwrap().into(),
|
||||
v8::Integer::new(s, frame.get_column() as i32).into(),
|
||||
);
|
||||
|
||||
if let Some(function_name) = frame.get_function_name(s) {
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "functionName").unwrap().into(),
|
||||
function_name.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let script_name = match frame.get_script_name_or_source_url(s) {
|
||||
Some(name) => name,
|
||||
None => v8::String::new(s, "<unknown>").unwrap(),
|
||||
};
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "scriptName").unwrap().into(),
|
||||
script_name.into(),
|
||||
);
|
||||
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "isEval").unwrap().into(),
|
||||
v8::Boolean::new(s, frame.is_eval()).into(),
|
||||
);
|
||||
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "isConstructor").unwrap().into(),
|
||||
v8::Boolean::new(s, frame.is_constructor()).into(),
|
||||
);
|
||||
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "isWasm").unwrap().into(),
|
||||
v8::Boolean::new(s, frame.is_wasm()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
frames
|
||||
} else {
|
||||
// No stack trace. We only have one stack frame of info..
|
||||
let frames = v8::Array::new(s, 1);
|
||||
let frame_obj = v8::Object::new(s);
|
||||
frames.set(context, v8::Integer::new(s, 0).into(), frame_obj.into());
|
||||
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "scriptResourceName").unwrap().into(),
|
||||
script_resource_name,
|
||||
);
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "line").unwrap().into(),
|
||||
v8::Integer::new(s, line_number as i32).into(),
|
||||
);
|
||||
frame_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "column").unwrap().into(),
|
||||
v8::Integer::new(s, message.get_start_column() as i32).into(),
|
||||
);
|
||||
|
||||
frames
|
||||
};
|
||||
|
||||
json_obj.set(
|
||||
context,
|
||||
v8::String::new(s, "frames").unwrap().into(),
|
||||
frames.into(),
|
||||
);
|
||||
|
||||
json_obj
|
||||
}
|
||||
|
|
|
@ -173,8 +173,8 @@ impl EsIsolate {
|
|||
/// Instantiates a ES module
|
||||
///
|
||||
/// ErrBox can be downcast to a type that exposes additional information about
|
||||
/// the V8 exception. By default this type is CoreJSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create() has been used.
|
||||
/// the V8 exception. By default this type is JSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create_fn() has been used.
|
||||
fn mod_instantiate(&mut self, id: ModuleId) -> Result<(), ErrBox> {
|
||||
let v8_isolate = self.core_isolate.v8_isolate.as_mut().unwrap();
|
||||
let js_error_create_fn = &*self.core_isolate.js_error_create_fn;
|
||||
|
@ -218,8 +218,8 @@ impl EsIsolate {
|
|||
/// 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 CoreJSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create() has been used.
|
||||
/// the V8 exception. By default this type is JSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create_fn() has been used.
|
||||
pub fn mod_evaluate(&mut self, id: ModuleId) -> Result<(), ErrBox> {
|
||||
let core_isolate = &mut self.core_isolate;
|
||||
let v8_isolate = core_isolate.v8_isolate.as_mut().unwrap();
|
||||
|
|
|
@ -9,8 +9,7 @@ use rusty_v8 as v8;
|
|||
|
||||
use crate::any_error::ErrBox;
|
||||
use crate::bindings;
|
||||
use crate::js_errors::CoreJSError;
|
||||
use crate::js_errors::V8Exception;
|
||||
use crate::js_errors::JSError;
|
||||
use crate::ops::*;
|
||||
use crate::shared_queue::SharedQueue;
|
||||
use crate::shared_queue::RECOMMENDED_SIZE;
|
||||
|
@ -147,7 +146,7 @@ pub enum StartupData<'a> {
|
|||
None,
|
||||
}
|
||||
|
||||
type JSErrorCreateFn = dyn Fn(V8Exception) -> ErrBox;
|
||||
type JSErrorCreateFn = dyn Fn(JSError) -> ErrBox;
|
||||
type IsolateErrorHandleFn = dyn FnMut(ErrBox) -> Result<(), ErrBox>;
|
||||
|
||||
/// A single execution context of JavaScript. Corresponds roughly to the "Web
|
||||
|
@ -169,7 +168,7 @@ pub struct Isolate {
|
|||
pub(crate) js_recv_cb: v8::Global<v8::Function>,
|
||||
pub(crate) pending_promise_exceptions: HashMap<i32, v8::Global<v8::Value>>,
|
||||
shared_isolate_handle: Arc<Mutex<Option<*mut v8::Isolate>>>,
|
||||
pub(crate) js_error_create_fn: Arc<JSErrorCreateFn>,
|
||||
pub(crate) js_error_create_fn: Box<JSErrorCreateFn>,
|
||||
needs_init: bool,
|
||||
pub(crate) shared: SharedQueue,
|
||||
pending_ops: FuturesUnordered<PendingOpFuture>,
|
||||
|
@ -304,7 +303,7 @@ impl Isolate {
|
|||
snapshot: load_snapshot,
|
||||
has_snapshotted: false,
|
||||
shared_isolate_handle: Arc::new(Mutex::new(None)),
|
||||
js_error_create_fn: Arc::new(CoreJSError::from_v8_exception),
|
||||
js_error_create_fn: Box::new(JSError::create),
|
||||
shared,
|
||||
needs_init,
|
||||
pending_ops: FuturesUnordered::new(),
|
||||
|
@ -349,13 +348,13 @@ impl Isolate {
|
|||
}
|
||||
|
||||
/// Allows a callback to be set whenever a V8 exception is made. This allows
|
||||
/// the caller to wrap the V8Exception into an error. By default this callback
|
||||
/// is set to CoreJSError::from_v8_exception.
|
||||
pub fn set_js_error_create<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(V8Exception) -> ErrBox + 'static,
|
||||
{
|
||||
self.js_error_create_fn = Arc::new(f);
|
||||
/// the caller to wrap the JSError into an error. By default this callback
|
||||
/// is set to JSError::create.
|
||||
pub fn set_js_error_create_fn(
|
||||
&mut self,
|
||||
f: impl Fn(JSError) -> ErrBox + 'static,
|
||||
) {
|
||||
self.js_error_create_fn = Box::new(f);
|
||||
}
|
||||
|
||||
/// Executes a bit of built-in JavaScript to provide Deno.sharedQueue.
|
||||
|
@ -418,8 +417,8 @@ impl Isolate {
|
|||
/// 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 CoreJSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create() has been used.
|
||||
/// the V8 exception. By default this type is JSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create_fn() has been used.
|
||||
pub fn execute(
|
||||
&mut self,
|
||||
js_filename: &str,
|
||||
|
@ -460,8 +459,8 @@ impl Isolate {
|
|||
/// set to true.
|
||||
///
|
||||
/// ErrBox can be downcast to a type that exposes additional information about
|
||||
/// the V8 exception. By default this type is CoreJSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create() has been used.
|
||||
/// the V8 exception. By default this type is JSError, however it may be a
|
||||
/// different type if Isolate::set_js_error_create_fn() has been used.
|
||||
pub fn snapshot(&mut self) -> v8::OwnedStartupData {
|
||||
assert!(self.snapshot_creator.is_some());
|
||||
|
||||
|
@ -612,16 +611,11 @@ pub(crate) fn attach_handle_to_error(
|
|||
ErrWithV8Handle::new(scope, err, handle).into()
|
||||
}
|
||||
|
||||
pub(crate) fn exception_to_err_result<'a, T>(
|
||||
scope: &mut impl v8::ToLocal<'a>,
|
||||
pub(crate) fn exception_to_err_result<'s, T>(
|
||||
scope: &mut impl v8::ToLocal<'s>,
|
||||
exception: v8::Local<v8::Value>,
|
||||
js_error_create_fn: &JSErrorCreateFn,
|
||||
) -> Result<T, ErrBox> {
|
||||
// Use a HandleScope because the functions below create a lot of
|
||||
// local handles (in particular, `encode_message_as_json()` does).
|
||||
let mut hs = v8::HandleScope::new(scope);
|
||||
let scope = hs.enter();
|
||||
|
||||
// TODO(piscisaureus): in rusty_v8, `is_execution_terminating()` should
|
||||
// also be implemented on `struct Isolate`.
|
||||
let is_terminating_exception = scope
|
||||
|
@ -642,18 +636,13 @@ pub(crate) fn exception_to_err_result<'a, T>(
|
|||
|
||||
// Maybe make a new exception object.
|
||||
if exception.is_null_or_undefined() {
|
||||
let exception_str =
|
||||
v8::String::new(scope, "execution terminated").unwrap();
|
||||
exception = v8::Exception::error(scope, exception_str);
|
||||
let message = v8::String::new(scope, "execution terminated").unwrap();
|
||||
exception = v8::Exception::error(scope, message);
|
||||
}
|
||||
}
|
||||
|
||||
let message = v8::Exception::create_message(scope, exception);
|
||||
// TODO(piscisaureus): don't encode the message as json first and then
|
||||
// immediately parse it after.
|
||||
let exception_json_str = encode_message_as_json(scope, message);
|
||||
let v8_exception = V8Exception::from_json(&exception_json_str).unwrap();
|
||||
let js_error = (js_error_create_fn)(v8_exception);
|
||||
let js_error = JSError::from_v8_exception(scope, exception);
|
||||
let js_error = (js_error_create_fn)(js_error);
|
||||
|
||||
if is_terminating_exception {
|
||||
// Re-enable exception termination.
|
||||
|
@ -679,16 +668,6 @@ fn check_promise_exceptions<'s>(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn encode_message_as_json<'a>(
|
||||
scope: &mut impl v8::ToLocal<'a>,
|
||||
message: v8::Local<v8::Message>,
|
||||
) -> String {
|
||||
let context = scope.get_current_context().unwrap();
|
||||
let json_obj = bindings::encode_message_as_object(scope, message);
|
||||
let json_string = v8::json::stringify(context, json_obj.into()).unwrap();
|
||||
json_string.to_rust_string_lossy(scope)
|
||||
}
|
||||
|
||||
pub fn js_check<T>(r: Result<T, ErrBox>) -> T {
|
||||
if let Err(e) = r {
|
||||
panic!(e.to_string());
|
||||
|
|
|
@ -9,200 +9,119 @@
|
|||
// console.log(err.stack);
|
||||
// It would require calling into Rust from Error.prototype.prepareStackTrace.
|
||||
|
||||
use crate::any_error::ErrBox;
|
||||
use serde_json;
|
||||
use serde_json::value::Value;
|
||||
use crate::ErrBox;
|
||||
use rusty_v8 as v8;
|
||||
use std::convert::TryFrom;
|
||||
use std::convert::TryInto;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
/// 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>,
|
||||
pub end_column: Option<i64>,
|
||||
pub frames: Vec<JSStackFrame>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct StackFrame {
|
||||
pub line: i64, // zero indexed
|
||||
pub column: i64, // zero indexed
|
||||
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,
|
||||
pub is_wasm: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct V8Exception {
|
||||
pub message: String,
|
||||
impl JSError {
|
||||
pub(crate) fn create(js_error: Self) -> ErrBox {
|
||||
ErrBox::from(js_error)
|
||||
}
|
||||
|
||||
pub source_line: Option<String>,
|
||||
pub script_resource_name: Option<String>,
|
||||
pub line_number: Option<i64>,
|
||||
pub start_position: Option<i64>,
|
||||
pub end_position: Option<i64>,
|
||||
pub error_level: Option<i64>,
|
||||
pub start_column: Option<i64>,
|
||||
pub end_column: Option<i64>,
|
||||
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();
|
||||
|
||||
pub frames: Vec<StackFrame>,
|
||||
}
|
||||
let msg = v8::Exception::create_message(scope, exception);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct CoreJSError(V8Exception);
|
||||
|
||||
impl StackFrame {
|
||||
// TODO Maybe use serde_derive?
|
||||
fn from_json_value(v: &serde_json::Value) -> Option<Self> {
|
||||
if !v.is_object() {
|
||||
return None;
|
||||
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),
|
||||
}
|
||||
let obj = v.as_object().unwrap();
|
||||
|
||||
let line_v = &obj["line"];
|
||||
if !line_v.is_u64() {
|
||||
return None;
|
||||
}
|
||||
let line = line_v.as_u64().unwrap() as i64;
|
||||
|
||||
let column_v = &obj["column"];
|
||||
if !column_v.is_u64() {
|
||||
return None;
|
||||
}
|
||||
let column = column_v.as_u64().unwrap() as i64;
|
||||
|
||||
let script_name_v = &obj["scriptName"];
|
||||
if !script_name_v.is_string() {
|
||||
return None;
|
||||
}
|
||||
let script_name = String::from(script_name_v.as_str().unwrap());
|
||||
|
||||
// Optional fields. See EncodeExceptionAsJSON() in libdeno.
|
||||
// Sometimes V8 doesn't provide all the frame information.
|
||||
|
||||
let mut function_name = String::from(""); // default
|
||||
if obj.contains_key("functionName") {
|
||||
let function_name_v = &obj["functionName"];
|
||||
if function_name_v.is_string() {
|
||||
function_name = String::from(function_name_v.as_str().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_eval = false; // default
|
||||
if obj.contains_key("isEval") {
|
||||
let is_eval_v = &obj["isEval"];
|
||||
if is_eval_v.is_boolean() {
|
||||
is_eval = is_eval_v.as_bool().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_constructor = false; // default
|
||||
if obj.contains_key("isConstructor") {
|
||||
let is_constructor_v = &obj["isConstructor"];
|
||||
if is_constructor_v.is_boolean() {
|
||||
is_constructor = is_constructor_v.as_bool().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let mut is_wasm = false; // default
|
||||
if obj.contains_key("isWasm") {
|
||||
let is_wasm_v = &obj["isWasm"];
|
||||
if is_wasm_v.is_boolean() {
|
||||
is_wasm = is_wasm_v.as_bool().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Some(StackFrame {
|
||||
line: line - 1,
|
||||
column: column - 1,
|
||||
script_name,
|
||||
function_name,
|
||||
is_eval,
|
||||
is_constructor,
|
||||
is_wasm,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl V8Exception {
|
||||
/// Creates a new V8Exception by parsing the raw exception JSON string from V8.
|
||||
pub fn from_json(json_str: &str) -> Option<Self> {
|
||||
let v = serde_json::from_str::<serde_json::Value>(json_str);
|
||||
if let Err(err) = v {
|
||||
eprintln!("V8Exception::from_json got problem {}", err);
|
||||
return None;
|
||||
}
|
||||
let v = v.unwrap();
|
||||
Self::from_json_value(v)
|
||||
}
|
||||
impl Error for JSError {}
|
||||
|
||||
pub fn from_json_value(v: serde_json::Value) -> Option<Self> {
|
||||
if !v.is_object() {
|
||||
return None;
|
||||
}
|
||||
let obj = v.as_object().unwrap();
|
||||
|
||||
let message_v = &obj["message"];
|
||||
if !message_v.is_string() {
|
||||
return None;
|
||||
}
|
||||
let message = String::from(message_v.as_str().unwrap());
|
||||
|
||||
let source_line = obj
|
||||
.get("sourceLine")
|
||||
.and_then(|v| v.as_str().map(String::from));
|
||||
let script_resource_name = obj
|
||||
.get("scriptResourceName")
|
||||
.and_then(|v| v.as_str().map(String::from));
|
||||
let line_number = obj.get("lineNumber").and_then(Value::as_i64);
|
||||
let start_position = obj.get("startPosition").and_then(Value::as_i64);
|
||||
let end_position = obj.get("endPosition").and_then(Value::as_i64);
|
||||
let error_level = obj.get("errorLevel").and_then(Value::as_i64);
|
||||
let start_column = obj.get("startColumn").and_then(Value::as_i64);
|
||||
let end_column = obj.get("endColumn").and_then(Value::as_i64);
|
||||
|
||||
let frames_v = &obj["frames"];
|
||||
if !frames_v.is_array() {
|
||||
return None;
|
||||
}
|
||||
let frame_values = frames_v.as_array().unwrap();
|
||||
|
||||
let mut frames = Vec::<StackFrame>::new();
|
||||
for frame_v in frame_values {
|
||||
match StackFrame::from_json_value(frame_v) {
|
||||
None => return None,
|
||||
Some(frame) => frames.push(frame),
|
||||
}
|
||||
}
|
||||
|
||||
Some(V8Exception {
|
||||
message,
|
||||
source_line,
|
||||
script_resource_name,
|
||||
line_number,
|
||||
start_position,
|
||||
end_position,
|
||||
error_level,
|
||||
start_column,
|
||||
end_column,
|
||||
frames,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreJSError {
|
||||
pub fn from_v8_exception(v8_exception: V8Exception) -> ErrBox {
|
||||
let error = Self(v8_exception);
|
||||
ErrBox::from(error)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_source_loc(script_name: &str, line: i64, column: i64) -> String {
|
||||
fn format_source_loc(
|
||||
script_name: &str,
|
||||
line_number: i64,
|
||||
column: i64,
|
||||
) -> String {
|
||||
// TODO match this style with how typescript displays errors.
|
||||
let line = line + 1;
|
||||
let line_number = line_number + 1;
|
||||
let column = column + 1;
|
||||
format!("{}:{}:{}", script_name, line, column)
|
||||
format!("{}:{}:{}", script_name, line_number, column)
|
||||
}
|
||||
|
||||
fn format_stack_frame(frame: &StackFrame) -> String {
|
||||
fn format_stack_frame(frame: &JSStackFrame) -> String {
|
||||
// Note when we print to string, we change from 0-indexed to 1-indexed.
|
||||
let source_loc =
|
||||
format_source_loc(&frame.script_name, frame.line, frame.column);
|
||||
format_source_loc(&frame.script_name, frame.line_number, frame.column);
|
||||
|
||||
if !frame.function_name.is_empty() {
|
||||
format!(" at {} ({})", frame.function_name, source_loc)
|
||||
|
@ -213,25 +132,25 @@ fn format_stack_frame(frame: &StackFrame) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CoreJSError {
|
||||
impl fmt::Display for JSError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.0.script_resource_name.is_some() {
|
||||
let script_resource_name = self.0.script_resource_name.as_ref().unwrap();
|
||||
if self.0.line_number.is_some() && self.0.start_column.is_some() {
|
||||
assert!(self.0.line_number.is_some());
|
||||
assert!(self.0.start_column.is_some());
|
||||
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());
|
||||
let source_loc = format_source_loc(
|
||||
script_resource_name,
|
||||
self.0.line_number.unwrap() - 1,
|
||||
self.0.start_column.unwrap() - 1,
|
||||
self.line_number.unwrap() - 1,
|
||||
self.start_column.unwrap() - 1,
|
||||
);
|
||||
write!(f, "{}", source_loc)?;
|
||||
}
|
||||
if self.0.source_line.is_some() {
|
||||
write!(f, "\n{}\n", self.0.source_line.as_ref().unwrap())?;
|
||||
if self.source_line.is_some() {
|
||||
write!(f, "\n{}\n", self.source_line.as_ref().unwrap())?;
|
||||
let mut s = String::new();
|
||||
for i in 0..self.0.end_column.unwrap() {
|
||||
if i >= self.0.start_column.unwrap() {
|
||||
for i in 0..self.end_column.unwrap() {
|
||||
if i >= self.start_column.unwrap() {
|
||||
s.push('^');
|
||||
} else {
|
||||
s.push(' ');
|
||||
|
@ -241,177 +160,57 @@ impl fmt::Display for CoreJSError {
|
|||
}
|
||||
}
|
||||
|
||||
write!(f, "{}", self.0.message)?;
|
||||
write!(f, "{}", self.message)?;
|
||||
|
||||
for frame in &self.0.frames {
|
||||
for frame in &self.frames {
|
||||
write!(f, "\n{}", format_stack_frame(frame))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for CoreJSError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn error1() -> V8Exception {
|
||||
V8Exception {
|
||||
#[test]
|
||||
fn js_error_to_string() {
|
||||
let js_error = JSError {
|
||||
message: "Error: foo bar".to_string(),
|
||||
source_line: None,
|
||||
script_resource_name: None,
|
||||
line_number: None,
|
||||
start_position: None,
|
||||
end_position: None,
|
||||
error_level: None,
|
||||
start_column: None,
|
||||
end_column: None,
|
||||
frames: vec![
|
||||
StackFrame {
|
||||
line: 4,
|
||||
JSStackFrame {
|
||||
line_number: 4,
|
||||
column: 16,
|
||||
script_name: "foo_bar.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 5,
|
||||
JSStackFrame {
|
||||
line_number: 5,
|
||||
column: 20,
|
||||
script_name: "bar_baz.ts".to_string(),
|
||||
function_name: "qat".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
StackFrame {
|
||||
line: 1,
|
||||
JSStackFrame {
|
||||
line_number: 1,
|
||||
column: 1,
|
||||
script_name: "deno_main.js".to_string(),
|
||||
function_name: "".to_string(),
|
||||
is_eval: false,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_frame_from_json_value_1() {
|
||||
let v = serde_json::from_str::<serde_json::Value>(
|
||||
r#"{
|
||||
"line":2,
|
||||
"column":11,
|
||||
"functionName":"foo",
|
||||
"scriptName":"/Users/rld/src/deno/cli/tests/error_001.ts",
|
||||
"isEval":true,
|
||||
"isConstructor":false,
|
||||
"isWasm":false
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let r = StackFrame::from_json_value(&v);
|
||||
assert_eq!(
|
||||
r,
|
||||
Some(StackFrame {
|
||||
line: 1,
|
||||
column: 10,
|
||||
script_name: "/Users/rld/src/deno/cli/tests/error_001.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: true,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stack_frame_from_json_value_2() {
|
||||
let v = serde_json::from_str::<serde_json::Value>(
|
||||
r#"{
|
||||
"scriptName": "/Users/rld/src/deno/cli/tests/error_001.ts",
|
||||
"line": 2,
|
||||
"column": 11
|
||||
}"#,
|
||||
)
|
||||
.unwrap();
|
||||
let r = StackFrame::from_json_value(&v);
|
||||
assert!(r.is_some());
|
||||
let f = r.unwrap();
|
||||
assert_eq!(f.line, 1);
|
||||
assert_eq!(f.column, 10);
|
||||
assert_eq!(f.script_name, "/Users/rld/src/deno/cli/tests/error_001.ts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_exception_from_json() {
|
||||
let r = V8Exception::from_json(
|
||||
r#"{
|
||||
"message":"Uncaught Error: bad",
|
||||
"frames":[
|
||||
{
|
||||
"line":2,
|
||||
"column":11,
|
||||
"functionName":"foo",
|
||||
"scriptName":"/Users/rld/src/deno/cli/tests/error_001.ts",
|
||||
"isEval":true,
|
||||
"isConstructor":false,
|
||||
"isWasm":false
|
||||
}, {
|
||||
"line":5,
|
||||
"column":5,
|
||||
"functionName":"bar",
|
||||
"scriptName":"/Users/rld/src/deno/cli/tests/error_001.ts",
|
||||
"isEval":true,
|
||||
"isConstructor":false,
|
||||
"isWasm":false
|
||||
}
|
||||
]}"#,
|
||||
);
|
||||
assert!(r.is_some());
|
||||
let e = r.unwrap();
|
||||
assert_eq!(e.message, "Uncaught Error: bad");
|
||||
assert_eq!(e.frames.len(), 2);
|
||||
assert_eq!(
|
||||
e.frames[0],
|
||||
StackFrame {
|
||||
line: 1,
|
||||
column: 10,
|
||||
script_name: "/Users/rld/src/deno/cli/tests/error_001.ts".to_string(),
|
||||
function_name: "foo".to_string(),
|
||||
is_eval: true,
|
||||
is_constructor: false,
|
||||
is_wasm: false,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn v8_exception_from_json_2() {
|
||||
let r = V8Exception::from_json(
|
||||
"{\"message\":\"Error: boo\",\"sourceLine\":\"throw Error('boo');\",\"scriptResourceName\":\"a.js\",\"lineNumber\":3,\"startPosition\":8,\"endPosition\":9,\"errorLevel\":8,\"startColumn\":6,\"endColumn\":7,\"isSharedCrossOrigin\":false,\"isOpaque\":false,\"frames\":[{\"line\":3,\"column\":7,\"functionName\":\"\",\"scriptName\":\"a.js\",\"isEval\":false,\"isConstructor\":false,\"isWasm\":false}]}"
|
||||
);
|
||||
assert!(r.is_some());
|
||||
let e = r.unwrap();
|
||||
assert_eq!(e.message, "Error: boo");
|
||||
assert_eq!(e.source_line, Some("throw Error('boo');".to_string()));
|
||||
assert_eq!(e.script_resource_name, Some("a.js".to_string()));
|
||||
assert_eq!(e.line_number, Some(3));
|
||||
assert_eq!(e.start_position, Some(8));
|
||||
assert_eq!(e.end_position, Some(9));
|
||||
assert_eq!(e.error_level, Some(8));
|
||||
assert_eq!(e.start_column, Some(6));
|
||||
assert_eq!(e.end_column, Some(7));
|
||||
assert_eq!(e.frames.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_error_to_string() {
|
||||
let e = CoreJSError(error1());
|
||||
};
|
||||
let actual = js_error.to_string();
|
||||
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";
|
||||
assert_eq!(expected, &e.to_string());
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate futures;
|
||||
extern crate libc;
|
||||
#[macro_use]
|
||||
extern crate downcast_rs;
|
||||
extern crate rusty_v8;
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod any_error;
|
||||
mod bindings;
|
||||
|
|
Loading…
Reference in a new issue