Index: clang/include/clang/Analysis/MacroExpansionContext.h =================================================================== --- clang/include/clang/Analysis/MacroExpansionContext.h +++ clang/include/clang/Analysis/MacroExpansionContext.h @@ -13,6 +13,7 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -71,25 +72,27 @@ /// parameter. class MacroExpansionContext { public: + /// Creates a MacroExpansionContext. + /// \remark You must call registerForPreprocessor to set the required + /// onTokenLexed callback and the PPCallbacks. + explicit MacroExpansionContext(const LangOptions &LangOpts); + /// Register the necessary callbacks to the Preprocessor to record the /// expansion events and the generated tokens. Must ensure that this object /// outlives the given Preprocessor. - MacroExpansionContext(Preprocessor &PP, const LangOptions &LangOpts); - using MacroExpansionText = SmallString<40>; + void registerForPreprocessor(Preprocessor &PP); /// \param MacroExpansionLoc Must be the expansion location of a macro. /// \return The textual representation of the token sequence which was - /// substituted in place of the macro. - /// If no macro was expanded at that location, returns an empty - /// string. - MacroExpansionText getExpandedText(SourceLocation MacroExpansionLoc) const; + /// substituted in place of the macro after the preprocessing. + /// If no macro was expanded at that location, returns llvm::None. + Optional getExpandedText(SourceLocation MacroExpansionLoc) const; /// \param MacroExpansionLoc Must be the expansion location of a macro. /// \return The text from the original source code which were substituted by /// the macro expansion chain from the given location. - /// If no macro was expanded at that location, returns an empty - /// string. - StringRef getOriginalText(SourceLocation MacroExpansionLoc) const; + /// If no macro was expanded at that location, returns llvm::None. + Optional getOriginalText(SourceLocation MacroExpansionLoc) const; LLVM_DUMP_METHOD void dumpExpansionRangesToStream(raw_ostream &OS) const; LLVM_DUMP_METHOD void dumpExpandedTextsToStream(raw_ostream &OS) const; @@ -98,6 +101,7 @@ private: friend class detail::MacroExpansionRangeRecorder; + using MacroExpansionText = SmallString<40>; using ExpansionMap = llvm::DenseMap; using ExpansionRangeMap = llvm::DenseMap; @@ -109,8 +113,8 @@ /// substitution starting from a given macro expansion location. ExpansionRangeMap ExpansionRanges; - const Preprocessor &PP; - const SourceManager &SM; + Preprocessor *PP = nullptr; + SourceManager *SM = nullptr; const LangOptions &LangOpts; /// This callback is called by the preprocessor. Index: clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h +++ clang/include/clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h @@ -21,7 +21,9 @@ namespace clang { class AnalyzerOptions; +class MacroExpansionContext; class Preprocessor; + namespace cross_tu { class CrossTranslationUnitContext; } @@ -35,7 +37,8 @@ void CREATEFN(PathDiagnosticConsumerOptions Diagopts, \ PathDiagnosticConsumers &C, const std::string &Prefix, \ const Preprocessor &PP, \ - const cross_tu::CrossTranslationUnitContext &CTU); + const cross_tu::CrossTranslationUnitContext &CTU, \ + const MacroExpansionContext &MacroExpansions); #include "clang/StaticAnalyzer/Core/Analyses.def" } // end 'ento' namespace Index: clang/lib/Analysis/MacroExpansionContext.cpp =================================================================== --- clang/lib/Analysis/MacroExpansionContext.cpp +++ clang/lib/Analysis/MacroExpansionContext.cpp @@ -76,43 +76,53 @@ } // namespace clang using namespace clang; -using MacroExpansionText = MacroExpansionContext::MacroExpansionText; -MacroExpansionContext::MacroExpansionContext(Preprocessor &PP, - const LangOptions &LangOpts) - : PP(PP), SM(PP.getSourceManager()), LangOpts(LangOpts) { +MacroExpansionContext::MacroExpansionContext(const LangOptions &LangOpts) + : LangOpts(LangOpts) {} + +void MacroExpansionContext::registerForPreprocessor(Preprocessor &NewPP) { + PP = &NewPP; + SM = &NewPP.getSourceManager(); // Make sure that the Preprocessor does not outlive the MacroExpansionContext. - PP.addPPCallbacks(std::make_unique( - PP, PP.getSourceManager(), ExpansionRanges)); + PP->addPPCallbacks(std::make_unique( + *PP, *SM, ExpansionRanges)); // Same applies here. - PP.setTokenWatcher([this](const Token &Tok) { onTokenLexed(Tok); }); + PP->setTokenWatcher([this](const Token &Tok) { onTokenLexed(Tok); }); } -MacroExpansionText +Optional MacroExpansionContext::getExpandedText(SourceLocation MacroExpansionLoc) const { - assert(MacroExpansionLoc.isFileID() && - "It has a spelling location, use the expansion location instead."); - - const auto it = ExpandedTokens.find_as(MacroExpansionLoc); - if (it == ExpandedTokens.end()) - llvm_unreachable( - "Every macro expansion must expand to some (possibly empty) text."); - return it->getSecond(); + if (MacroExpansionLoc.isMacroID()) + return llvm::None; + + // If there was no macro expansion at that location, return None. + if (ExpansionRanges.find_as(MacroExpansionLoc) == ExpansionRanges.end()) + return llvm::None; + + // There was macro expansion, but resulted in no tokens, return empty string. + const auto It = ExpandedTokens.find_as(MacroExpansionLoc); + if (It == ExpandedTokens.end()) + return StringRef{""}; + + // Otherwise we have the actual token sequence as string. + return StringRef{It->getSecond()}; } -StringRef +Optional MacroExpansionContext::getOriginalText(SourceLocation MacroExpansionLoc) const { - assert(MacroExpansionLoc.isFileID() && - "It has a spelling location, use the expansion location instead."); + if (MacroExpansionLoc.isMacroID()) + return llvm::None; + + const auto It = ExpansionRanges.find_as(MacroExpansionLoc); + if (It == ExpansionRanges.end()) + return llvm::None; - const auto it = ExpansionRanges.find_as(MacroExpansionLoc); - if (it == ExpansionRanges.end()) - llvm_unreachable("Every macro expansion must have a range whose text will " - "be substituted."); + assert(It->getFirst() != It->getSecond() && + "Every macro expansion must cover a non-empty range."); return Lexer::getSourceText( - CharSourceRange::getCharRange(it->getFirst(), it->getSecond()), SM, + CharSourceRange::getCharRange(It->getFirst(), It->getSecond()), *SM, LangOpts); } @@ -134,9 +144,9 @@ OS << "\n=============== ExpansionRanges ===============\n"; for (const auto &Record : LocalExpansionRanges) { OS << "> "; - Record.first.print(OS, SM); + Record.first.print(OS, *SM); OS << ", "; - Record.second.print(OS, SM); + Record.second.print(OS, *SM); OS << '\n'; } } @@ -153,7 +163,7 @@ OS << "\n=============== ExpandedTokens ===============\n"; for (const auto &Record : LocalExpandedTokens) { OS << "> "; - Record.first.print(OS, SM); + Record.first.print(OS, *SM); OS << " -> '" << Record.second << "'\n"; } } @@ -186,11 +196,11 @@ return; LLVM_DEBUG(llvm::dbgs() << "lexed macro expansion token '"; - dumpTokenInto(PP, llvm::dbgs(), Tok); llvm::dbgs() << "' at "; - SLoc.print(llvm::dbgs(), SM); llvm::dbgs() << '\n';); + dumpTokenInto(*PP, llvm::dbgs(), Tok); llvm::dbgs() << "' at "; + SLoc.print(llvm::dbgs(), *SM); llvm::dbgs() << '\n';); // Remove spelling location. - SourceLocation CurrExpansionLoc = SM.getExpansionLoc(SLoc); + SourceLocation CurrExpansionLoc = SM->getExpansionLoc(SLoc); MacroExpansionText TokenAsString; llvm::raw_svector_ostream OS(TokenAsString); @@ -198,7 +208,7 @@ // FIXME: Prepend newlines and space to produce the exact same output as the // preprocessor would for this token. - dumpTokenInto(PP, OS, Tok); + dumpTokenInto(*PP, OS, Tok); ExpansionMap::iterator It; bool Inserted; @@ -206,4 +216,4 @@ ExpandedTokens.try_emplace(CurrExpansionLoc, std::move(TokenAsString)); if (!Inserted) It->getSecond().append(TokenAsString); -} \ No newline at end of file +} Index: clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -10,11 +10,12 @@ // //===----------------------------------------------------------------------===// -#include "clang/Analysis/IssueHash.h" -#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/Stmt.h" +#include "clang/Analysis/IssueHash.h" +#include "clang/Analysis/MacroExpansionContext.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" @@ -135,14 +136,16 @@ void ento::createHTMLDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputDir, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { // FIXME: HTML is currently our default output type, but if the output // directory isn't specified, it acts like if it was in the minimal text // output mode. This doesn't make much sense, we should have the minimal text // as our default. In the case of backward compatibility concerns, this could // be preserved with -analyzer-config-compatibility-mode=true. - createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU); + createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU, + MacroExpansions); // TODO: Emit an error here. if (OutputDir.empty()) @@ -154,8 +157,10 @@ void ento::createHTMLSingleFileDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputDir, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { - createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU); + const cross_tu::CrossTranslationUnitContext &CTU, + const clang::MacroExpansionContext &MacroExpansions) { + createTextMinimalPathDiagnosticConsumer(DiagOpts, C, OutputDir, PP, CTU, + MacroExpansions); // TODO: Emit an error here. if (OutputDir.empty()) @@ -167,13 +172,15 @@ void ento::createPlistHTMLDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &prefix, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { createHTMLDiagnosticConsumer( - DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, - CTU); - createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU); + DiagOpts, C, std::string(llvm::sys::path::parent_path(prefix)), PP, CTU, + MacroExpansions); + createPlistMultiFileDiagnosticConsumer(DiagOpts, C, prefix, PP, CTU, + MacroExpansions); createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, prefix, PP, - CTU); + CTU, MacroExpansions); } //===----------------------------------------------------------------------===// Index: clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/IssueHash.h" +#include "clang/Analysis/MacroExpansionContext.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/PlistSupport.h" @@ -43,6 +44,7 @@ const std::string OutputFile; const Preprocessor &PP; const cross_tu::CrossTranslationUnitContext &CTU; + const MacroExpansionContext &MacroExpansions; const bool SupportsCrossFileDiagnostics; void printBugPath(llvm::raw_ostream &o, const FIDMap &FM, @@ -52,6 +54,7 @@ PlistDiagnostics(PathDiagnosticConsumerOptions DiagOpts, const std::string &OutputFile, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions, bool supportsMultipleFiles); ~PlistDiagnostics() override {} @@ -80,14 +83,14 @@ const FIDMap& FM; const Preprocessor &PP; const cross_tu::CrossTranslationUnitContext &CTU; + const MacroExpansionContext &MacroExpansions; llvm::SmallVector MacroPieces; public: - PlistPrinter(const FIDMap& FM, - const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) - : FM(FM), PP(PP), CTU(CTU) { - } + PlistPrinter(const FIDMap &FM, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) + : FM(FM), PP(PP), CTU(CTU), MacroExpansions(MacroExpansions) {} void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) { ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true); @@ -522,8 +525,9 @@ PlistDiagnostics::PlistDiagnostics( PathDiagnosticConsumerOptions DiagOpts, const std::string &output, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &CTU, - bool supportsMultipleFiles) + const MacroExpansionContext &MacroExpansions, bool supportsMultipleFiles) : DiagOpts(std::move(DiagOpts)), OutputFile(output), PP(PP), CTU(CTU), + MacroExpansions(MacroExpansions), SupportsCrossFileDiagnostics(supportsMultipleFiles) { // FIXME: Will be used by a later planned change. (void)this->CTU; @@ -532,36 +536,40 @@ void ento::createPlistDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { // TODO: Emit an error here. if (OutputFile.empty()) return; C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, + MacroExpansions, /*supportsMultipleFiles=*/false)); createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, - PP, CTU); + PP, CTU, MacroExpansions); } void ento::createPlistMultiFileDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &OutputFile, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { // TODO: Emit an error here. if (OutputFile.empty()) return; C.push_back(new PlistDiagnostics(DiagOpts, OutputFile, PP, CTU, + MacroExpansions, /*supportsMultipleFiles=*/true)); createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, OutputFile, - PP, CTU); + PP, CTU, MacroExpansions); } void PlistDiagnostics::printBugPath(llvm::raw_ostream &o, const FIDMap &FM, const PathPieces &Path) { - PlistPrinter Printer(FM, PP, CTU); + PlistPrinter Printer(FM, PP, CTU, MacroExpansions); assert(std::is_partitioned(Path.begin(), Path.end(), [](const PathDiagnosticPieceRef &E) { return E->getKind() == PathDiagnosticPiece::Note; Index: clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/MacroExpansionContext.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/Version.h" @@ -48,7 +49,8 @@ void ento::createSarifDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, const std::string &Output, const Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { // TODO: Emit an error here. if (Output.empty()) @@ -56,7 +58,7 @@ C.push_back(new SarifDiagnostics(Output, PP.getLangOpts())); createTextMinimalPathDiagnosticConsumer(std::move(DiagOpts), C, Output, PP, - CTU); + CTU, MacroExpansions); } static StringRef getFileName(const FileEntry &FE) { Index: clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp +++ clang/lib/StaticAnalyzer/Core/TextDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/MacroExpansionContext.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" @@ -138,8 +139,9 @@ void ento::createTextPathDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &Prefix, const clang::Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const std::string &Prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), PP.getLangOpts(), /*ShouldDisplayPathNotes=*/true)); @@ -147,8 +149,9 @@ void ento::createTextMinimalPathDiagnosticConsumer( PathDiagnosticConsumerOptions DiagOpts, PathDiagnosticConsumers &C, - const std::string &Prefix, const clang::Preprocessor &PP, - const cross_tu::CrossTranslationUnitContext &CTU) { + const std::string &Prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU, + const MacroExpansionContext &MacroExpansions) { C.emplace_back(new TextDiagnostics(std::move(DiagOpts), PP.getDiagnostics(), PP.getLangOpts(), /*ShouldDisplayPathNotes=*/false)); Index: clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp =================================================================== --- clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -20,6 +20,7 @@ #include "clang/Analysis/CFG.h" #include "clang/Analysis/CallGraph.h" #include "clang/Analysis/CodeInjector.h" +#include "clang/Analysis/MacroExpansionContext.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/SourceManager.h" #include "clang/CrossTU/CrossTranslationUnit.h" @@ -98,6 +99,8 @@ /// working with a PCH file. SetOfDecls LocalTUDecls; + MacroExpansionContext MacroExpansions; + // Set of PathDiagnosticConsumers. Owned by AnalysisManager. PathDiagnosticConsumers PathConsumers; @@ -122,7 +125,8 @@ CodeInjector *injector) : RecVisitorMode(0), RecVisitorBR(nullptr), Ctx(nullptr), PP(CI.getPreprocessor()), OutDir(outdir), Opts(std::move(opts)), - Plugins(plugins), Injector(injector), CTU(CI) { + Plugins(plugins), Injector(injector), CTU(CI), + MacroExpansions(CI.getLangOpts()) { DigestAnalyzerOptions(); if (Opts->PrintStats || Opts->ShouldSerializeStats) { AnalyzerTimers = std::make_unique( @@ -136,6 +140,9 @@ *AnalyzerTimers); llvm::EnableStatistics(/* PrintOnExit= */ false); } + + if (Opts->ShouldDisplayMacroExpansions) + MacroExpansions.registerForPreprocessor(PP); } ~AnalysisConsumer() override { @@ -150,7 +157,8 @@ break; #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ case PD_##NAME: \ - CREATEFN(Opts->getDiagOpts(), PathConsumers, OutDir, PP, CTU); \ + CREATEFN(Opts->getDiagOpts(), PathConsumers, OutDir, PP, CTU, \ + MacroExpansions); \ break; #include "clang/StaticAnalyzer/Core/Analyses.def" default: Index: clang/unittests/Analysis/MacroExpansionContextTest.cpp =================================================================== --- clang/unittests/Analysis/MacroExpansionContextTest.cpp +++ clang/unittests/Analysis/MacroExpansionContextTest.cpp @@ -67,7 +67,8 @@ /*OwnsHeaderSearch =*/false); PP.Initialize(*Target); - auto Ctx = std::make_unique(PP, LangOpts); + auto Ctx = std::make_unique(LangOpts); + Ctx->registerForPreprocessor(PP); // Lex source text. PP.EnterMainSourceFile(); @@ -91,6 +92,46 @@ } }; +TEST_F(MacroExpansionContextTest, NoneForNonExpansionLocations) { + const auto Ctx = getMacroExpansionContextFor(R"code( + #define EMPTY + A b cd EMPTY ef EMPTY gh +EMPTY zz + )code"); + // After preprocessing: + // A b cd ef gh + // zz + + // That's the beginning of the definition of EMPTY. + EXPECT_FALSE(Ctx->getExpandedText(at(2, 11)).hasValue()); + EXPECT_FALSE(Ctx->getOriginalText(at(2, 11)).hasValue()); + + // The space before the first expansion of EMPTY. + EXPECT_FALSE(Ctx->getExpandedText(at(3, 9)).hasValue()); + EXPECT_FALSE(Ctx->getOriginalText(at(3, 9)).hasValue()); + + // The beginning of the first expansion of EMPTY. + EXPECT_TRUE(Ctx->getExpandedText(at(3, 10)).hasValue()); + EXPECT_TRUE(Ctx->getOriginalText(at(3, 10)).hasValue()); + + // Pointing inside of the token EMPTY, but not at the beginning. + // FIXME: We only deal with begin locations. + EXPECT_FALSE(Ctx->getExpandedText(at(3, 11)).hasValue()); + EXPECT_FALSE(Ctx->getOriginalText(at(3, 11)).hasValue()); + + // Same here. + EXPECT_FALSE(Ctx->getExpandedText(at(3, 12)).hasValue()); + EXPECT_FALSE(Ctx->getOriginalText(at(3, 12)).hasValue()); + + // The beginning of the last expansion of EMPTY. + EXPECT_TRUE(Ctx->getExpandedText(at(4, 1)).hasValue()); + EXPECT_TRUE(Ctx->getOriginalText(at(4, 1)).hasValue()); + + // Same as for the 3:11 case. + EXPECT_FALSE(Ctx->getExpandedText(at(4, 2)).hasValue()); + EXPECT_FALSE(Ctx->getOriginalText(at(4, 2)).hasValue()); +} + TEST_F(MacroExpansionContextTest, EmptyExpansions) { const auto Ctx = getMacroExpansionContextFor(R"code( #define EMPTY @@ -101,14 +142,14 @@ // A b cd ef gh // zz - EXPECT_EQ("", Ctx->getExpandedText(at(3, 10))); - EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(3, 10))); + EXPECT_EQ("", Ctx->getExpandedText(at(3, 10)).getValue()); + EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(3, 10)).getValue()); - EXPECT_EQ("", Ctx->getExpandedText(at(3, 19))); - EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(3, 19))); + EXPECT_EQ("", Ctx->getExpandedText(at(3, 19)).getValue()); + EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(3, 19)).getValue()); - EXPECT_EQ("", Ctx->getExpandedText(at(4, 1))); - EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(4, 1))); + EXPECT_EQ("", Ctx->getExpandedText(at(4, 1)).getValue()); + EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(4, 1)).getValue()); } TEST_F(MacroExpansionContextTest, TransitiveExpansions) { @@ -120,11 +161,10 @@ // After preprocessing: // A b cd ) 1 ef gh - EXPECT_EQ(")1", Ctx->getExpandedText(at(4, 10))); - EXPECT_EQ("WOOF", Ctx->getOriginalText(at(4, 10))); + EXPECT_EQ("WOOF", Ctx->getOriginalText(at(4, 10)).getValue()); - EXPECT_EQ("", Ctx->getExpandedText(at(4, 18))); - EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(4, 18))); + EXPECT_EQ("", Ctx->getExpandedText(at(4, 18)).getValue()); + EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(4, 18)).getValue()); } TEST_F(MacroExpansionContextTest, MacroFunctions) { @@ -140,17 +180,17 @@ // WOOF( ) ) ) 1 // bar barr( ) ) ) 1( ) ) ) 1),,),') - EXPECT_EQ("$$ ef ()))1", Ctx->getExpandedText(at(4, 10))); - EXPECT_EQ("WOOF($$ ef)", Ctx->getOriginalText(at(4, 10))); + EXPECT_EQ("$$ ef ()))1", Ctx->getExpandedText(at(4, 10)).getValue()); + EXPECT_EQ("WOOF($$ ef)", Ctx->getOriginalText(at(4, 10)).getValue()); - EXPECT_EQ("", Ctx->getExpandedText(at(4, 22))); - EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(4, 22))); + EXPECT_EQ("", Ctx->getExpandedText(at(4, 22)).getValue()); + EXPECT_EQ("EMPTY", Ctx->getOriginalText(at(4, 22)).getValue()); - EXPECT_EQ("WOOF ()))1", Ctx->getExpandedText(at(5, 3))); - EXPECT_EQ("WOOF(WOOF)", Ctx->getOriginalText(at(5, 3))); + EXPECT_EQ("WOOF ()))1", Ctx->getExpandedText(at(5, 3)).getValue()); + EXPECT_EQ("WOOF(WOOF)", Ctx->getOriginalText(at(5, 3)).getValue()); - EXPECT_EQ("bar barr ()))1()))1", Ctx->getExpandedText(at(6, 3))); - EXPECT_EQ("WOOF(WOOF(bar barr))", Ctx->getOriginalText(at(6, 3))); + EXPECT_EQ("bar barr ()))1()))1", Ctx->getExpandedText(at(6, 3)).getValue()); + EXPECT_EQ("WOOF(WOOF(bar barr))", Ctx->getOriginalText(at(6, 3)).getValue()); } TEST_F(MacroExpansionContextTest, VariadicMacros) { @@ -172,20 +212,24 @@ // fprintf (stderr, "success!\n" ); EXPECT_EQ(R"(fprintf (stderr ,"success!\n",))", - Ctx->getExpandedText(at(3, 3))); - EXPECT_EQ(R"(eprintf("success!\n", ))", Ctx->getOriginalText(at(3, 3))); + Ctx->getExpandedText(at(3, 3)).getValue()); + EXPECT_EQ(R"(eprintf("success!\n", ))", + Ctx->getOriginalText(at(3, 3)).getValue()); EXPECT_EQ(R"(fprintf (stderr ,"success!\n",))", - Ctx->getExpandedText(at(4, 3))); - EXPECT_EQ(R"(eprintf("success!\n"))", Ctx->getOriginalText(at(4, 3))); + Ctx->getExpandedText(at(4, 3)).getValue()); + EXPECT_EQ(R"(eprintf("success!\n"))", + Ctx->getOriginalText(at(4, 3)).getValue()); EXPECT_EQ(R"(fprintf (stderr ,"success!\n"))", - Ctx->getExpandedText(at(8, 3))); - EXPECT_EQ(R"(eprintf2("success!\n", ))", Ctx->getOriginalText(at(8, 3))); + Ctx->getExpandedText(at(8, 3)).getValue()); + EXPECT_EQ(R"(eprintf2("success!\n", ))", + Ctx->getOriginalText(at(8, 3)).getValue()); EXPECT_EQ(R"(fprintf (stderr ,"success!\n"))", - Ctx->getExpandedText(at(9, 3))); - EXPECT_EQ(R"(eprintf2("success!\n"))", Ctx->getOriginalText(at(9, 3))); + Ctx->getExpandedText(at(9, 3)).getValue()); + EXPECT_EQ(R"(eprintf2("success!\n"))", + Ctx->getOriginalText(at(9, 3)).getValue()); } TEST_F(MacroExpansionContextTest, ConcatenationMacros) { @@ -202,11 +246,13 @@ // { "help", help_command }, // }; - EXPECT_EQ(R"({"quit",quit_command })", Ctx->getExpandedText(at(4, 5))); - EXPECT_EQ("COMMAND(quit)", Ctx->getOriginalText(at(4, 5))); + EXPECT_EQ(R"({"quit",quit_command })", + Ctx->getExpandedText(at(4, 5)).getValue()); + EXPECT_EQ("COMMAND(quit)", Ctx->getOriginalText(at(4, 5)).getValue()); - EXPECT_EQ(R"({"help",help_command })", Ctx->getExpandedText(at(5, 5))); - EXPECT_EQ("COMMAND(help)", Ctx->getOriginalText(at(5, 5))); + EXPECT_EQ(R"({"help",help_command })", + Ctx->getExpandedText(at(5, 5)).getValue()); + EXPECT_EQ("COMMAND(help)", Ctx->getOriginalText(at(5, 5)).getValue()); } TEST_F(MacroExpansionContextTest, StringizingMacros) { @@ -231,14 +277,14 @@ EXPECT_EQ( R"(do {if (x ==0)fprintf (stderr ,"Warning: ""x == 0""\n");}while (0))", - Ctx->getExpandedText(at(6, 3))); - EXPECT_EQ("WARN_IF (x == 0)", Ctx->getOriginalText(at(6, 3))); + Ctx->getExpandedText(at(6, 3)).getValue()); + EXPECT_EQ("WARN_IF (x == 0)", Ctx->getOriginalText(at(6, 3)).getValue()); - EXPECT_EQ(R"("foo")", Ctx->getExpandedText(at(11, 3))); - EXPECT_EQ("str (foo)", Ctx->getOriginalText(at(11, 3))); + EXPECT_EQ(R"("foo")", Ctx->getExpandedText(at(11, 3)).getValue()); + EXPECT_EQ("str (foo)", Ctx->getOriginalText(at(11, 3)).getValue()); - EXPECT_EQ(R"("4")", Ctx->getExpandedText(at(12, 3))); - EXPECT_EQ("xstr (foo)", Ctx->getOriginalText(at(12, 3))); + EXPECT_EQ(R"("4")", Ctx->getExpandedText(at(12, 3)).getValue()); + EXPECT_EQ("xstr (foo)", Ctx->getOriginalText(at(12, 3)).getValue()); } TEST_F(MacroExpansionContextTest, StringizingVariadicMacros) { @@ -267,18 +313,18 @@ EXPECT_EQ("zz !apple !x *apple !x !**y (apple )zz !apple !x *apple !x !**y " "(appleapple ))))", - Ctx->getExpandedText(at(11, 3))); - EXPECT_EQ("q(g)", Ctx->getOriginalText(at(11, 3))); + Ctx->getExpandedText(at(11, 3)).getValue()); + EXPECT_EQ("q(g)", Ctx->getOriginalText(at(11, 3)).getValue()); EXPECT_EQ(R"res("apple"(apple )"apple"(appleapple )))))res", - Ctx->getExpandedText(at(12, 3))); - EXPECT_EQ("q(xstr)", Ctx->getOriginalText(at(12, 3))); + Ctx->getExpandedText(at(12, 3)).getValue()); + EXPECT_EQ("q(xstr)", Ctx->getOriginalText(at(12, 3)).getValue()); - EXPECT_EQ("zz !*)!x )!**y ", Ctx->getExpandedText(at(13, 3))); - EXPECT_EQ("g(RParen2x)", Ctx->getOriginalText(at(13, 3))); + EXPECT_EQ("zz !*)!x )!**y ", Ctx->getExpandedText(at(13, 3)).getValue()); + EXPECT_EQ("g(RParen2x)", Ctx->getOriginalText(at(13, 3)).getValue()); - EXPECT_EQ("!))*))", Ctx->getExpandedText(at(14, 3))); - EXPECT_EQ("f( RParen2x )", Ctx->getOriginalText(at(14, 3))); + EXPECT_EQ("!))*))", Ctx->getExpandedText(at(14, 3)).getValue()); + EXPECT_EQ("f( RParen2x )", Ctx->getOriginalText(at(14, 3)).getValue()); } TEST_F(MacroExpansionContextTest, RedefUndef) { @@ -296,15 +342,15 @@ // Hi(Hi) // FIXME: Extra space follows every identifier. - EXPECT_EQ("Welcome Adam ", Ctx->getExpandedText(at(3, 3))); - EXPECT_EQ("Hi(Adam)", Ctx->getOriginalText(at(3, 3))); + EXPECT_EQ("Welcome Adam ", Ctx->getExpandedText(at(3, 3)).getValue()); + EXPECT_EQ("Hi(Adam)", Ctx->getOriginalText(at(3, 3)).getValue()); - EXPECT_EQ("Willkommen ", Ctx->getExpandedText(at(5, 3))); - EXPECT_EQ("Hi", Ctx->getOriginalText(at(5, 3))); + EXPECT_EQ("Willkommen ", Ctx->getExpandedText(at(5, 3)).getValue()); + EXPECT_EQ("Hi", Ctx->getOriginalText(at(5, 3)).getValue()); - // There was no macro expansion at 7:3, empty returned in that case. - EXPECT_EQ("", Ctx->getExpandedText(at(7, 3))); - EXPECT_EQ("", Ctx->getOriginalText(at(7, 3))); + // There was no macro expansion at 7:3, we should expect None. + EXPECT_FALSE(Ctx->getExpandedText(at(7, 3)).hasValue()); + EXPECT_FALSE(Ctx->getOriginalText(at(7, 3)).hasValue()); } TEST_F(MacroExpansionContextTest, UnbalacedParenthesis) { @@ -326,11 +372,12 @@ // fun(); // int x = ((1, fun(), 1, fun(), 1 )); - EXPECT_EQ("fun ()", Ctx->getExpandedText(at(8, 3))); - EXPECT_EQ("applyInt )", Ctx->getOriginalText(at(8, 3))); + EXPECT_EQ("fun ()", Ctx->getExpandedText(at(8, 3)).getValue()); + EXPECT_EQ("applyInt )", Ctx->getOriginalText(at(8, 3)).getValue()); - EXPECT_EQ("((1,fun (),1,fun (),1", Ctx->getExpandedText(at(13, 12))); - EXPECT_EQ("f(f(1))", Ctx->getOriginalText(at(13, 12))); + EXPECT_EQ("((1,fun (),1,fun (),1", + Ctx->getExpandedText(at(13, 12)).getValue()); + EXPECT_EQ("f(f(1))", Ctx->getOriginalText(at(13, 12)).getValue()); } } // namespace