-
Notifications
You must be signed in to change notification settings - Fork 86
Expand file tree
/
Copy pathcommons.js
More file actions
488 lines (455 loc) · 18.5 KB
/
commons.js
File metadata and controls
488 lines (455 loc) · 18.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
/**
* 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,
// Renamed to FERAL_* because it enables the NaN side-channel
Float64Array: FERAL_FLOAT64_ARRAY,
DataView,
JSON,
Map,
Math,
Number,
BigInt,
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,
} = Object;
export const {
species: speciesSymbol,
toStringTag: toStringTagSymbol,
iterator: iteratorSymbol,
matchAll: matchAllSymbol,
replace: replaceSymbol,
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
// See https://github.com/endojs/endo/issues/2930
if (!('hasOwn' in Object)) {
const ObjectPrototypeHasOwnProperty = objectPrototype.hasOwnProperty;
const hasOwnShim = (obj, key) => {
if (obj === undefined || obj === null) {
// We need to add this extra test because of differences in
// the order in which `hasOwn` vs `hasOwnProperty` validates
// arguments.
throw TypeError('Cannot convert undefined or null to object');
}
return apply(ObjectPrototypeHasOwnProperty, obj, [key]);
};
defineProperty(Object, 'hasOwn', {
value: hasOwnShim,
writable: true,
enumerable: false,
configurable: true,
});
}
export const { hasOwn } = Object;
//
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]);
//
/**
* `regexpExec` is provided in exclusion of `regexpTest`, which would be
* vulnerable to RegExp.prototype poisoning.
*/
export const regexpExec = uncurryThis(regexpPrototype.exec);
/**
* @type { &
* ((thisArg: RegExp, string: string, replaceValue: string) => string) &
* ((thisArg: RegExp, string: string, replacer: (substring: string, ...args: any[]) => string) => string)
* }
*/
export const regexpReplace = /** @type {any} */ (
uncurryThis(regexpPrototype[replaceSymbol])
);
export const matchAllRegExp = uncurryThis(regexpPrototype[matchAllSymbol]);
const { _regexpConstructor, ...regexpDescriptors } =
getOwnPropertyDescriptors(regexpPrototype);
arrayForEach(ownKeys(regexpDescriptors), key => {
const desc = regexpDescriptors[/** @type {any} */ (key)];
desc.configurable = false;
if (desc.writable) desc.writable = false;
});
/**
* Protect a RegExp instance against RegExp.prototype poisoning ("exec",
* "flags", Symbol.replace, etc.).
* @type {<T extends RegExp>(regexp: T) => T}
*/
export const sealRegexp = regexp =>
seal(defineProperties(regexp, regexpDescriptors));
//
export const stringEndsWith = uncurryThis(stringPrototype.endsWith);
export const stringIncludes = uncurryThis(stringPrototype.includes);
export const stringIndexOf = uncurryThis(stringPrototype.indexOf);
export const stringMatch = uncurryThis(stringPrototype.match);
// `stringReplace` is intentionally omitted because it would be vulnerable to
// RegExp.prototype poisoning; use `regexpReplace(re, str, replacer)` instead
// (and `sealRegexp` on its regular expressions).
export const stringSearch = uncurryThis(stringPrototype.search);
export const stringSlice = uncurryThis(stringPrototype.slice);
/**
* `stringSplit` should only be used with a string separator; regular
* expressions are vulnerable to RegExp.prototype poisoning.
* @type {(thisArg: string, separator: string, limit?: number) => string[]}
*/
export const stringSplit = /** @type {any} */ (
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);
//
export const generatorNext = uncurryThis(generatorPrototype.next);
export const generatorThrow = uncurryThis(generatorPrototype.throw);
//
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);
/**
* 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 ////////////////////
// The error repair mechanism is very similar to code in
// pass-style/src/error.js and these implementations should be kept in sync.
/**
* We gratuitiously construct a TypeError instance using syntax in order to
* obviate the possibility that code that ran before SES (for which we are
* irreducable vulnerable) may have replaced the global TypeError constructor.
* We treat the nature of this error instance as the source of truth for the
* nature of runtime constructed errors on the platform, particularly whether
* such errors will have an own "stack" property with getters and setters.
* At time of writing (2025) we know of no comparable mechanism for obtaining a
* host-generated base Error instance, but we corroborate the nature of the
* global Error constructor's instances and refuse to initialize SES in an
* environment where the syntactic TypeError and global Error produce
* inconsistent "stack" properties.
* @returns {TypeError}
*/
const makeTypeError = () => {
try {
// @ts-expect-error deliberate TypeError
null.null;
throw TypeError('obligatory'); // To convince the type flow inferrence.
} catch (error) {
return error;
}
};
const errorStackDesc = getOwnPropertyDescriptor(Error('obligatory'), 'stack');
const typeErrorStackDesc = getOwnPropertyDescriptor(makeTypeError(), 'stack');
let feralStackGetter;
let feralStackSetter;
if (typeErrorStackDesc && typeErrorStackDesc.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.
errorStackDesc &&
typeof typeErrorStackDesc.get === 'function' &&
typeErrorStackDesc.get === errorStackDesc.get &&
typeof typeErrorStackDesc.set === 'function' &&
typeErrorStackDesc.set === errorStackDesc.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(typeErrorStackDesc.get);
feralStackSetter = freeze(typeErrorStackDesc.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();