Skip to content

Commit cf73d3f

Browse files
committed
[clang][deps] Ensure module invocation can be serialized
When reseting modular options, propagate the values from certain options that have ImpliedBy relations instead of setting to the default. Also, verify in clang-scan-deps that the command line produced round trips exactly. Ideally we would automatically derive the set of options that need this kind of propagation, but for now there aren't very many impacted. rdar://105148590 Differential Revision: https://reviews.llvm.org/D143446
1 parent 79971d0 commit cf73d3f

File tree

5 files changed

+180
-27
lines changed

5 files changed

+180
-27
lines changed

clang/include/clang/Frontend/CompilerInvocation.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,17 @@ class CompilerInvocation : public CompilerInvocationRefBase,
241241
/// This is a (less-efficient) wrapper over generateCC1CommandLine().
242242
std::vector<std::string> getCC1CommandLine() const;
243243

244+
/// Check that \p Args can be parsed and re-serialized without change,
245+
/// emiting diagnostics for any differences.
246+
///
247+
/// This check is only suitable for command-lines that are expected to already
248+
/// be canonical.
249+
///
250+
/// \return false if there are any errors.
251+
static bool checkCC1RoundTrip(ArrayRef<const char *> Args,
252+
DiagnosticsEngine &Diags,
253+
const char *Argv0 = nullptr);
254+
244255
/// Reset all of the options that are not considered when building a
245256
/// module.
246257
void resetNonModularOptions();

clang/lib/Basic/LangOptions.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ void LangOptions::resetNonModularOptions() {
2929
Name = static_cast<unsigned>(Default);
3030
#include "clang/Basic/LangOptions.def"
3131

32+
// Reset "benign" options with implied values (Options.td ImpliedBy relations)
33+
// rather than their defaults. This avoids unexpected combinations and
34+
// invocations that cannot be round-tripped to arguments.
35+
// FIXME: we should derive this automatically from ImpliedBy in tablegen.
36+
AllowFPReassoc = UnsafeFPMath;
37+
NoHonorNaNs = FiniteMathOnly;
38+
NoHonorInfs = FiniteMathOnly;
39+
3240
// These options do not affect AST generation.
3341
NoSanitizeFiles.clear();
3442
XRayAlwaysInstrumentFiles.clear();

clang/lib/Frontend/CompilerInvocation.cpp

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -641,30 +641,47 @@ using GenerateFn = llvm::function_ref<void(
641641
CompilerInvocation &, SmallVectorImpl<const char *> &,
642642
CompilerInvocation::StringAllocator)>;
643643

644-
// May perform round-trip of command line arguments. By default, the round-trip
645-
// is enabled in assert builds. This can be overwritten at run-time via the
646-
// "-round-trip-args" and "-no-round-trip-args" command line flags.
647-
// During round-trip, the command line arguments are parsed into a dummy
648-
// instance of CompilerInvocation which is used to generate the command line
649-
// arguments again. The real CompilerInvocation instance is then created by
650-
// parsing the generated arguments, not the original ones.
644+
/// May perform round-trip of command line arguments. By default, the round-trip
645+
/// is enabled in assert builds. This can be overwritten at run-time via the
646+
/// "-round-trip-args" and "-no-round-trip-args" command line flags, or via the
647+
/// ForceRoundTrip parameter.
648+
///
649+
/// During round-trip, the command line arguments are parsed into a dummy
650+
/// CompilerInvocation, which is used to generate the command line arguments
651+
/// again. The real CompilerInvocation is then created by parsing the generated
652+
/// arguments, not the original ones. This (in combination with tests covering
653+
/// argument behavior) ensures the generated command line is complete (doesn't
654+
/// drop/mangle any arguments).
655+
///
656+
/// Finally, we check the command line that was used to create the real
657+
/// CompilerInvocation instance. By default, we compare it to the command line
658+
/// the real CompilerInvocation generates. This checks whether the generator is
659+
/// deterministic. If \p CheckAgainstOriginalInvocation is enabled, we instead
660+
/// compare it to the original command line to verify the original command-line
661+
/// was canonical and can round-trip exactly.
651662
static bool RoundTrip(ParseFn Parse, GenerateFn Generate,
652663
CompilerInvocation &RealInvocation,
653664
CompilerInvocation &DummyInvocation,
654665
ArrayRef<const char *> CommandLineArgs,
655-
DiagnosticsEngine &Diags, const char *Argv0) {
666+
DiagnosticsEngine &Diags, const char *Argv0,
667+
bool CheckAgainstOriginalInvocation = false,
668+
bool ForceRoundTrip = false) {
656669
#ifndef NDEBUG
657670
bool DoRoundTripDefault = true;
658671
#else
659672
bool DoRoundTripDefault = false;
660673
#endif
661674

662675
bool DoRoundTrip = DoRoundTripDefault;
663-
for (const auto *Arg : CommandLineArgs) {
664-
if (Arg == StringRef("-round-trip-args"))
665-
DoRoundTrip = true;
666-
if (Arg == StringRef("-no-round-trip-args"))
667-
DoRoundTrip = false;
676+
if (ForceRoundTrip) {
677+
DoRoundTrip = true;
678+
} else {
679+
for (const auto *Arg : CommandLineArgs) {
680+
if (Arg == StringRef("-round-trip-args"))
681+
DoRoundTrip = true;
682+
if (Arg == StringRef("-no-round-trip-args"))
683+
DoRoundTrip = false;
684+
}
668685
}
669686

670687
// If round-trip was not requested, simply run the parser with the real
@@ -719,30 +736,34 @@ static bool RoundTrip(ParseFn Parse, GenerateFn Generate,
719736
// Generate arguments from the dummy invocation. If Generate is the
720737
// inverse of Parse, the newly generated arguments must have the same
721738
// semantics as the original.
722-
SmallVector<const char *> GeneratedArgs1;
723-
Generate(DummyInvocation, GeneratedArgs1, SA);
739+
SmallVector<const char *> GeneratedArgs;
740+
Generate(DummyInvocation, GeneratedArgs, SA);
724741

725742
// Run the second parse, now on the generated arguments, and with the real
726743
// invocation and diagnostics. The result is what we will end up using for the
727744
// rest of compilation, so if Generate is not inverse of Parse, something down
728745
// the line will break.
729-
bool Success2 = Parse(RealInvocation, GeneratedArgs1, Diags, Argv0);
746+
bool Success2 = Parse(RealInvocation, GeneratedArgs, Diags, Argv0);
730747

731748
// The first parse on original arguments succeeded, but second parse of
732749
// generated arguments failed. Something must be wrong with the generator.
733750
if (!Success2) {
734751
Diags.Report(diag::err_cc1_round_trip_ok_then_fail);
735752
Diags.Report(diag::note_cc1_round_trip_generated)
736-
<< 1 << SerializeArgs(GeneratedArgs1);
753+
<< 1 << SerializeArgs(GeneratedArgs);
737754
return false;
738755
}
739756

740-
// Generate arguments again, this time from the options we will end up using
741-
// for the rest of the compilation.
742-
SmallVector<const char *> GeneratedArgs2;
743-
Generate(RealInvocation, GeneratedArgs2, SA);
757+
SmallVector<const char *> ComparisonArgs;
758+
if (CheckAgainstOriginalInvocation)
759+
// Compare against original arguments.
760+
ComparisonArgs.assign(CommandLineArgs.begin(), CommandLineArgs.end());
761+
else
762+
// Generate arguments again, this time from the options we will end up using
763+
// for the rest of the compilation.
764+
Generate(RealInvocation, ComparisonArgs, SA);
744765

745-
// Compares two lists of generated arguments.
766+
// Compares two lists of arguments.
746767
auto Equal = [](const ArrayRef<const char *> A,
747768
const ArrayRef<const char *> B) {
748769
return std::equal(A.begin(), A.end(), B.begin(), B.end(),
@@ -754,23 +775,41 @@ static bool RoundTrip(ParseFn Parse, GenerateFn Generate,
754775
// If we generated different arguments from what we assume are two
755776
// semantically equivalent CompilerInvocations, the Generate function may
756777
// be non-deterministic.
757-
if (!Equal(GeneratedArgs1, GeneratedArgs2)) {
778+
if (!Equal(GeneratedArgs, ComparisonArgs)) {
758779
Diags.Report(diag::err_cc1_round_trip_mismatch);
759780
Diags.Report(diag::note_cc1_round_trip_generated)
760-
<< 1 << SerializeArgs(GeneratedArgs1);
781+
<< 1 << SerializeArgs(GeneratedArgs);
761782
Diags.Report(diag::note_cc1_round_trip_generated)
762-
<< 2 << SerializeArgs(GeneratedArgs2);
783+
<< 2 << SerializeArgs(ComparisonArgs);
763784
return false;
764785
}
765786

766787
Diags.Report(diag::remark_cc1_round_trip_generated)
767-
<< 1 << SerializeArgs(GeneratedArgs1);
788+
<< 1 << SerializeArgs(GeneratedArgs);
768789
Diags.Report(diag::remark_cc1_round_trip_generated)
769-
<< 2 << SerializeArgs(GeneratedArgs2);
790+
<< 2 << SerializeArgs(ComparisonArgs);
770791

771792
return Success2;
772793
}
773794

795+
bool CompilerInvocation::checkCC1RoundTrip(ArrayRef<const char *> Args,
796+
DiagnosticsEngine &Diags,
797+
const char *Argv0) {
798+
CompilerInvocation DummyInvocation1, DummyInvocation2;
799+
return RoundTrip(
800+
[](CompilerInvocation &Invocation, ArrayRef<const char *> CommandLineArgs,
801+
DiagnosticsEngine &Diags, const char *Argv0) {
802+
return CreateFromArgsImpl(Invocation, CommandLineArgs, Diags, Argv0);
803+
},
804+
[](CompilerInvocation &Invocation, SmallVectorImpl<const char *> &Args,
805+
StringAllocator SA) {
806+
Args.push_back("-cc1");
807+
Invocation.generateCC1CommandLine(Args, SA);
808+
},
809+
DummyInvocation1, DummyInvocation2, Args, Diags, Argv0,
810+
/*CheckAgainstOriginalInvocation=*/true, /*ForceRoundTrip=*/true);
811+
}
812+
774813
static void addDiagnosticArgs(ArgList &Args, OptSpecifier Group,
775814
OptSpecifier GroupWithValue,
776815
std::vector<std::string> &Diagnostics) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Check that we get canonical round-trippable command-lines, in particular
2+
// for the options modified for modules.
3+
4+
// RUN: rm -rf %t
5+
// RUN: split-file %s %t
6+
// RUN: sed -e "s|DIR|%/t|g" %t/cdb.json.template > %t/cdb.json
7+
8+
// RUN: clang-scan-deps -compilation-database %t/cdb.json -format experimental-full -round-trip-args > %t/result.json
9+
// RUN: cat %t/result.json | sed 's:\\\\\?:/:g' | FileCheck %s -DPREFIX=%/t --check-prefixes=CHECK,NEGATIVE
10+
11+
// -ffast-math implies -menable-no-infs, -menable-no-nans, and -mreassociate;
12+
// those options are modified by resetNonModularOptions.
13+
14+
// NEGATIVE-NOT: "-menable-no-infs"
15+
// NEGATIVE-NOT: "-menable-no-nans"
16+
// NEGATIVE-NOT: "-mreassociate"
17+
18+
// CHECK: "modules": [
19+
// CHECK-NEXT: {
20+
// CHECK: "clang-module-deps": []
21+
// CHECK: "command-line": [
22+
// CHECK: "-ffast-math"
23+
// CHECK: ]
24+
// CHECK: "name": "Mod"
25+
// CHECK: }
26+
// CHECK-NEXT: ]
27+
// CHECK: "translation-units": [
28+
// CHECK-NEXT: {
29+
// CHECK-NEXT: "commands": [
30+
// CHECK: {
31+
// CHECK: "command-line": [
32+
// CHECK: "-ffast-math"
33+
// CHECK: ]
34+
35+
//--- cdb.json.template
36+
[{
37+
"file": "DIR/tu.c",
38+
"directory": "DIR",
39+
"command": "clang -fsyntax-only DIR/tu.c -fmodules -fmodules-cache-path=DIR/cache -ffast-math"
40+
}]
41+
42+
//--- module.modulemap
43+
module Mod { header "mod.h" }
44+
//--- mod.h
45+
//--- tu.c
46+
#include "mod.h"

clang/tools/clang-scan-deps/ClangScanDeps.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "clang/Driver/Driver.h"
1010
#include "clang/Frontend/CompilerInstance.h"
11+
#include "clang/Frontend/TextDiagnosticPrinter.h"
1112
#include "clang/Tooling/CommonOptionsParser.h"
1213
#include "clang/Tooling/DependencyScanning/DependencyScanningService.h"
1314
#include "clang/Tooling/DependencyScanning/DependencyScanningTool.h"
@@ -197,6 +198,19 @@ static llvm::cl::opt<ResourceDirRecipeKind> ResourceDirRecipe(
197198
llvm::cl::init(RDRK_ModifyCompilerPath),
198199
llvm::cl::cat(DependencyScannerCategory));
199200

201+
#ifndef NDEBUG
202+
static constexpr bool DoRoundTripDefault = true;
203+
#else
204+
static constexpr bool DoRoundTripDefault = false;
205+
#endif
206+
207+
llvm::cl::opt<bool>
208+
RoundTripArgs("round-trip-args", llvm::cl::Optional,
209+
llvm::cl::desc("verify that command-line arguments are "
210+
"canonical by parsing and re-serializing"),
211+
llvm::cl::init(DoRoundTripDefault),
212+
llvm::cl::cat(DependencyScannerCategory));
213+
200214
llvm::cl::opt<bool> Verbose("v", llvm::cl::Optional,
201215
llvm::cl::desc("Use verbose output."),
202216
llvm::cl::init(false),
@@ -278,6 +292,37 @@ class FullDeps {
278292
}
279293
}
280294

295+
bool roundTripCommand(ArrayRef<std::string> ArgStrs,
296+
DiagnosticsEngine &Diags) {
297+
if (ArgStrs.empty() || ArgStrs[0] != "-cc1")
298+
return false;
299+
SmallVector<const char *> Args;
300+
for (const std::string &Arg : ArgStrs)
301+
Args.push_back(Arg.c_str());
302+
return !CompilerInvocation::checkCC1RoundTrip(Args, Diags);
303+
}
304+
305+
// Returns \c true if any command lines fail to round-trip. We expect
306+
// commands already be canonical when output by the scanner.
307+
bool roundTripCommands(raw_ostream &ErrOS) {
308+
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions{};
309+
TextDiagnosticPrinter DiagConsumer(ErrOS, &*DiagOpts);
310+
IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
311+
CompilerInstance::createDiagnostics(&*DiagOpts, &DiagConsumer,
312+
/*ShouldOwnClient=*/false);
313+
314+
for (auto &&M : Modules)
315+
if (roundTripCommand(M.second.BuildArguments, *Diags))
316+
return true;
317+
318+
for (auto &&I : Inputs)
319+
for (const auto &Cmd : I.Commands)
320+
if (roundTripCommand(Cmd.Arguments, *Diags))
321+
return true;
322+
323+
return false;
324+
}
325+
281326
void printFullOutput(raw_ostream &OS) {
282327
// Sort the modules by name to get a deterministic order.
283328
std::vector<IndexedModuleID> ModuleIDs;
@@ -615,6 +660,10 @@ int main(int argc, const char **argv) {
615660
}
616661
Pool.wait();
617662

663+
if (RoundTripArgs)
664+
if (FD.roundTripCommands(llvm::errs()))
665+
HadErrors = true;
666+
618667
if (Format == ScanningOutputFormat::Full)
619668
FD.printFullOutput(llvm::outs());
620669

0 commit comments

Comments
 (0)