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 TEST_F(WrapperGenTest, RunWrapperGenAndGetNoErrors) {
71   llvm::Optional<llvm::StringRef> Redirects[] = {
72       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
73       llvm::StringRef(STDErrFile.get().TmpName)};
74 
75   llvm::StringRef ArgV[] = {ProgPath,
76                             llvm::StringRef(IncludeArg),
77                             llvm::StringRef(APIArg),
78                             "--gen-wrapper",
79                             "--name",
80                             "strlen"};
81 
82   int ExitCode =
83       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
84 
85   EXPECT_EQ(ExitCode, 0);
86 
87   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
88   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
89   ASSERT_EQ(STDErrOutput, "");
90 }
91 
92 TEST_F(WrapperGenTest, RunWrapperGenOnStrlen) {
93   llvm::Optional<llvm::StringRef> Redirects[] = {
94       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
95       llvm::StringRef(STDErrFile.get().TmpName)};
96 
97   llvm::StringRef ArgV[] = {ProgPath,
98                             llvm::StringRef(IncludeArg),
99                             llvm::StringRef(APIArg),
100                             "--gen-wrapper",
101                             "--name",
102                             "strlen"};
103 
104   int ExitCode =
105       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
106 
107   EXPECT_EQ(ExitCode, 0);
108 
109   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
110   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
111 
112   ASSERT_EQ(STDErrOutput, "");
113 
114   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
115   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
116 
117   ASSERT_EQ(STDOutOutput, "#include \"src/string/strlen.h\"\n"
118                           "extern \"C\" size_t strlen(const char * __arg0) {\n"
119                           "  return __llvm_libc::strlen(__arg0);\n"
120                           "}\n");
121   // TODO:(michaelrj) Figure out how to make this output comparison
122   // less brittle. Currently it's just comparing the output of the program
123   // to an exact string, this means that even a small formatting change
124   // would break this test.
125 }
126 
127 TEST_F(WrapperGenTest, GenAliasForStrlen) {
128   llvm::Optional<llvm::StringRef> Redirects[] = {
129       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
130       llvm::StringRef(STDErrFile.get().TmpName)};
131 
132   llvm::StringRef ArgV[] = {ProgPath,
133                             llvm::StringRef(IncludeArg),
134                             llvm::StringRef(APIArg),
135                             "--gen-alias",
136                             "--mangled-name",
137                             "__llvm_libc_strlen_mangled_name",
138                             "--name",
139                             "strlen"};
140 
141   int ExitCode =
142       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
143 
144   EXPECT_EQ(ExitCode, 0);
145 
146   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
147   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
148 
149   ASSERT_EQ(STDErrOutput, "");
150 
151   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
152   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
153 
154   ASSERT_EQ(STDOutOutput,
155             "extern \"C\" size_t strlen(const char * __arg0) "
156             "__attribute__((alias(\"__llvm_libc_strlen_mangled_name\")));\n");
157   // TODO:(michaelrj) Figure out how to make this output comparison
158   // less brittle. Currently it's just comparing the output of the program
159   // to an exact string, this means that even a small formatting change
160   // would break this test.
161 }
162 
163 TEST_F(WrapperGenTest, DeclStrlenAliasUsingMangledNameFile) {
164   llvm::Optional<llvm::StringRef> Redirects[] = {
165       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
166       llvm::StringRef(STDErrFile.get().TmpName)};
167 
168   const char *MangledNameFileContent =
169       "abc\nxyz__llvm_libc_strlen_mangled_name\nijk\n";
170   llvm::SmallVector<char> MangledNameFilePath;
171   auto MangledNameFileCreateError = llvm::sys::fs::createUniqueFile(
172       "libc-wrappergen-test-aliasee-file-%%-%%-%%-%%.txt", MangledNameFilePath);
173   ASSERT_FALSE(MangledNameFileCreateError);
174   auto MangledNameFileWriteError = llvm::writeFileAtomically(
175       "libc-wrappergen-temp-test-aliasee-file-%%-%%-%%-%%.txt",
176       llvm::StringRef(MangledNameFilePath.data()),
177       llvm::StringRef(MangledNameFileContent));
178   ASSERT_FALSE(MangledNameFileWriteError);
179 
180   llvm::StringRef ArgV[] = {ProgPath,
181                             llvm::StringRef(IncludeArg),
182                             llvm::StringRef(APIArg),
183                             "--gen-alias",
184                             "--mangled-name-file",
185                             llvm::StringRef(MangledNameFilePath.data()),
186                             "--name",
187                             "strlen"};
188 
189   int ExitCode =
190       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
191 
192   EXPECT_EQ(ExitCode, 0);
193 
194   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
195   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
196 
197   ASSERT_EQ(STDErrOutput, "");
198 
199   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
200   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
201 
202   ASSERT_EQ(
203       STDOutOutput,
204       "extern \"C\" size_t strlen(const char * __arg0) "
205       "__attribute__((alias(\"xyz__llvm_libc_strlen_mangled_name\")));\n");
206 }
207 
208 /////////////////////////////////////////////////////////////////////
209 /////////////////////////////////////////////////////////////////////
210 // BAD INPUT TESTS
211 // all of the tests after this point are testing inputs that should
212 // return errors
213 /////////////////////////////////////////////////////////////////////
214 
215 TEST_F(WrapperGenTest, RunWrapperGenOnStrlenWithMangledNameAndMangledNameFile) {
216   llvm::Optional<llvm::StringRef> Redirects[] = {
217       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
218       llvm::StringRef(STDErrFile.get().TmpName)};
219 
220   llvm::StringRef ArgV[] = {ProgPath,
221                             llvm::StringRef(IncludeArg),
222                             llvm::StringRef(APIArg),
223                             "--gen-alias",
224                             "--mangled-name",
225                             "__llvm_libc_strlen_mangled_name",
226                             "--mangled-name-file",
227                             "non-existant-mangled-name-file.txt",
228                             "--name",
229                             "strlen"};
230 
231   int ExitCode =
232       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
233 
234   EXPECT_EQ(ExitCode, 1);
235 
236   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
237   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
238 
239   ASSERT_EQ(STDErrOutput,
240             "error: The options 'mangled-name' and 'mangled-name-file' "
241             "cannot be specified simultaneously.\n");
242 
243   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
244   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
245 
246   ASSERT_EQ(STDOutOutput, "");
247 }
248 
249 TEST_F(WrapperGenTest, RunWrapperGenOnBadFuncName) {
250   llvm::Optional<llvm::StringRef> Redirects[] = {
251       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
252       llvm::StringRef(STDErrFile.get().TmpName)};
253 
254   llvm::StringRef BadFuncName = "FAKE_TEST_FUNC";
255 
256   llvm::StringRef ArgV[] = {ProgPath,
257                             llvm::StringRef(IncludeArg),
258                             llvm::StringRef(APIArg),
259                             "--gen-wrapper",
260                             "--name",
261                             BadFuncName};
262 
263   int ExitCode =
264       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
265 
266   EXPECT_EQ(ExitCode, 1);
267 
268   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
269   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
270 
271   ASSERT_EQ(STDErrOutput, ("error: Function '" + BadFuncName +
272                            "' not found in any standard spec.\n")
273                               .str());
274 
275   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
276   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
277 
278   ASSERT_EQ(STDOutOutput, "");
279 }
280 
281 TEST_F(WrapperGenTest, RunWrapperGenOnStrlenWithBadMangledNameFile) {
282   llvm::Optional<llvm::StringRef> Redirects[] = {
283       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
284       llvm::StringRef(STDErrFile.get().TmpName)};
285 
286   llvm::StringRef BadMangledNameFileName = "FILE_THAT_DOESNT_EXIST.txt";
287 
288   llvm::StringRef ArgV[] = {ProgPath,
289                             llvm::StringRef(IncludeArg),
290                             llvm::StringRef(APIArg),
291                             "--gen-alias",
292                             "--mangled-name-file",
293                             BadMangledNameFileName,
294                             "--name",
295                             "strlen"};
296 
297   int ExitCode =
298       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
299 
300   EXPECT_EQ(ExitCode, 1);
301 
302   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
303   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
304 
305   ASSERT_EQ(STDErrOutput, ("error: Unable to read the mangled name file " +
306                            BadMangledNameFileName + "\n")
307                               .str());
308 
309   auto STDOutOrError = llvm::MemoryBuffer::getFile(STDOutFile.get().TmpName);
310   std::string STDOutOutput = STDOutOrError.get()->getBuffer().str();
311 
312   ASSERT_EQ(STDOutOutput, "");
313 }
314 
315 TEST_F(WrapperGenTest, RunWithMangledNameFileMissingLLVMLibcName) {
316   llvm::Optional<llvm::StringRef> Redirects[] = {
317       llvm::None, llvm::StringRef(STDOutFile.get().TmpName),
318       llvm::StringRef(STDErrFile.get().TmpName)};
319 
320   llvm::SmallVector<char> MangledNameFilePath;
321   auto MangledNameFileCreateError = llvm::sys::fs::createUniqueFile(
322       "libc-wrappergen-test-mangled-name-file-%%-%%-%%-%%.txt",
323       MangledNameFilePath);
324   ASSERT_FALSE(MangledNameFileCreateError);
325 
326   llvm::StringRef ArgV[] = {ProgPath,
327                             llvm::StringRef(IncludeArg),
328                             llvm::StringRef(APIArg),
329                             "--gen-alias",
330                             "--mangled-name-file",
331                             llvm::StringRef(MangledNameFilePath.data()),
332                             "--name",
333                             "strlen"};
334 
335   int ExitCode =
336       llvm::sys::ExecuteAndWait(ProgPath, ArgV, llvm::None, Redirects);
337 
338   EXPECT_NE(ExitCode, 0);
339 
340   auto STDErrOrError = llvm::MemoryBuffer::getFile(STDErrFile.get().TmpName);
341   std::string STDErrOutput = STDErrOrError.get()->getBuffer().str();
342 
343   ASSERT_EQ(STDErrOutput, ("error: Did not find an LLVM libc mangled name in " +
344                            MangledNameFilePath + "\n")
345                               .str());
346 }
347