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