Skip to content

Commit a9b1711

Browse files
committed
[Clang] Match MSVC handling of duplicate header search paths in Microsoft compatibility modes.
Clang has historically matched GCC's behavior for header search path order and pruning of duplicate paths. That traditional behavior is to order user search paths before system search paths, to ignore user search paths that duplicate a (later) system search path, and to ignore search paths that duplicate an earlier search path of the same user/system kind. This differs from MSVC and can result in inconsistent header file resolution for `#include` directives. MSVC orders header search paths as follows: 1) Paths specified by the `/I` and `/external:I` options are processed in the order that they appear. Paths specified by `/I` that duplicate a path specified by `/external:I` are ignored regardless of the order of the options. Paths specified by `/I` that duplicate a path from a prior `/I` option are ignored. Paths specified by `/external:I` that duplicate a path from a later `/external:I` option are ignored. 2) Paths specified by `/external:env` are processed in the order that they appear. Paths that duplicate a path from a `/I` or `/external:I` option are ignored regardless of the order of the options. Paths that duplicate a path from a prior `/external:env` option or an earlier path from the same `/external:env` option are ignored. 3) Paths specified by the `INCLUDE` environment variable are processed in the order they appear. Paths that duplicate a path from a `/I`, `/external:I`, or `/external:env` option are ignored. Paths that duplicate an earlier path in the `INCLUDE` environment variable are ignored. 4) Paths specified by the `EXTERNAL_INCLUDE` environment variable are processed in the order they appear. Paths that duplicate a path from a `/I`, `/external:I`, or `/external:env` option are ignored. Paths that duplicate a path from the `INCLUDE` environment variable are ignored. Paths that duplicate an earlier path in the `EXTERNAL_INCLUDE environment variable are ignored. Prior to this change, Clang handled the `/external:I` and `/external:env` options and the paths present in the `INCLUDE` and `EXTERNAL_INCLUDE` environment variables as though they were specified with the `-isystem` option. The GCC behavior described above then lead to a command line such as `/external:I dir1 /Idir2` having a header search order of `dir2` followed by `dir1`; contrary to MSVC behavior. This change adds support for the MSVC external path concept for both the `clang` and `clang-cl` drivers with the following option syntax. These options match the MSVC behavior described above for both drivers. clang clang-cl -------------------- ------------------- -iexternal <dir> /external:I <dir> -iexternal-env=<ENV> /external:env:<ENV> Paths specified by these options are still treated as system paths. That is, whether warnings are issued in header files found via these paths remains subject to use of the `-Wsystem-headers` and `-Wno-system-headers` options. In the future, it would make sense to add a separate option that matches the MSVC `/external:Wn` option to control such warnings. The MSVC behavior described above implies that (system) paths present in the `INCLUDE` and `EXTERNAL_INCLUDE` environment variables do not suppress matching user paths specified via `/I`. This contrasts with GCC's behavior of suppressing user paths that match a system path regardless of how each is specified. Since the `clang-cl` driver maps paths from the `INCLUDE` and `EXTERNAL_INCLUDE` environment variable to `-internal-isystem`, matching MSVC behavior requires suppressing that aspect of the GCC behavior. With this change, system paths will no longer suppress user paths when the `-fms-compatibility` option is explicitly or implicitly enabled. This will affect header search path ordering for options like `-isystem` when duplicate user paths are present. Should motivation arise for preserving such suppression of user paths when compiling with `-fms-compatibility` enabled, it would make sense to introduce a new option for the `clang-cl` driver to map paths in these environment variabless to that would match MSVC behavior without impacting other system path options.
1 parent f855975 commit a9b1711

File tree

16 files changed

+1016
-127
lines changed

16 files changed

+1016
-127
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,44 @@ Windows Support
701701
When `-fms-compatibility-version=18.00` or prior is set on the command line this Microsoft extension is still
702702
allowed as VS2013 and prior allow it.
703703

704+
- Clang now matches MSVC behavior regarding the handling of duplicate header
705+
search paths when running in Microsoft compatibility mode. Historically,
706+
Clang has mimicked gcc behavior in which user search paths are ordered before
707+
system search paths, user search paths that duplicate a (later) system search
708+
path are ignored, and search paths that duplicate an earlier search path of
709+
the same user/system kind are ignored. This ordering is not compatible with
710+
the ordering that MSVC uses when paths are duplicated across ``/I`` options
711+
and the ``INCLUDE`` environment variable.
712+
713+
The order that MSVC uses and that Clang now replicates when the
714+
``-fms-compatibility`` option is enabled follows.
715+
716+
- Paths specified by the ``/I`` and ``/external:I`` options are processed in
717+
the order that they appear. Paths specified by ``/I`` that duplicate a path
718+
specified by ``/external:I`` are ignored regardless of the order of the
719+
options. Paths specified by ``/I`` that duplicate a path from a prior ``/I``
720+
option are ignored. Paths specified by ``/external:I`` that duplicate a
721+
path from a later ``/external:I`` option are ignored.
722+
723+
- Paths specified by ``/external:env`` are processed in the order that they
724+
appear. Paths that duplicate a path from a ``/I`` or ``/external:I`` option
725+
are ignored regardless of the order of the options. Paths that duplicate a
726+
path from a prior ``/external:env`` option or an earlier path from the same
727+
``/external:env`` option are ignored.
728+
729+
- Paths specified by the ``INCLUDE`` environment variable are processed in
730+
the order they appear. Paths that duplicate a path from a ``/I``,
731+
``/external:I``, or ``/external:env`` option are ignored. Paths that
732+
duplicate an earlier path in the ``INCLUDE`` environment variable are
733+
ignored.
734+
735+
- Paths specified by the ``EXTERNAL_INCLUDE`` environment variable are
736+
processed in the order they appear. Paths that duplicate a path from a
737+
``/I``, ``/external:I``, or ``/external:env`` option are ignored. Paths that
738+
duplicate a path from the ``INCLUDE`` environment variable are ignored.
739+
Paths that duplicate an earlier path in the ``EXTERNAL_INCLUDE``
740+
environment variable are ignored.
741+
704742
LoongArch Support
705743
^^^^^^^^^^^^^^^^^
706744

clang/include/clang/Driver/Options.td

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4576,6 +4576,15 @@ def iapinotes_modules : JoinedOrSeparate<["-"], "iapinotes-modules">, Group<clan
45764576
def idirafter : JoinedOrSeparate<["-"], "idirafter">, Group<clang_i_Group>,
45774577
Visibility<[ClangOption, CC1Option]>,
45784578
HelpText<"Add directory to AFTER include search path">;
4579+
def iexternal : Separate<["-"], "iexternal">, Group<clang_i_Group>,
4580+
Visibility<[ClangOption, CC1Option]>,
4581+
HelpText<"Add directory to include search path with warnings suppressed">, MetaVarName<"<dir>">;
4582+
def iexternal_after : Separate<["-"], "iexternal-after">, Group<clang_i_Group>,
4583+
Visibility<[ClangOption, CC1Option]>,
4584+
HelpText<"Add directory to include search path with warnings suppressed">, MetaVarName<"<dir>">;
4585+
def iexternal_env_EQ : Joined<["-"], "iexternal-env=">, Group<clang_i_Group>,
4586+
Visibility<[ClangOption]>,
4587+
HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">, MetaVarName<"<var>">;
45794588
def iframework : JoinedOrSeparate<["-"], "iframework">, Group<clang_i_Group>,
45804589
Visibility<[ClangOption, CC1Option]>,
45814590
HelpText<"Add directory to SYSTEM framework search path">;
@@ -8464,9 +8473,12 @@ def _SLASH_diagnostics_classic : CLFlag<"diagnostics:classic">,
84648473
def _SLASH_D : CLJoinedOrSeparate<"D", [CLOption, DXCOption]>,
84658474
HelpText<"Define macro">, MetaVarName<"<macro[=value]>">, Alias<D>;
84668475
def _SLASH_E : CLFlag<"E">, HelpText<"Preprocess to stdout">, Alias<E>;
8467-
def _SLASH_external_COLON_I : CLJoinedOrSeparate<"external:I">, Alias<isystem>,
8476+
def _SLASH_external_COLON_I : CLJoinedOrSeparate<"external:I">, Alias<iexternal>,
84688477
HelpText<"Add directory to include search path with warnings suppressed">,
84698478
MetaVarName<"<dir>">;
8479+
def _SLASH_external_env : CLJoined<"external:env:">, Alias<iexternal_env_EQ>,
8480+
HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">,
8481+
MetaVarName<"<var>">;
84708482
def _SLASH_fp_contract : CLFlag<"fp:contract">, HelpText<"">, Alias<ffp_contract>, AliasArgs<["on"]>;
84718483
def _SLASH_fp_except : CLFlag<"fp:except">, HelpText<"">, Alias<ffp_exception_behavior_EQ>, AliasArgs<["strict"]>;
84728484
def _SLASH_fp_except_ : CLFlag<"fp:except-">, HelpText<"">, Alias<ffp_exception_behavior_EQ>, AliasArgs<["ignore"]>;
@@ -8696,9 +8708,6 @@ def _SLASH_volatile_Group : OptionGroup<"</volatile group>">,
86968708
def _SLASH_EH : CLJoined<"EH">, HelpText<"Set exception handling model">;
86978709
def _SLASH_EP : CLFlag<"EP">,
86988710
HelpText<"Disable linemarker output and preprocess to stdout">;
8699-
def _SLASH_external_env : CLJoined<"external:env:">,
8700-
HelpText<"Add dirs in env var <var> to include search path with warnings suppressed">,
8701-
MetaVarName<"<var>">;
87028711
def _SLASH_FA : CLJoined<"FA">,
87038712
HelpText<"Output assembly code file during compilation">;
87048713
def _SLASH_Fa : CLJoined<"Fa">,

clang/include/clang/Driver/ToolChain.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ class ToolChain {
224224
/// \return The subdirectory path if it exists.
225225
std::optional<std::string> getTargetSubDirPath(StringRef BaseDir) const;
226226

227+
public:
227228
/// \name Utilities for implementing subclasses.
228229
///@{
229230
static void addSystemInclude(const llvm::opt::ArgList &DriverArgs,
@@ -239,12 +240,20 @@ class ToolChain {
239240
static void addSystemIncludes(const llvm::opt::ArgList &DriverArgs,
240241
llvm::opt::ArgStringList &CC1Args,
241242
ArrayRef<StringRef> Paths);
243+
static bool addSystemIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
244+
llvm::opt::ArgStringList &CC1Args,
245+
StringRef Var);
246+
static void addExternalAfterIncludes(const llvm::opt::ArgList &DriverArgs,
247+
llvm::opt::ArgStringList &CC1Args,
248+
ArrayRef<StringRef> Paths);
249+
static bool addExternalIncludesFromEnv(const llvm::opt::ArgList &DriverArgs,
250+
llvm::opt::ArgStringList &CC1Args,
251+
StringRef Var);
242252

243253
static std::string concat(StringRef Path, const Twine &A, const Twine &B = "",
244254
const Twine &C = "", const Twine &D = "");
245255
///@}
246256

247-
public:
248257
virtual ~ToolChain();
249258

250259
// Accessors

clang/include/clang/Lex/HeaderSearchOptions.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ enum IncludeDirGroup {
3535
/// Paths for '\#include <>' added by '-I'.
3636
Angled,
3737

38+
/// Like Angled, but marks system directories while retaining relative order
39+
/// with user directories. This group is intended to match the semantics of
40+
/// the MSVC /external:I option.
41+
External,
42+
43+
/// Like External, but searched after other external directories but before
44+
/// system directories. This group is intended to match the semantics of the
45+
/// MSVC /external:env option.
46+
ExternalAfter,
47+
3848
/// Like Angled, but marks system directories.
3949
System,
4050

clang/lib/Driver/Driver.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1296,7 +1296,9 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
12961296
if (VFS->setCurrentWorkingDirectory(WD->getValue()))
12971297
Diag(diag::err_drv_unable_to_set_working_directory) << WD->getValue();
12981298

1299-
// Check for missing include directories.
1299+
// Check for missing include directories. Diagnostics should not be issued
1300+
// for directories specified with -iexternal, -iexternal-env=, or
1301+
// -iexternal-after since those options may be used to specify partial paths.
13001302
if (!Diags.isIgnored(diag::warn_missing_include_dirs, SourceLocation())) {
13011303
for (auto IncludeDir : Args.getAllArgValues(options::OPT_I_Group)) {
13021304
if (!VFS->exists(IncludeDir))

clang/lib/Driver/ToolChain.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,51 @@ void ToolChain::addExternCSystemIncludeIfExists(const ArgList &DriverArgs,
12691269
}
12701270
}
12711271

1272+
/// Utility function to add a list of ';' delimited directories specified in
1273+
/// an environment variable to the system include path list for CC1. Returns
1274+
/// true if the variable is set and not empty.
1275+
/*static*/ bool ToolChain::addSystemIncludesFromEnv(const ArgList &DriverArgs,
1276+
ArgStringList &CC1Args,
1277+
StringRef Var) {
1278+
if (auto Val = llvm::sys::Process::GetEnv(Var)) {
1279+
SmallVector<StringRef, 8> Dirs;
1280+
StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
1281+
if (!Dirs.empty()) {
1282+
addSystemIncludes(DriverArgs, CC1Args, Dirs);
1283+
return true;
1284+
}
1285+
}
1286+
return false;
1287+
}
1288+
1289+
/// Utility function to add a list of directories to the end of the external
1290+
/// include path list for CC1.
1291+
/*static*/ void ToolChain::addExternalAfterIncludes(const ArgList &DriverArgs,
1292+
ArgStringList &CC1Args,
1293+
ArrayRef<StringRef> Paths) {
1294+
for (const auto &Path : Paths) {
1295+
CC1Args.push_back("-iexternal-after");
1296+
CC1Args.push_back(DriverArgs.MakeArgString(Path));
1297+
}
1298+
}
1299+
1300+
/// Utility function to add a list of ';' delimited directories specified in
1301+
/// an environment variable to the external include path list for CC1. Returns
1302+
/// true if the variable is set and not empty.
1303+
/*static*/ bool ToolChain::addExternalIncludesFromEnv(const ArgList &DriverArgs,
1304+
ArgStringList &CC1Args,
1305+
StringRef Var) {
1306+
if (auto Val = llvm::sys::Process::GetEnv(Var)) {
1307+
SmallVector<StringRef, 8> Dirs;
1308+
StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
1309+
if (!Dirs.empty()) {
1310+
addExternalAfterIncludes(DriverArgs, CC1Args, Dirs);
1311+
return true;
1312+
}
1313+
}
1314+
return false;
1315+
}
1316+
12721317
/*static*/ std::string ToolChain::concat(StringRef Path, const Twine &A,
12731318
const Twine &B, const Twine &C,
12741319
const Twine &D) {

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,16 +1198,23 @@ void Clang::AddPreprocessingOptions(Compilation &C, const JobAction &JA,
11981198
} else if (A->getOption().matches(options::OPT_ibuiltininc)) {
11991199
// This is used only by the driver. No need to pass to cc1.
12001200
continue;
1201+
} else if (A->getOption().matches(options::OPT_iexternal)) {
1202+
// This option has to retain relative order with other -I options.
1203+
continue;
1204+
} else if (A->getOption().matches(options::OPT_iexternal_env_EQ)) {
1205+
A->claim();
1206+
ToolChain::addExternalIncludesFromEnv(Args, CmdArgs, A->getValue());
1207+
continue;
12011208
}
12021209

12031210
// Not translated, render as usual.
12041211
A->claim();
12051212
A->render(Args, CmdArgs);
12061213
}
12071214

1208-
Args.addAllArgs(CmdArgs,
1209-
{options::OPT_D, options::OPT_U, options::OPT_I_Group,
1210-
options::OPT_F, options::OPT_embed_dir_EQ});
1215+
Args.addAllArgs(CmdArgs, {options::OPT_D, options::OPT_U,
1216+
options::OPT_I_Group, options::OPT_F,
1217+
options::OPT_iexternal, options::OPT_embed_dir_EQ});
12111218

12121219
// Add -Wp, and -Xpreprocessor if using the preprocessor.
12131220

@@ -8664,7 +8671,7 @@ void ClangAs::ConstructJob(Compilation &C, const JobAction &JA,
86648671
(void)Args.hasArg(options::OPT_force__cpusubtype__ALL);
86658672

86668673
// Pass along any -I options so we get proper .include search paths.
8667-
Args.AddAllArgs(CmdArgs, options::OPT_I_Group);
8674+
Args.addAllArgs(CmdArgs, {options::OPT_I_Group, options::OPT_iexternal});
86688675

86698676
// Pass along any --embed-dir or similar options so we get proper embed paths.
86708677
Args.AddAllArgs(CmdArgs, options::OPT_embed_dir_EQ);

clang/lib/Driver/ToolChains/MSVC.cpp

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -648,24 +648,6 @@ void MSVCToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
648648
for (const auto &Path : DriverArgs.getAllArgValues(options::OPT__SLASH_imsvc))
649649
addSystemInclude(DriverArgs, CC1Args, Path);
650650

651-
auto AddSystemIncludesFromEnv = [&](StringRef Var) -> bool {
652-
if (auto Val = llvm::sys::Process::GetEnv(Var)) {
653-
SmallVector<StringRef, 8> Dirs;
654-
StringRef(*Val).split(Dirs, ";", /*MaxSplit=*/-1, /*KeepEmpty=*/false);
655-
if (!Dirs.empty()) {
656-
addSystemIncludes(DriverArgs, CC1Args, Dirs);
657-
return true;
658-
}
659-
}
660-
return false;
661-
};
662-
663-
// Add %INCLUDE%-like dirs via /external:env: flags.
664-
for (const auto &Var :
665-
DriverArgs.getAllArgValues(options::OPT__SLASH_external_env)) {
666-
AddSystemIncludesFromEnv(Var);
667-
}
668-
669651
// Add DIA SDK include if requested.
670652
if (const Arg *A = DriverArgs.getLastArg(options::OPT__SLASH_diasdkdir,
671653
options::OPT__SLASH_winsysroot)) {
@@ -682,12 +664,14 @@ void MSVCToolChain::AddClangSystemIncludeArgs(const ArgList &DriverArgs,
682664
if (DriverArgs.hasArg(options::OPT_nostdlibinc))
683665
return;
684666

685-
// Honor %INCLUDE% and %EXTERNAL_INCLUDE%. It should have essential search
686-
// paths set by vcvarsall.bat. Skip if the user expressly set a vctoolsdir.
667+
// Add paths from the INCLUDE and EXTERNAL_INCLUDE environment variables if
668+
// neither a vctoolsdir or winsysroot directory has been explicitly specified.
669+
// If any paths are present in these environment variables, then skip adding
670+
// additional system directories.
687671
if (!DriverArgs.getLastArg(options::OPT__SLASH_vctoolsdir,
688672
options::OPT__SLASH_winsysroot)) {
689-
bool Found = AddSystemIncludesFromEnv("INCLUDE");
690-
Found |= AddSystemIncludesFromEnv("EXTERNAL_INCLUDE");
673+
bool Found = addSystemIncludesFromEnv(DriverArgs, CC1Args, "INCLUDE");
674+
Found |= addSystemIncludesFromEnv(DriverArgs, CC1Args, "EXTERNAL_INCLUDE");
691675
if (Found)
692676
return;
693677
}

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3190,19 +3190,27 @@ static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts,
31903190
auto It = Opts.UserEntries.begin();
31913191
auto End = Opts.UserEntries.end();
31923192

3193-
// Add -I... and -F... options in order.
3194-
for (; It < End && Matches(*It, {frontend::Angled}, std::nullopt, true);
3193+
// Add the -I..., -F..., and -iexternal options in order.
3194+
for (; It < End && Matches(*It, {frontend::Angled, frontend::External},
3195+
std::nullopt, true);
31953196
++It) {
31963197
OptSpecifier Opt = [It, Matches]() {
31973198
if (Matches(*It, frontend::Angled, true, true))
31983199
return OPT_F;
31993200
if (Matches(*It, frontend::Angled, false, true))
32003201
return OPT_I;
3202+
if (Matches(*It, frontend::External, false, true))
3203+
return OPT_iexternal;
32013204
llvm_unreachable("Unexpected HeaderSearchOptions::Entry.");
32023205
}();
32033206

32043207
GenerateArg(Consumer, Opt, It->Path);
3205-
};
3208+
}
3209+
3210+
// Add the paths for the -iexternal-env= and -iexternal-after options in
3211+
// order.
3212+
for (; It < End && Matches(*It, {frontend::ExternalAfter}, false, true); ++It)
3213+
GenerateArg(Consumer, OPT_iexternal_after, It->Path);
32063214

32073215
// Note: some paths that came from "[-iprefix=xx] -iwithprefixbefore=yy" may
32083216
// have already been generated as "-I[xx]yy". If that's the case, their
@@ -3312,7 +3320,6 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
33123320
llvm::CachedHashString(MacroDef.split('=').first));
33133321
}
33143322

3315-
// Add -I... and -F... options in order.
33163323
bool IsSysrootSpecified =
33173324
Args.hasArg(OPT__sysroot_EQ) || Args.hasArg(OPT_isysroot);
33183325

@@ -3331,12 +3338,21 @@ static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
33313338
return A->getValue();
33323339
};
33333340

3334-
for (const auto *A : Args.filtered(OPT_I, OPT_F)) {
3341+
// Add the -I..., -F..., and -iexternal options in order.
3342+
for (const auto *A : Args.filtered(OPT_I, OPT_F, OPT_iexternal)) {
3343+
frontend::IncludeDirGroup Group = frontend::Angled;
3344+
if (A->getOption().matches(OPT_iexternal))
3345+
Group = frontend::External;
33353346
bool IsFramework = A->getOption().matches(OPT_F);
3336-
Opts.AddPath(PrefixHeaderPath(A, IsFramework), frontend::Angled,
3337-
IsFramework, /*IgnoreSysroot=*/true);
3347+
Opts.AddPath(PrefixHeaderPath(A, IsFramework), Group, IsFramework,
3348+
/*IgnoreSysroot=*/true);
33383349
}
33393350

3351+
// Add the -iexternal-env= and -iexternal-after options in order.
3352+
for (const auto *A : Args.filtered(OPT_iexternal_after))
3353+
Opts.AddPath(A->getValue(), frontend::ExternalAfter,
3354+
/*IsFramework=*/false, /*IgnoreSysRoot=*/true);
3355+
33403356
// Add -iprefix/-iwithprefix/-iwithprefixbefore options.
33413357
StringRef Prefix = ""; // FIXME: This isn't the correct default prefix.
33423358
for (const auto *A :

0 commit comments

Comments
 (0)