0
0
Fork 0
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:
Ryan Dahl 2018-12-06 23:05:36 -05:00 committed by GitHub
parent 568ac0c902
commit c113df1bb8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 854 additions and 807 deletions

View file

@ -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",

View file

@ -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"

View file

@ -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",
]
}

View file

@ -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

View file

@ -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");

View file

@ -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();

View file

@ -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;
}

View file

@ -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";

View file

@ -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;
}

View file

@ -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");
});

View file

@ -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);
}
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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);
}

View file

@ -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;
}
};

View file

@ -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);

View file

@ -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",

View file

@ -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() {

View file

@ -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
View 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
})
);
}
}

View file

@ -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);
});
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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])

View file

@ -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])

View file

@ -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])

View file

@ -1 +1 @@
Thrown: [object Object]
[object Object]

@ -1 +1 @@
Subproject commit e058979631fd3ecc55f8995a02eaa6ff8f35c321
Subproject commit 7d8c9aa769778140e1619f545e706bf34545509e