Skip to content

Fix CFA stack overflow crash in for loops where initializer throws#4274

Open
ibesuperv wants to merge 1 commit into
microsoft:mainfrom
ibesuperv:main
Open

Fix CFA stack overflow crash in for loops where initializer throws#4274
ibesuperv wants to merge 1 commit into
microsoft:mainfrom
ibesuperv:main

Conversation

@ibesuperv

Copy link
Copy Markdown

Fixes microsoft/TypeScript#63092

Analysis

Unlike while or do-while, a for-loop's initializer is bound before the loop's flow graph is constructed. If the initializer makes the control flow unreachable (e.g. an IIFE with a throw), addAntecedent filters out the unreachable entry to preLoopLabel, leaving only the back-edge from the incrementor. This creates a cycle with no exit that causes isReachableFlowNodeWorker to loop infinitely and overflow the stack.

Fix

Bail out early in bindForStatement if the initializer makes the control flow unreachable, and bind the remaining children as unreachable without constructing the cyclic loop flow graph.

Checklist

  • Includes test case
  • No regressions in existing tests
  • Code formatted with npx hereby format
  • Linted with npx hereby lint

Note

This PR was developed with AI assistance (Gemini).

Copilot AI review requested due to automatic review settings June 11, 2026 09:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a regression test for a control-flow analysis stack overflow involving for-loop initializers that always throw inside try/catch, and updates the Go binder to avoid creating an unreachable-cycle in the loop flow graph.

Changes:

  • Added a new compiler test case and reference baselines for the repro from TypeScript issue #63092.
  • Updated bindForStatement to bind the initializer before constructing the loop flow graph and to bail out early if the initializer makes flow unreachable.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
testdata/tests/cases/compiler/forLoopInitializerAlwaysThrowsInTryCatch.ts New regression test covering throwing for initializers in try/catch.
testdata/baselines/reference/compiler/forLoopInitializerAlwaysThrowsInTryCatch.types Baseline types output for the new regression test.
testdata/baselines/reference/compiler/forLoopInitializerAlwaysThrowsInTryCatch.symbols Baseline symbols output for the new regression test.
internal/binder/binder.go Binder change to prevent unreachable-cycle stack overflow during reachability analysis.

Comment thread internal/binder/binder.go
Comment thread internal/binder/binder.go
@ibesuperv

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@RyanCavanaugh RyanCavanaugh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copilot review comments seem plausible to me - can you add testcases to verify, and fix if needed?

@ibesuperv

Copy link
Copy Markdown
Author

The copilot review comments seem plausible to me - can you add testcases to verify, and fix if needed?

Hi Ryan! Thank you so much for helping me out with my first PR and guiding me to the right repository.

I actually addressed both of Copilot's review comments in the latest force-pushed commit (3e186c0):

  1. Dummy Targets Fix: Added dummy break/continue targets (b.createBranchLabel() and b.createLoopLabel()) in binder.go before binding the body statement in the unreachable initializer early-return path. This ensures that any break/continue inside the loop body bind correctly to this loop rather than leaking.
  2. Added Test Case: Added test3 to forLoopInitializerAlwaysThrowsInTryCatch.ts, which nests the throwing for loop inside a while (true) loop and includes break and continue in the body to verify this behavior works.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread internal/binder/binder.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crash: RangeError: Maximum call stack size exceeded in isReachableFlowNodeWorker with complex for loop headers

4 participants