Repository navigation
fix(cpp): a call or type name never links to what only another source file can see - #2538
Merged
Merged
Conversation
… file can see
Name matching linked C and C++ names to declarations no other
translation unit can name: a class or alias a `.cc` file declares for
itself, its members, and what a source file's unnamed namespace
declares. In rocksdb, other test files' bare `Put(…)`, `Flush()` and
`dbfull()` reached column_family_test.cc's `ColumnFamilyTestBase`
thousands of times, and inlineskiplist.h's `Node*` receivers reached the
`Node` hash_linklist_rep.cc declares in `namespace { … }`.
- isVisibleAcrossFiles rejects such a candidate for a C or C++ ref
(isCppTranslationUnitLocal), as it does a `static` C function: a type
only source files declare; a member written inside such a class; an
out-of-line member of a type the member's own file sees only source
files declare (a scope its file sees no type of may be a namespace:
fmt's posix-mock-test.cc defines posix-mock.h's `test::close` beside
ostream-test.cc's own `struct test`); and, for the rest, what an
unnamed namespace of a source file holds. An alias of the scope keeps
the member, since the class may be declared anywhere.
- A source file's declarations are still seen by the file itself, by a
file that includes it, and by a header it includes below them
(rocksdb's version_set.cc includes version_set_sync_and_async.h after
its `FilePicker` classes).
- The unnamed-namespace spans come from a scanner that skips comments,
preprocessor lines (continued past a CRLF too) and string, character
and raw string literals.
- The typed lookups (cppMethodOf, resolveMethodOnType) take the
candidates the call's file can see first (cppVisibleFirst): rocksdb's
db_stress_shared_state.h and two benchmark `.cc` files each declare a
`SharedState`, and a call in db_stress_common.cc is the header's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
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
Name matching linked C and C++ names to declarations no other translation unit can name. That covers a class, struct or alias that a
.ccfile declares for itself, the members of such a class, and anything inside a source file's unnamednamespace { … }. In facebook/rocksdb, other test files' barePut(…),Flush(),Get(…)anddbfull()calls reached the methods ofColumnFamilyTestBase, the fixturecolumn_family_test.ccdeclares, 7,989 times at 0.4.inlineskiplist.h'sNode*receivers reached theNodethathash_linklist_rep.ccdeclares in an unnamed namespace.What changed (name-matcher.ts)
isCppTranslationUnitLocal.isVisibleAcrossFilesalready rejects astaticC function for a ref in another file. It now also rejects, for a C or C++ ref, a candidate that only another translation unit can name:cppEnclosingClassDecl, the innermost class around the member in its file);posix-mock-test.ccdefinesposix-mock.h'stest::close, andostream-test.ccdeclares astruct testof its own. An alias of the scope keeps the member, since the class may be declared anywhere.cppUnnamedNamespaceSpans).#includes it (cppIncluders, transitive), and a header it includes below them (cppIncludesAfter). rocksdb'sversion_set.ccincludesversion_set_sync_and_async.hafter itsFilePickerclasses.cppUnnamedNamespaceSpansfinds the outermost unnamed namespaces with a scanner. The scanner skips comments, preprocessor lines (with\continuations, before a CRLF too) and string, character and raw string literals.cppVisibleFirst. The typed lookups (cppMethodOf,resolveMethodOnType) take the candidates the call's file can see first. rocksdb'sdb_stress_shared_state.hand two benchmark.ccfiles each declare aSharedState, and a call indb_stress_common.ccis the header's.staticrule (fix(resolution): fuzzy matching skips a nested function the reference cannot reach #1709 / C: a call resolves by exact name onto a static function in another translation unit (4,448 edges on betaflight) #1730), the pipeline's pick is rejected, with no fallback to the next candidate.Validation
Site-keyed edge diffs (
diff-sites.mjs) of maindf3ce972against this branch. Both arms were built from the same tree, kernel included. All repos are checked out with CRLF line endings.Nodes and files are identical between the arms.
Removed, by the rule that rejects the old target. Every target group was read (
tri-anon.mjsgroups by target and prints its declaration and a call site). All were wrong.rocksdb. Other test files' bare calls hit other test files' fixtures:
ColumnFamilyTestBase(dbfull,Flush,Get,Reopen,NumTableFilesAtLevel…),CompactionJobStatsTest,CuckooTableDBTest::CurrentOptionsand others. In those files the right method is usuallyDBTestBase's (fix(cpp): a bare call in a gtest TEST_F or TEST_P body reaches the fixture's method #2537 links those). Other wrong hits:H::Put/H::Deleteinc.cc(3,041);DummyOldStats(options.statistics->getTickerCount(…));PosixEnvandLevelCompactionBuildermethods from outside their.cc;Timestamp,IterStatusandKey1;log::LogTest::StringSourcefornew test::StringSource(…);HashSkipListRep::IteratorforMemtableSkipList::Iterator.The removed typed calls are
inlineskiplist.h'sNodereachinghash_linklist_rep.cc's unnamed-namespaceNode(27, at 0.9). Its ownInlineSkipList<C>::Nodeis indexed<anonymous>, an extraction gap followed up separately. The other 13 (at 0.8) are aBitFieldsstate typed as another test'sConcurrentTest::State.protobuf:
compiler::IsEmptyandjson_internal::IsEmpty(unnamed-namespace helpers) for gmock'sIsEmpty()matcher;lite_unittest.cc's misparsedabsl::Cord"method" forabsl::Cord("foo");arena.cc's<anonymous>nested classes for.size(),.insert(…)and.capacity()on library types;FlatAllocation::data,FileDescriptorTables::FindFieldByLowercaseNameandLeftoverBuffer::appendfrom other files;conformance::Harness::RunTestfor a mock'sEXPECT_CALL(delegate, RunTest(…));MapReflectionFriend::MakeMapFieldDirtyfor the free functionmap_container.hdeclares.fmt:
#include <string>andstd::stringreachingformat-test.cc'sadl_test::string;fmt::string_view/fmt::wstring_viewmethods forfmt::string_view(…)constructions;fmt::formatter<…>specializations inargs-test.cc/scan-test.ccforformat(…)ands.scan(…);os-test.cc'susing wstring_view;ranges-test.cc'snon_const_only_range::begin/endforscan-test.cc'srange.begin().leveldb:
Env*that reachedmemenv.cc'sInMemoryEnv. They reachEnvwith fix(cpp): a declaration with a qualifier or annotation around its name gives its type to calls on it #2536'sEnv* const env_.repthat reacheddb_iter.cc'sDBIter;c.cc'sleveldb_comparator_t::Compareforucmp->Compare(…);cache.cc'sShardedLRUCache::Valueforcrc32c::Value(…).Retargeted (rocksdb, 36), all wrong → right. Calls in
db_stress_tool/on aSharedState*(shared->GetMutex(),GetCondVar(), …) now reachdb_stress_shared_state.h's class instead ofcache_bench_tool.cc's ownSharedState.A rule I dropped after the A/B. The first version checked an out-of-line member's scope against every type of that name. fmt's 62
test::close/fdopen/fopenremovals came only from an unrelatedstruct test {}inostream-test.cc. Those edges were wrong anyway, but the reason wasn't, so only the declarations the member's own file sees count now. Those 62 sites are unchanged from main.Tests
__tests__/cpp-translation-unit-visibility.test.tshas 14 cases checking targets and confidence. They cover:.cc;SharedStateover a.ccnamesake, for a parameter and for a namespace-scope variable;.cc-only class;.cc-only class used as a type;#define, a CRLF-continued#define, a string and a raw string;.ccit tests;structof its name;.ccdeclares.Mutation check. 19 guards were each disabled alone (
mutate-anon.mjs), and each one fails at least one test:cppVisibleFirstin both lookups;Full suite: 6,977 passed, 82 skipped. 8 tests in 5 files failed, all in unrelated areas: git index currency, sync, the MCP daemon, the unindexed-root policy, and a 60-second
function-reftimeout. The run overlapped two background repo indexings, and those 5 files pass when rerun alone (114 passed, 1 skipped).🤖 Generated with Claude Code