blob: 93318aa1475a2a23e67d74e8400c0d20103f5ecf [file] [log] [blame]
const TEST_VALUE = 1062630713; // Array's "length" is required to be int32
const cleanUpMap = new WeakMap;
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
testSetResult(() => [1, 2], "length", true);
testSetResult(
() => Object.defineProperty(new Array, "length", { writable: false }),
"length",
false,
);
testSetResult(() => new String, "length", false);
testSetResult(() => new RegExp, "lastIndex", true);
testSetResult(
() => Object.defineProperty(/(?:)/g, "lastIndex", { writable: false }),
"lastIndex",
false,
);
for (const key of ["line", "column", "sourceURL", "stack"])
testSetResult(() => new Error, key, true);
const mappedArguments = () => (function(a, b) { return arguments; })(1, 2);
testSetResult(mappedArguments, "callee", true);
testSetResult(mappedArguments, "length", true);
testSetResult(mappedArguments, Symbol.iterator, true);
const unmappedArguments = function() { "use strict"; return arguments; };
testSetResult(unmappedArguments, "length", true);
testSetResult(unmappedArguments, Symbol.iterator, true);
var foo;
function bar() {}
for (const key of ["foo", "bar", "parseInt"])
testSetResult(() => globalThis, key, true);
for (const key of ["Infinity", "NaN", "undefined"])
testSetResult(() => $vm.createGlobalObject().globalThis, key, false);
for (const key of ["name", "length"]) {
testSetResult(() => function() {}.bind(), key, false);
testSetResult(
() => Object.defineProperty(function() {}, key, { writable: true }),
key,
true,
);
testSetResult(() => () => { "use strict"; }, key, false);
testSetResult(
() => Object.defineProperty(parseFloat, key, { writable: true }),
key,
true,
);
}
testSetResult(() => function() { "use strict"; }, "prototype", true);
testSetResult(
() => Object.defineProperty(function() {}, "prototype", { writable: false }),
"prototype",
false,
);
// === harness ===
function testSetResult(getTarget, key, expectedResult) {
for (const primitive of [true, 1, "foo", Symbol(), 0n]) {
const primitivePrototype = Object.getPrototypeOf(primitive);
const getTargetWithDummySetterOnPrototype = withSetterOnPrototype(() => {
const target = getTarget();
if (Object.getPrototypeOf(target) !== primitivePrototype)
Object.setPrototypeOf(primitivePrototype, target);
return target;
}, key, () => {});
testSetResultInternal(getTargetWithDummySetterOnPrototype, () => primitive, key, false);
Object.setPrototypeOf(primitivePrototype, Object.prototype);
}
const getTargetWithPoisonedSetterOnPrototype = withSetterOnPrototype(getTarget, key, () => { throw new Error(`${String(key)} setter should be unreachable!`); });
testSetResultInternal(getTargetWithPoisonedSetterOnPrototype, t => Object.create(t), key, expectedResult);
testSetResultInternal(getTargetWithPoisonedSetterOnPrototype, t => Object.create(Object.create(t)), key, expectedResult);
testSetResultInternal(getTargetWithPoisonedSetterOnPrototype, t => new Proxy(t, {}), key, expectedResult);
testSetResultInternal(getTargetWithPoisonedSetterOnPrototype, t => new Proxy(t, Reflect), key, expectedResult);
testSetResultInternal(getTargetWithPoisonedSetterOnPrototype, t => $vm.createProxy(Object.create(t)), key, expectedResult);
}
function withSetterOnPrototype(getTarget, key, set) {
"use strict";
return () => {
const target = getTarget();
const targetPrototype = Object.getPrototypeOf(target);
const newDescriptor = { set, configurable: true };
if (key === "length" && !Object.getOwnPropertyDescriptor(target, key).configurable) {
const newPrototype = Object.create(null, { [key]: newDescriptor });
Object.setPrototypeOf(target, newPrototype);
cleanUpMap.set(target, () => { Object.setPrototypeOf(target, targetPrototype); });
} else {
const prevDescriptor = Object.getOwnPropertyDescriptor(targetPrototype, key);
Object.defineProperty(targetPrototype, key, newDescriptor);
cleanUpMap.set(target, prevDescriptor
? () => { Object.defineProperty(targetPrototype, key, prevDescriptor); }
: () => { delete targetPrototype[key]; });
}
return target;
};
}
function testSetResultInternal(getTarget, getReceiver, key, expectedResult) {
testSetResultSloppy(getTarget, getReceiver, key, expectedResult);
testSetResultStrict(getTarget, getReceiver, key, expectedResult);
testSetResultReflectSet(getTarget, getReceiver, key, expectedResult);
}
function testSetResultSloppy(getTarget, getReceiver, key, expectedResult) {
const target = getTarget();
const receiver = getReceiver(target);
receiver[key] = TEST_VALUE;
shouldBe(receiver[key] === TEST_VALUE, expectedResult, key);
if (expectedResult)
shouldBe(hasOwn(receiver, key), true, key);
cleanUpMap.get(target)();
}
function testSetResultStrict(getTarget, getReceiver, key, expectedResult) {
"use strict";
const target = getTarget();
const receiver = getReceiver(target);
if (expectedResult) {
receiver[key] = TEST_VALUE;
shouldBe(receiver[key], TEST_VALUE, key);
shouldBe(hasOwn(receiver, key), true, key);
} else
shouldThrowTypeError(() => { receiver[key] = TEST_VALUE; }, key);
cleanUpMap.get(target)();
}
function testSetResultReflectSet(getTarget, getReceiver, key, expectedResult) {
"use strict";
const target = getTarget();
const receiver = getReceiver(target);
if (receiver === Object(receiver))
shouldBe(Reflect.set(receiver, key, TEST_VALUE), expectedResult, key);
shouldBe(Reflect.set(target, key, TEST_VALUE, receiver), expectedResult, key);
if (expectedResult)
shouldBe(hasOwn(receiver, key), true, key);
cleanUpMap.get(target)();
}
function shouldBe(actual, expected, key) {
if (actual !== expected)
throw new Error(`Key: ${String(key)}. ${actual} !== ${expected}`);
}
function shouldThrowTypeError(func, key) {
let errorThrown = false;
try {
func();
} catch (error) {
errorThrown = true;
if (error.constructor !== TypeError)
throw new Error(`Key: ${String(key)}. Bad error: ${error}`);
}
if (!errorThrown)
throw new Error(`Key: ${String(key)}. Didn't throw!`);
}