Skip to content

[BUG][auto] ts/finally-block-reported-unreachable: statements in a finally block are reported unreachable when the try or catch block returns or throws #172

Description

@DaisukeYoda

Bug

When a try block ends in return or throw, or its catch ends in throw, the CFG never connects an edge into the attached finally block, so every statement in that finally is reported as unreachable_branch at warning severity. Those are exactly the cases finally exists for, so the cleanup is guaranteed to run.

Cause: buildTryStatement (polyscan/internal/js/analyzer/cfg_builder.go:698-712) guards both edges into the finally block with !b.endsWithJump(...):

if tryEndBlock != nil && !b.endsWithJump(tryEndBlock) {
    b.cfg.ConnectBlocks(tryEndBlock, finallyBlock, EdgeNormal)
}
if catchBlock != nil && b.currentBlock != nil && !b.endsWithJump(b.currentBlock) {
    b.cfg.ConnectBlocks(b.currentBlock, finallyBlock, EdgeNormal)
}

A return or throw inside a try or catch must route through the finally block before leaving the function; the guard drops that edge instead.

Repro

f.ts

export const withReturn = async (t: unknown) => {
	let timer: unknown = null;
	try {
		return await Promise.resolve(t);
	} finally {
		if (timer) clearCleanup(timer);   // line 6
	}
};

export const withThrow = (t: unknown) => {
	try {
		throw new Error('x');
	} finally {
		cleanup(t);                        // line 14
	}
};

export const noReturnInTry = (t: unknown) => {
	try {
		cleanup(t);
	} finally {
		cleanup(t);                        // line 22
	}
};

declare function clearCleanup(x: unknown): void;
declare function cleanup(x: unknown): void;
polyscan analyze --select deadcode --format json f.ts

Actual

withReturn  line 6   unreachable_branch  This branch is unreachable
withThrow   line 14  unreachable_branch  This branch is unreachable

Expected

No findings. Both finally bodies run. Line 22 is correctly silent, which isolates the trigger: the false positive appears only when the try (or catch) terminates via return or throw.

Real-world occurrences

On imbue-ai/latchkey@d2ee791, 7 of the 8 unreachable_branch findings are this bug:

finding finally body why it is reached
src/playwrightUtils.ts:601 clearTimeout(timer) try { return await Promise.race(...) }
src/playwrightUtils.ts:188-193 await browser.close() + temp dir cleanup try { ... return result } catch { ... throw error }
src/services/dropbox.ts:341-342 page.off('close', ...) same shape
src/services/fastmail.ts:305-306 page.off('close', ...) same shape
src/services/google/base.ts:904-905 page.off('close', ...) same shape
src/services/notion-mcp.ts:336-337 page.off('close', ...) same shape
src/services/ramp.ts:194-195 page.off('close', ...) same shape

All three unreachable_branch findings on almeidazs/better-drizzle@23527ae (src/shared/client/explain.ts:189, src/shared/client/factory.ts:587, :918) are the same: a timer or abort controller released in a finally after return in the try.

Priority

P1 by the audit rubric: a wrong finding on a common construct. try { return } finally { cleanup } is the standard cleanup idiom for cancellable async work, and the findings are warning severity. Good first issue: the fix is local to the two guards above.

polyscan 0.4.1 (commit d41b3bd). Found via the FP-audit skill in repos imbue-ai/latchkey@d2ee7918068c96ccbd12860204502e36575a9d3f and almeidazs/better-drizzle@23527ae.

Activity

  1. added
    bugSomething isn't working
    P1High priority
    auto-filedFiled automatically by the polyscan-fp-audit skill (review before acting)
    on Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High priorityauto-filedFiled automatically by the polyscan-fp-audit skill (review before acting)bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions