diff --git a/BUILD.gn b/BUILD.gn index 461e5fcd19..4e7ab41adb 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -20,6 +20,8 @@ group("default") { } main_extern = [ + "core:deno_core", + "$rust_build:ansi_term", "$rust_build:atty", "$rust_build:dirs", diff --git a/Cargo.lock b/Cargo.lock index c75e0dc2a3..c341d34800 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,7 @@ version = "0.3.1" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "deno_core 0.0.1", "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "flatbuffers 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 4ce9fa2a9e..47862713db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ version = "0.3.1" edition = "2018" [dependencies] +deno_core = { path = "./core" } + ansi_term = "0.11.0" atty = "0.2.11" dirs = "1.0.5" diff --git a/core/js_errors.rs b/core/js_errors.rs index c07af136fe..e8fb0701c9 100644 --- a/core/js_errors.rs +++ b/core/js_errors.rs @@ -73,32 +73,27 @@ impl fmt::Display for JSError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.script_resource_name.is_some() { let script_resource_name = self.script_resource_name.as_ref().unwrap(); - // Avoid showing internal code from gen/bundle/main.js - if script_resource_name != "gen/bundle/main.js" - && script_resource_name != "gen/bundle/compiler.js" - { - if self.line_number.is_some() && self.start_column.is_some() { - assert!(self.line_number.is_some()); - assert!(self.start_column.is_some()); - let script_line_column = format_script_line_column( - script_resource_name, - self.line_number.unwrap() - 1, - self.start_column.unwrap() - 1, - ); - write!(f, "{}", script_line_column)?; - } - 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.end_column.unwrap() { - if i >= self.start_column.unwrap() { - s.push('^'); - } else { - s.push(' '); - } + if self.line_number.is_some() && self.start_column.is_some() { + assert!(self.line_number.is_some()); + assert!(self.start_column.is_some()); + let script_line_column = format_script_line_column( + script_resource_name, + self.line_number.unwrap() - 1, + self.start_column.unwrap() - 1, + ); + write!(f, "{}", script_line_column)?; + } + 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.end_column.unwrap() { + if i >= self.start_column.unwrap() { + s.push('^'); + } else { + s.push(' '); } - writeln!(f, "{}", s)?; } + writeln!(f, "{}", s)?; } } diff --git a/core/lib.rs b/core/lib.rs index d13339ee50..6416704e27 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -7,7 +7,7 @@ mod js_errors; mod libdeno; mod shared; -pub use crate::js_errors::JSError; +pub use crate::js_errors::*; pub use crate::libdeno::deno_buf; pub use crate::shared::*; use futures::Async; diff --git a/src/ansi.rs b/src/ansi.rs index 73ae00949a..f9cd39ac46 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -1,3 +1,4 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. use ansi_term::Color::Fixed; use ansi_term::Color::Red; use ansi_term::Style; diff --git a/src/errors.rs b/src/errors.rs index 3a6937495a..65118d0707 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -use crate::js_errors::JSError; +use crate::js_errors::JSErrorColor; pub use crate::msg::ErrorKind; use crate::resolve_addr::ResolveAddrError; - +use deno_core::JSError; use hyper; use std; use std::fmt; @@ -202,7 +201,7 @@ impl fmt::Display for RustOrJsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { RustOrJsError::Rust(e) => e.fmt(f), - RustOrJsError::Js(e) => e.fmt(f), + RustOrJsError::Js(e) => JSErrorColor(e).fmt(f), } } } diff --git a/src/isolate.rs b/src/isolate.rs index 440c168f2b..3473f306b3 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -12,12 +12,13 @@ use crate::errors::DenoError; use crate::errors::DenoResult; use crate::errors::RustOrJsError; use crate::flags; -use crate::js_errors::JSError; +use crate::js_errors::apply_source_map; use crate::libdeno; use crate::modules::Modules; use crate::msg; use crate::permissions::DenoPermissions; use crate::tokio_util; +use deno_core::JSError; use futures::sync::mpsc as async_mpsc; use futures::Future; use libc::c_char; @@ -250,7 +251,7 @@ impl Isolate { let v8_exception = cstr.to_str().unwrap(); debug!("v8_exception\n{}\n", v8_exception); let js_error = JSError::from_v8_exception(v8_exception).unwrap(); - let js_error_mapped = js_error.apply_source_map(&self.state.dir); + let js_error_mapped = apply_source_map(&js_error, &self.state.dir); Some(js_error_mapped) } } diff --git a/src/js_errors.rs b/src/js_errors.rs index 5ba36c0cf8..f42d9cb512 100644 --- a/src/js_errors.rs +++ b/src/js_errors.rs @@ -1,16 +1,8 @@ // Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. - -// 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. - +//! This mod adds source maps and ANSI color display to deno_core::JSError. use crate::ansi; -use serde_json; +use deno_core::JSError; +use deno_core::StackFrame; use source_map_mappings::parse_mappings; use source_map_mappings::Bias; use source_map_mappings::Mappings; @@ -18,57 +10,36 @@ use std::collections::HashMap; use std::fmt; use std::str; +/// Wrapper around JSError which provides color to_string. +pub struct JSErrorColor<'a>(pub &'a JSError); + +struct StackFrameColor<'a>(&'a StackFrame); + pub trait SourceMapGetter { /// Returns the raw source map file. fn get_source_map(&self, script_name: &str) -> Option>; } -struct SourceMap { - mappings: Mappings, - sources: Vec, -} - /// Cached filename lookups. The key can be None if a previous lookup failed to /// find a SourceMap. type CachedMaps = HashMap>; -#[derive(Debug, PartialEq)] -pub struct StackFrame { - pub line: 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, +struct SourceMap { + mappings: Mappings, + sources: Vec, } -#[derive(Debug, PartialEq)] -pub struct JSError { - pub message: String, - - pub source_line: Option, - pub script_resource_name: Option, - pub line_number: Option, - pub start_position: Option, - pub end_position: Option, - pub error_level: Option, - pub start_column: Option, - pub end_column: Option, - - pub frames: Vec, -} - -impl fmt::Display for StackFrame { +impl<'a> fmt::Display for StackFrameColor<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let frame = self.0; // Note when we print to string, we change from 0-indexed to 1-indexed. - let function_name = ansi::italic_bold(self.function_name.clone()); + let function_name = ansi::italic_bold(frame.function_name.clone()); let script_line_column = - format_script_line_column(&self.script_name, self.line, self.column); + format_script_line_column(&frame.script_name, frame.line, frame.column); - if !self.function_name.is_empty() { + if !frame.function_name.is_empty() { write!(f, " at {} ({})", function_name, script_line_column) - } else if self.is_eval { + } else if frame.is_eval { write!(f, " at eval ({})", script_line_column) } else { write!(f, " at {}", script_line_column) @@ -88,29 +59,30 @@ fn format_script_line_column( format!("{}:{}:{}", script_name, line, column) } -impl fmt::Display for JSError { +impl<'a> fmt::Display for JSErrorColor<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.script_resource_name.is_some() { - let script_resource_name = self.script_resource_name.as_ref().unwrap(); + let e = self.0; + if e.script_resource_name.is_some() { + let script_resource_name = e.script_resource_name.as_ref().unwrap(); // Avoid showing internal code from gen/bundle/main.js if script_resource_name != "gen/bundle/main.js" && script_resource_name != "gen/bundle/compiler.js" { - if self.line_number.is_some() && self.start_column.is_some() { - assert!(self.line_number.is_some()); - assert!(self.start_column.is_some()); + if e.line_number.is_some() && e.start_column.is_some() { + assert!(e.line_number.is_some()); + assert!(e.start_column.is_some()); let script_line_column = format_script_line_column( script_resource_name, - self.line_number.unwrap() - 1, - self.start_column.unwrap() - 1, + e.line_number.unwrap() - 1, + e.start_column.unwrap() - 1, ); write!(f, "{}", script_line_column)?; } - if self.source_line.is_some() { - write!(f, "\n{}\n", self.source_line.as_ref().unwrap())?; + if e.source_line.is_some() { + write!(f, "\n{}\n", e.source_line.as_ref().unwrap())?; let mut s = String::new(); - for i in 0..self.end_column.unwrap() { - if i >= self.start_column.unwrap() { + for i in 0..e.end_column.unwrap() { + if i >= e.start_column.unwrap() { s.push('^'); } else { s.push(' '); @@ -121,133 +93,15 @@ impl fmt::Display for JSError { } } - write!(f, "{}", ansi::bold(self.message.clone()))?; + write!(f, "{}", ansi::bold(e.message.clone()))?; - for frame in &self.frames { - write!(f, "\n{}", &frame.to_string())?; + for frame in &e.frames { + write!(f, "\n{}", StackFrameColor(&frame).to_string())?; } Ok(()) } } -impl StackFrame { - // TODO Maybe use serde_derive? - fn from_json_value(v: &serde_json::Value) -> Option { - if !v.is_object() { - return None; - } - 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, - }) - } - - fn apply_source_map( - &self, - mappings_map: &mut CachedMaps, - getter: &dyn SourceMapGetter, - ) -> StackFrame { - let maybe_sm = - get_mappings(self.script_name.as_ref(), mappings_map, getter); - let frame_pos = ( - self.script_name.to_owned(), - self.line as i64, - self.column as i64, - ); - let (script_name, line, column) = match maybe_sm { - None => frame_pos, - Some(sm) => match sm.mappings.original_location_for( - self.line as u32, - self.column as u32, - Bias::default(), - ) { - None => frame_pos, - Some(mapping) => match &mapping.original { - None => frame_pos, - Some(original) => { - let orig_source = sm.sources[original.source as usize].clone(); - ( - orig_source, - i64::from(original.original_line), - i64::from(original.original_column), - ) - } - }, - }, - }; - - StackFrame { - script_name, - function_name: self.function_name.clone(), - line, - column, - is_eval: self.is_eval, - is_constructor: self.is_constructor, - is_wasm: self.is_wasm, - } - } -} - impl SourceMap { fn from_json(json_str: &str) -> Option { // Ugly. Maybe use serde_derive. @@ -283,87 +137,72 @@ impl SourceMap { } } -impl JSError { - /// Creates a new JSError by parsing the raw exception JSON string from V8. - pub fn from_v8_exception(json_str: &str) -> Option { - let v = serde_json::from_str::(json_str); - if v.is_err() { - return None; - } - let v = v.unwrap(); +fn frame_apply_source_map( + frame: &StackFrame, + mappings_map: &mut CachedMaps, + getter: &dyn SourceMapGetter, +) -> StackFrame { + let maybe_sm = get_mappings(frame.script_name.as_ref(), mappings_map, getter); + let frame_pos = ( + frame.script_name.to_owned(), + frame.line as i64, + frame.column as i64, + ); + let (script_name, line, column) = match maybe_sm { + None => frame_pos, + Some(sm) => match sm.mappings.original_location_for( + frame.line as u32, + frame.column as u32, + Bias::default(), + ) { + None => frame_pos, + Some(mapping) => match &mapping.original { + None => frame_pos, + Some(original) => { + let orig_source = sm.sources[original.source as usize].clone(); + ( + orig_source, + i64::from(original.original_line), + i64::from(original.original_column), + ) + } + }, + }, + }; - 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(|v| v.as_i64()); - let start_position = obj.get("startPosition").and_then(|v| v.as_i64()); - let end_position = obj.get("endPosition").and_then(|v| v.as_i64()); - let error_level = obj.get("errorLevel").and_then(|v| v.as_i64()); - let start_column = obj.get("startColumn").and_then(|v| v.as_i64()); - let end_column = obj.get("endColumn").and_then(|v| v.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::::new(); - for frame_v in frame_values { - match StackFrame::from_json_value(frame_v) { - None => return None, - Some(frame) => frames.push(frame), - } - } - - Some(JSError { - message, - source_line, - script_resource_name, - line_number, - start_position, - end_position, - error_level, - start_column, - end_column, - frames, - }) + StackFrame { + script_name, + function_name: frame.function_name.clone(), + line, + column, + is_eval: frame.is_eval, + is_constructor: frame.is_constructor, + is_wasm: frame.is_wasm, } +} - pub fn apply_source_map(&self, getter: &dyn SourceMapGetter) -> Self { - let mut mappings_map: CachedMaps = HashMap::new(); - let mut frames = Vec::::new(); - for frame in &self.frames { - let f = frame.apply_source_map(&mut mappings_map, getter); - frames.push(f); - } - JSError { - message: self.message.clone(), - frames, - error_level: self.error_level, - source_line: self.source_line.clone(), - // TODO the following need to be source mapped: - script_resource_name: self.script_resource_name.clone(), - line_number: self.line_number, - start_position: self.start_position, - end_position: self.end_position, - start_column: self.start_column, - end_column: self.end_column, - } +pub fn apply_source_map( + js_error: &JSError, + getter: &dyn SourceMapGetter, +) -> JSError { + let mut mappings_map: CachedMaps = HashMap::new(); + let mut frames = Vec::::new(); + for frame in &js_error.frames { + let f = frame_apply_source_map(&frame, &mut mappings_map, getter); + frames.push(f); + } + JSError { + message: js_error.message.clone(), + frames, + error_level: js_error.error_level, + source_line: js_error.source_line.clone(), + // TODO the following need to be source mapped: + script_resource_name: js_error.script_resource_name.clone(), + line_number: js_error.line_number, + start_position: js_error.start_position, + end_position: js_error.end_position, + start_column: js_error.start_column, + end_column: js_error.end_column, } } @@ -468,126 +307,6 @@ mod tests { } } - #[test] - fn stack_frame_from_json_value_1() { - let v = serde_json::from_str::( - r#"{ - "line":2, - "column":11, - "functionName":"foo", - "scriptName":"/Users/rld/src/deno/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/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::( - r#"{ - "scriptName": "/Users/rld/src/deno/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/tests/error_001.ts"); - } - - #[test] - fn js_error_from_v8_exception() { - let r = JSError::from_v8_exception( - r#"{ - "message":"Uncaught Error: bad", - "frames":[ - { - "line":2, - "column":11, - "functionName":"foo", - "scriptName":"/Users/rld/src/deno/tests/error_001.ts", - "isEval":true, - "isConstructor":false, - "isWasm":false - }, { - "line":5, - "column":5, - "functionName":"bar", - "scriptName":"/Users/rld/src/deno/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/tests/error_001.ts".to_string(), - function_name: "foo".to_string(), - is_eval: true, - is_constructor: false, - is_wasm: false, - } - ) - } - - #[test] - fn js_error_from_v8_exception2() { - let r = JSError::from_v8_exception( - "{\"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 stack_frame_to_string() { - let e = error1(); - assert_eq!( - " at foo (foo_bar.ts:5:17)", - strip_ansi_codes(&e.frames[0].to_string()) - ); - assert_eq!( - " at qat (bar_baz.ts:6:21)", - strip_ansi_codes(&e.frames[1].to_string()) - ); - } - #[test] fn js_error_to_string() { let e = error1(); @@ -598,7 +317,7 @@ mod tests { fn js_error_apply_source_map_1() { let e = error1(); let getter = MockSourceMapGetter {}; - let actual = e.apply_source_map(&getter); + let actual = apply_source_map(&e, &getter); let expected = JSError { message: "Error: foo bar".to_string(), source_line: None, @@ -665,7 +384,7 @@ mod tests { }], }; let getter = MockSourceMapGetter {}; - let actual = e.apply_source_map(&getter); + let actual = apply_source_map(&e, &getter); assert_eq!(actual.message, "TypeError: baz"); // Because this is accessing the live bundle, this test might be more fragile assert_eq!(actual.frames.len(), 1); diff --git a/src/ops.rs b/src/ops.rs index 8f32ebc031..3bd2e68940 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -10,7 +10,8 @@ use crate::isolate::Buf; use crate::isolate::Isolate; use crate::isolate::IsolateState; use crate::isolate::Op; -use crate::js_errors::JSError; +use crate::js_errors::apply_source_map; +use crate::js_errors::JSErrorColor; use crate::libdeno; use crate::msg; use crate::msg_util; @@ -21,6 +22,7 @@ use crate::resources::table_entries; use crate::resources::Resource; use crate::tokio_util; use crate::version; +use deno_core::JSError; use flatbuffers::FlatBufferBuilder; use futures; use futures::Async; @@ -302,8 +304,8 @@ fn op_format_error( let orig_error = String::from(inner.error().unwrap()); let js_error = JSError::from_v8_exception(&orig_error).unwrap(); - let js_error_mapped = js_error.apply_source_map(&state.dir); - let js_error_string = js_error_mapped.to_string(); + let js_error_mapped = apply_source_map(&js_error, &state.dir); + let js_error_string = JSErrorColor(&js_error_mapped).to_string(); let mut builder = FlatBufferBuilder::new(); let new_error = builder.create_string(&js_error_string); diff --git a/src/workers.rs b/src/workers.rs index 5eb75ae13d..3b20ae371d 100644 --- a/src/workers.rs +++ b/src/workers.rs @@ -3,11 +3,12 @@ use crate::isolate::Buf; use crate::isolate::Isolate; use crate::isolate::IsolateState; use crate::isolate::WorkerChannels; -use crate::js_errors::JSError; +use crate::js_errors::JSErrorColor; use crate::ops; use crate::resources; use crate::snapshot; use crate::tokio_util; +use deno_core::JSError; use futures::sync::mpsc; use futures::sync::oneshot; @@ -75,7 +76,7 @@ pub fn spawn( worker.event_loop()?; Ok(()) })().or_else(|err: JSError| -> Result<(), JSError> { - eprintln!("{}", err.to_string()); + eprintln!("{}", JSErrorColor(&err).to_string()); std::process::exit(1) }).unwrap(); });