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