Skip to content

Commit 4199d38

Browse files
committed
header shadowing diagnostics
1 parent ee0818a commit 4199d38

File tree

6 files changed

+133
-0
lines changed

6 files changed

+133
-0
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ Improvements to Clang's diagnostics
404404
- Clang now emits a diagnostic in case `vector_size` or `ext_vector_type`
405405
attributes are used with a negative size (#GH165463).
406406

407+
- A new warning, -Wshadow-header, has been added to detect when a header file
408+
included via quotes (#include "...") is found in multiple distinct search
409+
directories.
410+
407411
Improvements to Clang's time-trace
408412
----------------------------------
409413

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,8 @@ def ShadowFieldInConstructor : DiagGroup<"shadow-field-in-constructor",
790790
def ShadowIvar : DiagGroup<"shadow-ivar">;
791791
def ShadowUncapturedLocal : DiagGroup<"shadow-uncaptured-local">;
792792

793+
def ShadowHeader : DiagGroup<"shadow-header">;
794+
793795
// -Wshadow-all is a catch-all for all shadowing. -Wshadow is just the
794796
// shadowing that we think is unsafe.
795797
def Shadow : DiagGroup<"shadow", [ShadowFieldInConstructorModified,

clang/include/clang/Basic/DiagnosticLexKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,10 @@ def warn_quoted_include_in_framework_header : Warning<
959959
def warn_framework_include_private_from_public : Warning<
960960
"public framework header includes private framework header '%0'"
961961
>, InGroup<FrameworkIncludePrivateFromPublic>;
962+
def warn_header_shadowing : Warning<
963+
"multiple candidates for header '%0' found; "
964+
"directory '%1' chosen, ignoring '%2' and others">,
965+
InGroup<ShadowHeader>, DefaultIgnore;
962966
def warn_deprecated_module_dot_map : Warning<
963967
"'%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">,
964968
InGroup<DeprecatedModuleDotMap>;

clang/include/clang/Lex/HeaderSearch.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,12 @@ class HeaderSearch {
465465
ExternalSource = ES;
466466
}
467467

468+
void DiagnoseHeaderShadowing(
469+
StringRef Filename, OptionalFileEntryRef FE, bool &DiagnosedShadowing,
470+
SourceLocation IncludeLoc, ConstSearchDirIterator FromDir,
471+
ArrayRef<std::pair<OptionalFileEntryRef, DirectoryEntryRef>> Includers,
472+
bool isAngled, int IncluderLoopIndex, ConstSearchDirIterator MainLoopIt);
473+
468474
/// Set the target information for the header search, if not
469475
/// already known.
470476
void setTarget(const TargetInfo &Target);

clang/lib/Lex/HeaderSearch.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,60 @@ diagnoseFrameworkInclude(DiagnosticsEngine &Diags, SourceLocation IncludeLoc,
881881
<< IncludeFilename;
882882
}
883883

884+
void HeaderSearch::DiagnoseHeaderShadowing(
885+
StringRef Filename, OptionalFileEntryRef FE, bool &DiagnosedShadowing,
886+
SourceLocation IncludeLoc, ConstSearchDirIterator FromDir,
887+
ArrayRef<std::pair<OptionalFileEntryRef, DirectoryEntryRef>> Includers,
888+
bool isAngled, int IncluderLoopIndex, ConstSearchDirIterator MainLoopIt) {
889+
890+
if (Diags.isIgnored(diag::warn_header_shadowing, IncludeLoc) || isAngled ||
891+
DiagnosedShadowing)
892+
return;
893+
894+
DiagnosedShadowing = true;
895+
896+
// Indicates that file is first found in the includer's directory
897+
if (!MainLoopIt) {
898+
for (size_t i = IncluderLoopIndex + 1; i < Includers.size(); ++i) {
899+
const auto &IncluderAndDir = Includers[i];
900+
SmallString<1024> TmpDir = IncluderAndDir.second.getName();
901+
llvm::sys::path::append(TmpDir, Filename);
902+
if (auto File = getFileMgr().getFileRef(TmpDir, false, false)) {
903+
if (&File->getFileEntry() == *FE)
904+
continue;
905+
Diags.Report(IncludeLoc, diag::warn_header_shadowing)
906+
<< Filename << (*FE).getDir().getName()
907+
<< IncluderAndDir.second.getName();
908+
return;
909+
} else {
910+
llvm::errorToErrorCode(File.takeError());
911+
}
912+
}
913+
}
914+
915+
// Continue searching in the regular search paths
916+
ConstSearchDirIterator It =
917+
isAngled ? angled_dir_begin() : search_dir_begin();
918+
if (MainLoopIt) {
919+
It = std::next(MainLoopIt);
920+
} else if (FromDir) {
921+
It = FromDir;
922+
}
923+
for (; It != search_dir_end(); ++It) {
924+
SmallString<1024> TmpPath = It->getName();
925+
llvm::sys::path::append(TmpPath, Filename);
926+
if (auto File = getFileMgr().getFileRef(TmpPath, false, false)) {
927+
if (&File->getFileEntry() == *FE)
928+
continue;
929+
Diags.Report(IncludeLoc, diag::warn_header_shadowing)
930+
<< Filename << (*FE).getDir().getName() << It->getName();
931+
return;
932+
} else {
933+
llvm::errorToErrorCode(File.takeError());
934+
}
935+
}
936+
}
937+
884938
/// LookupFile - Given a "foo" or \<foo> reference, look up the indicated file,
885939
/// return null on failure. isAngled indicates whether the file reference is
886940
/// for system \#include's or not (i.e. using <> instead of ""). Includers, if
@@ -930,6 +984,7 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
930984
// This is the header that MSVC's header search would have found.
931985
ModuleMap::KnownHeader MSSuggestedModule;
932986
OptionalFileEntryRef MSFE;
987+
bool DiagnosedShadowing = false;
933988

934989
// Check to see if the file is in the #includer's directory. This cannot be
935990
// based on CurDir, because each includer could be a #include of a
@@ -963,6 +1018,9 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
9631018
if (OptionalFileEntryRef FE = getFileAndSuggestModule(
9641019
TmpDir, IncludeLoc, IncluderAndDir.second, IncluderIsSystemHeader,
9651020
RequestingModule, SuggestedModule)) {
1021+
DiagnoseHeaderShadowing(Filename, FE, DiagnosedShadowing, IncludeLoc,
1022+
FromDir, Includers, isAngled,
1023+
&IncluderAndDir - Includers.begin(), nullptr);
9661024
if (!Includer) {
9671025
assert(First && "only first includer can have no file");
9681026
return FE;
@@ -1097,6 +1155,9 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
10971155
if (!File)
10981156
continue;
10991157

1158+
DiagnoseHeaderShadowing(Filename, File, DiagnosedShadowing, IncludeLoc,
1159+
FromDir, Includers, isAngled, -1, It);
1160+
11001161
CurDir = It;
11011162

11021163
IncludeNames[*File] = Filename;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: rm -rf %t
2+
// RUN: split-file %s %t
3+
4+
/// Check that:
5+
/// - Quoted includes ("...") trigger the diagnostic.
6+
/// - Angled includes (<...>) are ignored.
7+
/// - #include_next does not cause a duplicate warning.
8+
// RUN: %clang_cc1 -Wshadow-header -Eonly %t/main.c -I %t/include1 -I %t/include2 \
9+
// RUN: -isystem %t/system1 -isystem %t/system2 2>&1 | FileCheck %s --check-prefix=SHADOWING
10+
11+
// SHADOWING: {{.*}} warning: multiple candidates for header 'header.h' found; directory '{{.*}}include1' chosen, ignoring '{{.*}}include2' and others [-Wshadow-header]
12+
// SHADOWING: warning: include1/header.h included!
13+
// SHADOWING: warning: include2/header.h included!
14+
// SHADOWING: warning: system1/stdio.h included!
15+
// SHADOWING-NOT: {{.*}} warning: multiple candidates for header 'header.h' found; directory '{{.*}}include2' chosen, ignoring '{{.*}}include1' and others [-Wshadow-header]
16+
// SHADOWING-NOT: {{.*}} warning: multiple candidates for header 'stdio.h' found; directory '{{.*}}system1' chosen, ignoring '{{.*}}system2' and others [-Wshadow-header]
17+
18+
/// Check that the diagnostic is only performed once in MSVC compatibility mode.
19+
// 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
20+
21+
// SHADOWING-MS: {{.*}} warning: multiple candidates for header 't3.h' found; directory '{{.*}}foo' chosen, ignoring '{{.*}}' and others [-Wshadow-header]
22+
// SHADOWING-MS-NOT: {{.*}} warning: multiple candidates for header 't3.h' found; directory '{{.*}}foo' chosen, ignoring '{{.*}}' and others [-Wshadow-header]
23+
24+
//--- main.c
25+
#include "header.h"
26+
#include <stdio.h>
27+
28+
//--- include1/header.h
29+
#warning include1/header.h included!
30+
#include_next "header.h"
31+
32+
//--- include2/header.h
33+
#warning include2/header.h included!
34+
35+
//--- system1/stdio.h
36+
#warning system1/stdio.h included!
37+
38+
//--- system2/stdio.h
39+
#warning system2/stdio.h included!
40+
41+
42+
/// Used to test when running in MSVC compatibility
43+
//--- t.c
44+
#include "foo/t1.h"
45+
46+
//--- foo/t1.h
47+
#include "bar/t2.h"
48+
49+
//--- foo/bar/t2.h
50+
#include "t3.h"
51+
52+
//--- foo/t3.h
53+
#warning Found foo/t3.h.
54+
55+
//--- t3.h
56+
#warning Found t3.h.

0 commit comments

Comments
 (0)