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