Repository navigation
fix(c,cpp): a struct links the types its members are declared with - #3144
Merged
Merged
Conversation
C and C++ structs referenced nothing through their members. 1.6.2 linked many of them by accident, as false `extends` edges; PR #2394 was right to remove that label but left nothing in its place, so impact on a type missed everything that holds one. On redis `impact pause_event` went from 2 symbols to 1, and rocksdb lost 4,284 such relations, redis 697: the largest loss against 1.6.2. Extraction, wasm extractor and kernel mirrored: a class, struct or union pushes a `references` ref, on the name, for each type its members are declared with: plain, pointer, reference, array, tagged, enum, union, function pointer (return and parameters), qualified, and template arguments, so `std::vector<Foo>` links `Foo`. Members mint no nodes, like Go's fields (PR #2655). Members in `#if` groups and in unnamed structs and unions are the holder's. A method's return type counts, its parameters don't: a member a macro trails (`Version* v GUARDED_BY(mu);`) parses the same way. Not read: builtins, `std::` names, the type names C defines itself, the holder's own name, `decltype` and `auto`. An alias references what it names, so a change to `redisObject` reaches what holds a `robj *`. Resolution: the C++ name lookup base classes and receivers use, widened to enums and stopping at an alias. Only type declarations are candidates, so a function or macro of that name links nothing. In C, where one type spreads over files, what the file declares or includes comes first and an alias is the class's when a file including it defines it, and a type only a source file defines is found for a header it includes. Explore: these edges take no render window and stay out of the rank walk. With them in, rocksdb's read-path query lost four named symbols to member lines of its headers. Validation against main: redis +1,968 edges, rocksdb +10,145, leveldb +440, fmt +622, jq +114, 0 lost or moved, nodes identical; gin and flask dumps byte-identical. Of 1.6.2's member sites, redis 697: 610 same target, 87 corrected; rocksdb 4,284: 4,201 same, 58 corrected, 25 rightly gone (the class itself, a template parameter). By-value members, 2,794 on rocksdb, are new coverage. Kernel parity 0 diffs on all five repos, and full-index dumps wasm vs kernel byte-identical on redis, leveldb, fmt and jq. Index cost on rocksdb, median of 3 interleaved: CPU +3.4%, peak memory +1.9%, database +2.6%, wall within noise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Nov 1, 2026
bompus
added a commit
to bompus/codegraph
that referenced
this pull request
Nov 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
C and C++ structs linked to nothing through their members. On 1.6.2 many did, by accident, as false
extendsedges; #2394 was right to remove that label, but left nothing in its place, soimpact pause_eventon redis-shaped code lostredisServer(2 symbols on 1.6.2, 1 on main). rocksdb lost 4,284 such relations and redis 697: the largest loss found against 1.6.2.A class, struct or union now has a
referencesedge to each type its members are declared with, and atypedeforusingalias has one to what it names.extendsdoes not come back: the type hierarchy is untouched.Root cause
extractInheritanceused to read a C member with a nested declarator as Go's embedding. #2394 gated that to Go. Nothing else read a member's type: C and C++ have no field nodes, andimpactnever climbs from a member to its container, so a type held by a struct had no incoming edge at all.Fix
Extraction, wasm and kernel mirrored (
cMemberTypeNames/cAliasTypeNamesinlanguages/c-cpp.ts,member_type_names/alias_type_namesinccpp/mod.rs):field_declarationwith a declarator pushes areferencesref from the holder, on the name, like Go's fields (fix(go): a struct links the types its fields are declared with #2655). Members mint no nodes.const, tagged (struct foo *f),enum, union, function pointer (return and parameters), qualified and namespaced names, and template arguments (std::vector<Foo>linksFoo).#ifgroups and in unnamed structs or unions belong to the holder: nothing can name the unnamed type.std::names, the type names C defines itself (_Bool,uintmax_t, ...), the holder's own name (a list'snext),decltype/auto, and a nested definition that declares no member (class Rep;).Why impact needs no change:
getImpactRadiusfollows every incoming edge kind butcontains, soreferencesfrom a holder reach it, and on to the holder's own dependents. It never climbs from a member to its container (#536), which is why the edge is the class's and members mint no nodes.Resolution (
matchCppDeclaredTypeincpp-supertypes.ts): the same C++ name lookup base classes and receivers use, throughcppClassWritten, widened to enums and stopping at an alias rather than following it. Only type declarations are candidates, sostruct stat st;besideint stat(...)or a#defineof the same name links nothing. No other strategy sees these refs. Three rules on top:dictEntryis a typedef insrc/dict.hand a struct insrc/dict.c, whiledeps/hiredis/dict.hdeclares its own, the only structsrc/dict.hcan see by name. What a file declares or includes comes first, and an alias is the class's when a file including it defines it.Rep), in a scope the class can reach.pthread_key_tto rocksdb'sport::pthread_key_t.Explore (
src/mcp/tools.ts): these edges take no render window and stay out of the rank walk, for C and C++ only. Each member line had become an edge site, and on rocksdb's read-path query those windows pushed four named symbols out of the output.Calls for review
Status Put(...);in a class linksStatus; parameters don't. They are 2,850 of rocksdb's 4,259 recovered sites, so leaving them out would have left two thirds of the loss in place, and a member with a trailing macro,Version* v GUARDED_BY(mu);, parses the same way, so the two can't be told apart.impact redisObjectnever reaches what holds arobj *. They're one call in each engine if you want them out: 929 of rocksdb's added edges, 411 of redis's.EXTRACTION_VERSIONstays 28. 1.6.2 is 27 and 28 is unreleased, so upgraders are already told to re-index.Not covered
extendstoo. No REG repo covers them.ConnectionCallbackFunc cb;, links nothing: such typedefs index as(*Name), an existing naming bug: 138 member refs on redis, 23 on rocksdb.struct foo_s *xwhere the only declaration istypedef struct foo_s {...} foo_t;: the tag isn't indexed.Validation
Red to green:
__tests__/c-cpp-member-type-refs.test.ts, 36 tests on the kernel and wasm, LF and CRLF: 29 fail on main, all pass here. The explore rule's test was also run with the rule off, and fails. Torture fixtures extended.Fixture
cstruct:impact pause_eventis 2 symbols on 1.6.2, 1 on main, 2 here.cstruct2: 1.6.2 links 3 of 4 members, this links all 4.A/B against main. Same base, each arm its own build and kernel, keyed by site. No edge lost, moved or changed metadata on any repo, and no node changed. REG's 1.6.2 indexes supply the member sites.
Control dumps are byte-identical. Corrected means 1.6.2 had the wrong target: rocksdb's
Iterator*pointed atWriteThread::WriteGroup::Iterator,StatisticsatBlockCacheTier::Statistics, redis'sclient *atredis-benchmark.c's struct. Gone means no edge is right: on rocksdb, 18 template parameters and 7 classes naming themselves. leveldb's one is the local-class gap above, and fmt's are 2 inline methods and a template parameter.Every added edge, by class:
By-value members never linked on 1.6.2, so they are new coverage, not recovered edges.
Reading the new edges. About 500 read against the source, by fixed-seed sample per class on all five repos, by value and template arguments included, with extra weight on names several declarations share and all 85 of rocksdb's fallback matches. That reading found four wrong patterns, all fixed and pinned by tests: rocksdb's system
pthread_key_tbound toport::pthread_key_t; a macro-importedHashbound to a sibling class's; redis'sdictEntryinsrc/dict.hbound to hiredis's copy; jemalloc's MSVC shims taking_Boolanduintmax_t. Nothing wrong remained in the final read. An independent check with its own#includereader then covered all 13,289 added edges: 26 fail it, and all 26 were read and are right: opaque handles a.cfile defines, jemalloc tests that reach their types through a generated header, and one#include <ae.h>the checker misread.Impact (
impact <name> -j, default depth):pause_eventrobjredisObjectdictclientStatusFileMetaDataIteratorColumnFamilyDataFileMetaDatarocksdb's
ColumnFamilyDatastays under 1.6.2, and not through members: every struct 1.6.2 linked to it is linked here. The gap is callers of those structs' methods, likeArenaWrappedDBIter's, with 6,822callsedges on 1.6.2 and 74 here, which main has re-resolved since. rocksdb'sStatuscan't be compared (18,656 on 1.6.2, 10,334 here): impact now stops at its safety budget of 10,000 nodes, and 1.6.2 had none.Kernel:
scripts/kernel-parity.mjs: 0 diffs on rocksdb (1,196 of 1,413 files compared, 217 deferred), redis, leveldb, fmt and jq. WithCODEGRAPH_KERNEL_CCPP_ERROR_EXTRACT=1, the files that differ are the same on main and here (redis 18, leveldb 2). Full-indexdump-graphwasm vs kernel is byte-identical on redis, leveldb, fmt and jq. A sync after editing headers equals a fresh index on leveldb and jq.Explore: 22 queries on the five repos, flows and type questions, on main and here: every Flow section unchanged, and the same 68 of 72 named symbols shown on both. Budgets are untouched and output stays under the cap. 5 queries render a different file set: rocksdb's flush query swaps a Java file for
db/flush_job.handlistener.h, redis's command path movessds.cto "Not shown". Type questions gain the holders:pause_eventnow showsredisServer.Index cost, median of 3, interleaved, kernel path:
Unresolved rows grow 3.6% on rocksdb, nearly all gtest's
T1…T50, which name no type.Suite:
CODEGRAPH_KERNEL_EXPECT=1 npx vitest run --maxWorkers=4on Vitest 4, on this commit: 846 files passed, 6 skipped; 10,503 tests passed, 2 expected failures, 87 skipped, 0 failed.🤖 Generated with Claude Code