481 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			481 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
//===- EditedSource.cpp - Collection of source edits ----------------------===//
 | 
						|
//
 | 
						|
// 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/Edit/EditedSource.h"
 | 
						|
#include "clang/Basic/CharInfo.h"
 | 
						|
#include "clang/Basic/LLVM.h"
 | 
						|
#include "clang/Basic/SourceLocation.h"
 | 
						|
#include "clang/Basic/SourceManager.h"
 | 
						|
#include "clang/Edit/Commit.h"
 | 
						|
#include "clang/Edit/EditsReceiver.h"
 | 
						|
#include "clang/Edit/FileOffset.h"
 | 
						|
#include "clang/Lex/Lexer.h"
 | 
						|
#include "llvm/ADT/STLExtras.h"
 | 
						|
#include "llvm/ADT/SmallString.h"
 | 
						|
#include "llvm/ADT/StringRef.h"
 | 
						|
#include "llvm/ADT/Twine.h"
 | 
						|
#include <algorithm>
 | 
						|
#include <cassert>
 | 
						|
#include <tuple>
 | 
						|
#include <utility>
 | 
						|
 | 
						|
using namespace clang;
 | 
						|
using namespace edit;
 | 
						|
 | 
						|
void EditsReceiver::remove(CharSourceRange range) {
 | 
						|
  replace(range, StringRef());
 | 
						|
}
 | 
						|
 | 
						|
void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
 | 
						|
                                          SourceLocation &ExpansionLoc,
 | 
						|
                                          MacroArgUse &ArgUse) {
 | 
						|
  assert(SourceMgr.isMacroArgExpansion(Loc));
 | 
						|
  SourceLocation DefArgLoc =
 | 
						|
      SourceMgr.getImmediateExpansionRange(Loc).getBegin();
 | 
						|
  SourceLocation ImmediateExpansionLoc =
 | 
						|
      SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();
 | 
						|
  ExpansionLoc = ImmediateExpansionLoc;
 | 
						|
  while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
 | 
						|
    ExpansionLoc =
 | 
						|
        SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
 | 
						|
  SmallString<20> Buf;
 | 
						|
  StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
 | 
						|
                                         Buf, SourceMgr, LangOpts);
 | 
						|
  ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
 | 
						|
  if (!ArgName.empty())
 | 
						|
    ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
 | 
						|
              SourceMgr.getSpellingLoc(DefArgLoc)};
 | 
						|
}
 | 
						|
 | 
						|
void EditedSource::startingCommit() {}
 | 
						|
 | 
						|
void EditedSource::finishedCommit() {
 | 
						|
  for (auto &ExpArg : CurrCommitMacroArgExps) {
 | 
						|
    SourceLocation ExpLoc;
 | 
						|
    MacroArgUse ArgUse;
 | 
						|
    std::tie(ExpLoc, ArgUse) = ExpArg;
 | 
						|
    auto &ArgUses = ExpansionToArgMap[ExpLoc];
 | 
						|
    if (!llvm::is_contained(ArgUses, ArgUse))
 | 
						|
      ArgUses.push_back(ArgUse);
 | 
						|
  }
 | 
						|
  CurrCommitMacroArgExps.clear();
 | 
						|
}
 | 
						|
 | 
						|
StringRef EditedSource::copyString(const Twine &twine) {
 | 
						|
  SmallString<128> Data;
 | 
						|
  return copyString(twine.toStringRef(Data));
 | 
						|
}
 | 
						|
 | 
						|
bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
 | 
						|
  FileEditsTy::iterator FA = getActionForOffset(Offs);
 | 
						|
  if (FA != FileEdits.end()) {
 | 
						|
    if (FA->first != Offs)
 | 
						|
      return false; // position has been removed.
 | 
						|
  }
 | 
						|
 | 
						|
  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
 | 
						|
    SourceLocation ExpLoc;
 | 
						|
    MacroArgUse ArgUse;
 | 
						|
    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
 | 
						|
    auto I = ExpansionToArgMap.find(ExpLoc);
 | 
						|
    if (I != ExpansionToArgMap.end() &&
 | 
						|
        find_if(I->second, [&](const MacroArgUse &U) {
 | 
						|
          return ArgUse.Identifier == U.Identifier &&
 | 
						|
                 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
 | 
						|
                     std::tie(U.ImmediateExpansionLoc, U.UseLoc);
 | 
						|
        }) != I->second.end()) {
 | 
						|
      // Trying to write in a macro argument input that has already been
 | 
						|
      // written by a previous commit for another expansion of the same macro
 | 
						|
      // argument name. For example:
 | 
						|
      //
 | 
						|
      // \code
 | 
						|
      //   #define MAC(x) ((x)+(x))
 | 
						|
      //   MAC(a)
 | 
						|
      // \endcode
 | 
						|
      //
 | 
						|
      // A commit modified the macro argument 'a' due to the first '(x)'
 | 
						|
      // expansion inside the macro definition, and a subsequent commit tried
 | 
						|
      // to modify 'a' again for the second '(x)' expansion. The edits of the
 | 
						|
      // second commit will be rejected.
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool EditedSource::commitInsert(SourceLocation OrigLoc,
 | 
						|
                                FileOffset Offs, StringRef text,
 | 
						|
                                bool beforePreviousInsertions) {
 | 
						|
  if (!canInsertInOffset(OrigLoc, Offs))
 | 
						|
    return false;
 | 
						|
  if (text.empty())
 | 
						|
    return true;
 | 
						|
 | 
						|
  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
 | 
						|
    MacroArgUse ArgUse;
 | 
						|
    SourceLocation ExpLoc;
 | 
						|
    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
 | 
						|
    if (ArgUse.Identifier)
 | 
						|
      CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
 | 
						|
  }
 | 
						|
 | 
						|
  FileEdit &FA = FileEdits[Offs];
 | 
						|
  if (FA.Text.empty()) {
 | 
						|
    FA.Text = copyString(text);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (beforePreviousInsertions)
 | 
						|
    FA.Text = copyString(Twine(text) + FA.Text);
 | 
						|
  else
 | 
						|
    FA.Text = copyString(Twine(FA.Text) + text);
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
 | 
						|
                                   FileOffset Offs,
 | 
						|
                                   FileOffset InsertFromRangeOffs, unsigned Len,
 | 
						|
                                   bool beforePreviousInsertions) {
 | 
						|
  if (Len == 0)
 | 
						|
    return true;
 | 
						|
 | 
						|
  SmallString<128> StrVec;
 | 
						|
  FileOffset BeginOffs = InsertFromRangeOffs;
 | 
						|
  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
 | 
						|
  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
 | 
						|
  if (I != FileEdits.begin())
 | 
						|
    --I;
 | 
						|
 | 
						|
  for (; I != FileEdits.end(); ++I) {
 | 
						|
    FileEdit &FA = I->second;
 | 
						|
    FileOffset B = I->first;
 | 
						|
    FileOffset E = B.getWithOffset(FA.RemoveLen);
 | 
						|
 | 
						|
    if (BeginOffs == B)
 | 
						|
      break;
 | 
						|
 | 
						|
    if (BeginOffs < E) {
 | 
						|
      if (BeginOffs > B) {
 | 
						|
        BeginOffs = E;
 | 
						|
        ++I;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
 | 
						|
    FileEdit &FA = I->second;
 | 
						|
    FileOffset B = I->first;
 | 
						|
    FileOffset E = B.getWithOffset(FA.RemoveLen);
 | 
						|
 | 
						|
    if (BeginOffs < B) {
 | 
						|
      bool Invalid = false;
 | 
						|
      StringRef text = getSourceText(BeginOffs, B, Invalid);
 | 
						|
      if (Invalid)
 | 
						|
        return false;
 | 
						|
      StrVec += text;
 | 
						|
    }
 | 
						|
    StrVec += FA.Text;
 | 
						|
    BeginOffs = E;
 | 
						|
  }
 | 
						|
 | 
						|
  if (BeginOffs < EndOffs) {
 | 
						|
    bool Invalid = false;
 | 
						|
    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
 | 
						|
    if (Invalid)
 | 
						|
      return false;
 | 
						|
    StrVec += text;
 | 
						|
  }
 | 
						|
 | 
						|
  return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
 | 
						|
}
 | 
						|
 | 
						|
void EditedSource::commitRemove(SourceLocation OrigLoc,
 | 
						|
                                FileOffset BeginOffs, unsigned Len) {
 | 
						|
  if (Len == 0)
 | 
						|
    return;
 | 
						|
 | 
						|
  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
 | 
						|
  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
 | 
						|
  if (I != FileEdits.begin())
 | 
						|
    --I;
 | 
						|
 | 
						|
  for (; I != FileEdits.end(); ++I) {
 | 
						|
    FileEdit &FA = I->second;
 | 
						|
    FileOffset B = I->first;
 | 
						|
    FileOffset E = B.getWithOffset(FA.RemoveLen);
 | 
						|
 | 
						|
    if (BeginOffs < E)
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  FileOffset TopBegin, TopEnd;
 | 
						|
  FileEdit *TopFA = nullptr;
 | 
						|
 | 
						|
  if (I == FileEdits.end()) {
 | 
						|
    FileEditsTy::iterator
 | 
						|
      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
 | 
						|
    NewI->second.RemoveLen = Len;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  FileEdit &FA = I->second;
 | 
						|
  FileOffset B = I->first;
 | 
						|
  FileOffset E = B.getWithOffset(FA.RemoveLen);
 | 
						|
  if (BeginOffs < B) {
 | 
						|
    FileEditsTy::iterator
 | 
						|
      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
 | 
						|
    TopBegin = BeginOffs;
 | 
						|
    TopEnd = EndOffs;
 | 
						|
    TopFA = &NewI->second;
 | 
						|
    TopFA->RemoveLen = Len;
 | 
						|
  } else {
 | 
						|
    TopBegin = B;
 | 
						|
    TopEnd = E;
 | 
						|
    TopFA = &I->second;
 | 
						|
    if (TopEnd >= EndOffs)
 | 
						|
      return;
 | 
						|
    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
 | 
						|
    TopEnd = EndOffs;
 | 
						|
    TopFA->RemoveLen += diff;
 | 
						|
    if (B == BeginOffs)
 | 
						|
      TopFA->Text = StringRef();
 | 
						|
    ++I;
 | 
						|
  }
 | 
						|
 | 
						|
  while (I != FileEdits.end()) {
 | 
						|
    FileEdit &FA = I->second;
 | 
						|
    FileOffset B = I->first;
 | 
						|
    FileOffset E = B.getWithOffset(FA.RemoveLen);
 | 
						|
 | 
						|
    if (B >= TopEnd)
 | 
						|
      break;
 | 
						|
 | 
						|
    if (E <= TopEnd) {
 | 
						|
      FileEdits.erase(I++);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (B < TopEnd) {
 | 
						|
      unsigned diff = E.getOffset() - TopEnd.getOffset();
 | 
						|
      TopEnd = E;
 | 
						|
      TopFA->RemoveLen += diff;
 | 
						|
      FileEdits.erase(I);
 | 
						|
    }
 | 
						|
 | 
						|
    break;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool EditedSource::commit(const Commit &commit) {
 | 
						|
  if (!commit.isCommitable())
 | 
						|
    return false;
 | 
						|
 | 
						|
  struct CommitRAII {
 | 
						|
    EditedSource &Editor;
 | 
						|
 | 
						|
    CommitRAII(EditedSource &Editor) : Editor(Editor) {
 | 
						|
      Editor.startingCommit();
 | 
						|
    }
 | 
						|
 | 
						|
    ~CommitRAII() {
 | 
						|
      Editor.finishedCommit();
 | 
						|
    }
 | 
						|
  } CommitRAII(*this);
 | 
						|
 | 
						|
  for (edit::Commit::edit_iterator
 | 
						|
         I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
 | 
						|
    const edit::Commit::Edit &edit = *I;
 | 
						|
    switch (edit.Kind) {
 | 
						|
    case edit::Commit::Act_Insert:
 | 
						|
      commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
 | 
						|
      break;
 | 
						|
    case edit::Commit::Act_InsertFromRange:
 | 
						|
      commitInsertFromRange(edit.OrigLoc, edit.Offset,
 | 
						|
                            edit.InsertFromRangeOffs, edit.Length,
 | 
						|
                            edit.BeforePrev);
 | 
						|
      break;
 | 
						|
    case edit::Commit::Act_Remove:
 | 
						|
      commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
 | 
						|
      break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
// Returns true if it is ok to make the two given characters adjacent.
 | 
						|
static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
 | 
						|
  // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
 | 
						|
  // making two '<' adjacent.
 | 
						|
  return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&
 | 
						|
           Lexer::isAsciiIdentifierContinueChar(right, LangOpts));
 | 
						|
}
 | 
						|
 | 
						|
/// Returns true if it is ok to eliminate the trailing whitespace between
 | 
						|
/// the given characters.
 | 
						|
static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
 | 
						|
                                const LangOptions &LangOpts) {
 | 
						|
  if (!canBeJoined(left, right, LangOpts))
 | 
						|
    return false;
 | 
						|
  if (isWhitespace(left) || isWhitespace(right))
 | 
						|
    return true;
 | 
						|
  if (canBeJoined(beforeWSpace, right, LangOpts))
 | 
						|
    return false; // the whitespace was intentional, keep it.
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
/// Check the range that we are going to remove and:
 | 
						|
/// -Remove any trailing whitespace if possible.
 | 
						|
/// -Insert a space if removing the range is going to mess up the source tokens.
 | 
						|
static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
 | 
						|
                          SourceLocation Loc, FileOffset offs,
 | 
						|
                          unsigned &len, StringRef &text) {
 | 
						|
  assert(len && text.empty());
 | 
						|
  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
 | 
						|
  if (BeginTokLoc != Loc)
 | 
						|
    return; // the range is not at the beginning of a token, keep the range.
 | 
						|
 | 
						|
  bool Invalid = false;
 | 
						|
  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
 | 
						|
  if (Invalid)
 | 
						|
    return;
 | 
						|
 | 
						|
  unsigned begin = offs.getOffset();
 | 
						|
  unsigned end = begin + len;
 | 
						|
 | 
						|
  // Do not try to extend the removal if we're at the end of the buffer already.
 | 
						|
  if (end == buffer.size())
 | 
						|
    return;
 | 
						|
 | 
						|
  assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
 | 
						|
 | 
						|
  // FIXME: Remove newline.
 | 
						|
 | 
						|
  if (begin == 0) {
 | 
						|
    if (buffer[end] == ' ')
 | 
						|
      ++len;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (buffer[end] == ' ') {
 | 
						|
    assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
 | 
						|
           "buffer not zero-terminated!");
 | 
						|
    if (canRemoveWhitespace(/*left=*/buffer[begin-1],
 | 
						|
                            /*beforeWSpace=*/buffer[end-1],
 | 
						|
                            /*right=*/buffer.data()[end + 1], // zero-terminated
 | 
						|
                            LangOpts))
 | 
						|
      ++len;
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
 | 
						|
    text = " ";
 | 
						|
}
 | 
						|
 | 
						|
static void applyRewrite(EditsReceiver &receiver,
 | 
						|
                         StringRef text, FileOffset offs, unsigned len,
 | 
						|
                         const SourceManager &SM, const LangOptions &LangOpts,
 | 
						|
                         bool shouldAdjustRemovals) {
 | 
						|
  assert(offs.getFID().isValid());
 | 
						|
  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
 | 
						|
  Loc = Loc.getLocWithOffset(offs.getOffset());
 | 
						|
  assert(Loc.isFileID());
 | 
						|
 | 
						|
  if (text.empty() && shouldAdjustRemovals)
 | 
						|
    adjustRemoval(SM, LangOpts, Loc, offs, len, text);
 | 
						|
 | 
						|
  CharSourceRange range = CharSourceRange::getCharRange(Loc,
 | 
						|
                                                     Loc.getLocWithOffset(len));
 | 
						|
 | 
						|
  if (text.empty()) {
 | 
						|
    assert(len);
 | 
						|
    receiver.remove(range);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (len)
 | 
						|
    receiver.replace(range, text);
 | 
						|
  else
 | 
						|
    receiver.insert(Loc, text);
 | 
						|
}
 | 
						|
 | 
						|
void EditedSource::applyRewrites(EditsReceiver &receiver,
 | 
						|
                                 bool shouldAdjustRemovals) {
 | 
						|
  SmallString<128> StrVec;
 | 
						|
  FileOffset CurOffs, CurEnd;
 | 
						|
  unsigned CurLen;
 | 
						|
 | 
						|
  if (FileEdits.empty())
 | 
						|
    return;
 | 
						|
 | 
						|
  FileEditsTy::iterator I = FileEdits.begin();
 | 
						|
  CurOffs = I->first;
 | 
						|
  StrVec = I->second.Text;
 | 
						|
  CurLen = I->second.RemoveLen;
 | 
						|
  CurEnd = CurOffs.getWithOffset(CurLen);
 | 
						|
  ++I;
 | 
						|
 | 
						|
  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
 | 
						|
    FileOffset offs = I->first;
 | 
						|
    FileEdit act = I->second;
 | 
						|
    assert(offs >= CurEnd);
 | 
						|
 | 
						|
    if (offs == CurEnd) {
 | 
						|
      StrVec += act.Text;
 | 
						|
      CurLen += act.RemoveLen;
 | 
						|
      CurEnd.getWithOffset(act.RemoveLen);
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
 | 
						|
                 shouldAdjustRemovals);
 | 
						|
    CurOffs = offs;
 | 
						|
    StrVec = act.Text;
 | 
						|
    CurLen = act.RemoveLen;
 | 
						|
    CurEnd = CurOffs.getWithOffset(CurLen);
 | 
						|
  }
 | 
						|
 | 
						|
  applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
 | 
						|
               shouldAdjustRemovals);
 | 
						|
}
 | 
						|
 | 
						|
void EditedSource::clearRewrites() {
 | 
						|
  FileEdits.clear();
 | 
						|
  StrAlloc.Reset();
 | 
						|
}
 | 
						|
 | 
						|
StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
 | 
						|
                                      bool &Invalid) {
 | 
						|
  assert(BeginOffs.getFID() == EndOffs.getFID());
 | 
						|
  assert(BeginOffs <= EndOffs);
 | 
						|
  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
 | 
						|
  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
 | 
						|
  assert(BLoc.isFileID());
 | 
						|
  SourceLocation
 | 
						|
    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
 | 
						|
  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
 | 
						|
                              SourceMgr, LangOpts, &Invalid);
 | 
						|
}
 | 
						|
 | 
						|
EditedSource::FileEditsTy::iterator
 | 
						|
EditedSource::getActionForOffset(FileOffset Offs) {
 | 
						|
  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
 | 
						|
  if (I == FileEdits.begin())
 | 
						|
    return FileEdits.end();
 | 
						|
  --I;
 | 
						|
  FileEdit &FA = I->second;
 | 
						|
  FileOffset B = I->first;
 | 
						|
  FileOffset E = B.getWithOffset(FA.RemoveLen);
 | 
						|
  if (Offs >= B && Offs < E)
 | 
						|
    return I;
 | 
						|
 | 
						|
  return FileEdits.end();
 | 
						|
}
 |