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 =
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
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
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,
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());
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}
Expr * E
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)
Definition: OffloadArch.cpp:16
SourceLocation Loc
Definition: SemaObjC.cpp:754
Defines the clang::SourceLocation class and associated facilities.
Defines the SourceManager interface.
Represents a character-granular source range.
static CharSourceRange getCharRange(SourceRange R)
SourceLocation getBegin() const
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...
Definition: LangOptions.h:434
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.
bool isMacroBodyExpansion(SourceLocation Loc) const
Tests whether the given source location represents the expansion of a macro body.
bool isMacroArgExpansion(SourceLocation Loc, SourceLocation *StartLoc=nullptr) const
Tests whether the given source location represents a macro argument's expansion into the function-lik...
SourceLocation getSpellingLoc(SourceLocation Loc) const
Given a SourceLocation object, return the spelling location referenced by the ID.
CharSourceRange getImmediateExpansionRange(SourceLocation Loc) const
Return the start/end of the expansion information for an expansion location.
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file.
SmallVectorImpl< Edit >::const_iterator edit_iterator
Definition: Commit.h:119
StringRef copyString(StringRef str)
Definition: EditedSource.h:91
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
void applyRewrites(EditsReceiver &receiver, bool adjustRemovals=true)
bool commit(const Commit &commit)
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
EditGenerator edit(ASTEdit E)
Generates a single (specified) edit.
Definition: RewriteRule.cpp:83
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