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