Skip to content

Commit 6377cc2

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 6377cc2

File tree

4 files changed

+194
-9
lines changed

4 files changed

+194
-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: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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 -nostdinc \
15+
// RUN: -I%t/include/w \
16+
// RUN: -isystem %t/include/z \
17+
// RUN: -I%t/include/x \
18+
// RUN: -isystem %t/include/y \
19+
// RUN: -isystem %t/include/x \
20+
// RUN: -I%t/include/w \
21+
// RUN: -isystem %t/include/y \
22+
// RUN: -isystem %t/include/z \
23+
// RUN: %t/test.c 2>&1 | FileCheck -DPWD=%t %t/test.c
24+
25+
#--- test.c
26+
#include <a.h>
27+
#include <b.h>
28+
#include <c.h>
29+
30+
// The expected behavior is that user search paths are ordered before system
31+
// search paths, that user search paths that duplicate a (later) system search
32+
// path are ignored, and that search paths that duplicate an earlier search
33+
// path of the same user/system kind are ignored.
34+
// CHECK: ignoring duplicate directory "[[PWD]]/include/w"
35+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/x"
36+
// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
37+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/y"
38+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/z"
39+
// CHECK: #include <...> search starts here:
40+
// CHECK-NEXT: [[PWD]]/include/w
41+
// CHECK-NEXT: [[PWD]]/include/z
42+
// CHECK-NEXT: [[PWD]]/include/y
43+
// CHECK-NEXT: [[PWD]]/include/x
44+
// CHECK-NEXT: End of search list.
45+
46+
#--- include/w/b.h
47+
#define INCLUDE_W_B_H
48+
#include_next <b.h>
49+
50+
#--- include/w/c.h
51+
#define INCLUDE_W_C_H
52+
#include_next <c.h>
53+
54+
#--- include/x/a.h
55+
#if !defined(INCLUDE_Y_A_H)
56+
#error 'include/y/a.h' should have been included before 'include/x/a.h'!
57+
#endif
58+
59+
#--- include/x/b.h
60+
#if !defined(INCLUDE_W_B_H)
61+
#error 'include/w/b.h' should have been included before 'include/x/b.h'!
62+
#endif
63+
64+
#--- include/x/c.h
65+
#if !defined(INCLUDE_Z_C_H)
66+
#error 'include/z/c.h' should have been included before 'include/x/c.h'!
67+
#endif
68+
69+
#--- include/y/a.h
70+
#define INCLUDE_Y_A_H
71+
#include_next <a.h>
72+
73+
#--- include/z/c.h
74+
#define INCLUDE_Z_C_H
75+
#include_next <c.h>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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 -nostdinc \
15+
// RUN: -I%t/include/w \
16+
// RUN: -isystem %t/include/z \
17+
// RUN: -I%t/include/x \
18+
// RUN: -isystem %t/include/y \
19+
// RUN: -isystem %t/include/x \
20+
// RUN: -I%t/include/w \
21+
// RUN: -isystem %t/include/y \
22+
// RUN: -isystem %t/include/z \
23+
// RUN: %t/test.c 2>&1 | FileCheck -DPWD=%t %t/test.c
24+
25+
// Test the clang-cl driver with a Windows target that implicitly enables the
26+
// -fms-compatibility option. The -nobuiltininc option is used instead of
27+
// -nostdinc or /X because the latter two suppress all system include paths
28+
// (including those specified by -isystem and -imsvc). The -imsvc option is
29+
// used because the -nobuiltininc option suppresses search paths specified
30+
// by -isystem.
31+
// RUN: %clang_cl \
32+
// RUN: -target x86_64-pc-windows \
33+
// RUN: -v -fsyntax-only -nobuiltininc \
34+
// RUN: -I%t/include/w \
35+
// RUN: -imsvc %t/include/z \
36+
// RUN: -I%t/include/x \
37+
// RUN: -imsvc %t/include/y \
38+
// RUN: -imsvc %t/include/x \
39+
// RUN: -I%t/include/w \
40+
// RUN: -imsvc %t/include/y \
41+
// RUN: -imsvc %t/include/z \
42+
// RUN: %t/test.c 2>&1 | FileCheck -DPWD=%t %t/test.c
43+
44+
#--- test.c
45+
#include <a.h>
46+
#include <b.h>
47+
#include <c.h>
48+
49+
// The expected behavior is that user search paths are ordered before system
50+
// search paths and that search paths that duplicate an earlier search path
51+
// (regardless of user/system concerns) are ignored.
52+
// CHECK: ignoring duplicate directory "[[PWD]]/include/w"
53+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/x"
54+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/y"
55+
// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/include/z"
56+
// CHECK-NOT: as it is a non-system directory that duplicates a system directory
57+
// CHECK: #include <...> search starts here:
58+
// CHECK-NEXT: [[PWD]]/include/w
59+
// CHECK-NEXT: [[PWD]]/include/x
60+
// CHECK-NEXT: [[PWD]]/include/z
61+
// CHECK-NEXT: [[PWD]]/include/y
62+
// CHECK-NEXT: End of search list.
63+
64+
#--- include/w/b.h
65+
#define INCLUDE_W_B_H
66+
#include_next <b.h>
67+
68+
#--- include/w/c.h
69+
#define INCLUDE_W_C_H
70+
#include_next <c.h>
71+
72+
#--- include/x/a.h
73+
#define INCLUDE_X_A_H
74+
#include_next <a.h>
75+
76+
#--- include/x/b.h
77+
#if !defined(INCLUDE_W_B_H)
78+
#error 'include/w/b.h' should have been included before 'include/x/b.h'!
79+
#endif
80+
81+
#--- include/x/c.h
82+
#define INCLUDE_X_C_H
83+
84+
#--- include/y/a.h
85+
#if !defined(INCLUDE_X_A_H)
86+
#error 'include/x/a.h' should have been included before 'include/y/a.h'!
87+
#endif
88+
89+
#--- include/z/c.h
90+
#include_next <c.h>
91+
#if !defined(INCLUDE_X_C_H)
92+
#error 'include/x/c.h' should have been included before 'include/z/c.h'!
93+
#endif

0 commit comments

Comments
 (0)