1 | //===- unittests/Analysis/MacroExpansionContextTest.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 "clang/Analysis/MacroExpansionContext.h" |
10 | #include "clang/AST/ASTConsumer.h" |
11 | #include "clang/AST/ASTContext.h" |
12 | #include "clang/Basic/Diagnostic.h" |
13 | #include "clang/Basic/DiagnosticOptions.h" |
14 | #include "clang/Basic/FileManager.h" |
15 | #include "clang/Basic/LangOptions.h" |
16 | #include "clang/Basic/SourceManager.h" |
17 | #include "clang/Basic/TargetInfo.h" |
18 | #include "clang/Basic/TargetOptions.h" |
19 | #include "clang/Lex/HeaderSearch.h" |
20 | #include "clang/Lex/HeaderSearchOptions.h" |
21 | #include "clang/Lex/Preprocessor.h" |
22 | #include "clang/Lex/PreprocessorOptions.h" |
23 | #include "clang/Parse/Parser.h" |
24 | #include "llvm/ADT/SmallString.h" |
25 | #include "gtest/gtest.h" |
26 | |
27 | // static bool HACK_EnableDebugInUnitTest = (::llvm::DebugFlag = true); |
28 | |
29 | namespace clang { |
30 | namespace analysis { |
31 | namespace { |
32 | |
33 | class MacroExpansionContextTest : public ::testing::Test { |
34 | protected: |
35 | MacroExpansionContextTest() |
36 | : InMemoryFileSystem(new llvm::vfs::InMemoryFileSystem), |
37 | FileMgr(FileSystemOptions(), InMemoryFileSystem), |
38 | DiagID(new DiagnosticIDs()), DiagOpts(new DiagnosticOptions()), |
39 | Diags(DiagID, DiagOpts.get(), new IgnoringDiagConsumer()), |
40 | SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions()) { |
41 | TargetOpts->Triple = "x86_64-pc-linux-unknown" ; |
42 | Target = TargetInfo::CreateTargetInfo(Diags, Opts: TargetOpts); |
43 | LangOpts.CPlusPlus20 = 1; // For __VA_OPT__ |
44 | } |
45 | |
46 | IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> InMemoryFileSystem; |
47 | FileManager FileMgr; |
48 | IntrusiveRefCntPtr<DiagnosticIDs> DiagID; |
49 | IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts; |
50 | DiagnosticsEngine Diags; |
51 | SourceManager SourceMgr; |
52 | LangOptions LangOpts; |
53 | std::shared_ptr<TargetOptions> TargetOpts; |
54 | IntrusiveRefCntPtr<TargetInfo> Target; |
55 | |
56 | std::unique_ptr<MacroExpansionContext> |
57 | getMacroExpansionContextFor(StringRef SourceText) { |
58 | std::unique_ptr<llvm::MemoryBuffer> Buf = |
59 | llvm::MemoryBuffer::getMemBuffer(InputData: SourceText); |
60 | SourceMgr.setMainFileID(SourceMgr.createFileID(Buffer: std::move(Buf))); |
61 | TrivialModuleLoader ModLoader; |
62 | HeaderSearch (std::make_shared<HeaderSearchOptions>(), SourceMgr, |
63 | Diags, LangOpts, Target.get()); |
64 | Preprocessor PP(std::make_shared<PreprocessorOptions>(), Diags, LangOpts, |
65 | SourceMgr, HeaderInfo, ModLoader, |
66 | /*IILookup =*/nullptr, |
67 | /*OwnsHeaderSearch =*/false); |
68 | |
69 | PP.Initialize(Target: *Target); |
70 | auto Ctx = std::make_unique<MacroExpansionContext>(args&: LangOpts); |
71 | Ctx->registerForPreprocessor(PP); |
72 | |
73 | // Lex source text. |
74 | PP.EnterMainSourceFile(); |
75 | |
76 | PP.LexTokensUntilEOF(); |
77 | |
78 | // Callbacks have been executed at this point. |
79 | return Ctx; |
80 | } |
81 | |
82 | /// Returns the expansion location to main file at the given row and column. |
83 | SourceLocation at(unsigned row, unsigned col) const { |
84 | SourceLocation Loc = |
85 | SourceMgr.translateLineCol(FID: SourceMgr.getMainFileID(), Line: row, Col: col); |
86 | return SourceMgr.getExpansionLoc(Loc); |
87 | } |
88 | |
89 | static std::string dumpExpandedTexts(const MacroExpansionContext &Ctx) { |
90 | std::string Buf; |
91 | llvm::raw_string_ostream OS{Buf}; |
92 | Ctx.dumpExpandedTextsToStream(OS); |
93 | return Buf; |
94 | } |
95 | |
96 | static std::string dumpExpansionRanges(const MacroExpansionContext &Ctx) { |
97 | std::string Buf; |
98 | llvm::raw_string_ostream OS{Buf}; |
99 | Ctx.dumpExpansionRangesToStream(OS); |
100 | return Buf; |
101 | } |
102 | }; |
103 | |
104 | TEST_F(MacroExpansionContextTest, IgnoresPragmas) { |
105 | // No-crash during lexing. |
106 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
107 | _Pragma("pack(push, 1)") |
108 | _Pragma("pack(pop, 1)") |
109 | )code" ); |
110 | // After preprocessing: |
111 | // #pragma pack(push, 1) |
112 | // #pragma pack(pop, 1) |
113 | |
114 | EXPECT_EQ("\n=============== ExpandedTokens ===============\n" , |
115 | dumpExpandedTexts(*Ctx)); |
116 | EXPECT_EQ("\n=============== ExpansionRanges ===============\n" , |
117 | dumpExpansionRanges(*Ctx)); |
118 | |
119 | EXPECT_FALSE(Ctx->getExpandedText(at(2, 1)).has_value()); |
120 | EXPECT_FALSE(Ctx->getOriginalText(at(2, 1)).has_value()); |
121 | |
122 | EXPECT_FALSE(Ctx->getExpandedText(at(2, 3)).has_value()); |
123 | EXPECT_FALSE(Ctx->getOriginalText(at(2, 3)).has_value()); |
124 | |
125 | EXPECT_FALSE(Ctx->getExpandedText(at(3, 3)).has_value()); |
126 | EXPECT_FALSE(Ctx->getOriginalText(at(3, 3)).has_value()); |
127 | } |
128 | |
129 | TEST_F(MacroExpansionContextTest, NoneForNonExpansionLocations) { |
130 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
131 | #define EMPTY |
132 | A b cd EMPTY ef EMPTY gh |
133 | EMPTY zz |
134 | )code" ); |
135 | // After preprocessing: |
136 | // A b cd ef gh |
137 | // zz |
138 | |
139 | // That's the beginning of the definition of EMPTY. |
140 | EXPECT_FALSE(Ctx->getExpandedText(at(2, 11)).has_value()); |
141 | EXPECT_FALSE(Ctx->getOriginalText(at(2, 11)).has_value()); |
142 | |
143 | // The space before the first expansion of EMPTY. |
144 | EXPECT_FALSE(Ctx->getExpandedText(at(3, 9)).has_value()); |
145 | EXPECT_FALSE(Ctx->getOriginalText(at(3, 9)).has_value()); |
146 | |
147 | // The beginning of the first expansion of EMPTY. |
148 | EXPECT_TRUE(Ctx->getExpandedText(at(3, 10)).has_value()); |
149 | EXPECT_TRUE(Ctx->getOriginalText(at(3, 10)).has_value()); |
150 | |
151 | // Pointing inside of the token EMPTY, but not at the beginning. |
152 | // FIXME: We only deal with begin locations. |
153 | EXPECT_FALSE(Ctx->getExpandedText(at(3, 11)).has_value()); |
154 | EXPECT_FALSE(Ctx->getOriginalText(at(3, 11)).has_value()); |
155 | |
156 | // Same here. |
157 | EXPECT_FALSE(Ctx->getExpandedText(at(3, 12)).has_value()); |
158 | EXPECT_FALSE(Ctx->getOriginalText(at(3, 12)).has_value()); |
159 | |
160 | // The beginning of the last expansion of EMPTY. |
161 | EXPECT_TRUE(Ctx->getExpandedText(at(4, 1)).has_value()); |
162 | EXPECT_TRUE(Ctx->getOriginalText(at(4, 1)).has_value()); |
163 | |
164 | // Same as for the 3:11 case. |
165 | EXPECT_FALSE(Ctx->getExpandedText(at(4, 2)).has_value()); |
166 | EXPECT_FALSE(Ctx->getOriginalText(at(4, 2)).has_value()); |
167 | } |
168 | |
169 | TEST_F(MacroExpansionContextTest, EmptyExpansions) { |
170 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
171 | #define EMPTY |
172 | A b cd EMPTY ef EMPTY gh |
173 | EMPTY zz |
174 | )code" ); |
175 | // After preprocessing: |
176 | // A b cd ef gh |
177 | // zz |
178 | |
179 | EXPECT_EQ("" , *Ctx->getExpandedText(at(3, 10))); |
180 | EXPECT_EQ("EMPTY" , *Ctx->getOriginalText(at(3, 10))); |
181 | |
182 | EXPECT_EQ("" , *Ctx->getExpandedText(at(3, 19))); |
183 | EXPECT_EQ("EMPTY" , *Ctx->getOriginalText(at(3, 19))); |
184 | |
185 | EXPECT_EQ("" , *Ctx->getExpandedText(at(4, 1))); |
186 | EXPECT_EQ("EMPTY" , *Ctx->getOriginalText(at(4, 1))); |
187 | } |
188 | |
189 | TEST_F(MacroExpansionContextTest, TransitiveExpansions) { |
190 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
191 | #define EMPTY |
192 | #define WOOF EMPTY ) EMPTY 1 |
193 | A b cd WOOF ef EMPTY gh |
194 | )code" ); |
195 | // After preprocessing: |
196 | // A b cd ) 1 ef gh |
197 | |
198 | EXPECT_EQ("WOOF" , *Ctx->getOriginalText(at(4, 10))); |
199 | |
200 | EXPECT_EQ("" , *Ctx->getExpandedText(at(4, 18))); |
201 | EXPECT_EQ("EMPTY" , *Ctx->getOriginalText(at(4, 18))); |
202 | } |
203 | |
204 | TEST_F(MacroExpansionContextTest, MacroFunctions) { |
205 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
206 | #define EMPTY |
207 | #define WOOF(x) x(EMPTY ) ) ) EMPTY 1 |
208 | A b cd WOOF($$ ef) EMPTY gh |
209 | WOOF(WOOF) |
210 | WOOF(WOOF(bar barr))),,),') |
211 | )code" ); |
212 | // After preprocessing: |
213 | // A b cd $$ ef( ) ) ) 1 gh |
214 | // WOOF( ) ) ) 1 |
215 | // bar barr( ) ) ) 1( ) ) ) 1),,),') |
216 | |
217 | EXPECT_EQ("$$ ef ()))1" , *Ctx->getExpandedText(at(4, 10))); |
218 | EXPECT_EQ("WOOF($$ ef)" , *Ctx->getOriginalText(at(4, 10))); |
219 | |
220 | EXPECT_EQ("" , *Ctx->getExpandedText(at(4, 22))); |
221 | EXPECT_EQ("EMPTY" , *Ctx->getOriginalText(at(4, 22))); |
222 | |
223 | EXPECT_EQ("WOOF ()))1" , *Ctx->getExpandedText(at(5, 3))); |
224 | EXPECT_EQ("WOOF(WOOF)" , *Ctx->getOriginalText(at(5, 3))); |
225 | |
226 | EXPECT_EQ("bar barr ()))1()))1" , *Ctx->getExpandedText(at(6, 3))); |
227 | EXPECT_EQ("WOOF(WOOF(bar barr))" , *Ctx->getOriginalText(at(6, 3))); |
228 | } |
229 | |
230 | TEST_F(MacroExpansionContextTest, VariadicMacros) { |
231 | // From the GCC website. |
232 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
233 | #define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__) |
234 | eprintf("success!\n", ); |
235 | eprintf("success!\n"); |
236 | |
237 | #define eprintf2(format, ...) \ |
238 | fprintf (stderr, format __VA_OPT__(,) __VA_ARGS__) |
239 | eprintf2("success!\n", ); |
240 | eprintf2("success!\n"); |
241 | )code" ); |
242 | // After preprocessing: |
243 | // fprintf (stderr, "success!\n", ); |
244 | // fprintf (stderr, "success!\n", ); |
245 | // fprintf (stderr, "success!\n" ); |
246 | // fprintf (stderr, "success!\n" ); |
247 | |
248 | EXPECT_EQ(R"(fprintf (stderr ,"success!\n",))" , |
249 | *Ctx->getExpandedText(at(3, 3))); |
250 | EXPECT_EQ(R"(eprintf("success!\n", ))" , *Ctx->getOriginalText(at(3, 3))); |
251 | |
252 | EXPECT_EQ(R"(fprintf (stderr ,"success!\n",))" , |
253 | *Ctx->getExpandedText(at(4, 3))); |
254 | EXPECT_EQ(R"(eprintf("success!\n"))" , *Ctx->getOriginalText(at(4, 3))); |
255 | |
256 | EXPECT_EQ(R"(fprintf (stderr ,"success!\n"))" , |
257 | *Ctx->getExpandedText(at(8, 3))); |
258 | EXPECT_EQ(R"(eprintf2("success!\n", ))" , *Ctx->getOriginalText(at(8, 3))); |
259 | |
260 | EXPECT_EQ(R"(fprintf (stderr ,"success!\n"))" , |
261 | *Ctx->getExpandedText(at(9, 3))); |
262 | EXPECT_EQ(R"(eprintf2("success!\n"))" , *Ctx->getOriginalText(at(9, 3))); |
263 | } |
264 | |
265 | TEST_F(MacroExpansionContextTest, ConcatenationMacros) { |
266 | // From the GCC website. |
267 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
268 | #define COMMAND(NAME) { #NAME, NAME ## _command } |
269 | struct command commands[] = { |
270 | COMMAND(quit), |
271 | COMMAND(help), |
272 | };)code" ); |
273 | // After preprocessing: |
274 | // struct command commands[] = { |
275 | // { "quit", quit_command }, |
276 | // { "help", help_command }, |
277 | // }; |
278 | |
279 | EXPECT_EQ(R"({"quit",quit_command })" , *Ctx->getExpandedText(at(4, 5))); |
280 | EXPECT_EQ("COMMAND(quit)" , *Ctx->getOriginalText(at(4, 5))); |
281 | |
282 | EXPECT_EQ(R"({"help",help_command })" , *Ctx->getExpandedText(at(5, 5))); |
283 | EXPECT_EQ("COMMAND(help)" , *Ctx->getOriginalText(at(5, 5))); |
284 | } |
285 | |
286 | TEST_F(MacroExpansionContextTest, StringizingMacros) { |
287 | // From the GCC website. |
288 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
289 | #define WARN_IF(EXP) \ |
290 | do { if (EXP) \ |
291 | fprintf (stderr, "Warning: " #EXP "\n"); } \ |
292 | while (0) |
293 | WARN_IF (x == 0); |
294 | |
295 | #define xstr(s) str(s) |
296 | #define str(s) #s |
297 | #define foo 4 |
298 | str (foo) |
299 | xstr (foo) |
300 | )code" ); |
301 | // After preprocessing: |
302 | // do { if (x == 0) fprintf (stderr, "Warning: " "x == 0" "\n"); } while (0); |
303 | // "foo" |
304 | // "4" |
305 | |
306 | EXPECT_EQ( |
307 | R"(do {if (x ==0)fprintf (stderr ,"Warning: ""x == 0""\n");}while (0))" , |
308 | *Ctx->getExpandedText(at(6, 3))); |
309 | EXPECT_EQ("WARN_IF (x == 0)" , *Ctx->getOriginalText(at(6, 3))); |
310 | |
311 | EXPECT_EQ(R"("foo")" , *Ctx->getExpandedText(at(11, 3))); |
312 | EXPECT_EQ("str (foo)" , *Ctx->getOriginalText(at(11, 3))); |
313 | |
314 | EXPECT_EQ(R"("4")" , *Ctx->getExpandedText(at(12, 3))); |
315 | EXPECT_EQ("xstr (foo)" , *Ctx->getOriginalText(at(12, 3))); |
316 | } |
317 | |
318 | TEST_F(MacroExpansionContextTest, StringizingVariadicMacros) { |
319 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
320 | #define xstr(...) str(__VA_ARGS__) |
321 | #define str(...) #__VA_ARGS__ |
322 | #define RParen2x ) ) |
323 | #define EMPTY |
324 | #define f(x, ...) __VA_ARGS__ ! x * x |
325 | #define g(...) zz EMPTY f(__VA_ARGS__ ! x) f() * y |
326 | #define h(x, G) G(x) G(x ## x RParen2x |
327 | #define q(G) h(apple, G(apple)) RParen2x |
328 | |
329 | q(g) |
330 | q(xstr) |
331 | g(RParen2x) |
332 | f( RParen2x )s |
333 | )code" ); |
334 | // clang-format off |
335 | // After preprocessing: |
336 | // zz ! apple ! x * apple ! x ! * * y(apple) zz ! apple ! x * apple ! x ! * * y(appleapple ) ) ) ) |
337 | // "apple"(apple) "apple"(appleapple ) ) ) ) |
338 | // zz ! * ) ! x) ! * * y |
339 | // ! ) ) * ) ) |
340 | // clang-format on |
341 | |
342 | EXPECT_EQ("zz !apple !x *apple !x !**y (apple )zz !apple !x *apple !x !**y " |
343 | "(appleapple ))))" , |
344 | *Ctx->getExpandedText(at(11, 3))); |
345 | EXPECT_EQ("q(g)" , *Ctx->getOriginalText(at(11, 3))); |
346 | |
347 | EXPECT_EQ(R"res("apple"(apple )"apple"(appleapple )))))res" , |
348 | *Ctx->getExpandedText(at(12, 3))); |
349 | EXPECT_EQ("q(xstr)" , *Ctx->getOriginalText(at(12, 3))); |
350 | |
351 | EXPECT_EQ("zz !*)!x )!**y " , *Ctx->getExpandedText(at(13, 3))); |
352 | EXPECT_EQ("g(RParen2x)" , *Ctx->getOriginalText(at(13, 3))); |
353 | |
354 | EXPECT_EQ("!))*))" , *Ctx->getExpandedText(at(14, 3))); |
355 | EXPECT_EQ("f( RParen2x )" , *Ctx->getOriginalText(at(14, 3))); |
356 | } |
357 | |
358 | TEST_F(MacroExpansionContextTest, RedefUndef) { |
359 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
360 | #define Hi(x) Welcome x |
361 | Hi(Adam) |
362 | #define Hi Willkommen |
363 | Hi Hans |
364 | #undef Hi |
365 | Hi(Hi) |
366 | )code" ); |
367 | // After preprocessing: |
368 | // Welcome Adam |
369 | // Willkommen Hans |
370 | // Hi(Hi) |
371 | |
372 | // FIXME: Extra space follows every identifier. |
373 | EXPECT_EQ("Welcome Adam " , *Ctx->getExpandedText(at(3, 3))); |
374 | EXPECT_EQ("Hi(Adam)" , *Ctx->getOriginalText(at(3, 3))); |
375 | |
376 | EXPECT_EQ("Willkommen " , *Ctx->getExpandedText(at(5, 3))); |
377 | EXPECT_EQ("Hi" , *Ctx->getOriginalText(at(5, 3))); |
378 | |
379 | // There was no macro expansion at 7:3, we should expect None. |
380 | EXPECT_FALSE(Ctx->getExpandedText(at(7, 3)).has_value()); |
381 | EXPECT_FALSE(Ctx->getOriginalText(at(7, 3)).has_value()); |
382 | } |
383 | |
384 | TEST_F(MacroExpansionContextTest, UnbalacedParenthesis) { |
385 | const auto Ctx = getMacroExpansionContextFor(SourceText: R"code( |
386 | #define retArg(x) x |
387 | #define retArgUnclosed retArg(fun() |
388 | #define BB CC |
389 | #define applyInt BB(int) |
390 | #define CC(x) retArgUnclosed |
391 | |
392 | applyInt ); |
393 | |
394 | #define expandArgUnclosedCommaExpr(x) (x, fun(), 1 |
395 | #define f expandArgUnclosedCommaExpr |
396 | |
397 | int x = f(f(1)) )); |
398 | )code" ); |
399 | // After preprocessing: |
400 | // fun(); |
401 | // int x = ((1, fun(), 1, fun(), 1 )); |
402 | |
403 | EXPECT_EQ("fun ()" , *Ctx->getExpandedText(at(8, 3))); |
404 | EXPECT_EQ("applyInt )" , *Ctx->getOriginalText(at(8, 3))); |
405 | |
406 | EXPECT_EQ("((1,fun (),1,fun (),1" , *Ctx->getExpandedText(at(13, 12))); |
407 | EXPECT_EQ("f(f(1))" , *Ctx->getOriginalText(at(13, 12))); |
408 | } |
409 | |
410 | } // namespace |
411 | } // namespace analysis |
412 | } // namespace clang |
413 | |