diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -590,6 +590,11 @@ def gen_cdb_fragment_path: Separate<["-"], "gen-cdb-fragment-path">, InternalDebugOpt, HelpText<"Emit a compilation database fragment to the specified directory">; +def round_trip_args : Flag<["-"], "round-trip-args">, Flags<[CC1Option, NoDriverOption]>, + HelpText<"Performs 'parse-generate-parse' round-trip of command line arguments.">; +def round_trip_args_debug : Flag<["-"], "round-trip-args-debug">, Flags<[CC1Option, NoDriverOption]>, + HelpText<"Print debug information when performing the 'parse-generate-parse' round-trip of command line arguments.">; + def _migrate : Flag<["--"], "migrate">, Flags<[NoXarchOption]>, HelpText<"Run the migrator">; def ccc_objcmt_migrate : Separate<["-"], "ccc-objcmt-migrate">, diff --git a/clang/include/clang/Frontend/CompilerInvocation.h b/clang/include/clang/Frontend/CompilerInvocation.h --- a/clang/include/clang/Frontend/CompilerInvocation.h +++ b/clang/include/clang/Frontend/CompilerInvocation.h @@ -272,6 +272,18 @@ const llvm::Triple &T, const std::string &OutputFile, const LangOptions &LangOptsRef); + + /// Parse commnad line options that map to HeaderSearchOptions. + static void ParseHeaderSearchArgs(CompilerInvocation &Res, + HeaderSearchOptions &Opts, + llvm::opt::ArgList &Args, + DiagnosticsEngine &Diags, + const std::string &WorkingDir); + + /// Generate command line options from HeaderSearchOptions. + static void GenerateHeaderSearchArgs(HeaderSearchOptions &Opts, + SmallVectorImpl &Args, + CompilerInvocation::StringAllocator SA); }; IntrusiveRefCntPtr diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4475,6 +4475,9 @@ const bool IsAssertBuild = true; #endif + if (IsAssertBuild) + CmdArgs.push_back("-round-trip-args"); + // Disable the verification pass in -asserts builds. if (!IsAssertBuild) CmdArgs.push_back("-disable-llvm-verifier"); diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -48,6 +48,7 @@ #include "llvm/ADT/APInt.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/CachedHashString.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/FloatingPointMode.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/None.h" @@ -204,7 +205,7 @@ const char *Spelling, CompilerInvocation::StringAllocator SA, Option::OptionClass OptClass, unsigned, - Twine Value) { + const Twine &Value) { switch (OptClass) { case Option::SeparateClass: case Option::JoinedOrSeparateClass: @@ -523,9 +524,94 @@ return 0; } -static std::string GetOptName(llvm::opt::OptSpecifier OptSpecifier) { - static const OptTable &OptTable = getDriverOptTable(); - return OptTable.getOption(OptSpecifier).getPrefixedName(); +static void GenerateArg(SmallVectorImpl &Args, + llvm::opt::OptSpecifier OptSpecifier, + CompilerInvocation::StringAllocator SA) { + Option Opt = getDriverOptTable().getOption(OptSpecifier); + denormalizeSimpleFlag(Args, SA(Opt.getPrefix() + Opt.getName()), SA, + Option::OptionClass::FlagClass, 0); +} + +static void GenerateArg(SmallVectorImpl &Args, + llvm::opt::OptSpecifier OptSpecifier, + const Twine &Value, + CompilerInvocation::StringAllocator SA) { + Option Opt = getDriverOptTable().getOption(OptSpecifier); + denormalizeString(Args, SA(Opt.getPrefix() + Opt.getName()), SA, + Opt.getKind(), 0, Value); +} + +static void DebugArgs(ArgStringList &Args, const char *Description) { + llvm::dbgs() << Description; + llvm::interleave(Args, llvm::dbgs(), " "); + llvm::dbgs() << "\n"; +} + +template +static void RoundTrip(ParseFn &&Parse, GenerateFn &&Generate, SwapFn &&Swap, + CompilerInvocation &Res, ArgList &OriginalArgs) { + bool DoRoundTrip = OriginalArgs.hasArg(OPT_round_trip_args); + bool DebugRoundTrip = OriginalArgs.hasArg(OPT_round_trip_args_debug); + + if (!DoRoundTrip) { + Parse(Res, OriginalArgs); + return; + } + + auto SA = [&Res](const Twine &Arg) { return Res.AllocateString(Arg); }; + + // Invokes the Parse functor and returns set of queried command line options. + auto ParseAndGetOpts = [&Parse](CompilerInvocation &Res, ArgList &Args) { + Args.clearQueriedOpts(); + Parse(Res, Args); + return Args.getQueriedOpts(); + }; + + // Generates new argument string list. All arguments except those in Tested + // are taken directly from Args. Arguments in Tested are generated by calling + // the Generate functor. + auto GenerateArgs = [&Generate, &SA](CompilerInvocation &Res, ArgList &Args, + llvm::DenseSet Tested) { + ArgStringList Generated; + Args.AddAllArgsExcept(Generated, Tested); + Generate(Res, Args, Generated, SA); + return Generated; + }; + + llvm::DenseSet QueriedBefore = OriginalArgs.getQueriedOpts(); + + // Run the first parse (of original arguments) on a dummy instance. + Swap(Res); + + llvm::DenseSet QueriedOpts1 = ParseAndGetOpts(Res, OriginalArgs); + + ArgStringList GenArgs1Strs = GenerateArgs(Res, OriginalArgs, QueriedOpts1); + + unsigned MissingArgIndex, MissingArgCount; + InputArgList GenArgs1 = getDriverOptTable().ParseArgs( + GenArgs1Strs, MissingArgIndex, MissingArgCount, options::CC1Option); + + // Run the second parse (of generated arguments) on the original instance. + Swap(Res); + + llvm::DenseSet QueriedOpts2 = ParseAndGetOpts(Res, GenArgs1); + + ArgStringList GenArgs2Strs = GenerateArgs(Res, GenArgs1, QueriedOpts2); + + bool GenArgsMatch = + std::equal(GenArgs1Strs.begin(), GenArgs1Strs.end(), GenArgs2Strs.begin(), + GenArgs2Strs.end(), [](const char *A, const char *B) { + return StringRef(A) == StringRef(B); + }); + + if (!GenArgsMatch || DebugRoundTrip) { + DebugArgs(GenArgs1Strs, "Generated args #1: "); + DebugArgs(GenArgs2Strs, "Generated args #2: "); + } + + if (!GenArgsMatch) + llvm::report_fatal_error("Mismatch between generated arguments during " + "round-tripping."); } static void addDiagnosticArgs(ArgList &Args, OptSpecifier Group, @@ -1799,9 +1885,9 @@ return Driver::GetResourcesPath(ClangExecutable, CLANG_RESOURCE_DIR); } -static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts, - SmallVectorImpl &Args, - CompilerInvocation::StringAllocator SA) { +void CompilerInvocation::GenerateHeaderSearchArgs( + HeaderSearchOptions &Opts, SmallVectorImpl &Args, + CompilerInvocation::StringAllocator SA) { const HeaderSearchOptions *HeaderSearchOpts = &Opts; #define HEADER_SEARCH_OPTION_WITH_MARSHALLING( \ PREFIX_TYPE, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ @@ -1813,6 +1899,116 @@ IMPLIED_CHECK, IMPLIED_VALUE, DENORMALIZER, EXTRACTOR, TABLE_INDEX) #include "clang/Driver/Options.inc" #undef HEADER_SEARCH_OPTION_WITH_MARSHALLING + + if (Opts.UseLibcxx) + GenerateArg(Args, OPT_stdlib_EQ, "libc++", SA); + + if (!Opts.ModuleCachePath.empty()) + GenerateArg(Args, OPT_fmodules_cache_path, Opts.ModuleCachePath, SA); + + for (const auto &File : Opts.PrebuiltModuleFiles) + GenerateArg(Args, OPT_fmodule_file, File.first + "=" + File.second, SA); + + for (const auto &Path : Opts.PrebuiltModulePaths) + GenerateArg(Args, OPT_fprebuilt_module_path, Path, SA); + + for (const auto &Macro : Opts.ModulesIgnoreMacros) + GenerateArg(Args, OPT_fmodules_ignore_macro, Macro.val(), SA); + + auto Matches = [](const HeaderSearchOptions::Entry &Entry, + llvm::ArrayRef Groups, + llvm::Optional IsFramework, + llvm::Optional IgnoreSysRoot) { + return llvm::find(Groups, Entry.Group) != Groups.end() && + (!IsFramework || (Entry.IsFramework == *IsFramework)) && + (!IgnoreSysRoot || (Entry.IgnoreSysRoot == *IgnoreSysRoot)); + }; + + auto It = Opts.UserEntries.begin(); + auto End = Opts.UserEntries.end(); + + // Add -I..., -F..., and -index-header-map options in order. + for (; It < End && + Matches(*It, {frontend::IndexHeaderMap, frontend::Angled}, None, true); + ++It) { + OptSpecifier Opt = [It, Matches]() { + if (Matches(*It, frontend::IndexHeaderMap, true, true)) + return OPT_F; + if (Matches(*It, frontend::IndexHeaderMap, false, true)) + return OPT_I; + if (Matches(*It, frontend::Angled, true, true)) + return OPT_F; + if (Matches(*It, frontend::Angled, false, true)) + return OPT_I; + llvm_unreachable("Unexpected HeaderSearchOptions::Entry."); + }(); + + if (It->Group == frontend::IndexHeaderMap) + GenerateArg(Args, OPT_index_header_map, SA); + GenerateArg(Args, Opt, It->Path, SA); + }; + + // Note: some paths that came from "[-iprefix=xx] -iwithprefixbefore=yy" may + // have already been generated as "-I[xx]yy". If that's the case, their + // position on command line was such that this has no semantic impact on + // include paths. + for (; It < End && + Matches(*It, {frontend::After, frontend::Angled}, false, true); + ++It) { + OptSpecifier Opt = + It->Group == frontend::After ? OPT_iwithprefix : OPT_iwithprefixbefore; + GenerateArg(Args, Opt, It->Path, SA); + } + + // Note: Some paths that came from "-idirafter=xxyy" may have already been + // generated as "-iwithprefix=xxyy". 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::After}, false, true); ++It) + GenerateArg(Args, OPT_idirafter, It->Path, SA); + for (; It < End && Matches(*It, {frontend::Quoted}, false, true); ++It) + GenerateArg(Args, OPT_iquote, It->Path, SA); + for (; It < End && Matches(*It, {frontend::System}, false, None); ++It) + GenerateArg(Args, It->IgnoreSysRoot ? OPT_isystem : OPT_iwithsysroot, + It->Path, SA); + for (; It < End && Matches(*It, {frontend::System}, true, true); ++It) + GenerateArg(Args, OPT_iframework, It->Path, SA); + for (; It < End && Matches(*It, {frontend::System}, true, false); ++It) + GenerateArg(Args, OPT_iframeworkwithsysroot, It->Path, SA); + + // Add the paths for the various language specific isystem flags. + for (; It < End && Matches(*It, {frontend::CSystem}, false, true); ++It) + GenerateArg(Args, OPT_c_isystem, It->Path, SA); + for (; It < End && Matches(*It, {frontend::CXXSystem}, false, true); ++It) + GenerateArg(Args, OPT_cxx_isystem, It->Path, SA); + for (; It < End && Matches(*It, {frontend::ObjCSystem}, false, true); ++It) + GenerateArg(Args, OPT_objc_isystem, It->Path, SA); + for (; It < End && Matches(*It, {frontend::ObjCXXSystem}, false, true); ++It) + GenerateArg(Args, OPT_objcxx_isystem, It->Path, SA); + + // Add the internal paths from a driver that detects standard include paths. + // 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); + ++It) { + OptSpecifier Opt = It->Group == frontend::System + ? OPT_internal_isystem + : OPT_internal_externc_isystem; + GenerateArg(Args, Opt, It->Path, SA); + } + + assert(It == End && "Unhandled HeaderSearchOption::Entry."); + + // Add the path prefixes which are implicitly treated as being system headers. + for (const auto &P : Opts.SystemHeaderPrefixes) { + OptSpecifier Opt = P.IsSystemHeader ? OPT_system_header_prefix + : OPT_no_system_header_prefix; + GenerateArg(Args, Opt, P.Prefix, SA); + } + + for (const std::string &F : Opts.VFSOverlayFiles) + GenerateArg(Args, OPT_ivfsoverlay, F, SA); } static void ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args, @@ -1948,6 +2144,31 @@ Opts.AddVFSOverlayFile(A->getValue()); } +void CompilerInvocation::ParseHeaderSearchArgs(CompilerInvocation &Res, + HeaderSearchOptions &Opts, + ArgList &Args, + DiagnosticsEngine &Diags, + const std::string &WorkingDir) { + auto Temp = std::make_shared(); + + auto Parse = [&Diags, &WorkingDir](CompilerInvocation &Res, ArgList &Args) { + ::ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args, Diags, WorkingDir); + }; + + auto Generate = [](CompilerInvocation &Res, + ArgList &OriginalArgs, + SmallVectorImpl &GeneratedArgs, + CompilerInvocation::StringAllocator SA) { + GenerateHeaderSearchArgs(Res.getHeaderSearchOpts(), GeneratedArgs, SA); + }; + + auto Swap = [&Temp](CompilerInvocation &Res) { + Res.HeaderSearchOpts.swap(Temp); + }; + + RoundTrip(Parse, Generate, Swap, Res, Args); +} + void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK, const llvm::Triple &T, std::vector &Includes, @@ -2183,9 +2404,9 @@ SmallVectorImpl &Args, CompilerInvocation::StringAllocator SA) { if (Opts.IncludeDefaultHeader) - Args.push_back(SA(GetOptName(OPT_finclude_default_header))); + GenerateArg(Args, OPT_finclude_default_header, SA); if (Opts.DeclareOpenCLBuiltins) - Args.push_back(SA(GetOptName(OPT_fdeclare_opencl_builtins))); + GenerateArg(Args, OPT_fdeclare_opencl_builtins, SA); } void CompilerInvocation::ParseLangArgs(LangOptions &Opts, ArgList &Args, @@ -2966,7 +3187,7 @@ LangOpts.IsHeaderFile); ParseTargetArgs(Res.getTargetOpts(), Args, Diags); llvm::Triple T(Res.getTargetOpts().Triple); - ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args, Diags, + ParseHeaderSearchArgs(Res, Res.getHeaderSearchOpts(), Args, Diags, Res.getFileSystemOpts().WorkingDir); if (DashX.getFormat() == InputKind::Precompiled || DashX.getLanguage() == Language::LLVM_IR) { diff --git a/llvm/include/llvm/Option/ArgList.h b/llvm/include/llvm/Option/ArgList.h --- a/llvm/include/llvm/Option/ArgList.h +++ b/llvm/include/llvm/Option/ArgList.h @@ -137,6 +137,16 @@ /// The first and last index of each different OptSpecifier ID. DenseMap OptRanges; + /// The OptSpecifiers that were queried from this argument list. + mutable DenseSet QueriedOpts; + + /// Record the queried OptSpecifiers. + template + void recordQueriedOpts(OptSpecifiers... Ids) const { + SmallVector OptsSpecifiers({toOptSpecifier(Ids).getID()...}); + QueriedOpts.insert(OptsSpecifiers.begin(), OptsSpecifiers.end()); + } + /// Get the range of indexes in which options with the specified IDs might /// reside, or (0, 0) if there are no such options. OptRange getRange(std::initializer_list Ids) const; @@ -203,6 +213,7 @@ template iterator_range> filtered(OptSpecifiers ...Ids) const { + recordQueriedOpts(Ids...); OptRange Range = getRange({toOptSpecifier(Ids)...}); auto B = Args.begin() + Range.first; auto E = Args.begin() + Range.second; @@ -214,6 +225,7 @@ template iterator_range> filtered_reverse(OptSpecifiers ...Ids) const { + recordQueriedOpts(Ids...); OptRange Range = getRange({toOptSpecifier(Ids)...}); auto B = Args.rend() - Range.second; auto E = Args.rend() - Range.first; @@ -308,6 +320,10 @@ A->render(*this, Output); } + /// AddAllArgsExcept - Render all arguments not matching any of the excluded + /// ids. + void AddAllArgsExcept(ArgStringList &Output, + const DenseSet &ExcludeIds) const; /// AddAllArgsExcept - Render all arguments matching any of the given ids /// and not matching any of the excluded ids. void AddAllArgsExcept(ArgStringList &Output, ArrayRef Ids, @@ -342,6 +358,12 @@ /// void ClaimAllArgs() const; + /// Return the OptSpecifiers queried from this argument list. + const DenseSet &getQueriedOpts() const { return QueriedOpts; } + + /// Clear the set of queried OptSpecifiers. + void clearQueriedOpts() const { QueriedOpts.clear(); } + /// @} /// @name Arg Synthesis /// @{ diff --git a/llvm/lib/Option/ArgList.cpp b/llvm/lib/Option/ArgList.cpp --- a/llvm/lib/Option/ArgList.cpp +++ b/llvm/lib/Option/ArgList.cpp @@ -90,11 +90,22 @@ } std::vector ArgList::getAllArgValues(OptSpecifier Id) const { + recordQueriedOpts(Id); SmallVector Values; AddAllArgValues(Values, Id); return std::vector(Values.begin(), Values.end()); } +void ArgList::AddAllArgsExcept(ArgStringList &Output, + const DenseSet &ExcludeIds) const { + for (const Arg *Arg : *this) { + if (!ExcludeIds.contains(Arg->getOption().getID())) { + Arg->claim(); + Arg->render(*this, Output); + } + } +} + void ArgList::AddAllArgsExcept(ArgStringList &Output, ArrayRef Ids, ArrayRef ExcludeIds) const {