Files
codegraph/__tests__/cpp-overload-argument-reentry.test.ts
T
Josh66andClaude Opus 5.5 0d7480f83b fix(cpp): an overload chosen by arguments that call it again no longer overflows the stack
On main, indexing darts-clone failed in "Resolving refs" with "Maximum call
stack size exceeded", and the whole run was lost. darts.h writes
`extras(extras(id).prev()).set_next(extras(id).next())` on a class with a
const and a non-const `extras(id)`. Choosing between them types the
arguments; typing `extras(id).prev()` types `extras(id)`, whose overload is
chosen by the arguments of the call the reference is at, the outer one, so
the same arguments are typed again. cppArgumentType restarts
cppExpressionType's depth at 0 on each lap, so the depth bound never trips.

cppArgumentTypeChoice now keeps the calls whose arguments it is typing
(`file:line:column:name`), and a choice that comes back to the same call
takes the first tied overload, the answer it already gives when no
argument's type is known. A try/finally clears the entry.

Validation on main 5526e33d (Windows 11):
- Red/green: the new test fails with main's name-matcher.ts (RangeError:
  Maximum call stack size exceeded) and passes with the fix.
- A/B against main: darts-clone fails on main and indexes with the fix,
  503 more linked sites (897 edges against the 394 the failed run left),
  with `reserve_id` and `expand_units` reaching `extras`, `prev`, `next`,
  `set_prev` and `set_next`. leveldb, fmt, googletest and nlohmann/json are
  byte-identical, as are the express and flask controls.
- Index time unchanged: median of 3 interleaved runs on nlohmann/json,
  15.9 s on main and 16.0 s with the fix.

Original PR #2999 by @JosefAschauer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:41:11 +00:00

68 lines
2.3 KiB
TypeScript

/**
* Choosing among a C++ method's overloads types the call's arguments, and an
* argument can be a call of that same overloaded method:
* `extras(extras(id).prev()).set_next(…)` on a class with a const and a
* non-const `extras(id)`. Typing `extras(id).prev()` types `extras(id)`, whose
* overload is chosen by the arguments of the call the reference is at — the
* outer one — so the choice re-entered itself with a fresh depth on every lap
* until the stack ran out, and the whole index failed ("Maximum call stack size
* exceeded"). A re-entered choice now takes the first overload, as one whose
* arguments' types are unknown does.
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';
let root = '';
let cg: CodeGraph | undefined;
beforeAll(() => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cpp-overload-reentry-'));
fs.mkdirSync(path.join(root, 'src'));
fs.writeFileSync(path.join(root, 'src/builder.h'), `#pragma once
class Builder {
public:
struct Extra {
int prev() const { return prev_; }
int next() const { return next_; }
void set_prev(int prev) { prev_ = prev; }
void set_next(int next) { next_ = next; }
int prev_;
int next_;
};
void unlink(int id) {
extras(extras(id).prev()).set_next(extras(id).next());
extras(extras(id).next()).set_prev(extras(id).prev());
}
private:
const Extra &extras(int id) const { return extras_[id]; }
Extra &extras(int id) { return extras_[id]; }
Extra extras_[16];
};
`);
});
afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});
describe('a C++ overload chosen by arguments that call the same overloaded method', () => {
it('indexes, and links the calls in the method', async () => {
cg = await CodeGraph.init(root, { index: true });
const unlink = cg.getNodesInFile('src/builder.h').find((n) => n.name === 'unlink')!;
expect(unlink).toBeDefined();
const called = new Set(cg.getOutgoingEdges(unlink.id).filter((e) => e.kind === 'calls')
.map((e) => cg!.getNode(e.target)!.name));
expect(called).toContain('extras');
expect(called).toContain('set_next');
expect(called).toContain('set_prev');
});
});