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