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