Bug 1412202 - Part 9: Blank out slots when leaving a lexical scope (in generators only). r=jandem

This fixes bug 1542660 for the usual case (no direct eval, less than
ParseContext::GeneratorFixedSlotLimit locals), so this adds a unit test
contributed by Mathieu Hofman in that bug.

Differential Revision: https://phabricator.services.mozilla.com/D93386
This commit is contained in:
Jason Orendorff 2020-10-15 19:30:51 +00:00
Родитель 74dcf4f8e8
Коммит ddaff03448
4 изменённых файлов: 174 добавлений и 0 удалений

Просмотреть файл

@ -416,6 +416,9 @@ bool EmitterScope::deadZoneFrameSlotRange(BytecodeEmitter* bce,
// InitializeBinding, after which touching the binding will no longer
// throw reference errors. See 13.1.11, 9.2.13, 13.6.3.4, 13.6.4.6,
// 13.6.4.8, 13.14.5, 15.1.8, and 15.2.0.15.
//
// The same code is used to clear slots on exit, in generators and async
// functions, to avoid keeping garbage alive indefinitely.
if (slotStart != slotEnd) {
if (!bce->emit1(JSOp::Uninitialized)) {
return false;
@ -1043,6 +1046,12 @@ bool EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal) {
case ScopeKind::Catch:
case ScopeKind::FunctionLexical:
case ScopeKind::ClassBody:
if (bce->sc->isFunctionBox() &&
bce->sc->asFunctionBox()->needsClearSlotsOnExit()) {
if (!deadZoneFrameSlots(bce)) {
return false;
}
}
if (!bce->emit1(hasEnvironment() ? JSOp::PopLexicalEnv
: JSOp::DebugLeaveLexicalEnv)) {
return false;

Просмотреть файл

@ -536,6 +536,7 @@ class FunctionBox : public SharedContext {
bool needsFinalYield() const { return isGenerator() || isAsync(); }
bool needsDotGeneratorName() const { return isGenerator() || isAsync(); }
bool needsClearSlotsOnExit() const { return isGenerator() || isAsync(); }
bool needsIteratorResult() const { return isGenerator() && !isAsync(); }
bool needsPromiseResult() const { return isAsync() && !isGenerator(); }

Просмотреть файл

@ -0,0 +1,111 @@
// |jit-test| skip-if: !('gc' in this) || !('clearKeptObjects' in this)
// In generators, when we exit a lexical scope, its non-aliased bindings go away;
// they don't keep their last values gc-alive.
let cases = [
function* onNormalExitFromFunction_VarBinding() {
var tmp = yield 1;
consumeValue(tmp);
},
function* onNormalExitFromFunction_LetBinding() {
let tmp = yield 1;
consumeValue(tmp);
},
function* onNormalExitFromBlock() {
if (typeof onNormalExitFromBlock === 'function') {
let tmp = yield 1;
consumeValue(tmp);
}
yield 2;
},
function* onNormalExitFromCStyleForLet() {
for (let tmp = yield 1; tmp !== null; tmp = null) {
consumeValue(tmp);
}
yield 2;
},
function* onNormalExitFromForLetOf() {
for (let tmp of [yield 1]) {
consumeValue(tmp);
}
yield 2;
},
function* onNormalExitFromForConstOf() {
for (const tmp of [yield 1]) {
consumeValue(tmp);
}
yield 2;
},
function* onNormalExitFromForConstDestructuringOf() {
for (const {a, b, c, d} of [yield 1]) {
consumeValue(a);
}
yield 2;
},
function* onNormalExitFromForConstArrayDestructuringOf() {
for (const [x] of [[yield 1]]) {
consumeValue(x);
}
yield 2;
},
function* onNormalExitFromBlockInLoop() {
for (var i = 0; i < 2; i++) {
if (i == 0) {
let tmp = yield 1;
consumeValue(tmp);
} else {
yield 2;
}
}
},
function* onBreakFromBlock() {
x: {
let tmp = yield 1;
consumeValue(tmp);
break x;
}
yield 2;
},
function* onExitFromCatchBlock() {
try {
throw yield 1;
} catch (exc) {
consumeValue(exc);
}
yield 2;
},
];
var consumeValue;
function runTest(g) {
consumeValue = eval("_ => {}");
let generator = g();
let result = generator.next();
assertEq(result.done, false);
assertEq(result.value, 1);
let object = {};
let weakRef = new WeakRef(object);
result = generator.next(object);
assertEq(result.value, result.done ? undefined : 2);
object = null;
clearKeptObjects();
gc();
assertEq(weakRef.deref(), undefined);
}
for (let g of cases) {
runTest(g);
}

Просмотреть файл

@ -0,0 +1,53 @@
// |jit-test| skip-if: !('gc' in this) || !('clearKeptObjects' in this)
// Locals in async functions should not keep objects alive after going out of scope.
// Test by Mathieu Hofman.
let nextId = 0;
let weakRef;
let savedCallback;
const tests = [
function() {
let object = { id: ++nextId };
console.log(`created object ${object.id}`);
savedCallback = () => {};
weakRef = new WeakRef(object);
},
async function() {
let object = { id: ++nextId };
console.log(`created object ${object.id}`);
savedCallback = () => {};
weakRef = new WeakRef(object);
},
async function() {
function* gen() {
{
let object = { id: ++nextId };
console.log(`created object ${object.id}`);
// Yielding here stores the local variable `object` in the generator
// object.
yield 1;
weakRef = new WeakRef(object);
}
// Yielding here should clear it.
yield 2;
}
let iter = gen();
assertEq(iter.next().value, 1);
assertEq(iter.next().value, 2);
savedCallback = iter; // Keep the generator alive for GC.
}
];
(async () => {
for (const test of tests) {
await test();
assertEq(!!weakRef.deref(), true);
clearKeptObjects();
gc();
if (weakRef.deref()) {
throw new Error(`object ${nextId} was not collected`);
}
}
})();