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
25using namespace clang;
26using namespace edit;
27
28void EditsReceiver::remove(CharSourceRange range) {
29 replace(range, text: StringRef());
30}
31
32void 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(Loc: DefArgLoc).getBegin();
40 ExpansionLoc = ImmediateExpansionLoc;
41 while (SourceMgr.isMacroBodyExpansion(Loc: ExpansionLoc))
42 ExpansionLoc =
43 SourceMgr.getImmediateExpansionRange(Loc: ExpansionLoc).getBegin();
44 SmallString<20> Buf;
45 StringRef ArgName = Lexer::getSpelling(loc: SourceMgr.getSpellingLoc(Loc: DefArgLoc),
46 buffer&: Buf, SM: SourceMgr, options: LangOpts);
47 ArgUse = MacroArgUse{.Identifier: nullptr, .ImmediateExpansionLoc: SourceLocation(), .UseLoc: SourceLocation()};
48 if (!ArgName.empty())
49 ArgUse = {.Identifier: &IdentTable.get(Name: ArgName), .ImmediateExpansionLoc: ImmediateExpansionLoc,
50 .UseLoc: SourceMgr.getSpellingLoc(Loc: DefArgLoc)};
51}
52
53void EditedSource::startingCommit() {}
54
55void EditedSource::finishedCommit() {
56 for (auto &ExpArg : CurrCommitMacroArgExps) {
57 SourceLocation ExpLoc;
58 MacroArgUse ArgUse;
59 std::tie(args&: ExpLoc, args&: ArgUse) = ExpArg;
60 auto &ArgUses = ExpansionToArgMap[ExpLoc];
61 if (!llvm::is_contained(Range&: ArgUses, Element: ArgUse))
62 ArgUses.push_back(Elt: ArgUse);
63 }
64 CurrCommitMacroArgExps.clear();
65}
66
67StringRef EditedSource::copyString(const Twine &twine) {
68 SmallString<128> Data;
69 return copyString(str: twine.toStringRef(Out&: Data));
70}
71
72bool 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(Loc: OrigLoc)) {
80 SourceLocation ExpLoc;
81 MacroArgUse ArgUse;
82 deconstructMacroArgLoc(Loc: OrigLoc, ExpansionLoc&: ExpLoc, ArgUse);
83 auto I = ExpansionToArgMap.find(Val: ExpLoc);
84 if (I != ExpansionToArgMap.end() &&
85 llvm::any_of(Range&: I->second, P: [&](const MacroArgUse &U) {
86 return ArgUse.Identifier == U.Identifier &&
87 std::tie(args&: ArgUse.ImmediateExpansionLoc, args&: ArgUse.UseLoc) !=
88 std::tie(args: U.ImmediateExpansionLoc, args: 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
109bool 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(Loc: OrigLoc)) {
118 MacroArgUse ArgUse;
119 SourceLocation ExpLoc;
120 deconstructMacroArgLoc(Loc: OrigLoc, ExpansionLoc&: ExpLoc, ArgUse);
121 if (ArgUse.Identifier)
122 CurrCommitMacroArgExps.emplace_back(Args&: ExpLoc, Args&: ArgUse);
123 }
124
125 FileEdit &FA = FileEdits[Offs];
126 if (FA.Text.empty()) {
127 FA.Text = copyString(str: text);
128 return true;
129 }
130
131 if (beforePreviousInsertions)
132 FA.Text = copyString(twine: Twine(text) + FA.Text);
133 else
134 FA.Text = copyString(twine: Twine(FA.Text) + text);
135
136 return true;
137}
138
139bool 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(offset: Len);
149 FileEditsTy::iterator I = FileEdits.upper_bound(x: 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(offset: 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(offset: FA.RemoveLen);
174
175 if (BeginOffs < B) {
176 bool Invalid = false;
177 StringRef text = getSourceText(BeginOffs, EndOffs: 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, text: StrVec, beforePreviousInsertions);
195}
196
197void EditedSource::commitRemove(SourceLocation OrigLoc,
198 FileOffset BeginOffs, unsigned Len) {
199 if (Len == 0)
200 return;
201
202 FileOffset EndOffs = BeginOffs.getWithOffset(offset: Len);
203 FileEditsTy::iterator I = FileEdits.upper_bound(x: 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(offset: 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(position: I, x: std::make_pair(x&: BeginOffs, y: 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(offset: FA.RemoveLen);
229 if (BeginOffs < B) {
230 FileEditsTy::iterator
231 NewI = FileEdits.insert(position: I, x: std::make_pair(x&: BeginOffs, y: 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(offset: FA.RemoveLen);
254
255 if (B >= TopEnd)
256 break;
257
258 if (E <= TopEnd) {
259 FileEdits.erase(position: 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(position: I);
268 }
269
270 break;
271 }
272}
273
274bool 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(OrigLoc: edit.OrigLoc, Offs: edit.Offset, text: edit.Text, beforePreviousInsertions: edit.BeforePrev);
296 break;
297 case edit::Commit::Act_InsertFromRange:
298 commitInsertFromRange(OrigLoc: edit.OrigLoc, Offs: edit.Offset,
299 InsertFromRangeOffs: edit.InsertFromRangeOffs, Len: edit.Length,
300 beforePreviousInsertions: edit.BeforePrev);
301 break;
302 case edit::Commit::Act_Remove:
303 commitRemove(OrigLoc: edit.OrigLoc, BeginOffs: edit.Offset, Len: 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.
312static 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(c: left, LangOpts) &&
316 Lexer::isAsciiIdentifierContinueChar(c: right, LangOpts));
317}
318
319/// Returns true if it is ok to eliminate the trailing whitespace between
320/// the given characters.
321static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
322 const LangOptions &LangOpts) {
323 if (!canBeJoined(left, right, LangOpts))
324 return false;
325 if (isWhitespace(c: left) || isWhitespace(c: right))
326 return true;
327 if (canBeJoined(left: 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.
335static 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(FID: offs.getFID(), Invalid: &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(left: buffer[begin-1], right: buffer[end], LangOpts))
377 text = " ";
378}
379
380static 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(FID: offs.getFID());
386 Loc = Loc.getLocWithOffset(Offset: 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(B: Loc,
393 E: Loc.getLocWithOffset(Offset: 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: Loc, text);
405}
406
407void 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(offset: 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(offset: act.RemoveLen);
432 continue;
433 }
434
435 applyRewrite(receiver, text: StrVec, offs: CurOffs, len: CurLen, SM: SourceMgr, LangOpts,
436 shouldAdjustRemovals);
437 CurOffs = offs;
438 StrVec = act.Text;
439 CurLen = act.RemoveLen;
440 CurEnd = CurOffs.getWithOffset(offset: CurLen);
441 }
442
443 applyRewrite(receiver, text: StrVec, offs: CurOffs, len: CurLen, SM: SourceMgr, LangOpts,
444 shouldAdjustRemovals);
445}
446
447void EditedSource::clearRewrites() {
448 FileEdits.clear();
449 StrAlloc.Reset();
450}
451
452StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
453 bool &Invalid) {
454 assert(BeginOffs.getFID() == EndOffs.getFID());
455 assert(BeginOffs <= EndOffs);
456 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(FID: BeginOffs.getFID());
457 BLoc = BLoc.getLocWithOffset(Offset: BeginOffs.getOffset());
458 assert(BLoc.isFileID());
459 SourceLocation
460 ELoc = BLoc.getLocWithOffset(Offset: EndOffs.getOffset() - BeginOffs.getOffset());
461 return Lexer::getSourceText(Range: CharSourceRange::getCharRange(B: BLoc, E: ELoc),
462 SM: SourceMgr, LangOpts, Invalid: &Invalid);
463}
464
465EditedSource::FileEditsTy::iterator
466EditedSource::getActionForOffset(FileOffset Offs) {
467 FileEditsTy::iterator I = FileEdits.upper_bound(x: 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(offset: FA.RemoveLen);
474 if (Offs >= B && Offs < E)
475 return I;
476
477 return FileEdits.end();
478}
479