[clang-tools-extra] [include-cleaner] Map internal underscore-prefixed system headers to public headers. (PR #216192)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 13 14:59:01 PDT 2026
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 <dmaclach at gmail.com>
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
More information about the cfe-commits
mailing list