mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
fix(cfg): synthesize a protected block for an empty Kotlin try {} (#2195)
An empty `try {}` body produced zero protected blocks, so visitTry's throw-edge
loop wired nothing to the catch and the try's entry fell through to the finally
— leaving the catch handler block + its error binding orphaned (unreachable from
ENTRY), a malformed CFG with stranded def/use facts. Mirror the existing
empty-`catch` synthesis: when the try body is empty and there is a catch or
finally, synthesize one protected block so the catch handler(s) are wired and
the try entry is the body, not the finally. Found by the per-language CFG
verification swarm. Non-empty try is byte-identical; kotlin suite 31 passed,
tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5d284b8f5f
commit
6fed52e1a6
2 changed files with 23 additions and 1 deletions
|
|
@ -739,9 +739,19 @@ class KotlinCfgWalk {
|
|||
const tryHandler = firstCatchEntry ?? finalizerEntry ?? this.currentHandler();
|
||||
const protectedStart = this.builder.blockCount;
|
||||
this.handlers.push(tryHandler);
|
||||
const bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
||||
let bodyRes = bodyNode ? this.visitSeq(this.statementsOf(bodyNode)) : null;
|
||||
this.handlers.pop();
|
||||
|
||||
if (bodyRes === null && (catchBlocks.length > 0 || finalizerEntry !== undefined)) {
|
||||
// An empty `try {}` body still establishes a protected region. Synthesize
|
||||
// one block (like the empty-`catch` case above) so the throw-edge loop
|
||||
// wires the catch handler(s) and the try's entry is the body — otherwise
|
||||
// the catch handler block + its error binding are orphaned (unreachable
|
||||
// from ENTRY) and control routes straight to the finally, bypassing catch.
|
||||
const idx = this.builder.newBlock(startLineOf(stmt), startLineOf(stmt), '');
|
||||
bodyRes = { entry: idx, exits: [idx] };
|
||||
}
|
||||
|
||||
if (catchBlocks.length > 0 || finalizerEntry !== undefined) {
|
||||
for (let b = protectedStart; b < this.builder.blockCount; b++) {
|
||||
this.builder.edge(b, tryHandler, 'throw');
|
||||
|
|
|
|||
|
|
@ -239,6 +239,18 @@ describe('Kotlin CfgVisitor — try/catch/finally', () => {
|
|||
expect(reaches(cfg, thr, block(cfg, 'done()'))).toBe(false);
|
||||
expect(reachable(cfg, block(cfg, 'done()'))).toBe(true); // via the if false branch
|
||||
});
|
||||
|
||||
it('empty try {} with catch + finally keeps the catch handler reachable (#2195)', () => {
|
||||
// An empty try body still establishes a protected region — the catch (and
|
||||
// its error binding) must not be orphaned/unreachable from ENTRY.
|
||||
const cfg = kotlin.cfgOf(
|
||||
`fun f() { try {} catch (e: Exception) { handle(e) } finally { cl() }; a() }`,
|
||||
);
|
||||
expect(reachable(cfg, block(cfg, 'handle(e)'))).toBe(true);
|
||||
expect(reachable(cfg, block(cfg, 'cl()'))).toBe(true);
|
||||
expect(reachable(cfg, block(cfg, 'a()'))).toBe(true);
|
||||
expect(definesBinding(cfg, bindingIdx(cfg, 'e'))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Kotlin CfgVisitor — labeled break/continue', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue