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 15#include "clang/Rewrite/Core/Rewriter.h" 16#include "clang/AST/Stmt.h" 17#include "clang/AST/Decl.h" 18#include "clang/Basic/DiagnosticIDs.h" 19#include "clang/Basic/FileManager.h" 20#include "clang/Basic/SourceManager.h" 21#include "clang/Lex/Lexer.h" 22#include "llvm/ADT/SmallString.h" 23#include "llvm/Support/FileSystem.h" 24using namespace clang; 25 26raw_ostream &RewriteBuffer::write(raw_ostream &os) const { 27 // FIXME: eliminate the copy by writing out each chunk at a time 28 os << std::string(begin(), end()); 29 return os; 30} 31 32/// \brief Return true if this character is non-new-line whitespace: 33/// ' ', '\\t', '\\f', '\\v', '\\r'. 34static inline bool isWhitespace(unsigned char c) { 35 switch (c) { 36 case ' ': 37 case '\t': 38 case '\f': 39 case '\v': 40 case '\r': 41 return true; 42 default: 43 return false; 44 } 45} 46 47void RewriteBuffer::RemoveText(unsigned OrigOffset, unsigned Size, 48 bool removeLineIfEmpty) { 49 // Nothing to remove, exit early. 50 if (Size == 0) return; 51 52 unsigned RealOffset = getMappedOffset(OrigOffset, true); 53 assert(RealOffset+Size < Buffer.size() && "Invalid location"); 54 55 // Remove the dead characters. 56 Buffer.erase(RealOffset, Size); 57 58 // Add a delta so that future changes are offset correctly. 59 AddReplaceDelta(OrigOffset, -Size); 60 61 if (removeLineIfEmpty) { 62 // Find the line that the remove occurred and if it is completely empty 63 // remove the line as well. 64 65 iterator curLineStart = begin(); 66 unsigned curLineStartOffs = 0; 67 iterator posI = begin(); 68 for (unsigned i = 0; i != RealOffset; ++i) { 69 if (*posI == '\n') { 70 curLineStart = posI; 71 ++curLineStart; 72 curLineStartOffs = i + 1; 73 } 74 ++posI; 75 } 76 77 unsigned lineSize = 0; 78 posI = curLineStart; 79 while (posI != end() && isWhitespace(*posI)) { 80 ++posI; 81 ++lineSize; 82 } 83 if (posI != end() && *posI == '\n') { 84 Buffer.erase(curLineStartOffs, lineSize + 1/* + '\n'*/); 85 AddReplaceDelta(curLineStartOffs, -(lineSize + 1/* + '\n'*/)); 86 } 87 } 88} 89 90void RewriteBuffer::InsertText(unsigned OrigOffset, StringRef Str, 91 bool InsertAfter) { 92 93 // Nothing to insert, exit early. 94 if (Str.empty()) return; 95 96 unsigned RealOffset = getMappedOffset(OrigOffset, InsertAfter); 97 Buffer.insert(RealOffset, Str.begin(), Str.end()); 98 99 // Add a delta so that future changes are offset correctly. 100 AddInsertDelta(OrigOffset, Str.size()); 101} 102 103/// ReplaceText - This method replaces a range of characters in the input 104/// buffer with a new string. This is effectively a combined "remove+insert" 105/// operation. 106void RewriteBuffer::ReplaceText(unsigned OrigOffset, unsigned OrigLength, 107 StringRef NewStr) { 108 unsigned RealOffset = getMappedOffset(OrigOffset, true); 109 Buffer.erase(RealOffset, OrigLength); 110 Buffer.insert(RealOffset, NewStr.begin(), NewStr.end()); 111 if (OrigLength != NewStr.size()) 112 AddReplaceDelta(OrigOffset, NewStr.size() - OrigLength); 113} 114 115 116//===----------------------------------------------------------------------===// 117// Rewriter class 118//===----------------------------------------------------------------------===// 119 120/// getRangeSize - Return the size in bytes of the specified range if they 121/// are in the same file. If not, this returns -1. 122int Rewriter::getRangeSize(const CharSourceRange &Range, 123 RewriteOptions opts) const { 124 if (!isRewritable(Range.getBegin()) || 125 !isRewritable(Range.getEnd())) return -1; 126 127 FileID StartFileID, EndFileID; 128 unsigned StartOff, EndOff; 129 130 StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID); 131 EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID); 132 133 if (StartFileID != EndFileID) 134 return -1; 135 136 // If edits have been made to this buffer, the delta between the range may 137 // have changed. 138 std::map<FileID, RewriteBuffer>::const_iterator I = 139 RewriteBuffers.find(StartFileID); 140 if (I != RewriteBuffers.end()) { 141 const RewriteBuffer &RB = I->second; 142 EndOff = RB.getMappedOffset(EndOff, opts.IncludeInsertsAtEndOfRange); 143 StartOff = RB.getMappedOffset(StartOff, !opts.IncludeInsertsAtBeginOfRange); 144 } 145 146 147 // Adjust the end offset to the end of the last token, instead of being the 148 // start of the last token if this is a token range. 149 if (Range.isTokenRange()) 150 EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); 151 152 return EndOff-StartOff; 153} 154 155int Rewriter::getRangeSize(SourceRange Range, RewriteOptions opts) const { 156 return getRangeSize(CharSourceRange::getTokenRange(Range), opts); 157} 158 159 160/// getRewrittenText - Return the rewritten form of the text in the specified 161/// range. If the start or end of the range was unrewritable or if they are 162/// in different buffers, this returns an empty string. 163/// 164/// Note that this method is not particularly efficient. 165/// 166std::string Rewriter::getRewrittenText(SourceRange Range) const { 167 if (!isRewritable(Range.getBegin()) || 168 !isRewritable(Range.getEnd())) 169 return ""; 170 171 FileID StartFileID, EndFileID; 172 unsigned StartOff, EndOff; 173 StartOff = getLocationOffsetAndFileID(Range.getBegin(), StartFileID); 174 EndOff = getLocationOffsetAndFileID(Range.getEnd(), EndFileID); 175 176 if (StartFileID != EndFileID) 177 return ""; // Start and end in different buffers. 178 179 // If edits have been made to this buffer, the delta between the range may 180 // have changed. 181 std::map<FileID, RewriteBuffer>::const_iterator I = 182 RewriteBuffers.find(StartFileID); 183 if (I == RewriteBuffers.end()) { 184 // If the buffer hasn't been rewritten, just return the text from the input. 185 const char *Ptr = SourceMgr->getCharacterData(Range.getBegin()); 186 187 // Adjust the end offset to the end of the last token, instead of being the 188 // start of the last token. 189 EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); 190 return std::string(Ptr, Ptr+EndOff-StartOff); 191 } 192 193 const RewriteBuffer &RB = I->second; 194 EndOff = RB.getMappedOffset(EndOff, true); 195 StartOff = RB.getMappedOffset(StartOff); 196 197 // Adjust the end offset to the end of the last token, instead of being the 198 // start of the last token. 199 EndOff += Lexer::MeasureTokenLength(Range.getEnd(), *SourceMgr, *LangOpts); 200 201 // Advance the iterators to the right spot, yay for linear time algorithms. 202 RewriteBuffer::iterator Start = RB.begin(); 203 std::advance(Start, StartOff); 204 RewriteBuffer::iterator End = Start; 205 std::advance(End, EndOff-StartOff); 206 207 return std::string(Start, End); 208} 209 210unsigned Rewriter::getLocationOffsetAndFileID(SourceLocation Loc, 211 FileID &FID) const { 212 assert(Loc.isValid() && "Invalid location"); 213 std::pair<FileID,unsigned> V = SourceMgr->getDecomposedLoc(Loc); 214 FID = V.first; 215 return V.second; 216} 217 218 219/// getEditBuffer - Get or create a RewriteBuffer for the specified FileID. 220/// 221RewriteBuffer &Rewriter::getEditBuffer(FileID FID) { 222 std::map<FileID, RewriteBuffer>::iterator I = 223 RewriteBuffers.lower_bound(FID); 224 if (I != RewriteBuffers.end() && I->first == FID) 225 return I->second; 226 I = RewriteBuffers.insert(I, std::make_pair(FID, RewriteBuffer())); 227 228 StringRef MB = SourceMgr->getBufferData(FID); 229 I->second.Initialize(MB.begin(), MB.end()); 230 231 return I->second; 232} 233 234/// InsertText - Insert the specified string at the specified location in the 235/// original buffer. 236bool Rewriter::InsertText(SourceLocation Loc, StringRef Str, 237 bool InsertAfter, bool indentNewLines) { 238 if (!isRewritable(Loc)) return true; 239 FileID FID; 240 unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID); 241 242 SmallString<128> indentedStr; 243 if (indentNewLines && Str.find('\n') != StringRef::npos) { 244 StringRef MB = SourceMgr->getBufferData(FID); 245 246 unsigned lineNo = SourceMgr->getLineNumber(FID, StartOffs) - 1; 247 const SrcMgr::ContentCache * 248 Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache(); 249 unsigned lineOffs = Content->SourceLineCache[lineNo]; 250 251 // Find the whitespace at the start of the line. 252 StringRef indentSpace; 253 { 254 unsigned i = lineOffs; 255 while (isWhitespace(MB[i])) 256 ++i; 257 indentSpace = MB.substr(lineOffs, i-lineOffs); 258 } 259 260 SmallVector<StringRef, 4> lines; 261 Str.split(lines, "\n"); 262 263 for (unsigned i = 0, e = lines.size(); i != e; ++i) { 264 indentedStr += lines[i]; 265 if (i < e-1) { 266 indentedStr += '\n'; 267 indentedStr += indentSpace; 268 } 269 } 270 Str = indentedStr.str(); 271 } 272 273 getEditBuffer(FID).InsertText(StartOffs, Str, InsertAfter); 274 return false; 275} 276 277bool Rewriter::InsertTextAfterToken(SourceLocation Loc, StringRef Str) { 278 if (!isRewritable(Loc)) return true; 279 FileID FID; 280 unsigned StartOffs = getLocationOffsetAndFileID(Loc, FID); 281 RewriteOptions rangeOpts; 282 rangeOpts.IncludeInsertsAtBeginOfRange = false; 283 StartOffs += getRangeSize(SourceRange(Loc, Loc), rangeOpts); 284 getEditBuffer(FID).InsertText(StartOffs, Str, /*InsertAfter*/true); 285 return false; 286} 287 288/// RemoveText - Remove the specified text region. 289bool Rewriter::RemoveText(SourceLocation Start, unsigned Length, 290 RewriteOptions opts) { 291 if (!isRewritable(Start)) return true; 292 FileID FID; 293 unsigned StartOffs = getLocationOffsetAndFileID(Start, FID); 294 getEditBuffer(FID).RemoveText(StartOffs, Length, opts.RemoveLineIfEmpty); 295 return false; 296} 297 298/// ReplaceText - This method replaces a range of characters in the input 299/// buffer with a new string. This is effectively a combined "remove/insert" 300/// operation. 301bool Rewriter::ReplaceText(SourceLocation Start, unsigned OrigLength, 302 StringRef NewStr) { 303 if (!isRewritable(Start)) return true; 304 FileID StartFileID; 305 unsigned StartOffs = getLocationOffsetAndFileID(Start, StartFileID); 306 307 getEditBuffer(StartFileID).ReplaceText(StartOffs, OrigLength, NewStr); 308 return false; 309} 310 311bool Rewriter::ReplaceText(SourceRange range, SourceRange replacementRange) { 312 if (!isRewritable(range.getBegin())) return true; 313 if (!isRewritable(range.getEnd())) return true; 314 if (replacementRange.isInvalid()) return true; 315 SourceLocation start = range.getBegin(); 316 unsigned origLength = getRangeSize(range); 317 unsigned newLength = getRangeSize(replacementRange); 318 FileID FID; 319 unsigned newOffs = getLocationOffsetAndFileID(replacementRange.getBegin(), 320 FID); 321 StringRef MB = SourceMgr->getBufferData(FID); 322 return ReplaceText(start, origLength, MB.substr(newOffs, newLength)); 323} 324 325/// ReplaceStmt - This replaces a Stmt/Expr with another, using the pretty 326/// printer to generate the replacement code. This returns true if the input 327/// could not be rewritten, or false if successful. 328bool Rewriter::ReplaceStmt(Stmt *From, Stmt *To) { 329 // Measaure the old text. 330 int Size = getRangeSize(From->getSourceRange()); 331 if (Size == -1) 332 return true; 333 334 // Get the new text. 335 std::string SStr; 336 llvm::raw_string_ostream S(SStr); 337 To->printPretty(S, 0, PrintingPolicy(*LangOpts)); 338 const std::string &Str = S.str(); 339 340 ReplaceText(From->getLocStart(), Size, Str); 341 return false; 342} 343 344std::string Rewriter::ConvertToString(Stmt *From) { 345 std::string SStr; 346 llvm::raw_string_ostream S(SStr); 347 From->printPretty(S, 0, PrintingPolicy(*LangOpts)); 348 return S.str(); 349} 350 351bool Rewriter::IncreaseIndentation(CharSourceRange range, 352 SourceLocation parentIndent) { 353 if (range.isInvalid()) return true; 354 if (!isRewritable(range.getBegin())) return true; 355 if (!isRewritable(range.getEnd())) return true; 356 if (!isRewritable(parentIndent)) return true; 357 358 FileID StartFileID, EndFileID, parentFileID; 359 unsigned StartOff, EndOff, parentOff; 360 361 StartOff = getLocationOffsetAndFileID(range.getBegin(), StartFileID); 362 EndOff = getLocationOffsetAndFileID(range.getEnd(), EndFileID); 363 parentOff = getLocationOffsetAndFileID(parentIndent, parentFileID); 364 365 if (StartFileID != EndFileID || StartFileID != parentFileID) 366 return true; 367 if (StartOff > EndOff) 368 return true; 369 370 FileID FID = StartFileID; 371 StringRef MB = SourceMgr->getBufferData(FID); 372 373 unsigned parentLineNo = SourceMgr->getLineNumber(FID, parentOff) - 1; 374 unsigned startLineNo = SourceMgr->getLineNumber(FID, StartOff) - 1; 375 unsigned endLineNo = SourceMgr->getLineNumber(FID, EndOff) - 1; 376 377 const SrcMgr::ContentCache * 378 Content = SourceMgr->getSLocEntry(FID).getFile().getContentCache(); 379 380 // Find where the lines start. 381 unsigned parentLineOffs = Content->SourceLineCache[parentLineNo]; 382 unsigned startLineOffs = Content->SourceLineCache[startLineNo]; 383 384 // Find the whitespace at the start of each line. 385 StringRef parentSpace, startSpace; 386 { 387 unsigned i = parentLineOffs; 388 while (isWhitespace(MB[i])) 389 ++i; 390 parentSpace = MB.substr(parentLineOffs, i-parentLineOffs); 391 392 i = startLineOffs; 393 while (isWhitespace(MB[i])) 394 ++i; 395 startSpace = MB.substr(startLineOffs, i-startLineOffs); 396 } 397 if (parentSpace.size() >= startSpace.size()) 398 return true; 399 if (!startSpace.startswith(parentSpace)) 400 return true; 401 402 StringRef indent = startSpace.substr(parentSpace.size()); 403 404 // Indent the lines between start/end offsets. 405 RewriteBuffer &RB = getEditBuffer(FID); 406 for (unsigned lineNo = startLineNo; lineNo <= endLineNo; ++lineNo) { 407 unsigned offs = Content->SourceLineCache[lineNo]; 408 unsigned i = offs; 409 while (isWhitespace(MB[i])) 410 ++i; 411 StringRef origIndent = MB.substr(offs, i-offs); 412 if (origIndent.startswith(startSpace)) 413 RB.InsertText(offs, indent, /*InsertAfter=*/false); 414 } 415 416 return false; 417} 418 419// A wrapper for a file stream that atomically overwrites the target. 420// 421// Creates a file output stream for a temporary file in the constructor, 422// which is later accessible via getStream() if ok() return true. 423// Flushes the stream and moves the temporary file to the target location 424// in the destructor. 425class AtomicallyMovedFile { 426public: 427 AtomicallyMovedFile(DiagnosticsEngine &Diagnostics, StringRef Filename, 428 bool &AllWritten) 429 : Diagnostics(Diagnostics), Filename(Filename), AllWritten(AllWritten) { 430 TempFilename = Filename; 431 TempFilename += "-%%%%%%%%"; 432 int FD; 433 if (llvm::sys::fs::unique_file(TempFilename.str(), FD, TempFilename, 434 /*makeAbsolute=*/true, 0664)) { 435 AllWritten = false; 436 Diagnostics.Report(clang::diag::err_unable_to_make_temp) 437 << TempFilename; 438 } else { 439 FileStream.reset(new llvm::raw_fd_ostream(FD, /*shouldClose=*/true)); 440 } 441 } 442 443 ~AtomicallyMovedFile() { 444 if (!ok()) return; 445 446 FileStream->flush(); 447#ifdef _WIN32 448 // Win32 does not allow rename/removing opened files. 449 FileStream.reset(); 450#endif 451 if (llvm::error_code ec = 452 llvm::sys::fs::rename(TempFilename.str(), Filename)) { 453 AllWritten = false; 454 Diagnostics.Report(clang::diag::err_unable_to_rename_temp) 455 << TempFilename << Filename << ec.message(); 456 bool existed; 457 // If the remove fails, there's not a lot we can do - this is already an 458 // error. 459 llvm::sys::fs::remove(TempFilename.str(), existed); 460 } 461 } 462 463 bool ok() { return FileStream; } 464 llvm::raw_ostream &getStream() { return *FileStream; } 465 466private: 467 DiagnosticsEngine &Diagnostics; 468 StringRef Filename; 469 SmallString<128> TempFilename; 470 OwningPtr<llvm::raw_fd_ostream> FileStream; 471 bool &AllWritten; 472}; 473 474bool Rewriter::overwriteChangedFiles() { 475 bool AllWritten = true; 476 for (buffer_iterator I = buffer_begin(), E = buffer_end(); I != E; ++I) { 477 const FileEntry *Entry = 478 getSourceMgr().getFileEntryForID(I->first); 479 AtomicallyMovedFile File(getSourceMgr().getDiagnostics(), Entry->getName(), 480 AllWritten); 481 if (File.ok()) { 482 I->second.write(File.getStream()); 483 } 484 } 485 return !AllWritten; 486} 487