Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In a project that uses Vue, an import written through an `@/` or `~/` alias, like `import { Button } from '@/components'`, now links the file it imports, as it does in any other project, so `codegraph sync` and the file watcher update the files that import a module when that module, or a barrel they import through, is added or deleted. Before, Vue support linked such an import to the first declaration or the component in that file, which sync does not follow, and an import through a `tsconfig` alias that starts like one of Nuxt's built-in modules, like `#app/where`, linked the importing file to itself. Re-index Vue projects after upgrading.
- `codegraph sync` and the file watcher now move an import written without an extension, like `import { sizeOf } from './button'`, to a file added beside the one it found before when the new file comes first, such as `button.ts` next to `button.vue`. Before, the import and the names it binds stayed linked to `button.vue` until the importing file changed or the project was re-indexed. JavaScript, TypeScript, Vue, Svelte and Astro.
- In C, C++ and Objective-C, a struct or class member declared as a pointer, reference, array or function, like `SharedState* shared;`, `jv elements[];` or `virtual Status Put(…) = 0;`, no longer makes the struct look like it inherits from the member's type. Before, `codegraph_explore` and impact listed those types as base classes, every struct holding a pointer to a type as one of its subclasses, and a method of that struct as an override of the type's method with the same name. Real base classes like `class Derived : public Base`, Objective-C superclasses and Go's embedded fields are unchanged. Re-index C, C++ and Objective-C projects after upgrading.
- In C and C++, a function-pointer table's handler, like `.read = impl_read` in an ops struct, no longer links to a Python or Rust function that only shares its name, and such a function no longer takes the place of the C function the table names. Re-index C and C++ projects that also hold Python or Rust after upgrading. Thanks @bompus.
- In C++, a call on a variable, parameter or member declared through a `typedef` or `using` alias now links to the method of the type the alias names, with the alias looked up the way C++ does it: in the calling function, its class and the classes that class inherits from, then the namespaces around it. In google/leveldb, `table_.Insert(buf)` on a `Table table_;` declared next to `typedef SkipList<const char*, KeyComparator> Table;` now reaches `SkipList::Insert` instead of the unrelated `HandleTable::Insert`, and `Table::Iterator iter(&table_);` reaches `SkipList::Iterator`'s constructor. A class that only shares the alias's name is no longer taken for it, and when the aliased type has no such method, as with an alias of `std::vector` or of a template parameter, no method is guessed from the receiver's name. An alias of a type written from the global scope names that global type, also inside a namespace of the same name: protocolbuffers/protobuf's `using Arena = ::upb::Arena;` in `hpb::internal::backend::upb`, so hpb's `arena_.Fuse(…)` on a `backend::Arena` now reaches `upb::Arena::Fuse` instead of the calling method itself. Re-index C++ projects after upgrading.
- In C++, a call on a variable, parameter or member declared with template arguments nested inside its template arguments, like `autovector<std::pair<int, FileMetaData*>>` or `Striped<CacheAlignedWrapper<port::Mutex>>`, now links to the method of the class it is declared as. Before, the nested arguments hid that class: in RocksDB, `files_marked_for_compaction_.clear()` on such an `autovector` linked to an unrelated `CompactionInputFiles::clear` that only shares words with the variable's name, and `mutex_.Get(key)` on a `Striped` linked to nothing. Re-index C++ projects after upgrading.
- In C++, a base class like `Message` in `class DynamicMessage final : public Message` now links to the class C++ itself would pick: the one declared in the namespaces around the class, or brought in by the file's `using` declarations and namespace aliases, and the class a `typedef` or `using` alias names. Before, a base class was matched by its name alone, so it could land on a class of the same name in another namespace or another language: in protocolbuffers/protobuf, `DynamicMessage` derived from the JSON parser's internal `Message` class, and in google/leveldb and RocksDB, iterators derived from an unrelated nested `Iterator` class such as the skip list's. The wrong base showed up in `codegraph_explore`'s type hierarchy and in impact, and tied the base's methods to the wrong overrides. A template parameter used as a base no longer links to a class that shares its name, and a base written from the global scope, like `public ::testing::Test`, now links to its class. Re-index C++ projects after upgrading.
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