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