diff --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt --- a/clang/CMakeLists.txt +++ b/clang/CMakeLists.txt @@ -465,6 +465,8 @@ option(CLANG_ENABLE_PROTO_FUZZER "Build Clang protobuf fuzzer." OFF) +option(CLANG_ROUND_TRIP_CC1_ARGS "Round-trip command line arguments in -cc1." OFF) + if(NOT CLANG_ENABLE_STATIC_ANALYZER AND CLANG_ENABLE_ARCMT) message(FATAL_ERROR "Cannot disable static analyzer while enabling ARCMT or Z3") endif() @@ -473,6 +475,10 @@ set(CLANG_ENABLE_OBJC_REWRITER ON) endif() +if (CLANG_ROUND_TRIP_CC1_ARGS) + add_definitions(-DCLANG_ROUND_TRIP_CC1_ARGS=ON) +endif() + # Clang version information set(CLANG_EXECUTABLE_VERSION "${CLANG_VERSION_MAJOR}" CACHE STRING diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td --- a/clang/include/clang/Basic/DiagnosticDriverKinds.td +++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td @@ -536,4 +536,11 @@ def err_aix_default_altivec_abi : Error< "The default Altivec ABI on AIX is not yet supported, use '-mabi=vec-extabi' for the extended Altivec ABI">; + +def note_cc1_round_trip_original : Note<"Original arguments in %0 round-trip: %1">; +def note_cc1_round_trip_generated : Note<"Generated arguments #%1 in %0 round-trip: %2">; +def remark_cc1_round_trip_generated : Remark<"Generated arguments #%1 in %0 round-trip: %2">, InGroup; +def err_cc1_round_trip_fail_then_ok : Error<"Original arguments parse failed, then succeeded in %0 round-trip">; +def err_cc1_round_trip_ok_then_fail : Error<"Generated arguments parse failed in %0 round-trip">; +def err_cc1_round_trip_mismatch : Error<"Generated arguments do not match in %0 round-trip">; } diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -437,6 +437,7 @@ def ModuleImport : DiagGroup<"module-import">; def ModuleConflict : DiagGroup<"module-conflict">; def ModuleFileExtension : DiagGroup<"module-file-extension">; +def RoundTripCC1Args : DiagGroup<"round-trip-cc1-args">; def NewlineEOF : DiagGroup<"newline-eof">; def Nullability : DiagGroup<"nullability">; def NullabilityDeclSpec : DiagGroup<"nullability-declspec">; 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 @@ -520,6 +520,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<"Enable command line arguments round-trip.">; +def no_round_trip_args : Flag<["-"], "no-round-trip-args">, Flags<[CC1Option, NoDriverOption]>, + HelpText<"Disable command line arguments round-trip.">; + 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 @@ -260,6 +260,18 @@ const llvm::Triple &T, const std::string &OutputFile, const LangOptions &LangOptsRef); + + /// Parse command 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/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -38,6 +38,7 @@ #include "clang/Frontend/FrontendPluginRegistry.h" #include "clang/Frontend/MigratorOptions.h" #include "clang/Frontend/PreprocessorOutputOptions.h" +#include "clang/Frontend/TextDiagnosticBuffer.h" #include "clang/Frontend/Utils.h" #include "clang/Lex/HeaderSearchOptions.h" #include "clang/Lex/PreprocessorOptions.h" @@ -48,6 +49,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 +206,7 @@ const char *Spelling, CompilerInvocation::StringAllocator SA, Option::OptionClass OptClass, unsigned, - Twine Value) { + const Twine &Value) { switch (OptClass) { case Option::SeparateClass: case Option::JoinedOrSeparateClass: @@ -548,9 +550,174 @@ 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); +} + +// Parse subset of command line arguments into a member of CompilerInvocation. +using ParseFn = llvm::function_ref; + +// Generate part of command line arguments from a member of CompilerInvocation. +using GenerateFn = llvm::function_ref &, + CompilerInvocation::StringAllocator)>; + +// Swap between dummy/real instance of a CompilerInvocation member. +using SwapOptsFn = llvm::function_ref; + +// Performs round-trip of command line arguments if OriginalArgs contain +// "-round-trip-args". Effectively runs the Parse function for a part of +// CompilerInvocation on command line arguments that were already once parsed +// and generated. This is used to check the Generate function produces arguments +// that are semantically equivalent to those that were used to create +// CompilerInvocation. +static bool RoundTrip(ParseFn Parse, GenerateFn Generate, SwapOptsFn SwapOpts, + CompilerInvocation &Res, ArgList &OriginalArgs, + DiagnosticsEngine &Diags, StringRef OptsName) { + // FIXME: Switch to '#ifndef NDEBUG' when possible. +#ifdef CLANG_ROUND_TRIP_CC1_ARGS + bool DoRoundTripDefault = true; +#else + bool DoRoundTripDefault = false; +#endif + + bool DoRoundTrip = OriginalArgs.hasFlag( + OPT_round_trip_args, OPT_no_round_trip_args, DoRoundTripDefault); + + // If round-trip was not requested, simply run the parser with the original + // options and diagnostics. + if (!DoRoundTrip) + return Parse(Res, OriginalArgs, Diags); + + // Serializes quoted (and potentially escaped) arguments. + auto SerializeArgs = [](ArgStringList &Args) { + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + for (const char *Arg : Args) { + llvm::sys::printArg(OS, Arg, /*Quote=*/true); + OS << ' '; + } + OS.flush(); + return Buffer; + }; + + OriginalArgs.clearQueriedOpts(); + + // Setup a dummy DiagnosticsEngine. + DiagnosticsEngine DummyDiags(new DiagnosticIDs(), new DiagnosticOptions()); + DummyDiags.setClient(new TextDiagnosticBuffer()); + + // Run the first parse on the original arguments with dummy options and + // diagnostics. + SwapOpts(Res); + if (!Parse(Res, OriginalArgs, DummyDiags)) { + // If the first parse did not succeed, it must be user mistake (invalid + // command line arguments). We won't be able to generate arguments that + // would reproduce the same result. Let's fail again with the original + // options and diagnostics, so all side-effects of parsing are visible. + SwapOpts(Res); + if (!Parse(Res, OriginalArgs, Diags)) + return false; + + // Parse with original options and diagnostics succeeded even though it + // shouldn't have. Something is off. + Diags.Report(diag::err_cc1_round_trip_fail_then_ok) << OptsName; + ArgStringList OriginalStrings; + OriginalArgs.AddAllArgsExcept(OriginalStrings, {}); + Diags.Report(diag::note_cc1_round_trip_original) + << OptsName << SerializeArgs(OriginalStrings); + return false; + } + + // Setup string allocator. + llvm::BumpPtrAllocator Alloc; + llvm::StringSaver StringPool(Alloc); + auto SA = [&StringPool](const Twine &Arg) { + return StringPool.save(Arg).data(); + }; + + // Generate arguments. First simply copy any arguments the parser did not + // query. Then, use the Generate function that uses the CompilerInvocation + // options instance as the source of truth. If Generate is the inverse of + // Parse, the newly generated arguments must have the same semantics as the + // original. + ArgStringList GeneratedStrings1; + OriginalArgs.AddAllArgsExcept(GeneratedStrings1, + OriginalArgs.getQueriedOpts()); + Generate(Res, GeneratedStrings1, SA); + + // Process the generated arguments. + unsigned MissingArgIndex1, MissingArgCount1; + InputArgList GeneratedArgs1 = + getDriverOptTable().ParseArgs(GeneratedStrings1, MissingArgIndex1, + MissingArgCount1, options::CC1Option); + + // TODO: Once we're responsible for generating all arguments, check that we + // didn't create any unknown options or omitted required values. + + // Run the second parse, now on the generated arguments, and with the original + // options and diagnostics. The result is what we will end up using for the + // rest of compilation, so if Generate is not inverse of Parse, something down + // the line will break. + SwapOpts(Res); + bool Success2 = Parse(Res, GeneratedArgs1, Diags); + + // The first parse on original arguments succeeded, but second parse of + // generated arguments failed. Something must be wrong with the generator. + if (!Success2) { + Diags.Report(diag::err_cc1_round_trip_ok_then_fail) << OptsName; + Diags.Report(diag::note_cc1_round_trip_generated) + << OptsName << 1 << SerializeArgs(GeneratedStrings1); + return false; + } + + // Generate arguments again, this time from the options we will end up using + // for the rest of the compilation. + ArgStringList GeneratedStrings2; + GeneratedArgs1.AddAllArgsExcept(GeneratedStrings2, + GeneratedArgs1.getQueriedOpts()); + Generate(Res, GeneratedStrings2, SA); + + // Compares two lists of generated arguments. + auto Equal = [](const ArgStringList &A, const ArgStringList &B) { + return std::equal(A.begin(), A.end(), B.begin(), B.end(), + [](const char *AElem, const char *BElem) { + return StringRef(AElem) == StringRef(BElem); + }); + }; + + // If we generated different arguments from what we assume are two + // semantically equivalent CompilerInvocations, the Generate function may + // be non-deterministic. + if (!Equal(GeneratedStrings1, GeneratedStrings2)) { + Diags.Report(diag::err_cc1_round_trip_mismatch) << OptsName; + Diags.Report(diag::note_cc1_round_trip_generated) + << OptsName << 1 << SerializeArgs(GeneratedStrings1); + Diags.Report(diag::note_cc1_round_trip_generated) + << OptsName << 2 << SerializeArgs(GeneratedStrings2); + return false; + } + + Diags.Report(diag::remark_cc1_round_trip_generated) + << OptsName << 1 << SerializeArgs(GeneratedStrings1); + Diags.Report(diag::remark_cc1_round_trip_generated) + << OptsName << 2 << SerializeArgs(GeneratedStrings2); + + return Success2; } static void addDiagnosticArgs(ArgList &Args, OptSpecifier Group, @@ -1837,9 +2004,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, \ @@ -1851,9 +2018,119 @@ 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, +static bool ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args, DiagnosticsEngine &Diags, const std::string &WorkingDir) { HeaderSearchOptions *HeaderSearchOpts = &Opts; @@ -1984,6 +2261,31 @@ for (const auto *A : Args.filtered(OPT_ivfsoverlay)) Opts.AddVFSOverlayFile(A->getValue()); + + return Success; +} + +void CompilerInvocation::ParseHeaderSearchArgs(CompilerInvocation &Res, + HeaderSearchOptions &Opts, + ArgList &Args, + DiagnosticsEngine &Diags, + const std::string &WorkingDir) { + auto DummyOpts = std::make_shared(); + + RoundTrip( + [&WorkingDir](CompilerInvocation &Res, ArgList &Args, + DiagnosticsEngine &Diags) { + return ::ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args, Diags, + WorkingDir); + }, + [](CompilerInvocation &Res, SmallVectorImpl &GeneratedArgs, + CompilerInvocation::StringAllocator SA) { + GenerateHeaderSearchArgs(Res.getHeaderSearchOpts(), GeneratedArgs, SA); + }, + [&DummyOpts](CompilerInvocation &Res) { + Res.HeaderSearchOpts.swap(DummyOpts); + }, + Res, Args, Diags, "HeaderSearchOptions"); } void CompilerInvocation::setLangDefaults(LangOptions &Opts, InputKind IK, @@ -2203,9 +2505,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, @@ -2835,7 +3137,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/clang/test/Frontend/round-trip-cc1-args.c b/clang/test/Frontend/round-trip-cc1-args.c new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/round-trip-cc1-args.c @@ -0,0 +1,7 @@ +// RUN: %clang_cc1 %s -no-round-trip-args -Rround-trip-cc1-args 2>&1 | FileCheck %s -check-prefix=CHECK-WITHOUT-ROUND-TRIP -allow-empty +// RUN: %clang_cc1 %s -round-trip-args 2>&1 | FileCheck %s -check-prefix=CHECK-ROUND-TRIP-WITHOUT-REMARKS -allow-empty +// RUN: %clang_cc1 %s -round-trip-args -Rround-trip-cc1-args 2>&1 | FileCheck %s -check-prefix=CHECK-ROUND-TRIP-WITH-REMARKS + +// CHECK-WITHOUT-ROUND-TRIP-NOT: remark: +// CHECK-ROUND-TRIP-WITHOUT-REMARKS-NOT: remark: +// CHECK-ROUND-TRIP-WITH-REMARKS: remark: Generated arguments {{.*}} in {{.*}} round-trip: {{.*}} diff --git a/clang/tools/driver/cc1_main.cpp b/clang/tools/driver/cc1_main.cpp --- a/clang/tools/driver/cc1_main.cpp +++ b/clang/tools/driver/cc1_main.cpp @@ -203,6 +203,12 @@ IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); TextDiagnosticBuffer *DiagsBuffer = new TextDiagnosticBuffer; DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagsBuffer); + + // Setup round-trip remarks for the DiagnosticsEngine used in CreateFromArgs. + if (find(Argv, StringRef("-Rround-trip-cc1-args")) != Argv.end()) + Diags.setSeverity(diag::remark_cc1_round_trip_generated, + diag::Severity::Remark, {}); + bool Success = CompilerInvocation::CreateFromArgs(Clang->getInvocation(), Argv, Diags, Argv0); 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 {