clang  14.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 
10 #include "clang/Basic/CharInfo.h"
11 #include "clang/Basic/LLVM.h"
14 #include "clang/Edit/Commit.h"
16 #include "clang/Edit/FileOffset.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 
27 using namespace clang;
28 using namespace edit;
29 
31  replace(range, StringRef());
32 }
33 
34 void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
35  SourceLocation &ExpansionLoc,
36  MacroArgUse &ArgUse) {
37  assert(SourceMgr.isMacroArgExpansion(Loc));
38  SourceLocation DefArgLoc =
39  SourceMgr.getImmediateExpansionRange(Loc).getBegin();
40  SourceLocation ImmediateExpansionLoc =
41  SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();
42  ExpansionLoc = ImmediateExpansionLoc;
43  while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
44  ExpansionLoc =
45  SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
46  SmallString<20> Buf;
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 
55 void EditedSource::startingCommit() {}
56 
57 void 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 
69 StringRef EditedSource::copyString(const Twine &twine) {
70  SmallString<128> Data;
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  find_if(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  }) != I->second.end()) {
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 
111 bool 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 
141 bool 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 
199 void 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 
276 bool 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.
314 static 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.
323 static 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.
337 static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
338  SourceLocation Loc, FileOffset offs,
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 
382 static 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());
388  Loc = Loc.getLocWithOffset(offs.getOffset());
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 
454 StringRef 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 
467 EditedSource::FileEditsTy::iterator
468 EditedSource::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 }
clang::SourceManager::isMacroBodyExpansion
bool isMacroBodyExpansion(SourceLocation Loc) const
Tests whether the given source location represents the expansion of a macro body.
Definition: SourceManager.cpp:1072
clang::comments::tok::text
@ text
Definition: CommentLexer.h:35
clang::edit::EditedSource::canInsertInOffset
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
Definition: EditedSource.cpp:74
clang::edit::FileOffset::getOffset
unsigned getOffset() const
Definition: FileOffset.h:29
clang::CharSourceRange::getBegin
SourceLocation getBegin() const
Definition: SourceLocation.h:285
clang::Lexer::isAsciiIdentifierContinueChar
static bool isAsciiIdentifierContinueChar(char c, const LangOptions &LangOpts)
Returns true if the given character could appear in an identifier.
Definition: Lexer.cpp:1065
clang::edit::EditsReceiver::remove
virtual void remove(CharSourceRange range)
By default it calls replace with an empty string.
Definition: EditedSource.cpp:30
clang::Lexer::getSpelling
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:400
clang::IdentifierTable::get
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
Definition: IdentifierTable.h:592
clang::SourceLocation
Encodes a location in the source.
Definition: SourceLocation.h:88
clang::SourceLocation::getLocWithOffset
SourceLocation getLocWithOffset(IntTy Offset) const
Return a source location with the specified offset from this SourceLocation.
Definition: SourceLocation.h:136
clang::SourceManager::getImmediateExpansionRange
CharSourceRange getImmediateExpansionRange(SourceLocation Loc) const
Return the start/end of the expansion information for an expansion location.
Definition: SourceManager.cpp:1027
applyRewrite
static void applyRewrite(EditsReceiver &receiver, StringRef text, FileOffset offs, unsigned len, const SourceManager &SM, const LangOptions &LangOpts, bool shouldAdjustRemovals)
Definition: EditedSource.cpp:382
FileOffset.h
clang::edit::EditsReceiver::replace
virtual void replace(CharSourceRange range, StringRef text)=0
clang::Lexer::getSourceText
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:957
clang::edit::Commit::Edit
Definition: Commit.h:37
SourceManager.h
EditsReceiver.h
clang::edit::Commit::Act_InsertFromRange
@ Act_InsertFromRange
Definition: Commit.h:33
clang::SourceManager::isMacroArgExpansion
bool isMacroArgExpansion(SourceLocation Loc, SourceLocation *StartLoc=nullptr) const
Tests whether the given source location represents a macro argument's expansion into the function-lik...
Definition: SourceManager.cpp:1059
clang::edit::EditedSource::applyRewrites
void applyRewrites(EditsReceiver &receiver, bool adjustRemovals=true)
Definition: EditedSource.cpp:409
clang::edit::EditsReceiver
Definition: EditsReceiver.h:19
clang::transformer::edit
EditGenerator edit(ASTEdit E)
Generates a single (specified) edit.
Definition: RewriteRule.cpp:75
clang::SourceManager
This class handles loading and caching of source files into memory.
Definition: SourceManager.h:626
clang::edit::Commit::edit_iterator
SmallVectorImpl< Edit >::const_iterator edit_iterator
Definition: Commit.h:119
clang::edit::EditedSource
Definition: EditedSource.h:35
clang::driver::tools::arm::ReadTPMode::Invalid
@ Invalid
U
clang::CharSourceRange::getCharRange
static CharSourceRange getCharRange(SourceRange R)
Definition: SourceLocation.h:267
canRemoveWhitespace
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.
Definition: EditedSource.cpp:323
clang::FileID::isValid
bool isValid() const
Definition: SourceLocation.h:46
llvm::SmallString
Definition: LLVM.h:37
clang::SourceLocation::isFileID
bool isFileID() const
Definition: SourceLocation.h:104
clang::edit::EditedSource::copyString
StringRef copyString(StringRef str)
Definition: EditedSource.h:91
clang::SourceManager::getLocForStartOfFile
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file.
Definition: SourceManager.h:1118
clang::isWhitespace
LLVM_READONLY bool isWhitespace(unsigned char c)
Return true if this character is horizontal or vertical ASCII whitespace: ' ', '\t',...
Definition: CharInfo.h:92
SourceLocation.h
clang::edit::EditedSource::clearRewrites
void clearRewrites()
Definition: EditedSource.cpp:449
adjustRemoval
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.
Definition: EditedSource.cpp:337
CharInfo.h
clang::edit::Commit
Definition: Commit.h:29
canBeJoined
static bool canBeJoined(char left, char right, const LangOptions &LangOpts)
Definition: EditedSource.cpp:314
Lexer.h
clang::CharSourceRange
Represents a character-granular source range.
Definition: SourceLocation.h:255
LLVM.h
Commit.h
clang::edit::FileOffset::getWithOffset
FileOffset getWithOffset(unsigned offset) const
Definition: FileOffset.h:31
clang::edit::EditedSource::commit
bool commit(const Commit &commit)
Definition: EditedSource.cpp:276
clang::LangOptions
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:58
clang
Definition: CalledOnceCheck.h:17
clang::edit::Commit::Act_Insert
@ Act_Insert
Definition: Commit.h:32
clang::edit::EditsReceiver::insert
virtual void insert(SourceLocation loc, StringRef text)=0
clang::transformer::range
RangeSelector range(RangeSelector Begin, RangeSelector End)
DEPRECATED. Use enclose.
Definition: RangeSelector.h:41
clang::edit::FileOffset::getFID
FileID getFID() const
Definition: FileOffset.h:28
clang::Lexer::GetBeginningOfToken
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:557
clang::edit::Commit::Act_Remove
@ Act_Remove
Definition: Commit.h:34
clang::edit::FileOffset
Definition: FileOffset.h:18
SM
#define SM(sm)
Definition: Cuda.cpp:78
EditedSource.h
clang::SourceManager::getSpellingLoc
SourceLocation getSpellingLoc(SourceLocation Loc) const
Given a SourceLocation object, return the spelling location referenced by the ID.
Definition: SourceManager.h:1206