fix(cfg): model C++ co_return as a return terminator to EXIT (#2195)

co_return_statement was neither in CPP_CONTROL_FLOW_TYPES nor dispatched, so a
coroutine's co_return coalesced into a straight-line block and emitted a
spurious seq fallthrough to the following statement instead of an edge to EXIT
— statements after co_return looked reachable and the terminator edge was
missing, corrupting CFG/CDG for coroutines. Add the node type to the C++
control-flow set and dispatch it through visitReturn (block -> EXIT 'return',
no fallthrough). C path untouched; co_await/co_yield remain plain expressions.
Characterization test added; c-cpp suite + grammar-literal gate green, bench
--check unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gergo Magyar 2026-06-14 18:43:20 +00:00
parent 5165d71d7b
commit a654c1c764
2 changed files with 19 additions and 0 deletions

View file

@ -93,6 +93,7 @@ const CPP_CONTROL_FLOW_TYPES = new Set([
'for_range_loop',
'try_statement',
'throw_statement',
'co_return_statement',
]);
const LOOP_OR_SWITCH_TYPES = new Set([
@ -589,6 +590,11 @@ class CppCfgWalk extends CCfgWalk {
return this.visitTry(stmt);
case 'throw_statement':
return this.visitThrow(stmt);
case 'co_return_statement':
// A coroutine `co_return` terminates the coroutine like an ordinary
// return: edge to EXIT, no fallthrough (`co_await`/`co_yield` are plain
// expressions that continue, so they need no control-flow handling).
return this.visitReturn(stmt);
default:
return undefined;
}

View file

@ -253,6 +253,19 @@ describe('C++ CfgVisitor — structured bindings (#2195 P1)', () => {
});
});
describe('C++ CfgVisitor — coroutines (#2195 P1)', () => {
it('co_return edges to EXIT (return), not a seq fallthrough to the next statement', () => {
const cfg = cpp.cfgOf(`Task f(int x) { if (x) co_return early(); main(); }`);
const co = block(cfg, 'co_return early();');
// co_return is a return terminator: it edges to EXIT…
expect(
cfg.edges.some((e) => e.from === co && e.to === cfg.exitIndex && e.kind === 'return'),
).toBe(true);
// …and never falls through to the following statement.
expect(cfg.edges.some((e) => e.from === co && e.kind === 'seq')).toBe(false);
});
});
describe('C CfgVisitor — functionStartColumn', () => {
it('two same-line functions get distinct functionStartColumn', () => {
const cfgs = c.cfgsOf(`int a(){return 1;} int b(){return 2;}`);