deno/core/02_error.js
Luca Casonato c9204c4aee
refactor: introduce primordials (#10939)
This commit introduces primordials to deno_core. Primordials are a
frozen set of all intrinsic objects in the runtime. They are not
vulnerable to prototype pollution.
2021-07-02 12:18:30 +02:00

260 lines
7.1 KiB
JavaScript

// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";
((window) => {
const {
Error,
ObjectFreeze,
ObjectAssign,
StringPrototypeStartsWith,
StringPrototypeEndsWith,
ObjectDefineProperties,
ArrayPrototypePush,
ArrayPrototypeMap,
ArrayPrototypeJoin,
} = window.__bootstrap.primordials;
// 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 (!StringPrototypeStartsWith(functionName, typeName)) {
result += `${typeName}.`;
}
}
result += functionName;
if (methodName) {
if (!StringPrototypeEndsWith(functionName, 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 = ArrayPrototypeMap(callSites, (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;
});
ObjectDefineProperties(error, {
__callSiteEvals: { value: [], configurable: true },
});
const formattedCallSites = [];
for (const callSite of mappedCallSites) {
ArrayPrototypePush(error.__callSiteEvals, evaluateCallSite(callSite));
ArrayPrototypePush(formattedCallSites, 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 +
ArrayPrototypeJoin(
ArrayPrototypeMap(formattedCallSites, (s) => `\n at ${s}`),
"",
);
};
}
ObjectAssign(globalThis.__bootstrap.core, { createPrepareStackTrace });
ObjectFreeze(globalThis.__bootstrap.core);
})(this);