ses
Version:
Hardened JavaScript for Fearless Cooperation
426 lines (393 loc) • 16.4 kB
JavaScript
/**
* Captures native intrinsics during initialization, so vetted shims
* (running between initialization of SES and calling lockdown) are free to
* modify the environment without compromising the integrity of SES. For
* example, a vetted shim can modify Object.assign because we capture and
* export Object and assign here, then never again consult Object to get its
* assign property.
*
* This pattern of use is enforced by eslint rules no-restricted-globals and
* no-polymorphic-call.
* We maintain the list of restricted globals in ../package.json.
*
* @module
*/
/* global globalThis */
/* eslint-disable no-restricted-globals */
// We cannot use globalThis as the local name since it would capture the
// lexical name.
const universalThis = globalThis;
export { universalThis as globalThis };
export const {
Array,
ArrayBuffer,
Date,
FinalizationRegistry,
Float32Array,
JSON,
Map,
Math,
Number,
Object,
Promise,
Proxy,
Reflect,
RegExp: FERAL_REG_EXP,
Set,
String,
Symbol,
Uint8Array,
WeakMap,
WeakSet,
} = globalThis;
export const {
// 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,
TypeError,
AggregateError,
} = globalThis;
export const {
assign,
create,
defineProperties,
entries,
freeze,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
getOwnPropertyNames,
getPrototypeOf,
is,
isFrozen,
isSealed,
isExtensible,
keys,
prototype: objectPrototype,
seal,
preventExtensions,
setPrototypeOf,
values,
fromEntries,
hasOwn,
} = Object;
export const {
species: speciesSymbol,
toStringTag: toStringTagSymbol,
iterator: iteratorSymbol,
matchAll: matchAllSymbol,
unscopables: unscopablesSymbol,
keyFor: symbolKeyFor,
for: symbolFor,
} = Symbol;
export const { isInteger } = Number;
export const { stringify: stringifyJson } = JSON;
// Needed only for the Safari bug workaround below
const { defineProperty: originalDefineProperty } = Object;
export const defineProperty = (object, prop, descriptor) => {
// We used to do the following, until we had to reopen Safari bug
// https://bugs.webkit.org/show_bug.cgi?id=222538#c17
// Once this is fixed, we may restore it.
// // Object.defineProperty is allowed to fail silently so we use
// // Object.defineProperties instead.
// return defineProperties(object, { [prop]: descriptor });
// Instead, to workaround the Safari bug
const result = originalDefineProperty(object, prop, descriptor);
if (result !== object) {
// See https://github.com/endojs/endo/blob/master/packages/ses/error-codes/SES_DEFINE_PROPERTY_FAILED_SILENTLY.md
throw TypeError(
`Please report that the original defineProperty silently failed to set ${stringifyJson(
String(prop),
)}. (SES_DEFINE_PROPERTY_FAILED_SILENTLY)`,
);
}
return result;
};
export const {
apply,
construct,
get: reflectGet,
getOwnPropertyDescriptor: reflectGetOwnPropertyDescriptor,
has: reflectHas,
isExtensible: reflectIsExtensible,
ownKeys,
preventExtensions: reflectPreventExtensions,
set: reflectSet,
} = Reflect;
export const { isArray, prototype: arrayPrototype } = Array;
export const { prototype: arrayBufferPrototype } = ArrayBuffer;
export const { prototype: mapPrototype } = Map;
export const { revocable: proxyRevocable } = Proxy;
export const { prototype: regexpPrototype } = RegExp;
export const { prototype: setPrototype } = Set;
export const { prototype: stringPrototype } = String;
export const { prototype: weakmapPrototype } = WeakMap;
export const { prototype: weaksetPrototype } = WeakSet;
export const { prototype: functionPrototype } = Function;
export const { prototype: promisePrototype } = Promise;
export const { prototype: generatorPrototype } = getPrototypeOf(
// eslint-disable-next-line no-empty-function, func-names
function* () {},
);
export const iteratorPrototype = getPrototypeOf(
// eslint-disable-next-line @endo/no-polymorphic-call
getPrototypeOf(arrayPrototype.values()),
);
export const typedArrayPrototype = getPrototypeOf(Uint8Array.prototype);
const { bind } = functionPrototype;
/**
* uncurryThis()
* Equivalent of: fn => (thisArg, ...args) => apply(fn, thisArg, args)
*
* See those reference for a complete explanation:
* http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
* which only lives at
* http://web.archive.org/web/20160805225710/http://wiki.ecmascript.org/doku.php?id=conventions:safe_meta_programming
*
* @type {<F extends (this: any, ...args: any[]) => any>(fn: F) => ((thisArg: ThisParameterType<F>, ...args: Parameters<F>) => ReturnType<F>)}
*/
export const uncurryThis = bind.bind(bind.call); // eslint-disable-line @endo/no-polymorphic-call
/**
* @deprecated Use `hasOwn` instead
*/
export const objectHasOwnProperty = hasOwn;
//
export const arrayFilter = uncurryThis(arrayPrototype.filter);
export const arrayForEach = uncurryThis(arrayPrototype.forEach);
export const arrayIncludes = uncurryThis(arrayPrototype.includes);
export const arrayJoin = uncurryThis(arrayPrototype.join);
/** @type {<T, U>(thisArg: readonly T[], callbackfn: (value: T, index: number, array: T[]) => U, cbThisArg?: any) => U[]} */
export const arrayMap = /** @type {any} */ (uncurryThis(arrayPrototype.map));
export const arrayFlatMap = /** @type {any} */ (
uncurryThis(arrayPrototype.flatMap)
);
export const arrayPop = uncurryThis(arrayPrototype.pop);
/** @type {<T>(thisArg: T[], ...items: T[]) => number} */
export const arrayPush = uncurryThis(arrayPrototype.push);
export const arraySlice = uncurryThis(arrayPrototype.slice);
export const arraySome = uncurryThis(arrayPrototype.some);
export const arraySort = uncurryThis(arrayPrototype.sort);
export const iterateArray = uncurryThis(arrayPrototype[iteratorSymbol]);
//
export const arrayBufferSlice = uncurryThis(arrayBufferPrototype.slice);
/** @type {(b: ArrayBuffer) => number} */
export const arrayBufferGetByteLength = uncurryThis(
// @ts-expect-error we know it is there on all conforming platforms
getOwnPropertyDescriptor(arrayBufferPrototype, 'byteLength').get,
);
//
export const typedArraySet = uncurryThis(typedArrayPrototype.set);
//
export const mapSet = uncurryThis(mapPrototype.set);
export const mapGet = uncurryThis(mapPrototype.get);
export const mapHas = uncurryThis(mapPrototype.has);
export const mapDelete = uncurryThis(mapPrototype.delete);
export const mapEntries = uncurryThis(mapPrototype.entries);
export const iterateMap = uncurryThis(mapPrototype[iteratorSymbol]);
//
export const setAdd = uncurryThis(setPrototype.add);
export const setDelete = uncurryThis(setPrototype.delete);
export const setForEach = uncurryThis(setPrototype.forEach);
export const setHas = uncurryThis(setPrototype.has);
export const iterateSet = uncurryThis(setPrototype[iteratorSymbol]);
//
export const regexpTest = uncurryThis(regexpPrototype.test);
export const regexpExec = uncurryThis(regexpPrototype.exec);
export const matchAllRegExp = uncurryThis(regexpPrototype[matchAllSymbol]);
//
export const stringEndsWith = uncurryThis(stringPrototype.endsWith);
export const stringIncludes = uncurryThis(stringPrototype.includes);
export const stringIndexOf = uncurryThis(stringPrototype.indexOf);
export const stringMatch = uncurryThis(stringPrototype.match);
export const generatorNext = uncurryThis(generatorPrototype.next);
export const generatorThrow = uncurryThis(generatorPrototype.throw);
/**
* @type { &
* ((thisArg: string, searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string) => string) &
* ((thisArg: string, searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string) => string)
* }
*/
export const stringReplace = /** @type {any} */ (
uncurryThis(stringPrototype.replace)
);
export const stringSearch = uncurryThis(stringPrototype.search);
export const stringSlice = uncurryThis(stringPrototype.slice);
export const stringSplit =
/** @type {(thisArg: string, splitter: string | RegExp | { [Symbol.split](string: string, limit?: number): string[]; }, limit?: number) => string[]} */ (
uncurryThis(stringPrototype.split)
);
export const stringStartsWith = uncurryThis(stringPrototype.startsWith);
export const iterateString = uncurryThis(stringPrototype[iteratorSymbol]);
//
export const weakmapDelete = uncurryThis(weakmapPrototype.delete);
/** @type {<K extends {}, V>(thisArg: WeakMap<K, V>, ...args: Parameters<WeakMap<K,V>['get']>) => ReturnType<WeakMap<K,V>['get']>} */
export const weakmapGet = uncurryThis(weakmapPrototype.get);
export const weakmapHas = uncurryThis(weakmapPrototype.has);
export const weakmapSet = uncurryThis(weakmapPrototype.set);
//
export const weaksetAdd = uncurryThis(weaksetPrototype.add);
export const weaksetHas = uncurryThis(weaksetPrototype.has);
//
export const functionToString = uncurryThis(functionPrototype.toString);
export const functionBind = uncurryThis(bind);
//
const { all } = Promise;
export const promiseAll = promises => apply(all, Promise, [promises]);
export const promiseCatch = uncurryThis(promisePrototype.catch);
/** @type {<T, TResult1 = T, TResult2 = never>(thisArg: T, onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null) => Promise<TResult1 | TResult2>} */
export const promiseThen = /** @type {any} */ (
uncurryThis(promisePrototype.then)
);
//
export const finalizationRegistryRegister =
FinalizationRegistry && uncurryThis(FinalizationRegistry.prototype.register);
export const finalizationRegistryUnregister =
FinalizationRegistry &&
uncurryThis(FinalizationRegistry.prototype.unregister);
/**
* getConstructorOf()
* Return the constructor from an instance.
*
* @param {Function} fn
*/
export const getConstructorOf = fn =>
reflectGet(getPrototypeOf(fn), 'constructor');
/**
* TODO Consolidate with `isPrimitive` that's currently in `@endo/pass-style`.
* Layering constraints make this tricky, which is why we haven't yet figured
* out how to do this.
*
* @type {(val: unknown) => val is (undefined
* | null
* | boolean
* | number
* | bigint
* | string
* | symbol)}
*/
export const isPrimitive = val =>
!val || (typeof val !== 'object' && typeof val !== 'function');
/**
* isError tests whether an object inherits from the intrinsic
* `Error.prototype`.
* 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,
* like stack trace inspection, that we are carefully hiding from client code.
* 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;
/**
* @template T
* @param {T} x
*/
export const identity = x => x;
// 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.
// eslint-disable-next-line no-eval
export const FERAL_EVAL = eval;
// The original unsafe untamed Function constructor, which must not escape.
// Sample at module initialization time, which is before lockdown can
// repair it. Use it only to build powerless abstractions.
export const FERAL_FUNCTION = Function;
export const noEvalEvaluate = () => {
// See https://github.com/endojs/endo/blob/master/packages/ses/error-codes/SES_NO_EVAL.md
throw TypeError('Cannot eval with evalTaming set to "no-eval" (SES_NO_EVAL)');
};
// ////////////////// FERAL_STACK_GETTER FERAL_STACK_SETTER ////////////////////
const er1StackDesc = getOwnPropertyDescriptor(Error('er1'), 'stack');
const er2StackDesc = getOwnPropertyDescriptor(TypeError('er2'), 'stack');
let feralStackGetter;
let feralStackSetter;
if (er1StackDesc && er2StackDesc && er1StackDesc.get) {
// We should only encounter this case on v8 because of its problematic
// error own stack accessor behavior.
// Note that FF/SpiderMonkey, Moddable/XS, and the error stack proposal
// all inherit a stack accessor property from Error.prototype, which is
// great. That case needs no heroics to secure.
if (
// In the v8 case as we understand it, all errors have an own stack
// accessor property, but within the same realm, all these accessor
// properties have the same getter and have the same setter.
// This is therefore the case that we repair.
typeof er1StackDesc.get === 'function' &&
er1StackDesc.get === er2StackDesc.get &&
typeof er1StackDesc.set === 'function' &&
er1StackDesc.set === er2StackDesc.set
) {
// Otherwise, we have own stack accessor properties that are outside
// our expectations, that therefore need to be understood better
// before we know how to repair them.
feralStackGetter = freeze(er1StackDesc.get);
feralStackSetter = freeze(er1StackDesc.set);
} else {
// See https://github.com/endojs/endo/blob/master/packages/ses/error-codes/SES_UNEXPECTED_ERROR_OWN_STACK_ACCESSOR.md
throw TypeError(
'Unexpected Error own stack accessor functions (SES_UNEXPECTED_ERROR_OWN_STACK_ACCESSOR)',
);
}
}
/**
* If on a v8 with the problematic error own stack accessor behavior,
* `FERAL_STACK_GETTER` will be the shared getter of all those accessors
* and `FERAL_STACK_SETTER` will be the shared setter. On any platform
* without this problem, `FERAL_STACK_GETTER` and `FERAL_STACK_SETTER` are
* both `undefined`.
*
* @type {(() => any) | undefined}
*/
export const FERAL_STACK_GETTER = feralStackGetter;
/**
* If on a v8 with the problematic error own stack accessor behavior,
* `FERAL_STACK_GETTER` will be the shared getter of all those accessors
* and `FERAL_STACK_SETTER` will be the shared setter. On any platform
* without this problem, `FERAL_STACK_GETTER` and `FERAL_STACK_SETTER` are
* both `undefined`.
*
* @type {((newValue: any) => void) | undefined}
*/
export const FERAL_STACK_SETTER = feralStackSetter;
const getAsyncGeneratorFunctionInstance = () => {
// Test for async generator function syntax support.
try {
// Wrapping one in an new Function lets the `hermesc` binary file
// parse the Metro js bundle without SyntaxError, to generate the
// optimised Hermes bytecode bundle, when `gradlew` is called to
// assemble the release build APK for React Native prod Android apps.
// Delaying the error until runtime lets us customise lockdown behaviour.
return new FERAL_FUNCTION(
'return (async function* AsyncGeneratorFunctionInstance() {})',
)();
} catch (error) {
// Note: `Error.prototype.jsEngine` is only set by React Native runtime, not Hermes:
// https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/hermes/executor/HermesExecutorFactory.cpp#L224-L230
if (error.name === 'SyntaxError') {
// Swallows Hermes error `async generators are unsupported` at runtime.
// Note: `console` is not a JS built-in, so Hermes engine throws:
// Uncaught ReferenceError: Property 'console' doesn't exist
// See: https://github.com/facebook/hermes/issues/675
// However React Native provides a `console` implementation when setting up error handling:
// https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/InitializeCore.js
return undefined;
} else if (error.name === 'EvalError') {
// eslint-disable-next-line no-empty-function
return async function* AsyncGeneratorFunctionInstance() {};
} else {
throw error;
}
}
};
/**
* If the platform supports async generator functions, this will be an
* async generator function instance. Otherwise, it will be `undefined`.
*
* @type {AsyncGeneratorFunction | undefined}
*/
export const AsyncGeneratorFunctionInstance =
getAsyncGeneratorFunctionInstance();