clang  6.0.0svn
Rewriter.cpp
Go to the documentation of this file.
1 //===--- Rewriter.cpp - Code rewriting interface --------------------------===//
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 //
10 // This file defines the Rewriter class, which is used for code
11 // transformations.
12 //
13 //===----------------------------------------------------------------------===//
14 
16 #include "clang/Basic/Diagnostic.h"
19 #include "clang/Lex/Lexer.h"
20 #include "llvm/ADT/SmallString.h"
21 #include "llvm/Support/FileSystem.h"
22 #include "llvm/Support/raw_ostream.h"
23 using namespace clang;
24 
25 raw_ostream &RewriteBuffer::write(raw_ostream &os) const {
26  // Walk RewriteRope chunks efficiently using MoveToNextPiece() instead of the
27  // character iterator.
28  for (RopePieceBTreeIterator I = begin(), E = end(); I != E;
29  I.MoveToNextPiece())
30  os << I.piece();
31  return os;
32 }
33 
34 /// \brief Return true if this character is non-new-line whitespace:
35 /// ' ', '\\t', '\\f', '\\v', '\\r'.
36 static inline bool isWhitespaceExceptNL(unsigned char c) {
37  switch (c) {
38  case ' ':
39  case '\t':
40  case '\f':
41  case '\v':
42  case '\r':
43  return true;
44  default:
45  return false;
46  }
47 }
48 
49 void RewriteBuffer::RemoveText(unsigned OrigOffset, unsigned Size,
50  bool removeLineIfEmpty) {
51  // Nothing to remove, exit early.
52  if (Size == 0) return;
53 
54  unsigned RealOffset = getMappedOffset(OrigOffset, true);
55  assert(RealOffset+Size <= Buffer.size() && "Invalid location");
56 
57  // Remove the dead characters.
58  Buffer.erase(RealOffset, Size);
59 
60  // Add a delta so that future changes are offset correctly.
61  AddReplaceDelta(OrigOffset, -Size);
62 
63  if (removeLineIfEmpty) {
64  // Find the line that the remove occurred and if it is completely empty
65  // remove the line as well.
66 
67  iterator curLineStart = begin();
68  unsigned curLineStartOffs = 0;
69  iterator posI = begin();
70  for (unsigned i = 0; i != RealOffset; ++i) {
71  if (*posI == '\n') {
72  curLineStart = posI;
73  ++curLineStart;
74  curLineStartOffs = i + 1;
75  }
76  ++posI;
77  }
78 
79  unsigned lineSize = 0;
80  posI = curLineStart;
81  while (posI != end() && isWhitespaceExceptNL(*posI)) {
82  ++posI;
83  ++lineSize;
84  }
85  if (posI != end() && *posI == '\n') {
86  Buffer.erase(curLineStartOffs, lineSize + 1/* + '\n'*/);
87  AddReplaceDelta(curLineStartOffs, -(lineSize + 1/* + '\n'*/));
88  }
89  }
90 }
91 
92 void RewriteBuffer::InsertText(unsigned OrigOffset, StringRef Str,
93  bool InsertAfter) {
94 
95  // Nothing to insert, exit early.
96  if (Str.empty()) return;
97 
98  unsigned RealOffset = getMappedOffset(OrigOffset, InsertAfter);
99  Buffer.insert(RealOffset, Str.begin(), Str.end());
100 
101  // Add a delta so that future changes are offset correctly.
102  AddInsertDelta(OrigOffset, Str.size());
103 }
104 
105 /// ReplaceText - This method replaces a range of characters in the input
106 /// buffer with a new string. This is effectively a combined "remove+insert"
107 /// operation.
108 void RewriteBuffer::ReplaceText(unsigned OrigOffset, unsigned OrigLength,
109  StringRef NewStr) {
110  unsigned RealOffset = getMappedOffset(OrigOffset, true);
111  Buffer.erase(RealOffset, OrigLength);
112  Buffer.insert(RealOffset, NewStr.begin(), NewStr.end());
113  if (OrigLength != NewStr.size())
114  AddReplaceDelta(OrigOffset, NewStr.size() - OrigLength);
115 }
116 
117 
118 //===----------------------------------------------------------------------===//
119 // Rewriter class
120 //===----------------------------------------------------------------------===//
121 
122 /// getRangeSize - Return the size in bytes of the specified range if they
123 /// are in the same file. If not, this returns -1.
125  RewriteOptions opts) const {
126  if (!isRewritable(Range.getBegin()) ||
127  !isRewritable(Range.getEnd())) return -1;
128 
129  FileID StartFileID, EndFileID;
130  unsigned StartOff, EndOff;
131 
132  StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);
133  EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);
134 
135  if (StartFileID != EndFileID)
136  return -1;
137 
138  // If edits have been made to this buffer, the delta between the range may
139  // have changed.
140  std::map<FileID, RewriteBuffer>::const_iterator I =
141  RewriteBuffers.find(StartFileID);
142  if (I != RewriteBuffers.end()) {
143  const RewriteBuffer &RB = I->second;
144  EndOff = RB.getMappedOffset(EndOff, opts.IncludeInsertsAtEndOfRange);
145  StartOff = RB.getMappedOffset(StartOff, !opts.IncludeInsertsAtBeginOfRange);
146  }
147 
148 
149  // Adjust the end offset to the end of the last token, instead of being the
150  // start of the last token if this is a token range.
151  if (Range.isTokenRange())
152  EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);
153 
154  return EndOff-StartOff;
155 }
156 
158  return getRangeSize(CharSourceRange::getTokenRange(Range), opts);
159 }
160 
161 
162 /// getRewrittenText - Return the rewritten form of the text in the specified
163 /// range. If the start or end of the range was unrewritable or if they are
164 /// in different buffers, this returns an empty string.
165 ///
166 /// Note that this method is not particularly efficient.
167 ///
168 std::string Rewriter::getRewrittenText(SourceRange Range) const {
169  if (!isRewritable(Range.getBegin()) ||
170  !isRewritable(Range.getEnd()))
171  return "";
172 
173  FileID StartFileID, EndFileID;
174  unsigned StartOff, EndOff;
175  StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID);
176  EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID);
177 
178  if (StartFileID != EndFileID)
179  return ""; // Start and end in different buffers.
180 
181  // If edits have been made to this buffer, the delta between the range may
182  // have changed.
183  std::map<FileID, RewriteBuffer>::const_iterator I =
184  RewriteBuffers.find(StartFileID);
185  if (I == RewriteBuffers.end()) {
186  // If the buffer hasn't been rewritten, just return the text from the input.
187  const char *Ptr = SourceMgr->getCharacterData(Range.getBegin());
188 
189  // Adjust the end offset to the end of the last token, instead of being the
190  // start of the last token.
191  EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);
192  return std::string(Ptr, Ptr+EndOff-StartOff);
193  }
194 
195  const RewriteBuffer &RB = I->second;
196  EndOff = RB.getMappedOffset(EndOff, true);
197  StartOff = RB.getMappedOffset(StartOff);
198 
199  // Adjust the end offset to the end of the last token, instead of being the
200  // start of the last token.
201  EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts);
202 
203  // Advance the iterators to the right spot, yay for linear time algorithms.
204  RewriteBuffer::iterator Start = RB.begin();
205  std::advance(Start, StartOff);
207  std::advance(End, EndOff-StartOff);
208 
209  return std::string(Start, End);
210 }
211 
212 unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc,
213  FileID &FID) const {
214  assert(Loc.isValid() && "Invalid location");
215  std::pair<FileID,unsigned> V = SourceMgr->getDecomposedLoc(Loc);
216  FID = V.first;
217  return V.second;
218 }
219 
220 
221 /// getEditBuffer - Get or create a RewriteBuffer for the specified FileID.
222 ///
224  std::map<FileID, RewriteBuffer>::iterator I =
225  RewriteBuffers.lower_bound(FID);
226  if (I != RewriteBuffers.end() && I->first == FID)
227  return I->second;
228  I = RewriteBuffers.insert(I, std::make_pair(FID, RewriteBuffer()));
229 
230  StringRef MB = SourceMgr->getBufferData(FID);
231  I->second.Initialize(MB.begin(), MB.end());
232 
233  return I->second;
234 }
235 
236 /// InsertText - Insert the specified string at the specified location in the
237 /// original buffer.
238 bool Rewriter::InsertText(SourceLocation Loc, StringRef Str,
239  bool InsertAfter, bool indentNewLines) {
240  if (!isRewritable(Loc)) return true;
241  FileID FID;
242  unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID);
243 
244  SmallString<128> indentedStr;
245  if (indentNewLines && Str.find('\n') != StringRef::npos) {
246  StringRef MB = SourceMgr->getBufferData(FID);
247 
248  unsigned lineNo = SourceMgr->getLineNumber(FID, StartOffs) - 1;
249  const SrcMgr::ContentCache *
250  Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache();
251  unsigned lineOffs = Content->SourceLineCache[lineNo];
252 
253  // Find the whitespace at the start of the line.
254  StringRef indentSpace;
255  {
256  unsigned i = lineOffs;
257  while (isWhitespaceExceptNL(MB[i]))
258  ++i;
259  indentSpace = MB.substr(lineOffs, i-lineOffs);
260  }
261 
263  Str.split(lines, "\n");
264 
265  for (unsigned i = 0, e = lines.size(); i != e; ++i) {
266  indentedStr += lines[i];
267  if (i < e-1) {
268  indentedStr += '\n';
269  indentedStr += indentSpace;
270  }
271  }
272  Str = indentedStr.str();
273  }
274 
275  getEditBuffer(FID).InsertText(StartOffs, Str, InsertAfter);
276  return false;
277 }
278 
280  if (!isRewritable(Loc)) return true;
281  FileID FID;
282  unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID);
283  RewriteOptions rangeOpts;
284  rangeOpts.IncludeInsertsAtBeginOfRange = false;
285  StartOffs += getRangeSize(SourceRange(Loc, Loc), rangeOpts);
286  getEditBuffer(FID).InsertText(StartOffs, Str, /*InsertAfter*/true);
287  return false;
288 }
289 
290 /// RemoveText - Remove the specified text region.
291 bool Rewriter::RemoveText(SourceLocation Start, unsigned Length,
292  RewriteOptions opts) {
293  if (!isRewritable(Start)) return true;
294  FileID FID;
295  unsigned StartOffs = getLocationOffsetAndFileID(Start, FID);
296  getEditBuffer(FID).RemoveText(StartOffs, Length, opts.RemoveLineIfEmpty);
297  return false;
298 }
299 
300 /// ReplaceText - This method replaces a range of characters in the input
301 /// buffer with a new string. This is effectively a combined "remove/insert"
302 /// operation.
303 bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength,
304  StringRef NewStr) {
305  if (!isRewritable(Start)) return true;
306  FileID StartFileID;
307  unsigned StartOffs = getLocationOffsetAndFileID(Start, StartFileID);
308 
309  getEditBuffer(StartFileID).ReplaceText(StartOffs, OrigLength, NewStr);
310  return false;
311 }
312 
313 bool Rewriter::ReplaceText(SourceRange range, SourceRange replacementRange) {
314  if (!isRewritable(range.getBegin())) return true;
315  if (!isRewritable(range.getEnd())) return true;
316  if (replacementRange.isInvalid()) return true;
317  SourceLocation start = range.getBegin();
318  unsigned origLength = getRangeSize(range);
319  unsigned newLength = getRangeSize(replacementRange);
320  FileID FID;
321  unsigned newOffs = getLocationOffsetAndFileID(replacementRange.getBegin(),
322  FID);
323  StringRef MB = SourceMgr->getBufferData(FID);
324  return ReplaceText(start, origLength, MB.substr(newOffs, newLength));
325 }
326 
328  SourceLocation parentIndent) {
329  if (range.isInvalid()) return true;
330  if (!isRewritable(range.getBegin())) return true;
331  if (!isRewritable(range.getEnd())) return true;
332  if (!isRewritable(parentIndent)) return true;
333 
334  FileID StartFileID, EndFileID, parentFileID;
335  unsigned StartOff, EndOff, parentOff;
336 
337  StartOff = getLocationOffsetAndFileID(range.getBegin(), StartFileID);
338  EndOff = getLocationOffsetAndFileID(range.getEnd(), EndFileID);
339  parentOff = getLocationOffsetAndFileID(parentIndent, parentFileID);
340 
341  if (StartFileID != EndFileID || StartFileID != parentFileID)
342  return true;
343  if (StartOff > EndOff)
344  return true;
345 
346  FileID FID = StartFileID;
347  StringRef MB = SourceMgr->getBufferData(FID);
348 
349  unsigned parentLineNo = SourceMgr->getLineNumber(FID, parentOff) - 1;
350  unsigned startLineNo = SourceMgr->getLineNumber(FID, StartOff) - 1;
351  unsigned endLineNo = SourceMgr->getLineNumber(FID, EndOff) - 1;
352 
353  const SrcMgr::ContentCache *
354  Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache();
355 
356  // Find where the lines start.
357  unsigned parentLineOffs = Content->SourceLineCache[parentLineNo];
358  unsigned startLineOffs = Content->SourceLineCache[startLineNo];
359 
360  // Find the whitespace at the start of each line.
361  StringRef parentSpace, startSpace;
362  {
363  unsigned i = parentLineOffs;
364  while (isWhitespaceExceptNL(MB[i]))
365  ++i;
366  parentSpace = MB.substr(parentLineOffs, i-parentLineOffs);
367 
368  i = startLineOffs;
369  while (isWhitespaceExceptNL(MB[i]))
370  ++i;
371  startSpace = MB.substr(startLineOffs, i-startLineOffs);
372  }
373  if (parentSpace.size() >= startSpace.size())
374  return true;
375  if (!startSpace.startswith(parentSpace))
376  return true;
377 
378  StringRef indent = startSpace.substr(parentSpace.size());
379 
380  // Indent the lines between start/end offsets.
381  RewriteBuffer &RB = getEditBuffer(FID);
382  for (unsigned lineNo = startLineNo; lineNo <= endLineNo; ++lineNo) {
383  unsigned offs = Content->SourceLineCache[lineNo];
384  unsigned i = offs;
385  while (isWhitespaceExceptNL(MB[i]))
386  ++i;
387  StringRef origIndent = MB.substr(offs, i-offs);
388  if (origIndent.startswith(startSpace))
389  RB.InsertText(offs, indent, /*InsertAfter=*/false);
390  }
391 
392  return false;
393 }
394 
395 namespace {
396 // A wrapper for a file stream that atomically overwrites the target.
397 //
398 // Creates a file output stream for a temporary file in the constructor,
399 // which is later accessible via getStream() if ok() return true.
400 // Flushes the stream and moves the temporary file to the target location
401 // in the destructor.
402 class AtomicallyMovedFile {
403 public:
404  AtomicallyMovedFile(DiagnosticsEngine &Diagnostics, StringRef Filename,
405  bool &AllWritten)
406  : Diagnostics(Diagnostics), Filename(Filename), AllWritten(AllWritten) {
407  TempFilename = Filename;
408  TempFilename += "-%%%%%%%%";
409  int FD;
410  if (llvm::sys::fs::createUniqueFile(TempFilename, FD, TempFilename)) {
411  AllWritten = false;
412  Diagnostics.Report(clang::diag::err_unable_to_make_temp)
413  << TempFilename;
414  } else {
415  FileStream.reset(new llvm::raw_fd_ostream(FD, /*shouldClose=*/true));
416  }
417  }
418 
419  ~AtomicallyMovedFile() {
420  if (!ok()) return;
421 
422  // Close (will also flush) theFileStream.
423  FileStream->close();
424  if (std::error_code ec = llvm::sys::fs::rename(TempFilename, Filename)) {
425  AllWritten = false;
426  Diagnostics.Report(clang::diag::err_unable_to_rename_temp)
427  << TempFilename << Filename << ec.message();
428  // If the remove fails, there's not a lot we can do - this is already an
429  // error.
430  llvm::sys::fs::remove(TempFilename);
431  }
432  }
433 
434  bool ok() { return (bool)FileStream; }
435  raw_ostream &getStream() { return *FileStream; }
436 
437 private:
438  DiagnosticsEngine &Diagnostics;
439  StringRef Filename;
440  SmallString<128> TempFilename;
441  std::unique_ptr<llvm::raw_fd_ostream> FileStream;
442  bool &AllWritten;
443 };
444 } // end anonymous namespace
445 
447  bool AllWritten = true;
448  for (buffer_iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) {
449  const FileEntry *Entry =
450  getSourceMgr().getFileEntryForID(I->first);
451  AtomicallyMovedFile File(getSourceMgr().getDiagnostics(), Entry->getName(),
452  AllWritten);
453  if (File.ok()) {
454  I->second.write(File.getStream());
455  }
456  }
457  return !AllWritten;
458 }
void insert(unsigned Offset, const char *Start, const char *End)
Definition: RewriteRope.h:196
bool IncreaseIndentation(CharSourceRange range, SourceLocation parentIndent)
Increase indentation for the lines between the given source range.
Definition: Rewriter.cpp:327
Defines the SourceManager interface.
static CharSourceRange getTokenRange(SourceRange R)
DiagnosticBuilder Report(SourceLocation Loc, unsigned DiagID)
Issue the message to the client.
Definition: Diagnostic.h:1207
bool RemoveText(SourceLocation Start, unsigned Length, RewriteOptions opts=RewriteOptions())
RemoveText - Remove the specified text region.
Definition: Rewriter.cpp:291
void erase(unsigned Offset, unsigned NumBytes)
Definition: RewriteRope.h:202
RewriteBuffer & getEditBuffer(FileID FID)
getEditBuffer - This is like getRewriteBufferFor, but always returns a buffer, and allows you to writ...
Definition: Rewriter.cpp:223
RewriteBuffer - As code is rewritten, SourceBuffer&#39;s from the original input with modifications get a...
Definition: RewriteBuffer.h:27
iterator end() const
Definition: RewriteBuffer.h:36
std::string getRewrittenText(SourceRange Range) const
getRewrittenText - Return the rewritten form of the text in the specified range.
Definition: Rewriter.cpp:168
SourceLocation getBegin() const
One instance of this struct is kept for every file loaded or used.
Definition: SourceManager.h:95
static int getRangeSize(const SourceManager &Sources, const CharSourceRange &Range, const LangOptions &LangOpts)
bool ReplaceText(SourceLocation Start, unsigned OrigLength, StringRef NewStr)
ReplaceText - This method replaces a range of characters in the input buffer with a new string...
Definition: Rewriter.cpp:303
Concrete class used by the front-end to report problems and issues.
Definition: Diagnostic.h:147
Defines the Diagnostic-related interfaces.
StringRef Filename
Definition: Format.cpp:1345
SourceLocation End
Represents a character-granular source range.
static unsigned MeasureTokenLength(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
MeasureTokenLength - Relex the token at the specified location and return its length in bytes in the ...
Definition: Lexer.cpp:435
bool isInvalid() const
SourceLocation getEnd() const
int getRangeSize(SourceRange Range, RewriteOptions opts=RewriteOptions()) const
getRangeSize - Return the size in bytes of the specified range if they are in the same file...
Definition: Rewriter.cpp:157
void InsertText(unsigned OrigOffset, StringRef Str, bool InsertAfter=true)
InsertText - Insert some text at the specified point, where the offset in the buffer is specified rel...
Definition: Rewriter.cpp:92
bool overwriteChangedFiles()
overwriteChangedFiles - Save all changed files to disk.
Definition: Rewriter.cpp:446
iterator begin() const
Definition: RewriteBuffer.h:35
void ReplaceText(unsigned OrigOffset, unsigned OrigLength, StringRef NewStr)
ReplaceText - This method replaces a range of characters in the input buffer with a new string...
Definition: Rewriter.cpp:108
Encodes a location in the source.
static bool isWhitespaceExceptNL(unsigned char c)
Return true if this character is non-new-line whitespace: &#39; &#39;, &#39;\t&#39;, &#39;\f&#39;, &#39;\v&#39;, &#39;\r&#39;.
Definition: Rewriter.cpp:36
raw_ostream & write(raw_ostream &Stream) const
Write to Stream the result of applying all changes to the original buffer.
Definition: Rewriter.cpp:25
StringRef getName() const
Definition: FileManager.h:84
unsigned size() const
Definition: RewriteRope.h:184
Cached information about one file (either on disk or in the virtual file system). ...
Definition: FileManager.h:59
bool InsertText(SourceLocation Loc, StringRef Str, bool InsertAfter=true, bool indentNewLines=false)
InsertText - Insert the specified string at the specified location in the original buffer...
Definition: Rewriter.cpp:238
bool RemoveLineIfEmpty
If true and removing some text leaves a blank line also remove the empty line (false by default)...
Definition: Rewriter.h:45
void RemoveText(unsigned OrigOffset, unsigned Size, bool removeLineIfEmpty=false)
RemoveText - Remove the specified text.
Definition: Rewriter.cpp:49
unsigned * SourceLineCache
A bump pointer allocated array of offsets for each source line.
bool isTokenRange() const
Return true if the end of this range specifies the start of the last token.
An opaque identifier used by SourceManager which refers to a source file (MemoryBuffer) along with it...
Dataflow Directional Tag Classes.
bool isValid() const
Return true if this is a valid SourceLocation object.
RopePieceBTreeIterator - This class provides read-only forward iteration over bytes that are in a Rop...
Definition: RewriteRope.h:88
bool IncludeInsertsAtBeginOfRange
Given a source range, true to include previous inserts at the beginning of the range as part of the r...
Definition: Rewriter.h:39
std::map< FileID, RewriteBuffer >::iterator buffer_iterator
Definition: Rewriter.h:53
SourceLocation getEnd() const
Defines the Diagnostic IDs-related interfaces.
A trivial tuple used to represent a source range.
bool InsertTextAfterToken(SourceLocation Loc, StringRef Str)
Insert the specified string after the token in the specified location.
Definition: Rewriter.cpp:279
SourceLocation getBegin() const
bool IncludeInsertsAtEndOfRange
Given a source range, true to include previous inserts at the end of the range as part of the range i...
Definition: Rewriter.h:42