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
remove(CharSourceRange range)28 void EditsReceiver::remove(CharSourceRange range) {
29 replace(range, StringRef());
30 }
31
deconstructMacroArgLoc(SourceLocation Loc,SourceLocation & ExpansionLoc,MacroArgUse & ArgUse)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
startingCommit()53 void EditedSource::startingCommit() {}
54
finishedCommit()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
copyString(const Twine & twine)67 StringRef EditedSource::copyString(const Twine &twine) {
68 SmallString<128> Data;
69 return copyString(twine.toStringRef(Data));
70 }
71
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)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
commitInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)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
commitInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset InsertFromRangeOffs,unsigned Len,bool beforePreviousInsertions)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
commitRemove(SourceLocation OrigLoc,FileOffset BeginOffs,unsigned Len)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
commit(const Commit & commit)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.
canBeJoined(char left,char right,const LangOptions & LangOpts)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.
canRemoveWhitespace(char left,char beforeWSpace,char right,const LangOptions & LangOpts)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.
adjustRemoval(const SourceManager & SM,const LangOptions & LangOpts,SourceLocation Loc,FileOffset offs,unsigned & len,StringRef & text)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
applyRewrite(EditsReceiver & receiver,StringRef text,FileOffset offs,unsigned len,const SourceManager & SM,const LangOptions & LangOpts,bool shouldAdjustRemovals)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
applyRewrites(EditsReceiver & receiver,bool shouldAdjustRemovals)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
clearRewrites()447 void EditedSource::clearRewrites() {
448 FileEdits.clear();
449 StrAlloc.Reset();
450 }
451
getSourceText(FileOffset BeginOffs,FileOffset EndOffs,bool & Invalid)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
getActionForOffset(FileOffset Offs)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