summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2018-12-06 23:05:36 -0500
committerGitHub <noreply@github.com>2018-12-06 23:05:36 -0500
commitc113df1bb8a0c7d0c560ad32c0291c918c7da7b4 (patch)
tree0d15de448be602c22aecb2ec65ac7667c437a209
parent568ac0c9026b6f4012e2511a026bb5eb31a06020 (diff)
Process source maps in Rust instead of JS (#1280)
- Improves speed and binary size significantly. - Makes deno_last_exception() output a JSON structure. - Isolate::execute and Isolate::event_loop now return structured, mapped JSError objects on errors. - Removes libdeno functions: libdeno.setGlobalErrorHandler() libdeno.setPromiseRejectHandler() libdeno.setPromiseErrorExaminer() In collaboration with Ryan Dahl.
-rw-r--r--BUILD.gn4
-rw-r--r--Cargo.toml2
-rw-r--r--build_extra/rust/BUILD.gn42
-rw-r--r--js/compiler.ts48
-rw-r--r--js/libdeno.ts4
-rw-r--r--js/main.ts33
-rw-r--r--js/promise_util.ts50
-rw-r--r--js/unit_tests.ts1
-rw-r--r--js/v8_source_maps.ts285
-rw-r--r--js/v8_source_maps_test.ts17
-rw-r--r--libdeno/api.cc37
-rw-r--r--libdeno/binding.cc312
-rw-r--r--libdeno/deno.h14
-rw-r--r--libdeno/internal.h19
-rw-r--r--libdeno/libdeno_test.cc65
-rw-r--r--libdeno/libdeno_test.js48
-rw-r--r--libdeno/snapshot_creator.cc4
-rw-r--r--package.json2
-rw-r--r--src/deno_dir.rs20
-rw-r--r--src/isolate.rs42
-rw-r--r--src/js_errors.rs543
-rw-r--r--src/main.rs17
-rw-r--r--tests/async_error.ts.out5
-rw-r--r--tests/error_001.ts.out5
-rw-r--r--tests/error_002.ts.out8
-rw-r--r--tests/error_004_missing_module.ts.out12
-rw-r--r--tests/error_005_missing_dynamic_import.ts.out12
-rw-r--r--tests/error_006_import_ext_failure.ts.out12
-rw-r--r--tests/error_007_any.ts.out2
m---------third_party0
30 files changed, 856 insertions, 809 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 86326ed6e..f2e96bbbb 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -39,6 +39,8 @@ main_extern = [
"$rust_build:remove_dir_all",
"$rust_build:ring",
"$rust_build:rustyline",
+ "$rust_build:serde_json",
+ "$rust_build:source_map_mappings",
"$rust_build:tempfile",
"$rust_build:tokio",
"$rust_build:tokio_executor",
@@ -84,7 +86,6 @@ ts_sources = [
"js/platform.ts",
"js/plugins.d.ts",
"js/process.ts",
- "js/promise_util.ts",
"js/read_dir.ts",
"js/read_file.ts",
"js/read_link.ts",
@@ -101,7 +102,6 @@ ts_sources = [
"js/types.ts",
"js/url_search_params.ts",
"js/util.ts",
- "js/v8_source_maps.ts",
"js/write_file.ts",
"tsconfig.json",
diff --git a/Cargo.toml b/Cargo.toml
index 82aab94ff..d7cd0a5fe 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,8 @@ rand = "=0.6.1"
remove_dir_all = "=0.5.1"
rustyline = "=2.1.0"
ring = "=0.13.5"
+serde_json = "1.0.33"
+source-map-mappings = "0.5.0"
tempfile = "=3.0.5"
tokio = "=0.1.13"
tokio-executor = "=0.1.5"
diff --git a/build_extra/rust/BUILD.gn b/build_extra/rust/BUILD.gn
index de6a01c20..f872dccb1 100644
--- a/build_extra/rust/BUILD.gn
+++ b/build_extra/rust/BUILD.gn
@@ -1101,3 +1101,45 @@ rust_crate("tokio_process") {
]
}
}
+
+rust_crate("vlq") {
+ source_root = "$registry_github/vlq-0.5.1/src/lib.rs"
+}
+
+rust_crate("source_map_mappings") {
+ source_root = "$registry_github/source-map-mappings-0.5.0/src/lib.rs"
+ extern = [
+ ":rand",
+ ":vlq",
+ ]
+}
+
+rust_crate("ryu") {
+ source_root = "$registry_github/ryu-0.2.7/src/lib.rs"
+ features = [ "small" ]
+}
+
+rust_crate("serde") {
+ source_root = "$registry_github/serde-1.0.80/src/lib.rs"
+ features = [
+ "default",
+ "std",
+ ]
+}
+
+rust_crate("serde_json") {
+ source_root = "$registry_github/serde_json-1.0.33/src/lib.rs"
+ features = [
+ "arbitrary_precision",
+ "default",
+ "preserve_order",
+ "indexmap",
+ "raw_value",
+ ]
+ extern = [
+ ":indexmap",
+ ":itoa",
+ ":ryu",
+ ":serde",
+ ]
+}
diff --git a/js/compiler.ts b/js/compiler.ts
index c9fa4611c..e0bfb77bb 100644
--- a/js/compiler.ts
+++ b/js/compiler.ts
@@ -3,10 +3,8 @@ import * as ts from "typescript";
import { MediaType } from "gen/msg_generated";
import { assetSourceCode } from "./assets";
-import { libdeno } from "./libdeno";
import * as os from "./os";
import { CodeProvider } from "./runner";
-import { RawSourceMap } from "./types";
import { assert, log, notImplemented } from "./util";
const EOL = "\n";
@@ -71,7 +69,7 @@ export class ModuleMetaData implements ts.IScriptSnapshot {
public readonly mediaType: MediaType,
public readonly sourceCode: SourceCode = "",
public outputCode: OutputCode = "",
- public sourceMap: SourceMap | RawSourceMap = ""
+ public sourceMap: SourceMap = ""
) {
if (outputCode !== "" || fileName.endsWith(".d.ts")) {
this.scriptVersion = "1";
@@ -131,8 +129,6 @@ export class Compiler
ContainingFile,
Map<ModuleSpecifier, ModuleFileName>
>();
- // Keep track of state of the last module requested via `getGeneratedContents`
- private _lastModule: ModuleMetaData | undefined;
// A reference to the log utility, so it can be monkey patched during testing
private _log = log;
// A map of module file names to module meta data
@@ -369,19 +365,15 @@ export class Compiler
moduleMetaData.outputCode = `${
outputFile.text
}\n//# sourceURL=${fileName}`;
- moduleMetaData.sourceMap = JSON.parse(sourceMapFile.text);
+ moduleMetaData.sourceMap = sourceMapFile.text;
}
moduleMetaData.scriptVersion = "1";
- const sourceMap =
- moduleMetaData.sourceMap === "string"
- ? moduleMetaData.sourceMap
- : JSON.stringify(moduleMetaData.sourceMap);
this._os.codeCache(
fileName,
sourceCode,
moduleMetaData.outputCode,
- sourceMap
+ moduleMetaData.sourceMap
);
return moduleMetaData.outputCode;
}
@@ -398,36 +390,10 @@ export class Compiler
}
/** Given a fileName, return what was generated by the compiler. */
- getGeneratedContents = (fileName: string): string | RawSourceMap => {
- this._log("compiler.getGeneratedContents", fileName);
- if (fileName === "gen/bundle/main.js") {
- assert(libdeno.mainSource.length > 0);
- return libdeno.mainSource;
- } else if (fileName === "main.js.map") {
- return libdeno.mainSourceMap;
- } else if (fileName === "deno_main.js") {
- return "";
- } else if (!fileName.endsWith(".map")) {
- const moduleMetaData = this._moduleMetaDataMap.get(fileName);
- if (!moduleMetaData) {
- this._lastModule = undefined;
- return "";
- }
- this._lastModule = moduleMetaData;
- return moduleMetaData.outputCode;
- } else {
- if (this._lastModule && this._lastModule.sourceMap) {
- // Assuming the the map will always be asked for after the source
- // code.
- const { sourceMap } = this._lastModule;
- this._lastModule = undefined;
- return sourceMap;
- } else {
- // Errors thrown here are caught by source-map.
- throw new Error(`Unable to find source map: "${fileName}"`);
- }
- }
- };
+ getGeneratedSourceMap(fileName: string): string {
+ const moduleMetaData = this._moduleMetaDataMap.get(fileName);
+ return moduleMetaData ? moduleMetaData.sourceMap : "";
+ }
/** Get the output code for a module based on its filename. A call to
* `.getFilename()` should occur before attempting to get the output code as
diff --git a/js/libdeno.ts b/js/libdeno.ts
index c61027324..5448165a1 100644
--- a/js/libdeno.ts
+++ b/js/libdeno.ts
@@ -1,5 +1,4 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { RawSourceMap } from "./types";
import { globalEval } from "./global_eval";
// The libdeno functions are moved so that users can't access them.
@@ -39,9 +38,6 @@ interface Libdeno {
) => void;
setPromiseErrorExaminer: (handler: () => boolean) => void;
-
- mainSource: string;
- mainSourceMap: RawSourceMap;
}
const window = globalEval("this");
diff --git a/js/main.ts b/js/main.ts
index 241e8d7d1..818713593 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -11,22 +11,9 @@ import { Runner } from "./runner";
import { libdeno } from "./libdeno";
import { args } from "./deno";
import { sendSync, handleAsyncMsgFromRust } from "./dispatch";
-import { promiseErrorExaminer, promiseRejectHandler } from "./promise_util";
import { replLoop } from "./repl";
-import * as sourceMaps from "./v8_source_maps";
import { version } from "typescript";
-// Install the source maps handler and do some pre-calculations so all of it is
-// available in the snapshot
-const compiler = Compiler.instance();
-sourceMaps.install({
- installPrepareStackTrace: true,
- getGeneratedContents: compiler.getGeneratedContents
-});
-const consumer = sourceMaps.loadConsumer("gen/bundle/main.js");
-assert(consumer != null);
-consumer!.computeColumnSpans();
-
function sendStart(): msg.StartRes {
const builder = flatbuffers.createBuilder();
msg.Start.startStart(builder);
@@ -39,27 +26,9 @@ function sendStart(): msg.StartRes {
return startRes;
}
-function onGlobalError(
- message: string,
- source: string,
- lineno: number,
- colno: number,
- error: any // tslint:disable-line:no-any
-) {
- if (error instanceof Error) {
- console.log(error.stack);
- } else {
- console.log(`Thrown: ${String(error)}`);
- }
- os.exit(1);
-}
-
/* tslint:disable-next-line:no-default-export */
export default function denoMain() {
libdeno.recv(handleAsyncMsgFromRust);
- libdeno.setGlobalErrorHandler(onGlobalError);
- libdeno.setPromiseRejectHandler(promiseRejectHandler);
- libdeno.setPromiseErrorExaminer(promiseErrorExaminer);
// First we send an empty "Start" message to let the privileged side know we
// are ready. The response should be a "StartRes" message containing the CLI
@@ -68,6 +37,8 @@ export default function denoMain() {
setLogDebug(startResMsg.debugFlag());
+ const compiler = Compiler.instance();
+
// handle `--types`
if (startResMsg.typesFlag()) {
const defaultLibFileName = compiler.getDefaultLibFileName();
diff --git a/js/promise_util.ts b/js/promise_util.ts
deleted file mode 100644
index 3c789124d..000000000
--- a/js/promise_util.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { PromiseRejectEvent } from "./libdeno";
-
-/* tslint:disable-next-line:no-any */
-const rejectMap = new Map<Promise<any>, string>();
-// For uncaught promise rejection errors
-
-/* tslint:disable-next-line:no-any */
-const otherErrorMap = new Map<Promise<any>, string>();
-// For reject after resolve / resolve after resolve errors
-
-export function promiseRejectHandler(
- error: Error | string,
- event: PromiseRejectEvent,
- /* tslint:disable-next-line:no-any */
- promise: Promise<any>
-) {
- switch (event) {
- case "RejectWithNoHandler":
- rejectMap.set(promise, (error as Error).stack || "RejectWithNoHandler");
- break;
- case "HandlerAddedAfterReject":
- rejectMap.delete(promise);
- break;
- case "ResolveAfterResolved":
- // Should not warn. See #1272
- break;
- default:
- // error is string here
- otherErrorMap.set(promise, `Promise warning: ${error as string}`);
- }
-}
-
-// Return true when continue, false to die on uncaught promise reject
-export function promiseErrorExaminer(): boolean {
- if (otherErrorMap.size > 0) {
- for (const msg of otherErrorMap.values()) {
- console.log(msg);
- }
- otherErrorMap.clear();
- }
- if (rejectMap.size > 0) {
- for (const msg of rejectMap.values()) {
- console.log(msg);
- }
- rejectMap.clear();
- return false;
- }
- return true;
-}
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index 4863b7ac4..55877091e 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -35,5 +35,4 @@ import "./text_encoding_test.ts";
import "./timers_test.ts";
import "./truncate_test.ts";
import "./url_search_params_test.ts";
-import "./v8_source_maps_test.ts";
import "./write_file_test.ts";
diff --git a/js/v8_source_maps.ts b/js/v8_source_maps.ts
deleted file mode 100644
index dcdde16c7..000000000
--- a/js/v8_source_maps.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright 2014 Evan Wallace
-// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-// Originated from source-map-support but has been heavily modified for deno.
-
-import { SourceMapConsumer, MappedPosition } from "source-map";
-import { CallSite, RawSourceMap } from "./types";
-import { atob } from "./text_encoding";
-
-const consumers = new Map<string, SourceMapConsumer>();
-
-interface Options {
- // A callback the returns generated file contents.
- getGeneratedContents: GetGeneratedContentsCallback;
- // Usually set the following to true. Set to false for testing.
- installPrepareStackTrace: boolean;
-}
-
-interface Position {
- source: string; // Filename
- column: number;
- line: number;
-}
-
-type GetGeneratedContentsCallback = (fileName: string) => string | RawSourceMap;
-
-let getGeneratedContents: GetGeneratedContentsCallback;
-
-// @internal
-export function install(options: Options) {
- getGeneratedContents = options.getGeneratedContents;
- if (options.installPrepareStackTrace) {
- Error.prepareStackTrace = prepareStackTraceWrapper;
- }
-}
-
-// @internal
-export function prepareStackTraceWrapper(
- error: Error,
- stack: CallSite[]
-): string {
- try {
- return prepareStackTrace(error, stack);
- } catch (prepareStackError) {
- Error.prepareStackTrace = undefined;
- console.log("=====Error inside of prepareStackTrace====");
- console.log(prepareStackError.stack.toString());
- console.log("=====Original error=======================");
- throw error;
- }
-}
-
-// @internal
-export function prepareStackTrace(error: Error, stack: CallSite[]): string {
- const frames = stack.map(
- frame => `\n at ${wrapCallSite(frame).toString()}`
- );
- return `${error.toString()}${frames.join("")}`;
-}
-
-// @internal
-export function wrapCallSite(frame: CallSite): CallSite {
- if (frame.isNative()) {
- return frame;
- }
-
- // Most call sites will return the source file from getFileName(), but code
- // passed to eval() ending in "//# sourceURL=..." will return the source file
- // from getScriptNameOrSourceURL() instead
- const source = frame.getFileName() || frame.getScriptNameOrSourceURL();
-
- if (source) {
- const line = frame.getLineNumber() || 0;
- const column = (frame.getColumnNumber() || 1) - 1;
- const position = mapSourcePosition({ source, line, column });
- frame = cloneCallSite(frame);
- Object.assign(frame, {
- getFileName: () => position.source,
- getLineNumber: () => position.line,
- getColumnNumber: () => Number(position.column) + 1,
- getScriptNameOrSourceURL: () => position.source,
- toString: () => CallSiteToString(frame)
- });
- return frame;
- }
-
- // Code called using eval() needs special handling
- let origin = (frame.isEval() && frame.getEvalOrigin()) || undefined;
- if (origin) {
- origin = mapEvalOrigin(origin);
- frame = cloneCallSite(frame);
- Object.assign(frame, {
- getEvalOrigin: () => origin,
- toString: () => CallSiteToString(frame)
- });
- return frame;
- }
-
- // If we get here then we were unable to change the source position
- return frame;
-}
-
-function cloneCallSite(
- frame: CallSite
- // mixin: Partial<CallSite> & { toString: () => string }
-): CallSite {
- const obj = {} as CallSite;
- const props = Object.getOwnPropertyNames(
- Object.getPrototypeOf(frame)
- ) as Array<keyof CallSite>;
- for (const name of props) {
- obj[name] = /^(?:is|get)/.test(name)
- ? () => frame[name].call(frame)
- : frame[name];
- }
- return obj;
-}
-
-// Taken from source-map-support, original copied from V8's messages.js
-// MIT License. Copyright (c) 2014 Evan Wallace
-function CallSiteToString(frame: CallSite): string {
- let fileLocation = "";
- if (frame.isNative()) {
- fileLocation = "native";
- } else {
- const fileName = frame.getScriptNameOrSourceURL();
- if (!fileName && frame.isEval()) {
- fileLocation = frame.getEvalOrigin() || "";
- fileLocation += ", "; // Expecting source position to follow.
- }
-
- if (fileName) {
- fileLocation += fileName;
- } else {
- // Source code does not originate from a file and is not native, but we
- // can still get the source position inside the source string, e.g. in
- // an eval string.
- fileLocation += "<anonymous>";
- }
- const lineNumber = frame.getLineNumber();
- if (lineNumber != null) {
- fileLocation += `:${lineNumber}`;
- const columnNumber = frame.getColumnNumber();
- if (columnNumber) {
- fileLocation += `:${columnNumber}`;
- }
- }
- }
-
- let line = "";
- const functionName = frame.getFunctionName();
- let addSuffix = true;
- const isConstructor = frame.isConstructor();
- const isMethodCall = !(frame.isToplevel() || isConstructor);
- if (isMethodCall) {
- let typeName = frame.getTypeName();
- // Fixes shim to be backward compatible with Node v0 to v4
- if (typeName === "[object Object]") {
- typeName = "null";
- }
- const methodName = frame.getMethodName();
- if (functionName) {
- if (typeName && functionName.indexOf(typeName) !== 0) {
- line += `${typeName}.`;
- }
- line += functionName;
- if (
- methodName &&
- functionName.indexOf("." + methodName) !==
- functionName.length - methodName.length - 1
- ) {
- line += ` [as ${methodName} ]`;
- }
- } else {
- line += `${typeName}.${methodName || "<anonymous>"}`;
- }
- } else if (isConstructor) {
- line += `new ${functionName || "<anonymous>"}`;
- } else if (functionName) {
- line += functionName;
- } else {
- line += fileLocation;
- addSuffix = false;
- }
- if (addSuffix) {
- line += ` (${fileLocation})`;
- }
- return line;
-}
-
-// Regex for detecting source maps
-const reSourceMap = /^data:application\/json[^,]+base64,/;
-
-export function loadConsumer(source: string): SourceMapConsumer | null {
- let consumer = consumers.get(source);
- if (consumer == null) {
- const code = getGeneratedContents(source);
- if (!code) {
- return null;
- }
- if (typeof code !== "string") {
- throw new Error("expected string");
- }
-
- let sourceMappingURL = retrieveSourceMapURL(code);
- if (!sourceMappingURL) {
- throw Error("No source map?");
- }
-
- let sourceMapData: string | RawSourceMap;
- if (reSourceMap.test(sourceMappingURL)) {
- // Support source map URL as a data url
- const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(",") + 1);
- sourceMapData = atob(rawData);
- sourceMappingURL = source;
- } else {
- // TODO Support source map URLs relative to the source URL
- // sourceMappingURL = supportRelativeURL(source, sourceMappingURL);
- sourceMapData = getGeneratedContents(sourceMappingURL);
- }
-
- const rawSourceMap =
- typeof sourceMapData === "string"
- ? (JSON.parse(sourceMapData) as RawSourceMap)
- : sourceMapData;
- consumer = new SourceMapConsumer(rawSourceMap);
- consumers.set(source, consumer);
- }
- return consumer;
-}
-
-// tslint:disable-next-line:max-line-length
-const sourceMapUrlRe = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/gm;
-
-function retrieveSourceMapURL(fileData: string): string | null {
- // Keep executing the search to find the *last* sourceMappingURL to avoid
- // picking up sourceMappingURLs from comments, strings, etc.
- let lastMatch, match;
- while ((match = sourceMapUrlRe.exec(fileData))) {
- lastMatch = match;
- }
- if (!lastMatch) {
- return null;
- }
- return lastMatch[1];
-}
-
-export function mapSourcePosition(position: Position): MappedPosition {
- const consumer = loadConsumer(position.source);
- if (consumer == null) {
- return position;
- }
- return consumer.originalPositionFor(position);
-}
-
-const stackEvalRe = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/;
-const nestedEvalRe = /^eval at ([^(]+) \((.+)\)$/;
-
-// Parses code generated by FormatEvalOrigin(), a function inside V8:
-// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
-function mapEvalOrigin(origin: string): string {
- // Most eval() calls are in this format
- let match = stackEvalRe.exec(origin);
- if (match) {
- const position = mapSourcePosition({
- source: match[2],
- line: Number(match[3]),
- column: Number(match[4]) - 1
- });
- const pos = [
- position.source,
- position.line,
- Number(position.column) + 1
- ].join(":");
- return `eval at ${match[1]} (${pos})`;
- }
-
- // Parse nested eval() calls using recursion
- match = nestedEvalRe.exec(origin);
- if (match) {
- return `eval at ${match[1]} (${mapEvalOrigin(match[2])})`;
- }
-
- // Make sure we still return useful information if we didn't find anything
- return origin;
-}
diff --git a/js/v8_source_maps_test.ts b/js/v8_source_maps_test.ts
deleted file mode 100644
index 90c123431..000000000
--- a/js/v8_source_maps_test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { test, assert, assertEqual } from "./test_util.ts";
-
-// This test demonstrates a bug:
-// https://github.com/denoland/deno/issues/808
-test(function evalErrorFormatted() {
- let err;
- try {
- eval("boom");
- } catch (e) {
- err = e;
- }
- assert(!!err);
- // tslint:disable-next-line:no-unused-expression
- err.stack; // This would crash if err.stack is malformed
- assertEqual(err.name, "ReferenceError");
-});
diff --git a/libdeno/api.cc b/libdeno/api.cc
index 6f4ac826f..388ab6146 100644
--- a/libdeno/api.cc
+++ b/libdeno/api.cc
@@ -38,7 +38,7 @@ Deno* deno_new(deno_buf snapshot, deno_config config) {
if (!snapshot.data_ptr) {
// If no snapshot is provided, we initialize the context with empty
// main source code and source maps.
- deno::InitializeContext(isolate, context, "", "", "");
+ deno::InitializeContext(isolate, context, "", "");
}
d->context_.Reset(isolate, context);
}
@@ -47,7 +47,7 @@ Deno* deno_new(deno_buf snapshot, deno_config config) {
}
Deno* deno_new_snapshotter(deno_config config, const char* js_filename,
- const char* js_source, const char* source_map) {
+ const char* js_source) {
auto* creator = new v8::SnapshotCreator(deno::external_references);
auto* isolate = creator->GetIsolate();
auto* d = new deno::DenoIsolate(deno::empty_buf, config);
@@ -61,8 +61,7 @@ Deno* deno_new_snapshotter(deno_config config, const char* js_filename,
creator->SetDefaultContext(context,
v8::SerializeInternalFieldsCallback(
deno::SerializeInternalFields, nullptr));
- deno::InitializeContext(isolate, context, js_filename, js_source,
- source_map);
+ deno::InitializeContext(isolate, context, js_filename, js_source);
}
return reinterpret_cast<Deno*>(d);
}
@@ -96,7 +95,11 @@ void deno_set_v8_flags(int* argc, char** argv) {
const char* deno_last_exception(Deno* d_) {
auto* d = unwrap(d_);
- return d->last_exception_.c_str();
+ if (d->last_exception_.length() > 0) {
+ return d->last_exception_.c_str();
+ } else {
+ return nullptr;
+ }
}
int deno_execute(Deno* d_, void* user_data, const char* js_filename,
@@ -154,31 +157,19 @@ int deno_respond(Deno* d_, void* user_data, int32_t req_id, deno_buf buf) {
void deno_check_promise_errors(Deno* d_) {
auto* d = unwrap(d_);
- if (d->pending_promise_events_ > 0) {
+ if (d->pending_promise_map_.size() > 0) {
auto* isolate = d->isolate_;
v8::Locker locker(isolate);
v8::Isolate::Scope isolate_scope(isolate);
v8::HandleScope handle_scope(isolate);
-
auto context = d->context_.Get(d->isolate_);
v8::Context::Scope context_scope(context);
- v8::TryCatch try_catch(d->isolate_);
- auto promise_error_examiner_ = d->promise_error_examiner_.Get(d->isolate_);
- if (promise_error_examiner_.IsEmpty()) {
- d->last_exception_ =
- "libdeno.setPromiseErrorExaminer has not been called.";
- return;
- }
- v8::Local<v8::Value> args[0];
- auto result = promise_error_examiner_->Call(context->Global(), 0, args);
- if (try_catch.HasCaught()) {
- deno::HandleException(context, try_catch.Exception());
- }
- d->pending_promise_events_ = 0; // reset
- if (!result->BooleanValue(context).FromJust()) {
- // Has uncaught promise reject error, exiting...
- exit(1);
+ auto it = d->pending_promise_map_.begin();
+ while (it != d->pending_promise_map_.end()) {
+ auto error = it->second.Get(isolate);
+ deno::HandleException(context, error);
+ it = d->pending_promise_map_.erase(it);
}
}
}
diff --git a/libdeno/binding.cc b/libdeno/binding.cc
index b5d7ecafa..2a68d89aa 100644
--- a/libdeno/binding.cc
+++ b/libdeno/binding.cc
@@ -72,95 +72,94 @@ static inline v8::Local<v8::String> v8_str(const char* x) {
.ToLocalChecked();
}
-void HandleExceptionStr(v8::Local<v8::Context> context,
- v8::Local<v8::Value> exception,
- std::string* exception_str) {
+std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context,
+ v8::Local<v8::Value> exception) {
auto* isolate = context->GetIsolate();
- DenoIsolate* d = FromIsolate(isolate);
-
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context);
auto message = v8::Exception::CreateMessage(isolate, exception);
auto stack_trace = message->GetStackTrace();
- auto line =
- v8::Integer::New(isolate, message->GetLineNumber(context).FromJust());
- auto column =
- v8::Integer::New(isolate, message->GetStartColumn(context).FromJust());
-
- auto global_error_handler_ = d->global_error_handler_.Get(isolate);
-
- if (!global_error_handler_.IsEmpty()) {
- // global_error_handler_ is set so we try to handle the exception in
- // javascript.
- v8::Local<v8::Value> args[5];
- args[0] = exception->ToString(context).ToLocalChecked();
- args[1] = message->GetScriptResourceName();
- args[2] = line;
- args[3] = column;
- args[4] = exception;
- global_error_handler_->Call(context->Global(), 5, args);
- /* message, source, lineno, colno, error */
- return;
- }
+ // Encode the exception into a JS object, which we will then turn into JSON.
+ auto json_obj = v8::Object::New(isolate);
+
+ auto exception_str = exception->ToString(context).ToLocalChecked();
+ // Alternate and very similar string. Not sure which is appropriate.
+ // auto exception_str = message->Get();
+ CHECK(json_obj->Set(context, v8_str("message"), exception_str).FromJust());
- char buf[12 * 1024];
+ v8::Local<v8::Array> frames;
if (!stack_trace.IsEmpty()) {
- // No javascript error handler, but we do have a stack trace. Format it
- // into a string and add to last_exception_.
- std::string msg;
- v8::String::Utf8Value exceptionStr(isolate, exception);
- msg += ToCString(exceptionStr);
- msg += "\n";
-
- for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
+ uint32_t count = static_cast<uint32_t>(stack_trace->GetFrameCount());
+ frames = v8::Array::New(isolate, count);
+
+ for (uint32_t i = 0; i < count; ++i) {
auto frame = stack_trace->GetFrame(isolate, i);
- v8::String::Utf8Value script_name(isolate, frame->GetScriptName());
- int l = frame->GetLineNumber();
- int c = frame->GetColumn();
- snprintf(buf, sizeof(buf), "%s %d:%d\n", ToCString(script_name), l, c);
- msg += buf;
+ auto frame_obj = v8::Object::New(isolate);
+ CHECK(frames->Set(context, i, frame_obj).FromJust());
+ auto line = v8::Integer::New(isolate, frame->GetLineNumber());
+ auto column = v8::Integer::New(isolate, frame->GetColumn());
+ CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust());
+ CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("functionName"), frame->GetFunctionName())
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("scriptName"),
+ frame->GetScriptNameOrSourceURL())
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isEval"),
+ v8::Boolean::New(isolate, frame->IsEval()))
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isConstructor"),
+ v8::Boolean::New(isolate, frame->IsConstructor()))
+ .FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("isWasm"),
+ v8::Boolean::New(isolate, frame->IsWasm()))
+ .FromJust());
}
- *exception_str += msg;
} else {
- // No javascript error handler, no stack trace. Format the little info we
- // have into a string and add to last_exception_.
- v8::String::Utf8Value exceptionStr(isolate, exception);
- v8::String::Utf8Value script_name(isolate,
- message->GetScriptResourceName());
- v8::String::Utf8Value line_str(isolate, line);
- v8::String::Utf8Value col_str(isolate, column);
- snprintf(buf, sizeof(buf), "%s\n%s %s:%s\n", ToCString(exceptionStr),
- ToCString(script_name), ToCString(line_str), ToCString(col_str));
- *exception_str += buf;
+ // No stack trace. We only have one stack frame of info..
+ frames = v8::Array::New(isolate, 1);
+
+ auto frame_obj = v8::Object::New(isolate);
+ CHECK(frames->Set(context, 0, frame_obj).FromJust());
+
+ auto line =
+ v8::Integer::New(isolate, message->GetLineNumber(context).FromJust());
+ auto column =
+ v8::Integer::New(isolate, message->GetStartColumn(context).FromJust());
+
+ CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust());
+ CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust());
+ CHECK(frame_obj
+ ->Set(context, v8_str("scriptName"),
+ message->GetScriptResourceName())
+ .FromJust());
}
+
+ CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust());
+
+ auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked();
+ v8::String::Utf8Value json_string_(isolate, json_string);
+ return std::string(ToCString(json_string_));
}
void HandleException(v8::Local<v8::Context> context,
v8::Local<v8::Value> exception) {
v8::Isolate* isolate = context->GetIsolate();
DenoIsolate* d = FromIsolate(isolate);
- std::string exception_str;
- HandleExceptionStr(context, exception, &exception_str);
+ std::string json_str = EncodeExceptionAsJSON(context, exception);
if (d != nullptr) {
- d->last_exception_ = exception_str;
+ d->last_exception_ = json_str;
} else {
- std::cerr << "Pre-Deno Exception " << exception_str << std::endl;
- exit(1);
- }
-}
-
-const char* PromiseRejectStr(enum v8::PromiseRejectEvent e) {
- switch (e) {
- case v8::PromiseRejectEvent::kPromiseRejectWithNoHandler:
- return "RejectWithNoHandler";
- case v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject:
- return "HandlerAddedAfterReject";
- case v8::PromiseRejectEvent::kPromiseResolveAfterResolved:
- return "ResolveAfterResolved";
- case v8::PromiseRejectEvent::kPromiseRejectAfterResolved:
- return "RejectAfterResolved";
+ // This shouldn't happen in normal circumstances. Added for debugging.
+ std::cerr << "Pre-Deno Exception " << json_str << std::endl;
+ CHECK(false);
}
}
@@ -169,40 +168,35 @@ void PromiseRejectCallback(v8::PromiseRejectMessage promise_reject_message) {
DenoIsolate* d = static_cast<DenoIsolate*>(isolate->GetData(0));
DCHECK_EQ(d->isolate_, isolate);
v8::HandleScope handle_scope(d->isolate_);
- auto exception = promise_reject_message.GetValue();
+ auto error = promise_reject_message.GetValue();
auto context = d->context_.Get(d->isolate_);
auto promise = promise_reject_message.GetPromise();
- auto event = promise_reject_message.GetEvent();
v8::Context::Scope context_scope(context);
- auto promise_reject_handler = d->promise_reject_handler_.Get(isolate);
-
- if (!promise_reject_handler.IsEmpty()) {
- v8::Local<v8::Value> args[3];
- args[1] = v8_str(PromiseRejectStr(event));
- args[2] = promise;
- /* error, event, promise */
- if (event == v8::PromiseRejectEvent::kPromiseRejectWithNoHandler) {
- d->pending_promise_events_++;
- // exception only valid for kPromiseRejectWithNoHandler
- args[0] = exception;
- } else if (event ==
- v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject) {
- d->pending_promise_events_--; // unhandled event cancelled
- if (d->pending_promise_events_ < 0) {
- d->pending_promise_events_ = 0;
- }
- // Placeholder, not actually used
- args[0] = v8_str("Promise handler added");
- } else if (event == v8::PromiseRejectEvent::kPromiseResolveAfterResolved) {
- d->pending_promise_events_++;
- args[0] = v8_str("Promise resolved after resolved");
- } else if (event == v8::PromiseRejectEvent::kPromiseRejectAfterResolved) {
- d->pending_promise_events_++;
- args[0] = v8_str("Promise rejected after resolved");
- }
- promise_reject_handler->Call(context->Global(), 3, args);
- return;
+
+ int promise_id = promise->GetIdentityHash();
+ switch (promise_reject_message.GetEvent()) {
+ case v8::kPromiseRejectWithNoHandler:
+ // Insert the error into the pending_promise_map_ using the promise's id
+ // as the key.
+ d->pending_promise_map_.emplace(std::piecewise_construct,
+ std::make_tuple(promise_id),
+ std::make_tuple(d->isolate_, error));
+ break;
+
+ case v8::kPromiseHandlerAddedAfterReject:
+ d->pending_promise_map_.erase(promise_id);
+ break;
+
+ case v8::kPromiseRejectAfterResolved:
+ break;
+
+ case v8::kPromiseResolveAfterResolved:
+ // Should not warn. See #1272
+ break;
+
+ default:
+ CHECK(false && "unreachable");
}
}
@@ -359,69 +353,6 @@ void Shared(v8::Local<v8::Name> property,
info.GetReturnValue().Set(ab);
}
-// Sets the global error handler.
-void SetGlobalErrorHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {
- v8::Isolate* isolate = args.GetIsolate();
- DenoIsolate* d = FromIsolate(isolate);
- DCHECK_EQ(d->isolate_, isolate);
-
- v8::HandleScope handle_scope(isolate);
-
- if (!d->global_error_handler_.IsEmpty()) {
- isolate->ThrowException(
- v8_str("libdeno.setGlobalErrorHandler already called."));
- return;
- }
-
- v8::Local<v8::Value> v = args[0];
- CHECK(v->IsFunction());
- v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(v);
-
- d->global_error_handler_.Reset(isolate, func);
-}
-
-// Sets the promise uncaught reject handler
-void SetPromiseRejectHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {
- v8::Isolate* isolate = args.GetIsolate();
- DenoIsolate* d = FromIsolate(isolate);
- DCHECK_EQ(d->isolate_, isolate);
-
- v8::HandleScope handle_scope(isolate);
-
- if (!d->promise_reject_handler_.IsEmpty()) {
- isolate->ThrowException(
- v8_str("libdeno.setPromiseRejectHandler already called."));
- return;
- }
-
- v8::Local<v8::Value> v = args[0];
- CHECK(v->IsFunction());
- v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(v);
-
- d->promise_reject_handler_.Reset(isolate, func);
-}
-
-// Sets the promise uncaught reject handler
-void SetPromiseErrorExaminer(const v8::FunctionCallbackInfo<v8::Value>& args) {
- v8::Isolate* isolate = args.GetIsolate();
- DenoIsolate* d = FromIsolate(isolate);
- DCHECK_EQ(d->isolate_, isolate);
-
- v8::HandleScope handle_scope(isolate);
-
- if (!d->promise_error_examiner_.IsEmpty()) {
- isolate->ThrowException(
- v8_str("libdeno.setPromiseErrorExaminer already called."));
- return;
- }
-
- v8::Local<v8::Value> v = args[0];
- CHECK(v->IsFunction());
- v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(v);
-
- d->promise_error_examiner_.Reset(isolate, func);
-}
-
bool ExecuteV8StringSource(v8::Local<v8::Context> context,
const char* js_filename,
v8::Local<v8::String> source) {
@@ -466,8 +397,7 @@ bool Execute(v8::Local<v8::Context> context, const char* js_filename,
}
void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
- const char* js_filename, const char* js_source,
- const char* source_map) {
+ const char* js_filename, const char* js_source) {
CHECK_NE(js_source, nullptr);
CHECK_NE(js_filename, nullptr);
v8::HandleScope handle_scope(isolate);
@@ -493,61 +423,8 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared)
.FromJust());
- auto set_global_error_handler_tmpl =
- v8::FunctionTemplate::New(isolate, SetGlobalErrorHandler);
- auto set_global_error_handler_val =
- set_global_error_handler_tmpl->GetFunction(context).ToLocalChecked();
- CHECK(deno_val
- ->Set(context, deno::v8_str("setGlobalErrorHandler"),
- set_global_error_handler_val)
- .FromJust());
-
- auto set_promise_reject_handler_tmpl =
- v8::FunctionTemplate::New(isolate, SetPromiseRejectHandler);
- auto set_promise_reject_handler_val =
- set_promise_reject_handler_tmpl->GetFunction(context).ToLocalChecked();
- CHECK(deno_val
- ->Set(context, deno::v8_str("setPromiseRejectHandler"),
- set_promise_reject_handler_val)
- .FromJust());
-
- auto set_promise_error_examiner_tmpl =
- v8::FunctionTemplate::New(isolate, SetPromiseErrorExaminer);
- auto set_promise_error_examiner_val =
- set_promise_error_examiner_tmpl->GetFunction(context).ToLocalChecked();
- CHECK(deno_val
- ->Set(context, deno::v8_str("setPromiseErrorExaminer"),
- set_promise_error_examiner_val)
- .FromJust());
-
{
- if (source_map != nullptr) {
- v8::TryCatch try_catch(isolate);
- v8::ScriptOrigin origin(v8_str("set_source_map.js"));
- std::string source_map_parens =
- std::string("(") + std::string(source_map) + std::string(")");
- auto source_map_v8_str = deno::v8_str(source_map_parens.c_str());
- auto script = v8::Script::Compile(context, source_map_v8_str, &origin);
- if (script.IsEmpty()) {
- DCHECK(try_catch.HasCaught());
- HandleException(context, try_catch.Exception());
- return;
- }
- auto source_map_obj = script.ToLocalChecked()->Run(context);
- if (source_map_obj.IsEmpty()) {
- DCHECK(try_catch.HasCaught());
- HandleException(context, try_catch.Exception());
- return;
- }
- CHECK(deno_val
- ->Set(context, deno::v8_str("mainSourceMap"),
- source_map_obj.ToLocalChecked())
- .FromJust());
- }
-
auto source = deno::v8_str(js_source);
- CHECK(
- deno_val->Set(context, deno::v8_str("mainSource"), source).FromJust());
bool r = deno::ExecuteV8StringSource(context, js_filename, source);
CHECK(r);
@@ -558,10 +435,11 @@ void DenoIsolate::AddIsolate(v8::Isolate* isolate) {
isolate_ = isolate;
// Leaving this code here because it will probably be useful later on, but
// disabling it now as I haven't got tests for the desired behavior.
- // d->isolate->SetCaptureStackTraceForUncaughtExceptions(true);
// d->isolate->SetAbortOnUncaughtExceptionCallback(AbortOnUncaughtExceptionCallback);
// d->isolate->AddMessageListener(MessageCallback2);
// d->isolate->SetFatalErrorHandler(FatalErrorCallback2);
+ isolate_->SetCaptureStackTraceForUncaughtExceptions(
+ true, 10, v8::StackTrace::kDetailed);
isolate_->SetPromiseRejectCallback(deno::PromiseRejectCallback);
isolate_->SetData(0, this);
}
diff --git a/libdeno/deno.h b/libdeno/deno.h
index fb0b13746..48356bc86 100644
--- a/libdeno/deno.h
+++ b/libdeno/deno.h
@@ -37,7 +37,7 @@ typedef struct {
Deno* deno_new(deno_buf snapshot, deno_config config);
Deno* deno_new_snapshotter(deno_config config, const char* js_filename,
- const char* js_source, const char* source_map);
+ const char* js_source);
// Generate a snapshot. The resulting buf can be used with deno_new.
// The caller must free the returned data by calling delete[] buf.data_ptr.
@@ -48,6 +48,12 @@ void deno_delete(Deno* d);
// Returns false on error.
// Get error text with deno_last_exception().
// 0 = fail, 1 = success
+//
+// TODO change return value to be const char*. On success the return
+// value is nullptr, on failure it is the JSON exception text that
+// is returned by deno_last_exception(). Remove deno_last_exception().
+// The return string is valid until the next execution of deno_execute or
+// deno_respond (as deno_last_exception is now).
int deno_execute(Deno* d, void* user_data, const char* js_filename,
const char* js_source);
@@ -69,6 +75,12 @@ int deno_execute(Deno* d, void* user_data, const char* js_filename,
//
// A non-zero return value, means a JS exception was encountered during the
// libdeno.recv() callback. Check deno_last_exception() for exception text.
+//
+// TODO change return value to be const char*. On success the return
+// value is nullptr, on failure it is the JSON exception text that
+// is returned by deno_last_exception(). Remove deno_last_exception().
+// The return string is valid until the next execution of deno_execute or
+// deno_respond (as deno_last_exception is now).
int deno_respond(Deno* d, void* user_data, int32_t req_id, deno_buf buf);
void deno_check_promise_errors(Deno* d);
diff --git a/libdeno/internal.h b/libdeno/internal.h
index 8a366d515..3dc4d8d0e 100644
--- a/libdeno/internal.h
+++ b/libdeno/internal.h
@@ -19,7 +19,6 @@ class DenoIsolate {
current_args_(nullptr),
snapshot_creator_(nullptr),
global_import_buf_ptr_(nullptr),
- pending_promise_events_(0),
recv_cb_(config.recv_cb),
next_req_id_(0),
user_data_(nullptr) {
@@ -47,13 +46,13 @@ class DenoIsolate {
const v8::FunctionCallbackInfo<v8::Value>* current_args_;
v8::SnapshotCreator* snapshot_creator_;
void* global_import_buf_ptr_;
- int32_t pending_promise_events_;
deno_recv_cb recv_cb_;
int32_t next_req_id_;
void* user_data_;
v8::Persistent<v8::Context> context_;
std::map<int32_t, v8::Persistent<v8::Value>> async_data_map_;
+ std::map<int, v8::Persistent<v8::Value>> pending_promise_map_;
std::string last_exception_;
v8::Persistent<v8::Function> recv_;
v8::Persistent<v8::Function> global_error_handler_;
@@ -91,26 +90,16 @@ void Recv(const v8::FunctionCallbackInfo<v8::Value>& args);
void Send(const v8::FunctionCallbackInfo<v8::Value>& args);
void Shared(v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& info);
-void SetGlobalErrorHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
-void SetPromiseRejectHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
-void SetPromiseErrorExaminer(const v8::FunctionCallbackInfo<v8::Value>& args);
static intptr_t external_references[] = {
- reinterpret_cast<intptr_t>(Print),
- reinterpret_cast<intptr_t>(Recv),
- reinterpret_cast<intptr_t>(Send),
- reinterpret_cast<intptr_t>(Shared),
- reinterpret_cast<intptr_t>(SetGlobalErrorHandler),
- reinterpret_cast<intptr_t>(SetPromiseRejectHandler),
- reinterpret_cast<intptr_t>(SetPromiseErrorExaminer),
- 0};
+ reinterpret_cast<intptr_t>(Print), reinterpret_cast<intptr_t>(Recv),
+ reinterpret_cast<intptr_t>(Send), reinterpret_cast<intptr_t>(Shared), 0};
static const deno_buf empty_buf = {nullptr, 0, nullptr, 0};
Deno* NewFromSnapshot(void* user_data, deno_recv_cb cb);
void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
- const char* js_filename, const char* js_source,
- const char* source_map);
+ const char* js_filename, const char* js_source);
void HandleException(v8::Local<v8::Context> context,
v8::Local<v8::Value> exception);
diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc
index f7173210a..33a4702ae 100644
--- a/libdeno/libdeno_test.cc
+++ b/libdeno/libdeno_test.cc
@@ -15,14 +15,14 @@ TEST(LibDenoTest, InitializesCorrectlyWithoutSnapshot) {
}
TEST(LibDenoTest, SnapshotterInitializesCorrectly) {
- Deno* d = deno_new_snapshotter(deno_config{empty, nullptr}, "a.js",
- "a = 1 + 2", nullptr);
+ Deno* d =
+ deno_new_snapshotter(deno_config{empty, nullptr}, "a.js", "a = 1 + 2");
deno_delete(d);
}
TEST(LibDenoTest, Snapshotter) {
- Deno* d1 = deno_new_snapshotter(deno_config{empty, nullptr}, "a.js",
- "a = 1 + 2", nullptr);
+ Deno* d1 =
+ deno_new_snapshotter(deno_config{empty, nullptr}, "a.js", "a = 1 + 2");
deno_buf test_snapshot = deno_get_snapshot(d1);
deno_delete(d1);
@@ -182,23 +182,18 @@ TEST(LibDenoTest, SnapshotBug) {
}
TEST(LibDenoTest, GlobalErrorHandling) {
- static int count = 0;
- auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) {
- assert_null(data_buf);
- count++;
- EXPECT_EQ(static_cast<size_t>(1), buf.data_len);
- EXPECT_EQ(buf.data_ptr[0], 42);
- };
- Deno* d = deno_new(snapshot, deno_config{empty, recv_cb});
- EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()"));
- EXPECT_EQ(count, 1);
- deno_delete(d);
-}
-
-TEST(LibDenoTest, DoubleGlobalErrorHandlingFails) {
Deno* d = deno_new(snapshot, deno_config{empty, nullptr});
- EXPECT_FALSE(
- deno_execute(d, nullptr, "a.js", "DoubleGlobalErrorHandlingFails()"));
+ EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()"));
+ // We only check that it starts with this string, so we don't have to check
+ // the second frame, which contains line numbers in libdeno_test.js and may
+ // change over time.
+ std::string expected =
+ "{\"message\":\"ReferenceError: notdefined is not defined\","
+ "\"frames\":[{\"line\":3,\"column\":2,\"functionName\":\"\","
+ "\"scriptName\":\"helloworld.js\",\"isEval\":true,"
+ "\"isConstructor\":false,\"isWasm\":false},";
+ std::string actual(deno_last_exception(d), 0, expected.length());
+ EXPECT_STREQ(expected.c_str(), actual.c_str());
deno_delete(d);
}
@@ -225,17 +220,31 @@ TEST(LibDenoTest, DataBuf) {
deno_delete(d);
}
-TEST(LibDenoTest, PromiseRejectCatchHandling) {
+TEST(LibDenoTest, CheckPromiseErrors) {
static int count = 0;
- auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) {
- // If no error, nothing should be sent, and count should
- // not increment
- count++;
- };
+ auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) { count++; };
Deno* d = deno_new(snapshot, deno_config{empty, recv_cb});
- EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "PromiseRejectCatchHandling()"));
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()"));
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ EXPECT_EQ(count, 1);
+ // We caught the exception. So still no errors after calling
+ // deno_check_promise_errors().
+ deno_check_promise_errors(d);
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ deno_delete(d);
+}
- EXPECT_EQ(count, 0);
+TEST(LibDenoTest, LastException) {
+ Deno* d = deno_new(empty, deno_config{empty, nullptr});
+ EXPECT_EQ(deno_last_exception(d), nullptr);
+ EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "\n\nthrow Error('boo');\n\n"));
+ EXPECT_STREQ(deno_last_exception(d),
+ "{\"message\":\"Error: boo\","
+ "\"frames\":[{\"line\":3,\"column\":7,"
+ "\"functionName\":\"\",\"scriptName\":\"a.js\","
+ "\"isEval\":false,"
+ "\"isConstructor\":false,\"isWasm\":false}]}");
deno_delete(d);
}
diff --git a/libdeno/libdeno_test.js b/libdeno/libdeno_test.js
index c9eaaa0ca..bb36b02d9 100644
--- a/libdeno/libdeno_test.js
+++ b/libdeno/libdeno_test.js
@@ -99,23 +99,9 @@ global.SnapshotBug = () => {
};
global.GlobalErrorHandling = () => {
- libdeno.setGlobalErrorHandler((message, source, line, col, error) => {
- libdeno.print(`line ${line} col ${col}`, true);
- assert("ReferenceError: notdefined is not defined" === message);
- assert(source === "helloworld.js");
- assert(line === 3);
- assert(col === 1);
- assert(error instanceof Error);
- libdeno.send(new Uint8Array([42]));
- });
eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
};
-global.DoubleGlobalErrorHandlingFails = () => {
- libdeno.setGlobalErrorHandler((message, source, line, col, error) => {});
- libdeno.setGlobalErrorHandler((message, source, line, col, error) => {});
-};
-
// Allocate this buf at the top level to avoid GC.
const dataBuf = new Uint8Array([3, 4]);
@@ -134,33 +120,7 @@ global.DataBuf = () => {
b[1] = 8;
};
-global.PromiseRejectCatchHandling = () => {
- let count = 0;
- let promiseRef = null;
- // When we have an error, libdeno sends something
- function assertOrSend(cond) {
- if (!cond) {
- libdeno.send(new Uint8Array([42]));
- }
- }
- libdeno.setPromiseErrorExaminer(() => {
- assertOrSend(count === 2);
- });
- libdeno.setPromiseRejectHandler((error, event, promise) => {
- count++;
- if (event === "RejectWithNoHandler") {
- assertOrSend(error instanceof Error);
- assertOrSend(error.message === "message");
- assertOrSend(count === 1);
- promiseRef = promise;
- } else if (event === "HandlerAddedAfterReject") {
- assertOrSend(count === 2);
- assertOrSend(promiseRef === promise);
- }
- // Should never reach 3!
- assertOrSend(count !== 3);
- });
-
+global.CheckPromiseErrors = () => {
async function fn() {
throw new Error("message");
}
@@ -169,10 +129,10 @@ global.PromiseRejectCatchHandling = () => {
try {
await fn();
} catch (e) {
- assertOrSend(count === 2);
+ libdeno.send(new Uint8Array([42]));
}
})();
-}
+};
global.Shared = () => {
const ab = libdeno.shared;
@@ -185,4 +145,4 @@ global.Shared = () => {
ui8[0] = 42;
ui8[1] = 43;
ui8[2] = 44;
-}
+};
diff --git a/libdeno/snapshot_creator.cc b/libdeno/snapshot_creator.cc
index cfdd35804..fe9011c9d 100644
--- a/libdeno/snapshot_creator.cc
+++ b/libdeno/snapshot_creator.cc
@@ -30,9 +30,7 @@ int main(int argc, char** argv) {
deno_init();
deno_config config = {deno::empty_buf, nullptr};
- Deno* d = deno_new_snapshotter(
- config, js_fn, js_source.c_str(),
- source_map_fn != nullptr ? source_map.c_str() : nullptr);
+ Deno* d = deno_new_snapshotter(config, js_fn, js_source.c_str());
auto snapshot = deno_get_snapshot(d);
diff --git a/package.json b/package.json
index e591e09b2..08e8ea436 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,6 @@
"@types/base64-js": "^1.2.5",
"@types/flatbuffers": "^1.9.0",
"@types/prettier": "=1.15.3",
- "@types/source-map-support": "^0.4.1",
"@types/text-encoding": "0.0.33",
"base64-js": "^1.3.0",
"flatbuffers": "^1.9.0",
@@ -19,7 +18,6 @@
"rollup-plugin-string": "^2.0.2",
"rollup-plugin-typescript2": "^0.16.1",
"rollup-pluginutils": "^2.3.0",
- "source-map-support": "^0.5.6",
"ts-node": "^7.0.1",
"ts-simple-ast": "17.1.0",
"tslint": "^5.10.0",
diff --git a/src/deno_dir.rs b/src/deno_dir.rs
index 8f13fd843..a6a741ec1 100644
--- a/src/deno_dir.rs
+++ b/src/deno_dir.rs
@@ -5,7 +5,9 @@ use errors::DenoResult;
use errors::ErrorKind;
use fs as deno_fs;
use http_util;
+use js_errors::SourceMapGetter;
use msg;
+
use ring;
use std;
use std::fmt::Write;
@@ -346,6 +348,24 @@ impl DenoDir {
}
}
+impl SourceMapGetter for DenoDir {
+ fn get_source_map(&self, script_name: &str) -> Option<String> {
+ match self.code_fetch(script_name, ".") {
+ Err(_e) => {
+ return None;
+ }
+ Ok(out) => match out.maybe_source_map {
+ None => {
+ return None;
+ }
+ Some(source_map) => {
+ return Some(source_map);
+ }
+ },
+ }
+ }
+}
+
fn get_cache_filename(basedir: &Path, url: &Url) -> PathBuf {
let host = url.host_str().unwrap();
let host_port = match url.port() {
diff --git a/src/isolate.rs b/src/isolate.rs
index 6b2814ec4..8e78f770b 100644
--- a/src/isolate.rs
+++ b/src/isolate.rs
@@ -8,6 +8,7 @@ use deno_dir;
use errors::DenoError;
use errors::DenoResult;
use flags;
+use js_errors::JSError;
use libdeno;
use permissions::DenoPermissions;
@@ -26,8 +27,6 @@ use std::time::Instant;
use tokio;
use tokio_util;
-type DenoException<'a> = &'a str;
-
// Buf represents a byte array returned from a "Op".
// The message might be empty (which will be translated into a null object on
// the javascript side) or it is a heap allocated opaque sequence of bytes.
@@ -184,11 +183,25 @@ impl Isolate {
self.timeout_due.set(inst);
}
+ pub fn last_exception(&self) -> Option<JSError> {
+ let ptr = unsafe { libdeno::deno_last_exception(self.libdeno_isolate) };
+ if ptr == std::ptr::null() {
+ None
+ } else {
+ let cstr = unsafe { CStr::from_ptr(ptr) };
+ 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);
+ return Some(js_error_mapped);
+ }
+ }
+
pub fn execute(
&self,
js_filename: &str,
js_source: &str,
- ) -> Result<(), DenoException> {
+ ) -> Result<(), JSError> {
let filename = CString::new(js_filename).unwrap();
let source = CString::new(js_source).unwrap();
let r = unsafe {
@@ -200,9 +213,8 @@ impl Isolate {
)
};
if r == 0 {
- let ptr = unsafe { libdeno::deno_last_exception(self.libdeno_isolate) };
- let cstr = unsafe { CStr::from_ptr(ptr) };
- return Err(cstr.to_str().unwrap());
+ let js_error = self.last_exception().unwrap();
+ return Err(js_error);
}
Ok(())
}
@@ -249,7 +261,7 @@ impl Isolate {
// TODO Use Park abstraction? Note at time of writing Tokio default runtime
// does not have new_with_park().
- pub fn event_loop(&self) {
+ pub fn event_loop(&self) -> Result<(), JSError> {
// Main thread event loop.
while !self.is_idle() {
match recv_deadline(&self.rx, self.get_timeout_due()) {
@@ -258,9 +270,16 @@ impl Isolate {
Err(e) => panic!("recv_deadline() failed: {:?}", e),
}
self.check_promise_errors();
+ if let Some(err) = self.last_exception() {
+ return Err(err);
+ }
}
// Check on done
self.check_promise_errors();
+ if let Some(err) = self.last_exception() {
+ return Err(err);
+ }
+ Ok(())
}
#[inline]
@@ -389,7 +408,7 @@ mod tests {
}
"#,
).expect("execute error");
- isolate.event_loop();
+ isolate.event_loop().ok();
});
}
@@ -435,8 +454,8 @@ mod tests {
const data = new Uint8Array([42, 43, 44, 45, 46]);
libdeno.send(control, data);
"#,
- ).expect("execute error");
- isolate.event_loop();
+ ).expect("execute error");;
+ isolate.event_loop().unwrap();
let metrics = &isolate.state.metrics;
assert_eq!(metrics.ops_dispatched.load(Ordering::SeqCst), 1);
assert_eq!(metrics.ops_completed.load(Ordering::SeqCst), 1);
@@ -471,6 +490,7 @@ mod tests {
const control = new Uint8Array([4, 5, 6]);
const data = new Uint8Array([42, 43, 44, 45, 46]);
let r = libdeno.send(control, data);
+ libdeno.recv(() => {});
if (r != null) throw Error("expected null");
"#,
).expect("execute error");
@@ -486,7 +506,7 @@ mod tests {
// with metrics_dispatch_async() to properly validate them.
}
- isolate.event_loop();
+ isolate.event_loop().unwrap();
// Make sure relevant metrics are updated after task is executed.
{
diff --git a/src/js_errors.rs b/src/js_errors.rs
new file mode 100644
index 000000000..63862db3f
--- /dev/null
+++ b/src/js_errors.rs
@@ -0,0 +1,543 @@
+// Copyright 2018 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.
+
+use serde_json;
+use source_map_mappings::parse_mappings;
+use source_map_mappings::Bias;
+use source_map_mappings::Mappings;
+use std::collections::HashMap;
+
+pub trait SourceMapGetter {
+ /// Returns the raw source map file.
+ fn get_source_map(&self, script_name: &str) -> Option<String>;
+}
+
+struct SourceMap {
+ mappings: Mappings,
+ sources: Vec<String>,
+}
+
+/// Cached filename lookups. The key can be None if a previous lookup failed to
+/// find a SourceMap.
+type CachedMaps = HashMap<String, Option<SourceMap>>;
+
+#[derive(Debug, PartialEq)]
+pub struct StackFrame {
+ pub line: u32, // zero indexed
+ pub column: u32, // zero indexed
+ pub source_url: String, // TODO rename to 'script_name'
+ pub function_name: String,
+ pub is_eval: bool,
+ pub is_constructor: bool,
+ pub is_wasm: bool,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct JSError {
+ pub message: String,
+ pub frames: Vec<StackFrame>,
+}
+
+impl ToString for StackFrame {
+ fn to_string(&self) -> String {
+ // Note when we print to string, we change from 0-indexed to 1-indexed.
+ let (line, column) = (self.line + 1, self.column + 1);
+ if self.function_name.len() > 0 {
+ format!(
+ " at {} ({}:{}:{})",
+ self.function_name, self.source_url, line, column
+ )
+ } else if self.is_eval {
+ format!(" at eval ({}:{}:{})", self.source_url, line, column)
+ } else {
+ format!(" at {}:{}:{}", self.source_url, line, column)
+ }
+ }
+}
+
+impl ToString for JSError {
+ fn to_string(&self) -> String {
+ let mut s = self.message.clone();
+ for frame in &self.frames {
+ s.push_str("\n");
+ s.push_str(&frame.to_string());
+ }
+ s
+ }
+}
+
+impl StackFrame {
+ // TODO Maybe use serde_derive?
+ fn from_json_value(v: &serde_json::Value) -> Option<Self> {
+ 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 u32;
+
+ let column_v = &obj["column"];
+ if !column_v.is_u64() {
+ return None;
+ }
+ let column = column_v.as_u64().unwrap() as u32;
+
+ 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,
+ source_url: script_name,
+ function_name,
+ is_eval,
+ is_constructor,
+ is_wasm,
+ })
+ }
+
+ fn apply_source_map(
+ &self,
+ mappings_map: &mut CachedMaps,
+ getter: &SourceMapGetter,
+ ) -> StackFrame {
+ let maybe_sm = get_mappings(self.source_url.as_ref(), mappings_map, getter);
+ let frame_pos = (self.source_url.to_owned(), self.line, self.column);
+ let (source_url, line, column) = match maybe_sm {
+ None => frame_pos,
+ Some(sm) => match sm.mappings.original_location_for(
+ self.line,
+ self.column,
+ 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,
+ original.original_line,
+ original.original_column,
+ )
+ }
+ },
+ },
+ };
+
+ StackFrame {
+ source_url,
+ 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<Self> {
+ // Ugly. Maybe use serde_derive.
+ match serde_json::from_str::<serde_json::Value>(json_str) {
+ Ok(serde_json::Value::Object(map)) => match map["mappings"].as_str() {
+ None => return None,
+ Some(mappings_str) => {
+ match parse_mappings::<()>(mappings_str.as_bytes()) {
+ Err(_) => return None,
+ Ok(mappings) => {
+ if !map["sources"].is_array() {
+ return None;
+ }
+ let sources_val = map["sources"].as_array().unwrap();
+ let mut sources = Vec::<String>::new();
+
+ for source_val in sources_val {
+ match source_val.as_str() {
+ None => return None,
+ Some(source) => {
+ sources.push(source.to_string());
+ }
+ }
+ }
+
+ return Some(SourceMap { sources, mappings });
+ }
+ }
+ }
+ },
+ _ => return None,
+ }
+ }
+}
+
+impl JSError {
+ /// Creates a new JSError by parsing the raw exception JSON string from V8.
+ pub fn from_v8_exception(json_str: &str) -> Option<Self> {
+ let v = serde_json::from_str::<serde_json::Value>(json_str);
+ if v.is_err() {
+ return None;
+ }
+ let v = v.unwrap();
+
+ 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 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(JSError { message, frames })
+ }
+
+ pub fn apply_source_map(&self, getter: &SourceMapGetter) -> Self {
+ let message = self.message.clone();
+ let mut mappings_map: CachedMaps = HashMap::new();
+ let mut frames = Vec::<StackFrame>::new();
+ for frame in &self.frames {
+ let f = frame.apply_source_map(&mut mappings_map, getter);
+ frames.push(f);
+ }
+ JSError { message, frames }
+ }
+}
+
+fn parse_map_string(
+ source_url: &str,
+ getter: &SourceMapGetter,
+) -> Option<SourceMap> {
+ match source_url {
+ "gen/bundle/main.js" => {
+ let s =
+ include_str!(concat!(env!("GN_OUT_DIR"), "/gen/bundle/main.js.map"));
+ SourceMap::from_json(s)
+ }
+ _ => match getter.get_source_map(source_url) {
+ None => None,
+ Some(raw_source_map) => SourceMap::from_json(&raw_source_map),
+ },
+ }
+}
+
+fn get_mappings<'a>(
+ source_url: &str,
+ mappings_map: &'a mut CachedMaps,
+ getter: &SourceMapGetter,
+) -> &'a Option<SourceMap> {
+ mappings_map
+ .entry(source_url.to_string())
+ .or_insert_with(|| parse_map_string(source_url, getter))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ fn error1() -> JSError {
+ JSError {
+ message: "Error: foo bar".to_string(),
+ frames: vec![
+ StackFrame {
+ line: 4,
+ column: 16,
+ source_url: "foo_bar.ts".to_string(),
+ function_name: "foo".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ },
+ StackFrame {
+ line: 5,
+ column: 20,
+ source_url: "bar_baz.ts".to_string(),
+ function_name: "qat".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ },
+ StackFrame {
+ line: 1,
+ column: 1,
+ source_url: "deno_main.js".to_string(),
+ function_name: "".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ },
+ ],
+ }
+ }
+
+ struct MockSourceMapGetter {}
+
+ impl SourceMapGetter for MockSourceMapGetter {
+ fn get_source_map(&self, script_name: &str) -> Option<String> {
+ let s = match script_name {
+ "foo_bar.ts" => r#"{"sources": ["foo_bar.ts"], "mappings":";;;IAIA,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IAC/C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IAE3C,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#,
+ "bar_baz.ts" => r#"{"sources": ["bar_baz.ts"], "mappings":";;;IAEA,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,GAAG,GAAG,sDAAa,OAAO,2BAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC,EAAE,CAAC;IAEQ,QAAA,GAAG,GAAG,KAAK,CAAC;IAEzB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC"}"#,
+ _ => return None,
+ };
+ Some(s.to_string())
+ }
+ }
+
+ #[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/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,
+ source_url: "/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::<serde_json::Value>(
+ 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.source_url, "/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,
+ source_url: "/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_to_string() {
+ let e = error1();
+ assert_eq!(" at foo (foo_bar.ts:5:17)", e.frames[0].to_string());
+ assert_eq!(" at qat (bar_baz.ts:6:21)", e.frames[1].to_string());
+ }
+
+ #[test]
+ fn js_error_to_string() {
+ let e = error1();
+ assert_eq!("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", e.to_string());
+ }
+
+ #[test]
+ fn js_error_apply_source_map_1() {
+ let e = error1();
+ let getter = MockSourceMapGetter {};
+ let actual = e.apply_source_map(&getter);
+ let expected = JSError {
+ message: "Error: foo bar".to_string(),
+ frames: vec![
+ StackFrame {
+ line: 5,
+ column: 12,
+ source_url: "foo_bar.ts".to_string(),
+ function_name: "foo".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ },
+ StackFrame {
+ line: 4,
+ column: 14,
+ source_url: "bar_baz.ts".to_string(),
+ function_name: "qat".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ },
+ StackFrame {
+ line: 1,
+ column: 1,
+ source_url: "deno_main.js".to_string(),
+ function_name: "".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ },
+ ],
+ };
+ assert_eq!(actual, expected);
+ }
+
+ #[test]
+ fn js_error_apply_source_map_2() {
+ // Because this is accessing the live bundle, this test might be more fragile
+ let e = JSError {
+ message: "TypeError: baz".to_string(),
+ frames: vec![StackFrame {
+ line: 11,
+ column: 12,
+ source_url: "gen/bundle/main.js".to_string(),
+ function_name: "setLogDebug".to_string(),
+ is_eval: false,
+ is_constructor: false,
+ is_wasm: false,
+ }],
+ };
+ let getter = MockSourceMapGetter {};
+ let actual = e.apply_source_map(&getter);
+ assert_eq!(actual.message, "TypeError: baz");
+ assert_eq!(actual.frames.len(), 1);
+ assert_eq!(actual.frames[0].line, 15);
+ assert_eq!(actual.frames[0].column, 16);
+ assert_eq!(actual.frames[0].source_url, "deno/js/util.ts");
+ }
+
+ #[test]
+ fn source_map_from_json() {
+ let json = r#"{"version":3,"file":"error_001.js","sourceRoot":"","sources":["file:///Users/rld/src/deno/tests/error_001.ts"],"names":[],"mappings":"AAAA,SAAS,GAAG;IACV,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,GAAG;IACV,GAAG,EAAE,CAAC;AACR,CAAC;AAED,GAAG,EAAE,CAAC"}"#;
+ let sm = SourceMap::from_json(json).unwrap();
+ assert_eq!(sm.sources.len(), 1);
+ assert_eq!(
+ sm.sources[0],
+ "file:///Users/rld/src/deno/tests/error_001.ts"
+ );
+ let mapping = sm
+ .mappings
+ .original_location_for(1, 10, Bias::default())
+ .unwrap();
+ assert_eq!(mapping.generated_line, 1);
+ assert_eq!(mapping.generated_column, 10);
+ assert_eq!(
+ mapping.original,
+ Some(source_map_mappings::OriginalLocation {
+ source: 0,
+ original_line: 1,
+ original_column: 8,
+ name: None
+ })
+ );
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index f2e241ab1..cdb7d8dd0 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -10,6 +10,8 @@ extern crate rand;
extern crate remove_dir_all;
extern crate ring;
extern crate rustyline;
+extern crate serde_json;
+extern crate source_map_mappings;
extern crate tempfile;
extern crate tokio;
extern crate tokio_executor;
@@ -33,6 +35,7 @@ mod fs;
mod http_body;
mod http_util;
pub mod isolate;
+pub mod js_errors;
pub mod libdeno;
pub mod msg;
pub mod msg_util;
@@ -68,6 +71,13 @@ impl log::Log for Logger {
fn flush(&self) {}
}
+fn print_err_and_exit(err: js_errors::JSError) {
+ // TODO Currently tests depend on exception going to stdout. It should go
+ // to stderr. https://github.com/denoland/deno/issues/964
+ println!("{}", err.to_string());
+ std::process::exit(1);
+}
+
fn main() {
// Rust does not die on panic by default. And -Cpanic=abort is broken.
// https://github.com/rust-lang/cargo/issues/2738
@@ -102,10 +112,7 @@ fn main() {
tokio_util::init(|| {
isolate
.execute("deno_main.js", "denoMain();")
- .unwrap_or_else(|err| {
- error!("{}", err);
- std::process::exit(1);
- });
- isolate.event_loop();
+ .unwrap_or_else(print_err_and_exit);
+ isolate.event_loop().unwrap_or_else(print_err_and_exit);
});
}
diff --git a/tests/async_error.ts.out b/tests/async_error.ts.out
index 1205f10b0..602d76424 100644
--- a/tests/async_error.ts.out
+++ b/tests/async_error.ts.out
@@ -4,8 +4,7 @@ world
Error: error
at foo ([WILDCARD]tests/async_error.ts:4:9)
at eval ([WILDCARD]tests/async_error.ts:7:1)
- at Runner.eval [as _globalEval] (<anonymous>)
- at Runner._gatherDependencies ([WILDCARD]/js/runner.ts:[WILDCARD])
- at Runner.run ([WILDCARD]/js/runner.ts:[WILDCARD])
+ at _gatherDependencies ([WILDCARD]/js/runner.ts:[WILDCARD])
+ at run ([WILDCARD]/js/runner.ts:[WILDCARD])
at denoMain ([WILDCARD]/js/main.ts:[WILDCARD])
at deno_main.js:1:1
diff --git a/tests/error_001.ts.out b/tests/error_001.ts.out
index 5d0c2244c..7ca70a6af 100644
--- a/tests/error_001.ts.out
+++ b/tests/error_001.ts.out
@@ -2,8 +2,7 @@ Error: bad
at foo (file://[WILDCARD]tests/error_001.ts:2:9)
at bar (file://[WILDCARD]tests/error_001.ts:6:3)
at eval (file://[WILDCARD]tests/error_001.ts:9:1)
- at Runner.eval [as _globalEval] (<anonymous>)
- at Runner._gatherDependencies ([WILDCARD]/js/runner.ts:[WILDCARD])
- at Runner.run ([WILDCARD]/js/runner.ts:[WILDCARD])
+ at _gatherDependencies ([WILDCARD]/js/runner.ts:[WILDCARD])
+ at run ([WILDCARD]/js/runner.ts:[WILDCARD])
at denoMain ([WILDCARD]/js/main.ts:[WILDCARD])
at deno_main.js:1:1
diff --git a/tests/error_002.ts.out b/tests/error_002.ts.out
index 082acfb33..88f5138f3 100644
--- a/tests/error_002.ts.out
+++ b/tests/error_002.ts.out
@@ -1,8 +1,8 @@
Error: exception from mod1
- at Object.throwsError (file://[WILDCARD]/tests/subdir/mod1.ts:16:9)
+ at throwsError (file://[WILDCARD]/tests/subdir/mod1.ts:16:9)
at foo (file://[WILDCARD]/tests/error_002.ts:4:3)
- at Module.eval [as factory ] (file://[WILDCARD]/tests/error_002.ts:7:1)
- at Runner._drainRunQueue ([WILDCARD]/js/runner.ts:[WILDCARD])
- at Runner.run ([WILDCARD]/js/runner.ts:[WILDCARD])
+ at eval (file://[WILDCARD]/tests/error_002.ts:7:1)
+ at _drainRunQueue ([WILDCARD]/js/runner.ts:[WILDCARD])
+ at run ([WILDCARD]/js/runner.ts:[WILDCARD])
at denoMain ([WILDCARD]/js/main.ts:[WILDCARD])
at deno_main.js:1:1
diff --git a/tests/error_004_missing_module.ts.out b/tests/error_004_missing_module.ts.out
index 061c64701..e78692489 100644
--- a/tests/error_004_missing_module.ts.out
+++ b/tests/error_004_missing_module.ts.out
@@ -1,11 +1,11 @@
NotFound: Cannot resolve module "bad-module.ts" from "[WILDCARD]/tests/error_004_missing_module.ts"
+ at DenoError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeThrowError ([WILDCARD]/js/errors.ts:[WILDCARD])
at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD])
- at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
- at Compiler._resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
+ at codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
+ at _resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD])
- at Array.map (<anonymous>)
- at Compiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
- at Object.compilerHost.resolveModuleNames (<anonymous>)
- at resolveModuleNamesWorker (<anonymous>)
+ at resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
+ at compilerHost.resolveModuleNames ([WILDCARD]typescript.js:[WILDCARD])
+ at resolveModuleNamesWorker ([WILDCARD]typescript.js:[WILDCARD])
diff --git a/tests/error_005_missing_dynamic_import.ts.out b/tests/error_005_missing_dynamic_import.ts.out
index 5eae8b73a..7cf79a9b4 100644
--- a/tests/error_005_missing_dynamic_import.ts.out
+++ b/tests/error_005_missing_dynamic_import.ts.out
@@ -1,11 +1,11 @@
NotFound: Cannot resolve module "bad-module.ts" from "[WILDCARD]/tests/error_005_missing_dynamic_import.ts"
+ at DenoError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeThrowError ([WILDCARD]/js/errors.ts:[WILDCARD])
at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD])
- at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
- at Compiler._resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
+ at codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
+ at _resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD])
- at Array.map (<anonymous>)
- at Compiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
- at Object.compilerHost.resolveModuleNames (<anonymous>)
- at resolveModuleNamesWorker (<anonymous>)
+ at resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
+ at compilerHost.resolveModuleNames ([WILDCARD])
+ at resolveModuleNamesWorker ([WILDCARD])
diff --git a/tests/error_006_import_ext_failure.ts.out b/tests/error_006_import_ext_failure.ts.out
index 72a1c7686..aa56cc316 100644
--- a/tests/error_006_import_ext_failure.ts.out
+++ b/tests/error_006_import_ext_failure.ts.out
@@ -1,11 +1,11 @@
NotFound: Cannot resolve module "./non-existent" from "[WILDCARD]/tests/error_006_import_ext_failure.ts"
+ at DenoError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeError ([WILDCARD]/js/errors.ts:[WILDCARD])
at maybeThrowError ([WILDCARD]/js/errors.ts:[WILDCARD])
at sendSync ([WILDCARD]/js/dispatch.ts:[WILDCARD])
- at Object.codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
- at Compiler._resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
+ at codeFetch ([WILDCARD]/js/os.ts:[WILDCARD])
+ at _resolveModule ([WILDCARD]/js/compiler.ts:[WILDCARD])
at moduleNames.map.name ([WILDCARD]/js/compiler.ts:[WILDCARD])
- at Array.map (<anonymous>)
- at Compiler.resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
- at Object.compilerHost.resolveModuleNames (<anonymous>)
- at resolveModuleNamesWorker (<anonymous>)
+ at resolveModuleNames ([WILDCARD]/js/compiler.ts:[WILDCARD])
+ at compilerHost.resolveModuleNames ([WILDCARD])
+ at resolveModuleNamesWorker ([WILDCARD])
diff --git a/tests/error_007_any.ts.out b/tests/error_007_any.ts.out
index 13335a92c..5bf110299 100644
--- a/tests/error_007_any.ts.out
+++ b/tests/error_007_any.ts.out
@@ -1 +1 @@
-Thrown: [object Object]
+[object Object]
diff --git a/third_party b/third_party
-Subproject e058979631fd3ecc55f8995a02eaa6ff8f35c32
+Subproject 7d8c9aa769778140e1619f545e706bf34545509