[clang] 225ccf0 - [clang][cli] Command line round-trip for HeaderSearch options

Jan Svoboda via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 4 01:18:43 PST 2021


Author: Jan Svoboda
Date: 2021-02-04T10:18:34+01:00
New Revision: 225ccf0c50a8d4e03b51f2f09a8e8776328363d8

URL: https://github.com/llvm/llvm-project/commit/225ccf0c50a8d4e03b51f2f09a8e8776328363d8
DIFF: https://github.com/llvm/llvm-project/commit/225ccf0c50a8d4e03b51f2f09a8e8776328363d8.diff

LOG: [clang][cli] Command line round-trip for HeaderSearch options

This patch implements generation of remaining header search arguments.
It's done manually in C++ as opposed to TableGen, because we need the flexibility and don't anticipate reuse.

This patch also tests the generation of header search options via a round-trip. This way, the code gets exercised whenever Clang is built and tested in asserts mode. All `check-clang` tests pass.

Reviewed By: dexonsmith

Differential Revision: https://reviews.llvm.org/D94472

Added: 
    clang/test/Frontend/round-trip-cc1-args.c

Modified: 
    clang/CMakeLists.txt
    clang/include/clang/Basic/DiagnosticDriverKinds.td
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Driver/Options.td
    clang/include/clang/Frontend/CompilerInvocation.h
    clang/lib/Frontend/CompilerInvocation.cpp
    clang/tools/driver/cc1_main.cpp
    llvm/include/llvm/Option/ArgList.h
    llvm/lib/Option/ArgList.cpp

Removed: 
    


################################################################################
diff  --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt
index 9e74014134a0..7e1e58fdc138 100644
--- a/clang/CMakeLists.txt
+++ b/clang/CMakeLists.txt
@@ -465,6 +465,8 @@ option(CLANG_ENABLE_STATIC_ANALYZER
 
 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 @@ if(CLANG_ENABLE_ARCMT)
   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
index ad13f923fb63..ad5ccf4c106f 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -536,4 +536,11 @@ def err_drv_invalid_object_mode : Error<"OBJECT_MODE setting %0 is not recognize
 
 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<RoundTripCC1Args>;
+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
index 04ba89aa457e..bceed225e256 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -437,6 +437,7 @@ def ModuleBuild : DiagGroup<"module-build">;
 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
index edaa42741062..d76bc37b88d3 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -520,6 +520,11 @@ def gen_reproducer: Flag<["-"], "gen-reproducer">, InternalDebugOpt,
 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
index 0d83a228c301..233569aa727b 100644
--- a/clang/include/clang/Frontend/CompilerInvocation.h
+++ b/clang/include/clang/Frontend/CompilerInvocation.h
@@ -260,6 +260,18 @@ class CompilerInvocation : public CompilerInvocationBase {
                                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<const char *> &Args,
+                                       CompilerInvocation::StringAllocator SA);
 };
 
 IntrusiveRefCntPtr<llvm::vfs::FileSystem>

diff  --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 6dff00dfc308..d5ff72faac9a 100644
--- 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 @@ static void denormalizeStringImpl(SmallVectorImpl<const char *> &Args,
                                   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 @@ static unsigned getOptimizationLevelSize(ArgList &Args) {
   return 0;
 }
 
-static std::string GetOptName(llvm::opt::OptSpecifier OptSpecifier) {
-  static const OptTable &OptTable = getDriverOptTable();
-  return OptTable.getOption(OptSpecifier).getPrefixedName();
+static void GenerateArg(SmallVectorImpl<const char *> &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<const char *> &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<bool(CompilerInvocation &, ArgList &,
+                                        DiagnosticsEngine &)>;
+
+// Generate part of command line arguments from a member of CompilerInvocation.
+using GenerateFn = llvm::function_ref<void(
+    CompilerInvocation &, SmallVectorImpl<const char *> &,
+    CompilerInvocation::StringAllocator)>;
+
+// Swap between dummy/real instance of a CompilerInvocation member.
+using SwapOptsFn = llvm::function_ref<void(CompilerInvocation &)>;
+
+// 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 
diff erent 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 @@ std::string CompilerInvocation::GetResourcesPath(const char *Argv0,
   return Driver::GetResourcesPath(ClangExecutable, CLANG_RESOURCE_DIR);
 }
 
-static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts,
-                                     SmallVectorImpl<const char *> &Args,
-                                     CompilerInvocation::StringAllocator SA) {
+void CompilerInvocation::GenerateHeaderSearchArgs(
+    HeaderSearchOptions &Opts, SmallVectorImpl<const char *> &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 @@ static void GenerateHeaderSearchArgs(const HeaderSearchOptions &Opts,
       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<frontend::IncludeDirGroup> Groups,
+                    llvm::Optional<bool> IsFramework,
+                    llvm::Optional<bool> 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 @@ static void ParseHeaderSearchArgs(HeaderSearchOptions &Opts, ArgList &Args,
 
   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<HeaderSearchOptions>();
+
+  RoundTrip(
+      [&WorkingDir](CompilerInvocation &Res, ArgList &Args,
+                    DiagnosticsEngine &Diags) {
+        return ::ParseHeaderSearchArgs(Res.getHeaderSearchOpts(), Args, Diags,
+                                       WorkingDir);
+      },
+      [](CompilerInvocation &Res, SmallVectorImpl<const char *> &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 @@ static void GenerateLangArgs(const LangOptions &Opts,
                              SmallVectorImpl<const char *> &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 @@ bool CompilerInvocation::CreateFromArgs(CompilerInvocation &Res,
                                       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
index 000000000000..8e24f7f3a7fa
--- /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
index b4ab6f57345b..396d6ff529f3 100644
--- a/clang/tools/driver/cc1_main.cpp
+++ b/clang/tools/driver/cc1_main.cpp
@@ -203,6 +203,12 @@ int cc1_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
   IntrusiveRefCntPtr<DiagnosticOptions> 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
index 9ce783978185..4ac034635c39 100644
--- a/llvm/include/llvm/Option/ArgList.h
+++ b/llvm/include/llvm/Option/ArgList.h
@@ -137,6 +137,16 @@ class ArgList {
   /// The first and last index of each 
diff erent OptSpecifier ID.
   DenseMap<unsigned, OptRange> OptRanges;
 
+  /// The OptSpecifiers that were queried from this argument list.
+  mutable DenseSet<unsigned> QueriedOpts;
+
+  /// Record the queried OptSpecifiers.
+  template <typename... OptSpecifiers>
+  void recordQueriedOpts(OptSpecifiers... Ids) const {
+    SmallVector<unsigned, 4> 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<OptSpecifier> Ids) const;
@@ -203,6 +213,7 @@ class ArgList {
   template<typename ...OptSpecifiers>
   iterator_range<filtered_iterator<sizeof...(OptSpecifiers)>>
   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 @@ class ArgList {
   template<typename ...OptSpecifiers>
   iterator_range<filtered_reverse_iterator<sizeof...(OptSpecifiers)>>
   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 @@ class ArgList {
       A->render(*this, Output);
   }
 
+  /// AddAllArgsExcept - Render all arguments not matching any of the excluded
+  /// ids.
+  void AddAllArgsExcept(ArgStringList &Output,
+                        const DenseSet<unsigned> &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<OptSpecifier> Ids,
@@ -342,6 +358,12 @@ class ArgList {
   ///
   void ClaimAllArgs() const;
 
+  /// Return the OptSpecifiers queried from this argument list.
+  const DenseSet<unsigned> &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
index ad7be5fbec19..2a59bc8460ee 100644
--- a/llvm/lib/Option/ArgList.cpp
+++ b/llvm/lib/Option/ArgList.cpp
@@ -90,11 +90,22 @@ StringRef ArgList::getLastArgValue(OptSpecifier Id, StringRef Default) const {
 }
 
 std::vector<std::string> ArgList::getAllArgValues(OptSpecifier Id) const {
+  recordQueriedOpts(Id);
   SmallVector<const char *, 16> Values;
   AddAllArgValues(Values, Id);
   return std::vector<std::string>(Values.begin(), Values.end());
 }
 
+void ArgList::AddAllArgsExcept(ArgStringList &Output,
+                               const DenseSet<unsigned> &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<OptSpecifier> Ids,
                                ArrayRef<OptSpecifier> ExcludeIds) const {


        


More information about the cfe-commits mailing list