Fix continue/break at first instruction of env-allocating block (Issue #1571)

A continue/break that is the first instruction inside a lexical block which
allocates an environment (e.g. `for(;c;){ continue; eval(); const x=1; }`) was
emitted as a plain Jump, skipping the block's CloseLexicalEnvironment. The
leaked environment then caused a subsequent outer-scope `const` to initialize
in the wrong environment, producing a spurious
`ReferenceError: Cannot access '...' before initialization`.

registerJumpPositionsToComplexCase compared jump positions against frontlimit
(= lexicalBlockStartPosition, the first body instruction) with strict `>`, so a
jump located exactly at the first body instruction was never morphed into a
JumpComplexCase and the block environment was left un-popped. Use `>=` for
break/continue/labelledBreak/labelledContinue.

With the environment now unwound correctly, the hasBinding guard band-aid in
InterpreterSlowPath::initializeByName is no longer needed and is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
Seonghyun Kim 2026-06-16 14:29:43 +09:00 committed by MuHong Byun
commit b30b63fc63
2 changed files with 12 additions and 8 deletions

View file

@ -183,26 +183,33 @@ struct ByteCodeGenerateContext {
void registerJumpPositionsToComplexCase(size_t frontlimit)
{
ASSERT(tryCatchWithBlockStatementCount());
// `frontlimit` is the byte position of the first instruction of the block body
// (i.e. lexicalBlockStartPosition, set right after the BlockOperation opcode).
// A break/continue jump located at exactly that position is the very first
// statement of the block body and therefore IS inside the block, so it must be
// morphed into a JumpComplexCase to unwind the block's lexical environment.
// Using strictly greater-than (`>`) here would skip such a jump and leave the
// block environment un-popped (Issue #1571), so the comparison must be `>=`.
for (unsigned i = 0; i < m_breakStatementPositions.size(); i++) {
if (m_breakStatementPositions[i] > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_breakStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
if (m_breakStatementPositions[i] >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_breakStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
m_complexCaseStatementPositions.insert(std::make_pair(m_breakStatementPositions[i], tryCatchWithBlockStatementCount()));
}
}
for (unsigned i = 0; i < m_continueStatementPositions.size(); i++) {
if (m_continueStatementPositions[i] > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_continueStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
if (m_continueStatementPositions[i] >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_continueStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
m_complexCaseStatementPositions.insert(std::make_pair(m_continueStatementPositions[i], tryCatchWithBlockStatementCount()));
}
}
for (unsigned i = 0; i < m_labelledBreakStatmentPositions.size(); i++) {
if (m_labelledBreakStatmentPositions[i].second > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledBreakStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
if (m_labelledBreakStatmentPositions[i].second >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledBreakStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
m_complexCaseStatementPositions.insert(std::make_pair(m_labelledBreakStatmentPositions[i].second, tryCatchWithBlockStatementCount()));
}
}
for (unsigned i = 0; i < m_labelledContinueStatmentPositions.size(); i++) {
if (m_labelledContinueStatmentPositions[i].second > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledContinueStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
if (m_labelledContinueStatmentPositions[i].second >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledContinueStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
m_complexCaseStatementPositions.insert(std::make_pair(m_labelledContinueStatmentPositions[i].second, tryCatchWithBlockStatementCount()));
}
}

View file

@ -1840,10 +1840,7 @@ NEVER_INLINE void InterpreterSlowPath::storeByName(ExecutionState& state, Lexica
NEVER_INLINE void InterpreterSlowPath::initializeByName(ExecutionState& state, LexicalEnvironment* env, const AtomicString& name, bool isLexicallyDeclaredName, const Value& value)
{
if (isLexicallyDeclaredName) {
auto result = state.lexicalEnvironment()->record()->hasBinding(state, name);
if (result.m_index != SIZE_MAX) {
state.lexicalEnvironment()->record()->initializeBinding(state, name, value);
}
state.lexicalEnvironment()->record()->initializeBinding(state, name, value);
} else {
while (env) {
if (env->record()->isVarDeclarationTarget()) {