mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
3b1204eb2d
This commit makes error objects more resistant to prototype tampering. This bug was found when updating the deno_std Node compatibility layer to Node 18. The Node test 'parallel/test-assert-fail.js' was breaking std's assertion library. Refs: https://github.com/denoland/deno_std/pull/2585
156 lines
4.5 KiB
JavaScript
156 lines
4.5 KiB
JavaScript
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
|
|
"use strict";
|
|
|
|
((window) => {
|
|
const core = Deno.core;
|
|
const ops = core.ops;
|
|
const {
|
|
Error,
|
|
ObjectFreeze,
|
|
ObjectAssign,
|
|
StringPrototypeStartsWith,
|
|
StringPrototypeEndsWith,
|
|
ObjectDefineProperties,
|
|
ArrayPrototypePush,
|
|
ArrayPrototypeMap,
|
|
ArrayPrototypeJoin,
|
|
} = window.__bootstrap.primordials;
|
|
|
|
// Keep in sync with `cli/fmt_errors.rs`.
|
|
function formatLocation(cse) {
|
|
if (cse.isNative) {
|
|
return "native";
|
|
}
|
|
let result = "";
|
|
if (cse.fileName) {
|
|
result += ops.op_format_file_name(cse.fileName);
|
|
} else {
|
|
if (cse.isEval) {
|
|
if (cse.evalOrigin == null) {
|
|
throw new Error("assert evalOrigin");
|
|
}
|
|
result += `${cse.evalOrigin}, `;
|
|
}
|
|
result += "<anonymous>";
|
|
}
|
|
if (cse.lineNumber != null) {
|
|
result += `:${cse.lineNumber}`;
|
|
if (cse.columnNumber != null) {
|
|
result += `:${cse.columnNumber}`;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Keep in sync with `cli/fmt_errors.rs`.
|
|
function formatCallSiteEval(cse) {
|
|
let result = "";
|
|
if (cse.isAsync) {
|
|
result += "async ";
|
|
}
|
|
if (cse.isPromiseAll) {
|
|
result += `Promise.all (index ${cse.promiseIndex})`;
|
|
return result;
|
|
}
|
|
const isMethodCall = !(cse.isToplevel || cse.isConstructor);
|
|
if (isMethodCall) {
|
|
if (cse.functionName) {
|
|
if (cse.typeName) {
|
|
if (!StringPrototypeStartsWith(cse.functionName, cse.typeName)) {
|
|
result += `${cse.typeName}.`;
|
|
}
|
|
}
|
|
result += cse.functionName;
|
|
if (cse.methodName) {
|
|
if (!StringPrototypeEndsWith(cse.functionName, cse.methodName)) {
|
|
result += ` [as ${cse.methodName}]`;
|
|
}
|
|
}
|
|
} else {
|
|
if (cse.typeName) {
|
|
result += `${cse.typeName}.`;
|
|
}
|
|
if (cse.methodName) {
|
|
result += cse.methodName;
|
|
} else {
|
|
result += "<anonymous>";
|
|
}
|
|
}
|
|
} else if (cse.isConstructor) {
|
|
result += "new ";
|
|
if (cse.functionName) {
|
|
result += cse.functionName;
|
|
} else {
|
|
result += "<anonymous>";
|
|
}
|
|
} else if (cse.functionName) {
|
|
result += cse.functionName;
|
|
} else {
|
|
result += formatLocation(cse);
|
|
return result;
|
|
}
|
|
|
|
result += ` (${formatLocation(cse)})`;
|
|
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 sourceMapCallSiteEval(cse) {
|
|
if (cse.fileName && cse.lineNumber != null && cse.columnNumber != null) {
|
|
return { ...cse, ...ops.op_apply_source_map(cse) };
|
|
}
|
|
return cse;
|
|
}
|
|
|
|
/** A function that can be used as `Error.prepareStackTrace`. */
|
|
function prepareStackTrace(error, callSites) {
|
|
let callSiteEvals = ArrayPrototypeMap(callSites, evaluateCallSite);
|
|
callSiteEvals = ArrayPrototypeMap(callSiteEvals, sourceMapCallSiteEval);
|
|
ObjectDefineProperties(error, {
|
|
__callSiteEvals: { __proto__: null, value: [], configurable: true },
|
|
});
|
|
const formattedCallSites = [];
|
|
for (const cse of callSiteEvals) {
|
|
ArrayPrototypePush(error.__callSiteEvals, cse);
|
|
ArrayPrototypePush(formattedCallSites, formatCallSiteEval(cse));
|
|
}
|
|
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, { prepareStackTrace });
|
|
ObjectFreeze(globalThis.__bootstrap.core);
|
|
})(this);
|