1 //===-- CompileCommandsTests.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 "CompileCommands.h" 10 #include "Config.h" 11 #include "TestFS.h" 12 #include "support/Context.h" 13 14 #include "clang/Tooling/ArgumentsAdjusters.h" 15 #include "llvm/ADT/ArrayRef.h" 16 #include "llvm/ADT/STLExtras.h" 17 #include "llvm/ADT/ScopeExit.h" 18 #include "llvm/ADT/StringExtras.h" 19 #include "llvm/ADT/StringRef.h" 20 #include "llvm/Support/FileSystem.h" 21 #include "llvm/Support/Path.h" 22 #include "llvm/Support/Process.h" 23 24 #include "gmock/gmock.h" 25 #include "gtest/gtest.h" 26 27 namespace clang { 28 namespace clangd { 29 namespace { 30 31 using ::testing::_; 32 using ::testing::Contains; 33 using ::testing::ElementsAre; 34 using ::testing::HasSubstr; 35 using ::testing::Not; 36 37 // Sadly, CommandMangler::detect(), which contains much of the logic, is 38 // a bunch of untested integration glue. We test the string manipulation here 39 // assuming its results are correct. 40 41 // Make use of all features and assert the exact command we get out. 42 // Other tests just verify presence/absence of certain args. 43 TEST(CommandMangler, Everything) { 44 auto Mangler = CommandMangler::forTests(); 45 Mangler.ClangPath = testPath("fake/clang"); 46 Mangler.ResourceDir = testPath("fake/resources"); 47 Mangler.Sysroot = testPath("fake/sysroot"); 48 std::vector<std::string> Cmd = {"clang++", "--", "foo.cc", "bar.cc"}; 49 Mangler.adjust(Cmd, "foo.cc"); 50 EXPECT_THAT(Cmd, ElementsAre(testPath("fake/clang++"), 51 "-resource-dir=" + testPath("fake/resources"), 52 "-isysroot", testPath("fake/sysroot"), "--", 53 "foo.cc")); 54 } 55 56 TEST(CommandMangler, ResourceDir) { 57 auto Mangler = CommandMangler::forTests(); 58 Mangler.ResourceDir = testPath("fake/resources"); 59 std::vector<std::string> Cmd = {"clang++", "foo.cc"}; 60 Mangler.adjust(Cmd, "foo.cc"); 61 EXPECT_THAT(Cmd, Contains("-resource-dir=" + testPath("fake/resources"))); 62 } 63 64 TEST(CommandMangler, Sysroot) { 65 auto Mangler = CommandMangler::forTests(); 66 Mangler.Sysroot = testPath("fake/sysroot"); 67 68 std::vector<std::string> Cmd = {"clang++", "foo.cc"}; 69 Mangler.adjust(Cmd, "foo.cc"); 70 EXPECT_THAT(llvm::join(Cmd, " "), 71 HasSubstr("-isysroot " + testPath("fake/sysroot"))); 72 } 73 74 TEST(CommandMangler, ClangPath) { 75 auto Mangler = CommandMangler::forTests(); 76 Mangler.ClangPath = testPath("fake/clang"); 77 78 std::vector<std::string> Cmd = {"clang++", "foo.cc"}; 79 Mangler.adjust(Cmd, "foo.cc"); 80 EXPECT_EQ(testPath("fake/clang++"), Cmd.front()); 81 82 Cmd = {"unknown-binary", "foo.cc"}; 83 Mangler.adjust(Cmd, "foo.cc"); 84 EXPECT_EQ(testPath("fake/unknown-binary"), Cmd.front()); 85 86 Cmd = {testPath("path/clang++"), "foo.cc"}; 87 Mangler.adjust(Cmd, "foo.cc"); 88 EXPECT_EQ(testPath("path/clang++"), Cmd.front()); 89 90 Cmd = {"foo/unknown-binary", "foo.cc"}; 91 Mangler.adjust(Cmd, "foo.cc"); 92 EXPECT_EQ("foo/unknown-binary", Cmd.front()); 93 } 94 95 // Only run the PATH/symlink resolving test on unix, we need to fiddle 96 // with permissions and environment variables... 97 #ifdef LLVM_ON_UNIX 98 MATCHER(Ok, "") { 99 if (arg) { 100 *result_listener << arg.message(); 101 return false; 102 } 103 return true; 104 } 105 106 TEST(CommandMangler, ClangPathResolve) { 107 // Set up filesystem: 108 // /temp/ 109 // bin/ 110 // foo -> temp/lib/bar 111 // lib/ 112 // bar 113 llvm::SmallString<256> TempDir; 114 ASSERT_THAT(llvm::sys::fs::createUniqueDirectory("ClangPathResolve", TempDir), 115 Ok()); 116 // /var/tmp is a symlink on Mac. Resolve it so we're asserting the right path. 117 ASSERT_THAT(llvm::sys::fs::real_path(TempDir.str(), TempDir), Ok()); 118 auto CleanDir = llvm::make_scope_exit( 119 [&] { llvm::sys::fs::remove_directories(TempDir); }); 120 ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/bin"), Ok()); 121 ASSERT_THAT(llvm::sys::fs::create_directory(TempDir + "/lib"), Ok()); 122 int FD; 123 ASSERT_THAT(llvm::sys::fs::openFileForWrite(TempDir + "/lib/bar", FD), Ok()); 124 ASSERT_THAT(llvm::sys::Process::SafelyCloseFileDescriptor(FD), Ok()); 125 ::chmod((TempDir + "/lib/bar").str().c_str(), 0755); // executable 126 ASSERT_THAT( 127 llvm::sys::fs::create_link(TempDir + "/lib/bar", TempDir + "/bin/foo"), 128 Ok()); 129 130 // Test the case where the driver is an absolute path to a symlink. 131 auto Mangler = CommandMangler::forTests(); 132 Mangler.ClangPath = testPath("fake/clang"); 133 std::vector<std::string> Cmd = {(TempDir + "/bin/foo").str(), "foo.cc"}; 134 Mangler.adjust(Cmd, "foo.cc"); 135 // Directory based on resolved symlink, basename preserved. 136 EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.front()); 137 138 // Set PATH to point to temp/bin so we can find 'foo' on it. 139 ASSERT_TRUE(::getenv("PATH")); 140 auto RestorePath = 141 llvm::make_scope_exit([OldPath = std::string(::getenv("PATH"))] { 142 ::setenv("PATH", OldPath.c_str(), 1); 143 }); 144 ::setenv("PATH", (TempDir + "/bin").str().c_str(), /*overwrite=*/1); 145 146 // Test the case where the driver is a $PATH-relative path to a symlink. 147 Mangler = CommandMangler::forTests(); 148 Mangler.ClangPath = testPath("fake/clang"); 149 // Driver found on PATH. 150 Cmd = {"foo", "foo.cc"}; 151 Mangler.adjust(Cmd, "foo.cc"); 152 // Found the symlink and resolved the path as above. 153 EXPECT_EQ((TempDir + "/lib/foo").str(), Cmd.front()); 154 155 // Symlink not resolved with -no-canonical-prefixes. 156 Cmd = {"foo", "-no-canonical-prefixes", "foo.cc"}; 157 Mangler.adjust(Cmd, "foo.cc"); 158 EXPECT_EQ((TempDir + "/bin/foo").str(), Cmd.front()); 159 } 160 #endif 161 162 TEST(CommandMangler, ConfigEdits) { 163 auto Mangler = CommandMangler::forTests(); 164 std::vector<std::string> Cmd = {"clang++", "foo.cc"}; 165 { 166 Config Cfg; 167 Cfg.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) { 168 for (auto &Arg : Argv) 169 for (char &C : Arg) 170 C = llvm::toUpper(C); 171 }); 172 Cfg.CompileFlags.Edits.push_back([](std::vector<std::string> &Argv) { 173 Argv = tooling::getInsertArgumentAdjuster("--hello")(Argv, ""); 174 }); 175 WithContextValue WithConfig(Config::Key, std::move(Cfg)); 176 Mangler.adjust(Cmd, "foo.cc"); 177 } 178 // Edits are applied in given order and before other mangling and they always 179 // go before filename. 180 EXPECT_THAT(Cmd, ElementsAre(_, "--hello", "--", "FOO.CC")); 181 } 182 183 static std::string strip(llvm::StringRef Arg, llvm::StringRef Argv) { 184 llvm::SmallVector<llvm::StringRef> Parts; 185 llvm::SplitString(Argv, Parts); 186 std::vector<std::string> Args = {Parts.begin(), Parts.end()}; 187 ArgStripper S; 188 S.strip(Arg); 189 S.process(Args); 190 return printArgv(Args); 191 } 192 193 TEST(ArgStripperTest, Spellings) { 194 // May use alternate prefixes. 195 EXPECT_EQ(strip("-pedantic", "clang -pedantic foo.cc"), "clang foo.cc"); 196 EXPECT_EQ(strip("-pedantic", "clang --pedantic foo.cc"), "clang foo.cc"); 197 EXPECT_EQ(strip("--pedantic", "clang -pedantic foo.cc"), "clang foo.cc"); 198 EXPECT_EQ(strip("--pedantic", "clang --pedantic foo.cc"), "clang foo.cc"); 199 // May use alternate names. 200 EXPECT_EQ(strip("-x", "clang -x c++ foo.cc"), "clang foo.cc"); 201 EXPECT_EQ(strip("-x", "clang --language=c++ foo.cc"), "clang foo.cc"); 202 EXPECT_EQ(strip("--language=", "clang -x c++ foo.cc"), "clang foo.cc"); 203 EXPECT_EQ(strip("--language=", "clang --language=c++ foo.cc"), 204 "clang foo.cc"); 205 } 206 207 TEST(ArgStripperTest, UnknownFlag) { 208 EXPECT_EQ(strip("-xyzzy", "clang -xyzzy foo.cc"), "clang foo.cc"); 209 EXPECT_EQ(strip("-xyz*", "clang -xyzzy foo.cc"), "clang foo.cc"); 210 EXPECT_EQ(strip("-xyzzy", "clang -Xclang -xyzzy foo.cc"), "clang foo.cc"); 211 } 212 213 TEST(ArgStripperTest, Xclang) { 214 // Flags may be -Xclang escaped. 215 EXPECT_EQ(strip("-ast-dump", "clang -Xclang -ast-dump foo.cc"), 216 "clang foo.cc"); 217 // Args may be -Xclang escaped. 218 EXPECT_EQ(strip("-add-plugin", "clang -Xclang -add-plugin -Xclang z foo.cc"), 219 "clang foo.cc"); 220 } 221 222 TEST(ArgStripperTest, ClangCL) { 223 // /I is a synonym for -I in clang-cl mode only. 224 // Not stripped by default. 225 EXPECT_EQ(strip("-I", "clang -I /usr/inc /Interesting/file.cc"), 226 "clang /Interesting/file.cc"); 227 // Stripped when invoked as clang-cl. 228 EXPECT_EQ(strip("-I", "clang-cl -I /usr/inc /Interesting/file.cc"), 229 "clang-cl"); 230 // Stripped when invoked as CL.EXE 231 EXPECT_EQ(strip("-I", "CL.EXE -I /usr/inc /Interesting/file.cc"), "CL.EXE"); 232 // Stripped when passed --driver-mode=cl. 233 EXPECT_EQ(strip("-I", "cc -I /usr/inc /Interesting/file.cc --driver-mode=cl"), 234 "cc --driver-mode=cl"); 235 } 236 237 TEST(ArgStripperTest, ArgStyles) { 238 // Flag 239 EXPECT_EQ(strip("-Qn", "clang -Qn foo.cc"), "clang foo.cc"); 240 EXPECT_EQ(strip("-Qn", "clang -QnZ foo.cc"), "clang -QnZ foo.cc"); 241 // Joined 242 EXPECT_EQ(strip("-std=", "clang -std= foo.cc"), "clang foo.cc"); 243 EXPECT_EQ(strip("-std=", "clang -std=c++11 foo.cc"), "clang foo.cc"); 244 // Separate 245 EXPECT_EQ(strip("-mllvm", "clang -mllvm X foo.cc"), "clang foo.cc"); 246 EXPECT_EQ(strip("-mllvm", "clang -mllvmX foo.cc"), "clang -mllvmX foo.cc"); 247 // RemainingArgsJoined 248 EXPECT_EQ(strip("/link", "clang-cl /link b c d foo.cc"), "clang-cl"); 249 EXPECT_EQ(strip("/link", "clang-cl /linka b c d foo.cc"), "clang-cl"); 250 // CommaJoined 251 EXPECT_EQ(strip("-Wl,", "clang -Wl,x,y foo.cc"), "clang foo.cc"); 252 EXPECT_EQ(strip("-Wl,", "clang -Wl, foo.cc"), "clang foo.cc"); 253 // MultiArg 254 EXPECT_EQ(strip("-segaddr", "clang -segaddr a b foo.cc"), "clang foo.cc"); 255 EXPECT_EQ(strip("-segaddr", "clang -segaddra b foo.cc"), 256 "clang -segaddra b foo.cc"); 257 // JoinedOrSeparate 258 EXPECT_EQ(strip("-G", "clang -GX foo.cc"), "clang foo.cc"); 259 EXPECT_EQ(strip("-G", "clang -G X foo.cc"), "clang foo.cc"); 260 // JoinedAndSeparate 261 EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg-X Y foo.cc"), 262 "clang -cc1 foo.cc"); 263 EXPECT_EQ(strip("-plugin-arg-", "clang -cc1 -plugin-arg- Y foo.cc"), 264 "clang -cc1 foo.cc"); 265 } 266 267 TEST(ArgStripperTest, EndOfList) { 268 // When we hit the end-of-args prematurely, we don't crash. 269 // We consume the incomplete args if we've matched the target option. 270 EXPECT_EQ(strip("-I", "clang -Xclang"), "clang -Xclang"); 271 EXPECT_EQ(strip("-I", "clang -Xclang -I"), "clang"); 272 EXPECT_EQ(strip("-I", "clang -I -Xclang"), "clang"); 273 EXPECT_EQ(strip("-I", "clang -I"), "clang"); 274 } 275 276 TEST(ArgStripperTest, Multiple) { 277 ArgStripper S; 278 S.strip("-o"); 279 S.strip("-c"); 280 std::vector<std::string> Args = {"clang", "-o", "foo.o", "foo.cc", "-c"}; 281 S.process(Args); 282 EXPECT_THAT(Args, ElementsAre("clang", "foo.cc")); 283 } 284 285 TEST(ArgStripperTest, Warning) { 286 { 287 // -W is a flag name 288 ArgStripper S; 289 S.strip("-W"); 290 std::vector<std::string> Args = {"clang", "-Wfoo", "-Wno-bar", "-Werror", 291 "foo.cc"}; 292 S.process(Args); 293 EXPECT_THAT(Args, ElementsAre("clang", "foo.cc")); 294 } 295 { 296 // -Wfoo is not a flag name, matched literally. 297 ArgStripper S; 298 S.strip("-Wunused"); 299 std::vector<std::string> Args = {"clang", "-Wunused", "-Wno-unused", 300 "foo.cc"}; 301 S.process(Args); 302 EXPECT_THAT(Args, ElementsAre("clang", "-Wno-unused", "foo.cc")); 303 } 304 } 305 306 TEST(ArgStripperTest, Define) { 307 { 308 // -D is a flag name 309 ArgStripper S; 310 S.strip("-D"); 311 std::vector<std::string> Args = {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"}; 312 S.process(Args); 313 EXPECT_THAT(Args, ElementsAre("clang", "foo.cc")); 314 } 315 { 316 // -Dbar is not: matched literally 317 ArgStripper S; 318 S.strip("-Dbar"); 319 std::vector<std::string> Args = {"clang", "-Dfoo", "-Dbar=baz", "foo.cc"}; 320 S.process(Args); 321 EXPECT_THAT(Args, ElementsAre("clang", "-Dfoo", "-Dbar=baz", "foo.cc")); 322 S.strip("-Dfoo"); 323 S.process(Args); 324 EXPECT_THAT(Args, ElementsAre("clang", "-Dbar=baz", "foo.cc")); 325 S.strip("-Dbar=*"); 326 S.process(Args); 327 EXPECT_THAT(Args, ElementsAre("clang", "foo.cc")); 328 } 329 } 330 331 TEST(ArgStripperTest, OrderDependent) { 332 ArgStripper S; 333 // If -include is stripped first, we see -pch as its arg and foo.pch remains. 334 // To get this case right, we must process -include-pch first. 335 S.strip("-include"); 336 S.strip("-include-pch"); 337 std::vector<std::string> Args = {"clang", "-include-pch", "foo.pch", 338 "foo.cc"}; 339 S.process(Args); 340 EXPECT_THAT(Args, ElementsAre("clang", "foo.cc")); 341 } 342 343 TEST(PrintArgvTest, All) { 344 std::vector<llvm::StringRef> Args = { 345 "one", "two", "thr ee", "f\"o\"ur", "fi\\ve", "$" 346 }; 347 const char *Expected = R"(one two "thr ee" "f\"o\"ur" "fi\\ve" $)"; 348 EXPECT_EQ(Expected, printArgv(Args)); 349 } 350 351 TEST(CommandMangler, InputsAfterDashDash) { 352 const auto Mangler = CommandMangler::forTests(); 353 { 354 std::vector<std::string> Args = {"clang", "/Users/foo.cc"}; 355 Mangler.adjust(Args, "/Users/foo.cc"); 356 EXPECT_THAT(llvm::makeArrayRef(Args).take_back(2), 357 ElementsAre("--", "/Users/foo.cc")); 358 EXPECT_THAT(llvm::makeArrayRef(Args).drop_back(2), 359 Not(Contains("/Users/foo.cc"))); 360 } 361 // In CL mode /U triggers an undef operation, hence `/Users/foo.cc` shouldn't 362 // be interpreted as a file. 363 { 364 std::vector<std::string> Args = {"clang", "--driver-mode=cl", "bar.cc", 365 "/Users/foo.cc"}; 366 Mangler.adjust(Args, "bar.cc"); 367 EXPECT_THAT(llvm::makeArrayRef(Args).take_back(2), 368 ElementsAre("--", "bar.cc")); 369 EXPECT_THAT(llvm::makeArrayRef(Args).drop_back(2), Not(Contains("bar.cc"))); 370 } 371 // All inputs but the main file is dropped. 372 { 373 std::vector<std::string> Args = {"clang", "foo.cc", "bar.cc"}; 374 Mangler.adjust(Args, "baz.cc"); 375 EXPECT_THAT(llvm::makeArrayRef(Args).take_back(2), 376 ElementsAre("--", "baz.cc")); 377 EXPECT_THAT( 378 llvm::makeArrayRef(Args).drop_back(2), 379 testing::AllOf(Not(Contains("foo.cc")), Not(Contains("bar.cc")))); 380 } 381 } 382 383 TEST(CommandMangler, StripsMultipleArch) { 384 const auto Mangler = CommandMangler::forTests(); 385 std::vector<std::string> Args = {"clang", "-arch", "foo", 386 "-arch", "bar", "/Users/foo.cc"}; 387 Mangler.adjust(Args, "/Users/foo.cc"); 388 EXPECT_EQ( 389 llvm::count_if(Args, [](llvm::StringRef Arg) { return Arg == "-arch"; }), 390 0); 391 392 // Single arch option is preserved. 393 Args = {"clang", "-arch", "foo", "/Users/foo.cc"}; 394 Mangler.adjust(Args, "/Users/foo.cc"); 395 EXPECT_EQ( 396 llvm::count_if(Args, [](llvm::StringRef Arg) { return Arg == "-arch"; }), 397 1); 398 } 399 400 TEST(CommandMangler, EmptyArgs) { 401 const auto Mangler = CommandMangler::forTests(); 402 std::vector<std::string> Args = {}; 403 // Make sure we don't crash. 404 Mangler.adjust(Args, "foo.cc"); 405 } 406 } // namespace 407 } // namespace clangd 408 } // namespace clang 409