mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 09:31:22 -05:00
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.
This commit is contained in:
parent
568ac0c902
commit
c113df1bb8
30 changed files with 854 additions and 807 deletions
4
BUILD.gn
4
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",
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
33
js/main.ts
33
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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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");
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
// Encode the exception into a JS object, which we will then turn into JSON.
|
||||
auto json_obj = v8::Object::New(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 */
|
||||
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());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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";
|
||||
uint32_t count = static_cast<uint32_t>(stack_trace->GetFrameCount());
|
||||
frames = v8::Array::New(isolate, count);
|
||||
|
||||
for (int i = 0; i < stack_trace->GetFrameCount(); ++i) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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.
|
||||
{
|
||||
|
|
543
src/js_errors.rs
Normal file
543
src/js_errors.rs
Normal file
|
@ -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
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
17
src/main.rs
17
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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -1 +1 @@
|
|||
Thrown: [object Object]
|
||||
[object Object]
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit e058979631fd3ecc55f8995a02eaa6ff8f35c321
|
||||
Subproject commit 7d8c9aa769778140e1619f545e706bf34545509e
|
Loading…
Add table
Reference in a new issue