232 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C++
		
	
	
	
//===- MacroExpansionContext.cpp - Macro expansion information --*- C++ -*-===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "clang/Analysis/MacroExpansionContext.h"
 | 
						|
#include "llvm/Support/Debug.h"
 | 
						|
 | 
						|
#define DEBUG_TYPE "macro-expansion-context"
 | 
						|
 | 
						|
static void dumpTokenInto(const clang::Preprocessor &PP, clang::raw_ostream &OS,
 | 
						|
                          clang::Token Tok);
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace detail {
 | 
						|
class MacroExpansionRangeRecorder : public PPCallbacks {
 | 
						|
  const Preprocessor &PP;
 | 
						|
  SourceManager &SM;
 | 
						|
  MacroExpansionContext::ExpansionRangeMap &ExpansionRanges;
 | 
						|
 | 
						|
public:
 | 
						|
  explicit MacroExpansionRangeRecorder(
 | 
						|
      const Preprocessor &PP, SourceManager &SM,
 | 
						|
      MacroExpansionContext::ExpansionRangeMap &ExpansionRanges)
 | 
						|
      : PP(PP), SM(SM), ExpansionRanges(ExpansionRanges) {}
 | 
						|
 | 
						|
  void MacroExpands(const Token &MacroName, const MacroDefinition &MD,
 | 
						|
                    SourceRange Range, const MacroArgs *Args) override {
 | 
						|
    // Ignore annotation tokens like: _Pragma("pack(push, 1)")
 | 
						|
    if (MacroName.getIdentifierInfo()->getName() == "_Pragma")
 | 
						|
      return;
 | 
						|
 | 
						|
    SourceLocation MacroNameBegin = SM.getExpansionLoc(MacroName.getLocation());
 | 
						|
    assert(MacroNameBegin == SM.getExpansionLoc(Range.getBegin()));
 | 
						|
 | 
						|
    const SourceLocation ExpansionEnd = [Range, &SM = SM, &MacroName] {
 | 
						|
      // If the range is empty, use the length of the macro.
 | 
						|
      if (Range.getBegin() == Range.getEnd())
 | 
						|
        return SM.getExpansionLoc(
 | 
						|
            MacroName.getLocation().getLocWithOffset(MacroName.getLength()));
 | 
						|
 | 
						|
      // Include the last character.
 | 
						|
      return SM.getExpansionLoc(Range.getEnd()).getLocWithOffset(1);
 | 
						|
    }();
 | 
						|
 | 
						|
    (void)PP;
 | 
						|
    LLVM_DEBUG(llvm::dbgs() << "MacroExpands event: '";
 | 
						|
               dumpTokenInto(PP, llvm::dbgs(), MacroName);
 | 
						|
               llvm::dbgs()
 | 
						|
               << "' with length " << MacroName.getLength() << " at ";
 | 
						|
               MacroNameBegin.print(llvm::dbgs(), SM);
 | 
						|
               llvm::dbgs() << ", expansion end at ";
 | 
						|
               ExpansionEnd.print(llvm::dbgs(), SM); llvm::dbgs() << '\n';);
 | 
						|
 | 
						|
    // If the expansion range is empty, use the identifier of the macro as a
 | 
						|
    // range.
 | 
						|
    MacroExpansionContext::ExpansionRangeMap::iterator It;
 | 
						|
    bool Inserted;
 | 
						|
    std::tie(It, Inserted) =
 | 
						|
        ExpansionRanges.try_emplace(MacroNameBegin, ExpansionEnd);
 | 
						|
    if (Inserted) {
 | 
						|
      LLVM_DEBUG(llvm::dbgs() << "maps ";
 | 
						|
                 It->getFirst().print(llvm::dbgs(), SM); llvm::dbgs() << " to ";
 | 
						|
                 It->getSecond().print(llvm::dbgs(), SM);
 | 
						|
                 llvm::dbgs() << '\n';);
 | 
						|
    } else {
 | 
						|
      if (SM.isBeforeInTranslationUnit(It->getSecond(), ExpansionEnd)) {
 | 
						|
        It->getSecond() = ExpansionEnd;
 | 
						|
        LLVM_DEBUG(
 | 
						|
            llvm::dbgs() << "remaps "; It->getFirst().print(llvm::dbgs(), SM);
 | 
						|
            llvm::dbgs() << " to "; It->getSecond().print(llvm::dbgs(), SM);
 | 
						|
            llvm::dbgs() << '\n';);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 | 
						|
} // namespace detail
 | 
						|
} // namespace clang
 | 
						|
 | 
						|
using namespace clang;
 | 
						|
 | 
						|
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<detail::MacroExpansionRangeRecorder>(
 | 
						|
      *PP, *SM, ExpansionRanges));
 | 
						|
  // Same applies here.
 | 
						|
  PP->setTokenWatcher([this](const Token &Tok) { onTokenLexed(Tok); });
 | 
						|
}
 | 
						|
 | 
						|
Optional<StringRef>
 | 
						|
MacroExpansionContext::getExpandedText(SourceLocation MacroExpansionLoc) const {
 | 
						|
  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 It->getSecond().str();
 | 
						|
}
 | 
						|
 | 
						|
Optional<StringRef>
 | 
						|
MacroExpansionContext::getOriginalText(SourceLocation MacroExpansionLoc) const {
 | 
						|
  if (MacroExpansionLoc.isMacroID())
 | 
						|
    return llvm::None;
 | 
						|
 | 
						|
  const auto It = ExpansionRanges.find_as(MacroExpansionLoc);
 | 
						|
  if (It == ExpansionRanges.end())
 | 
						|
    return llvm::None;
 | 
						|
 | 
						|
  assert(It->getFirst() != It->getSecond() &&
 | 
						|
         "Every macro expansion must cover a non-empty range.");
 | 
						|
 | 
						|
  return Lexer::getSourceText(
 | 
						|
      CharSourceRange::getCharRange(It->getFirst(), It->getSecond()), *SM,
 | 
						|
      LangOpts);
 | 
						|
}
 | 
						|
 | 
						|
void MacroExpansionContext::dumpExpansionRanges() const {
 | 
						|
  dumpExpansionRangesToStream(llvm::dbgs());
 | 
						|
}
 | 
						|
void MacroExpansionContext::dumpExpandedTexts() const {
 | 
						|
  dumpExpandedTextsToStream(llvm::dbgs());
 | 
						|
}
 | 
						|
 | 
						|
void MacroExpansionContext::dumpExpansionRangesToStream(raw_ostream &OS) const {
 | 
						|
  std::vector<std::pair<SourceLocation, SourceLocation>> LocalExpansionRanges;
 | 
						|
  LocalExpansionRanges.reserve(ExpansionRanges.size());
 | 
						|
  for (const auto &Record : ExpansionRanges)
 | 
						|
    LocalExpansionRanges.emplace_back(
 | 
						|
        std::make_pair(Record.getFirst(), Record.getSecond()));
 | 
						|
  llvm::sort(LocalExpansionRanges);
 | 
						|
 | 
						|
  OS << "\n=============== ExpansionRanges ===============\n";
 | 
						|
  for (const auto &Record : LocalExpansionRanges) {
 | 
						|
    OS << "> ";
 | 
						|
    Record.first.print(OS, *SM);
 | 
						|
    OS << ", ";
 | 
						|
    Record.second.print(OS, *SM);
 | 
						|
    OS << '\n';
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void MacroExpansionContext::dumpExpandedTextsToStream(raw_ostream &OS) const {
 | 
						|
  std::vector<std::pair<SourceLocation, MacroExpansionText>>
 | 
						|
      LocalExpandedTokens;
 | 
						|
  LocalExpandedTokens.reserve(ExpandedTokens.size());
 | 
						|
  for (const auto &Record : ExpandedTokens)
 | 
						|
    LocalExpandedTokens.emplace_back(
 | 
						|
        std::make_pair(Record.getFirst(), Record.getSecond()));
 | 
						|
  llvm::sort(LocalExpandedTokens);
 | 
						|
 | 
						|
  OS << "\n=============== ExpandedTokens ===============\n";
 | 
						|
  for (const auto &Record : LocalExpandedTokens) {
 | 
						|
    OS << "> ";
 | 
						|
    Record.first.print(OS, *SM);
 | 
						|
    OS << " -> '" << Record.second << "'\n";
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void dumpTokenInto(const Preprocessor &PP, raw_ostream &OS, Token Tok) {
 | 
						|
  assert(Tok.isNot(tok::raw_identifier));
 | 
						|
 | 
						|
  // Ignore annotation tokens like: _Pragma("pack(push, 1)")
 | 
						|
  if (Tok.isAnnotation())
 | 
						|
    return;
 | 
						|
 | 
						|
  if (IdentifierInfo *II = Tok.getIdentifierInfo()) {
 | 
						|
    // FIXME: For now, we don't respect whitespaces between macro expanded
 | 
						|
    // tokens. We just emit a space after every identifier to produce a valid
 | 
						|
    // code for `int a ;` like expansions.
 | 
						|
    //              ^-^-- Space after the 'int' and 'a' identifiers.
 | 
						|
    OS << II->getName() << ' ';
 | 
						|
  } else if (Tok.isLiteral() && !Tok.needsCleaning() && Tok.getLiteralData()) {
 | 
						|
    OS << StringRef(Tok.getLiteralData(), Tok.getLength());
 | 
						|
  } else {
 | 
						|
    char Tmp[256];
 | 
						|
    if (Tok.getLength() < sizeof(Tmp)) {
 | 
						|
      const char *TokPtr = Tmp;
 | 
						|
      // FIXME: Might use a different overload for cleaner callsite.
 | 
						|
      unsigned Len = PP.getSpelling(Tok, TokPtr);
 | 
						|
      OS.write(TokPtr, Len);
 | 
						|
    } else {
 | 
						|
      OS << "<too long token>";
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void MacroExpansionContext::onTokenLexed(const Token &Tok) {
 | 
						|
  SourceLocation SLoc = Tok.getLocation();
 | 
						|
  if (SLoc.isFileID())
 | 
						|
    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';);
 | 
						|
 | 
						|
  // Remove spelling location.
 | 
						|
  SourceLocation CurrExpansionLoc = SM->getExpansionLoc(SLoc);
 | 
						|
 | 
						|
  MacroExpansionText TokenAsString;
 | 
						|
  llvm::raw_svector_ostream OS(TokenAsString);
 | 
						|
 | 
						|
  // FIXME: Prepend newlines and space to produce the exact same output as the
 | 
						|
  // preprocessor would for this token.
 | 
						|
 | 
						|
  dumpTokenInto(*PP, OS, Tok);
 | 
						|
 | 
						|
  ExpansionMap::iterator It;
 | 
						|
  bool Inserted;
 | 
						|
  std::tie(It, Inserted) =
 | 
						|
      ExpandedTokens.try_emplace(CurrExpansionLoc, std::move(TokenAsString));
 | 
						|
  if (!Inserted)
 | 
						|
    It->getSecond().append(TokenAsString);
 | 
						|
}
 | 
						|
 |