diff --git a/clang/lib/Tooling/CompilationDatabase.cpp b/clang/lib/Tooling/CompilationDatabase.cpp index 77c5b547ca09..0e835579e04e 100644 --- a/clang/lib/Tooling/CompilationDatabase.cpp +++ b/clang/lib/Tooling/CompilationDatabase.cpp @@ -1,344 +1,346 @@ //===--- CompilationDatabase.cpp - ----------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file contains implementations of the CompilationDatabase base class // and the FixedCompilationDatabase. // //===----------------------------------------------------------------------===// #include "clang/Tooling/CompilationDatabase.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "clang/Driver/Action.h" #include "clang/Driver/Compilation.h" #include "clang/Driver/Driver.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/Job.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Tooling/CompilationDatabasePluginRegistry.h" #include "clang/Tooling/Tooling.h" #include "llvm/ADT/SmallString.h" #include "llvm/Option/Arg.h" #include "llvm/Support/Host.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include #include using namespace clang; using namespace tooling; LLVM_INSTANTIATE_REGISTRY(CompilationDatabasePluginRegistry) CompilationDatabase::~CompilationDatabase() {} std::unique_ptr CompilationDatabase::loadFromDirectory(StringRef BuildDirectory, std::string &ErrorMessage) { llvm::raw_string_ostream ErrorStream(ErrorMessage); for (CompilationDatabasePluginRegistry::iterator It = CompilationDatabasePluginRegistry::begin(), Ie = CompilationDatabasePluginRegistry::end(); It != Ie; ++It) { std::string DatabaseErrorMessage; std::unique_ptr Plugin(It->instantiate()); if (std::unique_ptr DB = Plugin->loadFromDirectory(BuildDirectory, DatabaseErrorMessage)) return DB; ErrorStream << It->getName() << ": " << DatabaseErrorMessage << "\n"; } return nullptr; } static std::unique_ptr findCompilationDatabaseFromDirectory(StringRef Directory, std::string &ErrorMessage) { std::stringstream ErrorStream; bool HasErrorMessage = false; while (!Directory.empty()) { std::string LoadErrorMessage; if (std::unique_ptr DB = CompilationDatabase::loadFromDirectory(Directory, LoadErrorMessage)) return DB; if (!HasErrorMessage) { ErrorStream << "No compilation database found in " << Directory.str() << " or any parent directory\n" << LoadErrorMessage; HasErrorMessage = true; } Directory = llvm::sys::path::parent_path(Directory); } ErrorMessage = ErrorStream.str(); return nullptr; } std::unique_ptr CompilationDatabase::autoDetectFromSource(StringRef SourceFile, std::string &ErrorMessage) { SmallString<1024> AbsolutePath(getAbsolutePath(SourceFile)); StringRef Directory = llvm::sys::path::parent_path(AbsolutePath); std::unique_ptr DB = findCompilationDatabaseFromDirectory(Directory, ErrorMessage); if (!DB) ErrorMessage = ("Could not auto-detect compilation database for file \"" + SourceFile + "\"\n" + ErrorMessage).str(); return DB; } std::unique_ptr CompilationDatabase::autoDetectFromDirectory(StringRef SourceDir, std::string &ErrorMessage) { SmallString<1024> AbsolutePath(getAbsolutePath(SourceDir)); std::unique_ptr DB = findCompilationDatabaseFromDirectory(AbsolutePath, ErrorMessage); if (!DB) ErrorMessage = ("Could not auto-detect compilation database from directory \"" + SourceDir + "\"\n" + ErrorMessage).str(); return DB; } CompilationDatabasePlugin::~CompilationDatabasePlugin() {} namespace { // Helper for recursively searching through a chain of actions and collecting // all inputs, direct and indirect, of compile jobs. struct CompileJobAnalyzer { void run(const driver::Action *A) { runImpl(A, false); } SmallVector Inputs; private: void runImpl(const driver::Action *A, bool Collect) { bool CollectChildren = Collect; switch (A->getKind()) { case driver::Action::CompileJobClass: CollectChildren = true; break; case driver::Action::InputClass: { if (Collect) { const driver::InputAction *IA = cast(A); Inputs.push_back(IA->getInputArg().getSpelling()); } } break; default: // Don't care about others ; } for (const driver::Action *AI : A->inputs()) runImpl(AI, CollectChildren); } }; // Special DiagnosticConsumer that looks for warn_drv_input_file_unused // diagnostics from the driver and collects the option strings for those unused // options. class UnusedInputDiagConsumer : public DiagnosticConsumer { public: UnusedInputDiagConsumer(DiagnosticConsumer &Other) : Other(Other) {} void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) override { if (Info.getID() == clang::diag::warn_drv_input_file_unused) { // Arg 1 for this diagnostic is the option that didn't get used. UnusedInputs.push_back(Info.getArgStdStr(0)); } else if (DiagLevel >= DiagnosticsEngine::Error) { // If driver failed to create compilation object, show the diagnostics // to user. Other.HandleDiagnostic(DiagLevel, Info); } } DiagnosticConsumer &Other; SmallVector UnusedInputs; }; // Unary functor for asking "Given a StringRef S1, does there exist a string // S2 in Arr where S1 == S2?" struct MatchesAny { MatchesAny(ArrayRef Arr) : Arr(Arr) {} bool operator() (StringRef S) { for (const std::string *I = Arr.begin(), *E = Arr.end(); I != E; ++I) if (*I == S) return true; return false; } private: ArrayRef Arr; }; } // namespace /// \brief Strips any positional args and possible argv[0] from a command-line /// provided by the user to construct a FixedCompilationDatabase. /// /// FixedCompilationDatabase requires a command line to be in this format as it /// constructs the command line for each file by appending the name of the file /// to be compiled. FixedCompilationDatabase also adds its own argv[0] to the /// start of the command line although its value is not important as it's just /// ignored by the Driver invoked by the ClangTool using the /// FixedCompilationDatabase. /// /// FIXME: This functionality should probably be made available by /// clang::driver::Driver although what the interface should look like is not /// clear. /// /// \param[in] Args Args as provided by the user. /// \return Resulting stripped command line. /// \li true if successful. /// \li false if \c Args cannot be used for compilation jobs (e.g. /// contains an option like -E or -version). static bool stripPositionalArgs(std::vector Args, std::vector &Result, std::string &ErrorMsg) { IntrusiveRefCntPtr DiagOpts = new DiagnosticOptions(); llvm::raw_string_ostream Output(ErrorMsg); TextDiagnosticPrinter DiagnosticPrinter(Output, &*DiagOpts); UnusedInputDiagConsumer DiagClient(DiagnosticPrinter); DiagnosticsEngine Diagnostics( IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, &DiagClient, false); // The clang executable path isn't required since the jobs the driver builds // will not be executed. std::unique_ptr NewDriver(new driver::Driver( /* ClangExecutable= */ "", llvm::sys::getDefaultTargetTriple(), Diagnostics)); NewDriver->setCheckInputsExist(false); // This becomes the new argv[0]. The value is actually not important as it // isn't used for invoking Tools. Args.insert(Args.begin(), "clang-tool"); // By adding -c, we force the driver to treat compilation as the last phase. // It will then issue warnings via Diagnostics about un-used options that // would have been used for linking. If the user provided a compiler name as // the original argv[0], this will be treated as a linker input thanks to // insertng a new argv[0] above. All un-used options get collected by // UnusedInputdiagConsumer and get stripped out later. Args.push_back("-c"); // Put a dummy C++ file on to ensure there's at least one compile job for the // driver to construct. If the user specified some other argument that // prevents compilation, e.g. -E or something like -version, we may still end // up with no jobs but then this is the user's fault. Args.push_back("placeholder.cpp"); // Remove -no-integrated-as; it's not used for syntax checking, // and it confuses targets which don't support this option. Args.erase(std::remove_if(Args.begin(), Args.end(), MatchesAny(std::string("-no-integrated-as"))), Args.end()); const std::unique_ptr Compilation( NewDriver->BuildCompilation(Args)); if (!Compilation) return false; const driver::JobList &Jobs = Compilation->getJobs(); CompileJobAnalyzer CompileAnalyzer; for (const auto &Cmd : Jobs) { - // Collect only for Assemble jobs. If we do all jobs we get duplicates - // since Link jobs point to Assemble jobs as inputs. - if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass) + // Collect only for Assemble and Compile jobs. If we do all jobs we get + // duplicates since Link jobs point to Assemble jobs as inputs. + if (Cmd.getSource().getKind() == driver::Action::AssembleJobClass || + Cmd.getSource().getKind() == driver::Action::CompileJobClass) { CompileAnalyzer.run(&Cmd.getSource()); + } } if (CompileAnalyzer.Inputs.empty()) { ErrorMsg = "warning: no compile jobs found\n"; return false; } // Remove all compilation input files from the command line. This is // necessary so that getCompileCommands() can construct a command line for // each file. std::vector::iterator End = std::remove_if( Args.begin(), Args.end(), MatchesAny(CompileAnalyzer.Inputs)); // Remove all inputs deemed unused for compilation. End = std::remove_if(Args.begin(), End, MatchesAny(DiagClient.UnusedInputs)); // Remove the -c add above as well. It will be at the end right now. assert(strcmp(*(End - 1), "-c") == 0); --End; Result = std::vector(Args.begin() + 1, End); return true; } std::unique_ptr FixedCompilationDatabase::loadFromCommandLine(int &Argc, const char *const *Argv, std::string &ErrorMsg, Twine Directory) { ErrorMsg.clear(); if (Argc == 0) return nullptr; const char *const *DoubleDash = std::find(Argv, Argv + Argc, StringRef("--")); if (DoubleDash == Argv + Argc) return nullptr; std::vector CommandLine(DoubleDash + 1, Argv + Argc); Argc = DoubleDash - Argv; std::vector StrippedArgs; if (!stripPositionalArgs(CommandLine, StrippedArgs, ErrorMsg)) return nullptr; return std::unique_ptr( new FixedCompilationDatabase(Directory, StrippedArgs)); } FixedCompilationDatabase:: FixedCompilationDatabase(Twine Directory, ArrayRef CommandLine) { std::vector ToolCommandLine(1, "clang-tool"); ToolCommandLine.insert(ToolCommandLine.end(), CommandLine.begin(), CommandLine.end()); CompileCommands.emplace_back(Directory, StringRef(), std::move(ToolCommandLine), StringRef()); } std::vector FixedCompilationDatabase::getCompileCommands(StringRef FilePath) const { std::vector Result(CompileCommands); Result[0].CommandLine.push_back(FilePath); Result[0].Filename = FilePath; return Result; } std::vector FixedCompilationDatabase::getAllFiles() const { return std::vector(); } std::vector FixedCompilationDatabase::getAllCompileCommands() const { return std::vector(); } namespace clang { namespace tooling { // This anchor is used to force the linker to link in the generated object file // and thus register the JSONCompilationDatabasePlugin. extern volatile int JSONAnchorSource; static int LLVM_ATTRIBUTE_UNUSED JSONAnchorDest = JSONAnchorSource; } // end namespace tooling } // end namespace clang diff --git a/clang/unittests/Tooling/CompilationDatabaseTest.cpp b/clang/unittests/Tooling/CompilationDatabaseTest.cpp index 5a6693eb4dbb..fd8afe6b7976 100644 --- a/clang/unittests/Tooling/CompilationDatabaseTest.cpp +++ b/clang/unittests/Tooling/CompilationDatabaseTest.cpp @@ -1,609 +1,630 @@ //===- unittest/Tooling/CompilationDatabaseTest.cpp -----------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclGroup.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/FileMatchTrie.h" #include "clang/Tooling/JSONCompilationDatabase.h" #include "clang/Tooling/Tooling.h" #include "llvm/Support/Path.h" #include "gtest/gtest.h" namespace clang { namespace tooling { static void expectFailure(StringRef JSONDatabase, StringRef Explanation) { std::string ErrorMessage; EXPECT_EQ(nullptr, JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, JSONCommandLineSyntax::Gnu)) << "Expected an error because of: " << Explanation.str(); } TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) { expectFailure("", "Empty database"); expectFailure("{", "Invalid JSON"); expectFailure("[[]]", "Array instead of object"); expectFailure("[{\"a\":[]}]", "Array instead of value"); expectFailure("[{\"a\":\"b\"}]", "Unknown key"); expectFailure("[{[]:\"\"}]", "Incorrectly typed entry"); expectFailure("[{}]", "Empty entry"); expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file"); expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command or arguments"); expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory"); expectFailure("[{\"directory\":\"\",\"arguments\":[]}]", "Missing file"); expectFailure("[{\"arguments\":\"\",\"file\":\"\"}]", "Missing directory"); expectFailure("[{\"directory\":\"\",\"arguments\":\"\",\"file\":\"\"}]", "Arguments not array"); expectFailure("[{\"directory\":\"\",\"command\":[],\"file\":\"\"}]", "Command not string"); expectFailure("[{\"directory\":\"\",\"arguments\":[[]],\"file\":\"\"}]", "Arguments contain non-string"); expectFailure("[{\"output\":[]}]", "Expected strings as value."); } static std::vector getAllFiles(StringRef JSONDatabase, std::string &ErrorMessage, JSONCommandLineSyntax Syntax) { std::unique_ptr Database( JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, Syntax)); if (!Database) { ADD_FAILURE() << ErrorMessage; return std::vector(); } return Database->getAllFiles(); } static std::vector getAllCompileCommands(JSONCommandLineSyntax Syntax, StringRef JSONDatabase, std::string &ErrorMessage) { std::unique_ptr Database( JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, Syntax)); if (!Database) { ADD_FAILURE() << ErrorMessage; return std::vector(); } return Database->getAllCompileCommands(); } TEST(JSONCompilationDatabase, GetAllFiles) { std::string ErrorMessage; EXPECT_EQ(std::vector(), getAllFiles("[]", ErrorMessage, JSONCommandLineSyntax::Gnu)) << ErrorMessage; std::vector expected_files; SmallString<16> PathStorage; llvm::sys::path::native("//net/dir/file1", PathStorage); expected_files.push_back(PathStorage.str()); llvm::sys::path::native("//net/dir/file2", PathStorage); expected_files.push_back(PathStorage.str()); EXPECT_EQ(expected_files, getAllFiles("[{\"directory\":\"//net/dir\"," "\"command\":\"command\"," "\"file\":\"file1\"}," " {\"directory\":\"//net/dir\"," "\"command\":\"command\"," "\"file\":\"file2\"}]", ErrorMessage, JSONCommandLineSyntax::Gnu)) << ErrorMessage; } TEST(JSONCompilationDatabase, GetAllCompileCommands) { std::string ErrorMessage; EXPECT_EQ( 0u, getAllCompileCommands(JSONCommandLineSyntax::Gnu, "[]", ErrorMessage) .size()) << ErrorMessage; StringRef Directory1("//net/dir1"); StringRef FileName1("file1"); StringRef Command1("command1"); StringRef Output1("file1.o"); StringRef Directory2("//net/dir2"); StringRef FileName2("file2"); StringRef Command2("command2"); StringRef Output2(""); std::vector Commands = getAllCompileCommands( JSONCommandLineSyntax::Gnu, ("[{\"directory\":\"" + Directory1 + "\"," + "\"command\":\"" + Command1 + "\"," "\"file\":\"" + FileName1 + "\", \"output\":\"" + Output1 + "\"}," " {\"directory\":\"" + Directory2 + "\"," + "\"command\":\"" + Command2 + "\"," "\"file\":\"" + FileName2 + "\"}]") .str(), ErrorMessage); EXPECT_EQ(2U, Commands.size()) << ErrorMessage; EXPECT_EQ(Directory1, Commands[0].Directory) << ErrorMessage; EXPECT_EQ(FileName1, Commands[0].Filename) << ErrorMessage; EXPECT_EQ(Output1, Commands[0].Output) << ErrorMessage; ASSERT_EQ(1u, Commands[0].CommandLine.size()); EXPECT_EQ(Command1, Commands[0].CommandLine[0]) << ErrorMessage; EXPECT_EQ(Directory2, Commands[1].Directory) << ErrorMessage; EXPECT_EQ(FileName2, Commands[1].Filename) << ErrorMessage; EXPECT_EQ(Output2, Commands[1].Output) << ErrorMessage; ASSERT_EQ(1u, Commands[1].CommandLine.size()); EXPECT_EQ(Command2, Commands[1].CommandLine[0]) << ErrorMessage; // Check that order is preserved. Commands = getAllCompileCommands( JSONCommandLineSyntax::Gnu, ("[{\"directory\":\"" + Directory2 + "\"," + "\"command\":\"" + Command2 + "\"," "\"file\":\"" + FileName2 + "\"}," " {\"directory\":\"" + Directory1 + "\"," + "\"command\":\"" + Command1 + "\"," "\"file\":\"" + FileName1 + "\"}]") .str(), ErrorMessage); EXPECT_EQ(2U, Commands.size()) << ErrorMessage; EXPECT_EQ(Directory2, Commands[0].Directory) << ErrorMessage; EXPECT_EQ(FileName2, Commands[0].Filename) << ErrorMessage; ASSERT_EQ(1u, Commands[0].CommandLine.size()); EXPECT_EQ(Command2, Commands[0].CommandLine[0]) << ErrorMessage; EXPECT_EQ(Directory1, Commands[1].Directory) << ErrorMessage; EXPECT_EQ(FileName1, Commands[1].Filename) << ErrorMessage; ASSERT_EQ(1u, Commands[1].CommandLine.size()); EXPECT_EQ(Command1, Commands[1].CommandLine[0]) << ErrorMessage; } static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName, StringRef JSONDatabase, std::string &ErrorMessage) { std::unique_ptr Database( JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, JSONCommandLineSyntax::Gnu)); if (!Database) return CompileCommand(); std::vector Commands = Database->getCompileCommands(FileName); EXPECT_LE(Commands.size(), 1u); if (Commands.empty()) return CompileCommand(); return Commands[0]; } TEST(JSONCompilationDatabase, ArgumentsPreferredOverCommand) { StringRef Directory("//net/dir"); StringRef FileName("//net/dir/filename"); StringRef Command("command"); StringRef Arguments = "arguments"; Twine ArgumentsAccumulate; std::string ErrorMessage; CompileCommand FoundCommand = findCompileArgsInJsonDatabase( FileName, ("[{\"directory\":\"" + Directory + "\"," "\"arguments\":[\"" + Arguments + "\"]," "\"command\":\"" + Command + "\"," "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage); EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; EXPECT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; EXPECT_EQ(Arguments, FoundCommand.CommandLine[0]) << ErrorMessage; } struct FakeComparator : public PathComparator { ~FakeComparator() override {} bool equivalent(StringRef FileA, StringRef FileB) const override { return FileA.equals_lower(FileB); } }; class FileMatchTrieTest : public ::testing::Test { protected: FileMatchTrieTest() : Trie(new FakeComparator()) {} StringRef find(StringRef Path) { llvm::raw_string_ostream ES(Error); return Trie.findEquivalent(Path, ES); } FileMatchTrie Trie; std::string Error; }; TEST_F(FileMatchTrieTest, InsertingRelativePath) { Trie.insert("//net/path/file.cc"); Trie.insert("file.cc"); EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc")); } TEST_F(FileMatchTrieTest, MatchingRelativePath) { EXPECT_EQ("", find("file.cc")); } TEST_F(FileMatchTrieTest, ReturnsBestResults) { Trie.insert("//net/d/c/b.cc"); Trie.insert("//net/d/b/b.cc"); EXPECT_EQ("//net/d/b/b.cc", find("//net/d/b/b.cc")); } TEST_F(FileMatchTrieTest, HandlesSymlinks) { Trie.insert("//net/AA/file.cc"); EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc")); } TEST_F(FileMatchTrieTest, ReportsSymlinkAmbiguity) { Trie.insert("//net/Aa/file.cc"); Trie.insert("//net/aA/file.cc"); EXPECT_TRUE(find("//net/aa/file.cc").empty()); EXPECT_EQ("Path is ambiguous", Error); } TEST_F(FileMatchTrieTest, LongerMatchingSuffixPreferred) { Trie.insert("//net/src/Aa/file.cc"); Trie.insert("//net/src/aA/file.cc"); Trie.insert("//net/SRC/aa/file.cc"); EXPECT_EQ("//net/SRC/aa/file.cc", find("//net/src/aa/file.cc")); } TEST_F(FileMatchTrieTest, EmptyTrie) { EXPECT_TRUE(find("//net/some/path").empty()); } TEST_F(FileMatchTrieTest, NoResult) { Trie.insert("//net/somepath/otherfile.cc"); Trie.insert("//net/otherpath/somefile.cc"); EXPECT_EQ("", find("//net/somepath/somefile.cc")); } TEST_F(FileMatchTrieTest, RootElementDifferent) { Trie.insert("//net/path/file.cc"); Trie.insert("//net/otherpath/file.cc"); EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc")); } TEST_F(FileMatchTrieTest, CannotResolveRelativePath) { EXPECT_EQ("", find("relative-path.cc")); EXPECT_EQ("Cannot resolve relative paths", Error); } TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) { std::string ErrorMessage; CompileCommand NotFound = findCompileArgsInJsonDatabase( "a-file.cpp", "", ErrorMessage); EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; } TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) { StringRef Directory("//net/some/directory"); StringRef FileName("//net/path/to/a-file.cpp"); StringRef Command("//net/path/to/compiler and some arguments"); std::string ErrorMessage; CompileCommand FoundCommand = findCompileArgsInJsonDatabase( FileName, ("[{\"directory\":\"" + Directory + "\"," + "\"command\":\"" + Command + "\"," "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage); EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage; EXPECT_EQ("//net/path/to/compiler", FoundCommand.CommandLine[0]) << ErrorMessage; EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage; EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage; EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage; CompileCommand NotFound = findCompileArgsInJsonDatabase( "a-file.cpp", ("[{\"directory\":\"" + Directory + "\"," + "\"command\":\"" + Command + "\"," "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage); EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; } TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) { StringRef Directory("//net/some/directory"); StringRef FileName("//net/path/to/a-file.cpp"); StringRef Command("\\\"//net/path to compiler\\\" \\\"and an argument\\\""); std::string ErrorMessage; CompileCommand FoundCommand = findCompileArgsInJsonDatabase( FileName, ("[{\"directory\":\"" + Directory + "\"," + "\"command\":\"" + Command + "\"," "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage); ASSERT_EQ(2u, FoundCommand.CommandLine.size()); EXPECT_EQ("//net/path to compiler", FoundCommand.CommandLine[0]) << ErrorMessage; EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage; } TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) { StringRef Directory("//net/some directory / with spaces"); StringRef FileName("//net/path/to/a-file.cpp"); StringRef Command("a command"); std::string ErrorMessage; CompileCommand FoundCommand = findCompileArgsInJsonDatabase( FileName, ("[{\"directory\":\"" + Directory + "\"," + "\"command\":\"" + Command + "\"," "\"file\":\"" + FileName + "\"}]").str(), ErrorMessage); EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; } TEST(findCompileArgsInJsonDatabase, FindsEntry) { StringRef Directory("//net/directory"); StringRef FileName("file"); StringRef Command("command"); std::string JsonDatabase = "["; for (int I = 0; I < 10; ++I) { if (I > 0) JsonDatabase += ","; JsonDatabase += ("{\"directory\":\"" + Directory + Twine(I) + "\"," + "\"command\":\"" + Command + Twine(I) + "\"," "\"file\":\"" + FileName + Twine(I) + "\"}").str(); } JsonDatabase += "]"; std::string ErrorMessage; CompileCommand FoundCommand = findCompileArgsInJsonDatabase( "//net/directory4/file4", JsonDatabase, ErrorMessage); EXPECT_EQ("//net/directory4", FoundCommand.Directory) << ErrorMessage; ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage; } static std::vector unescapeJsonCommandLine(StringRef Command) { std::string JsonDatabase = ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" + Command + "\"}]").str(); std::string ErrorMessage; CompileCommand FoundCommand = findCompileArgsInJsonDatabase( "//net/root/test", JsonDatabase, ErrorMessage); EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage; return FoundCommand.CommandLine; } TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) { std::vector Result = unescapeJsonCommandLine(""); EXPECT_TRUE(Result.empty()); } TEST(unescapeJsonCommandLine, SplitsOnSpaces) { std::vector Result = unescapeJsonCommandLine("a b c"); ASSERT_EQ(3ul, Result.size()); EXPECT_EQ("a", Result[0]); EXPECT_EQ("b", Result[1]); EXPECT_EQ("c", Result[2]); } TEST(unescapeJsonCommandLine, MungesMultipleSpaces) { std::vector Result = unescapeJsonCommandLine(" a b "); ASSERT_EQ(2ul, Result.size()); EXPECT_EQ("a", Result[0]); EXPECT_EQ("b", Result[1]); } TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) { std::vector Backslash = unescapeJsonCommandLine("a\\\\\\\\"); ASSERT_EQ(1ul, Backslash.size()); EXPECT_EQ("a\\", Backslash[0]); std::vector Quote = unescapeJsonCommandLine("a\\\\\\\""); ASSERT_EQ(1ul, Quote.size()); EXPECT_EQ("a\"", Quote[0]); } TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) { std::vector Result = unescapeJsonCommandLine("\\\" a b \\\""); ASSERT_EQ(1ul, Result.size()); EXPECT_EQ(" a b ", Result[0]); } TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) { std::vector Result = unescapeJsonCommandLine( " \\\" a \\\" \\\" b \\\" "); ASSERT_EQ(2ul, Result.size()); EXPECT_EQ(" a ", Result[0]); EXPECT_EQ(" b ", Result[1]); } TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) { std::vector Result = unescapeJsonCommandLine( "\\\"\\\"\\\"\\\""); ASSERT_EQ(1ul, Result.size()); EXPECT_TRUE(Result[0].empty()) << Result[0]; } TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) { std::vector Result = unescapeJsonCommandLine( "\\\"\\\\\\\"\\\""); ASSERT_EQ(1ul, Result.size()); EXPECT_EQ("\"", Result[0]); } TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) { std::vector Result = unescapeJsonCommandLine( " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\""); ASSERT_EQ(4ul, Result.size()); EXPECT_EQ("\"", Result[0]); EXPECT_EQ("a \" b ", Result[1]); EXPECT_EQ("and\\c", Result[2]); EXPECT_EQ("\"", Result[3]); } TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) { std::vector QuotedNoSpaces = unescapeJsonCommandLine( "\\\"a\\\"\\\"b\\\""); ASSERT_EQ(1ul, QuotedNoSpaces.size()); EXPECT_EQ("ab", QuotedNoSpaces[0]); std::vector MixedNoSpaces = unescapeJsonCommandLine( "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\""); ASSERT_EQ(1ul, MixedNoSpaces.size()); EXPECT_EQ("abcdefg", MixedNoSpaces[0]); } TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) { std::vector Unclosed = unescapeJsonCommandLine("\\\"abc"); ASSERT_EQ(1ul, Unclosed.size()); EXPECT_EQ("abc", Unclosed[0]); std::vector Empty = unescapeJsonCommandLine("\\\""); ASSERT_EQ(1ul, Empty.size()); EXPECT_EQ("", Empty[0]); } TEST(unescapeJsonCommandLine, ParsesSingleQuotedString) { std::vector Args = unescapeJsonCommandLine("a'\\\\b \\\"c\\\"'"); ASSERT_EQ(1ul, Args.size()); EXPECT_EQ("a\\b \"c\"", Args[0]); } TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { std::vector CommandLine; CommandLine.push_back("one"); CommandLine.push_back("two"); FixedCompilationDatabase Database(".", CommandLine); StringRef FileName("source"); std::vector Result = Database.getCompileCommands(FileName); ASSERT_EQ(1ul, Result.size()); std::vector ExpectedCommandLine(1, "clang-tool"); ExpectedCommandLine.insert(ExpectedCommandLine.end(), CommandLine.begin(), CommandLine.end()); ExpectedCommandLine.push_back("source"); EXPECT_EQ(".", Result[0].Directory); EXPECT_EQ(FileName, Result[0].Filename); EXPECT_EQ(ExpectedCommandLine, Result[0].CommandLine); } TEST(FixedCompilationDatabase, GetAllFiles) { std::vector CommandLine; CommandLine.push_back("one"); CommandLine.push_back("two"); FixedCompilationDatabase Database(".", CommandLine); EXPECT_EQ(0ul, Database.getAllFiles().size()); } TEST(FixedCompilationDatabase, GetAllCompileCommands) { std::vector CommandLine; CommandLine.push_back("one"); CommandLine.push_back("two"); FixedCompilationDatabase Database(".", CommandLine); EXPECT_EQ(0ul, Database.getAllCompileCommands().size()); } TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { int Argc = 0; std::string ErrorMsg; std::unique_ptr Database = FixedCompilationDatabase::loadFromCommandLine(Argc, nullptr, ErrorMsg); EXPECT_FALSE(Database); EXPECT_TRUE(ErrorMsg.empty()); EXPECT_EQ(0, Argc); } TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) { int Argc = 2; const char *Argv[] = { "1", "2" }; std::string ErrorMsg; std::unique_ptr Database( FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); EXPECT_FALSE(Database); EXPECT_TRUE(ErrorMsg.empty()); EXPECT_EQ(2, Argc); } TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) { int Argc = 5; const char *Argv[] = { "1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4" }; std::string ErrorMsg; std::unique_ptr Database( FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); ASSERT_TRUE((bool)Database); ASSERT_TRUE(ErrorMsg.empty()); std::vector Result = Database->getCompileCommands("source"); ASSERT_EQ(1ul, Result.size()); ASSERT_EQ(".", Result[0].Directory); std::vector CommandLine; CommandLine.push_back("clang-tool"); CommandLine.push_back("-DDEF3"); CommandLine.push_back("-DDEF4"); CommandLine.push_back("source"); ASSERT_EQ(CommandLine, Result[0].CommandLine); EXPECT_EQ(2, Argc); } TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) { int Argc = 3; const char *Argv[] = { "1", "2", "--\0no-constant-folding" }; std::string ErrorMsg; std::unique_ptr Database = FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); ASSERT_TRUE((bool)Database); ASSERT_TRUE(ErrorMsg.empty()); std::vector Result = Database->getCompileCommands("source"); ASSERT_EQ(1ul, Result.size()); ASSERT_EQ(".", Result[0].Directory); std::vector CommandLine; CommandLine.push_back("clang-tool"); CommandLine.push_back("source"); ASSERT_EQ(CommandLine, Result[0].CommandLine); EXPECT_EQ(2, Argc); } TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) { const char *Argv[] = {"1", "2", "--", "-c", "somefile.cpp", "-DDEF3"}; int Argc = sizeof(Argv) / sizeof(char*); std::string ErrorMsg; std::unique_ptr Database = FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); ASSERT_TRUE((bool)Database); ASSERT_TRUE(ErrorMsg.empty()); std::vector Result = Database->getCompileCommands("source"); ASSERT_EQ(1ul, Result.size()); ASSERT_EQ(".", Result[0].Directory); std::vector Expected; Expected.push_back("clang-tool"); Expected.push_back("-c"); Expected.push_back("-DDEF3"); Expected.push_back("source"); ASSERT_EQ(Expected, Result[0].CommandLine); EXPECT_EQ(2, Argc); } +TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsSyntaxOnly) { + // Adjust the given command line arguments to ensure that any positional + // arguments in them are stripped. + const char *Argv[] = {"--", "somefile.cpp", "-fsyntax-only", "-DDEF3"}; + int Argc = llvm::array_lengthof(Argv); + std::string ErrorMessage; + std::unique_ptr Database = + FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMessage); + ASSERT_TRUE((bool)Database); + ASSERT_TRUE(ErrorMessage.empty()); + std::vector Result = Database->getCompileCommands("source"); + ASSERT_EQ(1ul, Result.size()); + ASSERT_EQ(".", Result[0].Directory); + std::vector Expected; + Expected.push_back("clang-tool"); + Expected.push_back("-fsyntax-only"); + Expected.push_back("-DDEF3"); + Expected.push_back("source"); + ASSERT_EQ(Expected, Result[0].CommandLine); +} + TEST(ParseFixedCompilationDatabase, HandlesArgv0) { const char *Argv[] = {"1", "2", "--", "mytool", "somefile.cpp"}; int Argc = sizeof(Argv) / sizeof(char*); std::string ErrorMsg; std::unique_ptr Database = FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); ASSERT_TRUE((bool)Database); ASSERT_TRUE(ErrorMsg.empty()); std::vector Result = Database->getCompileCommands("source"); ASSERT_EQ(1ul, Result.size()); ASSERT_EQ(".", Result[0].Directory); std::vector Expected; Expected.push_back("clang-tool"); Expected.push_back("source"); ASSERT_EQ(Expected, Result[0].CommandLine); EXPECT_EQ(2, Argc); } } // end namespace tooling } // end namespace clang