Narrowing after an early exit; a narrowed field is read from the field - #383
Merged
Merged
Conversation
`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.
Two related narrowing gaps, both found in
Event.tsof the BrowserLib sources attached to #231:1. Narrowing after an early exit
An
ifwithout anelse, whose body always returns, throws,breaks orcontinues, now applies itselsenarrowing after theif, for the rest of the enclosing block. This is what TypeScript does.statementAlwaysExits):return,throw,breakorcontinue;if/elsewhose branches both always exit.mlirGen(ts::Block), and the try body ofmlirGenBlockWithUnwindCleanup) now opens its ownSafeTypesMapScopeT, so a narrowing made in a block ends with it. Without that, a narrowed field would outlive its function, the same leak Narrowing in an else branch ends with the branch #377 fixed forelse.2. A narrowed field is read from the field
safeTypesMapmapped a narrowed field (this.headafterthis.head !== null) to the value the narrowing cast, and every later read returned that same value. That caused three problems:safeTypesMapnow keeps only the narrowed type:castToNarrowedType. That new helper does whataddSafeCastStatementalready did:GetValueFromUnionOpfor a union,ValueOpfor an optional,UnboxOpforany, andcastotherwise.mlirGenAssignedPropertyAccess) skips the narrowing and writes the field itself.Tests
New
00safe_cast_early_exit.ts(compile, jit, and the rc/none corpus). It covers:if (… === null) return;on a field, and on a parameter withthrow;continuein afor…of;removefrom Event.ts, assigning to the narrowed field and walking withcur.next;The old build crashes on it (0xC0000005).
Full release suite: 2993/2993 passed, including the
owned-unionsand ownership-verifier tests, which exercise the union narrowing path.TypeScriptCompilerDefaultLib tests, release jit and compile: 156/156 each.
Not in this PR
this.head = this.head.next, a laterthis.head.vin the same narrowed block is still cast as non-null. TypeScript would reject that read in strict mode.if (x !== null) { … } else return;, where theelseis the exiting side, does not narrow after theifyet.🤖 Generated with Claude Code