Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

- The Claude Code prompt hook no longer runs on the task-notification messages Claude Code injects when a background agent finishes, removing a multi-second stall on every such turn. (#1832)

- C function-pointer calls no longer link to a Python or Rust function that happens to share the handler's name, and such a function no longer hides the real C handler.

- Rust calls on `self` now stay with the enclosing type instead of linking to an unrelated type’s same-named method. Thanks @L4XB. (#1861)

- Turning telemetry off now resets its identity and stops running processes from recording, sending, or restoring unsent data. (#1869)
Expand Down
23 changes: 23 additions & 0 deletions __tests__/c-fnptr-synthesizer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,29 @@ int total(struct box *x) { return x->count + 1; }
expect(edges.length).toBe(0);
});

it('links registrations only to C/C++ functions, never a same-named function in another language', async () => {
write('ops.h', `struct ops { int (*read)(int); int (*probe)(int); };\n`);
write('impl.c', `int impl_read(int fd) { return fd; }\n`);
write('ops.c', `
#include "ops.h"
int impl_read(int fd);
static struct ops the_ops = { .read = impl_read, .probe = gen_probe };
int use(struct ops *o, int fd) { return o->read(fd) + o->probe(fd); }
`);
write('aaa/gen.py', `def impl_read():\n return 1\n\ndef gen_probe():\n return 2\n`);
const cg = await CodeGraph.init(dir, { silent: true });
await cg.indexAll();
const rows: { tgt: string; file: string }[] = (cg as any).db.db
.prepare(
`SELECT t.name tgt, t.file_path file FROM edges e JOIN nodes t ON t.id = e.target
WHERE json_extract(e.metadata,'$.synthesizedBy') = 'fn-pointer-dispatch'`
)
.all();
cg.close?.();
expect(rows.filter((r) => r.tgt === 'impl_read').map((r) => r.file)).toEqual(['impl.c']);
expect(rows.some((r) => r.file.endsWith('.py'))).toBe(false);
});

it('is a no-op on a project with no C/C++ (clean control)', async () => {
write('app.js', `
const handlers = { add: (x) => x + 1, rm: (x) => x - 1 };
Expand Down
4 changes: 3 additions & 1 deletion src/resolution/c-fnptr-synthesizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -879,7 +879,9 @@ export async function cFnPointerDispatchEdges(

// ---- function-name → node resolution (prefer a function in the same file) ----
const resolveFn = (name: string, preferFile?: string): Node | null => {
const cands = ctx.getNodesByName(name).filter((n) => FN_KINDS.has(n.kind));
// C and C++ functions only: a same-named Python or Rust function is never
// what a C table registers, and must not displace the C one.
const cands = ctx.getNodesByName(name).filter((n) => FN_KINDS.has(n.kind) && C_CPP_EXT.test(n.filePath));
if (cands.length === 0) return null;
if (cands.length === 1) return cands[0]!;
if (preferFile) {
Expand Down