brintos

brintos / llvm-project-archived public Read only

0
0
Text · 14.0 KiB · 398cce7 Raw
479 lines · cpp
1//===- EditedSource.cpp - Collection of source edits ----------------------===//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/Edit/EditedSource.h"10#include "clang/Basic/CharInfo.h"11#include "clang/Basic/LLVM.h"12#include "clang/Basic/SourceLocation.h"13#include "clang/Basic/SourceManager.h"14#include "clang/Edit/Commit.h"15#include "clang/Edit/EditsReceiver.h"16#include "clang/Edit/FileOffset.h"17#include "clang/Lex/Lexer.h"18#include "llvm/ADT/STLExtras.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/ADT/Twine.h"21#include <cassert>22#include <tuple>23#include <utility>24 25using namespace clang;26using namespace edit;27 28void EditsReceiver::remove(CharSourceRange range) {29  replace(range, StringRef());30}31 32void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,33                                          SourceLocation &ExpansionLoc,34                                          MacroArgUse &ArgUse) {35  assert(SourceMgr.isMacroArgExpansion(Loc));36  SourceLocation DefArgLoc =37      SourceMgr.getImmediateExpansionRange(Loc).getBegin();38  SourceLocation ImmediateExpansionLoc =39      SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();40  ExpansionLoc = ImmediateExpansionLoc;41  while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))42    ExpansionLoc =43        SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();44  SmallString<20> Buf;45  StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),46                                         Buf, SourceMgr, LangOpts);47  ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};48  if (!ArgName.empty())49    ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,50              SourceMgr.getSpellingLoc(DefArgLoc)};51}52 53void EditedSource::startingCommit() {}54 55void EditedSource::finishedCommit() {56  for (auto &ExpArg : CurrCommitMacroArgExps) {57    SourceLocation ExpLoc;58    MacroArgUse ArgUse;59    std::tie(ExpLoc, ArgUse) = ExpArg;60    auto &ArgUses = ExpansionToArgMap[ExpLoc];61    if (!llvm::is_contained(ArgUses, ArgUse))62      ArgUses.push_back(ArgUse);63  }64  CurrCommitMacroArgExps.clear();65}66 67StringRef EditedSource::copyString(const Twine &twine) {68  SmallString<128> Data;69  return copyString(twine.toStringRef(Data));70}71 72bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {73  FileEditsTy::iterator FA = getActionForOffset(Offs);74  if (FA != FileEdits.end()) {75    if (FA->first != Offs)76      return false; // position has been removed.77  }78 79  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {80    SourceLocation ExpLoc;81    MacroArgUse ArgUse;82    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);83    auto I = ExpansionToArgMap.find(ExpLoc);84    if (I != ExpansionToArgMap.end() &&85        llvm::any_of(I->second, [&](const MacroArgUse &U) {86          return ArgUse.Identifier == U.Identifier &&87                 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=88                     std::tie(U.ImmediateExpansionLoc, U.UseLoc);89        })) {90      // Trying to write in a macro argument input that has already been91      // written by a previous commit for another expansion of the same macro92      // argument name. For example:93      //94      // \code95      //   #define MAC(x) ((x)+(x))96      //   MAC(a)97      // \endcode98      //99      // A commit modified the macro argument 'a' due to the first '(x)'100      // expansion inside the macro definition, and a subsequent commit tried101      // to modify 'a' again for the second '(x)' expansion. The edits of the102      // second commit will be rejected.103      return false;104    }105  }106  return true;107}108 109bool EditedSource::commitInsert(SourceLocation OrigLoc,110                                FileOffset Offs, StringRef text,111                                bool beforePreviousInsertions) {112  if (!canInsertInOffset(OrigLoc, Offs))113    return false;114  if (text.empty())115    return true;116 117  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {118    MacroArgUse ArgUse;119    SourceLocation ExpLoc;120    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);121    if (ArgUse.Identifier)122      CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);123  }124 125  FileEdit &FA = FileEdits[Offs];126  if (FA.Text.empty()) {127    FA.Text = copyString(text);128    return true;129  }130 131  if (beforePreviousInsertions)132    FA.Text = copyString(Twine(text) + FA.Text);133  else134    FA.Text = copyString(Twine(FA.Text) + text);135 136  return true;137}138 139bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,140                                   FileOffset Offs,141                                   FileOffset InsertFromRangeOffs, unsigned Len,142                                   bool beforePreviousInsertions) {143  if (Len == 0)144    return true;145 146  SmallString<128> StrVec;147  FileOffset BeginOffs = InsertFromRangeOffs;148  FileOffset EndOffs = BeginOffs.getWithOffset(Len);149  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);150  if (I != FileEdits.begin())151    --I;152 153  for (; I != FileEdits.end(); ++I) {154    FileEdit &FA = I->second;155    FileOffset B = I->first;156    FileOffset E = B.getWithOffset(FA.RemoveLen);157 158    if (BeginOffs == B)159      break;160 161    if (BeginOffs < E) {162      if (BeginOffs > B) {163        BeginOffs = E;164        ++I;165      }166      break;167    }168  }169 170  for (; I != FileEdits.end() && EndOffs > I->first; ++I) {171    FileEdit &FA = I->second;172    FileOffset B = I->first;173    FileOffset E = B.getWithOffset(FA.RemoveLen);174 175    if (BeginOffs < B) {176      bool Invalid = false;177      StringRef text = getSourceText(BeginOffs, B, Invalid);178      if (Invalid)179        return false;180      StrVec += text;181    }182    StrVec += FA.Text;183    BeginOffs = E;184  }185 186  if (BeginOffs < EndOffs) {187    bool Invalid = false;188    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);189    if (Invalid)190      return false;191    StrVec += text;192  }193 194  return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);195}196 197void EditedSource::commitRemove(SourceLocation OrigLoc,198                                FileOffset BeginOffs, unsigned Len) {199  if (Len == 0)200    return;201 202  FileOffset EndOffs = BeginOffs.getWithOffset(Len);203  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);204  if (I != FileEdits.begin())205    --I;206 207  for (; I != FileEdits.end(); ++I) {208    FileEdit &FA = I->second;209    FileOffset B = I->first;210    FileOffset E = B.getWithOffset(FA.RemoveLen);211 212    if (BeginOffs < E)213      break;214  }215 216  FileOffset TopBegin, TopEnd;217  FileEdit *TopFA = nullptr;218 219  if (I == FileEdits.end()) {220    FileEditsTy::iterator221      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));222    NewI->second.RemoveLen = Len;223    return;224  }225 226  FileEdit &FA = I->second;227  FileOffset B = I->first;228  FileOffset E = B.getWithOffset(FA.RemoveLen);229  if (BeginOffs < B) {230    FileEditsTy::iterator231      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));232    TopBegin = BeginOffs;233    TopEnd = EndOffs;234    TopFA = &NewI->second;235    TopFA->RemoveLen = Len;236  } else {237    TopBegin = B;238    TopEnd = E;239    TopFA = &I->second;240    if (TopEnd >= EndOffs)241      return;242    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();243    TopEnd = EndOffs;244    TopFA->RemoveLen += diff;245    if (B == BeginOffs)246      TopFA->Text = StringRef();247    ++I;248  }249 250  while (I != FileEdits.end()) {251    FileEdit &FA = I->second;252    FileOffset B = I->first;253    FileOffset E = B.getWithOffset(FA.RemoveLen);254 255    if (B >= TopEnd)256      break;257 258    if (E <= TopEnd) {259      FileEdits.erase(I++);260      continue;261    }262 263    if (B < TopEnd) {264      unsigned diff = E.getOffset() - TopEnd.getOffset();265      TopEnd = E;266      TopFA->RemoveLen += diff;267      FileEdits.erase(I);268    }269 270    break;271  }272}273 274bool EditedSource::commit(const Commit &commit) {275  if (!commit.isCommitable())276    return false;277 278  struct CommitRAII {279    EditedSource &Editor;280 281    CommitRAII(EditedSource &Editor) : Editor(Editor) {282      Editor.startingCommit();283    }284 285    ~CommitRAII() {286      Editor.finishedCommit();287    }288  } CommitRAII(*this);289 290  for (edit::Commit::edit_iterator291         I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {292    const edit::Commit::Edit &edit = *I;293    switch (edit.Kind) {294    case edit::Commit::Act_Insert:295      commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);296      break;297    case edit::Commit::Act_InsertFromRange:298      commitInsertFromRange(edit.OrigLoc, edit.Offset,299                            edit.InsertFromRangeOffs, edit.Length,300                            edit.BeforePrev);301      break;302    case edit::Commit::Act_Remove:303      commitRemove(edit.OrigLoc, edit.Offset, edit.Length);304      break;305    }306  }307 308  return true;309}310 311// Returns true if it is ok to make the two given characters adjacent.312static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {313  // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like314  // making two '<' adjacent.315  return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&316           Lexer::isAsciiIdentifierContinueChar(right, LangOpts));317}318 319/// Returns true if it is ok to eliminate the trailing whitespace between320/// the given characters.321static bool canRemoveWhitespace(char left, char beforeWSpace, char right,322                                const LangOptions &LangOpts) {323  if (!canBeJoined(left, right, LangOpts))324    return false;325  if (isWhitespace(left) || isWhitespace(right))326    return true;327  if (canBeJoined(beforeWSpace, right, LangOpts))328    return false; // the whitespace was intentional, keep it.329  return true;330}331 332/// Check the range that we are going to remove and:333/// -Remove any trailing whitespace if possible.334/// -Insert a space if removing the range is going to mess up the source tokens.335static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,336                          SourceLocation Loc, FileOffset offs,337                          unsigned &len, StringRef &text) {338  assert(len && text.empty());339  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);340  if (BeginTokLoc != Loc)341    return; // the range is not at the beginning of a token, keep the range.342 343  bool Invalid = false;344  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);345  if (Invalid)346    return;347 348  unsigned begin = offs.getOffset();349  unsigned end = begin + len;350 351  // Do not try to extend the removal if we're at the end of the buffer already.352  if (end == buffer.size())353    return;354 355  assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");356 357  // FIXME: Remove newline.358 359  if (begin == 0) {360    if (buffer[end] == ' ')361      ++len;362    return;363  }364 365  if (buffer[end] == ' ') {366    assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&367           "buffer not zero-terminated!");368    if (canRemoveWhitespace(/*left=*/buffer[begin-1],369                            /*beforeWSpace=*/buffer[end-1],370                            /*right=*/buffer.data()[end + 1], // zero-terminated371                            LangOpts))372      ++len;373    return;374  }375 376  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))377    text = " ";378}379 380static void applyRewrite(EditsReceiver &receiver,381                         StringRef text, FileOffset offs, unsigned len,382                         const SourceManager &SM, const LangOptions &LangOpts,383                         bool shouldAdjustRemovals) {384  assert(offs.getFID().isValid());385  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());386  Loc = Loc.getLocWithOffset(offs.getOffset());387  assert(Loc.isFileID());388 389  if (text.empty() && shouldAdjustRemovals)390    adjustRemoval(SM, LangOpts, Loc, offs, len, text);391 392  CharSourceRange range = CharSourceRange::getCharRange(Loc,393                                                     Loc.getLocWithOffset(len));394 395  if (text.empty()) {396    assert(len);397    receiver.remove(range);398    return;399  }400 401  if (len)402    receiver.replace(range, text);403  else404    receiver.insert(Loc, text);405}406 407void EditedSource::applyRewrites(EditsReceiver &receiver,408                                 bool shouldAdjustRemovals) {409  SmallString<128> StrVec;410  FileOffset CurOffs, CurEnd;411  unsigned CurLen;412 413  if (FileEdits.empty())414    return;415 416  FileEditsTy::iterator I = FileEdits.begin();417  CurOffs = I->first;418  StrVec = I->second.Text;419  CurLen = I->second.RemoveLen;420  CurEnd = CurOffs.getWithOffset(CurLen);421  ++I;422 423  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {424    FileOffset offs = I->first;425    FileEdit act = I->second;426    assert(offs >= CurEnd);427 428    if (offs == CurEnd) {429      StrVec += act.Text;430      CurLen += act.RemoveLen;431      CurEnd.getWithOffset(act.RemoveLen);432      continue;433    }434 435    applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,436                 shouldAdjustRemovals);437    CurOffs = offs;438    StrVec = act.Text;439    CurLen = act.RemoveLen;440    CurEnd = CurOffs.getWithOffset(CurLen);441  }442 443  applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,444               shouldAdjustRemovals);445}446 447void EditedSource::clearRewrites() {448  FileEdits.clear();449  StrAlloc.Reset();450}451 452StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,453                                      bool &Invalid) {454  assert(BeginOffs.getFID() == EndOffs.getFID());455  assert(BeginOffs <= EndOffs);456  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());457  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());458  assert(BLoc.isFileID());459  SourceLocation460    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());461  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),462                              SourceMgr, LangOpts, &Invalid);463}464 465EditedSource::FileEditsTy::iterator466EditedSource::getActionForOffset(FileOffset Offs) {467  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);468  if (I == FileEdits.begin())469    return FileEdits.end();470  --I;471  FileEdit &FA = I->second;472  FileOffset B = I->first;473  FileOffset E = B.getWithOffset(FA.RemoveLen);474  if (Offs >= B && Offs < E)475    return I;476 477  return FileEdits.end();478}479