[clang] [llvm] [Clang] Match MSVC handling of duplicate header search paths in Microsoft compatibility modes. (PR #105738)

Tom Honermann via llvm-commits llvm-commits at lists.llvm.org
Thu Jan 16 18:55:21 PST 2025


https://github.com/tahonermann updated https://github.com/llvm/llvm-project/pull/105738

>From 9dc8727fad50d7a808fc14173189105675cb46c9 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] Support for MSVC compatible header search path
 ordering.

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 MSVC `/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.

Paths specified by the MSVC `/external:I` or `/external:env` options or by the
`EXTERNAL_INCLUDE` environment variable are not just used to nominate a header
search path. MSVC also uses these paths as external directory prefixes to mark
paths that are to be treated as system directories. When a header file is
resolved to a path for which one of these paths is a prefix match, that header
file is treated as a system header regardless of whether the header file was
resolved against a `/I` specified path. Note that it is not necessary for the
final path component of the external path to match a directory in the filesystem
in order to be used as an external directory prefix, though trailing path
separators are significant. For example:

   Include directive         Command line         System header
   ------------------------  ------------------   -------------
   #include <foobar/file.h>  /I. /external:Ifoo   Yes
   #include <foobar/file.h>  /I. /external:Ifoo\  No

This change adds support for the MSVC external path concept through the addition
of new driver options with the following option syntax.
   clang                     clang-cl
   ------------------------  -------------------
   -iexternal <dir>          /external:I <dir>
   -iexternal-env=<ENV>      /external:env:<ENV>

Paths specified by these options are 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. Note that the
MSVC `/external:W<N>` options are mapped to these options.

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, and Clang's
historical behavior, of suppressing user paths that match a system path
regardless of how each is specified. In order to support both behaviors, the
following driver option has been added. The option arguments shown reflect the
default behavior for each driver.

   clang                     clang-cl
   ------------------------  -------------------------
   -fheader-search=gcc       -fheader-search=microsoft

Use of the MSVC compatible header search path order by default for `clang-cl`
is a change in behavior with potential to cause problems for some projects that
build with `clang-cl`. Potentially impacted projects include those that specify
a header search path via either the `/I` option or in the `CPATH` environment
variable, but rely on the path being suppressed and/or treated as a system
header due to a duplicate path specified by another MSVC option or environment
variable or by the `-imsvc` option or one of the `-isystem` family of options.
Such projects can pass the `-fheader-search=gcc` option in their `clang-cl`
invocations to (mostly) restore previous behavior.

Clang emulates the MSVC behavior of resolving quoted header file inclusions
(e.g., `#include "file.h"`) by searching for a matching file in the directories
for the including files in the include stack. See Microsoft's documentation of
this feature in the Microsoft-specific section of
https://learn.microsoft.com/en-us/cpp/preprocessor/hash-include-directive-c-cpp.
Previously, this behavior was implicitly enabled when the `-fms-compatibility`
option is specified (implicitly for Windows targets). This change ties this
behavior to the `-fheader-search=microsoft` option instead. Projects that use
the `clang` (not `clang-cl`) driver to build code targeting Windows that depend
on this local header file lookup may be impacted by this change. Such projects
can try adding `-fheader-search=microsoft` to their `clang` invocations to
restore the prior behavior.

There are at least two behaviors that MSVC exhibits that are not currently
replicated with these changes.
1) Clang does not preserve trailing path separators for include paths. MSVC
   does preserve them and uses them when performing external directory prefix
   matches against header file paths; when present, the final path component
   must match a complete path component in the header file path, not just a
   prefix. Since Clang does not preserve trailing path separators, prefix
   matches can succeed when they should not. This behavior is exercised by
   test 8 in clang/test/Driver/microsoft-header-search-duplicates.c.
2) Clang treats paths specified in the `INCLUDE` environment as system
   directories; MSVC does not. Clang will therefore suppress warnings in
   header files found via these paths by default while MSVC will not. However,
   recent MSVC releases set the `EXTERNAL_INCLUDE` environment variable to have
   the same paths as `INCLUDE` by default, so, for recent releases, MSVC
   appears to suppress warnings for header files found via `INCLUDE` by
   default when it does not. Note that many Microsoft provided header files
   use `#pragma` directives to suppress warnings.
---
 clang/docs/ReleaseNotes.rst                   |  43 ++
 clang/docs/UsersManual.rst                    |  49 +-
 clang/include/clang/Driver/Options.td         |  38 +-
 clang/include/clang/Driver/ToolChain.h        |  18 +-
 clang/include/clang/Lex/HeaderSearch.h        |  11 +
 clang/include/clang/Lex/HeaderSearchOptions.h |  18 +
 clang/lib/Driver/Driver.cpp                   |   5 +-
 clang/lib/Driver/ToolChain.cpp                |  68 +-
 clang/lib/Driver/ToolChains/Clang.cpp         |  24 +-
 clang/lib/Driver/ToolChains/MSVC.cpp          |  29 +-
 clang/lib/Frontend/CompilerInvocation.cpp     |  71 +-
 clang/lib/Lex/HeaderSearch.cpp                |  58 ++
 clang/lib/Lex/InitHeaderSearch.cpp            | 187 +++---
 clang/lib/Lex/PPDirectives.cpp                |   5 +-
 clang/lib/Serialization/ASTReader.cpp         |   2 +-
 clang/lib/Serialization/ASTWriter.cpp         |   1 +
 clang/test/Driver/cl-include.c                |  36 +-
 clang/test/Driver/cl-options.c                |   7 +-
 clang/test/Driver/header-search-duplicates.c  | 369 +++++++++++
 .../microsoft-header-search-duplicates.c      | 609 ++++++++++++++++++
 .../microsoft-header-search-fail.c            |   2 +-
 .../Preprocessor/microsoft-header-search.c    |   2 +-
 llvm/include/llvm/Option/ArgList.h            |   7 +-
 llvm/lib/Option/ArgList.cpp                   |   6 -
 24 files changed, 1487 insertions(+), 178 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 eb3a271fa59d02a..e9b0743973255d8 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -1110,6 +1110,49 @@ 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 run via the ``clang-cl`` driver (by default) or with the
+  ``-fheader-search=microsoft`` option otherwise. 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
+  ``-fheader-search=microsoft`` 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.
+
+  The ``-fheader-search=gcc`` option can be used to opt in to GCC duplicate
+  header search path handling (which remains the default behavior for the GCC
+  compatible drivers).
+
 LoongArch Support
 ^^^^^^^^^^^^^^^^^
 
diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 260e84910c6f783..698ac26db30508a 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -5196,12 +5196,26 @@ follows:
 
 2. Consult the environment.
 
-    TODO: This is not yet implemented.
+    - `/external:env:[VARIABLE]`
 
-    This will consult the environment variables:
+      This command line option specifies a user identified environment variable
+      which is treated as a path delimited (`;`) list of system header paths.
 
-    - `WindowsSdkDir`
-    - `UCRTVersion`
+    - `INCLUDE`
+
+      This environment variable is treated as a path delimited (`;`) list of
+      system header paths.
+
+    - `EXTERNAL_INCLUDE`
+
+      This environment variable is treated as a path delimited (`;`) list of
+      system header paths.
+
+    The following environment variables will be consulted and used to form paths
+    to validate and load content from as appropriate:
+
+      - `WindowsSdkDir`
+      - `UCRTVersion`
 
 3. Fallback to the registry.
 
@@ -5213,6 +5227,8 @@ The Visual C++ Toolset has a slightly more elaborate mechanism for detection.
 
 1. Consult the command line.
 
+    Anything the user specifies is always given precedence.
+
     - `/winsysroot:`
 
     The `/winsysroot:` is used as an equivalent to `-sysroot` on Unix
@@ -5230,21 +5246,30 @@ The Visual C++ Toolset has a slightly more elaborate mechanism for detection.
 
 2. Consult the environment.
 
-    - `/external:[VARIABLE]`
+    - `/external:env:[VARIABLE]`
+
+      This command line option specifies a user identified environment variable
+      which is treated as a path delimited (`;`) list of external header search
+      paths. Additionally, any header search path provided by other means that
+      has a prefix that matches one of these paths is treated as an external
+      header search path.
+
+    - `INCLUDE`
 
-      This specifies a user identified environment variable which is treated as
-      a path delimiter (`;`) separated list of paths to map into `-imsvc`
-      arguments which are treated as `-isystem`.
+      This environment variable is treated as a path delimited (`;`) list of
+      header search paths.
 
-    - `INCLUDE` and `EXTERNAL_INCLUDE`
+    - `EXTERNAL_INCLUDE`
 
-      The path delimiter (`;`) separated list of paths will be mapped to
-      `-imsvc` arguments which are treated as `-isystem`.
+      This environment variable is treated as a path delimited (`;`) list of
+      external header search paths. Additionally, any header search path
+      provided by other means that has a prefix that matches one of these paths
+      is treated as an external header search path.
 
     - `LIB` (indirectly)
 
       The linker `link.exe` or `lld-link.exe` will honour the environment
-      variable `LIB` which is a path delimiter (`;`) set of paths to consult for
+      variable `LIB` which is a path delimited (`;`) set of paths to consult for
       the import libraries to use when linking the final target.
 
     The following environment variables will be consulted and used to form paths
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index d38dd2b4e3cf09f..5328f6418a74752 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -4622,6 +4622,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_system : Separate<["-"], "iexternal-system">, Group<clang_i_Group>,
+  Visibility<[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">;
@@ -4664,6 +4673,10 @@ def isysroot : JoinedOrSeparate<["-"], "isysroot">, Group<clang_i_Group>,
 def isystem : JoinedOrSeparate<["-"], "isystem">, Group<clang_i_Group>,
   Visibility<[ClangOption, CC1Option]>,
   HelpText<"Add directory to SYSTEM include search path">, MetaVarName<"<directory>">;
+def isystem_env_EQ : Joined<["-"], "isystem-env=">, Group<clang_i_Group>,
+  MetaVarName<"<var>">,
+  Visibility<[ClangOption]>,
+  HelpText<"Add directoires in env var <var> to SYSTEM include search path">;
 def isystem_after : JoinedOrSeparate<["-"], "isystem-after">,
   Group<clang_i_Group>, Flags<[NoXarchOption]>, MetaVarName<"<directory>">,
   HelpText<"Add directory to end of the SYSTEM include search path">;
@@ -8281,6 +8294,17 @@ def fexperimental_max_bitint_width_EQ:
 // Header Search Options
 //===----------------------------------------------------------------------===//
 
+let Visibility = [ClangOption, CC1Option, CLOption] in {
+
+def fheader_search : Joined<["-"], "fheader-search=">, Group<clang_i_Group>,
+  HelpText<"Specify the method used to resolve included header files">,
+  Values<"gcc,microsoft">,
+  NormalizedValuesScope<"clang::HeaderSearchMode">,
+  NormalizedValues<["GCC", "Microsoft"]>,
+  MarshallingInfoEnum<HeaderSearchOpts<"Mode">, "GCC">;
+
+} // let Visibility = [ClangOption, CC1Option, CLOption]
+
 let Visibility = [CC1Option] in {
 
 def nostdsysteminc : Flag<["-"], "nostdsysteminc">,
@@ -8309,6 +8333,12 @@ def internal_isystem : Separate<["-"], "internal-isystem">,
   HelpText<"Add directory to the internal system include search path; these "
            "are assumed to not be user-provided and are used to model system "
            "and standard headers' paths.">;
+def internal_iexternal_system : Separate<["-"], "internal-iexternal-system">,
+  MetaVarName<"<directory>">,
+  HelpText<"Add directory to the internal system include search path with "
+           "external directory prefix semantics; these are assumed to not be "
+           "user-provided and are used to model system and standard headers' "
+           "paths.">;
 def internal_externc_isystem : Separate<["-"], "internal-externc-isystem">,
   MetaVarName<"<directory>">,
   HelpText<"Add directory to the internal system include search path with "
@@ -8552,9 +8582,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"]>;
@@ -8790,9 +8823,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 7d1d8feebf35e34..25846ad63079577 100644
--- a/clang/include/clang/Driver/ToolChain.h
+++ b/clang/include/clang/Driver/ToolChain.h
@@ -224,11 +224,12 @@ 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,
                                llvm::opt::ArgStringList &CC1Args,
-                               const Twine &Path);
+                               const Twine &Path, bool Internal = true);
   static void addExternCSystemInclude(const llvm::opt::ArgList &DriverArgs,
                                       llvm::opt::ArgStringList &CC1Args,
                                       const Twine &Path);
@@ -238,13 +239,24 @@ class ToolChain {
                                       const Twine &Path);
   static void addSystemIncludes(const llvm::opt::ArgList &DriverArgs,
                                 llvm::opt::ArgStringList &CC1Args,
-                                ArrayRef<StringRef> Paths);
+                                ArrayRef<StringRef> Paths,
+                                bool Internal = true);
+  static bool addSystemIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
+                                       llvm::opt::ArgStringList &CC1Args,
+                                       StringRef Var, bool Internal = true);
+  static void addExternalSystemIncludes(const llvm::opt::ArgList &DriverArgs,
+                                        llvm::opt::ArgStringList &CC1Args,
+                                        ArrayRef<StringRef> Paths,
+                                        bool Internal = true);
+  static bool
+  addExternalSystemIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
+                                   llvm::opt::ArgStringList &CC1Args,
+                                   StringRef Var, bool Internal = true);
 
   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/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h
index a10adae17998b59..600979f74450715 100644
--- a/clang/include/clang/Lex/HeaderSearch.h
+++ b/clang/include/clang/Lex/HeaderSearch.h
@@ -276,6 +276,14 @@ class HeaderSearch {
   /// a system header.
   std::vector<std::pair<std::string, bool>> SystemHeaderPrefixes;
 
+  /// External directories are user specified directories that are to be treated
+  /// like system directories for the purposes of warning suppression. A header
+  /// file that has a path that matches one of these prefixes is promoted to a
+  /// system header regardless of which header search path was used to resolve
+  /// the \#include directive. These paths are normalized using
+  /// llvm::sys::fs::real_path().
+  llvm::StringSet<llvm::BumpPtrAllocator> ExternalDirectoryPrefixes;
+
   /// The hash used for module cache paths.
   std::string ModuleHash;
 
@@ -387,6 +395,9 @@ class HeaderSearch {
     SearchDirsUsage.push_back(false);
   }
 
+  /// Add an additional external directory prefix path.
+  bool AddExternalDirectoryPrefix(StringRef Path);
+
   /// Set the list of system header prefixes.
   void SetSystemHeaderPrefixes(ArrayRef<std::pair<std::string, bool>> P) {
     SystemHeaderPrefixes.assign(P.begin(), P.end());
diff --git a/clang/include/clang/Lex/HeaderSearchOptions.h b/clang/include/clang/Lex/HeaderSearchOptions.h
index 7a16926c186d2c2..a65106394cfd7c4 100644
--- a/clang/include/clang/Lex/HeaderSearchOptions.h
+++ b/clang/include/clang/Lex/HeaderSearchOptions.h
@@ -35,9 +35,19 @@ enum IncludeDirGroup {
   /// Paths for '\#include <>' added by '-I'.
   Angled,
 
+  /// Like Angled, but marks the directory as an external directory prefix.
+  /// This group is intended to match the semantics of the MSVC /external:I
+  /// option.
+  External,
+
   /// Like Angled, but marks system directories.
   System,
 
+  /// Like System, but marks the directory as an external directory prefix.
+  /// This group is intended to match the semantics of the MSVC
+  /// /external:env option.
+  ExternalSystem,
+
   /// Like System, but headers are implicitly wrapped in extern "C".
   ExternCSystem,
 
@@ -59,6 +69,11 @@ enum IncludeDirGroup {
 
 } // namespace frontend
 
+/// HeaderSearchMode - The method used to resolve included headers to files.
+/// This controls the order in which include paths are searched and how
+/// duplicate search paths are handled.
+enum class HeaderSearchMode { GCC, Microsoft };
+
 /// HeaderSearchOptions - Helper class for storing options related to the
 /// initialization of the HeaderSearch object.
 class HeaderSearchOptions {
@@ -93,6 +108,9 @@ class HeaderSearchOptions {
         : Prefix(Prefix), IsSystemHeader(IsSystemHeader) {}
   };
 
+  /// The header search mode to use.
+  HeaderSearchMode Mode = HeaderSearchMode::GCC;
+
   /// If non-empty, the directory to use as a "virtual system root" for include
   /// paths.
   std::string Sysroot;
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 87855fdb7997105..e1db84e8cab988f 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1479,7 +1479,10 @@ 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-system since those options may be used to specify external
+  // directory prefixes that don't necessarily match an existing path.
   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 acf9d264d631b33..0a280f050ca6a97 100644
--- a/clang/lib/Driver/ToolChain.cpp
+++ b/clang/lib/Driver/ToolChain.cpp
@@ -1276,8 +1276,11 @@ ToolChain::CXXStdlibType ToolChain::GetCXXStdlibType(const ArgList &Args) const{
 /// Utility function to add a system include directory to CC1 arguments.
 /*static*/ void ToolChain::addSystemInclude(const ArgList &DriverArgs,
                                             ArgStringList &CC1Args,
-                                            const Twine &Path) {
-  CC1Args.push_back("-internal-isystem");
+                                            const Twine &Path, bool Internal) {
+  if (Internal)
+    CC1Args.push_back("-internal-isystem");
+  else
+    CC1Args.push_back("-isystem");
   CC1Args.push_back(DriverArgs.MakeArgString(Path));
 }
 
@@ -1306,13 +1309,70 @@ void ToolChain::addExternCSystemIncludeIfExists(const ArgList &DriverArgs,
 /// Utility function to add a list of system include directories to CC1.
 /*static*/ void ToolChain::addSystemIncludes(const ArgList &DriverArgs,
                                              ArgStringList &CC1Args,
-                                             ArrayRef<StringRef> Paths) {
+                                             ArrayRef<StringRef> Paths,
+                                             bool Internal) {
   for (const auto &Path : Paths) {
-    CC1Args.push_back("-internal-isystem");
+    if (Internal)
+      CC1Args.push_back("-internal-isystem");
+    else
+      CC1Args.push_back("-isystem");
     CC1Args.push_back(DriverArgs.MakeArgString(Path));
   }
 }
 
+/// Utility function to add a list of environment path separator 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,
+                                                    bool Internal) {
+  if (auto Val = llvm::sys::Process::GetEnv(Var)) {
+    SmallVector<StringRef, 8> Dirs;
+    StringRef(*Val).split(Dirs, llvm::sys::EnvPathSeparator, /*MaxSplit=*/-1,
+                          /*KeepEmpty=*/false);
+    if (!Dirs.empty()) {
+      addSystemIncludes(DriverArgs, CC1Args, Dirs, Internal);
+      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::addExternalSystemIncludes(const ArgList &DriverArgs,
+                                                     ArgStringList &CC1Args,
+                                                     ArrayRef<StringRef> Paths,
+                                                     bool Internal) {
+  for (const auto &Path : Paths) {
+    if (Internal)
+      CC1Args.push_back("-internal-iexternal-system");
+    else
+      CC1Args.push_back("-iexternal-system");
+    CC1Args.push_back(DriverArgs.MakeArgString(Path));
+  }
+}
+
+/// Utility function to add a list of environment path separator 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::addExternalSystemIncludesFromEnv(const ArgList &DriverArgs,
+                                            ArgStringList &CC1Args,
+                                            StringRef Var, bool Internal) {
+  if (auto Val = llvm::sys::Process::GetEnv(Var)) {
+    SmallVector<StringRef, 8> Dirs;
+    StringRef(*Val).split(Dirs, llvm::sys::EnvPathSeparator, /*MaxSplit=*/-1,
+                          /*KeepEmpty=*/false);
+    if (!Dirs.empty()) {
+      addExternalSystemIncludes(DriverArgs, CC1Args, Dirs, Internal);
+      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 33f08cf28feca18..b9c959013fa0b6b 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -1216,6 +1216,19 @@ 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_isystem_env_EQ)) {
+      A->claim();
+      ToolChain::addSystemIncludesFromEnv(Args, CmdArgs, A->getValue(),
+                                          /*Internal*/ false);
+      continue;
+    } else if (A->getOption().matches(options::OPT_iexternal_env_EQ)) {
+      A->claim();
+      ToolChain::addExternalSystemIncludesFromEnv(Args, CmdArgs, A->getValue(),
+                                                  /*Internal*/ false);
+      continue;
     }
 
     // Not translated, render as usual.
@@ -1223,9 +1236,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.
 
@@ -8421,6 +8434,9 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
 
   ProcessVSRuntimeLibrary(getToolChain(), Args, CmdArgs);
 
+  if (!Args.hasArg(options::OPT_fheader_search))
+    CmdArgs.push_back("-fheader-search=microsoft");
+
   if (Arg *ShowIncludes =
           Args.getLastArg(options::OPT__SLASH_showIncludes,
                           options::OPT__SLASH_showIncludes_user)) {
@@ -8731,7 +8747,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 bae41fc06c03698..0b0c0676c54961e 100644
--- a/clang/lib/Driver/ToolChains/MSVC.cpp
+++ b/clang/lib/Driver/ToolChains/MSVC.cpp
@@ -659,24 +659,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)) {
@@ -693,12 +675,15 @@ 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 |= addExternalSystemIncludesFromEnv(DriverArgs, CC1Args,
+                                              "EXTERNAL_INCLUDE");
     if (Found)
       return;
   }
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 58658dedbaf1ee4..0d05293df087294 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -3268,14 +3268,17 @@ 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.");
     }();
 
@@ -3301,10 +3304,16 @@ static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts,
     GenerateArg(Consumer, OPT_idirafter, It->Path);
   for (; It < End && Matches(*It, {frontend::Quoted}, false, true); ++It)
     GenerateArg(Consumer, OPT_iquote, It->Path);
-  for (; It < End && Matches(*It, {frontend::System}, false, std::nullopt);
-       ++It)
-    GenerateArg(Consumer, It->IgnoreSysRoot ? OPT_isystem : OPT_iwithsysroot,
-                It->Path);
+  for (; It < End && Matches(*It, {frontend::System, frontend::ExternalSystem},
+                             false, std::nullopt);
+       ++It) {
+    OptSpecifier Opt = OPT_isystem;
+    if (It->Group == frontend::ExternalSystem)
+      Opt = OPT_iexternal_system;
+    else if (!It->IgnoreSysRoot)
+      Opt = OPT_iwithsysroot;
+    GenerateArg(Consumer, Opt, It->Path);
+  }
   for (; It < End && Matches(*It, {frontend::System}, true, true); ++It)
     GenerateArg(Consumer, OPT_iframework, It->Path);
   for (; It < End && Matches(*It, {frontend::System}, true, false); ++It)
@@ -3324,12 +3333,16 @@ static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts,
   // Note: Some paths that came from "-internal-isystem" arguments may have
   // already been generated as "-isystem". If that's the case, their position on
   // command line was such that this has no semantic impact on include paths.
-  for (; It < End &&
-         Matches(*It, {frontend::System, frontend::ExternCSystem}, false, true);
+  for (; It < End && Matches(*It,
+                             {frontend::System, frontend::ExternalSystem,
+                              frontend::ExternCSystem},
+                             false, true);
        ++It) {
-    OptSpecifier Opt = It->Group == frontend::System
-                           ? OPT_internal_isystem
-                           : OPT_internal_externc_isystem;
+    OptSpecifier Opt = OPT_internal_isystem;
+    if (It->Group == frontend::ExternalSystem)
+      Opt = OPT_internal_iexternal_system;
+    else if (It->Group == frontend::ExternCSystem)
+      Opt = OPT_internal_externc_isystem;
     GenerateArg(Consumer, Opt, It->Path);
   }
 
@@ -3390,7 +3403,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);
 
@@ -3409,10 +3421,14 @@ 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 -iprefix/-iwithprefix/-iwithprefixbefore options.
@@ -3432,13 +3448,17 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
   for (const auto *A : Args.filtered(OPT_iquote))
     Opts.AddPath(PrefixHeaderPath(A), frontend::Quoted, false, true);
 
-  for (const auto *A : Args.filtered(OPT_isystem, OPT_iwithsysroot)) {
-    if (A->getOption().matches(OPT_iwithsysroot)) {
-      Opts.AddPath(A->getValue(), frontend::System, false,
-                   /*IgnoreSysRoot=*/false);
-      continue;
-    }
-    Opts.AddPath(PrefixHeaderPath(A), frontend::System, false, true);
+  for (const auto *A :
+       Args.filtered(OPT_isystem, OPT_iwithsysroot, OPT_iexternal_system)) {
+    if (A->getOption().matches(OPT_iexternal_system))
+      Opts.AddPath(A->getValue(), frontend::ExternalSystem,
+                   /*IsFramework=*/false, /*IgnoreSysRoot=*/true);
+    else if (A->getOption().matches(OPT_iwithsysroot))
+      Opts.AddPath(A->getValue(), frontend::System,
+                   /*IsFramework=*/false, /*IgnoreSysRoot=*/false);
+    else
+      Opts.AddPath(PrefixHeaderPath(A), frontend::System,
+                   /*IsFramework=*/false, /*IgnoreSysRoot=*/true);
   }
   for (const auto *A : Args.filtered(OPT_iframework))
     Opts.AddPath(A->getValue(), frontend::System, true, true);
@@ -3458,9 +3478,12 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
 
   // Add the internal paths from a driver that detects standard include paths.
   for (const auto *A :
-       Args.filtered(OPT_internal_isystem, OPT_internal_externc_isystem)) {
+       Args.filtered(OPT_internal_isystem, OPT_internal_iexternal_system,
+                     OPT_internal_externc_isystem)) {
     frontend::IncludeDirGroup Group = frontend::System;
-    if (A->getOption().matches(OPT_internal_externc_isystem))
+    if (A->getOption().matches(OPT_internal_iexternal_system))
+      Group = frontend::ExternalSystem;
+    else if (A->getOption().matches(OPT_internal_externc_isystem))
       Group = frontend::ExternCSystem;
     Opts.AddPath(A->getValue(), Group, false, true);
   }
diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp
index bf8fe44e4ca9ca4..75f2c6237cf487a 100644
--- a/clang/lib/Lex/HeaderSearch.cpp
+++ b/clang/lib/Lex/HeaderSearch.cpp
@@ -128,6 +128,35 @@ void HeaderSearch::AddSearchPath(const DirectoryLookup &dir, bool isAngled) {
   SystemDirIdx++;
 }
 
+bool HeaderSearch::AddExternalDirectoryPrefix(StringRef Path) {
+  // Canonicalize the file path while preserving a trailing path separator.
+  bool HasTrailingPathSep = llvm::sys::path::is_separator(Path.back());
+  SmallString<256> RealPath;
+  if (llvm::sys::fs::real_path(Path, RealPath)) {
+    // Canonicalization was not successful. If the final path component does
+    // not have a trailing path separator, remove it, try again, and then
+    // restore the path component. If that fails (e.g., the previous path
+    // component also doesn't match an existing directory entry), then skip
+    // this path as it can't possibly match a prefix of any existing file.
+    if (HasTrailingPathSep)
+      return false;
+    SmallString<16> FileName = llvm::sys::path::filename(Path);
+    if (FileName.empty() || llvm::sys::path::is_separator(FileName[0]))
+      return false;
+    SmallString<256> ParentDir = Path;
+    llvm::sys::path::remove_filename(ParentDir);
+    if (llvm::sys::fs::real_path(ParentDir, RealPath))
+      return false;
+    if (!llvm::sys::path::is_separator(RealPath.back()))
+      RealPath += llvm::sys::path::get_separator();
+    RealPath += FileName;
+  }
+  if (HasTrailingPathSep && !llvm::sys::path::is_separator(RealPath.back()))
+    RealPath += llvm::sys::path::get_separator();
+  ExternalDirectoryPrefixes.insert(RealPath);
+  return true;
+}
+
 std::vector<bool> HeaderSearch::computeUserEntryUsage() const {
   std::vector<bool> UserEntryUsage(HSOpts->UserEntries.size());
   for (unsigned I = 0, E = SearchDirsUsage.size(); I < E; ++I) {
@@ -1117,6 +1146,35 @@ OptionalFileEntryRef HeaderSearch::LookupFile(
       }
     }
 
+    // If the file is not already recognized as a system header, check if it
+    // matches an external directory prefix and override the file characteristic
+    // accordingly.
+    if (HFI.DirInfo == SrcMgr::C_User && !ExternalDirectoryPrefixes.empty()) {
+      // An external directory prefix is not required to match an existing
+      // directory on disk; the final path component may match the start of
+      // multiple file or directory entries. A string comparison is therefore
+      // required to determine if a prefix matches. External directory prefixes
+      // are canonicalized using llvm::sys::fs::real_path() so the header
+      // file path must be likewise canonicalized. If canonicalization fails,
+      // assume no prefix match.
+      SmallString<256> RealPath;
+      if (!llvm::sys::fs::real_path(File->getName(), RealPath)) {
+        for (const auto &ExtDir : ExternalDirectoryPrefixes) {
+          // llvm::sys::path::replace_path_prefix() is used to test for a prefix
+          // match since it will handle case insensitivity and alternate path
+          // separator matching. Note that this operation is destructive to
+          // RealPath if the prefix matches.
+          if (llvm::sys::path::replace_path_prefix(RealPath,
+                                                   ExtDir.getKey(), "")) {
+            // The external directory prefix is a match; override the file
+            // characteristic and break out of the loop.
+            HFI.DirInfo = SrcMgr::C_System;
+            break;
+          }
+        }
+      }
+    }
+
     if (checkMSVCHeaderSearch(Diags, MSFE, &File->getFileEntry(), IncludeLoc)) {
       if (SuggestedModule)
         *SuggestedModule = MSSuggestedModule;
diff --git a/clang/lib/Lex/InitHeaderSearch.cpp b/clang/lib/Lex/InitHeaderSearch.cpp
index bb2a21356fa8fe6..6bbcad012b96fba 100644
--- a/clang/lib/Lex/InitHeaderSearch.cpp
+++ b/clang/lib/Lex/InitHeaderSearch.cpp
@@ -98,7 +98,7 @@ class InitHeaderSearch {
                               const HeaderSearchOptions &HSOpts);
 
   /// Merges all search path lists into one list and send it to HeaderSearch.
-  void Realize(const LangOptions &Lang);
+  void Realize(const HeaderSearchOptions &HSOpts, const LangOptions &Lang);
 };
 
 }  // end anonymous namespace.
@@ -151,6 +151,7 @@ bool InitHeaderSearch::AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
   } else if (Group == ExternCSystem) {
     Type = SrcMgr::C_ExternCSystem;
   } else {
+    // Group in External, ExternalSystem, System, (Obj)C(XX)System, After.
     Type = SrcMgr::C_System;
   }
 
@@ -174,6 +175,11 @@ bool InitHeaderSearch::AddUnmappedPath(const Twine &Path, IncludeDirGroup Group,
     }
   }
 
+  // A non-existent external directory prefix is still used for header file
+  // prefix matching purposes despite not contributing to the include path.
+  if (Group == External || Group == ExternalSystem)
+    Headers.AddExternalDirectoryPrefix(MappedPathStr);
+
   if (Verbose)
     llvm::errs() << "ignoring nonexistent directory \""
                  << MappedPathStr << "\"\n";
@@ -363,90 +369,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 HeaderSearchOptions &HSOpts,
+                                 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 user 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 (HSOpts.Mode != HeaderSearchMode::Microsoft &&
+               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.
@@ -471,56 +495,71 @@ mapToUserEntries(const std::vector<DirectoryLookupInfo> &Infos) {
   return LookupsToUserEntries;
 }
 
-void InitHeaderSearch::Realize(const LangOptions &Lang) {
+void InitHeaderSearch::Realize(const HeaderSearchOptions &HSOpts,
+                               const LangOptions &Lang) {
   // Concatenate ANGLE+SYSTEM+AFTER chains together into SearchList.
   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(HSOpts, 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);
+  // Remove duplicate search paths within the angled inclusion list.
+  // This may leave paths duplicated across the quoted and angled inclusion
+  // sections.
+  RemoveDuplicates(HSOpts, SearchList, EndQuoted, EndQuoted, Verbose);
+  unsigned EndAngled = SearchList.size();
 
-  RemoveDuplicates(SearchList, NumQuoted, Verbose);
-  unsigned NumAngled = SearchList.size();
-
+  // Add search paths for system paths next.
   for (auto &Include : IncludePath)
-    if (Include.Group == System || Include.Group == ExternCSystem ||
+    if (Include.Group == System || Include.Group == ExternalSystem ||
+        Include.Group == ExternCSystem ||
         (!Lang.ObjC && !Lang.CPlusPlus && Include.Group == CSystem) ||
         (/*FIXME !Lang.ObjC && */ Lang.CPlusPlus &&
          Include.Group == CXXSystem) ||
         (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(HSOpts, SearchList, EndQuoted, EndAngled, Verbose);
+  EndAngled -= NonSystemRemoved;
 
-  Headers.SetSearchPaths(extractLookups(SearchList), NumQuoted, NumAngled,
+  Headers.SetSearchPaths(extractLookups(SearchList), EndQuoted, EndAngled,
                          mapToUserEntries(SearchList));
 
   Headers.SetSystemHeaderPrefixes(SystemHeaderPrefixes);
 
+  // Register external directory prefixes.
+  for (auto &Include : IncludePath)
+    if (Include.Group == External || Include.Group == ExternalSystem)
+      Headers.AddExternalDirectoryPrefix(Include.Lookup.getName().str());
+
   // If verbose, print the list of directories that will be searched.
   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;
@@ -568,5 +607,5 @@ void clang::ApplyHeaderSearchOptions(HeaderSearch &HS,
       HS.getModuleMap().setBuiltinIncludeDir(*Dir);
   }
 
-  Init.Realize(Lang);
+  Init.Realize(HSOpts, Lang);
 }
diff --git a/clang/lib/Lex/PPDirectives.cpp b/clang/lib/Lex/PPDirectives.cpp
index a23ad40884f249a..bd4b2f6464a3e2d 100644
--- a/clang/lib/Lex/PPDirectives.cpp
+++ b/clang/lib/Lex/PPDirectives.cpp
@@ -23,6 +23,7 @@
 #include "clang/Basic/TokenKinds.h"
 #include "clang/Lex/CodeCompletionHandler.h"
 #include "clang/Lex/HeaderSearch.h"
+#include "clang/Lex/HeaderSearchOptions.h"
 #include "clang/Lex/LexDiagnostic.h"
 #include "clang/Lex/LiteralSupport.h"
 #include "clang/Lex/MacroInfo.h"
@@ -999,7 +1000,9 @@ OptionalFileEntryRef Preprocessor::LookupFile(
     // MSVC searches the current include stack from top to bottom for
     // headers included by quoted include directives.
     // See: http://msdn.microsoft.com/en-us/library/36k2cdd4.aspx
-    if (LangOpts.MSVCCompat && !isAngled) {
+    if (getHeaderSearchInfo().getHeaderSearchOpts().Mode ==
+            HeaderSearchMode::Microsoft &&
+        !isAngled) {
       for (IncludeStackInfo &ISEntry : llvm::reverse(IncludeMacroStack)) {
         if (IsFileLexer(ISEntry))
           if ((FileEnt = ISEntry.ThePPLexer->getFileEntry()))
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 7361cace49dd7bf..d7d2e134867d942 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -6274,8 +6274,8 @@ bool ASTReader::ParseHeaderSearchOptions(const RecordData &Record,
                                          ASTReaderListener &Listener) {
   HeaderSearchOptions HSOpts;
   unsigned Idx = 0;
+  HSOpts.Mode = static_cast<HeaderSearchMode>(Record[Idx++]);
   HSOpts.Sysroot = ReadString(Record, Idx);
-
   HSOpts.ResourceDir = ReadString(Record, Idx);
   HSOpts.ModuleCachePath = ReadString(Record, Idx);
   HSOpts.ModuleUserBuildPath = ReadString(Record, Idx);
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 0ae2157eed4ecc4..239fe79a49610c1 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -1676,6 +1676,7 @@ void ASTWriter::WriteControlBlock(Preprocessor &PP, StringRef isysroot) {
   const HeaderSearchOptions &HSOpts =
       PP.getHeaderSearchInfo().getHeaderSearchOpts();
 
+  Record.push_back(static_cast<unsigned>(HSOpts.Mode));
   AddString(HSOpts.Sysroot, Record);
   AddString(HSOpts.ResourceDir, Record);
   AddString(HSOpts.ModuleCachePath, Record);
diff --git a/clang/test/Driver/cl-include.c b/clang/test/Driver/cl-include.c
index ca9e7db1e6f07ca..5164ea5e0589ce5 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"
+// STDINC: "-internal-iexternal-system" "/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
+// RUN: env INCLUDE=/my/system/inc1 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: "/my/other/inc"
+// NOSTDINC-NOT: "/my/system/inc1"
+// NOSTDINC-NOT: "/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
+// RUN: env INCLUDE=/my/system/inc1 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-NOT: "/my/system/inc1"
+// SLASHX-NOT: "/my/system/inc2"
+// SLASHX: "-iexternal-system" "/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/inc1 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/inc1"
+// SYSROOT-NOT: "/my/system/inc2"
+// SYSROOT: "-iexternal-system" "/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"
+// RUN: env "FOO=/dir1%{pathsep}/dir2" env "BAR=/dir3" %clang_cl /external:env:FOO /external:env:BAR -### -- %s 2>&1 | FileCheck %s --check-prefix=EXTERNAL_ENV
+// EXTERNAL_ENV: "-iexternal-system" "/dir1"
+// EXTERNAL_ENV: "-iexternal-system" "/dir2"
+// EXTERNAL_ENV: "-iexternal-system" "/dir3"
diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c
index 29a0fcbc17ac603..333023de7a8e604 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%{pathsep}path2" %clang_cl /external:env:EXTPATH -### -- %s 2>&1 | FileCheck -check-prefix=EXTERNAL_ENV %s
+// EXTERNAL_ENV: "-iexternal-system" "path1"
+// EXTERNAL_ENV: "-iexternal-system" "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 000000000000000..9a96566e331ca8f
--- /dev/null
+++ b/clang/test/Driver/header-search-duplicates.c
@@ -0,0 +1,369 @@
+// 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 uses the -nostdinc option 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 000000000000000..8de5703c1129413
--- /dev/null
+++ b/clang/test/Driver/microsoft-header-search-duplicates.c
@@ -0,0 +1,609 @@
+// 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 uses the '-nobuiltininc', '-nostdinc', and '/X ('-nostdlibinc')
+// options 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 'EXTERNAL_INCLUDE' 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:     -fheader-search=microsoft \
+// 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:     -fheader-search=microsoft \
+// 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:     -fheader-search=microsoft \
+// 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%{pathsep}%t/test4/include/y%{pathsep}%t/test4/include/x%{pathsep}%t/test4/include/w" \
+// RUN: %clang \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -fheader-search=microsoft \
+// 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%{pathsep}%t/test4/include/y%{pathsep}%t/test4/include/x%{pathsep}%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%{pathsep}%t/test5/include/y%{pathsep}%t/test5/include/w%{pathsep}%t/test5/include/v%{pathsep}%t/test5/include/u" \
+// RUN: env EXTERNAL_INCLUDE="%t/test5/include/z%{pathsep}%t/test5/include/y%{pathsep}%t/test5/include/w%{pathsep}%t/test5/include/v%{pathsep}%t/test5/include/u" \
+// RUN: %clang \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -fheader-search=microsoft \
+// RUN:     -nostdinc \
+// RUN:     -I%t/test5/include/u \
+// RUN:     -iexternal %t/test5/include/v \
+// RUN:     -iexternal-env=EXTRA_INCLUDE \
+// RUN:     -isystem-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%{pathsep}%t/test5/include/y%{pathsep}%t/test5/include/w%{pathsep}%t/test5/include/v%{pathsep}%t/test5/include/u" \
+// RUN: env EXTERNAL_INCLUDE="%t/test5/include/z%{pathsep}%t/test5/include/y%{pathsep}%t/test5/include/w%{pathsep}%t/test5/include/v%{pathsep}%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
+
+
+// Test 6: Validate that warning suppression is goverened by external include
+// path matching regardless of include path order.
+//
+// RUN: env EXTRA_INCLUDE="%t/test6/include/x" \
+// RUN: env INCLUDE="%t/test6/include/y" \
+// RUN: env EXTERNAL_INCLUDE="%t/test6/include/z" \
+// RUN: %clang \
+// RUN:     -Xclang -verify \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -fheader-search=microsoft \
+// RUN:     -nostdinc \
+// RUN:     -Wall \
+// RUN:     -Wno-system-headers \
+// RUN:     -I%t/test6/include/v \
+// RUN:     -I%t/test6/include/w \
+// RUN:     -iexternal %t/test6/include/w \
+// RUN:     -I%t/test6/include/x \
+// RUN:     -I%t/test6/include/y \
+// RUN:     -I%t/test6/include/z \
+// RUN:     -iexternal-env=EXTRA_INCLUDE \
+// RUN:     -isystem-env=INCLUDE \
+// RUN:     -iexternal-env=EXTERNAL_INCLUDE \
+// RUN:     %t/test6/t.c 2>&1 | FileCheck -DPWD=%t %t/test6/t.c
+// RUN: env EXTRA_INCLUDE="%t/test6/include/x" \
+// RUN: env INCLUDE="%t/test6/include/y" \
+// RUN: env EXTERNAL_INCLUDE="%t/test6/include/z" \
+// RUN: %clang_cl \
+// RUN:     -Xclang -verify \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -nobuiltininc \
+// RUN:     /W4 \
+// RUN:     /external:W0 \
+// RUN:     /I%t/test6/include/v \
+// RUN:     /I%t/test6/include/w \
+// RUN:     /external:I %t/test6/include/w \
+// RUN:     /I%t/test6/include/x \
+// RUN:     /I%t/test6/include/y \
+// RUN:     /I%t/test6/include/z \
+// RUN:     /external:env:EXTRA_INCLUDE \
+// RUN:     %t/test6/t.c 2>&1 | FileCheck -DPWD=%t %t/test6/t.c
+
+#--- test6/t.c
+#include <a.h>
+#include <b.h>
+#include <c.h>
+#include <d.h>
+#include <e.h>
+
+// CHECK:      ignoring duplicate directory "[[PWD]]/test6/include/w"
+// CHECK-NEXT:  as it is a non-system directory that duplicates a system directory
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test6/include/x"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test6/include/y"
+// CHECK-NEXT: ignoring duplicate directory "[[PWD]]/test6/include/z"
+// CHECK-NEXT: #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test6/include/v
+// CHECK-NEXT: [[PWD]]/test6/include/w
+// CHECK-NEXT: [[PWD]]/test6/include/x
+// CHECK-NEXT: [[PWD]]/test6/include/y
+// CHECK-NEXT: [[PWD]]/test6/include/z
+// CHECK-NEXT: End of search list.
+// CHECK-NOT:  diagnostics seen but not expected
+// CHECK-NOT:  diagnostics expected but not seen
+
+#--- test6/include/v/a.h
+// expected-warning at +1 {{shift count >= width of type}}
+int va = 1 << 1024;
+
+#--- test6/include/w/a.h
+#error 'test6/include/w/a.h' should not have been included!
+
+#--- test6/include/w/b.h
+int wb = 1 << 1024; // Warning should be suppressed.
+
+#--- test6/include/x/a.h
+#error 'test6/include/x/a.h' should not have been included!
+
+#--- test6/include/x/b.h
+#error 'test6/include/x/b.h' should not have been included!
+
+#--- test6/include/x/c.h
+int xc = 1 << 1024; // Warning should be suppressed.
+
+#--- test6/include/y/a.h
+#error 'test6/include/y/a.h' should not have been included!
+
+#--- test6/include/y/b.h
+#error 'test6/include/y/b.h' should not have been included!
+
+#--- test6/include/y/c.h
+#error 'test6/include/y/c.h' should not have been included!
+
+#--- test6/include/y/d.h
+// expected-warning at +1 {{shift count >= width of type}}
+int yd = 1 << 1024; // Warning should NOT be suppressed.
+
+#--- test6/include/z/a.h
+#error 'test6/include/z/a.h' should not have been included!
+
+#--- test6/include/z/b.h
+#error 'test6/include/z/b.h' should not have been included!
+
+#--- test6/include/z/c.h
+#error 'test6/include/z/c.h' should not have been included!
+
+#--- test6/include/z/d.h
+#error 'test6/include/z/d.h' should not have been included!
+
+#--- test6/include/z/e.h
+int ze = 1 << 1024; // Warning should be suppressed.
+
+
+// Test 7: Validate that warning suppression for a header file included via a
+// -I specified path is goverened by an external include path that is a partial
+// match for the resolved header file path (even if the #include directive would
+// not have matched relative to the external path). Note that partial matching
+// includes matching portions of the final path component even if the paths
+// would otherwise select distinct files or directories.
+//
+// RUN: %clang \
+// RUN:     -Xclang -verify \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -fheader-search=microsoft \
+// RUN:     -nostdinc \
+// RUN:     -Wall \
+// RUN:     -Wno-system-headers \
+// RUN:     -I%t/test7/include/w \
+// RUN:     -I%t/test7/include/x \
+// RUN:     -I%t/test7/include/y \
+// RUN:     -I%t/test7/include/z \
+// RUN:     -iexternal %t/test7/include/x/foo \
+// RUN:     -iexternal %t/test7/include/y/fo \
+// RUN:     -iexternal %t/test7/include/z/f \
+// RUN:     %t/test7/t.c 2>&1 | FileCheck -DPWD=%t %t/test7/t.c
+// RUN: %clang_cl \
+// RUN:     -Xclang -verify \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -nobuiltininc /X \
+// RUN:     /W4 \
+// RUN:     /external:W0 \
+// RUN:     /I%t/test7/include/w \
+// RUN:     /I%t/test7/include/x \
+// RUN:     /I%t/test7/include/y \
+// RUN:     /I%t/test7/include/z \
+// RUN:     /external:I %t/test7/include/x/foo \
+// RUN:     /external:I %t/test7/include/y/fo \
+// RUN:     /external:I %t/test7/include/z/f \
+// RUN:     %t/test7/t.c 2>&1 | FileCheck -DPWD=%t %t/test7/t.c
+
+#--- test7/t.c
+#include <foo/a.h>
+#include <foo/b.h>
+#include <foo/c.h>
+#include <foo/d.h>
+
+// CHECK:      #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test7/include/w
+// CHECK-NEXT: [[PWD]]/test7/include/x
+// CHECK-NEXT: [[PWD]]/test7/include/y
+// CHECK-NEXT: [[PWD]]/test7/include/z
+// CHECK-NEXT: [[PWD]]/test7/include/x/foo
+// CHECK-NEXT: [[PWD]]/test7/include/y/fo
+// CHECK-NEXT: End of search list.
+// CHECK-NOT:  diagnostics seen but not expected
+// CHECK-NOT:  diagnostics expected but not seen
+
+#--- test7/include/w/foo/a.h
+// expected-warning at +1 {{shift count >= width of type}}
+int wa = 1 << 1024;
+
+#--- test7/include/x/foo/a.h
+#error 'test7/include/x/foo/a.h' should not have been included!
+
+#--- test7/include/x/foo/b.h
+int xb = 1 << 1024; // Warning should be suppressed.
+
+#--- test7/include/y/foo/a.h
+#error 'test7/include/y/foo/a.h' should not have been included!
+
+#--- test7/include/y/foo/b.h
+#error 'test7/include/y/foo/b.h' should not have been included!
+
+#--- test7/include/y/fo/unused
+
+#--- test7/include/y/foo/c.h
+int yc = 1 << 1024; // Warning should be suppressed.
+
+#--- test7/include/z/foo/a.h
+#error 'test7/include/z/foo/a.h' should not have been included!
+
+#--- test7/include/z/foo/b.h
+#error 'test7/include/z/foo/b.h' should not have been included!
+
+#--- test7/include/z/foo/c.h
+#error 'test7/include/z/foo/c.h' should not have been included!
+
+#--- test7/include/z/foo/d.h
+int zd = 1 << 1024; // Warning should be suppressed.
+
+
+// Test 8: Validate that an external directory path with a trailing path
+// separator is not considered a partial match for an include path where
+// the path component before the trailing path separator is a prefix match
+// for a longer name.
+//
+// RUN: %clang \
+// RUN:     -Xclang -verify \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -fheader-search=microsoft \
+// RUN:     -nostdinc \
+// RUN:     -Wall \
+// RUN:     -Wno-system-headers \
+// RUN:     -I%t/test8/include/y \
+// RUN:     -I%t/test8/include/z \
+// RUN:     -iexternal %t/test8/include/z/fo \
+// RUN:     %t/test8/t.c 2>&1 | FileCheck -DPWD=%t %t/test8/t.c
+// RUN: %clang_cl \
+// RUN:     -Xclang -verify \
+// RUN:     -target x86_64-pc-windows -v -fsyntax-only \
+// RUN:     -nobuiltininc /X \
+// RUN:     /W4 \
+// RUN:     /external:W0 \
+// RUN:     /I%t/test8/include/y \
+// RUN:     /I%t/test8/include/z \
+// RUN:     /external:I %t/test8/include/z/fo/ \
+// RUN:     %t/test8/t.c 2>&1 | FileCheck -DPWD=%t %t/test8/t.c
+
+#--- test8/t.c
+#include <foo/a.h>
+#include <foo/b.h>
+
+// CHECK:      #include "..." search starts here:
+// CHECK-NEXT: #include <...> search starts here:
+// CHECK-NEXT: [[PWD]]/test8/include/y
+// CHECK-NEXT: [[PWD]]/test8/include/z
+// CHECK-NEXT: [[PWD]]/test8/include/z/fo
+// CHECK-NEXT: End of search list.
+// CHECK-NOT:  diagnostics seen but not expected
+// CHECK-NOT:  diagnostics expected but not seen
+
+#--- test8/include/y/foo/a.h
+// expected-warning at +1 {{shift count >= width of type}}
+int wa = 1 << 1024;
+
+#--- test8/include/z/foo/a.h
+#error 'test8/include/z/foo/a.h' should not have been included!
+
+#--- test8/include/z/fo/unused
+
+#--- test8/include/z/foo/b.h
+// FIXME: MSVC retains trailing path separators on external directory prefixes
+// FIXME: and will only match the final path component against a complete name
+// FIXME: Clang discards trailing path separators thereby allowing a prefix
+// FIXME: match for the final path component.
+// FIXME-expected-warning at +1 {{shift count >= width of type}}
+int zd = 1 << 1024; // Warning should NOT be suppressed.
diff --git a/clang/test/Preprocessor/microsoft-header-search-fail.c b/clang/test/Preprocessor/microsoft-header-search-fail.c
index c377cb11d658aa9..9f8927974de9399 100644
--- a/clang/test/Preprocessor/microsoft-header-search-fail.c
+++ b/clang/test/Preprocessor/microsoft-header-search-fail.c
@@ -1,7 +1,7 @@
 // RUN: rm -rf %t
 // RUN: split-file %s %t
 
-// RUN: %clang_cc1 -Eonly -fms-compatibility %t/test.c -I %t/include -verify
+// RUN: %clang_cc1 -Eonly -fheader-search=microsoft %t/test.c -I %t/include -verify
 
 //--- test.c
 #include "x/header.h"
diff --git a/clang/test/Preprocessor/microsoft-header-search.c b/clang/test/Preprocessor/microsoft-header-search.c
index 875bffe8793b8bb..15fef1b0d184b4d 100644
--- a/clang/test/Preprocessor/microsoft-header-search.c
+++ b/clang/test/Preprocessor/microsoft-header-search.c
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -I%S/Inputs/microsoft-header-search %s -fms-compatibility -verify
+// RUN: %clang_cc1 -I%S/Inputs/microsoft-header-search %s -fheader-search=microsoft -verify
 
 // expected-warning at Inputs/microsoft-header-search/a/findme.h:3 {{findme.h successfully included using Microsoft header search rules}}
 // expected-warning at Inputs/microsoft-header-search/a/b/include3.h:3 {{#include resolved using non-portable Microsoft search rules as}}
diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h
index 09812f976d0166d..77703910b3d26ba 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 6e164150d2e5e9d..3bbff6c75e78aaf 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 llvm-commits mailing list