summaryrefslogtreecommitdiff
path: root/js
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2018-12-06 23:05:36 -0500
committerGitHub <noreply@github.com>2018-12-06 23:05:36 -0500
commitc113df1bb8a0c7d0c560ad32c0291c918c7da7b4 (patch)
tree0d15de448be602c22aecb2ec65ac7667c437a209 /js
parent568ac0c9026b6f4012e2511a026bb5eb31a06020 (diff)
Process source maps in Rust instead of JS (#1280)
- Improves speed and binary size significantly. - Makes deno_last_exception() output a JSON structure. - Isolate::execute and Isolate::event_loop now return structured, mapped JSError objects on errors. - Removes libdeno functions: libdeno.setGlobalErrorHandler() libdeno.setPromiseRejectHandler() libdeno.setPromiseErrorExaminer() In collaboration with Ryan Dahl.
Diffstat (limited to 'js')
-rw-r--r--js/compiler.ts48
-rw-r--r--js/libdeno.ts4
-rw-r--r--js/main.ts33
-rw-r--r--js/promise_util.ts50
-rw-r--r--js/unit_tests.ts1
-rw-r--r--js/v8_source_maps.ts285
-rw-r--r--js/v8_source_maps_test.ts17
7 files changed, 9 insertions, 429 deletions
diff --git a/js/compiler.ts b/js/compiler.ts
index c9fa4611c..e0bfb77bb 100644
--- a/js/compiler.ts
+++ b/js/compiler.ts
@@ -3,10 +3,8 @@ import * as ts from "typescript";
import { MediaType } from "gen/msg_generated";
import { assetSourceCode } from "./assets";
-import { libdeno } from "./libdeno";
import * as os from "./os";
import { CodeProvider } from "./runner";
-import { RawSourceMap } from "./types";
import { assert, log, notImplemented } from "./util";
const EOL = "\n";
@@ -71,7 +69,7 @@ export class ModuleMetaData implements ts.IScriptSnapshot {
public readonly mediaType: MediaType,
public readonly sourceCode: SourceCode = "",
public outputCode: OutputCode = "",
- public sourceMap: SourceMap | RawSourceMap = ""
+ public sourceMap: SourceMap = ""
) {
if (outputCode !== "" || fileName.endsWith(".d.ts")) {
this.scriptVersion = "1";
@@ -131,8 +129,6 @@ export class Compiler
ContainingFile,
Map<ModuleSpecifier, ModuleFileName>
>();
- // Keep track of state of the last module requested via `getGeneratedContents`
- private _lastModule: ModuleMetaData | undefined;
// A reference to the log utility, so it can be monkey patched during testing
private _log = log;
// A map of module file names to module meta data
@@ -369,19 +365,15 @@ export class Compiler
moduleMetaData.outputCode = `${
outputFile.text
}\n//# sourceURL=${fileName}`;
- moduleMetaData.sourceMap = JSON.parse(sourceMapFile.text);
+ moduleMetaData.sourceMap = sourceMapFile.text;
}
moduleMetaData.scriptVersion = "1";
- const sourceMap =
- moduleMetaData.sourceMap === "string"
- ? moduleMetaData.sourceMap
- : JSON.stringify(moduleMetaData.sourceMap);
this._os.codeCache(
fileName,
sourceCode,
moduleMetaData.outputCode,
- sourceMap
+ moduleMetaData.sourceMap
);
return moduleMetaData.outputCode;
}
@@ -398,36 +390,10 @@ export class Compiler
}
/** Given a fileName, return what was generated by the compiler. */
- getGeneratedContents = (fileName: string): string | RawSourceMap => {
- this._log("compiler.getGeneratedContents", fileName);
- if (fileName === "gen/bundle/main.js") {
- assert(libdeno.mainSource.length > 0);
- return libdeno.mainSource;
- } else if (fileName === "main.js.map") {
- return libdeno.mainSourceMap;
- } else if (fileName === "deno_main.js") {
- return "";
- } else if (!fileName.endsWith(".map")) {
- const moduleMetaData = this._moduleMetaDataMap.get(fileName);
- if (!moduleMetaData) {
- this._lastModule = undefined;
- return "";
- }
- this._lastModule = moduleMetaData;
- return moduleMetaData.outputCode;
- } else {
- if (this._lastModule && this._lastModule.sourceMap) {
- // Assuming the the map will always be asked for after the source
- // code.
- const { sourceMap } = this._lastModule;
- this._lastModule = undefined;
- return sourceMap;
- } else {
- // Errors thrown here are caught by source-map.
- throw new Error(`Unable to find source map: "${fileName}"`);
- }
- }
- };
+ getGeneratedSourceMap(fileName: string): string {
+ const moduleMetaData = this._moduleMetaDataMap.get(fileName);
+ return moduleMetaData ? moduleMetaData.sourceMap : "";
+ }
/** Get the output code for a module based on its filename. A call to
* `.getFilename()` should occur before attempting to get the output code as
diff --git a/js/libdeno.ts b/js/libdeno.ts
index c61027324..5448165a1 100644
--- a/js/libdeno.ts
+++ b/js/libdeno.ts
@@ -1,5 +1,4 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { RawSourceMap } from "./types";
import { globalEval } from "./global_eval";
// The libdeno functions are moved so that users can't access them.
@@ -39,9 +38,6 @@ interface Libdeno {
) => void;
setPromiseErrorExaminer: (handler: () => boolean) => void;
-
- mainSource: string;
- mainSourceMap: RawSourceMap;
}
const window = globalEval("this");
diff --git a/js/main.ts b/js/main.ts
index 241e8d7d1..818713593 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -11,22 +11,9 @@ import { Runner } from "./runner";
import { libdeno } from "./libdeno";
import { args } from "./deno";
import { sendSync, handleAsyncMsgFromRust } from "./dispatch";
-import { promiseErrorExaminer, promiseRejectHandler } from "./promise_util";
import { replLoop } from "./repl";
-import * as sourceMaps from "./v8_source_maps";
import { version } from "typescript";
-// Install the source maps handler and do some pre-calculations so all of it is
-// available in the snapshot
-const compiler = Compiler.instance();
-sourceMaps.install({
- installPrepareStackTrace: true,
- getGeneratedContents: compiler.getGeneratedContents
-});
-const consumer = sourceMaps.loadConsumer("gen/bundle/main.js");
-assert(consumer != null);
-consumer!.computeColumnSpans();
-
function sendStart(): msg.StartRes {
const builder = flatbuffers.createBuilder();
msg.Start.startStart(builder);
@@ -39,27 +26,9 @@ function sendStart(): msg.StartRes {
return startRes;
}
-function onGlobalError(
- message: string,
- source: string,
- lineno: number,
- colno: number,
- error: any // tslint:disable-line:no-any
-) {
- if (error instanceof Error) {
- console.log(error.stack);
- } else {
- console.log(`Thrown: ${String(error)}`);
- }
- os.exit(1);
-}
-
/* tslint:disable-next-line:no-default-export */
export default function denoMain() {
libdeno.recv(handleAsyncMsgFromRust);
- libdeno.setGlobalErrorHandler(onGlobalError);
- libdeno.setPromiseRejectHandler(promiseRejectHandler);
- libdeno.setPromiseErrorExaminer(promiseErrorExaminer);
// First we send an empty "Start" message to let the privileged side know we
// are ready. The response should be a "StartRes" message containing the CLI
@@ -68,6 +37,8 @@ export default function denoMain() {
setLogDebug(startResMsg.debugFlag());
+ const compiler = Compiler.instance();
+
// handle `--types`
if (startResMsg.typesFlag()) {
const defaultLibFileName = compiler.getDefaultLibFileName();
diff --git a/js/promise_util.ts b/js/promise_util.ts
deleted file mode 100644
index 3c789124d..000000000
--- a/js/promise_util.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { PromiseRejectEvent } from "./libdeno";
-
-/* tslint:disable-next-line:no-any */
-const rejectMap = new Map<Promise<any>, string>();
-// For uncaught promise rejection errors
-
-/* tslint:disable-next-line:no-any */
-const otherErrorMap = new Map<Promise<any>, string>();
-// For reject after resolve / resolve after resolve errors
-
-export function promiseRejectHandler(
- error: Error | string,
- event: PromiseRejectEvent,
- /* tslint:disable-next-line:no-any */
- promise: Promise<any>
-) {
- switch (event) {
- case "RejectWithNoHandler":
- rejectMap.set(promise, (error as Error).stack || "RejectWithNoHandler");
- break;
- case "HandlerAddedAfterReject":
- rejectMap.delete(promise);
- break;
- case "ResolveAfterResolved":
- // Should not warn. See #1272
- break;
- default:
- // error is string here
- otherErrorMap.set(promise, `Promise warning: ${error as string}`);
- }
-}
-
-// Return true when continue, false to die on uncaught promise reject
-export function promiseErrorExaminer(): boolean {
- if (otherErrorMap.size > 0) {
- for (const msg of otherErrorMap.values()) {
- console.log(msg);
- }
- otherErrorMap.clear();
- }
- if (rejectMap.size > 0) {
- for (const msg of rejectMap.values()) {
- console.log(msg);
- }
- rejectMap.clear();
- return false;
- }
- return true;
-}
diff --git a/js/unit_tests.ts b/js/unit_tests.ts
index 4863b7ac4..55877091e 100644
--- a/js/unit_tests.ts
+++ b/js/unit_tests.ts
@@ -35,5 +35,4 @@ import "./text_encoding_test.ts";
import "./timers_test.ts";
import "./truncate_test.ts";
import "./url_search_params_test.ts";
-import "./v8_source_maps_test.ts";
import "./write_file_test.ts";
diff --git a/js/v8_source_maps.ts b/js/v8_source_maps.ts
deleted file mode 100644
index dcdde16c7..000000000
--- a/js/v8_source_maps.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright 2014 Evan Wallace
-// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-// Originated from source-map-support but has been heavily modified for deno.
-
-import { SourceMapConsumer, MappedPosition } from "source-map";
-import { CallSite, RawSourceMap } from "./types";
-import { atob } from "./text_encoding";
-
-const consumers = new Map<string, SourceMapConsumer>();
-
-interface Options {
- // A callback the returns generated file contents.
- getGeneratedContents: GetGeneratedContentsCallback;
- // Usually set the following to true. Set to false for testing.
- installPrepareStackTrace: boolean;
-}
-
-interface Position {
- source: string; // Filename
- column: number;
- line: number;
-}
-
-type GetGeneratedContentsCallback = (fileName: string) => string | RawSourceMap;
-
-let getGeneratedContents: GetGeneratedContentsCallback;
-
-// @internal
-export function install(options: Options) {
- getGeneratedContents = options.getGeneratedContents;
- if (options.installPrepareStackTrace) {
- Error.prepareStackTrace = prepareStackTraceWrapper;
- }
-}
-
-// @internal
-export function prepareStackTraceWrapper(
- error: Error,
- stack: CallSite[]
-): string {
- try {
- return prepareStackTrace(error, stack);
- } catch (prepareStackError) {
- Error.prepareStackTrace = undefined;
- console.log("=====Error inside of prepareStackTrace====");
- console.log(prepareStackError.stack.toString());
- console.log("=====Original error=======================");
- throw error;
- }
-}
-
-// @internal
-export function prepareStackTrace(error: Error, stack: CallSite[]): string {
- const frames = stack.map(
- frame => `\n at ${wrapCallSite(frame).toString()}`
- );
- return `${error.toString()}${frames.join("")}`;
-}
-
-// @internal
-export function wrapCallSite(frame: CallSite): CallSite {
- if (frame.isNative()) {
- return frame;
- }
-
- // Most call sites will return the source file from getFileName(), but code
- // passed to eval() ending in "//# sourceURL=..." will return the source file
- // from getScriptNameOrSourceURL() instead
- const source = frame.getFileName() || frame.getScriptNameOrSourceURL();
-
- if (source) {
- const line = frame.getLineNumber() || 0;
- const column = (frame.getColumnNumber() || 1) - 1;
- const position = mapSourcePosition({ source, line, column });
- frame = cloneCallSite(frame);
- Object.assign(frame, {
- getFileName: () => position.source,
- getLineNumber: () => position.line,
- getColumnNumber: () => Number(position.column) + 1,
- getScriptNameOrSourceURL: () => position.source,
- toString: () => CallSiteToString(frame)
- });
- return frame;
- }
-
- // Code called using eval() needs special handling
- let origin = (frame.isEval() && frame.getEvalOrigin()) || undefined;
- if (origin) {
- origin = mapEvalOrigin(origin);
- frame = cloneCallSite(frame);
- Object.assign(frame, {
- getEvalOrigin: () => origin,
- toString: () => CallSiteToString(frame)
- });
- return frame;
- }
-
- // If we get here then we were unable to change the source position
- return frame;
-}
-
-function cloneCallSite(
- frame: CallSite
- // mixin: Partial<CallSite> & { toString: () => string }
-): CallSite {
- const obj = {} as CallSite;
- const props = Object.getOwnPropertyNames(
- Object.getPrototypeOf(frame)
- ) as Array<keyof CallSite>;
- for (const name of props) {
- obj[name] = /^(?:is|get)/.test(name)
- ? () => frame[name].call(frame)
- : frame[name];
- }
- return obj;
-}
-
-// Taken from source-map-support, original copied from V8's messages.js
-// MIT License. Copyright (c) 2014 Evan Wallace
-function CallSiteToString(frame: CallSite): string {
- let fileLocation = "";
- if (frame.isNative()) {
- fileLocation = "native";
- } else {
- const fileName = frame.getScriptNameOrSourceURL();
- if (!fileName && frame.isEval()) {
- fileLocation = frame.getEvalOrigin() || "";
- fileLocation += ", "; // Expecting source position to follow.
- }
-
- if (fileName) {
- fileLocation += fileName;
- } else {
- // Source code does not originate from a file and is not native, but we
- // can still get the source position inside the source string, e.g. in
- // an eval string.
- fileLocation += "<anonymous>";
- }
- const lineNumber = frame.getLineNumber();
- if (lineNumber != null) {
- fileLocation += `:${lineNumber}`;
- const columnNumber = frame.getColumnNumber();
- if (columnNumber) {
- fileLocation += `:${columnNumber}`;
- }
- }
- }
-
- let line = "";
- const functionName = frame.getFunctionName();
- let addSuffix = true;
- const isConstructor = frame.isConstructor();
- const isMethodCall = !(frame.isToplevel() || isConstructor);
- if (isMethodCall) {
- let typeName = frame.getTypeName();
- // Fixes shim to be backward compatible with Node v0 to v4
- if (typeName === "[object Object]") {
- typeName = "null";
- }
- const methodName = frame.getMethodName();
- if (functionName) {
- if (typeName && functionName.indexOf(typeName) !== 0) {
- line += `${typeName}.`;
- }
- line += functionName;
- if (
- methodName &&
- functionName.indexOf("." + methodName) !==
- functionName.length - methodName.length - 1
- ) {
- line += ` [as ${methodName} ]`;
- }
- } else {
- line += `${typeName}.${methodName || "<anonymous>"}`;
- }
- } else if (isConstructor) {
- line += `new ${functionName || "<anonymous>"}`;
- } else if (functionName) {
- line += functionName;
- } else {
- line += fileLocation;
- addSuffix = false;
- }
- if (addSuffix) {
- line += ` (${fileLocation})`;
- }
- return line;
-}
-
-// Regex for detecting source maps
-const reSourceMap = /^data:application\/json[^,]+base64,/;
-
-export function loadConsumer(source: string): SourceMapConsumer | null {
- let consumer = consumers.get(source);
- if (consumer == null) {
- const code = getGeneratedContents(source);
- if (!code) {
- return null;
- }
- if (typeof code !== "string") {
- throw new Error("expected string");
- }
-
- let sourceMappingURL = retrieveSourceMapURL(code);
- if (!sourceMappingURL) {
- throw Error("No source map?");
- }
-
- let sourceMapData: string | RawSourceMap;
- if (reSourceMap.test(sourceMappingURL)) {
- // Support source map URL as a data url
- const rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(",") + 1);
- sourceMapData = atob(rawData);
- sourceMappingURL = source;
- } else {
- // TODO Support source map URLs relative to the source URL
- // sourceMappingURL = supportRelativeURL(source, sourceMappingURL);
- sourceMapData = getGeneratedContents(sourceMappingURL);
- }
-
- const rawSourceMap =
- typeof sourceMapData === "string"
- ? (JSON.parse(sourceMapData) as RawSourceMap)
- : sourceMapData;
- consumer = new SourceMapConsumer(rawSourceMap);
- consumers.set(source, consumer);
- }
- return consumer;
-}
-
-// tslint:disable-next-line:max-line-length
-const sourceMapUrlRe = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/gm;
-
-function retrieveSourceMapURL(fileData: string): string | null {
- // Keep executing the search to find the *last* sourceMappingURL to avoid
- // picking up sourceMappingURLs from comments, strings, etc.
- let lastMatch, match;
- while ((match = sourceMapUrlRe.exec(fileData))) {
- lastMatch = match;
- }
- if (!lastMatch) {
- return null;
- }
- return lastMatch[1];
-}
-
-export function mapSourcePosition(position: Position): MappedPosition {
- const consumer = loadConsumer(position.source);
- if (consumer == null) {
- return position;
- }
- return consumer.originalPositionFor(position);
-}
-
-const stackEvalRe = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/;
-const nestedEvalRe = /^eval at ([^(]+) \((.+)\)$/;
-
-// Parses code generated by FormatEvalOrigin(), a function inside V8:
-// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
-function mapEvalOrigin(origin: string): string {
- // Most eval() calls are in this format
- let match = stackEvalRe.exec(origin);
- if (match) {
- const position = mapSourcePosition({
- source: match[2],
- line: Number(match[3]),
- column: Number(match[4]) - 1
- });
- const pos = [
- position.source,
- position.line,
- Number(position.column) + 1
- ].join(":");
- return `eval at ${match[1]} (${pos})`;
- }
-
- // Parse nested eval() calls using recursion
- match = nestedEvalRe.exec(origin);
- if (match) {
- return `eval at ${match[1]} (${mapEvalOrigin(match[2])})`;
- }
-
- // Make sure we still return useful information if we didn't find anything
- return origin;
-}
diff --git a/js/v8_source_maps_test.ts b/js/v8_source_maps_test.ts
deleted file mode 100644
index 90c123431..000000000
--- a/js/v8_source_maps_test.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2018 the Deno authors. All rights reserved. MIT license.
-import { test, assert, assertEqual } from "./test_util.ts";
-
-// This test demonstrates a bug:
-// https://github.com/denoland/deno/issues/808
-test(function evalErrorFormatted() {
- let err;
- try {
- eval("boom");
- } catch (e) {
- err = e;
- }
- assert(!!err);
- // tslint:disable-next-line:no-unused-expression
- err.stack; // This would crash if err.stack is malformed
- assertEqual(err.name, "ReferenceError");
-});