From e978feba76c11c9882c59321a1901445555c1f89 Mon Sep 17 00:00:00 2001 From: Hamza Sood Date: Sun, 9 Sep 2018 12:06:35 +0000 Subject: [PATCH] [Tooling] Improve handling of CL-style options This patch fixes the handling of clang-cl options in InterpolatingCompilationDatabase. They were previously ignored completely, which led to a lot of bugs: Additional options were being added with the wrong syntax. E.g. a file was specified as C++ by adding -x c++, which causes an error in CL mode. The args were parsed and then rendered, which means that the aliasing information was lost. E.g. /W4 was rendered to -Wall, which in CL mode means -Weverything. CL options were ignored when checking things like -std=, so a lot of logic was being bypassed. Differential Revision: https://reviews.llvm.org/D51321 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@341760 91177308-0d34-0410-b5e6-96231b3b80d8 --- .../InterpolatingCompilationDatabase.cpp | 153 ++++++++++++++---- unittests/Tooling/CompilationDatabaseTest.cpp | 33 +++- 2 files changed, 149 insertions(+), 37 deletions(-) diff --git a/lib/Tooling/InterpolatingCompilationDatabase.cpp b/lib/Tooling/InterpolatingCompilationDatabase.cpp index 914377cc6c..6e40a2dcee 100644 --- a/lib/Tooling/InterpolatingCompilationDatabase.cpp +++ b/lib/Tooling/InterpolatingCompilationDatabase.cpp @@ -48,6 +48,7 @@ #include "clang/Frontend/LangStandard.h" #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Option/ArgList.h" @@ -127,47 +128,68 @@ struct TransferableCommand { Optional Type; // Standard specified by -std. LangStandard::Kind Std = LangStandard::lang_unspecified; + // Whether the command line is for the cl-compatible driver. + bool ClangCLMode; TransferableCommand(CompileCommand C) - : Cmd(std::move(C)), Type(guessType(Cmd.Filename)) { - std::vector NewArgs = {Cmd.CommandLine.front()}; + : Cmd(std::move(C)), Type(guessType(Cmd.Filename)), + ClangCLMode(checkIsCLMode(Cmd.CommandLine)) { + std::vector OldArgs = std::move(Cmd.CommandLine); + Cmd.CommandLine.clear(); + + // Wrap the old arguments in an InputArgList. + llvm::opt::InputArgList ArgList; + { + SmallVector TmpArgv; + for (const std::string &S : OldArgs) + TmpArgv.push_back(S.c_str()); + ArgList = {TmpArgv.begin(), TmpArgv.end()}; + } + // Parse the old args in order to strip out and record unwanted flags. + // We parse each argument individually so that we can retain the exact + // spelling of each argument; re-rendering is lossy for aliased flags. + // E.g. in CL mode, /W4 maps to -Wall. auto OptTable = clang::driver::createDriverOptTable(); - std::vector Argv; - for (unsigned I = 1; I < Cmd.CommandLine.size(); ++I) - Argv.push_back(Cmd.CommandLine[I].c_str()); - unsigned MissingI, MissingC; - auto ArgList = OptTable->ParseArgs(Argv, MissingI, MissingC); - for (const auto *Arg : ArgList) { - const auto &option = Arg->getOption(); + Cmd.CommandLine.emplace_back(OldArgs.front()); + for (unsigned Pos = 1; Pos < OldArgs.size();) { + using namespace driver::options; + + const unsigned OldPos = Pos; + std::unique_ptr Arg(OptTable->ParseOneArg( + ArgList, Pos, + /* Include */ClangCLMode ? CoreOption | CLOption : 0, + /* Exclude */ClangCLMode ? 0 : CLOption)); + + if (!Arg) + continue; + + const llvm::opt::Option &Opt = Arg->getOption(); + // Strip input and output files. - if (option.matches(clang::driver::options::OPT_INPUT) || - option.matches(clang::driver::options::OPT_o)) { + if (Opt.matches(OPT_INPUT) || Opt.matches(OPT_o) || + (ClangCLMode && (Opt.matches(OPT__SLASH_Fa) || + Opt.matches(OPT__SLASH_Fe) || + Opt.matches(OPT__SLASH_Fi) || + Opt.matches(OPT__SLASH_Fo)))) continue; - } + // Strip -x, but record the overridden language. - if (option.matches(clang::driver::options::OPT_x)) { - for (const char *Value : Arg->getValues()) - Type = types::lookupTypeForTypeSpecifier(Value); + if (const auto GivenType = tryParseTypeArg(*Arg)) { + Type = *GivenType; continue; } - // Strip --std, but record the value. - if (option.matches(clang::driver::options::OPT_std_EQ)) { - for (const char *Value : Arg->getValues()) { - Std = llvm::StringSwitch(Value) -#define LANGSTANDARD(id, name, lang, desc, features) \ - .Case(name, LangStandard::lang_##id) -#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id) -#include "clang/Frontend/LangStandards.def" - .Default(Std); - } + + // Strip -std, but record the value. + if (const auto GivenStd = tryParseStdArg(*Arg)) { + if (*GivenStd != LangStandard::lang_unspecified) + Std = *GivenStd; continue; } - llvm::opt::ArgStringList ArgStrs; - Arg->render(ArgList, ArgStrs); - NewArgs.insert(NewArgs.end(), ArgStrs.begin(), ArgStrs.end()); + + Cmd.CommandLine.insert(Cmd.CommandLine.end(), + &OldArgs[OldPos], &OldArgs[Pos]); } - Cmd.CommandLine = std::move(NewArgs); if (Std != LangStandard::lang_unspecified) // -std take precedence over -x Type = toType(LangStandard::getLangStandardForKind(Std).getLanguage()); @@ -188,21 +210,39 @@ struct TransferableCommand { TargetType = types::onlyPrecompileType(TargetType) // header? ? types::lookupHeaderTypeForSourceType(*Type) : *Type; - Result.CommandLine.push_back("-x"); - Result.CommandLine.push_back(types::getTypeName(TargetType)); + if (ClangCLMode) { + const StringRef Flag = toCLFlag(TargetType); + if (!Flag.empty()) + Result.CommandLine.push_back(Flag); + } else { + Result.CommandLine.push_back("-x"); + Result.CommandLine.push_back(types::getTypeName(TargetType)); + } } // --std flag may only be transferred if the language is the same. // We may consider "translating" these, e.g. c++11 -> c11. if (Std != LangStandard::lang_unspecified && foldType(TargetType) == Type) { - Result.CommandLine.push_back( - "-std=" + - std::string(LangStandard::getLangStandardForKind(Std).getName())); + Result.CommandLine.emplace_back(( + llvm::Twine(ClangCLMode ? "/std:" : "-std=") + + LangStandard::getLangStandardForKind(Std).getName()).str()); } Result.CommandLine.push_back(Filename); return Result; } private: + // Determine whether the given command line is intended for the CL driver. + static bool checkIsCLMode(ArrayRef CmdLine) { + // First look for --driver-mode. + for (StringRef S : llvm::reverse(CmdLine)) { + if (S.consume_front("--driver-mode=")) + return S == "cl"; + } + + // Otherwise just check the clang executable file name. + return llvm::sys::path::stem(CmdLine.front()).endswith_lower("cl"); + } + // Map the language from the --std flag to that of the -x flag. static types::ID toType(InputKind::Language Lang) { switch (Lang) { @@ -218,6 +258,51 @@ private: return types::TY_INVALID; } } + + // Convert a file type to the matching CL-style type flag. + static StringRef toCLFlag(types::ID Type) { + switch (Type) { + case types::TY_C: + case types::TY_CHeader: + return "/TC"; + case types::TY_CXX: + case types::TY_CXXHeader: + return "/TP"; + default: + return StringRef(); + } + } + + // Try to interpret the argument as a type specifier, e.g. '-x'. + Optional tryParseTypeArg(const llvm::opt::Arg &Arg) { + const llvm::opt::Option &Opt = Arg.getOption(); + using namespace driver::options; + if (ClangCLMode) { + if (Opt.matches(OPT__SLASH_TC) || Opt.matches(OPT__SLASH_Tc)) + return types::TY_C; + if (Opt.matches(OPT__SLASH_TP) || Opt.matches(OPT__SLASH_Tp)) + return types::TY_CXX; + } else { + if (Opt.matches(driver::options::OPT_x)) + return types::lookupTypeForTypeSpecifier(Arg.getValue()); + } + return None; + } + + // Try to interpret the argument as '-std='. + Optional tryParseStdArg(const llvm::opt::Arg &Arg) { + using namespace driver::options; + if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) { + return llvm::StringSwitch(Arg.getValue()) +#define LANGSTANDARD(id, name, lang, ...) .Case(name, LangStandard::lang_##id) +#define LANGSTANDARD_ALIAS(id, alias) .Case(alias, LangStandard::lang_##id) +#include "clang/Frontend/LangStandards.def" +#undef LANGSTANDARD_ALIAS +#undef LANGSTANDARD + .Default(LangStandard::lang_unspecified); + } + return None; + } }; // Given a filename, FileIndex picks the best matching file from the underlying diff --git a/unittests/Tooling/CompilationDatabaseTest.cpp b/unittests/Tooling/CompilationDatabaseTest.cpp index cbe4d28efe..125ba4896e 100644 --- a/unittests/Tooling/CompilationDatabaseTest.cpp +++ b/unittests/Tooling/CompilationDatabaseTest.cpp @@ -648,14 +648,17 @@ class InterpolateTest : public ::testing::Test { protected: // Adds an entry to the underlying compilation database. // A flag is injected: -D , so the command used can be identified. - void add(llvm::StringRef File, llvm::StringRef Flags = "") { - llvm::SmallVector Argv = {"clang", File, "-D", File}; + void add(StringRef File, StringRef Clang, StringRef Flags) { + SmallVector Argv = {Clang, File, "-D", File}; llvm::SplitString(Flags, Argv); - llvm::SmallString<32> Dir; + + SmallString<32> Dir; llvm::sys::path::system_temp_directory(false, Dir); + Entries[path(File)].push_back( {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"}); } + void add(StringRef File, StringRef Flags = "") { add(File, "clang", Flags); } // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h) std::string path(llvm::SmallString<32> File) { @@ -739,6 +742,30 @@ TEST_F(InterpolateTest, Case) { EXPECT_EQ(getCommand("foo/bar/baz/shout.C"), "clang -D FOO/BAR/BAZ/SHOUT.cc"); } +TEST_F(InterpolateTest, Aliasing) { + add("foo.cpp", "-faligned-new"); + + // The interpolated command should keep the given flag as written, even though + // the flag is internally represented as an alias. + EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new"); +} + +TEST_F(InterpolateTest, ClangCL) { + add("foo.cpp", "clang-cl", "/W4"); + + // Language flags should be added with CL syntax. + EXPECT_EQ(getCommand("foo.h"), "clang-cl -D foo.cpp /W4 /TP"); +} + +TEST_F(InterpolateTest, DriverModes) { + add("foo.cpp", "clang-cl", "--driver-mode=gcc"); + add("bar.cpp", "clang", "--driver-mode=cl"); + + // --driver-mode overrides should be respected. + EXPECT_EQ(getCommand("foo.h"), "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header"); + EXPECT_EQ(getCommand("bar.h"), "clang -D bar.cpp --driver-mode=cl /TP"); +} + TEST(CompileCommandTest, EqualityOperator) { CompileCommand CCRef("/foo/bar", "hello.c", {"a", "b"}, "hello.o"); CompileCommand CCTest = CCRef; -- 2.40.0