[clang] [llvm] [Clang] Match MSVC handling of duplicate header search paths in Microsoft compatibility modes. (PR #105738)
Tom Honermann via cfe-commits
cfe-commits at lists.llvm.org
Tue Nov 5 10:14:14 PST 2024
https://github.com/tahonermann updated https://github.com/llvm/llvm-project/pull/105738
>From 35e3cb1c9901c365c0cd6225a61067987d6c40b8 Mon Sep 17 00:00:00 2001
From: Tom Honermann <tom.honermann at intel.com>
Date: Thu, 22 Aug 2024 09:44:56 -0700
Subject: [PATCH] [Clang] Match MSVC handling of duplicate header search paths
in Microsoft compatibility modes.
Clang has historically matched GCC's behavior for header search path order
and pruning of duplicate paths. That traditional behavior is to order user
search paths before system search paths, to ignore user search paths that
duplicate a (later) system search path, and to ignore search paths that
duplicate an earlier search path of the same user/system kind. This differs
from MSVC and can result in inconsistent header file resolution for `#include`
directives.
MSVC orders header search paths as follows:
1) Paths specified by the `/I` and `/external:I` options are processed in
the order that they appear. Paths specified by `/I` that duplicate a path
specified by `/external:I` are ignored regardless of the order of the
options. Paths specified by `/I` that duplicate a path from a prior `/I`
option are ignored. Paths specified by `/external:I` that duplicate a
path from a later `/external:I` option are ignored.
2) Paths specified by `/external:env` are processed in the order that they
appear. Paths that duplicate a path from a `/I` or `/external:I` option
are ignored regardless of the order of the options. Paths that duplicate a
path from a prior `/external:env` option or an earlier path from the same
`/external:env` option are ignored.
3) Paths specified by the `INCLUDE` environment variable are processed in
the order they appear. Paths that duplicate a path from a `/I`,
`/external:I`, or `/external:env` option are ignored. Paths that
duplicate an earlier path in the `INCLUDE` environment variable are
ignored.
4) Paths specified by the `EXTERNAL_INCLUDE` environment variable are
processed in the order they appear. Paths that duplicate a path from a
`/I`, `/external:I`, or `/external:env` option are ignored. Paths that
duplicate a path from the `INCLUDE` environment variable are ignored.
Paths that duplicate an earlier path in the `EXTERNAL_INCLUDE
environment variable are ignored.
Prior to this change, Clang handled the `/external:I` and `/external:env`
options and the paths present in the `INCLUDE` and `EXTERNAL_INCLUDE`
environment variables as though they were specified with the `-isystem` option.
The GCC behavior described above then lead to a command line such as
`/external:I dir1 /Idir2` having a header search order of `dir2` followed by
`dir1`; contrary to MSVC behavior.
This change adds support for the MSVC external path concept for both the `clang`
and `clang-cl` drivers with the following option syntax. These options match the
MSVC behavior described above for both drivers.
clang clang-cl
-------------------- -------------------
-iexternal <dir> /external:I <dir>
-iexternal-env=<ENV> /external:env:<ENV>
Paths specified by these options are still treated as system paths. That is,
whether warnings are issued in header files found via these paths remains
subject to use of the `-Wsystem-headers` and `-Wno-system-headers` options.
In the future, it would make sense to add a separate option that matches the
MSVC `/external:Wn` option to control such warnings.
The MSVC behavior described above implies that (system) paths present in the
`INCLUDE` and `EXTERNAL_INCLUDE` environment variables do not suppress matching
user paths specified via `/I`. This contrasts with GCC's behavior of suppressing
user paths that match a system path regardless of how each is specified. Since
the `clang-cl` driver maps paths from the `INCLUDE` and `EXTERNAL_INCLUDE`
environment variable to `-internal-isystem`, matching MSVC behavior requires
suppressing that aspect of the GCC behavior. With this change, system paths
will no longer suppress user paths when the `-fms-compatibility` option is
explicitly or implicitly enabled. This will affect header search path ordering
for options like `-isystem` when duplicate user paths are present. Should
motivation arise for preserving such suppression of user paths when compiling
with `-fms-compatibility` enabled, it would make sense to introduce a new
option for the `clang-cl` driver to map paths in these environment variabless
to that would match MSVC behavior without impacting other system path options.
---
clang/docs/ReleaseNotes.rst | 38 ++
clang/include/clang/Driver/Options.td | 17 +-
clang/include/clang/Driver/ToolChain.h | 11 +-
clang/include/clang/Lex/HeaderSearchOptions.h | 10 +
clang/lib/Driver/Driver.cpp | 4 +-
clang/lib/Driver/ToolChain.cpp | 45 +++
clang/lib/Driver/ToolChains/Clang.cpp | 15 +-
clang/lib/Driver/ToolChains/MSVC.cpp | 28 +-
clang/lib/Frontend/CompilerInvocation.cpp | 30 +-
clang/lib/Lex/InitHeaderSearch.cpp | 172 ++++----
clang/test/Driver/cl-include.c | 20 +-
clang/test/Driver/cl-options.c | 7 +-
clang/test/Driver/header-search-duplicates.c | 370 ++++++++++++++++++
.../microsoft-header-search-duplicates.c | 363 +++++++++++++++++
llvm/include/llvm/Option/ArgList.h | 7 +-
llvm/lib/Option/ArgList.cpp | 6 -
16 files changed, 1016 insertions(+), 127 deletions(-)
create mode 100644 clang/test/Driver/header-search-duplicates.c
create mode 100644 clang/test/Driver/microsoft-header-search-duplicates.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 4e555914caee8a..3eef3f1f6e77c6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -701,6 +701,44 @@ Windows Support
When `-fms-compatibility-version=18.00` or prior is set on the command line this Microsoft extension is still
allowed as VS2013 and prior allow it.
+- Clang now matches MSVC behavior regarding the handling of duplicate header
+ search paths when running in Microsoft compatibility mode. Historically,
+ Clang has mimicked gcc behavior 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. This ordering is not compatible with
+ the ordering that MSVC uses when paths are duplicated across ``/I`` options
+ and the ``INCLUDE`` environment variable.
+
+ The order that MSVC uses and that Clang now replicates when the
+ ``-fms-compatibility`` option is enabled follows.
+
+ - Paths specified by the ``/I`` and ``/external:I`` options are processed in
+ the order that they appear. Paths specified by ``/I`` that duplicate a path
+ specified by ``/external:I`` are ignored regardless of the order of the
+ options. Paths specified by ``/I`` that duplicate a path from a prior ``/I``
+ option are ignored. Paths specified by ``/external:I`` that duplicate a
+ path from a later ``/external:I`` option are ignored.
+
+ - Paths specified by ``/external:env`` are processed in the order that they
+ appear. Paths that duplicate a path from a ``/I`` or ``/external:I`` option
+ are ignored regardless of the order of the options. Paths that duplicate a
+ path from a prior ``/external:env`` option or an earlier path from the same
+ ``/external:env`` option are ignored.
+
+ - Paths specified by the ``INCLUDE`` environment variable are processed in
+ the order they appear. Paths that duplicate a path from a ``/I``,
+ ``/external:I``, or ``/external:env`` option are ignored. Paths that
+ duplicate an earlier path in the ``INCLUDE`` environment variable are
+ ignored.
+
+ - Paths specified by the ``EXTERNAL_INCLUDE`` environment variable are
+ processed in the order they appear. Paths that duplicate a path from a
+ ``/I``, ``/external:I``, or ``/external:env`` option are ignored. Paths that
+ duplicate a path from the ``INCLUDE`` environment variable are ignored.
+ Paths that duplicate an earlier path in the ``EXTERNAL_INCLUDE``
+ environment variable are ignored.
+
LoongArch Support
^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 805b79491e6ea4..d27804b0bad4bc 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -4576,6 +4576,15 @@ def iapinotes_modules : JoinedOrSeparate<["-"], "iapinotes-modules">, Group<clan
def idirafter : JoinedOrSeparate<["-"], "idirafter">, Group<clang_i_Group>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"Add directory to AFTER include search path">;
+def iexternal : Separate<["-"], "iexternal">, Group<clang_i_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ HelpText<"Add directory to include search path with warnings suppressed">, MetaVarName<"<dir>">;
+def iexternal_after : Separate<["-"], "iexternal-after">, Group<clang_i_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ HelpText<"Add directory to include search path with warnings suppressed">, MetaVarName<"<dir>">;
+def iexternal_env_EQ : Joined<["-"], "iexternal-env=">, Group<clang_i_Group>,
+ Visibility<[ClangOption]>,
+ HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">, MetaVarName<"<var>">;
def iframework : JoinedOrSeparate<["-"], "iframework">, Group<clang_i_Group>,
Visibility<[ClangOption, CC1Option]>,
HelpText<"Add directory to SYSTEM framework search path">;
@@ -8464,9 +8473,12 @@ def _SLASH_diagnostics_classic : CLFlag<"diagnostics:classic">,
def _SLASH_D : CLJoinedOrSeparate<"D", [CLOption, DXCOption]>,
HelpText<"Define macro">, MetaVarName<"<macro[=value]>">, Alias<D>;
def _SLASH_E : CLFlag<"E">, HelpText<"Preprocess to stdout">, Alias<E>;
-def _SLASH_external_COLON_I : CLJoinedOrSeparate<"external:I">, Alias<isystem>,
+def _SLASH_external_COLON_I : CLJoinedOrSeparate<"external:I">, Alias<iexternal>,
HelpText<"Add directory to include search path with warnings suppressed">,
MetaVarName<"<dir>">;
+def _SLASH_external_env : CLJoined<"external:env:">, Alias<iexternal_env_EQ>,
+ HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">,
+ MetaVarName<"<var>">;
def _SLASH_fp_contract : CLFlag<"fp:contract">, HelpText<"">, Alias<ffp_contract>, AliasArgs<["on"]>;
def _SLASH_fp_except : CLFlag<"fp:except">, HelpText<"">, Alias<ffp_exception_behavior_EQ>, AliasArgs<["strict"]>;
def _SLASH_fp_except_ : CLFlag<"fp:except-">, HelpText<"">, Alias<ffp_exception_behavior_EQ>, AliasArgs<["ignore"]>;
@@ -8696,9 +8708,6 @@ def _SLASH_volatile_Group : OptionGroup<"</volatile group>">,
def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">;
def _SLASH_EP : CLFlag<"EP">,
HelpText<"Disable linemarker output and preprocess to stdout">;
-def _SLASH_external_env : CLJoined<"external:env:">,
- HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">,
- MetaVarName<"<var>">;
def _SLASH_FA : CLJoined<"FA">,
HelpText<"Output assembly code file during compilation">;
def _SLASH_Fa : CLJoined<"Fa">,
diff --git a/clang/include/clang/Driver/ToolChain.h b/clang/include/clang/Driver/ToolChain.h
index 5347e29be91439..6687c3b3833fde 100644
--- a/clang/include/clang/Driver/ToolChain.h
+++ b/clang/include/clang/Driver/ToolChain.h
@@ -224,6 +224,7 @@ class ToolChain {
/// \return The subdirectory path if it exists.
std::optional<std::string> getTargetSubDirPath(StringRef BaseDir) const;
+public:
/// \name Utilities for implementing subclasses.
///@{
static void addSystemInclude(const llvm::opt::ArgList &DriverArgs,
@@ -239,12 +240,20 @@ class ToolChain {
static void addSystemIncludes(const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args,
ArrayRef<StringRef> Paths);
+ static bool addSystemIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
+ llvm::opt::ArgStringList &CC1Args,
+ StringRef Var);
+ static void addExternalAfterIncludes(const llvm::opt::ArgList &DriverArgs,
+ llvm::opt::ArgStringList &CC1Args,
+ ArrayRef<StringRef> Paths);
+ static bool addExternalIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
+ llvm::opt::ArgStringList &CC1Args,
+ StringRef Var);
static std::string concat(StringRef Path, const Twine &A, const Twine &B = "",
const Twine &C = "", const Twine &D = "");
///@}
-public:
virtual ~ToolChain();
// Accessors
diff --git a/clang/include/clang/Lex/HeaderSearchOptions.h b/clang/include/clang/Lex/HeaderSearchOptions.h
index c85e3d27281701..298afc12ab28bd 100644
--- a/clang/include/clang/Lex/HeaderSearchOptions.h
+++ b/clang/include/clang/Lex/HeaderSearchOptions.h
@@ -35,6 +35,16 @@ enum IncludeDirGroup {
/// Paths for '\#include <>' added by '-I'.
Angled,
+ /// Like Angled, but marks system directories while retaining relative order
+ /// with user directories. This group is intended to match the semantics of
+ /// the MSVC /external:I option.
+ External,
+
+ /// Like External, but searched after other external directories but before
+ /// system directories. This group is intended to match the semantics of the
+ /// MSVC /external:env option.
+ ExternalAfter,
+
/// Like Angled, but marks system directories.
System,
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index c5be122737051e..a74c071a425d92 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1292,7 +1292,9 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
if (VFS->setCurrentWorkingDirectory(WD->getValue()))
Diag(diag::err_drv_unable_to_set_working_directory) << WD->getValue();
- // Check for missing include directories.
+ // Check for missing include directories. Diagnostics should not be issued
+ // for directories specified with -iexternal, -iexternal-env=, or
+ // -iexternal-after since those options may be used to specify partial paths.
if (!Diags.isIgnored(diag::warn_missing_include_dirs, SourceLocation())) {
for (auto IncludeDir : Args.getAllArgValues(options::OPT_I_Group)) {
if (!VFS->exists(IncludeDir))
diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp
index 9774d3fab741cb..b98c8084c7baa6 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -1275,6 +1275,51 @@ void ToolChain::addExternCSystemIncludeIfExists(const ArgList &DriverArgs,
}
}
+/// Utility function to add a list of ';' delimited directories specified in
+/// an environment variable to the system include path list for CC1. Returns
+/// true if the variable is set and not empty.
+/*static*/ bool ToolChain::addSystemIncludesFromEnv(const ArgList &DriverArgs,
+ ArgStringList &CC1Args,
+ StringRef Var) {
+ if (auto Val = llvm::sys::Process::GetEnv(Var)) {
+ SmallVector<StringRef, 8> Dirs;
+ StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
+ if (!Dirs.empty()) {
+ addSystemIncludes(DriverArgs, CC1Args, Dirs);
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Utility function to add a list of directories to the end of the external
+/// include path list for CC1.
+/*static*/ void ToolChain::addExternalAfterIncludes(const ArgList &DriverArgs,
+ ArgStringList &CC1Args,
+ ArrayRef<StringRef> Paths) {
+ for (const auto &Path : Paths) {
+ CC1Args.push_back("-iexternal-after");
+ CC1Args.push_back(DriverArgs.MakeArgString(Path));
+ }
+}
+
+/// Utility function to add a list of ';' delimited directories specified in
+/// an environment variable to the external include path list for CC1. Returns
+/// true if the variable is set and not empty.
+/*static*/ bool ToolChain::addExternalIncludesFromEnv(const ArgList &DriverArgs,
+ ArgStringList &CC1Args,
+ StringRef Var) {
+ if (auto Val = llvm::sys::Process::GetEnv(Var)) {
+ SmallVector<StringRef, 8> Dirs;
+ StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
+ if (!Dirs.empty()) {
+ addExternalAfterIncludes(DriverArgs, CC1Args, Dirs);
+ return true;
+ }
+ }
+ return false;
+}
+
/*static*/ std::string ToolChain::concat(StringRef Path, const Twine &A,
const Twine &B, const Twine &C,
const Twine &D) {
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index dca8d3fd7b3eaf..465b6851017d48 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -1198,6 +1198,13 @@ void Clang::AddPreprocessingOptions(Compilation &C, const JobAction &JA,
} else if (A->getOption().matches(options::OPT_ibuiltininc)) {
// This is used only by the driver. No need to pass to cc1.
continue;
+ } else if (A->getOption().matches(options::OPT_iexternal)) {
+ // This option has to retain relative order with other -I options.
+ continue;
+ } else if (A->getOption().matches(options::OPT_iexternal_env_EQ)) {
+ A->claim();
+ ToolChain::addExternalIncludesFromEnv(Args, CmdArgs, A->getValue());
+ continue;
}
// Not translated, render as usual.
@@ -1205,9 +1212,9 @@ void Clang::AddPreprocessingOptions(Compilation &C, const JobAction &JA,
A->render(Args, CmdArgs);
}
- Args.addAllArgs(CmdArgs,
- {options::OPT_D, options::OPT_U, options::OPT_I_Group,
- options::OPT_F, options::OPT_embed_dir_EQ});
+ Args.addAllArgs(CmdArgs, {options::OPT_D, options::OPT_U,
+ options::OPT_I_Group, options::OPT_F,
+ options::OPT_iexternal, options::OPT_embed_dir_EQ});
// Add -Wp, and -Xpreprocessor if using the preprocessor.
@@ -8664,7 +8671,7 @@ void ClangAs::ConstructJob(Compilation &C, const JobAction &JA,
(void)Args.hasArg(options::OPT_force__cpusubtype__ALL);
// Pass along any -I options so we get proper .include search paths.
- Args.AddAllArgs(CmdArgs, options::OPT_I_Group);
+ Args.addAllArgs(CmdArgs, {options::OPT_I_Group, options::OPT_iexternal});
// Pass along any --embed-dir or similar options so we get proper embed paths.
Args.AddAllArgs(CmdArgs, options::OPT_embed_dir_EQ);
diff --git a/clang/lib/Driver/ToolChains/MSVC.cpp b/clang/lib/Driver/ToolChains/MSVC.cpp
index 80799d1e715f07..1590f572da97cb 100644
--- a/clang/lib/Driver/ToolChains/MSVC.cpp
+++ b/clang/lib/Driver/ToolChains/MSVC.cpp
@@ -648,24 +648,6 @@ void MSVCToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
for (const auto &Path : DriverArgs.getAllArgValues(options::OPT__SLASH_imsvc))
addSystemInclude(DriverArgs, CC1Args, Path);
- auto AddSystemIncludesFromEnv = [&](StringRef Var) -> bool {
- if (auto Val = llvm::sys::Process::GetEnv(Var)) {
- SmallVector<StringRef, 8> Dirs;
- StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
- if (!Dirs.empty()) {
- addSystemIncludes(DriverArgs, CC1Args, Dirs);
- return true;
- }
- }
- return false;
- };
-
- // Add %INCLUDE%-like dirs via /external:env: flags.
- for (const auto &Var :
- DriverArgs.getAllArgValues(options::OPT__SLASH_external_env)) {
- AddSystemIncludesFromEnv(Var);
- }
-
// Add DIA SDK include if requested.
if (const Arg *A = DriverArgs.getLastArg(options::OPT__SLASH_diasdkdir,
options::OPT__SLASH_winsysroot)) {
@@ -682,12 +664,14 @@ void MSVCToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
if (DriverArgs.hasArg(options::OPT_nostdlibinc))
return;
- // Honor %INCLUDE% and %EXTERNAL_INCLUDE%. It should have essential search
- // paths set by vcvarsall.bat. Skip if the user expressly set a vctoolsdir.
+ // Add paths from the INCLUDE and EXTERNAL_INCLUDE environment variables if
+ // neither a vctoolsdir or winsysroot directory has been explicitly specified.
+ // If any paths are present in these environment variables, then skip adding
+ // additional system directories.
if (!DriverArgs.getLastArg(options::OPT__SLASH_vctoolsdir,
options::OPT__SLASH_winsysroot)) {
- bool Found = AddSystemIncludesFromEnv("INCLUDE");
- Found |= AddSystemIncludesFromEnv("EXTERNAL_INCLUDE");
+ bool Found = addSystemIncludesFromEnv(DriverArgs, CC1Args, "INCLUDE");
+ Found |= addSystemIncludesFromEnv(DriverArgs, CC1Args, "EXTERNAL_INCLUDE");
if (Found)
return;
}
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index b5fd35aaa1e841..d2bf20230005c5 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -3190,19 +3190,27 @@ static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts,
auto It = Opts.UserEntries.begin();
auto End = Opts.UserEntries.end();
- // Add -I... and -F... options in order.
- for (; It < End && Matches(*It, {frontend::Angled}, std::nullopt, true);
+ // Add the -I..., -F..., and -iexternal options in order.
+ for (; It < End && Matches(*It, {frontend::Angled, frontend::External},
+ std::nullopt, true);
++It) {
OptSpecifier Opt = [It, Matches]() {
if (Matches(*It, frontend::Angled, true, true))
return OPT_F;
if (Matches(*It, frontend::Angled, false, true))
return OPT_I;
+ if (Matches(*It, frontend::External, false, true))
+ return OPT_iexternal;
llvm_unreachable("Unexpected HeaderSearchOptions::Entry.");
}();
GenerateArg(Consumer, Opt, It->Path);
- };
+ }
+
+ // Add the paths for the -iexternal-env= and -iexternal-after options in
+ // order.
+ for (; It < End && Matches(*It, {frontend::ExternalAfter}, false, true); ++It)
+ GenerateArg(Consumer, OPT_iexternal_after, It->Path);
// Note: some paths that came from "[-iprefix=xx] -iwithprefixbefore=yy" may
// have already been generated as "-I[xx]yy". If that's the case, their
@@ -3312,7 +3320,6 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
llvm::CachedHashString(MacroDef.split('=').first));
}
- // Add -I... and -F... options in order.
bool IsSysrootSpecified =
Args.hasArg(OPT__sysroot_EQ) || Args.hasArg(OPT_isysroot);
@@ -3331,12 +3338,21 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
return A->getValue();
};
- for (const auto *A : Args.filtered(OPT_I, OPT_F)) {
+ // Add the -I..., -F..., and -iexternal options in order.
+ for (const auto *A : Args.filtered(OPT_I, OPT_F, OPT_iexternal)) {
+ frontend::IncludeDirGroup Group = frontend::Angled;
+ if (A->getOption().matches(OPT_iexternal))
+ Group = frontend::External;
bool IsFramework = A->getOption().matches(OPT_F);
- Opts.AddPath(PrefixHeaderPath(A, IsFramework), frontend::Angled,
- IsFramework, /*IgnoreSysroot=*/true);
+ Opts.AddPath(PrefixHeaderPath(A, IsFramework), Group, IsFramework,
+ /*IgnoreSysroot=*/true);
}
+ // Add the -iexternal-env= and -iexternal-after options in order.
+ for (const auto *A : Args.filtered(OPT_iexternal_after))
+ Opts.AddPath(A->getValue(), frontend::ExternalAfter,
+ /*IsFramework=*/false, /*IgnoreSysRoot=*/true);
+
// Add -iprefix/-iwithprefix/-iwithprefixbefore options.
StringRef Prefix = ""; // FIXME: This isn't the correct default prefix.
for (const auto *A :
diff --git a/clang/lib/Lex/InitHeaderSearch.cpp b/clang/lib/Lex/InitHeaderSearch.cpp
index 86c2ecdf9e36eb..45870493d0df0e 100644
--- a/clang/lib/Lex/InitHeaderSearch.cpp
+++ b/clang/lib/Lex/InitHeaderSearch.cpp
@@ -154,6 +154,7 @@ bool InitHeaderSearch::AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
} else if (Group == ExternCSystem) {
Type = SrcMgr::C_ExternCSystem;
} else {
+ // Group in External, ExternalAfter, System, (Obj)C(XX)System, After.
Type = SrcMgr::C_System;
}
@@ -364,90 +365,108 @@ void InitHeaderSearch::AddDefaultIncludePaths(
AddDefaultCIncludePaths(triple, HSOpts);
}
-/// If there are duplicate directory entries in the specified search list,
-/// remove the later (dead) ones. Returns the number of non-system headers
-/// removed, which is used to update NumAngled.
-static unsigned RemoveDuplicates(std::vector<DirectoryLookupInfo> &SearchList,
- unsigned First, bool Verbose) {
+/// Remove duplicate paths from a partitioned search list with a diagnostic
+/// issued if Verbose is true. Partitioning is at the discretion of the
+/// caller and may be used to, for example, indicate a division between user
+/// and system search paths. If partitioning is not needed, then call with
+/// Part1Begin equal to Part2Begin. The return value is the number of items
+/// removed from the first partition.
+static unsigned RemoveDuplicates(const LangOptions &Lang,
+ std::vector<DirectoryLookupInfo> &SearchList,
+ unsigned Part1Begin, unsigned Part2Begin,
+ bool Verbose) {
llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenDirs;
llvm::SmallPtrSet<const DirectoryEntry *, 8> SeenFrameworkDirs;
llvm::SmallPtrSet<const HeaderMap *, 8> SeenHeaderMaps;
- unsigned NonSystemRemoved = 0;
- for (unsigned i = First; i != SearchList.size(); ++i) {
- unsigned DirToRemove = i;
-
+ unsigned NumPart1DirsRemoved = 0;
+ for (unsigned i = Part1Begin; i != SearchList.size(); ++i) {
+ IncludeDirGroup CurGroup = SearchList[i].Group;
const DirectoryLookup &CurEntry = SearchList[i].Lookup;
+ SrcMgr::CharacteristicKind CurSrcKind = CurEntry.getDirCharacteristic();
+ // If the current entry is for a previously unseen location, cache it and
+ // continue with the next entry.
if (CurEntry.isNormalDir()) {
- // If this isn't the first time we've seen this dir, remove it.
if (SeenDirs.insert(CurEntry.getDir()).second)
continue;
} else if (CurEntry.isFramework()) {
- // If this isn't the first time we've seen this framework dir, remove it.
if (SeenFrameworkDirs.insert(CurEntry.getFrameworkDir()).second)
continue;
} else {
assert(CurEntry.isHeaderMap() && "Not a headermap or normal dir?");
- // If this isn't the first time we've seen this headermap, remove it.
if (SeenHeaderMaps.insert(CurEntry.getHeaderMap()).second)
continue;
}
- // If we have a normal #include dir/framework/headermap that is shadowed
- // later in the chain by a system include location, we actually want to
- // ignore the user's request and drop the user dir... keeping the system
- // dir. This is weird, but required to emulate GCC's search path correctly.
- //
- // Since dupes of system dirs are rare, just rescan to find the original
- // that we're nuking instead of using a DenseMap.
- if (CurEntry.getDirCharacteristic() != SrcMgr::C_User) {
- // Find the dir that this is the same of.
- unsigned FirstDir;
- for (FirstDir = First;; ++FirstDir) {
- assert(FirstDir != i && "Didn't find dupe?");
-
- const DirectoryLookup &SearchEntry = SearchList[FirstDir].Lookup;
-
- // If these are different lookup types, then they can't be the dupe.
- if (SearchEntry.getLookupType() != CurEntry.getLookupType())
- continue;
-
- bool isSame;
- if (CurEntry.isNormalDir())
- isSame = SearchEntry.getDir() == CurEntry.getDir();
- else if (CurEntry.isFramework())
- isSame = SearchEntry.getFrameworkDir() == CurEntry.getFrameworkDir();
- else {
- assert(CurEntry.isHeaderMap() && "Not a headermap or normal dir?");
- isSame = SearchEntry.getHeaderMap() == CurEntry.getHeaderMap();
- }
-
- if (isSame)
- break;
+ // Find the previous matching search entry.
+ unsigned PrevIndex;
+ for (PrevIndex = Part1Begin; PrevIndex < i; ++PrevIndex) {
+ const DirectoryLookup &SearchEntry = SearchList[PrevIndex].Lookup;
+
+ // Different lookup types are not considered duplicate entries.
+ if (SearchEntry.getLookupType() != CurEntry.getLookupType())
+ continue;
+
+ bool isSame;
+ if (CurEntry.isNormalDir())
+ isSame = SearchEntry.getDir() == CurEntry.getDir();
+ else if (CurEntry.isFramework())
+ isSame = SearchEntry.getFrameworkDir() == CurEntry.getFrameworkDir();
+ else {
+ assert(CurEntry.isHeaderMap() && "Not a headermap or normal dir?");
+ isSame = SearchEntry.getHeaderMap() == CurEntry.getHeaderMap();
}
- // If the first dir in the search path is a non-system dir, zap it
- // instead of the system one.
- if (SearchList[FirstDir].Lookup.getDirCharacteristic() == SrcMgr::C_User)
- DirToRemove = FirstDir;
+ if (isSame)
+ break;
+ }
+ assert(PrevIndex < i && "Expected duplicate search location not found");
+ const DirectoryLookup &PrevEntry = SearchList[PrevIndex].Lookup;
+ SrcMgr::CharacteristicKind PrevSrcKind = PrevEntry.getDirCharacteristic();
+
+ // By default, a search path that follows a previous matching search path
+ // is removed. Exceptions exist for paths from the External include group
+ // and for uesr paths that match a later system path.
+ unsigned DirToRemove = i;
+ if (CurGroup == frontend::External) {
+ // A path that matches a later path specified by -iexternal is always
+ // suppressed.
+ DirToRemove = PrevIndex;
+ } else if (!Lang.MSVCCompat && PrevSrcKind == SrcMgr::C_User &&
+ CurSrcKind != SrcMgr::C_User) {
+ // When not in Microsoft compatibility mode, a user path that matches
+ // a later system path is suppressed.
+ DirToRemove = PrevIndex;
}
+ // If requested, issue a diagnostic about the ignored directory.
if (Verbose) {
+ bool NonSystemDirRemoved = false;
+ if (DirToRemove == i)
+ NonSystemDirRemoved =
+ PrevSrcKind != SrcMgr::C_User && CurSrcKind == SrcMgr::C_User;
+ else
+ NonSystemDirRemoved =
+ PrevSrcKind == SrcMgr::C_User && CurSrcKind != SrcMgr::C_User;
+
llvm::errs() << "ignoring duplicate directory \""
<< CurEntry.getName() << "\"\n";
- if (DirToRemove != i)
+ if (NonSystemDirRemoved)
llvm::errs() << " as it is a non-system directory that duplicates "
<< "a system directory\n";
}
- if (DirToRemove != i)
- ++NonSystemRemoved;
- // This is reached if the current entry is a duplicate. Remove the
- // DirToRemove (usually the current dir).
+ // Remove the duplicate entry from the search list.
SearchList.erase(SearchList.begin()+DirToRemove);
--i;
+
+ // Adjust the partition boundaries if necessary.
+ if (DirToRemove < Part2Begin) {
+ ++NumPart1DirsRemoved;
+ --Part2Begin;
+ }
}
- return NonSystemRemoved;
+ return NumPart1DirsRemoved;
}
/// Extract DirectoryLookups from DirectoryLookupInfos.
@@ -477,22 +496,32 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) {
std::vector<DirectoryLookupInfo> SearchList;
SearchList.reserve(IncludePath.size());
- // Quoted arguments go first.
+ // Add search paths for quoted inclusion first.
for (auto &Include : IncludePath)
if (Include.Group == Quoted)
SearchList.push_back(Include);
+ // Remove duplicate search paths within the quoted inclusion list.
+ RemoveDuplicates(Lang, SearchList, 0, 0, Verbose);
+ unsigned EndQuoted = SearchList.size();
- // Deduplicate and remember index.
- RemoveDuplicates(SearchList, 0, Verbose);
- unsigned NumQuoted = SearchList.size();
-
+ // Add search paths for angled inclusion next. Note that user paths and
+ // external paths may be interleaved; though external paths are treated like
+ // system paths, they are not reordered to the end of the search list.
for (auto &Include : IncludePath)
- if (Include.Group == Angled)
+ if (Include.Group == Angled || Include.Group == External)
SearchList.push_back(Include);
+ // Add external search paths that must come at the end of the angled
+ // inclusion list.
+ for (auto &Include : IncludePath)
+ if (Include.Group == ExternalAfter)
+ SearchList.push_back(Include);
+ // Remove duplicate search paths within the angled inclusion list.
+ // This may leave paths duplicated across the quoted and angled inclusion
+ // sections.
+ RemoveDuplicates(Lang, SearchList, EndQuoted, EndQuoted, Verbose);
+ unsigned EndAngled = SearchList.size();
- RemoveDuplicates(SearchList, NumQuoted, Verbose);
- unsigned NumAngled = SearchList.size();
-
+ // Add search paths for language dependent system paths next.
for (auto &Include : IncludePath)
if (Include.Group == System || Include.Group == ExternCSystem ||
(!Lang.ObjC && !Lang.CPlusPlus && Include.Group == CSystem) ||
@@ -501,18 +530,21 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) {
(Lang.ObjC && !Lang.CPlusPlus && Include.Group == ObjCSystem) ||
(Lang.ObjC && Lang.CPlusPlus && Include.Group == ObjCXXSystem))
SearchList.push_back(Include);
-
+ // Add search paths for system paths to be searched after other system paths.
for (auto &Include : IncludePath)
if (Include.Group == After)
SearchList.push_back(Include);
- // Remove duplicates across both the Angled and System directories. GCC does
- // this and failing to remove duplicates across these two groups breaks
- // #include_next.
- unsigned NonSystemRemoved = RemoveDuplicates(SearchList, NumQuoted, Verbose);
- NumAngled -= NonSystemRemoved;
+ // Remove duplicate search paths across both the angled inclusion list and
+ // the system search paths. This duplicate removal is necessary to ensure
+ // that header lookup in #include_next directives doesn't resolve to the
+ // same file. This may result in earlier user paths being removed, and thus
+ // requires updating the EndAngled index.
+ unsigned NonSystemRemoved =
+ RemoveDuplicates(Lang, SearchList, EndQuoted, EndAngled, Verbose);
+ EndAngled -= NonSystemRemoved;
- Headers.SetSearchPaths(extractLookups(SearchList), NumQuoted, NumAngled,
+ Headers.SetSearchPaths(extractLookups(SearchList), EndQuoted, EndAngled,
mapToUserEntries(SearchList));
Headers.SetSystemHeaderPrefixes(SystemHeaderPrefixes);
@@ -521,7 +553,7 @@ void InitHeaderSearch::Realize(const LangOptions &Lang) {
if (Verbose) {
llvm::errs() << "#include \"...\" search starts here:\n";
for (unsigned i = 0, e = SearchList.size(); i != e; ++i) {
- if (i == NumQuoted)
+ if (i == EndQuoted)
llvm::errs() << "#include <...> search starts here:\n";
StringRef Name = SearchList[i].Lookup.getName();
const char *Suffix;
diff --git a/clang/test/Driver/cl-include.c b/clang/test/Driver/cl-include.c
index ca9e7db1e6f07c..df5e2aeb4d4a1a 100644
--- a/clang/test/Driver/cl-include.c
+++ b/clang/test/Driver/cl-include.c
@@ -8,36 +8,38 @@
// NOBUILTIN-NOT: "-internal-isystem" "{{.*lib.*clang.*include}}"
// RUN: env INCLUDE=/my/system/inc env EXTERNAL_INCLUDE=/my/system/inc2 %clang_cl -### -- %s 2>&1 | FileCheck %s --check-prefix=STDINC
+// STDINC: "-internal-isystem" "{{.*lib.*clang.*include}}"
// STDINC: "-internal-isystem" "/my/system/inc"
// STDINC: "-internal-isystem" "/my/system/inc2"
// -nostdinc suppresses all of %INCLUDE%, clang resource dirs, and -imsvc dirs.
// RUN: env INCLUDE=/my/system/inc env EXTERNAL_INCLUDE=/my/system/inc2 %clang_cl -nostdinc -imsvc /my/other/inc -### -- %s 2>&1 | FileCheck %s --check-prefix=NOSTDINC
// NOSTDINC: argument unused{{.*}}-imsvc
-// NOSTDINC-NOT: "-internal-isystem" "/my/system/inc"
-// NOSTDINC-NOT: "-internal-isystem" "/my/system/inc2"
// NOSTDINC-NOT: "-internal-isystem" "{{.*lib.*clang.*include}}"
// NOSTDINC-NOT: "-internal-isystem" "/my/other/inc"
+// NOSTDINC-NOT: "-internal-isystem" "/my/system/inc"
+// NOSTDINC-NOT: "-internal-isystem" "/my/system/inc2"
// /X suppresses %INCLUDE% and %EXTERNAL_INCLUDE% but not clang resource dirs, -imsvc dirs, or /external: flags.
// RUN: env INCLUDE=/my/system/inc env EXTERNAL_INCLUDE=/my/system/inc2 env FOO=/my/other/inc2 %clang_cl /X -imsvc /my/other/inc /external:env:FOO -### -- %s 2>&1 | FileCheck %s --check-prefix=SLASHX
// SLASHX-NOT: "argument unused{{.*}}-imsvc"
// SLASHX-NOT: "-internal-isystem" "/my/system/inc"
// SLASHX-NOT: "-internal-isystem" "/my/system/inc2"
+// SLASHX: "-iexternal-after" "/my/other/inc2"
// SLASHX: "-internal-isystem" "{{.*lib.*clang.*include}}"
// SLASHX: "-internal-isystem" "/my/other/inc"
-// SLASHX: "-internal-isystem" "/my/other/inc2"
-// /winsysroot suppresses %EXTERNAL_INCLUDE% but not -imsvc dirs or /external: flags.
-// RUN: env env EXTERNAL_INCLUDE=/my/system/inc env FOO=/my/other/inc2 %clang_cl /winsysroot /foo -imsvc /my/other/inc /external:env:FOO -### -- %s 2>&1 | FileCheck %s --check-prefix=SYSROOT
+// /winsysroot suppresses %INCLUDE% and %EXTERNAL_INCLUDE% but not -imsvc dirs or /external: flags.
+// RUN: env INCLUDE=/my/system/inc env EXTERNAL_INCLUDE=/my/system/inc2 env FOO=/my/other/inc2 %clang_cl /winsysroot /foo -imsvc /my/other/inc /external:env:FOO -### -- %s 2>&1 | FileCheck %s --check-prefix=SYSROOT
// SYSROOT-NOT: "argument unused{{.*}}-imsvc"
// SYSROOT-NOT: "argument unused{{.*}}/external:"
// SYSROOT-NOT: "/my/system/inc"
+// SYSROOT-NOT: "/my/system/inc2"
+// SYSROOT: "-iexternal-after" "/my/other/inc2"
// SYSROOT: "-internal-isystem" "/my/other/inc"
-// SYSROOT: "-internal-isystem" "/my/other/inc2"
// SYSROOT: "-internal-isystem" "/foo{{.*}}"
// RUN: env "FOO=/dir1;/dir2" env "BAR=/dir3" %clang_cl /external:env:FOO /external:env:BAR -### -- %s 2>&1 | FileCheck %s --check-prefix=EXTERNAL_ENV
-// EXTERNAL_ENV: "-internal-isystem" "/dir1"
-// EXTERNAL_ENV: "-internal-isystem" "/dir2"
-// EXTERNAL_ENV: "-internal-isystem" "/dir3"
+// EXTERNAL_ENV: "-iexternal-after" "/dir1"
+// EXTERNAL_ENV: "-iexternal-after" "/dir2"
+// EXTERNAL_ENV: "-iexternal-after" "/dir3"
diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c
index 8191fda97788c1..c3885c2db45778 100644
--- a/clang/test/Driver/cl-options.c
+++ b/clang/test/Driver/cl-options.c
@@ -40,7 +40,11 @@
// RUN: %clang_cl /external:Ipath -### -- %s 2>&1 | FileCheck -check-prefix=EXTERNAL_I %s
// RUN: %clang_cl /external:I path -### -- %s 2>&1 | FileCheck -check-prefix=EXTERNAL_I %s
-// EXTERNAL_I: "-isystem" "path"
+// EXTERNAL_I: "-iexternal" "path"
+
+// RUN: env EXTPATH="path1;path2" %clang_cl /external:env:EXTPATH -### -- %s 2>&1 | FileCheck -check-prefix=EXTERNAL_ENV %s
+// EXTERNAL_ENV: "-iexternal-after" "path1"
+// EXTERNAL_ENV: "-iexternal-after" "path2"
// RUN: %clang_cl /fp:fast /fp:except -### -- %s 2>&1 | FileCheck -check-prefix=fpexcept %s
// fpexcept-NOT: -funsafe-math-optimizations
@@ -443,7 +447,6 @@
// RUN: /experimental:preprocessor \
// RUN: /exportHeader /headerName:foo \
// RUN: /external:anglebrackets \
-// RUN: /external:env:var \
// RUN: /external:W0 \
// RUN: /external:W1 \
// RUN: /external:W2 \
diff --git a/clang/test/Driver/header-search-duplicates.c b/clang/test/Driver/header-search-duplicates.c
new file mode 100644
index 00000000000000..b539bcdf18e2e7
--- /dev/null
+++ b/clang/test/Driver/header-search-duplicates.c
@@ -0,0 +1,370 @@
+// Test that pruning of header search paths emulates GCC behavior when not in
+// Microsoft compatibility mode.
+// See microsoft-header-search-duplicates.c for Microsoft compatible behavior.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// This test exercises the clang driver using a target that does not implicitly
+// enable the -fms-compatibility option. The -nostdinc option is used to
+// suppress default search paths to ease testing.
+
+// Header search paths are categorized into the following general groups.
+// - Quoted: Search paths that are only used to resolve inclusion of header
+// files specified with quoted inclusion ('#include "X"'). Paths nominated
+// by the '-iquoted' option are added to this group.
+// - Angled: Search paths used to resolve inclusion of header files specified
+// with angled inclusion ('#include <X>') or quoted inclusion if a match
+// was not found in the Quoted group. Paths nominated by the '-I',
+// '-iexternal', '-iexternal-env=', and '-iwithprefixbefore' options are
+// added to this group.
+// - System: Search paths used to resolve inclusion of a header file for which
+// a match is not found in the Quoted or Angled groups. Paths nominated by
+// the '-dirafter', '-isystem', '-isystem-after', '-iwithprefix', and
+// related language specific options are added to this group.
+// Duplicate search paths are identified and processed as follows:
+// 1) Paths in the Quoted group that duplicate a previous path in the Quoted
+// group are removed.
+// 2) Paths in the Angled group that are duplicated by an external path
+// (as nominated by the '-iexternal' or '-iexternal-env=' options) in the
+// Angled group (regardless of the relative order of the paths) or by a
+// path in the System group are removed
+// 3) Paths in the Angled or System groups that duplicate a previous path in
+// the Angled or System group are removed.
+
+
+// Test 1: Validate ordering and duplicate elimination in the Quoted group.
+// This test exhibits a behavioral difference between GCC and Clang. GCC
+// removes the last path in the quoted group if it matches the first path
+// in the angled group. Clang does not. The difference is observable via
+// '#include_next' as this test demonstrates. Clang's behavior makes use of
+// '#include_next' across the Quoted and Angled groups reliable regardless
+// of whether there is an intervening search path present at the start of
+// the Angled group.
+//
+// RUN: %clang \
+// RUN: -target x86_64-unknown-linux-gnu -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -iquote %t/test1/include/x \
+// RUN: -iquote %t/test1/include/y \
+// RUN: -iquote %t/test1/include/x \
+// RUN: -iquote %t/test1/include/z \
+// RUN: -I%t/test1/include/z \
+// RUN: -I%t/test1/include/y \
+// RUN: %t/test1/t.c 2>&1 | FileCheck -DPWD=%t %t/test1/t.c
+
+#--- test1/t.c
+#include "a.h"
+#include "b.h"
+#include "c.h"
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test1/include/x"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: [[PWD]]/test1/include/x
+// CHECK-NEXT: [[PWD]]/test1/include/y
+// CHECK-NEXT: [[PWD]]/test1/include/z
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test1/include/z
+// CHECK-NEXT: [[PWD]]/test1/include/y
+// CHECK-NEXT: End of search list.
+
+#--- test1/include/x/a.h
+
+#--- test1/include/y/a.h
+#error 'test1/include/y/a.h' should not have been included!
+
+#--- test1/include/y/b.h
+#if !defined(Y_B_DEFINED)
+#define Y_B_DEFINED
+#include_next <b.h>
+#endif
+
+#--- test1/include/z/a.h
+#error 'test1/include/z/a.h' should not have been included!
+
+#--- test1/include/z/b.h
+#if !defined(Y_B_DEFINED)
+#error 'Y_B_DEFINED' is not defined in test1/include/z/b.h!
+#endif
+
+#--- test1/include/z/c.h
+#if !defined(Z_C_DEFINED)
+#define Z_C_DEFINED
+#include_next <c.h>
+#endif
+
+
+// Test 2: Validate ordering and duplicate elimination in the Angled group.
+//
+// RUN: %clang \
+// RUN: -target x86_64-unknown-linux-gnu -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -iprefix %t/ \
+// RUN: -I%t/test2/include/v \
+// RUN: -iwithprefixbefore test2/include/y \
+// RUN: -I%t/test2/include/u \
+// RUN: -iexternal %t/test2/include/v \
+// RUN: -iwithprefixbefore test2/include/z \
+// RUN: -iexternal %t/test2/include/w \
+// RUN: -I%t/test2/include/x \
+// RUN: -iexternal %t/test2/include/y \
+// RUN: -iwithprefixbefore test2/include/x \
+// RUN: %t/test2/t.c 2>&1 | FileCheck -DPWD=%t %t/test2/t.c
+
+#--- test2/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+#include <e.h>
+#include <f.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test2/include/v"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test2/include/y"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test2/include/x"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test2/include/u
+// CHECK-NEXT: [[PWD]]/test2/include/v
+// CHECK-NEXT: [[PWD]]/test2/include/w
+// CHECK-NEXT: [[PWD]]/test2/include/x
+// CHECK-NEXT: [[PWD]]/test2/include/y
+// CHECK-NEXT: [[PWD]]/test2/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test2/include/u/a.h
+
+#--- test2/include/v/a.h
+#error 'test2/include/v/a.h' should not have been included!
+
+#--- test2/include/v/b.h
+
+#--- test2/include/w/a.h
+#error 'test2/include/w/a.h' should not have been included!
+
+#--- test2/include/w/b.h
+#error 'test2/include/w/b.h' should not have been included!
+
+#--- test2/include/w/c.h
+
+#--- test2/include/x/a.h
+#error 'test2/include/x/a.h' should not have been included!
+
+#--- test2/include/x/b.h
+#error 'test2/include/x/b.h' should not have been included!
+
+#--- test2/include/x/c.h
+#error 'test2/include/x/c.h' should not have been included!
+
+#--- test2/include/x/d.h
+
+#--- test2/include/y/a.h
+#error 'test2/include/y/a.h' should not have been included!
+
+#--- test2/include/y/b.h
+#error 'test2/include/y/b.h' should not have been included!
+
+#--- test2/include/y/c.h
+#error 'test2/include/y/c.h' should not have been included!
+
+#--- test2/include/y/d.h
+#error 'test2/include/y/d.h' should not have been included!
+
+#--- test2/include/y/e.h
+
+#--- test2/include/z/a.h
+#error 'test2/include/z/a.h' should not have been included!
+
+#--- test2/include/z/b.h
+#error 'test2/include/z/b.h' should not have been included!
+
+#--- test2/include/z/c.h
+#error 'test2/include/z/c.h' should not have been included!
+
+#--- test2/include/z/d.h
+#error 'test2/include/z/d.h' should not have been included!
+
+#--- test2/include/z/e.h
+#error 'test2/include/z/e.h' should not have been included!
+
+#--- test2/include/y/f.h
+
+
+// Test 3: Validate ordering and duplicate elimination across the Angled and
+// System groups.
+//
+// RUN: %clang \
+// RUN: -target x86_64-unknown-linux-gnu -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -I%t/test3/include/y \
+// RUN: -iexternal %t/test3/include/u \
+// RUN: -I%t/test3/include/v \
+// RUN: -isystem %t/test3/include/y \
+// RUN: -iexternal %t/test3/include/w \
+// RUN: -isystem %t/test3/include/z \
+// RUN: -I%t/test3/include/x \
+// RUN: -isystem %t/test3/include/u \
+// RUN: -iexternal %t/test3/include/x \
+// RUN: %t/test3/t.c 2>&1 | FileCheck -DPWD=%t %t/test3/t.c
+
+#--- test3/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+#include <e.h>
+#include <f.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test3/include/x"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test3/include/y"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test3/include/u"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test3/include/u
+// CHECK-NEXT: [[PWD]]/test3/include/v
+// CHECK-NEXT: [[PWD]]/test3/include/w
+// CHECK-NEXT: [[PWD]]/test3/include/x
+// CHECK-NEXT: [[PWD]]/test3/include/y
+// CHECK-NEXT: [[PWD]]/test3/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test3/include/u/a.h
+
+#--- test3/include/v/a.h
+#error 'test3/include/v/a.h' should not have been included!
+
+#--- test3/include/v/b.h
+
+#--- test3/include/w/a.h
+#error 'test3/include/w/a.h' should not have been included!
+
+#--- test3/include/w/b.h
+#error 'test3/include/w/b.h' should not have been included!
+
+#--- test3/include/w/c.h
+
+#--- test3/include/x/a.h
+#error 'test3/include/x/a.h' should not have been included!
+
+#--- test3/include/x/b.h
+#error 'test3/include/x/b.h' should not have been included!
+
+#--- test3/include/x/c.h
+#error 'test3/include/x/c.h' should not have been included!
+
+#--- test3/include/x/d.h
+
+#--- test3/include/y/a.h
+#error 'test3/include/y/a.h' should not have been included!
+
+#--- test3/include/y/b.h
+#error 'test3/include/y/b.h' should not have been included!
+
+#--- test3/include/y/c.h
+#error 'test3/include/y/c.h' should not have been included!
+
+#--- test3/include/y/d.h
+#error 'test3/include/y/d.h' should not have been included!
+
+#--- test3/include/y/e.h
+
+#--- test3/include/z/a.h
+#error 'test3/include/z/a.h' should not have been included!
+
+#--- test3/include/z/b.h
+#error 'test3/include/z/b.h' should not have been included!
+
+#--- test3/include/z/c.h
+#error 'test3/include/z/c.h' should not have been included!
+
+#--- test3/include/z/d.h
+#error 'test3/include/z/d.h' should not have been included!
+
+#--- test3/include/z/e.h
+#error 'test3/include/z/e.h' should not have been included!
+
+#--- test3/include/z/f.h
+
+
+// Test 4: Validate ordering and duplicate elimination across the Angled and
+// System groups.
+//
+// RUN: env EXTRA_INCLUDE="%t/test4/include/w" \
+// RUN: %clang \
+// RUN: -target x86_64-unknown-linux-gnu -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -I%t/test4/include/z \
+// RUN: -iexternal %t/test4/include/v \
+// RUN: -iexternal-env=EXTRA_INCLUDE \
+// RUN: -isystem %t/test4/include/x \
+// RUN: -isystem %t/test4/include/y \
+// RUN: -isystem %t/test4/include/x \
+// RUN: -isystem %t/test4/include/w \
+// RUN: -isystem %t/test4/include/v \
+// RUN: -isystem %t/test4/include/z \
+// RUN: %t/test4/t.c 2>&1 | FileCheck -DPWD=%t %t/test4/t.c
+
+#--- test4/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+#include <e.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test4/include/x"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test4/include/w"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test4/include/v"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test4/include/z"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test4/include/v
+// CHECK-NEXT: [[PWD]]/test4/include/w
+// CHECK-NEXT: [[PWD]]/test4/include/x
+// CHECK-NEXT: [[PWD]]/test4/include/y
+// CHECK-NEXT: [[PWD]]/test4/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test4/include/v/a.h
+
+#--- test4/include/w/a.h
+#error 'test4/include/w/a.h' should not have been included!
+
+#--- test4/include/w/b.h
+
+#--- test4/include/x/a.h
+#error 'test4/include/x/a.h' should not have been included!
+
+#--- test4/include/x/b.h
+#error 'test4/include/x/b.h' should not have been included!
+
+#--- test4/include/x/c.h
+
+#--- test4/include/y/a.h
+#error 'test4/include/y/a.h' should not have been included!
+
+#--- test4/include/y/b.h
+#error 'test4/include/y/b.h' should not have been included!
+
+#--- test4/include/y/c.h
+#error 'test4/include/y/c.h' should not have been included!
+
+#--- test4/include/y/d.h
+
+#--- test4/include/z/a.h
+#error 'test4/include/z/a.h' should not have been included!
+
+#--- test4/include/z/b.h
+#error 'test4/include/z/b.h' should not have been included!
+
+#--- test4/include/z/c.h
+#error 'test4/include/z/c.h' should not have been included!
+
+#--- test4/include/z/d.h
+#error 'test4/include/z/d.h' should not have been included!
+
+#--- test4/include/z/e.h
diff --git a/clang/test/Driver/microsoft-header-search-duplicates.c b/clang/test/Driver/microsoft-header-search-duplicates.c
new file mode 100644
index 00000000000000..5a30e3be21ef36
--- /dev/null
+++ b/clang/test/Driver/microsoft-header-search-duplicates.c
@@ -0,0 +1,363 @@
+// Test that pruning of header search paths emulates MSVC behavior when in
+// Microsoft compatibility mode. See header-search-duplicates.c for GCC
+// compatible behavior.
+
+// This test is intended to be usable to validate MSVC behavior using a .bat
+// test driver similar to the following. A failure to compile successfully
+// would indicate a problem with the test or, perhaps, a behavioral difference
+// across MSVC versions.
+// @echo on
+// setlocal
+// rd /s/q test-msvc
+// split-file microsoft-header-search-duplicates.c test-msvc
+// pushd test-msvc
+// REM Validate test 1:
+// set INCLUDE=...
+// set EXTERNAL_INCLUDE=...
+// set EXTRA_INCLUDE=...
+// cl.exe /c /showIncludes ... test1.c
+// popd
+
+// This test exercises both the clang and clang-cl drivers using a target that
+// implicitly enables the '-fms-compatibility' option. The '-nobuiltininc',
+// '-nostdinc', and '/X ('-nostdlibinc') options are used to suppress implicit
+// header search paths to ease testing.
+
+// Header search paths are processed as follows:
+// 1) Paths specified by the '/I' and '/external:I' options are processed in
+// order.
+// 1.1) Paths specified by '/I' that duplicate a path specified by
+// '/external:I' are ignored regardless of the option order.
+// 1.2) Paths specified by '/I' that duplicate a prior '/I' option are
+// ignored.
+// 1.3) Paths specified by '/external:I' that duplicate a later
+// '/external:I' option are ignored.
+// 2) Paths specified by the '/external:env' options are processed in order.
+// Paths that duplicate a path from step 1, a prior '/external:env' option,
+// or a prior path from the current '/external:env' option are ignored.
+// 3) Paths specified by the 'INCLUDE' environment variable are processed in
+// order. Paths that duplicate a path from step 1, step 2, or an earlier
+// path in the 'INCLUDE' environment variable are ignored.
+// 4) Paths specified by the 'EXTERNALINCLUDE' environment variable are
+// processed in order. Paths that duplicate a path from step 1, step 2,
+// step 3, or an earlier path in the 'EXTERNAL_INCLUDE' environment
+// variable are ignored.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+
+// Test 1: Validate ordering and duplicate elimination for /I.
+//
+// RUN: %clang \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -I%t/test1/include/y \
+// RUN: -I%t/test1/include/z \
+// RUN: -I%t/test1/include/y \
+// RUN: %t/test1/t.c 2>&1 | FileCheck -DPWD=%t %t/test1/t.c
+// RUN: %clang_cl \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nobuiltininc /X \
+// RUN: /I%t/test1/include/y \
+// RUN: /I%t/test1/include/z \
+// RUN: /I%t/test1/include/y \
+// RUN: %t/test1/t.c 2>&1 | FileCheck -DPWD=%t %t/test1/t.c
+
+#--- test1/t.c
+#include <a.h>
+#include <b.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test1/include/y"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test1/include/y
+// CHECK-NEXT: [[PWD]]/test1/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test1/include/y/a.h
+
+#--- test1/include/z/a.h
+#error 'test1/include/z/a.h' should not have been included!
+
+#--- test1/include/z/b.h
+
+
+// Test 2: Validate ordering and duplicate elimination for /external:I.
+//
+// RUN: %clang \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -iexternal %t/test2/include/z \
+// RUN: -iexternal %t/test2/include/y \
+// RUN: -iexternal %t/test2/include/z \
+// RUN: %t/test2/t.c 2>&1 | FileCheck -DPWD=%t %t/test2/t.c
+// RUN: %clang_cl \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nobuiltininc /X \
+// RUN: /external:I %t/test2/include/z \
+// RUN: /external:I %t/test2/include/y \
+// RUN: /external:I %t/test2/include/z \
+// RUN: %t/test2/t.c 2>&1 | FileCheck -DPWD=%t %t/test2/t.c
+
+#--- test2/t.c
+#include <a.h>
+#include <b.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test2/include/z"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test2/include/y
+// CHECK-NEXT: [[PWD]]/test2/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test2/include/y/a.h
+
+#--- test2/include/z/a.h
+#error 'test2/include/z/a.h' should not have been included!
+
+#--- test2/include/z/b.h
+
+
+// Test 3: Validate ordering and duplicate elimination for /I vs /external:I.
+//
+// RUN: %clang \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -iexternal %t/test3/include/w \
+// RUN: -I%t/test3/include/z \
+// RUN: -I%t/test3/include/x \
+// RUN: -I%t/test3/include/w \
+// RUN: -iexternal %t/test3/include/y \
+// RUN: -iexternal %t/test3/include/z \
+// RUN: %t/test3/t.c 2>&1 | FileCheck -DPWD=%t %t/test3/t.c
+// RUN: %clang_cl \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nobuiltininc /X \
+// RUN: /external:I %t/test3/include/w \
+// RUN: /I%t/test3/include/z \
+// RUN: /I%t/test3/include/x \
+// RUN: /I%t/test3/include/w \
+// RUN: /external:I %t/test3/include/y \
+// RUN: /external:I %t/test3/include/z \
+// RUN: %t/test3/t.c 2>&1 | FileCheck -DPWD=%t %t/test3/t.c
+
+#--- test3/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test3/include/w"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test3/include/z"
+// CHECK-NEXT: as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test3/include/w
+// CHECK-NEXT: [[PWD]]/test3/include/x
+// CHECK-NEXT: [[PWD]]/test3/include/y
+// CHECK-NEXT: [[PWD]]/test3/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test3/include/w/a.h
+
+#--- test3/include/x/a.h
+#error 'test3/include/x/a.h' should not have been included!
+
+#--- test3/include/x/b.h
+
+#--- test3/include/y/a.h
+#error 'test3/include/y/a.h' should not have been included!
+
+#--- test3/include/y/b.h
+#error 'test3/include/y/b.h' should not have been included!
+
+#--- test3/include/y/c.h
+
+#--- test3/include/z/a.h
+#error 'test3/include/z/a.h' should not have been included!
+
+#--- test3/include/z/b.h
+#error 'test3/include/z/b.h' should not have been included!
+
+#--- test3/include/z/c.h
+#error 'test3/include/z/c.h' should not have been included!
+
+#--- test3/include/z/d.h
+
+
+// Test 4: Validate ordering and duplicate elimination for /external:env.
+//
+// RUN: env EXTRA_INCLUDE1="%t/test4/include/y" \
+// RUN: env EXTRA_INCLUDE2="%t/test4/include/z;%t/test4/include/y;%t/test4/include/x;%t/test4/include/w" \
+// RUN: %clang \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -I%t/test4/include/w \
+// RUN: -iexternal %t/test4/include/x \
+// RUN: -iexternal-env=EXTRA_INCLUDE1 \
+// RUN: -iexternal-env=EXTRA_INCLUDE2 \
+// RUN: %t/test4/t.c 2>&1 | FileCheck -DPWD=%t %t/test4/t.c
+// RUN: env EXTRA_INCLUDE1="%t/test4/include/y" \
+// RUN: env EXTRA_INCLUDE2="%t/test4/include/z;%t/test4/include/y;%t/test4/include/x;%t/test4/include/w" \
+// RUN: %clang_cl \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nobuiltininc /X \
+// RUN: /I%t/test4/include/w \
+// RUN: /external:I %t/test4/include/x \
+// RUN: /external:env:EXTRA_INCLUDE1 \
+// RUN: /external:env:EXTRA_INCLUDE2 \
+// RUN: %t/test4/t.c 2>&1 | FileCheck -DPWD=%t %t/test4/t.c
+
+#--- test4/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test4/include/y"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test4/include/x"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test4/include/w"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test4/include/w
+// CHECK-NEXT: [[PWD]]/test4/include/x
+// CHECK-NEXT: [[PWD]]/test4/include/y
+// CHECK-NEXT: [[PWD]]/test4/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test4/include/w/a.h
+
+#--- test4/include/x/a.h
+#error 'test4/include/x/a.h' should not have been included!
+
+#--- test4/include/x/b.h
+
+#--- test4/include/y/a.h
+#error 'test4/include/y/a.h' should not have been included!
+
+#--- test4/include/y/b.h
+#error 'test4/include/y/b.h' should not have been included!
+
+#--- test4/include/y/c.h
+
+#--- test4/include/z/a.h
+#error 'test4/include/z/a.h' should not have been included!
+
+#--- test4/include/z/b.h
+#error 'test4/include/z/b.h' should not have been included!
+
+#--- test4/include/z/c.h
+#error 'test4/include/z/c.h' should not have been included!
+
+#--- test4/include/z/d.h
+
+
+// Test 5: Validate ordering and duplicate elimination for the INCLUDE and
+// EXTERNAL_INCLUDE environment variables.
+//
+// RUN: env EXTRA_INCLUDE="%t/test5/include/w" \
+// RUN: env INCLUDE="%t/test5/include/x;%t/test5/include/y;%t/test5/include/w;%t/test5/include/v;%t/test5/include/u" \
+// RUN: env EXTERNAL_INCLUDE="%t/test5/include/z;%t/test5/include/y;%t/test5/include/w;%t/test5/include/v;%t/test5/include/u" \
+// RUN: %clang \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nostdinc \
+// RUN: -I%t/test5/include/u \
+// RUN: -iexternal %t/test5/include/v \
+// RUN: -iexternal-env=EXTRA_INCLUDE \
+// RUN: -iexternal-env=INCLUDE \
+// RUN: -iexternal-env=EXTERNAL_INCLUDE \
+// RUN: %t/test5/t.c 2>&1 | FileCheck -DPWD=%t %t/test5/t.c
+// RUN: env EXTRA_INCLUDE="%t/test5/include/w" \
+// RUN: env INCLUDE="%t/test5/include/x;%t/test5/include/y;%t/test5/include/w;%t/test5/include/v;%t/test5/include/u" \
+// RUN: env EXTERNAL_INCLUDE="%t/test5/include/z;%t/test5/include/y;%t/test5/include/w;%t/test5/include/v;%t/test5/include/u" \
+// RUN: %clang_cl \
+// RUN: -target x86_64-pc-windows -v -fsyntax-only \
+// RUN: -nobuiltininc \
+// RUN: /I%t/test5/include/u \
+// RUN: /external:I %t/test5/include/v \
+// RUN: /external:env:EXTRA_INCLUDE \
+// RUN: %t/test5/t.c 2>&1 | FileCheck -DPWD=%t %t/test5/t.c
+
+#--- test5/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+#include <e.h>
+#include <f.h>
+
+// CHECK: ignoring duplicate directory "[[PWD]]/test5/include/w"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test5/include/v"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test5/include/u"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test5/include/y"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test5/include/w"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test5/include/v"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test5/include/u"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test5/include/u
+// CHECK-NEXT: [[PWD]]/test5/include/v
+// CHECK-NEXT: [[PWD]]/test5/include/w
+// CHECK-NEXT: [[PWD]]/test5/include/x
+// CHECK-NEXT: [[PWD]]/test5/include/y
+// CHECK-NEXT: [[PWD]]/test5/include/z
+// CHECK-NEXT: End of search list.
+
+#--- test5/include/u/a.h
+
+#--- test5/include/v/a.h
+#error 'test5/include/v/a.h' should not have been included!
+
+#--- test5/include/v/b.h
+
+#--- test5/include/w/a.h
+#error 'test5/include/w/a.h' should not have been included!
+
+#--- test5/include/w/b.h
+#error 'test5/include/w/b.h' should not have been included!
+
+#--- test5/include/w/c.h
+
+#--- test5/include/x/a.h
+#error 'test5/include/x/a.h' should not have been included!
+
+#--- test5/include/x/b.h
+#error 'test5/include/x/b.h' should not have been included!
+
+#--- test5/include/x/c.h
+#error 'test5/include/x/c.h' should not have been included!
+
+#--- test5/include/x/d.h
+
+#--- test5/include/y/a.h
+#error 'test5/include/y/a.h' should not have been included!
+
+#--- test5/include/y/b.h
+#error 'test5/include/y/b.h' should not have been included!
+
+#--- test5/include/y/c.h
+#error 'test5/include/y/c.h' should not have been included!
+
+#--- test5/include/y/d.h
+#error 'test5/include/y/d.h' should not have been included!
+
+#--- test5/include/y/e.h
+
+#--- test5/include/z/a.h
+#error 'test5/include/z/a.h' should not have been included!
+
+#--- test5/include/z/b.h
+#error 'test5/include/z/b.h' should not have been included!
+
+#--- test5/include/z/c.h
+#error 'test5/include/z/c.h' should not have been included!
+
+#--- test5/include/z/d.h
+#error 'test5/include/z/d.h' should not have been included!
+
+#--- test5/include/z/e.h
+#error 'test5/include/z/e.h' should not have been included!
+
+#--- test5/include/z/f.h
diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h
index 09812f976d0166..77703910b3d26b 100644
--- a/llvm/include/llvm/Option/ArgList.h
+++ b/llvm/include/llvm/Option/ArgList.h
@@ -288,7 +288,12 @@ class ArgList {
/// getAllArgValues - Get the values of all instances of the given argument
/// as strings.
- std::vector<std::string> getAllArgValues(OptSpecifier Id) const;
+ template <typename... OptSpecifiers>
+ std::vector<std::string> getAllArgValues(OptSpecifiers... Ids) const {
+ SmallVector<const char *, 16> Values;
+ AddAllArgValues(Values, Ids...);
+ return std::vector<std::string>(Values.begin(), Values.end());
+ }
/// @}
/// @name Translation Utilities
diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp
index 6e164150d2e5e9..3bbff6c75e78aa 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -95,12 +95,6 @@ StringRef ArgList::getLastArgValue(OptSpecifier Id, StringRef Default) const {
return Default;
}
-std::vector<std::string> ArgList::getAllArgValues(OptSpecifier Id) const {
- SmallVector<const char *, 16> Values;
- AddAllArgValues(Values, Id);
- return std::vector<std::string>(Values.begin(), Values.end());
-}
-
void ArgList::addOptInFlag(ArgStringList &Output, OptSpecifier Pos,
OptSpecifier Neg) const {
if (Arg *A = getLastArg(Pos, Neg))
More information about the cfe-commits
mailing list