]> granicus.if.org Git - clang/commitdiff
[Tooling] Improve handling of CL-style options
authorHamza Sood <hamza_sood@me.com>
Sun, 9 Sep 2018 12:06:35 +0000 (12:06 +0000)
committerHamza Sood <hamza_sood@me.com>
Sun, 9 Sep 2018 12:06:35 +0000 (12:06 +0000)
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

lib/Tooling/InterpolatingCompilationDatabase.cpp
unittests/Tooling/CompilationDatabaseTest.cpp

index 914377cc6cf0316099e623ef074d7ec5cf0c1965..6e40a2dcee66ce664b427d2f14298c60efe0aa95 100644 (file)
@@ -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<types::ID> 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<std::string> NewArgs = {Cmd.CommandLine.front()};
+      : Cmd(std::move(C)), Type(guessType(Cmd.Filename)),
+        ClangCLMode(checkIsCLMode(Cmd.CommandLine)) {
+    std::vector<std::string> OldArgs = std::move(Cmd.CommandLine);
+    Cmd.CommandLine.clear();
+
+    // Wrap the old arguments in an InputArgList.
+    llvm::opt::InputArgList ArgList;
+    {
+      SmallVector<const char *, 16> 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<const char *> 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<llvm::opt::Arg> 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<LangStandard::Kind>(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<std::string> 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<types::ID> 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<LangStandard::Kind> tryParseStdArg(const llvm::opt::Arg &Arg) {
+    using namespace driver::options;
+    if (Arg.getOption().matches(ClangCLMode ? OPT__SLASH_std : OPT_std_EQ)) {
+      return llvm::StringSwitch<LangStandard::Kind>(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
index cbe4d28efe623bb0d02b8e92c312f54e9e028b2d..125ba4896ef69a03e94baf75940fd3981b7bf72f 100644 (file)
@@ -648,14 +648,17 @@ class InterpolateTest : public ::testing::Test {
 protected:
   // Adds an entry to the underlying compilation database.
   // A flag is injected: -D <File>, so the command used can be identified.
-  void add(llvm::StringRef File, llvm::StringRef Flags = "") {
-    llvm::SmallVector<StringRef, 8> Argv = {"clang", File, "-D", File};
+  void add(StringRef File, StringRef Clang, StringRef Flags) {
+    SmallVector<StringRef, 8> 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;