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