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