diff options
-rw-r--r-- | cli/rt/40_error_stack.js | 226 | ||||
-rw-r--r-- | cli/rt/99_main.js | 9 | ||||
-rw-r--r-- | cli/tests/unit/error_stack_test.ts | 99 | ||||
-rw-r--r-- | core/error.js | 247 | ||||
-rw-r--r-- | core/runtime.rs | 3 |
5 files changed, 258 insertions, 326 deletions
diff --git a/cli/rt/40_error_stack.js b/cli/rt/40_error_stack.js index 834503e34..da2ee51f3 100644 --- a/cli/rt/40_error_stack.js +++ b/cli/rt/40_error_stack.js @@ -1,11 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. ((window) => { - // Some of the code here is adapted directly from V8 and licensed under a BSD - // style license available here: https://github.com/v8/v8/blob/24886f2d1c565287d33d71e4109a53bf0b54b75c/LICENSE.v8 const core = window.Deno.core; - const assert = window.__bootstrap.util.assert; - const internals = window.__bootstrap.internals; function opFormatDiagnostics(diagnostics) { return core.jsonOpSync("op_format_diagnostic", diagnostics); @@ -20,229 +16,7 @@ }; } - function patchCallSite(callSite, location) { - return { - getThis() { - return callSite.getThis(); - }, - getTypeName() { - return callSite.getTypeName(); - }, - getFunction() { - return callSite.getFunction(); - }, - getFunctionName() { - return callSite.getFunctionName(); - }, - getMethodName() { - return callSite.getMethodName(); - }, - getFileName() { - return location.fileName; - }, - getLineNumber() { - return location.lineNumber; - }, - getColumnNumber() { - return location.columnNumber; - }, - getEvalOrigin() { - return callSite.getEvalOrigin(); - }, - isToplevel() { - return callSite.isToplevel(); - }, - isEval() { - return callSite.isEval(); - }, - isNative() { - return callSite.isNative(); - }, - isConstructor() { - return callSite.isConstructor(); - }, - isAsync() { - return callSite.isAsync(); - }, - isPromiseAll() { - return callSite.isPromiseAll(); - }, - getPromiseIndex() { - return callSite.getPromiseIndex(); - }, - }; - } - - // Keep in sync with `cli/fmt_errors.rs`. - function formatLocation(callSite) { - if (callSite.isNative()) { - return "native"; - } - - let result = ""; - - const fileName = callSite.getFileName(); - - if (fileName) { - result += fileName; - } else { - if (callSite.isEval()) { - const evalOrigin = callSite.getEvalOrigin(); - assert(evalOrigin != null); - result += `${evalOrigin}, `; - } - result += "<anonymous>"; - } - - const lineNumber = callSite.getLineNumber(); - if (lineNumber != null) { - result += `:${lineNumber}`; - - const columnNumber = callSite.getColumnNumber(); - if (columnNumber != null) { - result += `:${columnNumber}`; - } - } - - return result; - } - - // Keep in sync with `cli/fmt_errors.rs`. - function formatCallSite(callSite) { - let result = ""; - const functionName = callSite.getFunctionName(); - - const isTopLevel = callSite.isToplevel(); - const isAsync = callSite.isAsync(); - const isPromiseAll = callSite.isPromiseAll(); - const isConstructor = callSite.isConstructor(); - const isMethodCall = !(isTopLevel || isConstructor); - - if (isAsync) { - result += "async "; - } - if (isPromiseAll) { - result += `Promise.all (index ${callSite.getPromiseIndex()})`; - return result; - } - if (isMethodCall) { - const typeName = callSite.getTypeName(); - const methodName = callSite.getMethodName(); - - if (functionName) { - if (typeName) { - if (!functionName.startsWith(typeName)) { - result += `${typeName}.`; - } - } - result += functionName; - if (methodName) { - if (!functionName.endsWith(methodName)) { - result += ` [as ${methodName}]`; - } - } - } else { - if (typeName) { - result += `${typeName}.`; - } - if (methodName) { - result += methodName; - } else { - result += "<anonymous>"; - } - } - } else if (isConstructor) { - result += "new "; - if (functionName) { - result += functionName; - } else { - result += "<anonymous>"; - } - } else if (functionName) { - result += functionName; - } else { - result += formatLocation(callSite); - return result; - } - - result += ` (${formatLocation(callSite)})`; - return result; - } - - function evaluateCallSite(callSite) { - return { - this: callSite.getThis(), - typeName: callSite.getTypeName(), - function: callSite.getFunction(), - functionName: callSite.getFunctionName(), - methodName: callSite.getMethodName(), - fileName: callSite.getFileName(), - lineNumber: callSite.getLineNumber(), - columnNumber: callSite.getColumnNumber(), - evalOrigin: callSite.getEvalOrigin(), - isToplevel: callSite.isToplevel(), - isEval: callSite.isEval(), - isNative: callSite.isNative(), - isConstructor: callSite.isConstructor(), - isAsync: callSite.isAsync(), - isPromiseAll: callSite.isPromiseAll(), - promiseIndex: callSite.getPromiseIndex(), - }; - } - - function prepareStackTrace( - error, - callSites, - ) { - const mappedCallSites = callSites.map( - (callSite) => { - const fileName = callSite.getFileName(); - const lineNumber = callSite.getLineNumber(); - const columnNumber = callSite.getColumnNumber(); - if (fileName && lineNumber != null && columnNumber != null) { - return patchCallSite( - callSite, - opApplySourceMap({ - fileName, - lineNumber, - columnNumber, - }), - ); - } - return callSite; - }, - ); - Object.defineProperties(error, { - __callSiteEvals: { value: [], configurable: true }, - }); - const formattedCallSites = []; - for (const callSite of mappedCallSites) { - error.__callSiteEvals.push(Object.freeze(evaluateCallSite(callSite))); - formattedCallSites.push(formatCallSite(callSite)); - } - Object.freeze(error.__callSiteEvals); - const message = error.message !== undefined ? error.message : ""; - const name = error.name !== undefined ? error.name : "Error"; - let messageLine; - if (name != "" && message != "") { - messageLine = `${name}: ${message}`; - } else if ((name || message) != "") { - messageLine = name || message; - } else { - messageLine = ""; - } - return messageLine + - formattedCallSites.map((s) => `\n at ${s}`).join(""); - } - - function setPrepareStackTrace(ErrorConstructor) { - ErrorConstructor.prepareStackTrace = prepareStackTrace; - } - - internals.exposeForTest("setPrepareStackTrace", setPrepareStackTrace); - window.__bootstrap.errorStack = { - setPrepareStackTrace, opApplySourceMap, opFormatDiagnostics, }; diff --git a/cli/rt/99_main.js b/cli/rt/99_main.js index 40c9c636f..2aa140990 100644 --- a/cli/rt/99_main.js +++ b/cli/rt/99_main.js @@ -164,9 +164,16 @@ delete Object.prototype.__proto__; // TODO(bartlomieju): a very crude way to disable // source mapping of errors. This condition is true // only for compiled standalone binaries. + let prepareStackTrace; if (s.applySourceMaps) { - errorStack.setPrepareStackTrace(Error); + prepareStackTrace = core.createPrepareStackTrace( + errorStack.opApplySourceMap, + ); + } else { + prepareStackTrace = core.createPrepareStackTrace(); } + Error.prepareStackTrace = prepareStackTrace; + return s; } diff --git a/cli/tests/unit/error_stack_test.ts b/cli/tests/unit/error_stack_test.ts index a5fe13796..ad5f2e093 100644 --- a/cli/tests/unit/error_stack_test.ts +++ b/cli/tests/unit/error_stack_test.ts @@ -1,87 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { assert, assertEquals, assertMatch, unitTest } from "./test_util.ts"; -// @ts-expect-error TypeScript (as of 3.7) does not support indexing namespaces by symbol -const { setPrepareStackTrace } = Deno[Deno.internal]; - -interface CallSite { - getThis(): unknown; - getTypeName(): string | null; - // deno-lint-ignore ban-types - getFunction(): Function | null; - getFunctionName(): string | null; - getMethodName(): string | null; - getFileName(): string | null; - getLineNumber(): number | null; - getColumnNumber(): number | null; - getEvalOrigin(): string | null; - isToplevel(): boolean | null; - isEval(): boolean; - isNative(): boolean; - isConstructor(): boolean; - isAsync(): boolean; - isPromiseAll(): boolean; - getPromiseIndex(): number | null; -} - -function getMockCallSite( - fileName: string, - lineNumber: number | null, - columnNumber: number | null, -): CallSite { - return { - getThis(): unknown { - return undefined; - }, - getTypeName(): string { - return ""; - }, - // deno-lint-ignore ban-types - getFunction(): Function { - return (): void => {}; - }, - getFunctionName(): string { - return ""; - }, - getMethodName(): string { - return ""; - }, - getFileName(): string { - return fileName; - }, - getLineNumber(): number | null { - return lineNumber; - }, - getColumnNumber(): number | null { - return columnNumber; - }, - getEvalOrigin(): null { - return null; - }, - isToplevel(): false { - return false; - }, - isEval(): false { - return false; - }, - isNative(): false { - return false; - }, - isConstructor(): false { - return false; - }, - isAsync(): false { - return false; - }, - isPromiseAll(): false { - return false; - }, - getPromiseIndex(): null { - return null; - }, - }; -} - unitTest(function errorStackMessageLine(): void { const e1 = new Error(); e1.name = "Foo"; @@ -122,24 +41,6 @@ unitTest(function errorStackMessageLine(): void { assertMatch(e6.stack!, /^null: null\n/); }); -// FIXME(bartlomieju): no longer works after migrating -// to JavaScript runtime code -unitTest({ ignore: true }, function prepareStackTrace(): void { - // deno-lint-ignore no-explicit-any - const MockError = {} as any; - setPrepareStackTrace(MockError); - assert(typeof MockError.prepareStackTrace === "function"); - const prepareStackTrace: ( - error: Error, - structuredStackTrace: CallSite[], - ) => string = MockError.prepareStackTrace; - const result = prepareStackTrace(new Error("foo"), [ - getMockCallSite("CLI_SNAPSHOT.js", 23, 0), - ]); - assert(result.startsWith("Error: foo\n")); - assert(result.includes(".ts:"), "should remap to something in 'js/'"); -}); - unitTest(function captureStackTrace(): void { function foo(): void { const error = new Error(); diff --git a/core/error.js b/core/error.js new file mode 100644 index 000000000..708b1fd4a --- /dev/null +++ b/core/error.js @@ -0,0 +1,247 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +((window) => { + // Some of the code here is adapted directly from V8 and licensed under a BSD + // style license available here: https://github.com/v8/v8/blob/24886f2d1c565287d33d71e4109a53bf0b54b75c/LICENSE.v8 + function patchCallSite(callSite, location) { + return { + getThis() { + return callSite.getThis(); + }, + getTypeName() { + return callSite.getTypeName(); + }, + getFunction() { + return callSite.getFunction(); + }, + getFunctionName() { + return callSite.getFunctionName(); + }, + getMethodName() { + return callSite.getMethodName(); + }, + getFileName() { + return location.fileName; + }, + getLineNumber() { + return location.lineNumber; + }, + getColumnNumber() { + return location.columnNumber; + }, + getEvalOrigin() { + return callSite.getEvalOrigin(); + }, + isToplevel() { + return callSite.isToplevel(); + }, + isEval() { + return callSite.isEval(); + }, + isNative() { + return callSite.isNative(); + }, + isConstructor() { + return callSite.isConstructor(); + }, + isAsync() { + return callSite.isAsync(); + }, + isPromiseAll() { + return callSite.isPromiseAll(); + }, + getPromiseIndex() { + return callSite.getPromiseIndex(); + }, + }; + } + + // Keep in sync with `cli/fmt_errors.rs`. + function formatLocation(callSite) { + if (callSite.isNative()) { + return "native"; + } + + let result = ""; + + const fileName = callSite.getFileName(); + + if (fileName) { + result += fileName; + } else { + if (callSite.isEval()) { + const evalOrigin = callSite.getEvalOrigin(); + if (evalOrigin == null) { + throw new Error("assert evalOrigin"); + } + result += `${evalOrigin}, `; + } + result += "<anonymous>"; + } + + const lineNumber = callSite.getLineNumber(); + if (lineNumber != null) { + result += `:${lineNumber}`; + + const columnNumber = callSite.getColumnNumber(); + if (columnNumber != null) { + result += `:${columnNumber}`; + } + } + + return result; + } + + // Keep in sync with `cli/fmt_errors.rs`. + function formatCallSite(callSite) { + let result = ""; + const functionName = callSite.getFunctionName(); + + const isTopLevel = callSite.isToplevel(); + const isAsync = callSite.isAsync(); + const isPromiseAll = callSite.isPromiseAll(); + const isConstructor = callSite.isConstructor(); + const isMethodCall = !(isTopLevel || isConstructor); + + if (isAsync) { + result += "async "; + } + if (isPromiseAll) { + result += `Promise.all (index ${callSite.getPromiseIndex()})`; + return result; + } + if (isMethodCall) { + const typeName = callSite.getTypeName(); + const methodName = callSite.getMethodName(); + + if (functionName) { + if (typeName) { + if (!functionName.startsWith(typeName)) { + result += `${typeName}.`; + } + } + result += functionName; + if (methodName) { + if (!functionName.endsWith(methodName)) { + result += ` [as ${methodName}]`; + } + } + } else { + if (typeName) { + result += `${typeName}.`; + } + if (methodName) { + result += methodName; + } else { + result += "<anonymous>"; + } + } + } else if (isConstructor) { + result += "new "; + if (functionName) { + result += functionName; + } else { + result += "<anonymous>"; + } + } else if (functionName) { + result += functionName; + } else { + result += formatLocation(callSite); + return result; + } + + result += ` (${formatLocation(callSite)})`; + return result; + } + + function evaluateCallSite(callSite) { + return { + this: callSite.getThis(), + typeName: callSite.getTypeName(), + function: callSite.getFunction(), + functionName: callSite.getFunctionName(), + methodName: callSite.getMethodName(), + fileName: callSite.getFileName(), + lineNumber: callSite.getLineNumber(), + columnNumber: callSite.getColumnNumber(), + evalOrigin: callSite.getEvalOrigin(), + isToplevel: callSite.isToplevel(), + isEval: callSite.isEval(), + isNative: callSite.isNative(), + isConstructor: callSite.isConstructor(), + isAsync: callSite.isAsync(), + isPromiseAll: callSite.isPromiseAll(), + promiseIndex: callSite.getPromiseIndex(), + }; + } + + /** + * Returns a function that can be used as `Error.prepareStackTrace`. + * + * This function accepts an optional argument, a function that performs + * source mapping. It is not required to pass this argument, but + * in such case only JavaScript sources will have proper position in + * stack frames. + * @param {( + * fileName: string, + * lineNumber: number, + * columnNumber: number + * ) => { + * fileName: string, + * lineNumber: number, + * columnNumber: number + * }} sourceMappingFn + */ + function createPrepareStackTrace(sourceMappingFn) { + return function prepareStackTrace( + error, + callSites, + ) { + const mappedCallSites = callSites.map( + (callSite) => { + const fileName = callSite.getFileName(); + const lineNumber = callSite.getLineNumber(); + const columnNumber = callSite.getColumnNumber(); + if ( + sourceMappingFn && fileName && lineNumber != null && + columnNumber != null + ) { + return patchCallSite( + callSite, + sourceMappingFn({ + fileName, + lineNumber, + columnNumber, + }), + ); + } + return callSite; + }, + ); + Object.defineProperties(error, { + __callSiteEvals: { value: [], configurable: true }, + }); + const formattedCallSites = []; + for (const callSite of mappedCallSites) { + error.__callSiteEvals.push(evaluateCallSite(callSite)); + formattedCallSites.push(formatCallSite(callSite)); + } + const message = error.message !== undefined ? error.message : ""; + const name = error.name !== undefined ? error.name : "Error"; + let messageLine; + if (name != "" && message != "") { + messageLine = `${name}: ${message}`; + } else if ((name || message) != "") { + messageLine = name || message; + } else { + messageLine = ""; + } + return messageLine + + formattedCallSites.map((s) => `\n at ${s}`).join(""); + }; + } + + Object.assign(window.Deno.core, { + createPrepareStackTrace, + }); +})(this); diff --git a/core/runtime.rs b/core/runtime.rs index ecac588ca..0f09926f8 100644 --- a/core/runtime.rs +++ b/core/runtime.rs @@ -329,6 +329,9 @@ impl JsRuntime { self .execute("deno:core/core.js", include_str!("core.js")) .unwrap(); + self + .execute("deno:core/error.js", include_str!("error.js")) + .unwrap(); } } |