-
Notifications
You must be signed in to change notification settings - Fork 72
/
commons.js
380 lines (350 loc) · 14.2 KB
/
commons.js
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
/* global globalThis */
/* eslint-disable no-restricted-globals */
/**
* commons.js
* Declare shorthand functions. Sharing these declarations across modules
* improves on consistency and minification. Unused declarations are
* dropped by the tree shaking process.
*
* We capture these, not just for brevity, but for security. If any code
* modifies Object to change what 'assign' points to, the Compartment shim
* would be corrupted.
*/
// 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,
} = 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
export const objectHasOwnProperty = uncurryThis(objectPrototype.hasOwnProperty);
//
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');
/**
* immutableObject
* An immutable (frozen) empty object that is safe to share.
*/
export const immutableObject = freeze(create(null));
/**
* isObject tests whether a value is an object.
* Today, this is equivalent to:
*
* const isObject = value => {
* if (value === null) return false;
* const type = typeof value;
* return type === 'object' || type === 'function';
* };
*
* But this is not safe in the face of possible evolution of the language, for
* example new types or semantics of records and tuples.
* We use this implementation despite the unnecessary allocation implied by
* attempting to box a primitive.
*
* @param {any} value
*/
export const isObject = value => Object(value) === value;
/**
* 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;
// 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 "noEval" (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;