Skip to content

fix(c,cpp): a source implements the header beside it that it reaches through other headers - #2693

Merged
colbymchenry merged 2 commits into
mainfrom
claude/cpp-internal-stem-headers
Oct 20, 2026
Merged

colbymchenry merged 2 commits into
mainfrom
claude/cpp-internal-stem-headers

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

What

Follow-up to #2684, closing the gap its notes left open.

#2684 counts a source file as implementing a header when it includes a header named like it, preferring the one beside it in its own folder. upb's reflection sources don't include that header directly:

  • upb/reflection/field_def.c includes only upb/reflection/internal/field_def.h;
  • that header includes upb/reflection/def.h, which includes the public upb/reflection/field_def.h that field_def.c implements.

The rule therefore never read field_def.c as that header's implementation. Calls from protobuf's Python extension to upb_FieldDef_*, upb_MessageDef_*, upb_FileDef_* and the rest of upb's reflection API still went to the copy the PHP extension bundles in php-upb.c.

Now, when a source includes no header named like it in its own folder, a header there that it reaches through other headers counts.

Which sources it changes

Across the 9 C/C++ repos, the header read as implemented changes for 21 sources:

  • protobuf, 12: the eight upb/reflection/*_def.c, ruby/ext/google/protobuf_c/defs.c, and three generated *.upb.c in stage0/ folders;
  • redis, 7: src/rdb.c, object.c, latency.c, config.c, keymeta.c, sparkline.c, sparsearray.c, which reach their headers through server.h;
  • rocksdb, 1: db_stress_tool/db_stress_test_base.cc;
  • breakpad, 1: src/client/minidump_file_writer.cc.

Twenty of them define what the header beside them declares. The exception is redis's config.h, the build's configuration header; config.c's functions are declared in server.h. That is the same-stem guess #2684 already makes for any source that includes such a header directly, and it changes no edge here.

A/B (main 6c202bc vs this branch)

Full indexes with the same kernel. A site is (kind, source node, line, col, refName). The A/B on main 3308cff, before the rebase, gave the same numbers.

Repo Added Retargeted Removed Confidence only
protocolbuffers/protobuf 100 431 0 271
redis/redis 0 0 0 139
rocksdb, leveldb, fmt, breakpad, glog, jq, llm.c byte-identical
tokio, excalidraw, okhttp, typeorm (controls) byte-identical

Triage. I grouped every changed site by (caller's folder, before target's folder → after target's folder). All of them are upb reflection calls reaching upb's own reflection sources:

  • 343 retargeted from python/: php/ext/google/protobuf/php-upb.c → upb/reflection/*.c. This is the whole gap fix(c,cpp): a name in C or C++ code outside tests links to what the file's own includes declare #2684 left.
  • 88 retargeted from lua/, the same way.
  • 99 added from upb/, mostly the C++ wrappers in upb/reflection/def.hpp (upb_FieldDef_Name(ptr_) and the rest) and upb's tests, plus 1 from benchmarks/. Main left them unlinked.
  • Confidence only: 271 protobuf sites within upb/reflection/ now see a single candidate and go to 0.9. In redis, 139 calls of createStringObject / createArrayObject keep object.c as their target, now at 0.9.

No site changed anywhere else, and nothing was removed.

Cost

I measured the new code directly. A copy of the branch build timed cppIncludedHeadersBeside in every resolution thread during a full index:

  • protobuf: 135 calls, 37 ms in total, of an 85 s index;
  • rocksdb: 352 calls, 36 ms, of 43 s;
  • redis: 122 calls, 8 ms, of 11 s.

The include closures it walks are memoized, and the rest of resolution reuses them, so these totals are an upper bound.

Interleaved full indexes were too noisy on a busy machine to separate that from noise. For example, main's own rocksdb runs spread 52.7–75.4 s.

The check runs only for a source that includes no same-stem header beside it, once per source. It looks at the includes spelled with the source's own stem, and walks only the includers of the headers it finds in the source's folder.

Tests

__tests__/cpp-translation-unit.test.ts gains a case shaped like upb's reflection layout: python/convert.c → def.h → field_def.h, with field_def.c → internal/field_def.h → def.h, and a PHP-bundled copy of the function. On main it fails (the call reaches php-upb.c); on this branch it passes, along with the rest of that file and #2675's cpp-test-translation-unit.test.ts.

Full suite on this change on main 6c202bc, with the kernel built, CODEGRAPH_KERNEL_EXPECT=1 and --maxWorkers=4 --minWorkers=1: 7,551 passed, 0 failed, 80 skipped. A first run on a busy machine had 23 daemon tests time out waiting for spawned daemons (mcp-daemon, daemon-older-version, daemon-pid-reuse). Those three files passed on their own (50 passed, 1 skipped), and the clean rerun above passed everything.

Notes

🤖 Generated with Claude Code

colbymchenry and others added 2 commits October 19, 2026 19:17
…through other headers

upb's reflection sources include only their internal header:
upb/reflection/field_def.c includes upb/reflection/internal/field_def.h,
which reaches the upb/reflection/field_def.h it implements through def.h.
So the translation-unit rule of PR 2684 never read field_def.c as that
header's implementation, and 343 calls from protobuf's Python extension,
plus 88 from its Lua binding, still went to the copy of upb that the PHP
extension bundles in php-upb.c.

When a source includes no header named like it in its own folder, one
there that it includes through other headers counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant