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