All files / near-membrane-base/src intrinsics.ts

100% Statements 35/35
69.23% Branches 9/13
100% Functions 4/4
100% Lines 29/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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                                                          4x                                                                                                                                                           4x                                 4x           29x       29x     29x           15x 15x 22x       22x 22x 22x         22x       15x       14x 14x 14x 14x 3420x       3420x 2888x     14x         23x 322x 322x 322x   322x 253x   69x          
import {
    ArrayProtoIncludes,
    ObjectAssign,
    ObjectHasOwn,
    ReflectApply,
    ReflectOwnKeys,
    toSafeArray,
} from '@locker/near-membrane-shared';
import { VirtualEnvironment } from './environment';
 
/**
 * This list must be in sync with ecma-262, anything new added to the global object
 * should be considered, to decide whether or not they need remapping. The default
 * behavior, if missing form the following list, is to be remapped, which is safer.
 *
 * Note: remapped means the functionality is provided by the blue realm, rather than
 * the red one. This helps with the identity discontinuity issue, e.g.: all Set objects
 * have the same identity because it is always derived from the outer realm's Set.
 *
 * Note 1: We have identified 3 types of intrinsics
 * A: primitives driven intrinsics
 * B: syntax driven intrinsics (they usually have a imperative form as well)
 * C: imperative only intrinsics
 *
 * While A is not remapped, it is safe, and works fast that way, and C is remapped to
 * preserve the identity of all produced objects from the same realm, B is really
 * problematic, and requires a lot more work to guarantee that objects from both sides
 * can be considered equivalents (without identity discontinuity).
 */
const ESGlobalKeys = [
    // *** 19.1 Value Properties of the Global Object
    'globalThis',
    'Infinity',
    'NaN',
    'undefined',
 
    // *** 19.2 Function Properties of the Global Object
    // 'eval', // dangerous & Reflective
    'isFinite',
    'isNaN',
    'parseFloat',
    'parseInt',
    'decodeURI',
    'decodeURIComponent',
    'encodeURI',
    'encodeURIComponent',
 
    // *** 19.3 Constructor Properties of the Global Object
    // 'AggregateError', // Reflective
    // 'Array', // Reflective
    // 'ArrayBuffer', // Remapped
    'BigInt',
    // 'BigInt64Array', // Remapped
    // 'BigUint64Array', // Remapped
    'Boolean',
    // 'DataView', // Remapped
    // 'Date', // Remapped
    // 'Error', // Reflective
    // 'EvalError', // Reflective
    'FinalizationRegistry',
    // 'Float32Array', // Remapped
    // 'Float64Array', // Remapped
    // 'Function', // dangerous & Reflective
    // 'Int8Array', // Remapped
    // 'Int16Array', // Remapped
    // 'Int32Array', // Remapped
    // 'Map', // Remapped
    'Number',
    // 'Object', // Reflective
    // Allow blue `Promise` constructor to overwrite the Red one so that promises
    // created by the `Promise` constructor or APIs like `fetch` will work.
    // 'Promise', // Remapped
    // 'Proxy', // Reflective
    // 'RangeError', // Reflective
    // 'ReferenceError', // Reflective
    'RegExp',
    // 'Set', // Remapped
    // 'SharedArrayBuffer', // Remapped
    'String',
    'Symbol',
    // 'SyntaxError', // Reflective
    // 'TypeError', // Reflective
    // 'Uint8Array', // Remapped
    // 'Uint8ClampedArray', // Remapped
    // 'Uint16Array', // Remapped
    // 'Uint32Array', // Remapped
    // 'URIError', // Reflective
    // 'WeakMap', // Remapped
    // 'WeakSet', // Remapped
    'WeakRef',
 
    // *** 18.4 Other Properties of the Global Object
    // 'Atomics', // Remapped
    'JSON',
    'Math',
    'Reflect',
 
    // *** Annex B
    'escape',
    'unescape',
 
    // *** ECMA-402
    // 'Intl',  // Remapped
];
 
// These are foundational things that should never be wrapped but are equivalent
// @TODO: Revisit this list.
const ReflectiveIntrinsicObjectNames = [
    'AggregateError',
    'Array',
    'Error',
    'EvalError',
    'Function',
    'Object',
    'Proxy',
    'RangeError',
    'ReferenceError',
    'SyntaxError',
    'TypeError',
    'URIError',
    'eval',
    'globalThis',
];
 
const ESGlobalsAndReflectiveIntrinsicObjectNames = toSafeArray([
    ...ESGlobalKeys,
    ...ReflectiveIntrinsicObjectNames,
]);
 
function getGlobalObjectOwnKeys(source: object): PropertyKey[] {
    const ownKeys = ReflectOwnKeys(source);
    // WKWebView incorrectly excludes the 'webkit' own property of the global
    // object from `Object.keys()` and `Reflect.ownKeys()` results, so add it.
    // istanbul ignore if: currently unreachable via tests
    if (ObjectHasOwn(source, 'webkit') && !ReflectApply(ArrayProtoIncludes, ownKeys, ['webkit'])) {
        ownKeys[ownKeys.length] = 'webkit';
    }
    return ownKeys;
}
 
export function assignFilteredGlobalDescriptorsFromPropertyDescriptorMap<
    T extends PropertyDescriptorMap
>(descs: T, source: PropertyDescriptorMap): T {
    const ownKeys = getGlobalObjectOwnKeys(source);
    for (let i = 0, { length } = ownKeys; i < length; i += 1) {
        const ownKey = ownKeys[i];
        // Avoid overriding ECMAScript global names that correspond to
        // global intrinsics. This guarantee that those entries will be
        // ignored if present in the source property descriptor map.
        Eif (!ESGlobalsAndReflectiveIntrinsicObjectNames.includes(ownKey as any)) {
            const unsafeDesc = (source as any)[ownKey];
            Eif (unsafeDesc) {
                // Avoid poisoning by only installing own properties from
                // unsafeDesc. We don't use a toSafeDescriptor() style helper
                // since that mutates the unsafeBlueDesc.
                // eslint-disable-next-line prefer-object-spread
                (descs as any)[ownKey] = ObjectAssign({ __proto__: null }, unsafeDesc);
            }
        }
    }
    return descs;
}
 
export function getFilteredGlobalOwnKeys(source: object): PropertyKey[] {
    const result: PropertyKey[] = [];
    let resultOffset = 0;
    const ownKeys = getGlobalObjectOwnKeys(source);
    for (let i = 0, { length } = ownKeys; i < length; i += 1) {
        const ownKey = ownKeys[i];
        // Avoid overriding ECMAScript global names that correspond to global
        // intrinsics. This guarantees that those entries will be ignored if
        // present in the source object.
        if (!ESGlobalsAndReflectiveIntrinsicObjectNames.includes(ownKey as any)) {
            result[resultOffset++] = ownKey;
        }
    }
    return result;
}
 
export function linkIntrinsics(env: VirtualEnvironment, globalObject: typeof globalThis) {
    // Remap intrinsics that are realm agnostic.
    for (let i = 0, { length } = ReflectiveIntrinsicObjectNames; i < length; i += 1) {
        const globalName = ReflectiveIntrinsicObjectNames[i];
        const reflectiveValue = (globalObject as any)[globalName];
        Eif (reflectiveValue) {
            // Proxy.prototype is undefined.
            if (reflectiveValue.prototype) {
                env.link(globalName, 'prototype');
            } else {
                env.link(globalName);
            }
        }
    }
}