[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