lh123 updated this revision to Diff 230756.
lh123 added a comment.

Expand response files before `inferMissingCompileCommands` and 
`inferTargetAndDriverMode`


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D70222/new/

https://reviews.llvm.org/D70222

Files:
  clang/include/clang/Tooling/CompilationDatabase.h
  clang/lib/Tooling/CMakeLists.txt
  clang/lib/Tooling/CompilationDatabase.cpp
  clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp
  clang/lib/Tooling/JSONCompilationDatabase.cpp
  clang/unittests/Tooling/CompilationDatabaseTest.cpp

Index: clang/unittests/Tooling/CompilationDatabaseTest.cpp
===================================================================
--- clang/unittests/Tooling/CompilationDatabaseTest.cpp
+++ clang/unittests/Tooling/CompilationDatabaseTest.cpp
@@ -17,6 +17,7 @@
 #include "llvm/Support/TargetSelect.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include <fstream>
 
 namespace clang {
 namespace tooling {
@@ -859,5 +860,76 @@
             "clang++ --driver-mode=g++ bar.cpp -D bar.cpp");
 }
 
+class ExpandResponseFilesTest : public MemDBTest {
+protected:
+  void SetUp() override {
+    InnerDir = path(StringRef("inner"));
+    std::error_code EC = llvm::sys::fs::create_directory(InnerDir);
+    EXPECT_TRUE(!EC);
+
+    llvm::sys::path::append(RspFileName1, InnerDir, "rsp1.rsp");
+    std::ofstream RspFile1(RspFileName1.c_str());
+    RspFile1 << "-Dflag1";
+    RspFile1.close();
+
+    RspFileName2 = path(StringRef("rsp2.rsp"));
+    std::ofstream RspFile2(RspFileName2.c_str());
+    RspFile2 << "-Dflag2 @rsp3.rsp";
+    RspFile2.close();
+
+    RspFileName3 = path(StringRef("rsp3.rsp"));
+    std::ofstream RspFile3(RspFileName3.c_str());
+    RspFile3 << "-Dflag3";
+    RspFile3.close();
+
+    RspFileName4 = path(StringRef("rsp4.rsp"));
+    std::ofstream  RspFile4(RspFileName4.c_str());
+    RspFile4 << "-Dflag4 @rsp4.rsp";
+    RspFile4.close();
+
+    llvm::sys::path::append(RspFileName5, InnerDir, "rsp5.rsp");
+    std::ofstream RspFile5(RspFileName5.c_str());
+    RspFile5 << "-Dflag5 @inner/rsp1.rsp";
+    RspFile5.close();
+  }
+
+  void TearDown() override {
+    llvm::sys::fs::remove(RspFileName5);
+    llvm::sys::fs::remove(RspFileName4);
+    llvm::sys::fs::remove(RspFileName3);
+    llvm::sys::fs::remove(RspFileName2);
+    llvm::sys::fs::remove(RspFileName1);
+    llvm::sys::fs::remove(InnerDir);
+  }
+
+  std::string getCommand(llvm::StringRef F) {
+    auto Results = expandResponseFiles(std::make_unique<MemCDB>(Entries))
+                       ->getCompileCommands(path(F));
+    if (Results.empty()) {
+      return "none";
+    }
+    return llvm::join(Results[0].CommandLine, " ");
+  }
+
+  SmallString<128> InnerDir;
+  SmallString<128> RspFileName1;
+  SmallString<128> RspFileName2;
+  SmallString<128> RspFileName3;
+  SmallString<128> RspFileName4;
+  SmallString<128> RspFileName5;
+};
+
+TEST_F(ExpandResponseFilesTest, ExpandResponseFiles) {
+  // clang-format off
+  add("foo.cpp", "clang",
+      ("@inner/rsp1.rsp @rsp2.rsp @rsp4.rsp "
+        "@" + RspFileName1 + " @inner/rsp5.rsp @rsp6.rsp")
+          .str());
+  // clang-format on
+  EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag1 -Dflag2 "
+                                   "-Dflag3 -Dflag4 @rsp4.rsp -Dflag1 "
+                                   "-Dflag5 -Dflag1 @rsp6.rsp");
+}
+
 } // end namespace tooling
 } // end namespace clang
Index: clang/lib/Tooling/JSONCompilationDatabase.cpp
===================================================================
--- clang/lib/Tooling/JSONCompilationDatabase.cpp
+++ clang/lib/Tooling/JSONCompilationDatabase.cpp
@@ -167,8 +167,8 @@
     llvm::sys::path::append(JSONDatabasePath, "compile_commands.json");
     auto Base = JSONCompilationDatabase::loadFromFile(
         JSONDatabasePath, ErrorMessage, JSONCommandLineSyntax::AutoDetect);
-    return Base ? inferTargetAndDriverMode(
-                      inferMissingCompileCommands(std::move(Base)))
+    return Base ? inferTargetAndDriverMode(inferMissingCompileCommands(
+                      expandResponseFiles(std::move(Base))))
                 : nullptr;
   }
 };
Index: clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp
===================================================================
--- /dev/null
+++ clang/lib/Tooling/ExpandResponseFilesCompilationDatabase.cpp
@@ -0,0 +1,192 @@
+//===- ExpandResponseFileCompilationDataBase.cpp --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Tooling/CompilationDatabase.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/ConvertUTF.h"
+#include "llvm/Support/ErrorOr.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/StringSaver.h"
+
+namespace clang {
+namespace tooling {
+namespace {
+
+bool expandResponseFile(llvm::StringRef FName, llvm::StringSaver &Saver,
+                        llvm::cl::TokenizerCallback Tokenizer,
+                        SmallVectorImpl<const char *> &NewArgv) {
+  llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MemBufOrErr =
+      llvm::MemoryBuffer::getFile(FName);
+  if (!MemBufOrErr)
+    return false;
+  llvm::MemoryBuffer &MemBuf = *MemBufOrErr.get();
+  StringRef Str(MemBuf.getBufferStart(), MemBuf.getBufferSize());
+
+  // If we have a UTF-16 byte order mark, convert to UTF-8 for parsing.
+  ArrayRef<char> BufRef(MemBuf.getBufferStart(), MemBuf.getBufferEnd());
+  std::string UTF8Buf;
+  // It is called byte order marker but the UTF-8 BOM is actually not affected
+  // by the host system's endianness.
+  auto HasUtF8ByteOrderMark = [](ArrayRef<char> S) {
+    return (S.size() >= 3 && S[0] == '\xef' && S[1] == '\xbb' &&
+            S[2] == '\xbf');
+  };
+  if (llvm::hasUTF16ByteOrderMark(BufRef)) {
+    if (!convertUTF16ToUTF8String(BufRef, UTF8Buf))
+      return false;
+    Str = StringRef(UTF8Buf);
+  }
+  // If we see UTF-8 BOM sequence at the beginning of a file, we shall remove
+  // these bytes before parsing.
+  // Reference: http://en.wikipedia.org/wiki/UTF-8#Byte_order_mark
+  else if (HasUtF8ByteOrderMark(BufRef))
+    Str = StringRef(BufRef.data() + 3, BufRef.size() - 3);
+  // Tokenize the contents into NewArgv.
+  Tokenizer(Str, Saver, NewArgv, false);
+  return true;
+}
+
+bool expandResponseFiles(tooling::CompileCommand &Cmd,
+                         llvm::cl::TokenizerCallback Tokenizer) {
+  bool AllExpanded = true;
+  struct ResponseFileRecord {
+    llvm::SmallString<128> File;
+    size_t End;
+  };
+  std::vector<std::string> &Argv = Cmd.CommandLine;
+  // To detect recursive response files, we maintain a stack of files and the
+  // position of the last argument in the file. This position is updated
+  // dynamically as we recursively expand files.
+  SmallVector<ResponseFileRecord, 3> FileStack;
+
+  // Push a dummy entry that represents the initial command line, removing
+  // the need to check for an empty list.
+  FileStack.push_back({llvm::StringRef(""), Argv.size()});
+
+  // Don't cache Argv.size() because it can change.
+  for (unsigned I = 0; I != Argv.size();) {
+    while (I == FileStack.back().End) {
+      // Passing the end of a file's argument list, so we can remove it from the
+      // stack.
+      FileStack.pop_back();
+    }
+
+    std::string &Arg = Argv[I];
+
+    if (Arg[0] != '@') {
+      ++I;
+      continue;
+    }
+    SmallString<128> ResponseFile;
+    if (llvm::sys::path::is_relative(&Arg[1])) {
+      llvm::sys::path::append(ResponseFile, Cmd.Directory, &Arg[1]);
+    } else {
+      ResponseFile.append(Arg.begin() + 1, Arg.end());
+    }
+    llvm::sys::path::remove_dots(ResponseFile, true);
+    llvm::sys::path::native(ResponseFile);
+
+    auto IsEquivalent = [ResponseFile](const ResponseFileRecord &RFile) {
+      return llvm::sys::fs::equivalent(RFile.File, ResponseFile);
+    };
+
+    // Check for recursive response files.
+    if (std::any_of(FileStack.begin() + 1, FileStack.end(), IsEquivalent)) {
+      // This file is recursive, so we leave it in the argument stream and
+      // move on.
+      AllExpanded = false;
+      ++I;
+      continue;
+    }
+
+    // Replace this response file argument with the tokenization of its
+    // contents.  Nested response files are expanded in subsequent iterations.
+    SmallVector<const char *, 0> ExpandedArgv;
+    llvm::BumpPtrAllocator Alloc;
+    llvm::StringSaver Saver(Alloc);
+    llvm::SmallVector<const char *, 64> T;
+    if (!expandResponseFile(ResponseFile, Saver, Tokenizer, ExpandedArgv)) {
+      // We couldn't read this file, so we leave it in the argument stream and
+      // move on.
+      AllExpanded = false;
+      ++I;
+      continue;
+    }
+
+    for (ResponseFileRecord &Record : FileStack) {
+      // Increase the end of all active records by the number of newly expanded
+      // arguments, minus the response file itself.
+      Record.End += ExpandedArgv.size() - 1;
+    }
+
+    FileStack.push_back({ResponseFile, I + ExpandedArgv.size()});
+    Argv.erase(Argv.begin() + I);
+    Argv.insert(Argv.begin() + I, ExpandedArgv.begin(), ExpandedArgv.end());
+  }
+
+  // If successful, the top of the file stack will mark the end of the Argv
+  // stream. A failure here indicates a bug in the stack popping logic above.
+  // Note that FileStack may have more than one element at this point because we
+  // don't have a chance to pop the stack when encountering recursive files at
+  // the end of the stream, so seeing that doesn't indicate a bug.
+  assert(FileStack.size() > 0 && Argv.size() == FileStack.back().End);
+  return AllExpanded;
+}
+
+class ExpandResponseFilesDatabase : public CompilationDatabase {
+public:
+  ExpandResponseFilesDatabase(std::unique_ptr<CompilationDatabase> Base,
+                              llvm::cl::TokenizerCallback Tokenizer)
+      : Base(std::move(Base)), Tokenizer(Tokenizer) {
+    assert(this->Base != nullptr);
+  }
+
+  std::vector<std::string> getAllFiles() const override {
+    return Base->getAllFiles();
+  }
+
+  std::vector<CompileCommand>
+  getCompileCommands(StringRef FilePath) const override {
+    return expand(Base->getCompileCommands(FilePath));
+  }
+
+  std::vector<CompileCommand> getAllCompileCommands() const override {
+    return expand(Base->getAllCompileCommands());
+  }
+
+private:
+  std::vector<CompileCommand> expand(std::vector<CompileCommand> Cmds) const {
+    for (auto &Cmd : Cmds) {
+      expandResponseFiles(Cmd, Tokenizer);
+    }
+    return Cmds;
+  }
+
+private:
+  std::unique_ptr<CompilationDatabase> Base;
+  llvm::cl::TokenizerCallback Tokenizer;
+};
+
+} // namespace
+
+std::unique_ptr<CompilationDatabase>
+expandResponseFiles(std::unique_ptr<CompilationDatabase> Base,
+                    llvm::cl::TokenizerCallback Tokenizer) {
+  if (Tokenizer == nullptr) {
+    Tokenizer = llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
+                    ? llvm::cl::TokenizeWindowsCommandLine
+                    : llvm::cl::TokenizeGNUCommandLine;
+  }
+  return std::make_unique<ExpandResponseFilesDatabase>(std::move(Base),
+                                                       Tokenizer);
+}
+
+} // namespace tooling
+} // namespace clang
Index: clang/lib/Tooling/CompilationDatabase.cpp
===================================================================
--- clang/lib/Tooling/CompilationDatabase.cpp
+++ clang/lib/Tooling/CompilationDatabase.cpp
@@ -399,7 +399,9 @@
   loadFromDirectory(StringRef Directory, std::string &ErrorMessage) override {
     SmallString<1024> DatabasePath(Directory);
     llvm::sys::path::append(DatabasePath, "compile_flags.txt");
-    return FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage);
+    auto Base =
+        FixedCompilationDatabase::loadFromFile(DatabasePath, ErrorMessage);
+    return Base ? expandResponseFiles(std::move(Base)) : nullptr;
   }
 };
 
Index: clang/lib/Tooling/CMakeLists.txt
===================================================================
--- clang/lib/Tooling/CMakeLists.txt
+++ clang/lib/Tooling/CMakeLists.txt
@@ -17,6 +17,7 @@
   CommonOptionsParser.cpp
   CompilationDatabase.cpp
   Execution.cpp
+  ExpandResponseFilesCompilationDatabase.cpp
   FileMatchTrie.cpp
   FixIt.cpp
   GuessTargetAndModeCompilationDatabase.cpp
Index: clang/include/clang/Tooling/CompilationDatabase.h
===================================================================
--- clang/include/clang/Tooling/CompilationDatabase.h
+++ clang/include/clang/Tooling/CompilationDatabase.h
@@ -31,6 +31,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/Twine.h"
+#include "llvm/Support/CommandLine.h"
 #include <memory>
 #include <string>
 #include <utility>
@@ -219,6 +220,15 @@
 std::unique_ptr<CompilationDatabase>
 inferTargetAndDriverMode(std::unique_ptr<CompilationDatabase> Base);
 
+/// Returns a wrapped CompilationDatabase that will expand all response files on
+/// commandline returned by underlying database.
+///
+/// \param [in] Tokenizer Tokenization strategy. Typically Unix, Windows or
+/// nullptr. use nullptr for auto detect.
+std::unique_ptr<CompilationDatabase>
+expandResponseFiles(std::unique_ptr<CompilationDatabase> Base,
+                    llvm::cl::TokenizerCallback Tokenizer = nullptr);
+
 } // namespace tooling
 } // namespace clang
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to