xref: /freebsd/contrib/llvm-project/clang/lib/Edit/EditedSource.cpp (revision e64bea71c21eb42e97aa615188ba91f6cce0d36d)
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-exception
6 //
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 
25 using namespace clang;
26 using namespace edit;
27 
28 void EditsReceiver::remove(CharSourceRange range) {
29   replace(range, StringRef());
30 }
31 
32 void 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 
53 void EditedSource::startingCommit() {}
54 
55 void 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 
67 StringRef EditedSource::copyString(const Twine &twine) {
68   SmallString<128> Data;
69   return copyString(twine.toStringRef(Data));
70 }
71 
72 bool 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 been
91       // written by a previous commit for another expansion of the same macro
92       // argument name. For example:
93       //
94       // \code
95       //   #define MAC(x) ((x)+(x))
96       //   MAC(a)
97       // \endcode
98       //
99       // A commit modified the macro argument 'a' due to the first '(x)'
100       // expansion inside the macro definition, and a subsequent commit tried
101       // to modify 'a' again for the second '(x)' expansion. The edits of the
102       // second commit will be rejected.
103       return false;
104     }
105   }
106   return true;
107 }
108 
109 bool 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   else
134     FA.Text = copyString(Twine(FA.Text) + text);
135 
136   return true;
137 }
138 
139 bool 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 
197 void 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::iterator
221       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::iterator
231       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 
274 bool 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_iterator
291          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.
312 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
313   // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
314   // 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 between
320 /// the given characters.
321 static 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.
335 static 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-terminated
371                             LangOpts))
372       ++len;
373     return;
374   }
375 
376   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
377     text = " ";
378 }
379 
380 static 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   else
404     receiver.insert(Loc, text);
405 }
406 
407 void 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 
447 void EditedSource::clearRewrites() {
448   FileEdits.clear();
449   StrAlloc.Reset();
450 }
451 
452 StringRef 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   SourceLocation
460     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
461   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
462                               SourceMgr, LangOpts, &Invalid);
463 }
464 
465 EditedSource::FileEditsTy::iterator
466 EditedSource::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