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