diff options
Diffstat (limited to 'lib/Rewrite/Core/Rewriter.cpp')
| -rw-r--r-- | lib/Rewrite/Core/Rewriter.cpp | 486 | 
1 files changed, 486 insertions, 0 deletions
| diff --git a/lib/Rewrite/Core/Rewriter.cpp b/lib/Rewrite/Core/Rewriter.cpp new file mode 100644 index 000000000000..4df967f39bc0 --- /dev/null +++ b/lib/Rewrite/Core/Rewriter.cpp @@ -0,0 +1,486 @@ +//===--- Rewriter.cpp - Code rewriting interface --------------------------===// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +//  This file defines the Rewriter class, which is used for code +//  transformations. +// +//===----------------------------------------------------------------------===// + +#include "clang/Rewrite/Core/Rewriter.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/Decl.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/FileSystem.h" +using namespace clang; + +raw_ostream &RewriteBuffer::write(raw_ostream &os) const { +  // FIXME: eliminate the copy by writing out each chunk at a time +  os << std::string(begin(), end()); +  return os; +} + +/// \brief Return true if this character is non-new-line whitespace: +/// ' ', '\\t', '\\f', '\\v', '\\r'. +static inline bool isWhitespace(unsigned char c) { +  switch (c) { +  case ' ': +  case '\t': +  case '\f': +  case '\v': +  case '\r': +    return true; +  default: +    return false; +  } +} + +void RewriteBuffer::RemoveText(unsigned OrigOffset, unsigned Size, +                               bool removeLineIfEmpty) { +  // Nothing to remove, exit early. +  if (Size == 0) return; + +  unsigned RealOffset = getMappedOffset(OrigOffset, true); +  assert(RealOffset+Size < Buffer.size() && "Invalid location"); + +  // Remove the dead characters. +  Buffer.erase(RealOffset, Size); + +  // Add a delta so that future changes are offset correctly. +  AddReplaceDelta(OrigOffset, -Size); + +  if (removeLineIfEmpty) { +    // Find the line that the remove occurred and if it is completely empty +    // remove the line as well. + +    iterator curLineStart = begin(); +    unsigned curLineStartOffs = 0; +    iterator posI = begin(); +    for (unsigned i = 0; i != RealOffset; ++i) { +      if (*posI == '\n') { +        curLineStart = posI; +        ++curLineStart; +        curLineStartOffs = i + 1; +      } +      ++posI; +    } +   +    unsigned lineSize = 0; +    posI = curLineStart; +    while (posI != end() && isWhitespace(*posI)) { +      ++posI; +      ++lineSize; +    } +    if (posI != end() && *posI == '\n') { +      Buffer.erase(curLineStartOffs, lineSize + 1/* + '\n'*/); +      AddReplaceDelta(curLineStartOffs, -(lineSize + 1/* + '\n'*/)); +    } +  } +} + +void RewriteBuffer::InsertText(unsigned OrigOffset, StringRef Str, +                               bool InsertAfter) { + +  // Nothing to insert, exit early. +  if (Str.empty()) return; + +  unsigned RealOffset = getMappedOffset(OrigOffset, InsertAfter); +  Buffer.insert(RealOffset, Str.begin(), Str.end()); + +  // Add a delta so that future changes are offset correctly. +  AddInsertDelta(OrigOffset, Str.size()); +} + +/// ReplaceText - This method replaces a range of characters in the input +/// buffer with a new string.  This is effectively a combined "remove+insert" +/// operation. +void RewriteBuffer::ReplaceText(unsigned OrigOffset, unsigned OrigLength, +                                StringRef NewStr) { +  unsigned RealOffset = getMappedOffset(OrigOffset, true); +  Buffer.erase(RealOffset, OrigLength); +  Buffer.insert(RealOffset, NewStr.begin(), NewStr.end()); +  if (OrigLength != NewStr.size()) +    AddReplaceDelta(OrigOffset, NewStr.size() - OrigLength); +} + + +//===----------------------------------------------------------------------===// +// Rewriter class +//===----------------------------------------------------------------------===// + +/// getRangeSize - Return the size in bytes of the specified range if they +/// are in the same file.  If not, this returns -1. +int Rewriter::getRangeSize(const CharSourceRange &Range, +                           RewriteOptions opts) const { +  if (!isRewritable(Range.getBegin()) || +      !isRewritable(Range.getEnd())) return -1; + +  FileID StartFileID, EndFileID; +  unsigned StartOff, EndOff; + +  StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID); +  EndOff   = getLocationOffsetAndFileID(Range.getEnd(), EndFileID); + +  if (StartFileID != EndFileID) +    return -1; + +  // If edits have been made to this buffer, the delta between the range may +  // have changed. +  std::map<FileID, RewriteBuffer>::const_iterator I = +    RewriteBuffers.find(StartFileID); +  if (I != RewriteBuffers.end()) { +    const RewriteBuffer &RB = I->second; +    EndOff = RB.getMappedOffset(EndOff, opts.IncludeInsertsAtEndOfRange); +    StartOff = RB.getMappedOffset(StartOff, !opts.IncludeInsertsAtBeginOfRange); +  } + + +  // Adjust the end offset to the end of the last token, instead of being the +  // start of the last token if this is a token range. +  if (Range.isTokenRange()) +    EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); + +  return EndOff-StartOff; +} + +int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const { +  return getRangeSize(CharSourceRange::getTokenRange(Range), opts); +} + + +/// getRewrittenText - Return the rewritten form of the text in the specified +/// range.  If the start or end of the range was unrewritable or if they are +/// in different buffers, this returns an empty string. +/// +/// Note that this method is not particularly efficient. +/// +std::string Rewriter::getRewrittenText(SourceRange Range) const { +  if (!isRewritable(Range.getBegin()) || +      !isRewritable(Range.getEnd())) +    return ""; + +  FileID StartFileID, EndFileID; +  unsigned StartOff, EndOff; +  StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID); +  EndOff   = getLocationOffsetAndFileID(Range.getEnd(), EndFileID); + +  if (StartFileID != EndFileID) +    return ""; // Start and end in different buffers. + +  // If edits have been made to this buffer, the delta between the range may +  // have changed. +  std::map<FileID, RewriteBuffer>::const_iterator I = +    RewriteBuffers.find(StartFileID); +  if (I == RewriteBuffers.end()) { +    // If the buffer hasn't been rewritten, just return the text from the input. +    const char *Ptr = SourceMgr->getCharacterData(Range.getBegin()); + +    // Adjust the end offset to the end of the last token, instead of being the +    // start of the last token. +    EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); +    return std::string(Ptr, Ptr+EndOff-StartOff); +  } + +  const RewriteBuffer &RB = I->second; +  EndOff = RB.getMappedOffset(EndOff, true); +  StartOff = RB.getMappedOffset(StartOff); + +  // Adjust the end offset to the end of the last token, instead of being the +  // start of the last token. +  EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); + +  // Advance the iterators to the right spot, yay for linear time algorithms. +  RewriteBuffer::iterator Start = RB.begin(); +  std::advance(Start, StartOff); +  RewriteBuffer::iterator End = Start; +  std::advance(End, EndOff-StartOff); + +  return std::string(Start, End); +} + +unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc, +                                              FileID &FID) const { +  assert(Loc.isValid() && "Invalid location"); +  std::pair<FileID,unsigned> V = SourceMgr->getDecomposedLoc(Loc); +  FID = V.first; +  return V.second; +} + + +/// getEditBuffer - Get or create a RewriteBuffer for the specified FileID. +/// +RewriteBuffer &Rewriter::getEditBuffer(FileID FID) { +  std::map<FileID, RewriteBuffer>::iterator I = +    RewriteBuffers.lower_bound(FID); +  if (I != RewriteBuffers.end() && I->first == FID) +    return I->second; +  I = RewriteBuffers.insert(I, std::make_pair(FID, RewriteBuffer())); + +  StringRef MB = SourceMgr->getBufferData(FID); +  I->second.Initialize(MB.begin(), MB.end()); + +  return I->second; +} + +/// InsertText - Insert the specified string at the specified location in the +/// original buffer. +bool Rewriter::InsertText(SourceLocation Loc, StringRef Str, +                          bool InsertAfter, bool indentNewLines) { +  if (!isRewritable(Loc)) return true; +  FileID FID; +  unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID); + +  SmallString<128> indentedStr; +  if (indentNewLines && Str.find('\n') != StringRef::npos) { +    StringRef MB = SourceMgr->getBufferData(FID); + +    unsigned lineNo = SourceMgr->getLineNumber(FID, StartOffs) - 1; +    const SrcMgr::ContentCache * +        Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache(); +    unsigned lineOffs = Content->SourceLineCache[lineNo]; + +    // Find the whitespace at the start of the line. +    StringRef indentSpace; +    { +      unsigned i = lineOffs; +      while (isWhitespace(MB[i])) +        ++i; +      indentSpace = MB.substr(lineOffs, i-lineOffs); +    } + +    SmallVector<StringRef, 4> lines; +    Str.split(lines, "\n"); + +    for (unsigned i = 0, e = lines.size(); i != e; ++i) { +      indentedStr += lines[i]; +      if (i < e-1) { +        indentedStr += '\n'; +        indentedStr += indentSpace; +      } +    } +    Str = indentedStr.str(); +  } + +  getEditBuffer(FID).InsertText(StartOffs, Str, InsertAfter); +  return false; +} + +bool Rewriter::InsertTextAfterToken(SourceLocation Loc, StringRef Str) { +  if (!isRewritable(Loc)) return true; +  FileID FID; +  unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID); +  RewriteOptions rangeOpts; +  rangeOpts.IncludeInsertsAtBeginOfRange = false; +  StartOffs += getRangeSize(SourceRange(Loc, Loc), rangeOpts); +  getEditBuffer(FID).InsertText(StartOffs, Str, /*InsertAfter*/true); +  return false; +} + +/// RemoveText - Remove the specified text region. +bool Rewriter::RemoveText(SourceLocation Start, unsigned Length, +                          RewriteOptions opts) { +  if (!isRewritable(Start)) return true; +  FileID FID; +  unsigned StartOffs = getLocationOffsetAndFileID(Start, FID); +  getEditBuffer(FID).RemoveText(StartOffs, Length, opts.RemoveLineIfEmpty); +  return false; +} + +/// ReplaceText - This method replaces a range of characters in the input +/// buffer with a new string.  This is effectively a combined "remove/insert" +/// operation. +bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength, +                           StringRef NewStr) { +  if (!isRewritable(Start)) return true; +  FileID StartFileID; +  unsigned StartOffs = getLocationOffsetAndFileID(Start, StartFileID); + +  getEditBuffer(StartFileID).ReplaceText(StartOffs, OrigLength, NewStr); +  return false; +} + +bool Rewriter::ReplaceText(SourceRange range, SourceRange replacementRange) { +  if (!isRewritable(range.getBegin())) return true; +  if (!isRewritable(range.getEnd())) return true; +  if (replacementRange.isInvalid()) return true; +  SourceLocation start = range.getBegin(); +  unsigned origLength = getRangeSize(range); +  unsigned newLength = getRangeSize(replacementRange); +  FileID FID; +  unsigned newOffs = getLocationOffsetAndFileID(replacementRange.getBegin(), +                                                FID); +  StringRef MB = SourceMgr->getBufferData(FID); +  return ReplaceText(start, origLength, MB.substr(newOffs, newLength)); +} + +/// ReplaceStmt - This replaces a Stmt/Expr with another, using the pretty +/// printer to generate the replacement code.  This returns true if the input +/// could not be rewritten, or false if successful. +bool Rewriter::ReplaceStmt(Stmt *From, Stmt *To) { +  // Measaure the old text. +  int Size = getRangeSize(From->getSourceRange()); +  if (Size == -1) +    return true; + +  // Get the new text. +  std::string SStr; +  llvm::raw_string_ostream S(SStr); +  To->printPretty(S, 0, PrintingPolicy(*LangOpts)); +  const std::string &Str = S.str(); + +  ReplaceText(From->getLocStart(), Size, Str); +  return false; +} + +std::string Rewriter::ConvertToString(Stmt *From) { +  std::string SStr; +  llvm::raw_string_ostream S(SStr); +  From->printPretty(S, 0, PrintingPolicy(*LangOpts)); +  return S.str(); +} + +bool Rewriter::IncreaseIndentation(CharSourceRange range, +                                   SourceLocation parentIndent) { +  if (range.isInvalid()) return true; +  if (!isRewritable(range.getBegin())) return true; +  if (!isRewritable(range.getEnd())) return true; +  if (!isRewritable(parentIndent)) return true; + +  FileID StartFileID, EndFileID, parentFileID; +  unsigned StartOff, EndOff, parentOff; + +  StartOff = getLocationOffsetAndFileID(range.getBegin(), StartFileID); +  EndOff   = getLocationOffsetAndFileID(range.getEnd(), EndFileID); +  parentOff = getLocationOffsetAndFileID(parentIndent, parentFileID); + +  if (StartFileID != EndFileID || StartFileID != parentFileID) +    return true; +  if (StartOff > EndOff) +    return true; + +  FileID FID = StartFileID; +  StringRef MB = SourceMgr->getBufferData(FID); + +  unsigned parentLineNo = SourceMgr->getLineNumber(FID, parentOff) - 1; +  unsigned startLineNo = SourceMgr->getLineNumber(FID, StartOff) - 1; +  unsigned endLineNo = SourceMgr->getLineNumber(FID, EndOff) - 1; +   +  const SrcMgr::ContentCache * +      Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache(); +   +  // Find where the lines start. +  unsigned parentLineOffs = Content->SourceLineCache[parentLineNo]; +  unsigned startLineOffs = Content->SourceLineCache[startLineNo]; + +  // Find the whitespace at the start of each line. +  StringRef parentSpace, startSpace; +  { +    unsigned i = parentLineOffs; +    while (isWhitespace(MB[i])) +      ++i; +    parentSpace = MB.substr(parentLineOffs, i-parentLineOffs); + +    i = startLineOffs; +    while (isWhitespace(MB[i])) +      ++i; +    startSpace = MB.substr(startLineOffs, i-startLineOffs); +  } +  if (parentSpace.size() >= startSpace.size()) +    return true; +  if (!startSpace.startswith(parentSpace)) +    return true; + +  StringRef indent = startSpace.substr(parentSpace.size()); + +  // Indent the lines between start/end offsets. +  RewriteBuffer &RB = getEditBuffer(FID); +  for (unsigned lineNo = startLineNo; lineNo <= endLineNo; ++lineNo) { +    unsigned offs = Content->SourceLineCache[lineNo]; +    unsigned i = offs; +    while (isWhitespace(MB[i])) +      ++i; +    StringRef origIndent = MB.substr(offs, i-offs); +    if (origIndent.startswith(startSpace)) +      RB.InsertText(offs, indent, /*InsertAfter=*/false); +  } + +  return false; +} + +// A wrapper for a file stream that atomically overwrites the target. +// +// Creates a file output stream for a temporary file in the constructor, +// which is later accessible via getStream() if ok() return true. +// Flushes the stream and moves the temporary file to the target location +// in the destructor. +class AtomicallyMovedFile { +public: +  AtomicallyMovedFile(DiagnosticsEngine &Diagnostics, StringRef Filename, +                      bool &AllWritten) +    : Diagnostics(Diagnostics), Filename(Filename), AllWritten(AllWritten) { +    TempFilename = Filename; +    TempFilename += "-%%%%%%%%"; +    int FD; +    if (llvm::sys::fs::unique_file(TempFilename.str(), FD, TempFilename, +                                    /*makeAbsolute=*/true, 0664)) { +      AllWritten = false; +      Diagnostics.Report(clang::diag::err_unable_to_make_temp) +        << TempFilename; +    } else { +      FileStream.reset(new llvm::raw_fd_ostream(FD, /*shouldClose=*/true)); +    } +  } + +  ~AtomicallyMovedFile() { +    if (!ok()) return; + +    FileStream->flush(); +#ifdef _WIN32 +    // Win32 does not allow rename/removing opened files. +    FileStream.reset(); +#endif +    if (llvm::error_code ec = +          llvm::sys::fs::rename(TempFilename.str(), Filename)) { +      AllWritten = false; +      Diagnostics.Report(clang::diag::err_unable_to_rename_temp) +        << TempFilename << Filename << ec.message(); +      bool existed; +      // If the remove fails, there's not a lot we can do - this is already an +      // error. +      llvm::sys::fs::remove(TempFilename.str(), existed); +    } +  } + +  bool ok() { return FileStream; } +  llvm::raw_ostream &getStream() { return *FileStream; } + +private: +  DiagnosticsEngine &Diagnostics; +  StringRef Filename; +  SmallString<128> TempFilename; +  OwningPtr<llvm::raw_fd_ostream> FileStream; +  bool &AllWritten; +}; + +bool Rewriter::overwriteChangedFiles() { +  bool AllWritten = true; +  for (buffer_iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) { +    const FileEntry *Entry = +        getSourceMgr().getFileEntryForID(I->first); +    AtomicallyMovedFile File(getSourceMgr().getDiagnostics(), Entry->getName(), +                             AllWritten); +    if (File.ok()) { +      I->second.write(File.getStream()); +    } +  } +  return !AllWritten; +} | 
