Skip to content

fix(cpp): a qualified receiver type never reaches a class that only shares its last name, and rocksdb::DB is RocksDB's own - #2567

Merged
colbymchenry merged 1 commit into
mainfrom
claude/wonderful-roentgen-cbf49b
Oct 15, 2026
Merged

colbymchenry merged 1 commit into
mainfrom
claude/wonderful-roentgen-cbf49b

Conversation

@colbymchenry

Copy link
Copy Markdown
Owner

What

A C++ receiver declared with a namespace-qualified type should call a member of a class that qualified name can be, never one of a class that only shares its last name. Most of this landed on main while this branch waited:

I re-checked on main eec9288, and the original reproduction passes there. This PR fixes the three gaps left over:

  1. A namespace a macro stands for (a regression from fix(cpp): a call on an auto variable or a chained call reaches the class C++ finds, not a namesake #2532). RocksDB declares its classes in namespace ROCKSDB_NAMESPACE under #define ROCKSDB_NAMESPACE rocksdb, so the index names them ROCKSDB_NAMESPACE::DB. cppProjectScopeNames only held segments of indexed type names, so rocksdb::DB* db; db->Put(…) was judged a call on an outside type and got no edge. The same happened to ROCKSDB_NAMESPACE::Cache in a tree that declares namespace rocksdb itself. The set now adds the name a single-name macro stands for, in both directions, through cppNameMacros, the #define X y map cppNamespaceMacros already builds.
  2. A lowercase outside type beside a project namesake. isCppLibraryType (fix(cpp): a receiver declared as a std or other outside type with a lowercase name calls that type's own member #2413) took a non-std lowercase type as the library's only when no project type had its name at all, so absl::flat_hash_set<int> seen; seen.insert(…) reached a project's own util::flat_hash_set::insert. It is now also the library's when every project type of that name is a namesake (cppFindsOnlyNamesakes).
  3. A capitalized type whose last name finds only namesakes, on the paths fix(cpp): a call on a declared pointer, value or reference reaches the class its type names, not a namesake #2493's call-site rules leave to the last-name match. One is a declaration read outside the calling function. protobuf's _status.ok() in its RETURN_IF_ERROR macro body reached upb::Status::ok. auto bytes = ::hpb::Serialize(…); bytes.value(), typed from hpb.h's absl::StatusOr<absl::string_view>, reached hpb::StatusOr::value. The other is a namespace the project uses. Its generated DescriptorProto::ExtensionRange, which the index doesn't hold (descriptor.pb.h is over the size cap), reached Descriptor::ExtensionRange::options. Such a call now gets no edge, as isUndeclaredTypeName rules where no class shares the name. A -> on a value still goes to the name strategies, as isCppArrowThroughOutsideType does, and a template parameter's member is left alone.

cppFindsOnlyNamesakes asks whether every C/C++ type of the last name, and every owner of a method of the called name, has a qualified name that ends unlike the written one, neither being the other's tail. Method owners count because the index can hold a class's methods without the class, as with generated protobuf messages. C++ lookup only adds scopes in front of a written name, and the index drops some (a namespace a macro opens, one lost to a parse error), hence the tail rule both ways. Namespace aliases are expanded, and macro-named namespaces are read both ways. A name a project type qualifies (ns::Derived::Node), an unqualified name and an unbalanced <> are never judged, and neither is a name nothing in the project shares, which main's own rules already handle.

Validation

Tests: __tests__/cpp-outside-type-receiver.test.ts, 9 tests.

  • Red on main: 4 fail, the four bug cases: rocksdb::DB::Put and ROCKSDB_NAMESPACE::Cache getting no edge, absl::flat_hash_set reaching util::flat_hash_set::insert, and DescriptorProto::ExtensionRange reaching Descriptor::ExtensionRange::options. The 5 guard tests pass on main too.
  • Green: all 41 C/C++ test files pass, 392 tests. Full suite: 7,117 passed; the 2 failures were load-bound (daemon-pid-reuse EBUSY on teardown, function-ref Caller/impact graph misses methods passed as first-class references (callbacks), e.g. executor.submit(obj.method, …) #1820 hitting its 60 s in-file timeout), and both files pass on their own (36/36).

Mutations: each guard switched off fails exactly its own test.

Mutation Test that fails
macro mapping, forward / reverse the two rocksdb tests
lowercase rule absl::flat_hash_set reproduction
gate off DescriptorProto::ExtensionRange reproduction
owners.length > 0 outside type with no namesake stays unguessed (grpc::ClientReader)
method owners a class whose methods alone are indexed (DescriptorProto::Clear beside an upb::DescriptorProto)
template-member exclusion typename PolicyT::Payload
tail rule / alias expansion fmt's fmt::detail::buffer / u::flat_hash_set through namespace u = util;
qualifier guard ns::Derived::Node inherited from Base
-> on a value → name strategies folly::Optional<Widget> holder

The one guard-less mutation, the gate extended to lowercase names, changes nothing: those stay with isCppLibraryType.

Site-keyed A/B (main eec9288 vs this branch, kit %TEMP%\cgstd, diff-sites.mjs):

repo removed added retargeted
google/leveldb 0 0 0 (byte-identical)
fmtlib/fmt 0 0 0 (byte-identical)
redis (C control) 0 0 0 (byte-identical)
facebook/rocksdb 0 0 0 (byte-identical)
protocolbuffers/protobuf 7 0 0

All 7 protobuf removals were wrong edges:

  • hpb_generator/tests/test_generated.cc:488, :495: bytes.value() on auto bytes = ::hpb::Serialize(…), an absl::StatusOr<absl::string_view>, reached hpb::StatusOr::value.
  • src/google/protobuf/compiler/importer_unittest.cc:567, 569, 579, 581: range.options() on const DescriptorProto::ExtensionRange& range, the generated message, reached the descriptor class's Descriptor::ExtensionRange::options.
  • src/google/protobuf/stubs/status_macros.h:33: _status.ok() on const absl::Status _status in RETURN_IF_ERROR reached upb::Status::ok.

A logging probe on main listed every qualified-type site main still sends down the last-name path. Besides these 7, the only owner-mismatch sites left are right ones:

  • inherited methods, like DefaultFieldComparator → SimpleFieldComparator::set_treat_nan_as_equal;
  • typename PolicyT::Payload template members, kept on purpose;
  • hpb/arena.h's backend::Arena → hpb::Arena (3 sites), an alias question for the found-class path rather than an outside type.

The rocksdb and lowercase gaps don't occur in these five repos: rocksdb itself writes ROCKSDB_NAMESPACE:: (8,602 uses against a handful of rocksdb:: calls). The fixtures pin them.

Overlap / convergence

CHANGELOG: #2413's and #2532's [Unreleased] bullets were extended rather than given new ones, since neither is released yet (latest release 1.6.2), as #2530 did.

🤖 Generated with Claude Code

…hares its last name, and rocksdb::DB is RocksDB's own

A C++ receiver declared with a namespace-qualified type is from outside
the project only when no class of the project can be what that name names.
Three cases read that wrong on main:

- A namespace a macro stands for. RocksDB declares its classes in
  `namespace ROCKSDB_NAMESPACE` under `#define ROCKSDB_NAMESPACE rocksdb`,
  so the index names them `ROCKSDB_NAMESPACE::DB`, and #2532's outside rule
  (cppProjectScopeNames) never saw `rocksdb`: `rocksdb::DB* db; db->Put(…)`
  got no edge. cppProjectScopeNames now adds the name a single-name macro
  stands for, in both directions (cppNameMacros, the map
  cppNamespaceMacros already builds).
- A lowercase outside type beside a project namesake. isCppLibraryType
  (#2413) took a non-std lowercase type as the library's only when no
  project type had its name at all, so `absl::flat_hash_set<int> seen;
  seen.insert(…)` reached a project's own `util::flat_hash_set::insert`.
  It is now also the library's when every project type of that name is a
  namesake (cppFindsOnlyNamesakes).
- A capitalized type whose last name finds only namesakes, on the paths
  #2493's call-site rules leave to the last-name match: a declaration read
  outside the calling function (protobuf's `absl::Status _status` in its
  RETURN_IF_ERROR macro body reached `upb::Status::ok`; `auto bytes =
  ::hpb::Serialize(…); bytes.value()` reached `hpb::StatusOr::value`), and
  a namespace the project uses (its generated
  `DescriptorProto::ExtensionRange`, which the index doesn't hold, reached
  `Descriptor::ExtensionRange::options`). That call now gets no edge, as
  isUndeclaredTypeName rules where no class shares the name; `->` on a
  value still goes to the name strategies, and a template parameter's
  member is left alone.

cppFindsOnlyNamesakes: every type of the last name, and every owner of a
method of the called name (the index can hold a class's methods without
the class), has a qualified name that ends unlike the written one, neither
the other's tail; namespace aliases are expanded and macro-named
namespaces read both ways. A name a project type qualifies, an unqualified
name and an unbalanced `<>` are never judged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colbymchenry
colbymchenry merged commit 00d7430 into main Oct 15, 2026
@colbymchenry
colbymchenry deleted the claude/wonderful-roentgen-cbf49b branch October 15, 2026 03:29
colbymchenry added a commit that referenced this pull request Oct 15, 2026
…there, and a far lowercase outside type reaches no namesake (#2569)

The two follow-ups #2567 and #2568 left open.

A receiver declared with a leading `::` lost it: the declarator regex
started a type at a letter, so `::upb::Arena& a` inside `namespace hpb`
was looked up from `hpb` and found a namespace-local `hpb::upb::Arena`.
The declared type now keeps a leading `::` where one can stand (not after
a name, a template's arguments or a `)`, so `SkipList<Key,
Comparator>::Iterator iter` is not read as `::Iterator`), and
resolveCppAliasedType no longer takes a function-local `using Foo = Bar;`
for `::Foo`.

A lowercase outside type declared outside the calling function still used
the last-name typed match: #2413's library rule skips such declarations,
and #2567's namesake gate took capitalized types only. So a far
`absl::flat_hash_set<int> seen` linked `seen.insert(v)` to the project's
`util::flat_hash_set::insert`, and a far `std::string text` linked
`text.clear()` to a global project `string` class. The gate now covers
lowercase types, which are decided as when no class shares their name,
and cppFindsOnlyNamesakes reads a `std::` name exactly.

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