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