[clang] [clang] Support header shadowing diagnostics in Clang header search (PR #162491)
Jinjie Huang via cfe-commits
cfe-commits at lists.llvm.org
Thu Nov 6 04:03:01 PST 2025
https://github.com/Jinjie-Huang updated https://github.com/llvm/llvm-project/pull/162491
>From 4199d38c213ad859510664ed6510dbe466b35b91 Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Thu, 6 Nov 2025 19:38:53 +0800
Subject: [PATCH 1/2] header shadowing diagnostics
---
clang/docs/ReleaseNotes.rst | 4 ++
clang/include/clang/Basic/DiagnosticGroups.td | 2 +
.../include/clang/Basic/DiagnosticLexKinds.td | 4 ++
clang/include/clang/Lex/HeaderSearch.h | 6 ++
clang/lib/Lex/HeaderSearch.cpp | 61 +++++++++++++++++++
clang/test/Preprocessor/header-shadowing.c | 56 +++++++++++++++++
6 files changed, 133 insertions(+)
create mode 100644 clang/test/Preprocessor/header-shadowing.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ae21c69b2d3c5..c9b44b3555a38 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -404,6 +404,10 @@ Improvements to Clang's diagnostics
- Clang now emits a diagnostic in case `vector_size` or `ext_vector_type`
attributes are used with a negative size (#GH165463).
+- A new warning, -Wshadow-header, has been added to detect when a header file
+ included via quotes (#include "...") is found in multiple distinct search
+ directories.
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 1e0321de3f4b6..279c45bb938c1 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -790,6 +790,8 @@ def ShadowFieldInConstructor : DiagGroup<"shadow-field-in-constructor",
def ShadowIvar : DiagGroup<"shadow-ivar">;
def ShadowUncapturedLocal : DiagGroup<"shadow-uncaptured-local">;
+def ShadowHeader : DiagGroup<"shadow-header">;
+
// -Wshadow-all is a catch-all for all shadowing. -Wshadow is just the
// shadowing that we think is unsafe.
def Shadow : DiagGroup<"shadow", [ShadowFieldInConstructorModified,
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 417187222e448..7be1d3b4f0660 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -959,6 +959,10 @@ def warn_quoted_include_in_framework_header : Warning<
def warn_framework_include_private_from_public : Warning<
"public framework header includes private framework header '%0'"
>, InGroup<FrameworkIncludePrivateFromPublic>;
+def warn_header_shadowing : Warning<
+ "multiple candidates for header '%0' found; "
+ "directory '%1' chosen, ignoring '%2' and others">,
+ InGroup<ShadowHeader>, DefaultIgnore;
def warn_deprecated_module_dot_map : Warning<
"'%0' as a module map name is deprecated, rename it to %select{module.modulemap|module.private.modulemap}1%select{| in the 'Modules' directory of the framework}2">,
InGroup<DeprecatedModuleDotMap>;
diff --git a/clang/include/clang/Lex/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h
index 850aea41c4c3b..a996366337c31 100644
--- a/clang/include/clang/Lex/HeaderSearch.h
+++ b/clang/include/clang/Lex/HeaderSearch.h
@@ -465,6 +465,12 @@ class HeaderSearch {
ExternalSource = ES;
}
+ void DiagnoseHeaderShadowing(
+ StringRef Filename, OptionalFileEntryRef FE, bool &DiagnosedShadowing,
+ SourceLocation IncludeLoc, ConstSearchDirIterator FromDir,
+ ArrayRef<std::pair<OptionalFileEntryRef, DirectoryEntryRef>> Includers,
+ bool isAngled, int IncluderLoopIndex, ConstSearchDirIterator MainLoopIt);
+
/// Set the target information for the header search, if not
/// already known.
void setTarget(const TargetInfo &Target);
diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp
index f05c28fd7a123..49d7d736c8ed3 100644
--- a/clang/lib/Lex/HeaderSearch.cpp
+++ b/clang/lib/Lex/HeaderSearch.cpp
@@ -881,6 +881,60 @@ diagnoseFrameworkInclude(DiagnosticsEngine &Diags, SourceLocation IncludeLoc,
<< IncludeFilename;
}
+void HeaderSearch::DiagnoseHeaderShadowing(
+ StringRef Filename, OptionalFileEntryRef FE, bool &DiagnosedShadowing,
+ SourceLocation IncludeLoc, ConstSearchDirIterator FromDir,
+ ArrayRef<std::pair<OptionalFileEntryRef, DirectoryEntryRef>> Includers,
+ bool isAngled, int IncluderLoopIndex, ConstSearchDirIterator MainLoopIt) {
+
+ if (Diags.isIgnored(diag::warn_header_shadowing, IncludeLoc) || isAngled ||
+ DiagnosedShadowing)
+ return;
+
+ DiagnosedShadowing = true;
+
+ // Indicates that file is first found in the includer's directory
+ if (!MainLoopIt) {
+ for (size_t i = IncluderLoopIndex + 1; i < Includers.size(); ++i) {
+ const auto &IncluderAndDir = Includers[i];
+ SmallString<1024> TmpDir = IncluderAndDir.second.getName();
+ llvm::sys::path::append(TmpDir, Filename);
+ if (auto File = getFileMgr().getFileRef(TmpDir, false, false)) {
+ if (&File->getFileEntry() == *FE)
+ continue;
+ Diags.Report(IncludeLoc, diag::warn_header_shadowing)
+ << Filename << (*FE).getDir().getName()
+ << IncluderAndDir.second.getName();
+ return;
+ } else {
+ llvm::errorToErrorCode(File.takeError());
+ }
+ }
+ }
+
+ // Continue searching in the regular search paths
+ ConstSearchDirIterator It =
+ isAngled ? angled_dir_begin() : search_dir_begin();
+ if (MainLoopIt) {
+ It = std::next(MainLoopIt);
+ } else if (FromDir) {
+ It = FromDir;
+ }
+ for (; It != search_dir_end(); ++It) {
+ SmallString<1024> TmpPath = It->getName();
+ llvm::sys::path::append(TmpPath, Filename);
+ if (auto File = getFileMgr().getFileRef(TmpPath, false, false)) {
+ if (&File->getFileEntry() == *FE)
+ continue;
+ Diags.Report(IncludeLoc, diag::warn_header_shadowing)
+ << Filename << (*FE).getDir().getName() << It->getName();
+ return;
+ } else {
+ llvm::errorToErrorCode(File.takeError());
+ }
+ }
+}
+
/// LookupFile - Given a "foo" or \<foo> reference, look up the indicated file,
/// return null on failure. isAngled indicates whether the file reference is
/// for system \#include's or not (i.e. using <> instead of ""). Includers, if
@@ -930,6 +984,7 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
// This is the header that MSVC's header search would have found.
ModuleMap::KnownHeader MSSuggestedModule;
OptionalFileEntryRef MSFE;
+ bool DiagnosedShadowing = false;
// Check to see if the file is in the #includer's directory. This cannot be
// based on CurDir, because each includer could be a #include of a
@@ -963,6 +1018,9 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
if (OptionalFileEntryRef FE = getFileAndSuggestModule(
TmpDir, IncludeLoc, IncluderAndDir.second, IncluderIsSystemHeader,
RequestingModule, SuggestedModule)) {
+ DiagnoseHeaderShadowing(Filename, FE, DiagnosedShadowing, IncludeLoc,
+ FromDir, Includers, isAngled,
+ &IncluderAndDir - Includers.begin(), nullptr);
if (!Includer) {
assert(First && "only first includer can have no file");
return FE;
@@ -1097,6 +1155,9 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
if (!File)
continue;
+ DiagnoseHeaderShadowing(Filename, File, DiagnosedShadowing, IncludeLoc,
+ FromDir, Includers, isAngled, -1, It);
+
CurDir = It;
IncludeNames[*File] = Filename;
diff --git a/clang/test/Preprocessor/header-shadowing.c b/clang/test/Preprocessor/header-shadowing.c
new file mode 100644
index 0000000000000..23211c07e1e2b
--- /dev/null
+++ b/clang/test/Preprocessor/header-shadowing.c
@@ -0,0 +1,56 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+/// Check that:
+/// - Quoted includes ("...") trigger the diagnostic.
+/// - Angled includes (<...>) are ignored.
+/// - #include_next does not cause a duplicate warning.
+// RUN: %clang_cc1 -Wshadow-header -Eonly %t/main.c -I %t/include1 -I %t/include2 \
+// RUN: -isystem %t/system1 -isystem %t/system2 2>&1 | FileCheck %s --check-prefix=SHADOWING
+
+// SHADOWING: {{.*}} warning: multiple candidates for header 'header.h' found; directory '{{.*}}include1' chosen, ignoring '{{.*}}include2' and others [-Wshadow-header]
+// SHADOWING: warning: include1/header.h included!
+// SHADOWING: warning: include2/header.h included!
+// SHADOWING: warning: system1/stdio.h included!
+// SHADOWING-NOT: {{.*}} warning: multiple candidates for header 'header.h' found; directory '{{.*}}include2' chosen, ignoring '{{.*}}include1' and others [-Wshadow-header]
+// SHADOWING-NOT: {{.*}} warning: multiple candidates for header 'stdio.h' found; directory '{{.*}}system1' chosen, ignoring '{{.*}}system2' and others [-Wshadow-header]
+
+/// Check that the diagnostic is only performed once in MSVC compatibility mode.
+// RUN: %clang_cc1 -fms-compatibility -Wshadow-header -Eonly %t/t.c -I %t -I %t/foo -I %t/foo/bar 2>&1 | FileCheck %s --check-prefix=SHADOWING-MS
+
+// SHADOWING-MS: {{.*}} warning: multiple candidates for header 't3.h' found; directory '{{.*}}foo' chosen, ignoring '{{.*}}' and others [-Wshadow-header]
+// SHADOWING-MS-NOT: {{.*}} warning: multiple candidates for header 't3.h' found; directory '{{.*}}foo' chosen, ignoring '{{.*}}' and others [-Wshadow-header]
+
+//--- main.c
+#include "header.h"
+#include <stdio.h>
+
+//--- include1/header.h
+#warning include1/header.h included!
+#include_next "header.h"
+
+//--- include2/header.h
+#warning include2/header.h included!
+
+//--- system1/stdio.h
+#warning system1/stdio.h included!
+
+//--- system2/stdio.h
+#warning system2/stdio.h included!
+
+
+/// Used to test when running in MSVC compatibility
+//--- t.c
+#include "foo/t1.h"
+
+//--- foo/t1.h
+#include "bar/t2.h"
+
+//--- foo/bar/t2.h
+#include "t3.h"
+
+//--- foo/t3.h
+#warning Found foo/t3.h.
+
+//--- t3.h
+#warning Found t3.h.
>From afd9ae714fc641703a26f8a0303711b303e31d4f Mon Sep 17 00:00:00 2001
From: huangjinjie <huangjinjie at bytedance.com>
Date: Thu, 6 Nov 2025 20:02:24 +0800
Subject: [PATCH 2/2] update release note
---
clang/docs/ReleaseNotes.rst | 5 ++---
clang/test/Preprocessor/header-shadowing.c | 1 +
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c9b44b3555a38..a22fd4bdd70b7 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -404,9 +404,8 @@ Improvements to Clang's diagnostics
- Clang now emits a diagnostic in case `vector_size` or `ext_vector_type`
attributes are used with a negative size (#GH165463).
-- A new warning, -Wshadow-header, has been added to detect when a header file
- included via quotes (#include "...") is found in multiple distinct search
- directories.
+- A new warning ``-Wshadow-header`` has been added to detect when a header file
+ included via quotes (#include "...") is found in multiple distinct search directories.
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/test/Preprocessor/header-shadowing.c b/clang/test/Preprocessor/header-shadowing.c
index 23211c07e1e2b..92fc0664adf0c 100644
--- a/clang/test/Preprocessor/header-shadowing.c
+++ b/clang/test/Preprocessor/header-shadowing.c
@@ -19,6 +19,7 @@
// RUN: %clang_cc1 -fms-compatibility -Wshadow-header -Eonly %t/t.c -I %t -I %t/foo -I %t/foo/bar 2>&1 | FileCheck %s --check-prefix=SHADOWING-MS
// SHADOWING-MS: {{.*}} warning: multiple candidates for header 't3.h' found; directory '{{.*}}foo' chosen, ignoring '{{.*}}' and others [-Wshadow-header]
+// SHADOWING-MS: warning: Found foo/t3.h.
// SHADOWING-MS-NOT: {{.*}} warning: multiple candidates for header 't3.h' found; directory '{{.*}}foo' chosen, ignoring '{{.*}}' and others [-Wshadow-header]
//--- main.c
More information about the cfe-commits
mailing list