clang  10.0.0svn
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.getRawEncoding()];
63  if (llvm::find(ArgUses, ArgUse) == ArgUses.end())
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.getRawEncoding());
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::isIdentifierBodyChar(left, LangOpts) &&
318  Lexer::isIdentifierBodyChar(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 }
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file. ...
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:397
SourceLocation getLocWithOffset(int Offset) const
Return a source location with the specified offset from this SourceLocation.
unsigned getRawEncoding() const
When a SourceLocation itself cannot be used, this returns an (opaque) 32-bit integer encoding for it...
Defines the SourceManager interface.
SmallVectorImpl< Edit >::const_iterator edit_iterator
Definition: Commit.h:119
StringRef getBufferData(FileID FID, bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
RangeSelector range(RangeSelector Begin, RangeSelector End)
Selects from the start of Begin and to the end of End.
edit_iterator edit_end() const
Definition: Commit.h:122
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:49
LLVM_READONLY bool isWhitespace(unsigned char c)
Return true if this character is horizontal or vertical ASCII whitespace: &#39; &#39;, &#39;\t&#39;, &#39;\f&#39;, &#39;\v&#39;, &#39;\n&#39;, &#39;\r&#39;.
Definition: CharInfo.h:87
FileID getFID() const
Definition: FileOffset.h:28
Forward-declares and imports various common LLVM datatypes that clang wants to use unqualified...
SourceLocation OrigLoc
Definition: Commit.h:40
bool isValid() const
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:939
Represents a character-granular source range.
virtual void replace(CharSourceRange range, StringRef text)=0
edit_iterator edit_begin() const
Definition: Commit.h:121
FileOffset InsertFromRangeOffs
Definition: Commit.h:42
static const char * copyString(StringRef Str, llvm::BumpPtrAllocator &Alloc)
virtual void remove(CharSourceRange range)
By default it calls replace with an empty string.
const SourceManager & SM
Definition: Format.cpp:1609
FileOffset getWithOffset(unsigned offset) const
Definition: FileOffset.h:31
static CharSourceRange getCharRange(SourceRange R)
Encodes a location in the source.
static bool canBeJoined(char left, char right, const LangOptions &LangOpts)
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:554
StringRef copyString(StringRef str)
Definition: EditedSource.h:91
Dataflow Directional Tag Classes.
bool isCommitable() const
Definition: Commit.h:68
unsigned getOffset() const
Definition: FileOffset.h:29
bool commit(const Commit &commit)
static bool isIdentifierBodyChar(char c, const LangOptions &LangOpts)
Returns true if the given character could appear in an identifier.
Definition: Lexer.cpp:1047
bool canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs)
void applyRewrites(EditsReceiver &receiver, bool adjustRemovals=true)
Defines the clang::SourceLocation class and associated facilities.
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.
virtual void insert(SourceLocation loc, StringRef text)=0
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 void applyRewrite(EditsReceiver &receiver, StringRef text, FileOffset offs, unsigned len, const SourceManager &SM, const LangOptions &LangOpts, bool shouldAdjustRemovals)
This class handles loading and caching of source files into memory.