Skip to content

C++: pure virtual methods get no node, so calls through an abstract interface don't resolve #1727

Description

@Dshuishui

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions