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
19using namespace clang;
20using namespace edit;
21
22void EditsReceiver::remove(CharSourceRange range) {
23  replace(range, StringRef());
24}
25
26void 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
41void EditedSource::startingCommit() {}
42
43void 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
56StringRef EditedSource::copyString(const Twine &twine) {
57  SmallString<128> Data;
58  return copyString(twine.toStringRef(Data));
59}
60
61bool 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
95bool 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
125bool 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
183void 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
260bool 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.
296static 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.
305static 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.
319static 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
364static 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
390void 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
427void EditedSource::clearRewrites() {
428  FileEdits.clear();
429  StrAlloc.Reset();
430}
431
432StringRef 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
445EditedSource::FileEditsTy::iterator
446EditedSource::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