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 
remove(CharSourceRange range)22 void EditsReceiver::remove(CharSourceRange range) {
23   replace(range, StringRef());
24 }
25 
deconstructMacroArgLoc(SourceLocation Loc,SourceLocation & ExpansionLoc,IdentifierInfo * & II)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 
startingCommit()41 void EditedSource::startingCommit() {}
42 
finishedCommit()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 
copyString(const Twine & twine)56 StringRef EditedSource::copyString(const Twine &twine) {
57   SmallString<128> Data;
58   return copyString(twine.toStringRef(Data));
59 }
60 
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)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 
commitInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)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 
commitInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset InsertFromRangeOffs,unsigned Len,bool beforePreviousInsertions)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 
commitRemove(SourceLocation OrigLoc,FileOffset BeginOffs,unsigned Len)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 
commit(const Commit & commit)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.
canBeJoined(char left,char right,const LangOptions & LangOpts)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.
canRemoveWhitespace(char left,char beforeWSpace,char right,const LangOptions & LangOpts)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.
adjustRemoval(const SourceManager & SM,const LangOptions & LangOpts,SourceLocation Loc,FileOffset offs,unsigned & len,StringRef & text)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 
applyRewrite(EditsReceiver & receiver,StringRef text,FileOffset offs,unsigned len,const SourceManager & SM,const LangOptions & LangOpts)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 
applyRewrites(EditsReceiver & receiver)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 
clearRewrites()427 void EditedSource::clearRewrites() {
428   FileEdits.clear();
429   StrAlloc.Reset();
430 }
431 
getSourceText(FileOffset BeginOffs,FileOffset EndOffs,bool & Invalid)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
getActionForOffset(FileOffset Offs)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