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