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