Fix using declaration in a switch case clobbering its disposable record (Issue #1577 Crash #2)

A `using` declaration in a switch case preceded by another statement
aborted with Assertion `isDisposableResourceRecord()' failed / SEGV in
finalizeDisposable.

The switch releases its discriminant register before generating the
case bodies, but pushLexicalBlock had allocated the disposable-record
register on top of it. The early giveUpRegister therefore freed the
disposable register instead, and a statement in the case body (e.g.
`o.k = 1`) reused that register slot, clobbering the record;
Initialize/FinalizeDisposable then dereferenced a non-pointer value.

When the switch block contains a `using` declaration, defer releasing
the discriminant temporaries until after finalizeLexicalBlock has
popped the disposable register (preserving LIFO order). Switches
without `using` are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
Seonghyun Kim 2026-06-16 17:20:19 +09:00 committed by MuHong Byun
commit 5e3b91b052

View file

@ -53,6 +53,11 @@ public:
m_discriminant->generateExpressionByteCode(codeBlock, &newContext, rIndex0);
newContext.m_canSkipCopyToRegister = canSkipCopyToRegister;
// Whether the discriminant occupies its own freshly-allocated register (rather than
// reusing an existing variable's register). Captured here, before pushLexicalBlock
// may allocate a disposable-record register (for `using`) on top of it. - Issue #1577
bool discriminantHasOwnRegister = (rIndex0 == newContext.getLastRegisterIndex());
if (firstRegister == 0 && context->shouldCareScriptExecutionResult()) {
codeBlock->pushCode(LoadLiteral(ByteCodeLOC(m_loc.index), 0, Value()), context, this->m_loc.index);
}
@ -65,6 +70,14 @@ public:
blockContext = codeBlock->pushLexicalBlock(&newContext, bi, this);
}
// When the switch block declares a `using` variable, pushLexicalBlock allocates a
// disposable-record register that lives until finalizeLexicalBlock. It sits on the
// register stack above the discriminant temporaries, so those temporaries can only be
// released (LIFO) after the disposable register is popped in finalizeLexicalBlock.
// Releasing them earlier (the default path below) would free the disposable register
// itself, letting a statement in a case body clobber it. - Issue #1577
bool hasUsingBlock = (m_lexicalBlockIndex != LEXICAL_BLOCK_INDEX_MAX) && (blockContext.usingBlockTryStartPosition != SIZE_MAX);
std::vector<size_t> jumpCodePerCaseNodePosition;
StatementNode* nd = m_casesB->firstChild();
while (nd) {
@ -95,9 +108,11 @@ public:
nd = nd->nextSibling();
}
newContext.giveUpRegister();
if (registerWasSame) {
if (!hasUsingBlock) {
newContext.giveUpRegister();
if (registerWasSame) {
newContext.giveUpRegister();
}
}
size_t jmpToDefault = SIZE_MAX;
@ -134,6 +149,16 @@ public:
newContext.m_lexicalBlockIndex = lexicalBlockIndexBefore;
}
// Deferred release of the discriminant temporaries for the `using` case (see the
// comment where hasUsingBlock is computed). finalizeLexicalBlock above has already
// popped the disposable-record register, so these are now back on top. - Issue #1577
if (hasUsingBlock) {
newContext.giveUpRegister();
if (discriminantHasOwnRegister) {
newContext.giveUpRegister();
}
}
newContext.propagateInformationTo(*context);
}