зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
74dcf4f8e8
Коммит
ddaff03448
|
@ -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`);
|
||||
}
|
||||
}
|
||||
})();
|
Загрузка…
Ссылка в новой задаче