diff --git a/gitnexus/src/core/ingestion/cfg/visitors/swift.ts b/gitnexus/src/core/ingestion/cfg/visitors/swift.ts index 19bb85a32..e19279c05 100644 --- a/gitnexus/src/core/ingestion/cfg/visitors/swift.ts +++ b/gitnexus/src/core/ingestion/cfg/visitors/swift.ts @@ -740,10 +740,14 @@ class SwiftCfgWalk { const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null; this.handlers.pop(); - // Conservative exceptional edges: every protected-region block → the handler. - if (catchBlocks.length > 0) { + // Conservative exceptional edges: every protected-region block → EACH catch + // handler. Swift tries the catch clauses in order until one matches; the + // thrown type is unknown at CFG time, so any protected block may reach ANY + // clause. Edging only the first handler orphaned the 2nd..Nth catch blocks + // (unreachable from ENTRY, stranding their error bindings + def/use facts). + if (handlerEntries.length > 0) { for (let b = protectedStart; b < this.builder.blockCount; b++) { - this.builder.edge(b, doHandler, 'throw'); + for (const handler of handlerEntries) this.builder.edge(b, handler, 'throw'); } } diff --git a/gitnexus/test/unit/cfg/swift-visitor.test.ts b/gitnexus/test/unit/cfg/swift-visitor.test.ts index 3944875e7..7a46bb9ed 100644 --- a/gitnexus/test/unit/cfg/swift-visitor.test.ts +++ b/gitnexus/test/unit/cfg/swift-visitor.test.ts @@ -238,6 +238,17 @@ describe('Swift CfgVisitor — do/catch (error handling)', () => { expect(reaches(cfg, thr, block(cfg, 'done()'))).toBe(false); expect(reachable(cfg, block(cfg, 'done()'))).toBe(true); // via the if false branch }); + + it('multi-catch: EVERY catch handler is reachable from ENTRY (#2195)', () => { + // The protected body can throw an error matching ANY clause, so the 2nd..Nth + // catch must not be orphaned — the bug routed the throw edge only to the + // first handler, leaving later handlers unreachable from ENTRY. + const cfg = swift.cfgOf(`func f() { do { try r() } catch A { ha() } catch { hb() } }`); + expect(reachable(cfg, block(cfg, 'ha()'))).toBe(true); + expect(reachable(cfg, block(cfg, 'hb()'))).toBe(true); + // the protected `try r()` reaches both handlers. + expect(reaches(cfg, block(cfg, 'try r()'), block(cfg, 'hb()'))).toBe(true); + }); }); describe('Swift CfgVisitor — defer (LIFO scope-exit)', () => {