blob: 8361920e6ad3d018278f5ff198c6a7798cb48958 [file] [log] [blame]
//@ requireOptions("--useShadowRealm=1")
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error(`expected ${expected} but got ${actual}`);
}
function shouldThrow(func, errorType, assertionFn) {
let error;
try {
func();
} catch (e) {
error = e;
}
if (!(error instanceof errorType))
throw new Error(`Expected ${errorType.name} but got ${error.name}`);
assertionFn(error);
}
// basic evaluation and state setting
{
let realm = new ShadowRealm();
let otherRealm = new ShadowRealm();
shouldBe(realm.evaluate("1"), 1);
var x = 2;
realm.evaluate("var x = 1");
shouldBe(realm.evaluate("x"), 1);
shouldBe(x, 2);
// scope isn't shared across realms
shouldBe(otherRealm.evaluate("globalThis.x"), undefined);
}
// accessing `globalThis` within and outside of a shadow realm
{
let realm = new ShadowRealm();
globalThis.hi = 6;
shouldBe(realm.evaluate("globalThis.hi"), undefined);
realm.evaluate("globalThis.hi = 'fala amigo'");
shouldBe(realm.evaluate("globalThis.hi"), "fala amigo");
shouldBe(globalThis.hi, 6);
}
// ensure that errors thrown isn't associated with the shadow realm's global object
{
let realm = new ShadowRealm();
shouldThrow(
() => { realm.evaluate(".."); },
SyntaxError,
(err) => {
shouldBe($.globalObjectFor(err), globalThis);
shouldBe(String(err), `SyntaxError: Unexpected token '.'`);
});
}
// ensure that errors thrown don't carry information from the shadow realm
{
let realm = new ShadowRealm();
shouldThrow(
() => { realm.evaluate("throw new Error('secret')"); },
TypeError,
(err) => {
shouldBe($.globalObjectFor(err), globalThis);
shouldBe(String(err), "TypeError: Error encountered during evaluation");
});
}
// wrapped functions protect the shadow realm
{
let realm = new ShadowRealm();
let wrappedInvokeAndAdd = realm.evaluate("function invokeAndAdd(xFn, yFn) { return xFn() + yFn(); }; invokeAndAdd");
shouldBe(wrappedInvokeAndAdd(() => { return 1 }, () => { return 2 }), 3);
shouldBe($.globalObjectFor(wrappedInvokeAndAdd), globalThis);
shouldBe(Object.getPrototypeOf(wrappedInvokeAndAdd), Function.prototype);
// name and length properties from wrapped function are absent
let lengthDesc = Object.getOwnPropertyDescriptor(wrappedInvokeAndAdd, "length");
shouldBe(lengthDesc.value, 2);
shouldBe(lengthDesc.writable, false);
shouldBe(lengthDesc.enumerable, false);
shouldBe(lengthDesc.configurable, true);
let nameDesc = Object.getOwnPropertyDescriptor(wrappedInvokeAndAdd, "name");
shouldBe(nameDesc.value, "invokeAndAdd");
shouldBe(nameDesc.writable, false);
shouldBe(nameDesc.enumerable, false);
shouldBe(nameDesc.configurable, true);
// can't pass objects into a shadow realm-wrapped function
let numberObj = { valueOf() { return -1 } };
shouldThrow(
() => { wrappedInvokeAndAdd(numberObj, 1); },
TypeError,
(err) => {
shouldBe($.globalObjectFor(err), globalThis);
shouldBe(String(err), "TypeError: value passing between realms must be callable or primitive");
});
}
{
let realm = new ShadowRealm();
// can't call `evaluate` on a non shadow realm
let notRealm = {};
shouldThrow(
() => { realm.evaluate.call(notRealm, '1'); },
TypeError,
(err) => { shouldBe($.globalObjectFor(err), globalThis); }
);
}
// trigger JIT
{
function doEval(realm, s)
{
return realm.evaluate(s);
}
noInline(doEval);
let realm = new ShadowRealm();
realm.evaluate("globalThis.secret = 1;");
for (var i = 0; i < 10000; ++i)
shouldBe(doEval(realm, '42'), 42);
for (var i = 0; i < 1000; ++i) {
let f = doEval(realm, '(x) => { return x() + globalThis.secret; }');
shouldBe($.globalObjectFor(f), globalThis);
shouldBe(f(() => { return 41; }), 42);
shouldBe(Object.getPrototypeOf(f), Function.prototype);
}
// (potential) inlining of wrapped function uses correct global object
let f = doEval(realm, '(x) => { return x() + globalThis.secret; }');
for (var i = 0; i < 10000; ++i) {
shouldBe($.globalObjectFor(f), globalThis);
shouldBe(f(() => { return 41; }), 42);
shouldBe(Object.getPrototypeOf(f), Function.prototype);
}
// (potential) inlining inside a realm uses correct global object
let loopInside = doEval(realm, '(x) => { let acc = 0; for (var i = 0; i < 10000; ++i) { acc += x(); }; return acc; }');
globalThis.secret = -1;
shouldBe(loopInside(() => { return globalThis.secret; }), -10000);
}
// evaluate specs
{
shouldBe(typeof ShadowRealm.prototype.evaluate, "function");
let evaluateName = Object.getOwnPropertyDescriptor(ShadowRealm.prototype.evaluate, "name");
shouldBe(evaluateName.value, "evaluate");
shouldBe(evaluateName.enumerable, false);
shouldBe(evaluateName.writable, false);
shouldBe(evaluateName.configurable, true);
let evaluateLength = Object.getOwnPropertyDescriptor(ShadowRealm.prototype.evaluate, "length");
shouldBe(evaluateLength.value, 1);
shouldBe(evaluateLength.enumerable, false);
shouldBe(evaluateLength.writable, false);
shouldBe(evaluateLength.configurable, true);
}
// Enclosing realm is hidden from shaodw realm even when playing Function prototype tricks
{
let realm = new ShadowRealm();
foo = 42;
realm.evaluate("foo = false");
let realmFn = realm.evaluate(`(f) => {
let ourFn = Object.getPrototypeOf(f).constructor;
return (new ourFn("return this"))().foo
}`);
let retrievedFoo = realmFn(() => {});
let aFunction = Object.getPrototypeOf(realmFn).constructor;
let anotherFoo = (new aFunction("return this"))().foo;
shouldBe(retrievedFoo, false);
shouldBe(anotherFoo, 42);
}
// wrapped functions throw TypeError from the calling realm whenever the
// implementing function throws:
// (1) when the calling realm is the incubating realm
{
let realm = new ShadowRealm();
let f = realm.evaluate("() => {throw new Error('ahh');}");
shouldThrow(f, TypeError, (e) => {});
}
// (2) when the calling realm is the shadow realm
{
let realm = new ShadowRealm();
let f = realm.evaluate(`
(f) => {
try {
f();
} catch(e) {
if (e instanceof TypeError)
return 'ok';
else
return e.toString();
}
return 'fail: normal exit';
}
`);
shouldBe(
f(() => {throw new Error('ahhh');}),
'ok'
);
}