| 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 | |