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