Repository navigation
Narrowing in an else branch ends with the branch - #377
Merged
Merged
Conversation
The then-branch of an if, a conditional and a logical operator opens a SafeTypesMapScopeT, but the false side did not. A field narrowed there (`this.head` after `if (this.head === null) ... else`) stayed in safeTypesMap up to the module-level scope. Later reads of the field got a value from the else region: in the rest of the function, and in the next method compiled, whose discovery pass had already been discarded. The result was a crash or a bogus "saving to constant object". The false side now opens its own scope. Found in the BrowserLib sources attached to #231. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ASDAlexander77
force-pushed
the
fix-else-narrowing-scope
branch
from
September 27, 2026 21:51
e845917 to
2cdf9ff
Compare
ASDAlexander77
added a commit
that referenced
this pull request
Sep 28, 2026
#383) `if (x === null) return;` now narrows x for the rest of the block, as an else branch does: an if without else whose body always returns, throws, breaks or continues applies its else narrowing after the if. Each block opens its own narrowing scope, so a narrowing made in it ends with it. A narrowed field (`this.head` after `this.head !== null`) was mapped to the value the narrowing cast, and every later read reused that value. An assignment to the field then failed ("saving to constant object"), a read after it returned the old value, and a value used outside its region was the use-after-free #377 fixed for else. safeTypesMap now keeps only the narrowed type: a read loads the field again and casts it the way addSafeCastStatement does (castToNarrowedType), and an assignment writes the field itself. Found in the BrowserLib sources attached to #231 (Event.ts `remove`). Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Narrowing a field in an
elsebranch was not limited to that branch. Code after theif, and the next method compiled, then read a value from a region that no longer existed. Found in the BrowserLib sources attached to #231, where 7 of 14 files crashed.Event.tsalone reproduces it.Cause
Narrowing a property access registers
{object type, "this.head"} → narrowed valueinsafeTypesMap. The true side of anif, a?:and an&&opens aSafeTypesMapScopeT, so its entry is removed when that side ends. The false side (theelsenarrowing fromElseSafeCase) opened only aSymbolTableScopeT. Its entry stayed until the module-level scope inMLIRGenModule.cpp, which caused two problems:this.headas theelsevalue, even though that value is defined inside theelseregion.this.headup again and got a value from the discarded discovery body. What happened next depended on how the freed memory was reused: a crash, a bogus "saving to constant object", or nothing visible.Fix
The false side of
if(MLIRGenStatements.cpp), of?:(MLIRGenExpressions.cpp) and of||(MLIRGenImpl.h) now opens aSafeTypesMapScopeT, the same as the true side.||does not narrow its right side yet, so that change only keeps the three places consistent.Tests
00safe_cast_else_scope.ts(compile, jit, and the rc/none corpus). It covers narrowing in anif/else, in a?:, a read after anif/elsein the same method, and a later method. Each is checked withheadnull and not null. Before the fix it fails with 0xC0000005.The other #231 crash, a field of a union compared with
null, is fixed in #376.🤖 Generated with Claude Code