Skip to content

Commit

Permalink
refactor(ses): Capture Error and RegExp as FERAL_*
Browse files Browse the repository at this point in the history
  • Loading branch information
kriskowal committed Aug 28, 2021
1 parent 21a6390 commit 0c72ad6
Show file tree
Hide file tree
Showing 21 changed files with 100 additions and 76 deletions.
4 changes: 2 additions & 2 deletions packages/ses/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { globalThis, Error, assign } from './src/commons.js';
import { globalThis, TypeError, assign } from './src/commons.js';
import { tameFunctionToString } from './src/tame-function-tostring.js';
import { getGlobalIntrinsics } from './src/intrinsics.js';
import { getAnonymousIntrinsics } from './src/get-anonymous-intrinsics.js';
Expand All @@ -29,7 +29,7 @@ function getThis() {
}

if (getThis()) {
throw new Error(`SES failed to initialize, sloppy mode (SES_NO_SLOPPY)`);
throw new TypeError(`SES failed to initialize, sloppy mode (SES_NO_SLOPPY)`);
}

const markVirtualizedNativeFunction = tameFunctionToString();
Expand Down
20 changes: 18 additions & 2 deletions packages/ses/src/commons.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@ export const {
Promise,
Proxy,
Reflect,
RegExp,
RegExp: FERAL_REG_EXP,
Set,
String,
WeakMap,
WeakSet,
} = globalThis;

export const {
Error,
// The feral Error constructor is safe for internal use, but must not be
// revealed to post-lockdown code in any compartment including the start
// compartment since in V8 at least it bears stack inspection capabilities.
Error: FERAL_ERROR,
RangeError,
ReferenceError,
SyntaxError,
Expand Down Expand Up @@ -239,6 +242,19 @@ export const immutableObject = freeze(create(null));
*/
export const isObject = value => Object(value) === value;

/**
* isError tests whether an object descends from the intrinsic Error
* constructor.
* We capture the original error constructor as FERAL_ERROR to provide a clear
* signal for reviewers that we are handling an object with excess authority
* that we are carefully hiding from client code, like stack trace inspection.
* Checking instanceof happens to be safe, but to avoid uttering FERAL_ERROR
* for such a trivial case outside commons.js, we provide a utility function.
*
* @param {any} value
*/
export const isError = value => value instanceof FERAL_ERROR;

// The original unsafe untamed eval function, which must not escape.
// Sample at module initialization time, which is before lockdown can
// repair it. Use it only to build powerless abstractions.
Expand Down
3 changes: 1 addition & 2 deletions packages/ses/src/compartment-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
/// <reference types="ses">

import {
Error,
Map,
ReferenceError,
TypeError,
Expand Down Expand Up @@ -265,7 +264,7 @@ export const makeCompartmentConstructor = (
identifier => !isValidIdentifierName(identifier),
);
if (invalidNames.length) {
throw new Error(
throw new TypeError(
`Cannot create compartment with invalid names for global lexicals: ${arrayJoin(
invalidNames,
', ',
Expand Down
5 changes: 2 additions & 3 deletions packages/ses/src/enable-property-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// @ts-check

import {
Error,
Set,
String,
TypeError,
Expand Down Expand Up @@ -114,7 +113,7 @@ export default function enablePropertyOverrides(
} else {
if (isDebug) {
// eslint-disable-next-line @endo/no-polymorphic-call
console.error(new Error(`Override property ${prop}`));
console.error(new TypeError(`Override property ${prop}`));
}
defineProperty(this, prop, {
value: newValue,
Expand Down Expand Up @@ -196,7 +195,7 @@ export default function enablePropertyOverrides(
break;
}
default: {
throw new Error(`unrecognized overrideTaming ${overrideTaming}`);
throw new TypeError(`unrecognized overrideTaming ${overrideTaming}`);
}
}

Expand Down
14 changes: 7 additions & 7 deletions packages/ses/src/error/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// module should not be observably impure.

import {
Error,
RangeError,
TypeError,
WeakMap,
Expand All @@ -24,6 +23,7 @@ import {
freeze,
globalThis,
is,
isError,
stringIndexOf,
stringReplace,
stringSlice,
Expand Down Expand Up @@ -85,7 +85,7 @@ const getMessageString = ({ template, args }) => {
let argStr;
if (weakmapHas(declassifiers, arg)) {
argStr = `${arg}`;
} else if (arg instanceof Error) {
} else if (isError(arg)) {
argStr = `(${an(arg.name)})`;
} else {
argStr = `(${an(typeof arg)})`;
Expand Down Expand Up @@ -226,7 +226,7 @@ const tagError = (err, optErrorName = err.name) => {
*/
const makeError = (
optDetails = redactedDetails`Assert failed`,
ErrorConstructor = Error,
ErrorConstructor = globalThis.Error,
{ errorName = undefined } = {},
) => {
if (typeof optDetails === 'string') {
Expand All @@ -236,7 +236,7 @@ const makeError = (
}
const hiddenDetails = weakmapGet(hiddenDetailsMap, optDetails);
if (hiddenDetails === undefined) {
throw new Error(`unrecognized details ${quote(optDetails)}`);
throw new TypeError(`unrecognized details ${quote(optDetails)}`);
}
const messageString = getMessageString(hiddenDetails);
const error = new ErrorConstructor(messageString);
Expand Down Expand Up @@ -286,7 +286,7 @@ const note = (error, detailsNote) => {
}
const hiddenDetails = weakmapGet(hiddenDetailsMap, detailsNote);
if (hiddenDetails === undefined) {
throw new Error(`unrecognized details ${quote(detailsNote)}`);
throw new TypeError(`unrecognized details ${quote(detailsNote)}`);
}
const logArgs = getLogArgs(hiddenDetails);
const callbacks = weakmapGet(hiddenNoteCallbackArrays, error);
Expand Down Expand Up @@ -365,7 +365,7 @@ const makeAssert = (optRaise = undefined, unredacted = false) => {
/** @type {AssertFail} */
const fail = (
optDetails = details`Assert failed`,
ErrorConstructor = Error,
ErrorConstructor = globalThis.Error,
) => {
const reason = makeError(optDetails, ErrorConstructor);
if (optRaise !== undefined) {
Expand All @@ -382,7 +382,7 @@ const makeAssert = (optRaise = undefined, unredacted = false) => {
function baseAssert(
flag,
optDetails = details`Check failed`,
ErrorConstructor = Error,
ErrorConstructor = globalThis.Error,
) {
if (!flag) {
throw fail(optDetails, ErrorConstructor);
Expand Down
4 changes: 2 additions & 2 deletions packages/ses/src/error/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
// normally commented out.

import {
Error,
WeakSet,
arrayFilter,
arrayMap,
arrayPush,
defineProperty,
freeze,
fromEntries,
isError,
stringEndsWith,
weaksetAdd,
weaksetHas,
Expand Down Expand Up @@ -197,7 +197,7 @@ const makeCausalConsole = (baseConsole, loggedErrorHandler) => {
*/
const extractErrorArgs = (logArgs, subErrorsSink) => {
const argTags = arrayMap(logArgs, arg => {
if (arg instanceof Error) {
if (isError(arg)) {
arrayPush(subErrorsSink, arg);
return `(${tagError(arg)})`;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ses/src/error/stringify-utils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @ts-check

import {
Error,
Set,
String,
freeze,
is,
isError,
setAdd,
setHas,
stringStartsWith,
Expand Down Expand Up @@ -70,7 +70,7 @@ const bestEffortStringify = (payload, spaces = undefined) => {
return '[Seen]';
}
setAdd(seenSet, val);
if (val instanceof Error) {
if (isError(val)) {
return `[${val.name}: ${val.message}]`;
}
if (toStringTagSymbol in val) {
Expand Down
4 changes: 2 additions & 2 deletions packages/ses/src/error/tame-console.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-check

import { Error, globalThis } from '../commons.js';
import { TypeError, globalThis } from '../commons.js';
import { loggedErrorHandler as defaultHandler } from './assert.js';
import { makeCausalConsole } from './console.js';
import './types.js';
Expand All @@ -25,7 +25,7 @@ export const tameConsole = (
optGetStackString = undefined,
) => {
if (consoleTaming !== 'safe' && consoleTaming !== 'unsafe') {
throw new Error(`unrecognized consoleTaming ${consoleTaming}`);
throw new TypeError(`unrecognized consoleTaming ${consoleTaming}`);
}

if (consoleTaming === 'unsafe') {
Expand Down
36 changes: 18 additions & 18 deletions packages/ses/src/error/tame-error-constructor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Error,
FERAL_ERROR,
TypeError,
apply,
construct,
defineProperties,
Expand All @@ -11,7 +12,7 @@ import { tameV8ErrorConstructor } from './tame-v8-error-constructor.js';

// Present on at least FF. Proposed by Error-proposal. Not on SES whitelist
// so grab it before it is removed.
const stackDesc = getOwnPropertyDescriptor(Error.prototype, 'stack');
const stackDesc = getOwnPropertyDescriptor(FERAL_ERROR.prototype, 'stack');
const stackGetter = stackDesc && stackDesc.get;

// Use concise methods to obtain named functions without constructors.
Expand All @@ -32,30 +33,29 @@ export default function tameErrorConstructor(
stackFiltering = 'concise',
) {
if (errorTaming !== 'safe' && errorTaming !== 'unsafe') {
throw new Error(`unrecognized errorTaming ${errorTaming}`);
throw new TypeError(`unrecognized errorTaming ${errorTaming}`);
}
if (stackFiltering !== 'concise' && stackFiltering !== 'verbose') {
throw new Error(`unrecognized stackFiltering ${stackFiltering}`);
throw new TypeError(`unrecognized stackFiltering ${stackFiltering}`);
}
const OriginalError = Error;
const ErrorPrototype = OriginalError.prototype;
const ErrorPrototype = FERAL_ERROR.prototype;

const platform =
typeof OriginalError.captureStackTrace === 'function' ? 'v8' : 'unknown';
const { captureStackTrace: originalCaptureStackTrace } = OriginalError;
typeof FERAL_ERROR.captureStackTrace === 'function' ? 'v8' : 'unknown';
const { captureStackTrace: originalCaptureStackTrace } = FERAL_ERROR;

const makeErrorConstructor = (_ = {}) => {
// eslint-disable-next-line no-shadow
const ResultError = function Error(...rest) {
let error;
if (new.target === undefined) {
error = apply(OriginalError, this, rest);
error = apply(FERAL_ERROR, this, rest);
} else {
error = construct(OriginalError, rest, new.target);
error = construct(FERAL_ERROR, rest, new.target);
}
if (platform === 'v8') {
// TODO Likely expensive!
apply(originalCaptureStackTrace, OriginalError, [error, ResultError]);
apply(originalCaptureStackTrace, FERAL_ERROR, [error, ResultError]);
}
return error;
};
Expand Down Expand Up @@ -100,9 +100,9 @@ export default function tameErrorConstructor(
defineProperties(InitialError, {
stackTraceLimit: {
get() {
if (typeof OriginalError.stackTraceLimit === 'number') {
// OriginalError.stackTraceLimit is only on v8
return OriginalError.stackTraceLimit;
if (typeof FERAL_ERROR.stackTraceLimit === 'number') {
// FERAL_ERROR.stackTraceLimit is only on v8
return FERAL_ERROR.stackTraceLimit;
}
return undefined;
},
Expand All @@ -114,9 +114,9 @@ export default function tameErrorConstructor(
// harmless seems the safer option.
return;
}
if (typeof OriginalError.stackTraceLimit === 'number') {
// OriginalError.stackTraceLimit is only on v8
OriginalError.stackTraceLimit = newLimit;
if (typeof FERAL_ERROR.stackTraceLimit === 'number') {
// FERAL_ERROR.stackTraceLimit is only on v8
FERAL_ERROR.stackTraceLimit = newLimit;
// We place the useless return on the next line to ensure
// that anything we place after the if in the future only
// happens if the then-case does not.
Expand Down Expand Up @@ -149,7 +149,7 @@ export default function tameErrorConstructor(
let initialGetStackString = tamedMethods.getStackString;
if (platform === 'v8') {
initialGetStackString = tameV8ErrorConstructor(
OriginalError,
FERAL_ERROR,
InitialError,
errorTaming,
stackFiltering,
Expand Down
3 changes: 1 addition & 2 deletions packages/ses/src/get-anonymous-intrinsics.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {
FERAL_FUNCTION,
Float32Array,
Map,
RegExp,
Set,
String,
getOwnPropertyDescriptor,
Expand Down Expand Up @@ -57,7 +56,7 @@ export const getAnonymousIntrinsics = () => {

// 21.2.7.1 The %RegExpStringIteratorPrototype% Object
const RegExpStringIterator =
regexpPrototype[matchAllSymbol] && matchAllRegExp(new RegExp());
regexpPrototype[matchAllSymbol] && matchAllRegExp(/./);
const RegExpStringIteratorPrototype =
RegExpStringIterator && getPrototypeOf(RegExpStringIterator);

Expand Down
4 changes: 2 additions & 2 deletions packages/ses/src/get-source-url.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RegExp, regexpExec, stringSlice } from './commons.js';
import { FERAL_REG_EXP, regexpExec, stringSlice } from './commons.js';

// Captures a key and value of the form #key=value or @key=value
const sourceMetaEntryRegExp =
Expand All @@ -9,7 +9,7 @@ const sourceMetaEntryRegExp =
// On account of the mechanics of regular expressions, scanning from the end
// does not allow us to capture every pair, so getSourceURL must capture and
// trim until there are no matching comments.
const sourceMetaEntriesRegExp = new RegExp(
const sourceMetaEntriesRegExp = new FERAL_REG_EXP(
`(?:\\s*//${sourceMetaEntryRegExp}|/\\*${sourceMetaEntryRegExp}\\s*\\*/)\\s*$`,
);

Expand Down
Loading

0 comments on commit 0c72ad6

Please sign in to comment.