A virtual call through an abstract base yields no edge in the call graph — the pure virtual method itself is never indexed as a node.
Repro
src/store.cc, the whole project:
class Store {
public:
virtual ~Store() {}
virtual int read(int key) = 0;
};
class DiskStore : public Store {
public:
int read(int key) override { return key + 1; }
};
class MemStore : public Store {
public:
int read(int key) override { return key + 2; }
};
int fetch(Store* s, int k) {
return s->read(k);
}
After codegraph index:
nodes: Store (class) · Store::~Store · DiskStore (class) · DiskStore::read
MemStore (class) · MemStore::read · fetch (function)
edges: DiskStore -> Store (extends) · MemStore -> Store (extends)
Store::read is absent, and s->read(k) produces no calls edge at all — the graph holds only the two extends edges.
The same shape in Java
We noticed nodes seem to be minted for definitions, and a pure virtual has no body, so skipping it follows that rule. Java behaves differently, though:
interface Store { int read(int key); }
class DiskStore implements Store { public int read(int key) { return key + 1; } }
class MemStore implements Store { public int read(int key) { return key + 2; } }
class Client { Store s; int fetch(int k) { return s.read(k); } }
Client::fetch -> Store::read (real)
Store::read -> DiskStore::read (heuristic)
Store::read -> MemStore::read (heuristic)
A Java interface method has no body either, yet it gets a node, the call resolves onto it, and the synthesized edges reach both implementations.
What follows from the missing base node
- The call site has no static target, so it falls back to matching same-named methods. In real projects that candidate set is large —
Clear in protobuf and format in spdlog each have on the order of a hundred same-named nodes, and those are the projects' central interface methods. Which one gets picked is essentially arbitrary; on leveldb, DBImpl::NewDB (db/db_impl.cc:190) resolves onto SpecialEnv::NewWritableFile in db/db_test.cc.
cppOverrideEdges builds its base-method map from the base class's method nodes, so with no base node it never fires here, and the implementing classes end up with no incoming edges.
Scope
Indexed leveldb, re2, nlohmann/json, spdlog, yaml-cpp, fmt and protobuf: of the four hundred-odd pure virtual declarations across them, under 5% are indexed, and all seven show the same thing. Across those projects roughly seven in ten implementations of those methods end up with no real incoming edge (a rough upper bound — the count is name-based, so it will include some coincidental matches).
Related
#1638 reports the same shape in TypeScript — interface members are never indexed, so calls through a .d.ts API have nothing to attach to. That issue notes Java and C# are unaffected; the C++ case above suggests the pattern isn't limited to TypeScript either.
Happy to put up a PR if you'd like it done this way — the extractor rule looks deliberate, so I didn't want to change it unilaterally.
Environment: codegraph 1.6.0.
A virtual call through an abstract base yields no edge in the call graph — the pure virtual method itself is never indexed as a node.
Repro
src/store.cc, the whole project:After
codegraph index:Store::readis absent, ands->read(k)produces nocallsedge at all — the graph holds only the twoextendsedges.The same shape in Java
We noticed nodes seem to be minted for definitions, and a pure virtual has no body, so skipping it follows that rule. Java behaves differently, though:
A Java interface method has no body either, yet it gets a node, the call resolves onto it, and the synthesized edges reach both implementations.
What follows from the missing base node
Clearin protobuf andformatin spdlog each have on the order of a hundred same-named nodes, and those are the projects' central interface methods. Which one gets picked is essentially arbitrary; on leveldb,DBImpl::NewDB(db/db_impl.cc:190) resolves ontoSpecialEnv::NewWritableFileindb/db_test.cc.cppOverrideEdgesbuilds its base-method map from the base class's method nodes, so with no base node it never fires here, and the implementing classes end up with no incoming edges.Scope
Indexed leveldb, re2, nlohmann/json, spdlog, yaml-cpp, fmt and protobuf: of the four hundred-odd pure virtual declarations across them, under 5% are indexed, and all seven show the same thing. Across those projects roughly seven in ten implementations of those methods end up with no real incoming edge (a rough upper bound — the count is name-based, so it will include some coincidental matches).
Related
#1638 reports the same shape in TypeScript — interface members are never indexed, so calls through a
.d.tsAPI have nothing to attach to. That issue notes Java and C# are unaffected; the C++ case above suggests the pattern isn't limited to TypeScript either.Happy to put up a PR if you'd like it done this way — the extractor rule looks deliberate, so I didn't want to change it unilaterally.
Environment: codegraph 1.6.0.