clang 22.0.0git
EditedSource.cpp
Go to the documentation of this file.
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
11#include "clang/Basic/LLVM.h"
14#include "clang/Edit/Commit.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
29 replace(range, 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(DefArgLoc).getBegin();
40 ExpansionLoc = ImmediateExpansionLoc;
41 while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
42 ExpansionLoc =
43 SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
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
53void EditedSource::startingCommit() {}
54
55void 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
67StringRef EditedSource::copyString(const Twine &twine) {
69 return copyString(twine.toStringRef(Data));
70}
71
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
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(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
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(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
197void 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
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
291 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
292 const edit::Commit::Edit &edit = *I;
293 switch (edit.Kind) {
295 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
296 break;
298 commitInsertFromRange(edit.OrigLoc, edit.Offset,
299 edit.InsertFromRangeOffs, edit.Length,
300 edit.BeforePrev);
301 break;
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.
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(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.
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(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.
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(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
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(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
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
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
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(BeginOffs.getFID());
457 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
458 assert(BLoc.isFileID());
460 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
462 SourceMgr, LangOpts, &Invalid);
463}
464
465EditedSource::FileEditsTy::iterator
466EditedSource::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}
static bool canBeJoined(char left, char right, const LangOptions &LangOpts)
static void applyRewrite(EditsReceiver &receiver, StringRef text, FileOffset offs, unsigned len, const SourceManager &SM, const LangOptions &LangOpts, bool shouldAdjustRemovals)
static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation Loc, FileOffset offs, unsigned &len, StringRef &text)
Check the range that we are going to remove and: -Remove any trailing whitespace if possible.
static bool canRemoveWhitespace(char left, char beforeWSpace, char right, const LangOptions &LangOpts)
Returns true if it is ok to eliminate the trailing whitespace between the given characters.
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified.
#define SM(sm)
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
Represents a character-granular source range.
static CharSourceRange getCharRange(SourceRange R)
bool isValid() const
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
static StringRef getSourceText(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts, bool *Invalid=nullptr)
Returns a string for the source that the range encompasses.
Definition Lexer.cpp:1020
static unsigned getSpelling(const Token &Tok, const char *&Buffer, const SourceManager &SourceMgr, const LangOptions &LangOpts, bool *Invalid=nullptr)
getSpelling - This method is used to get the spelling of a token into a preallocated buffer,...
Definition Lexer.cpp:451
static bool isAsciiIdentifierContinueChar(char c, const LangOptions &LangOpts)
Returns true if the given character could appear in an identifier.
Definition Lexer.cpp:1130
static SourceLocation GetBeginningOfToken(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
Given a location any where in a source buffer, find the location that corresponds to the beginning of...
Definition Lexer.cpp:608
Encodes a location in the source.
SourceLocation getLocWithOffset(IntTy Offset) const
Return a source location with the specified offset from this SourceLocation.
This class handles loading and caching of source files into memory.
SmallVectorImpl< Edit >::const_iterator edit_iterator
Definition Commit.h:119
StringRef copyString(StringRef str)
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
void applyRewrites(EditsReceiver &receiver, bool adjustRemovals=true)
bool commit(const Commit &commit)
EditedSource(const SourceManager &SM, const LangOptions &LangOpts, const PPConditionalDirectiveRecord *PPRec=nullptr)
virtual void insert(SourceLocation loc, StringRef text)=0
virtual void remove(CharSourceRange range)
By default it calls replace with an empty string.
virtual void replace(CharSourceRange range, StringRef text)=0
FileOffset getWithOffset(unsigned offset) const
Definition FileOffset.h:31
unsigned getOffset() const
Definition FileOffset.h:29
FileID getFID() const
Definition FileOffset.h:28
The JSON file list parser is used to communicate input to InstallAPI.
LLVM_READONLY bool isWhitespace(unsigned char c)
Return true if this character is horizontal or vertical ASCII whitespace: ' ', '\t',...
Definition CharInfo.h:108