1 //===-- Unittests for WrapperGen ------------------------------------------===//
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 "llvm/ADT/SmallString.h"
10 #include "llvm/ADT/StringRef.h"
11 #include "llvm/ADT/Twine.h"
12 #include "llvm/Support/CommandLine.h"
13 #include "llvm/Support/Error.h"
14 #include "llvm/Support/FileSystem.h"
15 #include "llvm/Support/FileUtilities.h"
16 #include "llvm/Support/MemoryBuffer.h"
17 #include "llvm/Support/Program.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
21 #include <unistd.h>
22 
23 llvm::cl::opt<std::string>
24     LibcPath("path", llvm::cl::desc("Path to the top level libc directory."),
25              llvm::cl::value_desc("<path to libc>"), llvm::cl::Required);
26 llvm::cl::opt<std::string>
27     ToolPath("tool", llvm::cl::desc("Path to the tool executable."),
28              llvm::cl::value_desc("<path to tool>"), llvm::cl::Required);
29 llvm::cl::opt<std::string>
30     APIPath("api",
31             llvm::cl::desc("Path to the api tablegen file used by the tests."),
32             llvm::cl::value_desc("<path to testapi.td>"), llvm::cl::Required);
33 
34 class WrapperGenTest : public ::testing::Test {
35 public:
36   std::string IncludeArg;
37   std::string APIArg;
38   llvm::StringRef ProgPath;
39   llvm::Expected<llvm::sys::fs::TempFile> STDOutFile =
40       llvm::sys::fs::TempFile::create("wrappergen-stdout-%%-%%-%%-%%.txt");
41   llvm::Expected<llvm::sys::fs::TempFile> STDErrFile =
42       llvm::sys::fs::TempFile::create("wrappergen-stderr-%%-%%-%%-%%.txt");
43 
44 protected:
45   void SetUp() override {
46     IncludeArg = "-I=";
47     IncludeArg.append(LibcPath);
48     APIArg = APIPath;
49     ProgPath = llvm::StringRef(ToolPath);
50 
51     if (!STDOutFile) {
52       llvm::errs() << "Error: " << llvm::toString(STDOutFile.takeError())
53                    << "\n";
54       llvm::report_fatal_error(
55           "Temporary file failed to initialize for libc-wrappergen tests.");
56     }
57     if (!STDErrFile) {
58       llvm::errs() << "Error: " << llvm::toString(STDErrFile.takeError())
59                    << "\n";
60       llvm::report_fatal_error(
61           "Temporary file failed to initialize for libc-wrappergen tests.");
62     }
63   }
64   void TearDown() override {
65     llvm::consumeError(STDOutFile.get().discard());
66     llvm::consumeError(STDErrFile.get().discard());
67   }
68 };
69 
70 using LlvmLibcWrapperGenTest = WrapperGenTest;
71 
72 TEST_F(LlvmLibcWrapperGenTest, RunWrapperGenAndGetNoErrors) {
73   llvm::Optional<llvm::StringRef> Redirects[] = {
74       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
75       llvm::StringRef(STDErrFile.get().TmpName)};
76 
77   llvm::StringRef ArgV[] = {ProgPath,
78                             llvm::StringRef(IncludeArg),
79                             llvm::StringRef(APIArg),
80                             "--gen-wrapper",
81                             "--name",
82                             "strlen"};
83 
84   int ExitCode =
85       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
86 
87   EXPECT_EQ(ExitCode, 0);
88 
89   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
90   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
91   ASSERT_EQ(STDErrOutput, "");
92 }
93 
94 TEST_F(LlvmLibcWrapperGenTest, RunWrapperGenOnStrlen) {
95   llvm::Optional<llvm::StringRef> Redirects[] = {
96       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
97       llvm::StringRef(STDErrFile.get().TmpName)};
98 
99   llvm::StringRef ArgV[] = {ProgPath,
100                             llvm::StringRef(IncludeArg),
101                             llvm::StringRef(APIArg),
102                             "--gen-wrapper",
103                             "--name",
104                             "strlen"};
105 
106   int ExitCode =
107       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
108 
109   EXPECT_EQ(ExitCode, 0);
110 
111   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
112   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
113 
114   ASSERT_EQ(STDErrOutput, "");
115 
116   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
117   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
118 
119   ASSERT_EQ(STDOutOutput, "#include \"src/string/strlen.h\"\n"
120                           "extern \"C\" size_t strlen(const char * __arg0) {\n"
121                           "  return __llvm_libc::strlen(__arg0);\n"
122                           "}\n");
123   // TODO:(michaelrj) Figure out how to make this output comparison
124   // less brittle. Currently it's just comparing the output of the program
125   // to an exact string, this means that even a small formatting change
126   // would break this test.
127 }
128 
129 TEST_F(LlvmLibcWrapperGenTest, GenAliasForStrlen) {
130   llvm::Optional<llvm::StringRef> Redirects[] = {
131       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
132       llvm::StringRef(STDErrFile.get().TmpName)};
133 
134   llvm::StringRef ArgV[] = {ProgPath,
135                             llvm::StringRef(IncludeArg),
136                             llvm::StringRef(APIArg),
137                             "--gen-alias",
138                             "--mangled-name",
139                             "__llvm_libc_strlen_mangled_name",
140                             "--name",
141                             "strlen"};
142 
143   int ExitCode =
144       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
145 
146   EXPECT_EQ(ExitCode, 0);
147 
148   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
149   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
150 
151   ASSERT_EQ(STDErrOutput, "");
152 
153   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
154   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
155 
156   ASSERT_EQ(STDOutOutput,
157             "extern \"C\" size_t strlen(const char * __arg0) "
158             "__attribute__((alias(\"__llvm_libc_strlen_mangled_name\")));\n");
159   // TODO:(michaelrj) Figure out how to make this output comparison
160   // less brittle. Currently it's just comparing the output of the program
161   // to an exact string, this means that even a small formatting change
162   // would break this test.
163 }
164 
165 TEST_F(LlvmLibcWrapperGenTest, DeclStrlenAliasUsingMangledNameFile) {
166   llvm::Optional<llvm::StringRef> Redirects[] = {
167       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
168       llvm::StringRef(STDErrFile.get().TmpName)};
169 
170   const char *MangledNameFileContent =
171       "abc\nxyz__llvm_libc_strlen_mangled_name\nijk\n";
172   llvm::SmallVector<char> MangledNameFilePath;
173   auto MangledNameFileCreateError = llvm::sys::fs::createUniqueFile(
174       "libc-wrappergen-test-aliasee-file-%%-%%-%%-%%.txt", MangledNameFilePath);
175   ASSERT_FALSE(MangledNameFileCreateError);
176   auto MangledNameFileWriteError = llvm::writeFileAtomically(
177       "libc-wrappergen-temp-test-aliasee-file-%%-%%-%%-%%.txt",
178       llvm::StringRef(MangledNameFilePath.data()),
179       llvm::StringRef(MangledNameFileContent));
180   ASSERT_FALSE(MangledNameFileWriteError);
181 
182   llvm::StringRef ArgV[] = {ProgPath,
183                             llvm::StringRef(IncludeArg),
184                             llvm::StringRef(APIArg),
185                             "--gen-alias",
186                             "--mangled-name-file",
187                             llvm::StringRef(MangledNameFilePath.data()),
188                             "--name",
189                             "strlen"};
190 
191   int ExitCode =
192       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
193 
194   EXPECT_EQ(ExitCode, 0);
195 
196   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
197   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
198 
199   ASSERT_EQ(STDErrOutput, "");
200 
201   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
202   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
203 
204   ASSERT_EQ(
205       STDOutOutput,
206       "extern \"C\" size_t strlen(const char * __arg0) "
207       "__attribute__((alias(\"xyz__llvm_libc_strlen_mangled_name\")));\n");
208 }
209 
210 /////////////////////////////////////////////////////////////////////
211 /////////////////////////////////////////////////////////////////////
212 // BAD INPUT TESTS
213 // all of the tests after this point are testing inputs that should
214 // return errors
215 /////////////////////////////////////////////////////////////////////
216 
217 TEST_F(LlvmLibcWrapperGenTest,
218        RunWrapperGenOnStrlenWithMangledNameAndMangledNameFile) {
219   llvm::Optional<llvm::StringRef> Redirects[] = {
220       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
221       llvm::StringRef(STDErrFile.get().TmpName)};
222 
223   llvm::StringRef ArgV[] = {ProgPath,
224                             llvm::StringRef(IncludeArg),
225                             llvm::StringRef(APIArg),
226                             "--gen-alias",
227                             "--mangled-name",
228                             "__llvm_libc_strlen_mangled_name",
229                             "--mangled-name-file",
230                             "non-existant-mangled-name-file.txt",
231                             "--name",
232                             "strlen"};
233 
234   int ExitCode =
235       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
236 
237   EXPECT_EQ(ExitCode, 1);
238 
239   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
240   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
241 
242   ASSERT_EQ(STDErrOutput,
243             "error: The options 'mangled-name' and 'mangled-name-file' "
244             "cannot be specified simultaneously.\n");
245 
246   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
247   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
248 
249   ASSERT_EQ(STDOutOutput, "");
250 }
251 
252 TEST_F(LlvmLibcWrapperGenTest, RunWrapperGenOnBadFuncName) {
253   llvm::Optional<llvm::StringRef> Redirects[] = {
254       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
255       llvm::StringRef(STDErrFile.get().TmpName)};
256 
257   llvm::StringRef BadFuncName = "FAKE_TEST_FUNC";
258 
259   llvm::StringRef ArgV[] = {ProgPath,
260                             llvm::StringRef(IncludeArg),
261                             llvm::StringRef(APIArg),
262                             "--gen-wrapper",
263                             "--name",
264                             BadFuncName};
265 
266   int ExitCode =
267       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
268 
269   EXPECT_EQ(ExitCode, 1);
270 
271   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
272   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
273 
274   ASSERT_EQ(STDErrOutput, ("error: Function '" + BadFuncName +
275                            "' not found in any standard spec.\n")
276                               .str());
277 
278   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
279   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
280 
281   ASSERT_EQ(STDOutOutput, "");
282 }
283 
284 TEST_F(LlvmLibcWrapperGenTest, RunWrapperGenOnStrlenWithBadMangledNameFile) {
285   llvm::Optional<llvm::StringRef> Redirects[] = {
286       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
287       llvm::StringRef(STDErrFile.get().TmpName)};
288 
289   llvm::StringRef BadMangledNameFileName = "FILE_THAT_DOESNT_EXIST.txt";
290 
291   llvm::StringRef ArgV[] = {ProgPath,
292                             llvm::StringRef(IncludeArg),
293                             llvm::StringRef(APIArg),
294                             "--gen-alias",
295                             "--mangled-name-file",
296                             BadMangledNameFileName,
297                             "--name",
298                             "strlen"};
299 
300   int ExitCode =
301       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
302 
303   EXPECT_EQ(ExitCode, 1);
304 
305   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
306   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
307 
308   ASSERT_EQ(STDErrOutput, ("error: Unable to read the mangled name file " +
309                            BadMangledNameFileName + "\n")
310                               .str());
311 
312   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
313   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
314 
315   ASSERT_EQ(STDOutOutput, "");
316 }
317 
318 TEST_F(LlvmLibcWrapperGenTest, RunWithMangledNameFileMissingLLVMLibcName) {
319   llvm::Optional<llvm::StringRef> Redirects[] = {
320       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
321       llvm::StringRef(STDErrFile.get().TmpName)};
322 
323   llvm::SmallVector<char> MangledNameFilePath;
324   auto MangledNameFileCreateError = llvm::sys::fs::createUniqueFile(
325       "libc-wrappergen-test-mangled-name-file-%%-%%-%%-%%.txt",
326       MangledNameFilePath);
327   ASSERT_FALSE(MangledNameFileCreateError);
328 
329   llvm::StringRef ArgV[] = {ProgPath,
330                             llvm::StringRef(IncludeArg),
331                             llvm::StringRef(APIArg),
332                             "--gen-alias",
333                             "--mangled-name-file",
334                             llvm::StringRef(MangledNameFilePath.data()),
335                             "--name",
336                             "strlen"};
337 
338   int ExitCode =
339       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
340 
341   EXPECT_NE(ExitCode, 0);
342 
343   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
344   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
345 
346   ASSERT_EQ(STDErrOutput, ("error: Did not find an LLVM libc mangled name in " +
347                            MangledNameFilePath + "\n")
348                               .str());
349 }
350