Skip to content

Commit 79b2ceb

Browse files
committed
[Clang] Match MSVC handling of duplicate header search paths in Microsoft compatibility modes.
Clang has historically mimicked gcc behavior for header file searching in which user search paths are ordered before system search paths, user search paths that duplicate a (later) system search path are ignored, and search paths that duplicate an earlier search path of the same user/system kind are ignored. MSVC behavior differs in that user search paths are ordered before system search paths (like gcc), and search paths that duplicate an earlier search path are ignored regardless of user/system kind (similar to gcc, but without the preference for system search paths over duplicate user search paths). The gcc and MSVC differences are observable for driver invocations that pass, e.g., `-Idir1 -isystem dir2 -isystem dir1`. The gcc behavior will result in `dir2` being searched before `dir1` while the MSVC behavior will result in `dir1` being searched before `dir2`. This patch modifies Clang to match the MSVC behavior for handling of duplicate header search paths when running in Microsoft compatibility mode (e.g., when invoked with the `-fms-compatibility` option explicitly or implicitly enabled).
1 parent e490686 commit 79b2ceb

File tree

4 files changed

+199
-9
lines changed

4 files changed

+199
-9
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,20 @@ Windows Support
363363
When `-fms-compatibility-version=18.00` or prior is set on the command line this Microsoft extension is still
364364
allowed as VS2013 and prior allow it.
365365

366+
- Clang now matches MSVC behavior for handling of duplicate header search paths
367+
when running in Microsoft compatibility mode. Historically, Clang has
368+
mimicked gcc behavior in which user search paths are ordered before
369+
system search paths, user search paths that duplicate a (later) system search
370+
path are ignored, and search paths that duplicate an earlier search path of
371+
the same user/system kind are ignored. The MSVC behavior is that user search
372+
paths are ordered before system search paths (like gcc), and search paths that
373+
duplicate an earlier search path are ignored regardless of user/system kind
374+
(similar to gcc, but without the preference for system search paths over
375+
duplicate user search paths). These differences are observable for driver
376+
invocations that pass, e.g., `-Idir1 -isystem dir2 -isystem dir1`. The gcc
377+
behavior will search `dir2` before `dir1` and the MSVC behavior will search
378+
`dir1` before `dir2`.
379+
366380
LoongArch Support
367381
^^^^^^^^^^^^^^^^^
368382

clang/lib/Lex/InitHeaderSearch.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,8 @@ void InitHeaderSearch::AddDefaultIncludePaths(
368368
/// If there are duplicate directory entries in the specified search list,
369369
/// remove the later (dead) ones. Returns the number of non-system headers
370370
/// removed, which is used to update NumAngled.
371-
static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList,
371+
static unsigned RemoveDuplicates(const LangOptions &Lang,
372+
std::vector<DirectoryLookupInfo> &SearchList,
372373
unsigned First, bool Verbose) {
373374
llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenDirs;
374375
llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenFrameworkDirs;
@@ -394,14 +395,15 @@ static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList,
394395
continue;
395396
}
396397

397-
// If we have a normal #include dir/framework/headermap that is shadowed
398-
// later in the chain by a system include location, we actually want to
399-
// ignore the user's request and drop the user dir... keeping the system
400-
// dir. This is weird, but required to emulate GCC's search path correctly.
398+
// When not in MSVC compatibility mode, if we have a normal
399+
// #include dir/framework/headermap that is shadowed later in the chain by
400+
// a system include location, we actually want to ignore the user's request
401+
// and drop the user dir... keeping the system dir. This is weird, but
402+
// required to emulate GCC's search path correctly.
401403
//
402404
// Since dupes of system dirs are rare, just rescan to find the original
403405
// that we're nuking instead of using a DenseMap.
404-
if (CurEntry.getDirCharacteristic() != SrcMgr::C_User) {
406+
if (!Lang.MSVCCompat && CurEntry.getDirCharacteristic() != SrcMgr::C_User) {
405407
// Find the dir that this is the same of.
406408
unsigned FirstDir;
407409
for (FirstDir = First;; ++FirstDir) {
@@ -484,14 +486,14 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) {
484486
SearchList.push_back(Include);
485487

486488
// Deduplicate and remember index.
487-
RemoveDuplicates(SearchList, 0, Verbose);
489+
RemoveDuplicates(Lang, SearchList, 0, Verbose);
488490
unsigned NumQuoted = SearchList.size();
489491

490492
for (auto &Include : IncludePath)
491493
if (Include.Group == Angled || Include.Group == IndexHeaderMap)
492494
SearchList.push_back(Include);
493495

494-
RemoveDuplicates(SearchList, NumQuoted, Verbose);
496+
RemoveDuplicates(Lang, SearchList, NumQuoted, Verbose);
495497
unsigned NumAngled = SearchList.size();
496498

497499
for (auto &Include : IncludePath)
@@ -510,7 +512,8 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) {
510512
// Remove duplicates across both the Angled and System directories. GCC does
511513
// this and failing to remove duplicates across these two groups breaks
512514
// #include_next.
513-
unsigned NonSystemRemoved = RemoveDuplicates(SearchList, NumQuoted, Verbose);
515+
unsigned NonSystemRemoved =
516+
RemoveDuplicates(Lang, SearchList, NumQuoted, Verbose);
514517
NumAngled -= NonSystemRemoved;
515518

516519
Headers.SetSearchPaths(extractLookups(SearchList), NumQuoted, NumAngled,
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Test that the gcc-like driver, when not in Microsoft compatibility mode,
2+
// emulates the gcc behavior of eliding a user header search path when the
3+
// same path is present as a system header search path.
4+
// See microsoft-header-search-duplicates.c for Microsoft compatible behavior.
5+
6+
// RUN: rm -rf %t
7+
// RUN: split-file %s %t
8+
9+
// Test the clang driver with a target that does not implicitly enable the
10+
// -fms-compatibility option. The -nostdinc option is used to suppress default
11+
// search paths to ease testing.
12+
// RUN: %clang \
13+
// RUN: -target x86_64-unknown-linux-gnu \
14+
// RUN: -v -fsyntax-only \
15+
// RUN: -nostdinc \
16+
// RUN: -I%t/include/w \
17+
// RUN: -isystem %t/include/z \
18+
// RUN: -I%t/include/x \
19+
// RUN: -isystem %t/include/y \
20+
// RUN: -isystem %t/include/x \
21+
// RUN: -I%t/include/w \
22+
// RUN: -isystem %t/include/y \
23+
// RUN: -isystem %t/include/z \
24+
// RUN: %t/test.c 2>&1 | FileCheck -DPWD=%t %t/test.c
25+
26+
#--- test.c
27+
#include <a.h>
28+
#include <b.h>
29+
#include <c.h>
30+
31+
// The expected behavior is that user search paths are ordered before system
32+
// search paths, that user search paths that duplicate a (later) system search
33+
// path are ignored, and that search paths that duplicate an earlier search
34+
// path of the same user/system kind are ignored.
35+
// CHECK: ignoring duplicate directory "[[PWD]]/include/w"
36+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/x"
37+
// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
38+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/y"
39+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/z"
40+
// CHECK: #include <...> search starts here:
41+
// CHECK-NEXT: [[PWD]]/include/w
42+
// CHECK-NEXT: [[PWD]]/include/z
43+
// CHECK-NEXT: [[PWD]]/include/y
44+
// CHECK-NEXT: [[PWD]]/include/x
45+
// CHECK-NEXT: End of search list.
46+
47+
#--- include/w/b.h
48+
#define INCLUDE_W_B_H
49+
#include_next <b.h>
50+
51+
#--- include/w/c.h
52+
#define INCLUDE_W_C_H
53+
#include_next <c.h>
54+
55+
#--- include/x/a.h
56+
#if !defined(INCLUDE_Y_A_H)
57+
#error 'include/y/a.h' should have been included before 'include/x/a.h'!
58+
#endif
59+
60+
#--- include/x/b.h
61+
#if !defined(INCLUDE_W_B_H)
62+
#error 'include/w/b.h' should have been included before 'include/x/b.h'!
63+
#endif
64+
65+
#--- include/x/c.h
66+
#if !defined(INCLUDE_Z_C_H)
67+
#error 'include/z/c.h' should have been included before 'include/x/c.h'!
68+
#endif
69+
70+
#--- include/y/a.h
71+
#define INCLUDE_Y_A_H
72+
#include_next <a.h>
73+
74+
#--- include/z/c.h
75+
#define INCLUDE_Z_C_H
76+
#include_next <c.h>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Test that the cl-like driver and the gcc-like driver, when in Microsoft
2+
// compatibility mode, retain user header search paths that are duplicated in
3+
// system header search paths.
4+
// See header-search-duplicates.c for gcc compatible behavior.
5+
6+
// RUN: rm -rf %t
7+
// RUN: split-file %s %t
8+
9+
// Test the clang driver with a Windows target that implicitly enables the
10+
// -fms-compatibility option. The -nostdinc option is used to suppress default
11+
// search paths to ease testing.
12+
// RUN: %clang \
13+
// RUN: -target x86_64-pc-windows \
14+
// RUN: -v -fsyntax-only \
15+
// RUN: -nostdinc \
16+
// RUN: -I%t/include/w \
17+
// RUN: -isystem %t/include/z \
18+
// RUN: -I%t/include/x \
19+
// RUN: -isystem %t/include/y \
20+
// RUN: -isystem %t/include/x \
21+
// RUN: -I%t/include/w \
22+
// RUN: -isystem %t/include/y \
23+
// RUN: -isystem %t/include/z \
24+
// RUN: %t/test.c 2>&1 | FileCheck -DPWD=%t %t/test.c
25+
26+
// Test the clang-cl driver with a Windows target that implicitly enables the
27+
// -fms-compatibility option. The /X option is used instead of -nostdinc
28+
// because the latter option suppresses all system include paths including
29+
// those specified by -imsvc. The -nobuiltininc option is uesd to suppress
30+
// the Clang resource directory. The -nostdlibinc option is used to suppress
31+
// search paths for the Windows SDK, CRT, MFC, ATL, etc...
32+
// RUN: %clang_cl \
33+
// RUN: -target x86_64-pc-windows \
34+
// RUN: -v -fsyntax-only \
35+
// RUN: /X \
36+
// RUN: -nobuiltininc \
37+
// RUN: -nostdlibinc \
38+
// RUN: -I%t/include/w \
39+
// RUN: -imsvc %t/include/z \
40+
// RUN: -I%t/include/x \
41+
// RUN: -imsvc %t/include/y \
42+
// RUN: -imsvc %t/include/x \
43+
// RUN: -I%t/include/w \
44+
// RUN: -imsvc %t/include/y \
45+
// RUN: -imsvc %t/include/z \
46+
// RUN: %t/test.c 2>&1 | FileCheck -DPWD=%t %t/test.c
47+
48+
#--- test.c
49+
#include <a.h>
50+
#include <b.h>
51+
#include <c.h>
52+
53+
// The expected behavior is that user search paths are ordered before system
54+
// search paths and that search paths that duplicate an earlier search path
55+
// (regardless of user/system concerns) are ignored.
56+
// CHECK: ignoring duplicate directory "[[PWD]]/include/w"
57+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/x"
58+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/y"
59+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/z"
60+
// CHECK-NOT: as it is a non-system directory that duplicates a system directory
61+
// CHECK: #include <...> search starts here:
62+
// CHECK-NEXT: [[PWD]]/include/w
63+
// CHECK-NEXT: [[PWD]]/include/x
64+
// CHECK-NEXT: [[PWD]]/include/z
65+
// CHECK-NEXT: [[PWD]]/include/y
66+
// CHECK-NEXT: End of search list.
67+
68+
#--- include/w/b.h
69+
#define INCLUDE_W_B_H
70+
#include_next <b.h>
71+
72+
#--- include/w/c.h
73+
#define INCLUDE_W_C_H
74+
#include_next <c.h>
75+
76+
#--- include/x/a.h
77+
#define INCLUDE_X_A_H
78+
#include_next <a.h>
79+
80+
#--- include/x/b.h
81+
#if !defined(INCLUDE_W_B_H)
82+
#error 'include/w/b.h' should have been included before 'include/x/b.h'!
83+
#endif
84+
85+
#--- include/x/c.h
86+
#define INCLUDE_X_C_H
87+
88+
#--- include/y/a.h
89+
#if !defined(INCLUDE_X_A_H)
90+
#error 'include/x/a.h' should have been included before 'include/y/a.h'!
91+
#endif
92+
93+
#--- include/z/c.h
94+
#include_next <c.h>
95+
#if !defined(INCLUDE_X_C_H)
96+
#error 'include/x/c.h' should have been included before 'include/z/c.h'!
97+
#endif

0 commit comments

Comments
 (0)