1 //===- unittest/Tooling/CompilationDatabaseTest.cpp -----------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "clang/AST/DeclCXX.h" 10 #include "clang/AST/DeclGroup.h" 11 #include "clang/Frontend/FrontendAction.h" 12 #include "clang/Tooling/CompilationDatabase.h" 13 #include "clang/Tooling/FileMatchTrie.h" 14 #include "clang/Tooling/JSONCompilationDatabase.h" 15 #include "clang/Tooling/Tooling.h" 16 #include "llvm/Support/Path.h" 17 #include "llvm/Support/TargetSelect.h" 18 #include "gmock/gmock.h" 19 #include "gtest/gtest.h" 20 21 namespace clang { 22 namespace tooling { 23 24 using testing::ElementsAre; 25 using testing::EndsWith; 26 27 static void expectFailure(StringRef JSONDatabase, StringRef Explanation) { 28 std::string ErrorMessage; 29 EXPECT_EQ(nullptr, 30 JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, 31 JSONCommandLineSyntax::Gnu)) 32 << "Expected an error because of: " << Explanation.str(); 33 } 34 35 TEST(JSONCompilationDatabase, ErrsOnInvalidFormat) { 36 expectFailure("", "Empty database"); 37 expectFailure("{", "Invalid JSON"); 38 expectFailure("[[]]", "Array instead of object"); 39 expectFailure("[{\"a\":[]}]", "Array instead of value"); 40 expectFailure("[{\"a\":\"b\"}]", "Unknown key"); 41 expectFailure("[{[]:\"\"}]", "Incorrectly typed entry"); 42 expectFailure("[{}]", "Empty entry"); 43 expectFailure("[{\"directory\":\"\",\"command\":\"\"}]", "Missing file"); 44 expectFailure("[{\"directory\":\"\",\"file\":\"\"}]", "Missing command or arguments"); 45 expectFailure("[{\"command\":\"\",\"file\":\"\"}]", "Missing directory"); 46 expectFailure("[{\"directory\":\"\",\"arguments\":[]}]", "Missing file"); 47 expectFailure("[{\"arguments\":\"\",\"file\":\"\"}]", "Missing directory"); 48 expectFailure("[{\"directory\":\"\",\"arguments\":\"\",\"file\":\"\"}]", "Arguments not array"); 49 expectFailure("[{\"directory\":\"\",\"command\":[],\"file\":\"\"}]", "Command not string"); 50 expectFailure("[{\"directory\":\"\",\"arguments\":[[]],\"file\":\"\"}]", 51 "Arguments contain non-string"); 52 expectFailure("[{\"output\":[]}]", "Expected strings as value."); 53 } 54 55 static std::vector<std::string> getAllFiles(StringRef JSONDatabase, 56 std::string &ErrorMessage, 57 JSONCommandLineSyntax Syntax) { 58 std::unique_ptr<CompilationDatabase> Database( 59 JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, 60 Syntax)); 61 if (!Database) { 62 ADD_FAILURE() << ErrorMessage; 63 return std::vector<std::string>(); 64 } 65 return Database->getAllFiles(); 66 } 67 68 static std::vector<CompileCommand> 69 getAllCompileCommands(JSONCommandLineSyntax Syntax, StringRef JSONDatabase, 70 std::string &ErrorMessage) { 71 std::unique_ptr<CompilationDatabase> Database( 72 JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, 73 Syntax)); 74 if (!Database) { 75 ADD_FAILURE() << ErrorMessage; 76 return std::vector<CompileCommand>(); 77 } 78 return Database->getAllCompileCommands(); 79 } 80 81 TEST(JSONCompilationDatabase, GetAllFiles) { 82 std::string ErrorMessage; 83 EXPECT_EQ(std::vector<std::string>(), 84 getAllFiles("[]", ErrorMessage, JSONCommandLineSyntax::Gnu)) 85 << ErrorMessage; 86 87 std::vector<std::string> expected_files; 88 SmallString<16> PathStorage; 89 llvm::sys::path::native("//net/dir/file1", PathStorage); 90 expected_files.push_back(std::string(PathStorage.str())); 91 llvm::sys::path::native("//net/dir/file2", PathStorage); 92 expected_files.push_back(std::string(PathStorage.str())); 93 llvm::sys::path::native("//net/file1", PathStorage); 94 expected_files.push_back(std::string(PathStorage.str())); 95 EXPECT_EQ(expected_files, 96 getAllFiles("[{\"directory\":\"//net/dir\"," 97 "\"command\":\"command\"," 98 "\"file\":\"file1\"}," 99 " {\"directory\":\"//net/dir\"," 100 "\"command\":\"command\"," 101 "\"file\":\"../file1\"}," 102 " {\"directory\":\"//net/dir\"," 103 "\"command\":\"command\"," 104 "\"file\":\"file2\"}]", 105 ErrorMessage, JSONCommandLineSyntax::Gnu)) 106 << ErrorMessage; 107 } 108 109 TEST(JSONCompilationDatabase, GetAllCompileCommands) { 110 std::string ErrorMessage; 111 EXPECT_EQ( 112 0u, getAllCompileCommands(JSONCommandLineSyntax::Gnu, "[]", ErrorMessage) 113 .size()) 114 << ErrorMessage; 115 116 StringRef Directory1("//net/dir1"); 117 StringRef FileName1("file1"); 118 StringRef Command1("command1"); 119 StringRef Output1("file1.o"); 120 StringRef Directory2("//net/dir2"); 121 StringRef FileName2("file2"); 122 StringRef Command2("command2"); 123 StringRef Output2(""); 124 125 std::vector<CompileCommand> Commands = getAllCompileCommands( 126 JSONCommandLineSyntax::Gnu, 127 ("[{\"directory\":\"" + Directory1 + "\"," + "\"command\":\"" + Command1 + 128 "\"," 129 "\"file\":\"" + 130 FileName1 + "\", \"output\":\"" + 131 Output1 + "\"}," 132 " {\"directory\":\"" + 133 Directory2 + "\"," + "\"command\":\"" + Command2 + "\"," 134 "\"file\":\"" + 135 FileName2 + "\"}]") 136 .str(), 137 ErrorMessage); 138 EXPECT_EQ(2U, Commands.size()) << ErrorMessage; 139 EXPECT_EQ(Directory1, Commands[0].Directory) << ErrorMessage; 140 EXPECT_EQ(FileName1, Commands[0].Filename) << ErrorMessage; 141 EXPECT_EQ(Output1, Commands[0].Output) << ErrorMessage; 142 ASSERT_EQ(1u, Commands[0].CommandLine.size()); 143 EXPECT_EQ(Command1, Commands[0].CommandLine[0]) << ErrorMessage; 144 EXPECT_EQ(Directory2, Commands[1].Directory) << ErrorMessage; 145 EXPECT_EQ(FileName2, Commands[1].Filename) << ErrorMessage; 146 EXPECT_EQ(Output2, Commands[1].Output) << ErrorMessage; 147 ASSERT_EQ(1u, Commands[1].CommandLine.size()); 148 EXPECT_EQ(Command2, Commands[1].CommandLine[0]) << ErrorMessage; 149 150 // Check that order is preserved. 151 Commands = getAllCompileCommands( 152 JSONCommandLineSyntax::Gnu, 153 ("[{\"directory\":\"" + Directory2 + "\"," + "\"command\":\"" + Command2 + 154 "\"," 155 "\"file\":\"" + 156 FileName2 + "\"}," 157 " {\"directory\":\"" + 158 Directory1 + "\"," + "\"command\":\"" + Command1 + "\"," 159 "\"file\":\"" + 160 FileName1 + "\"}]") 161 .str(), 162 ErrorMessage); 163 EXPECT_EQ(2U, Commands.size()) << ErrorMessage; 164 EXPECT_EQ(Directory2, Commands[0].Directory) << ErrorMessage; 165 EXPECT_EQ(FileName2, Commands[0].Filename) << ErrorMessage; 166 ASSERT_EQ(1u, Commands[0].CommandLine.size()); 167 EXPECT_EQ(Command2, Commands[0].CommandLine[0]) << ErrorMessage; 168 EXPECT_EQ(Directory1, Commands[1].Directory) << ErrorMessage; 169 EXPECT_EQ(FileName1, Commands[1].Filename) << ErrorMessage; 170 ASSERT_EQ(1u, Commands[1].CommandLine.size()); 171 EXPECT_EQ(Command1, Commands[1].CommandLine[0]) << ErrorMessage; 172 } 173 174 static CompileCommand findCompileArgsInJsonDatabase(StringRef FileName, 175 std::string JSONDatabase, 176 std::string &ErrorMessage) { 177 std::unique_ptr<CompilationDatabase> Database( 178 JSONCompilationDatabase::loadFromBuffer(JSONDatabase, ErrorMessage, 179 JSONCommandLineSyntax::Gnu)); 180 if (!Database) 181 return CompileCommand(); 182 // Overwrite the string to verify we're not reading from it later. 183 JSONDatabase.assign(JSONDatabase.size(), '*'); 184 std::vector<CompileCommand> Commands = Database->getCompileCommands(FileName); 185 EXPECT_LE(Commands.size(), 1u); 186 if (Commands.empty()) 187 return CompileCommand(); 188 return Commands[0]; 189 } 190 191 TEST(JSONCompilationDatabase, ArgumentsPreferredOverCommand) { 192 StringRef Directory("//net/dir"); 193 StringRef FileName("//net/dir/filename"); 194 StringRef Command("command"); 195 StringRef Arguments = "arguments"; 196 Twine ArgumentsAccumulate; 197 std::string ErrorMessage; 198 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 199 FileName, 200 ("[{\"directory\":\"" + Directory + "\"," 201 "\"arguments\":[\"" + Arguments + "\"]," 202 "\"command\":\"" + Command + "\"," 203 "\"file\":\"" + FileName + "\"}]").str(), 204 ErrorMessage); 205 EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; 206 EXPECT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; 207 EXPECT_EQ(Arguments, FoundCommand.CommandLine[0]) << ErrorMessage; 208 } 209 210 struct FakeComparator : public PathComparator { 211 ~FakeComparator() override {} 212 bool equivalent(StringRef FileA, StringRef FileB) const override { 213 return FileA.equals_insensitive(FileB); 214 } 215 }; 216 217 class FileMatchTrieTest : public ::testing::Test { 218 protected: 219 FileMatchTrieTest() : Trie(new FakeComparator()) {} 220 221 StringRef find(StringRef Path) { 222 llvm::raw_string_ostream ES(Error); 223 return Trie.findEquivalent(Path, ES); 224 } 225 226 FileMatchTrie Trie; 227 std::string Error; 228 }; 229 230 TEST_F(FileMatchTrieTest, InsertingRelativePath) { 231 Trie.insert("//net/path/file.cc"); 232 Trie.insert("file.cc"); 233 EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc")); 234 } 235 236 TEST_F(FileMatchTrieTest, MatchingRelativePath) { 237 EXPECT_EQ("", find("file.cc")); 238 } 239 240 TEST_F(FileMatchTrieTest, ReturnsBestResults) { 241 Trie.insert("//net/d/c/b.cc"); 242 Trie.insert("//net/d/b/b.cc"); 243 EXPECT_EQ("//net/d/b/b.cc", find("//net/d/b/b.cc")); 244 } 245 246 TEST_F(FileMatchTrieTest, HandlesSymlinks) { 247 Trie.insert("//net/AA/file.cc"); 248 EXPECT_EQ("//net/AA/file.cc", find("//net/aa/file.cc")); 249 } 250 251 TEST_F(FileMatchTrieTest, ReportsSymlinkAmbiguity) { 252 Trie.insert("//net/Aa/file.cc"); 253 Trie.insert("//net/aA/file.cc"); 254 EXPECT_TRUE(find("//net/aa/file.cc").empty()); 255 EXPECT_EQ("Path is ambiguous", Error); 256 } 257 258 TEST_F(FileMatchTrieTest, LongerMatchingSuffixPreferred) { 259 Trie.insert("//net/src/Aa/file.cc"); 260 Trie.insert("//net/src/aA/file.cc"); 261 Trie.insert("//net/SRC/aa/file.cc"); 262 EXPECT_EQ("//net/SRC/aa/file.cc", find("//net/src/aa/file.cc")); 263 } 264 265 TEST_F(FileMatchTrieTest, EmptyTrie) { 266 EXPECT_TRUE(find("//net/some/path").empty()); 267 } 268 269 TEST_F(FileMatchTrieTest, NoResult) { 270 Trie.insert("//net/somepath/otherfile.cc"); 271 Trie.insert("//net/otherpath/somefile.cc"); 272 EXPECT_EQ("", find("//net/somepath/somefile.cc")); 273 } 274 275 TEST_F(FileMatchTrieTest, RootElementDifferent) { 276 Trie.insert("//net/path/file.cc"); 277 Trie.insert("//net/otherpath/file.cc"); 278 EXPECT_EQ("//net/path/file.cc", find("//net/path/file.cc")); 279 } 280 281 TEST_F(FileMatchTrieTest, CannotResolveRelativePath) { 282 EXPECT_EQ("", find("relative-path.cc")); 283 EXPECT_EQ("Cannot resolve relative paths", Error); 284 } 285 286 TEST_F(FileMatchTrieTest, SingleFile) { 287 Trie.insert("/root/RootFile.cc"); 288 EXPECT_EQ("", find("/root/rootfile.cc")); 289 // Add subpath to avoid `if (Children.empty())` special case 290 // which we hit at previous `find()`. 291 Trie.insert("/root/otherpath/OtherFile.cc"); 292 EXPECT_EQ("", find("/root/rootfile.cc")); 293 } 294 295 TEST(findCompileArgsInJsonDatabase, FindsNothingIfEmpty) { 296 std::string ErrorMessage; 297 CompileCommand NotFound = findCompileArgsInJsonDatabase( 298 "a-file.cpp", "", ErrorMessage); 299 EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; 300 EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; 301 } 302 303 TEST(findCompileArgsInJsonDatabase, ReadsSingleEntry) { 304 StringRef Directory("//net/some/directory"); 305 StringRef FileName("//net/path/to/a-file.cpp"); 306 StringRef Command("//net/path/to/compiler and some arguments"); 307 std::string ErrorMessage; 308 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 309 FileName, 310 ("[{\"directory\":\"" + Directory + "\"," + 311 "\"command\":\"" + Command + "\"," 312 "\"file\":\"" + FileName + "\"}]").str(), 313 ErrorMessage); 314 EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; 315 ASSERT_EQ(4u, FoundCommand.CommandLine.size()) << ErrorMessage; 316 EXPECT_EQ("//net/path/to/compiler", 317 FoundCommand.CommandLine[0]) << ErrorMessage; 318 EXPECT_EQ("and", FoundCommand.CommandLine[1]) << ErrorMessage; 319 EXPECT_EQ("some", FoundCommand.CommandLine[2]) << ErrorMessage; 320 EXPECT_EQ("arguments", FoundCommand.CommandLine[3]) << ErrorMessage; 321 322 CompileCommand NotFound = findCompileArgsInJsonDatabase( 323 "a-file.cpp", 324 ("[{\"directory\":\"" + Directory + "\"," + 325 "\"command\":\"" + Command + "\"," 326 "\"file\":\"" + FileName + "\"}]").str(), 327 ErrorMessage); 328 EXPECT_TRUE(NotFound.Directory.empty()) << ErrorMessage; 329 EXPECT_TRUE(NotFound.CommandLine.empty()) << ErrorMessage; 330 } 331 332 TEST(findCompileArgsInJsonDatabase, ReadsCompileCommandLinesWithSpaces) { 333 StringRef Directory("//net/some/directory"); 334 StringRef FileName("//net/path/to/a-file.cpp"); 335 StringRef Command("\\\"//net/path to compiler\\\" \\\"and an argument\\\""); 336 std::string ErrorMessage; 337 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 338 FileName, 339 ("[{\"directory\":\"" + Directory + "\"," + 340 "\"command\":\"" + Command + "\"," 341 "\"file\":\"" + FileName + "\"}]").str(), 342 ErrorMessage); 343 ASSERT_EQ(2u, FoundCommand.CommandLine.size()); 344 EXPECT_EQ("//net/path to compiler", 345 FoundCommand.CommandLine[0]) << ErrorMessage; 346 EXPECT_EQ("and an argument", FoundCommand.CommandLine[1]) << ErrorMessage; 347 } 348 349 TEST(findCompileArgsInJsonDatabase, ReadsDirectoryWithSpaces) { 350 StringRef Directory("//net/some directory / with spaces"); 351 StringRef FileName("//net/path/to/a-file.cpp"); 352 StringRef Command("a command"); 353 std::string ErrorMessage; 354 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 355 FileName, 356 ("[{\"directory\":\"" + Directory + "\"," + 357 "\"command\":\"" + Command + "\"," 358 "\"file\":\"" + FileName + "\"}]").str(), 359 ErrorMessage); 360 EXPECT_EQ(Directory, FoundCommand.Directory) << ErrorMessage; 361 } 362 363 TEST(findCompileArgsInJsonDatabase, FindsEntry) { 364 StringRef Directory("//net/directory"); 365 StringRef FileName("file"); 366 StringRef Command("command"); 367 std::string JsonDatabase = "["; 368 for (int I = 0; I < 10; ++I) { 369 if (I > 0) JsonDatabase += ","; 370 JsonDatabase += 371 ("{\"directory\":\"" + Directory + Twine(I) + "\"," + 372 "\"command\":\"" + Command + Twine(I) + "\"," 373 "\"file\":\"" + FileName + Twine(I) + "\"}").str(); 374 } 375 JsonDatabase += "]"; 376 std::string ErrorMessage; 377 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 378 "//net/directory4/file4", JsonDatabase, ErrorMessage); 379 EXPECT_EQ("//net/directory4", FoundCommand.Directory) << ErrorMessage; 380 ASSERT_EQ(1u, FoundCommand.CommandLine.size()) << ErrorMessage; 381 EXPECT_EQ("command4", FoundCommand.CommandLine[0]) << ErrorMessage; 382 } 383 384 TEST(findCompileArgsInJsonDatabase, ParsesCompilerWrappers) { 385 std::vector<std::pair<std::string, std::string>> Cases = { 386 {"distcc gcc foo.c", "gcc foo.c"}, 387 {"gomacc clang++ foo.c", "clang++ foo.c"}, 388 {"sccache clang++ foo.c", "clang++ foo.c"}, 389 {"ccache gcc foo.c", "gcc foo.c"}, 390 {"ccache.exe gcc foo.c", "gcc foo.c"}, 391 {"ccache g++.exe foo.c", "g++.exe foo.c"}, 392 {"ccache distcc gcc foo.c", "gcc foo.c"}, 393 394 {"distcc foo.c", "distcc foo.c"}, 395 {"distcc -I/foo/bar foo.c", "distcc -I/foo/bar foo.c"}, 396 }; 397 std::string ErrorMessage; 398 399 for (const auto &Case : Cases) { 400 std::string DB = 401 R"([{"directory":"//net/dir", "file":"//net/dir/foo.c", "command":")" + 402 Case.first + "\"}]"; 403 CompileCommand FoundCommand = 404 findCompileArgsInJsonDatabase("//net/dir/foo.c", DB, ErrorMessage); 405 EXPECT_EQ(Case.second, llvm::join(FoundCommand.CommandLine, " ")) 406 << Case.first; 407 } 408 } 409 410 static std::vector<std::string> unescapeJsonCommandLine(StringRef Command) { 411 std::string JsonDatabase = 412 ("[{\"directory\":\"//net/root\", \"file\":\"test\", \"command\": \"" + 413 Command + "\"}]").str(); 414 std::string ErrorMessage; 415 CompileCommand FoundCommand = findCompileArgsInJsonDatabase( 416 "//net/root/test", JsonDatabase, ErrorMessage); 417 EXPECT_TRUE(ErrorMessage.empty()) << ErrorMessage; 418 return FoundCommand.CommandLine; 419 } 420 421 TEST(unescapeJsonCommandLine, ReturnsEmptyArrayOnEmptyString) { 422 std::vector<std::string> Result = unescapeJsonCommandLine(""); 423 EXPECT_TRUE(Result.empty()); 424 } 425 426 TEST(unescapeJsonCommandLine, SplitsOnSpaces) { 427 std::vector<std::string> Result = unescapeJsonCommandLine("a b c"); 428 ASSERT_EQ(3ul, Result.size()); 429 EXPECT_EQ("a", Result[0]); 430 EXPECT_EQ("b", Result[1]); 431 EXPECT_EQ("c", Result[2]); 432 } 433 434 TEST(unescapeJsonCommandLine, MungesMultipleSpaces) { 435 std::vector<std::string> Result = unescapeJsonCommandLine(" a b "); 436 ASSERT_EQ(2ul, Result.size()); 437 EXPECT_EQ("a", Result[0]); 438 EXPECT_EQ("b", Result[1]); 439 } 440 441 TEST(unescapeJsonCommandLine, UnescapesBackslashCharacters) { 442 std::vector<std::string> Backslash = unescapeJsonCommandLine("a\\\\\\\\"); 443 ASSERT_EQ(1ul, Backslash.size()); 444 EXPECT_EQ("a\\", Backslash[0]); 445 std::vector<std::string> Quote = unescapeJsonCommandLine("a\\\\\\\""); 446 ASSERT_EQ(1ul, Quote.size()); 447 EXPECT_EQ("a\"", Quote[0]); 448 } 449 450 TEST(unescapeJsonCommandLine, DoesNotMungeSpacesBetweenQuotes) { 451 std::vector<std::string> Result = unescapeJsonCommandLine("\\\" a b \\\""); 452 ASSERT_EQ(1ul, Result.size()); 453 EXPECT_EQ(" a b ", Result[0]); 454 } 455 456 TEST(unescapeJsonCommandLine, AllowsMultipleQuotedArguments) { 457 std::vector<std::string> Result = unescapeJsonCommandLine( 458 " \\\" a \\\" \\\" b \\\" "); 459 ASSERT_EQ(2ul, Result.size()); 460 EXPECT_EQ(" a ", Result[0]); 461 EXPECT_EQ(" b ", Result[1]); 462 } 463 464 TEST(unescapeJsonCommandLine, AllowsEmptyArgumentsInQuotes) { 465 std::vector<std::string> Result = unescapeJsonCommandLine( 466 "\\\"\\\"\\\"\\\""); 467 ASSERT_EQ(1ul, Result.size()); 468 EXPECT_TRUE(Result[0].empty()) << Result[0]; 469 } 470 471 TEST(unescapeJsonCommandLine, ParsesEscapedQuotesInQuotedStrings) { 472 std::vector<std::string> Result = unescapeJsonCommandLine( 473 "\\\"\\\\\\\"\\\""); 474 ASSERT_EQ(1ul, Result.size()); 475 EXPECT_EQ("\"", Result[0]); 476 } 477 478 TEST(unescapeJsonCommandLine, ParsesMultipleArgumentsWithEscapedCharacters) { 479 std::vector<std::string> Result = unescapeJsonCommandLine( 480 " \\\\\\\" \\\"a \\\\\\\" b \\\" \\\"and\\\\\\\\c\\\" \\\\\\\""); 481 ASSERT_EQ(4ul, Result.size()); 482 EXPECT_EQ("\"", Result[0]); 483 EXPECT_EQ("a \" b ", Result[1]); 484 EXPECT_EQ("and\\c", Result[2]); 485 EXPECT_EQ("\"", Result[3]); 486 } 487 488 TEST(unescapeJsonCommandLine, ParsesStringsWithoutSpacesIntoSingleArgument) { 489 std::vector<std::string> QuotedNoSpaces = unescapeJsonCommandLine( 490 "\\\"a\\\"\\\"b\\\""); 491 ASSERT_EQ(1ul, QuotedNoSpaces.size()); 492 EXPECT_EQ("ab", QuotedNoSpaces[0]); 493 494 std::vector<std::string> MixedNoSpaces = unescapeJsonCommandLine( 495 "\\\"a\\\"bcd\\\"ef\\\"\\\"\\\"\\\"g\\\""); 496 ASSERT_EQ(1ul, MixedNoSpaces.size()); 497 EXPECT_EQ("abcdefg", MixedNoSpaces[0]); 498 } 499 500 TEST(unescapeJsonCommandLine, ParsesQuotedStringWithoutClosingQuote) { 501 std::vector<std::string> Unclosed = unescapeJsonCommandLine("\\\"abc"); 502 ASSERT_EQ(1ul, Unclosed.size()); 503 EXPECT_EQ("abc", Unclosed[0]); 504 505 std::vector<std::string> Empty = unescapeJsonCommandLine("\\\""); 506 ASSERT_EQ(1ul, Empty.size()); 507 EXPECT_EQ("", Empty[0]); 508 } 509 510 TEST(unescapeJsonCommandLine, ParsesSingleQuotedString) { 511 std::vector<std::string> Args = unescapeJsonCommandLine("a'\\\\b \\\"c\\\"'"); 512 ASSERT_EQ(1ul, Args.size()); 513 EXPECT_EQ("a\\b \"c\"", Args[0]); 514 } 515 516 TEST(FixedCompilationDatabase, ReturnsFixedCommandLine) { 517 FixedCompilationDatabase Database(".", /*CommandLine*/ {"one", "two"}); 518 StringRef FileName("source"); 519 std::vector<CompileCommand> Result = 520 Database.getCompileCommands(FileName); 521 ASSERT_EQ(1ul, Result.size()); 522 EXPECT_EQ(".", Result[0].Directory); 523 EXPECT_EQ(FileName, Result[0].Filename); 524 EXPECT_THAT(Result[0].CommandLine, 525 ElementsAre(EndsWith("clang-tool"), "one", "two", "source")); 526 } 527 528 TEST(FixedCompilationDatabase, GetAllFiles) { 529 std::vector<std::string> CommandLine; 530 CommandLine.push_back("one"); 531 CommandLine.push_back("two"); 532 FixedCompilationDatabase Database(".", CommandLine); 533 534 EXPECT_EQ(0ul, Database.getAllFiles().size()); 535 } 536 537 TEST(FixedCompilationDatabase, GetAllCompileCommands) { 538 std::vector<std::string> CommandLine; 539 CommandLine.push_back("one"); 540 CommandLine.push_back("two"); 541 FixedCompilationDatabase Database(".", CommandLine); 542 543 EXPECT_EQ(0ul, Database.getAllCompileCommands().size()); 544 } 545 546 TEST(FixedCompilationDatabase, FromBuffer) { 547 const char *Data = R"( 548 549 -DFOO=BAR 550 551 --baz 552 553 )"; 554 std::string ErrorMsg; 555 auto CDB = 556 FixedCompilationDatabase::loadFromBuffer("/cdb/dir", Data, ErrorMsg); 557 558 std::vector<CompileCommand> Result = CDB->getCompileCommands("/foo/bar.cc"); 559 ASSERT_EQ(1ul, Result.size()); 560 EXPECT_EQ("/cdb/dir", Result.front().Directory); 561 EXPECT_EQ("/foo/bar.cc", Result.front().Filename); 562 EXPECT_THAT( 563 Result.front().CommandLine, 564 ElementsAre(EndsWith("clang-tool"), "-DFOO=BAR", "--baz", "/foo/bar.cc")); 565 } 566 567 TEST(ParseFixedCompilationDatabase, ReturnsNullOnEmptyArgumentList) { 568 int Argc = 0; 569 std::string ErrorMsg; 570 std::unique_ptr<FixedCompilationDatabase> Database = 571 FixedCompilationDatabase::loadFromCommandLine(Argc, nullptr, ErrorMsg); 572 EXPECT_FALSE(Database); 573 EXPECT_TRUE(ErrorMsg.empty()); 574 EXPECT_EQ(0, Argc); 575 } 576 577 TEST(ParseFixedCompilationDatabase, ReturnsNullWithoutDoubleDash) { 578 int Argc = 2; 579 const char *Argv[] = { "1", "2" }; 580 std::string ErrorMsg; 581 std::unique_ptr<FixedCompilationDatabase> Database( 582 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); 583 EXPECT_FALSE(Database); 584 EXPECT_TRUE(ErrorMsg.empty()); 585 EXPECT_EQ(2, Argc); 586 } 587 588 TEST(ParseFixedCompilationDatabase, ReturnsArgumentsAfterDoubleDash) { 589 int Argc = 5; 590 const char *Argv[] = { 591 "1", "2", "--\0no-constant-folding", "-DDEF3", "-DDEF4" 592 }; 593 std::string ErrorMsg; 594 std::unique_ptr<FixedCompilationDatabase> Database( 595 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg)); 596 ASSERT_TRUE((bool)Database); 597 ASSERT_TRUE(ErrorMsg.empty()); 598 std::vector<CompileCommand> Result = 599 Database->getCompileCommands("source"); 600 ASSERT_EQ(1ul, Result.size()); 601 ASSERT_EQ(".", Result[0].Directory); 602 ASSERT_THAT(Result[0].CommandLine, ElementsAre(EndsWith("clang-tool"), 603 "-DDEF3", "-DDEF4", "source")); 604 EXPECT_EQ(2, Argc); 605 } 606 607 TEST(ParseFixedCompilationDatabase, ReturnsEmptyCommandLine) { 608 int Argc = 3; 609 const char *Argv[] = { "1", "2", "--\0no-constant-folding" }; 610 std::string ErrorMsg; 611 std::unique_ptr<FixedCompilationDatabase> Database = 612 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); 613 ASSERT_TRUE((bool)Database); 614 ASSERT_TRUE(ErrorMsg.empty()); 615 std::vector<CompileCommand> Result = 616 Database->getCompileCommands("source"); 617 ASSERT_EQ(1ul, Result.size()); 618 ASSERT_EQ(".", Result[0].Directory); 619 ASSERT_THAT(Result[0].CommandLine, 620 ElementsAre(EndsWith("clang-tool"), "source")); 621 EXPECT_EQ(2, Argc); 622 } 623 624 TEST(ParseFixedCompilationDatabase, HandlesPositionalArgs) { 625 const char *Argv[] = {"1", "2", "--", "-c", "somefile.cpp", "-DDEF3"}; 626 int Argc = sizeof(Argv) / sizeof(char*); 627 std::string ErrorMsg; 628 std::unique_ptr<FixedCompilationDatabase> Database = 629 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); 630 ASSERT_TRUE((bool)Database); 631 ASSERT_TRUE(ErrorMsg.empty()); 632 std::vector<CompileCommand> Result = 633 Database->getCompileCommands("source"); 634 ASSERT_EQ(1ul, Result.size()); 635 ASSERT_EQ(".", Result[0].Directory); 636 ASSERT_THAT(Result[0].CommandLine, 637 ElementsAre(EndsWith("clang-tool"), "-c", "-DDEF3", "source")); 638 EXPECT_EQ(2, Argc); 639 } 640 641 TEST(ParseFixedCompilationDatabase, HandlesPositionalArgsSyntaxOnly) { 642 // Adjust the given command line arguments to ensure that any positional 643 // arguments in them are stripped. 644 const char *Argv[] = {"--", "somefile.cpp", "-fsyntax-only", "-DDEF3"}; 645 int Argc = llvm::size(Argv); 646 std::string ErrorMessage; 647 std::unique_ptr<CompilationDatabase> Database = 648 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMessage); 649 ASSERT_TRUE((bool)Database); 650 ASSERT_TRUE(ErrorMessage.empty()); 651 std::vector<CompileCommand> Result = Database->getCompileCommands("source"); 652 ASSERT_EQ(1ul, Result.size()); 653 ASSERT_EQ(".", Result[0].Directory); 654 ASSERT_THAT( 655 Result[0].CommandLine, 656 ElementsAre(EndsWith("clang-tool"), "-fsyntax-only", "-DDEF3", "source")); 657 } 658 659 TEST(ParseFixedCompilationDatabase, HandlesArgv0) { 660 const char *Argv[] = {"1", "2", "--", "mytool", "somefile.cpp"}; 661 int Argc = sizeof(Argv) / sizeof(char*); 662 std::string ErrorMsg; 663 std::unique_ptr<FixedCompilationDatabase> Database = 664 FixedCompilationDatabase::loadFromCommandLine(Argc, Argv, ErrorMsg); 665 ASSERT_TRUE((bool)Database); 666 ASSERT_TRUE(ErrorMsg.empty()); 667 std::vector<CompileCommand> Result = 668 Database->getCompileCommands("source"); 669 ASSERT_EQ(1ul, Result.size()); 670 ASSERT_EQ(".", Result[0].Directory); 671 std::vector<std::string> Expected; 672 ASSERT_THAT(Result[0].CommandLine, 673 ElementsAre(EndsWith("clang-tool"), "source")); 674 EXPECT_EQ(2, Argc); 675 } 676 677 struct MemCDB : public CompilationDatabase { 678 using EntryMap = llvm::StringMap<SmallVector<CompileCommand, 1>>; 679 EntryMap Entries; 680 MemCDB(const EntryMap &E) : Entries(E) {} 681 682 std::vector<CompileCommand> getCompileCommands(StringRef F) const override { 683 auto Ret = Entries.lookup(F); 684 return {Ret.begin(), Ret.end()}; 685 } 686 687 std::vector<std::string> getAllFiles() const override { 688 std::vector<std::string> Result; 689 for (const auto &Entry : Entries) 690 Result.push_back(std::string(Entry.first())); 691 return Result; 692 } 693 }; 694 695 class MemDBTest : public ::testing::Test { 696 protected: 697 // Adds an entry to the underlying compilation database. 698 // A flag is injected: -D <File>, so the command used can be identified. 699 void add(StringRef File, StringRef Clang, StringRef Flags) { 700 SmallVector<StringRef, 8> Argv = {Clang, File, "-D", File}; 701 llvm::SplitString(Flags, Argv); 702 703 // Trim double quotation from the argumnets if any. 704 for (auto *It = Argv.begin(); It != Argv.end(); ++It) 705 *It = It->trim("\""); 706 707 SmallString<32> Dir; 708 llvm::sys::path::system_temp_directory(false, Dir); 709 710 Entries[path(File)].push_back( 711 {Dir, path(File), {Argv.begin(), Argv.end()}, "foo.o"}); 712 } 713 void add(StringRef File, StringRef Flags = "") { add(File, "clang", Flags); } 714 715 // Turn a unix path fragment (foo/bar.h) into a native path (C:\tmp\foo\bar.h) 716 std::string path(llvm::SmallString<32> File) { 717 llvm::SmallString<32> Dir; 718 llvm::sys::path::system_temp_directory(false, Dir); 719 llvm::sys::path::native(File); 720 llvm::SmallString<64> Result; 721 llvm::sys::path::append(Result, Dir, File); 722 return std::string(Result.str()); 723 } 724 725 MemCDB::EntryMap Entries; 726 }; 727 728 class InterpolateTest : public MemDBTest { 729 protected: 730 // Look up the command from a relative path, and return it in string form. 731 // The input file is not included in the returned command. 732 std::string getCommand(llvm::StringRef F, bool MakeNative = true) { 733 auto Results = 734 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries)) 735 ->getCompileCommands(MakeNative ? path(F) : F); 736 if (Results.empty()) 737 return "none"; 738 // drop the input file argument, so tests don't have to deal with path(). 739 EXPECT_EQ(Results[0].CommandLine.back(), MakeNative ? path(F) : F) 740 << "Last arg should be the file"; 741 Results[0].CommandLine.pop_back(); 742 EXPECT_EQ(Results[0].CommandLine.back(), "--") 743 << "Second-last arg should be --"; 744 Results[0].CommandLine.pop_back(); 745 return llvm::join(Results[0].CommandLine, " "); 746 } 747 748 // Parse the file whose command was used out of the Heuristic string. 749 std::string getProxy(llvm::StringRef F) { 750 auto Results = 751 inferMissingCompileCommands(std::make_unique<MemCDB>(Entries)) 752 ->getCompileCommands(path(F)); 753 if (Results.empty()) 754 return "none"; 755 StringRef Proxy = Results.front().Heuristic; 756 if (!Proxy.consume_front("inferred from ")) 757 return ""; 758 // We have a proxy file, convert back to a unix relative path. 759 // This is a bit messy, but we do need to test these strings somehow... 760 llvm::SmallString<32> TempDir; 761 llvm::sys::path::system_temp_directory(false, TempDir); 762 Proxy.consume_front(TempDir); 763 Proxy.consume_front(llvm::sys::path::get_separator()); 764 llvm::SmallString<32> Result = Proxy; 765 llvm::sys::path::native(Result, llvm::sys::path::Style::posix); 766 return std::string(Result.str()); 767 } 768 }; 769 770 TEST_F(InterpolateTest, Nearby) { 771 add("dir/foo.cpp"); 772 add("dir/bar.cpp"); 773 add("an/other/foo.cpp"); 774 775 // great: dir and name both match (prefix or full, case insensitive) 776 EXPECT_EQ(getProxy("dir/f.cpp"), "dir/foo.cpp"); 777 EXPECT_EQ(getProxy("dir/FOO.cpp"), "dir/foo.cpp"); 778 // no name match. prefer matching dir, break ties by alpha 779 EXPECT_EQ(getProxy("dir/a.cpp"), "dir/bar.cpp"); 780 // an exact name match beats one segment of directory match 781 EXPECT_EQ(getProxy("some/other/bar.h"), "dir/bar.cpp"); 782 // two segments of directory match beat a prefix name match 783 EXPECT_EQ(getProxy("an/other/b.cpp"), "an/other/foo.cpp"); 784 // if nothing matches at all, we still get the closest alpha match 785 EXPECT_EQ(getProxy("below/some/obscure/path.cpp"), "an/other/foo.cpp"); 786 } 787 788 TEST_F(InterpolateTest, Language) { 789 add("dir/foo.cpp", "-std=c++17"); 790 add("dir/bar.c", ""); 791 add("dir/baz.cee", "-x c"); 792 add("dir/aux.cpp", "-std=c++17 -x objective-c++"); 793 794 // .h is ambiguous, so we add explicit language flags 795 EXPECT_EQ(getCommand("foo.h"), 796 "clang -D dir/foo.cpp -x c++-header -std=c++17"); 797 // Same thing if we have no extension. (again, we treat as header). 798 EXPECT_EQ(getCommand("foo"), "clang -D dir/foo.cpp -x c++-header -std=c++17"); 799 // and invalid extensions. 800 EXPECT_EQ(getCommand("foo.cce"), 801 "clang -D dir/foo.cpp -x c++-header -std=c++17"); 802 // and don't add -x if the inferred language is correct. 803 EXPECT_EQ(getCommand("foo.hpp"), "clang -D dir/foo.cpp -std=c++17"); 804 // respect -x if it's already there. 805 EXPECT_EQ(getCommand("baz.h"), "clang -D dir/baz.cee -x c-header"); 806 // prefer a worse match with the right extension. 807 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/bar.c"); 808 Entries.erase(path(StringRef("dir/bar.c"))); 809 // Now we transfer across languages, so drop -std too. 810 EXPECT_EQ(getCommand("foo.c"), "clang -D dir/foo.cpp"); 811 // Prefer -x over -std when overriding language. 812 EXPECT_EQ(getCommand("aux.h"), 813 "clang -D dir/aux.cpp -x objective-c++-header -std=c++17"); 814 } 815 816 TEST_F(InterpolateTest, Strip) { 817 add("dir/foo.cpp", "-o foo.o -Wall"); 818 // the -o option and the input file are removed, but -Wall is preserved. 819 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall"); 820 } 821 822 TEST_F(InterpolateTest, StripDoubleDash) { 823 add("dir/foo.cpp", "-o foo.o -std=c++14 -Wall -- dir/foo.cpp"); 824 // input file and output option are removed 825 // -Wall flag isn't 826 // -std option gets re-added as the last argument before the input file 827 // -- is removed as it's not necessary - the new input file doesn't start with 828 // a dash 829 EXPECT_EQ(getCommand("dir/bar.cpp"), "clang -D dir/foo.cpp -Wall -std=c++14"); 830 } 831 832 TEST_F(InterpolateTest, Case) { 833 add("FOO/BAR/BAZ/SHOUT.cc"); 834 add("foo/bar/baz/quiet.cc"); 835 // Case mismatches are completely ignored, so we choose the name match. 836 EXPECT_EQ(getProxy("foo/bar/baz/shout.C"), "FOO/BAR/BAZ/SHOUT.cc"); 837 } 838 839 TEST_F(InterpolateTest, Aliasing) { 840 add("foo.cpp", "-faligned-new"); 841 842 // The interpolated command should keep the given flag as written, even though 843 // the flag is internally represented as an alias. 844 EXPECT_EQ(getCommand("foo.hpp"), "clang -D foo.cpp -faligned-new"); 845 } 846 847 TEST_F(InterpolateTest, ClangCL) { 848 add("foo.cpp", "clang-cl", "/W4"); 849 850 // Language flags should be added with CL syntax. 851 EXPECT_EQ(getCommand("foo.h", false), "clang-cl -D foo.cpp /W4 /TP"); 852 } 853 854 TEST_F(InterpolateTest, DriverModes) { 855 add("foo.cpp", "clang-cl", "--driver-mode=gcc"); 856 add("bar.cpp", "clang", "--driver-mode=cl"); 857 858 // --driver-mode overrides should be respected. 859 EXPECT_EQ(getCommand("foo.h"), 860 "clang-cl -D foo.cpp --driver-mode=gcc -x c++-header"); 861 EXPECT_EQ(getCommand("bar.h", false), 862 "clang -D bar.cpp --driver-mode=cl /TP"); 863 } 864 865 TEST(TransferCompileCommandTest, Smoke) { 866 CompileCommand Cmd; 867 Cmd.Filename = "foo.cc"; 868 Cmd.CommandLine = {"clang", "-Wall", "foo.cc"}; 869 Cmd.Directory = "dir"; 870 CompileCommand Transferred = transferCompileCommand(std::move(Cmd), "foo.h"); 871 EXPECT_EQ(Transferred.Filename, "foo.h"); 872 EXPECT_THAT(Transferred.CommandLine, 873 ElementsAre("clang", "-Wall", "-x", "c++-header", "--", "foo.h")); 874 EXPECT_EQ(Transferred.Directory, "dir"); 875 } 876 877 TEST(CompileCommandTest, EqualityOperator) { 878 CompileCommand CCRef("/foo/bar", "hello.c", {"a", "b"}, "hello.o"); 879 CompileCommand CCTest = CCRef; 880 881 EXPECT_TRUE(CCRef == CCTest); 882 EXPECT_FALSE(CCRef != CCTest); 883 884 CCTest = CCRef; 885 CCTest.Directory = "/foo/baz"; 886 EXPECT_FALSE(CCRef == CCTest); 887 EXPECT_TRUE(CCRef != CCTest); 888 889 CCTest = CCRef; 890 CCTest.Filename = "bonjour.c"; 891 EXPECT_FALSE(CCRef == CCTest); 892 EXPECT_TRUE(CCRef != CCTest); 893 894 CCTest = CCRef; 895 CCTest.CommandLine.push_back("c"); 896 EXPECT_FALSE(CCRef == CCTest); 897 EXPECT_TRUE(CCRef != CCTest); 898 899 CCTest = CCRef; 900 CCTest.Output = "bonjour.o"; 901 EXPECT_FALSE(CCRef == CCTest); 902 EXPECT_TRUE(CCRef != CCTest); 903 } 904 905 class TargetAndModeTest : public MemDBTest { 906 public: 907 TargetAndModeTest() { llvm::InitializeAllTargetInfos(); } 908 909 protected: 910 // Look up the command from a relative path, and return it in string form. 911 std::string getCommand(llvm::StringRef F) { 912 auto Results = inferTargetAndDriverMode(std::make_unique<MemCDB>(Entries)) 913 ->getCompileCommands(path(F)); 914 if (Results.empty()) 915 return "none"; 916 return llvm::join(Results[0].CommandLine, " "); 917 } 918 }; 919 920 TEST_F(TargetAndModeTest, TargetAndMode) { 921 add("foo.cpp", "clang-cl", ""); 922 add("bar.cpp", "clang++", ""); 923 924 EXPECT_EQ(getCommand("foo.cpp"), 925 "clang-cl --driver-mode=cl foo.cpp -D foo.cpp"); 926 EXPECT_EQ(getCommand("bar.cpp"), 927 "clang++ --driver-mode=g++ bar.cpp -D bar.cpp"); 928 } 929 930 class ExpandResponseFilesTest : public MemDBTest { 931 public: 932 ExpandResponseFilesTest() : FS(new llvm::vfs::InMemoryFileSystem) {} 933 934 protected: 935 void addFile(StringRef File, StringRef Content) { 936 ASSERT_TRUE( 937 FS->addFile(File, 0, llvm::MemoryBuffer::getMemBufferCopy(Content))); 938 } 939 940 std::string getCommand(llvm::StringRef F) { 941 auto Results = expandResponseFiles(std::make_unique<MemCDB>(Entries), FS) 942 ->getCompileCommands(path(F)); 943 if (Results.empty()) 944 return "none"; 945 return llvm::join(Results[0].CommandLine, " "); 946 } 947 948 llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS; 949 }; 950 951 TEST_F(ExpandResponseFilesTest, ExpandResponseFiles) { 952 addFile(path(StringRef("rsp1.rsp")), "-Dflag"); 953 954 add("foo.cpp", "clang", "@rsp1.rsp"); 955 add("bar.cpp", "clang", "-Dflag"); 956 EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag"); 957 EXPECT_EQ(getCommand("bar.cpp"), "clang bar.cpp -D bar.cpp -Dflag"); 958 } 959 960 TEST_F(ExpandResponseFilesTest, ExpandResponseFilesEmptyArgument) { 961 addFile(path(StringRef("rsp1.rsp")), "-Dflag"); 962 963 add("foo.cpp", "clang", "@rsp1.rsp \"\""); 964 EXPECT_EQ(getCommand("foo.cpp"), "clang foo.cpp -D foo.cpp -Dflag "); 965 } 966 967 } // end namespace tooling 968 } // end namespace clang 969