https://github.com/dmaclach created https://github.com/llvm/llvm-project/pull/216192
System and SDK headers (such as Darwin/BSD C libraries) often declare functions, types, and macros inside internal files prefixed with leading underscores (e.g., _stdlib.h, _stdio.h, _time.h), which are then included by public headers like stdlib.h. When include-cleaner resolves physical declarations for platform-specific or non-standard symbols defined in these files (such as arc4random_uniform), it suggests the internal underscored header (e.g. <_stdlib.h>) instead of the public header. In headersForSymbol, check physical system header paths whose filenames begin with leading underscores. When a corresponding public header without the leading underscores exists in that directory, redirect the candidate provider to the public header entry with Hints::PublicHeader and Hints::PreferredHeader. >From bbee74bb057f45f137d1c429dc4fa09e22f56439 Mon Sep 17 00:00:00 2001 From: Dave MacLachlan <[email protected]> Date: Thu, 13 Aug 2026 14:56:00 -0700 Subject: [PATCH] [include-cleaner] Map internal underscore-prefixed system headers to public headers. System and SDK headers (such as Darwin/BSD C libraries) often declare functions, types, and macros inside internal files prefixed with leading underscores (e.g., _stdlib.h, _stdio.h, _time.h), which are then included by public headers like stdlib.h. When include-cleaner resolves physical declarations for platform-specific or non-standard symbols defined in these files (such as arc4random_uniform), it suggests the internal underscored header (e.g. <_stdlib.h>) instead of the public header. In headersForSymbol, check physical system header paths whose filenames begin with leading underscores. When a corresponding public header without the leading underscores exists in that directory, redirect the candidate provider to the public header entry with Hints::PublicHeader and Hints::PreferredHeader. --- .../include-cleaner/lib/FindHeaders.cpp | 34 +++++++++++++ .../unittests/FindHeadersTest.cpp | 48 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp b/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp index b96d9a70728c2..8a6241b94656c 100644 --- a/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp +++ b/clang-tools-extra/include-cleaner/lib/FindHeaders.cpp @@ -18,6 +18,7 @@ #include "clang/Basic/FileEntry.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" +#include "clang/Lex/HeaderSearch.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/Inclusions/StandardLibrary.h" #include "llvm/ADT/ArrayRef.h" @@ -26,6 +27,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Path.h" #include <optional> #include <queue> #include <set> @@ -253,6 +255,38 @@ llvm::SmallVector<Header> headersForSymbol(const Symbol &S, for (auto &Loc : locateSymbol(S, PP.getLangOpts())) Headers.append(applyHints(findHeaders(Loc, SM, PI), Loc.Hint)); } + + llvm::SmallVector<Hinted<Header>> RedirectedHeaders; + for (auto &H : Headers) { + if (H.kind() == Header::Physical) { + FileEntryRef FE = H.physical(); + bool IsSystem = SrcMgr::isSystem(static_cast<SrcMgr::CharacteristicKind>( + PP.getHeaderSearchInfo().getFileInfo(FE).DirInfo)); + llvm::StringRef Path = H.resolvedPath(); + llvm::StringRef FileName = llvm::sys::path::filename(Path); + // Check if header is a system header and starts with '_' or '__' + if (IsSystem && FileName.starts_with("_")) { + // Some private headers have "__" + llvm::StringRef Stripped = FileName.ltrim('_'); + if (!Stripped.empty()) { + llvm::SmallString<128> PublicPath = + llvm::sys::path::parent_path(Path); + llvm::sys::path::append(PublicPath, Stripped); + if (auto PublicFE = + SM.getFileManager().getOptionalFileRef(PublicPath)) { + // We found a public header, so we'll redirect to it. + RedirectedHeaders.push_back( + Hinted<Header>(Header(*PublicFE), H.Hint | Hints::PublicHeader | + Hints::PreferredHeader)); + continue; + } + } + } + } + RedirectedHeaders.push_back(H); + } + Headers = std::move(RedirectedHeaders); + // If two Headers probably refer to the same file (e.g. Verbatim(foo.h) and // Physical(/path/to/foo.h), we won't deduplicate them or merge their hints llvm::stable_sort( diff --git a/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp b/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp index 0ac243937e6e4..577f0664e46e6 100644 --- a/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp +++ b/clang-tools-extra/include-cleaner/unittests/FindHeadersTest.cpp @@ -678,5 +678,53 @@ TEST_F(HeadersForSymbolTest, ExporterNoNameMatch) { physicalHeader("exporter/foo.h"))); } +TEST_F(HeadersForSymbolTest, InternalSystemHeaderUnderscoreRedirection) { + Inputs.Code = R"cpp( + #include <_stdlib.h> + void foo() { arc4random_uniform(42); } + )cpp"; + Inputs.ExtraFiles["/usr/include/_stdlib.h"] = guard(R"cpp( + void arc4random_uniform(int); + )cpp"); + Inputs.ExtraFiles["/usr/include/stdlib.h"] = guard(R"cpp( + #include <_stdlib.h> + )cpp"); + Inputs.ExtraArgs.push_back("-isystem/usr/include"); + buildAST(); + EXPECT_THAT(headersFor("arc4random_uniform"), + ElementsAre(physicalHeader("/usr/include/stdlib.h"))); +} + +TEST_F(HeadersForSymbolTest, InternalSystemHeaderNoPublicHeader) { + Inputs.Code = R"cpp( + #include <__stddef_null.h> + void foo() { my_null(); } + )cpp"; + Inputs.ExtraFiles["/usr/include/__stddef_null.h"] = guard(R"cpp( + void my_null(); + )cpp"); + Inputs.ExtraArgs.push_back("-isystem/usr/include"); + buildAST(); + EXPECT_THAT(headersFor("my_null"), + ElementsAre(physicalHeader("/usr/include/__stddef_null.h"))); +} + +TEST_F(HeadersForSymbolTest, InternalUserHeaderUnderscoreNotRedirected) { + Inputs.Code = R"cpp( + #include "user_proj/_myheader.h" + void foo() { my_func(); } + )cpp"; + Inputs.ExtraFiles["user_proj/_myheader.h"] = guard(R"cpp( + void my_func(); + )cpp"); + Inputs.ExtraFiles["user_proj/myheader.h"] = guard(R"cpp( + #include "user_proj/_myheader.h" + )cpp"); + Inputs.ExtraArgs.push_back("-Iuser_proj"); + buildAST(); + EXPECT_THAT(headersFor("my_func"), + ElementsAre(physicalHeader("user_proj/_myheader.h"))); +} + } // namespace } // namespace clang::include_cleaner _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
