Index: clang/include/clang/Frontend/CompilerInvocation.h =================================================================== --- clang/include/clang/Frontend/CompilerInvocation.h +++ clang/include/clang/Frontend/CompilerInvocation.h @@ -241,6 +241,17 @@ /// This is a (less-efficient) wrapper over generateCC1CommandLine(). std::vector getCC1CommandLine() const; + /// Check that \p Args can be parsed and re-serialized without change, + /// emiting diagnostics for any differences. + /// + /// This check is only suitable for command-lines that are expected to already + /// be canonical. + /// + /// \return false if there are any errors. + static bool checkCC1RoundTrip(ArrayRef Args, + DiagnosticsEngine &Diags, + const char *Argv0 = nullptr); + /// Reset all of the options that are not considered when building a /// module. void resetNonModularOptions(); Index: clang/lib/Basic/LangOptions.cpp =================================================================== --- clang/lib/Basic/LangOptions.cpp +++ clang/lib/Basic/LangOptions.cpp @@ -29,6 +29,14 @@ Name = static_cast(Default); #include "clang/Basic/LangOptions.def" + // Reset "benign" options with implied values (Options.td ImpliedBy relations) + // rather than their defaults. This avoids unexpected combinations and + // invocations that cannot be round-tripped to arguments. + // FIXME: we should derive this automatically from ImpliedBy in tablegen. + AllowFPReassoc = UnsafeFPMath; + NoHonorNaNs = FiniteMathOnly; + NoHonorInfs = FiniteMathOnly; + // These options do not affect AST generation. NoSanitizeFiles.clear(); XRayAlwaysInstrumentFiles.clear(); Index: clang/lib/Frontend/CompilerInvocation.cpp =================================================================== --- clang/lib/Frontend/CompilerInvocation.cpp +++ clang/lib/Frontend/CompilerInvocation.cpp @@ -652,7 +652,9 @@ CompilerInvocation &RealInvocation, CompilerInvocation &DummyInvocation, ArrayRef CommandLineArgs, - DiagnosticsEngine &Diags, const char *Argv0) { + DiagnosticsEngine &Diags, const char *Argv0, + bool CheckAgainstOriginalInvocation = false, + bool ForceRoundTrip = false) { #ifndef NDEBUG bool DoRoundTripDefault = true; #else @@ -660,11 +662,15 @@ #endif bool DoRoundTrip = DoRoundTripDefault; - for (const auto *Arg : CommandLineArgs) { - if (Arg == StringRef("-round-trip-args")) - DoRoundTrip = true; - if (Arg == StringRef("-no-round-trip-args")) - DoRoundTrip = false; + if (ForceRoundTrip) { + DoRoundTrip = true; + } else { + for (const auto *Arg : CommandLineArgs) { + if (Arg == StringRef("-round-trip-args")) + DoRoundTrip = true; + if (Arg == StringRef("-no-round-trip-args")) + DoRoundTrip = false; + } } // If round-trip was not requested, simply run the parser with the real @@ -737,10 +743,13 @@ return false; } - // Generate arguments again, this time from the options we will end up using - // for the rest of the compilation. SmallVector GeneratedArgs2; - Generate(RealInvocation, GeneratedArgs2, SA); + if (CheckAgainstOriginalInvocation) + GeneratedArgs2.assign(CommandLineArgs.begin(), CommandLineArgs.end()); + else + // Generate arguments again, this time from the options we will end up using + // for the rest of the compilation. + Generate(RealInvocation, GeneratedArgs2, SA); // Compares two lists of generated arguments. auto Equal = [](const ArrayRef A, @@ -771,6 +780,24 @@ return Success2; } +bool CompilerInvocation::checkCC1RoundTrip(ArrayRef Args, + DiagnosticsEngine &Diags, + const char *Argv0) { + CompilerInvocation DummyInvocation1, DummyInvocation2; + return RoundTrip( + [](CompilerInvocation &Invocation, ArrayRef CommandLineArgs, + DiagnosticsEngine &Diags, const char *Argv0) { + return CreateFromArgsImpl(Invocation, CommandLineArgs, Diags, Argv0); + }, + [](CompilerInvocation &Invocation, SmallVectorImpl &Args, + StringAllocator SA) { + Args.push_back("-cc1"); + Invocation.generateCC1CommandLine(Args, SA); + }, + DummyInvocation1, DummyInvocation2, Args, Diags, Argv0, + /*CheckAgainstOriginalInvocation=*/true, /*ForceRoundTrip=*/true); +} + static void addDiagnosticArgs(ArgList &Args, OptSpecifier Group, OptSpecifier GroupWithValue, std::vector &Diagnostics) { Index: clang/test/ClangScanDeps/modules-implied-args.c =================================================================== --- /dev/null +++ clang/test/ClangScanDeps/modules-implied-args.c @@ -0,0 +1,46 @@ +// Check that we get canonical round-trippable command-lines, in particular +// for the options modified for modules. + +// RUN: rm -rf %t +// RUN: split-file %s %t +// RUN: sed -e "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json + +// RUN: clang-scan-deps -compilation-database %t/cdb.json -format experimental-full -round-trip-args > %t/result.json +// RUN: cat %t/result.json | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t --check-prefixes=CHECK,NEGATIVE + +// -ffast-math implies -menable-no-infs, -menable-no-nans, and -mreassociate; +// those options are modified by resetNonModularOptions. + +// NEGATIVE-NOT: "-menable-no-infs" +// NEGATIVE-NOT: "-menable-no-nans" +// NEGATIVE-NOT: "-mreassociate" + +// CHECK: "modules": [ +// CHECK-NEXT: { +// CHECK: "clang-module-deps": [] +// CHECK: "command-line": [ +// CHECK: "-ffast-math" +// CHECK: ] +// CHECK: "name": "Mod" +// CHECK: } +// CHECK-NEXT: ] +// CHECK: "translation-units": [ +// CHECK-NEXT: { +// CHECK-NEXT: "commands": [ +// CHECK: { +// CHECK: "command-line": [ +// CHECK: "-ffast-math" +// CHECK: ] + +//--- cdb.json.template +[{ + "file": "DIR/tu.c", + "directory": "DIR", + "command": "clang -fsyntax-only DIR/tu.c -fmodules -fmodules-cache-path=DIR/cache -ffast-math" +}] + +//--- module.modulemap +module Mod { header "mod.h" } +//--- mod.h +//--- tu.c +#include "mod.h" Index: clang/tools/clang-scan-deps/ClangScanDeps.cpp =================================================================== --- clang/tools/clang-scan-deps/ClangScanDeps.cpp +++ clang/tools/clang-scan-deps/ClangScanDeps.cpp @@ -8,6 +8,7 @@ #include "clang/Driver/Driver.h" #include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Tooling/CommonOptionsParser.h" #include "clang/Tooling/DependencyScanning/DependencyScanningService.h" #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" @@ -202,6 +203,12 @@ llvm::cl::init(RDRK_ModifyCompilerPath), llvm::cl::cat(DependencyScannerCategory)); +llvm::cl::opt + RoundTripArgs("round-trip-args", llvm::cl::Optional, + llvm::cl::desc("verify that command-line arguments are " + "canonical by parsing and re-serializing"), + llvm::cl::cat(DependencyScannerCategory)); + llvm::cl::opt Verbose("v", llvm::cl::Optional, llvm::cl::desc("Use verbose output."), llvm::cl::init(false), @@ -280,6 +287,37 @@ Inputs.push_back(std::move(ID)); } + bool roundTripCommand(ArrayRef ArgStrs, + DiagnosticsEngine &Diags) { + if (ArgStrs.empty() || ArgStrs[0] != "-cc1") + return false; + SmallVector Args; + for (const std::string &Arg : ArgStrs) + Args.push_back(Arg.c_str()); + return !CompilerInvocation::checkCC1RoundTrip(Args, Diags); + } + + // Returns \c true if any command lines fail to round-trip. We expect + // commands already be canonical when output by the scanner. + bool roundTripCommands(raw_ostream &ErrOS) { + IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions{}; + TextDiagnosticPrinter DiagConsumer(ErrOS, &*DiagOpts); + IntrusiveRefCntPtr Diags = + CompilerInstance::createDiagnostics(&*DiagOpts, &DiagConsumer, + /*ShouldOwnClient=*/false); + + for (auto &&M : Modules) + if (roundTripCommand(M.second.BuildArguments, *Diags)) + return true; + + for (auto &&I : Inputs) + for (const auto &Cmd : I.Commands) + if (roundTripCommand(Cmd.Arguments, *Diags)) + return true; + + return false; + } + void printFullOutput(raw_ostream &OS) { // Sort the modules by name to get a deterministic order. std::vector ModuleIDs; @@ -602,6 +640,15 @@ } Pool.wait(); +#ifndef NDEBUG + bool DoRoundTripDefault = true; +#else + bool DoRoundTripDefault = false; +#endif + if (RoundTripArgs.getNumOccurrences() ? RoundTripArgs : DoRoundTripDefault) + if (FD.roundTripCommands(llvm::errs())) + HadErrors = true; + if (Format == ScanningOutputFormat::Full) FD.printFullOutput(llvm::outs());