1 //===--- Replacement.cpp - Framework for clang refactoring tools ----------===// 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 // Implements classes to support/store refactorings. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/Tooling/Core/Replacement.h" 15 16 #include "clang/Basic/Diagnostic.h" 17 #include "clang/Basic/DiagnosticIDs.h" 18 #include "clang/Basic/DiagnosticOptions.h" 19 #include "clang/Basic/FileManager.h" 20 #include "clang/Basic/SourceManager.h" 21 #include "clang/Lex/Lexer.h" 22 #include "clang/Rewrite/Core/Rewriter.h" 23 #include "llvm/Support/FileSystem.h" 24 #include "llvm/Support/Path.h" 25 #include "llvm/Support/raw_os_ostream.h" 26 27 namespace clang { 28 namespace tooling { 29 30 static const char * const InvalidLocation = ""; 31 32 Replacement::Replacement() 33 : FilePath(InvalidLocation) {} 34 35 Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, 36 StringRef ReplacementText) 37 : FilePath(FilePath), ReplacementRange(Offset, Length), 38 ReplacementText(ReplacementText) {} 39 40 Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, 41 unsigned Length, StringRef ReplacementText) { 42 setFromSourceLocation(Sources, Start, Length, ReplacementText); 43 } 44 45 Replacement::Replacement(const SourceManager &Sources, 46 const CharSourceRange &Range, 47 StringRef ReplacementText, 48 const LangOptions &LangOpts) { 49 setFromSourceRange(Sources, Range, ReplacementText, LangOpts); 50 } 51 52 bool Replacement::isApplicable() const { 53 return FilePath != InvalidLocation; 54 } 55 56 bool Replacement::apply(Rewriter &Rewrite) const { 57 SourceManager &SM = Rewrite.getSourceMgr(); 58 const FileEntry *Entry = SM.getFileManager().getFile(FilePath); 59 if (!Entry) 60 return false; 61 FileID ID; 62 // FIXME: Use SM.translateFile directly. 63 SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1); 64 ID = Location.isValid() ? 65 SM.getFileID(Location) : 66 SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User); 67 // FIXME: We cannot check whether Offset + Length is in the file, as 68 // the remapping API is not public in the RewriteBuffer. 69 const SourceLocation Start = 70 SM.getLocForStartOfFile(ID). 71 getLocWithOffset(ReplacementRange.getOffset()); 72 // ReplaceText returns false on success. 73 // ReplaceText only fails if the source location is not a file location, in 74 // which case we already returned false earlier. 75 bool RewriteSucceeded = !Rewrite.ReplaceText( 76 Start, ReplacementRange.getLength(), ReplacementText); 77 assert(RewriteSucceeded); 78 return RewriteSucceeded; 79 } 80 81 std::string Replacement::toString() const { 82 std::string Result; 83 llvm::raw_string_ostream Stream(Result); 84 Stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" 85 << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; 86 return Stream.str(); 87 } 88 89 bool operator<(const Replacement &LHS, const Replacement &RHS) { 90 if (LHS.getOffset() != RHS.getOffset()) 91 return LHS.getOffset() < RHS.getOffset(); 92 93 // Apply longer replacements first, specifically so that deletions are 94 // executed before insertions. It is (hopefully) never the intention to 95 // delete parts of newly inserted code. 96 if (LHS.getLength() != RHS.getLength()) 97 return LHS.getLength() > RHS.getLength(); 98 99 if (LHS.getFilePath() != RHS.getFilePath()) 100 return LHS.getFilePath() < RHS.getFilePath(); 101 return LHS.getReplacementText() < RHS.getReplacementText(); 102 } 103 104 bool operator==(const Replacement &LHS, const Replacement &RHS) { 105 return LHS.getOffset() == RHS.getOffset() && 106 LHS.getLength() == RHS.getLength() && 107 LHS.getFilePath() == RHS.getFilePath() && 108 LHS.getReplacementText() == RHS.getReplacementText(); 109 } 110 111 void Replacement::setFromSourceLocation(const SourceManager &Sources, 112 SourceLocation Start, unsigned Length, 113 StringRef ReplacementText) { 114 const std::pair<FileID, unsigned> DecomposedLocation = 115 Sources.getDecomposedLoc(Start); 116 const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); 117 this->FilePath = Entry ? Entry->getName() : InvalidLocation; 118 this->ReplacementRange = Range(DecomposedLocation.second, Length); 119 this->ReplacementText = ReplacementText; 120 } 121 122 // FIXME: This should go into the Lexer, but we need to figure out how 123 // to handle ranges for refactoring in general first - there is no obvious 124 // good way how to integrate this into the Lexer yet. 125 static int getRangeSize(const SourceManager &Sources, 126 const CharSourceRange &Range, 127 const LangOptions &LangOpts) { 128 SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); 129 SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); 130 std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin); 131 std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd); 132 if (Start.first != End.first) return -1; 133 if (Range.isTokenRange()) 134 End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, LangOpts); 135 return End.second - Start.second; 136 } 137 138 void Replacement::setFromSourceRange(const SourceManager &Sources, 139 const CharSourceRange &Range, 140 StringRef ReplacementText, 141 const LangOptions &LangOpts) { 142 setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), 143 getRangeSize(Sources, Range, LangOpts), 144 ReplacementText); 145 } 146 147 template <typename T> 148 unsigned shiftedCodePositionInternal(const T &Replaces, unsigned Position) { 149 unsigned Offset = 0; 150 for (const auto& R : Replaces) { 151 if (R.getOffset() + R.getLength() <= Position) { 152 Offset += R.getReplacementText().size() - R.getLength(); 153 continue; 154 } 155 if (R.getOffset() < Position && 156 R.getOffset() + R.getReplacementText().size() <= Position) { 157 Position = R.getOffset() + R.getReplacementText().size() - 1; 158 } 159 break; 160 } 161 return Position + Offset; 162 } 163 164 unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { 165 return shiftedCodePositionInternal(Replaces, Position); 166 } 167 168 // FIXME: Remove this function when Replacements is implemented as std::vector 169 // instead of std::set. 170 unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces, 171 unsigned Position) { 172 return shiftedCodePositionInternal(Replaces, Position); 173 } 174 175 void deduplicate(std::vector<Replacement> &Replaces, 176 std::vector<Range> &Conflicts) { 177 if (Replaces.empty()) 178 return; 179 180 auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) { 181 if (LHS.getOffset() != RHS.getOffset()) 182 return LHS.getOffset() < RHS.getOffset(); 183 if (LHS.getLength() != RHS.getLength()) 184 return LHS.getLength() < RHS.getLength(); 185 return LHS.getReplacementText() < RHS.getReplacementText(); 186 }; 187 188 auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) { 189 return LHS.getOffset() == RHS.getOffset() && 190 LHS.getLength() == RHS.getLength() && 191 LHS.getReplacementText() == RHS.getReplacementText(); 192 }; 193 194 // Deduplicate. We don't want to deduplicate based on the path as we assume 195 // that all replacements refer to the same file (or are symlinks). 196 std::sort(Replaces.begin(), Replaces.end(), LessNoPath); 197 Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), 198 Replaces.end()); 199 200 // Detect conflicts 201 Range ConflictRange(Replaces.front().getOffset(), 202 Replaces.front().getLength()); 203 unsigned ConflictStart = 0; 204 unsigned ConflictLength = 1; 205 for (unsigned i = 1; i < Replaces.size(); ++i) { 206 Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); 207 if (ConflictRange.overlapsWith(Current)) { 208 // Extend conflicted range 209 ConflictRange = Range(ConflictRange.getOffset(), 210 std::max(ConflictRange.getLength(), 211 Current.getOffset() + Current.getLength() - 212 ConflictRange.getOffset())); 213 ++ConflictLength; 214 } else { 215 if (ConflictLength > 1) 216 Conflicts.push_back(Range(ConflictStart, ConflictLength)); 217 ConflictRange = Current; 218 ConflictStart = i; 219 ConflictLength = 1; 220 } 221 } 222 223 if (ConflictLength > 1) 224 Conflicts.push_back(Range(ConflictStart, ConflictLength)); 225 } 226 227 bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { 228 bool Result = true; 229 for (Replacements::const_iterator I = Replaces.begin(), 230 E = Replaces.end(); 231 I != E; ++I) { 232 if (I->isApplicable()) { 233 Result = I->apply(Rewrite) && Result; 234 } else { 235 Result = false; 236 } 237 } 238 return Result; 239 } 240 241 // FIXME: Remove this function when Replacements is implemented as std::vector 242 // instead of std::set. 243 bool applyAllReplacements(const std::vector<Replacement> &Replaces, 244 Rewriter &Rewrite) { 245 bool Result = true; 246 for (std::vector<Replacement>::const_iterator I = Replaces.begin(), 247 E = Replaces.end(); 248 I != E; ++I) { 249 if (I->isApplicable()) { 250 Result = I->apply(Rewrite) && Result; 251 } else { 252 Result = false; 253 } 254 } 255 return Result; 256 } 257 258 std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { 259 IntrusiveRefCntPtr<vfs::InMemoryFileSystem> InMemoryFileSystem( 260 new vfs::InMemoryFileSystem); 261 FileManager Files(FileSystemOptions(), InMemoryFileSystem); 262 DiagnosticsEngine Diagnostics( 263 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 264 new DiagnosticOptions); 265 SourceManager SourceMgr(Diagnostics, Files); 266 Rewriter Rewrite(SourceMgr, LangOptions()); 267 InMemoryFileSystem->addFile( 268 "<stdin>", 0, llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>")); 269 FileID ID = SourceMgr.createFileID(Files.getFile("<stdin>"), SourceLocation(), 270 clang::SrcMgr::C_User); 271 for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); 272 I != E; ++I) { 273 Replacement Replace("<stdin>", I->getOffset(), I->getLength(), 274 I->getReplacementText()); 275 if (!Replace.apply(Rewrite)) 276 return ""; 277 } 278 std::string Result; 279 llvm::raw_string_ostream OS(Result); 280 Rewrite.getEditBuffer(ID).write(OS); 281 OS.flush(); 282 return Result; 283 } 284 285 std::vector<Range> calculateChangedRangesInFile(const Replacements &Replaces) { 286 std::vector<Range> ChangedRanges; 287 int Shift = 0; 288 for (const Replacement &R : Replaces) { 289 unsigned Offset = R.getOffset() + Shift; 290 unsigned Length = R.getReplacementText().size(); 291 Shift += Length - R.getLength(); 292 ChangedRanges.push_back(Range(Offset, Length)); 293 } 294 return ChangedRanges; 295 } 296 297 namespace { 298 // Represents a merged replacement, i.e. a replacement consisting of multiple 299 // overlapping replacements from 'First' and 'Second' in mergeReplacements. 300 // 301 // Position projection: 302 // Offsets and lengths of the replacements can generally refer to two different 303 // coordinate spaces. Replacements from 'First' refer to the original text 304 // whereas replacements from 'Second' refer to the text after applying 'First'. 305 // 306 // MergedReplacement always operates in the coordinate space of the original 307 // text, i.e. transforms elements from 'Second' to take into account what was 308 // changed based on the elements from 'First'. 309 // 310 // We can correctly calculate this projection as we look at the replacements in 311 // order of strictly increasing offsets. 312 // 313 // Invariants: 314 // * We always merge elements from 'First' into elements from 'Second' and vice 315 // versa. Within each set, the replacements are non-overlapping. 316 // * We only extend to the right, i.e. merge elements with strictly increasing 317 // offsets. 318 class MergedReplacement { 319 public: 320 MergedReplacement(const Replacement &R, bool MergeSecond, int D) 321 : MergeSecond(MergeSecond), Delta(D), FilePath(R.getFilePath()), 322 Offset(R.getOffset() + (MergeSecond ? 0 : Delta)), Length(R.getLength()), 323 Text(R.getReplacementText()) { 324 Delta += MergeSecond ? 0 : Text.size() - Length; 325 DeltaFirst = MergeSecond ? Text.size() - Length : 0; 326 } 327 328 // Merges the next element 'R' into this merged element. As we always merge 329 // from 'First' into 'Second' or vice versa, the MergedReplacement knows what 330 // set the next element is coming from. 331 void merge(const Replacement &R) { 332 if (MergeSecond) { 333 unsigned REnd = R.getOffset() + Delta + R.getLength(); 334 unsigned End = Offset + Text.size(); 335 if (REnd > End) { 336 Length += REnd - End; 337 MergeSecond = false; 338 } 339 StringRef TextRef = Text; 340 StringRef Head = TextRef.substr(0, R.getOffset() + Delta - Offset); 341 StringRef Tail = TextRef.substr(REnd - Offset); 342 Text = (Head + R.getReplacementText() + Tail).str(); 343 Delta += R.getReplacementText().size() - R.getLength(); 344 } else { 345 unsigned End = Offset + Length; 346 StringRef RText = R.getReplacementText(); 347 StringRef Tail = RText.substr(End - R.getOffset()); 348 Text = (Text + Tail).str(); 349 if (R.getOffset() + RText.size() > End) { 350 Length = R.getOffset() + R.getLength() - Offset; 351 MergeSecond = true; 352 } else { 353 Length += R.getLength() - RText.size(); 354 } 355 DeltaFirst += RText.size() - R.getLength(); 356 } 357 } 358 359 // Returns 'true' if 'R' starts strictly after the MergedReplacement and thus 360 // doesn't need to be merged. 361 bool endsBefore(const Replacement &R) const { 362 if (MergeSecond) 363 return Offset + Text.size() < R.getOffset() + Delta; 364 return Offset + Length < R.getOffset(); 365 } 366 367 // Returns 'true' if an element from the second set should be merged next. 368 bool mergeSecond() const { return MergeSecond; } 369 int deltaFirst() const { return DeltaFirst; } 370 Replacement asReplacement() const { return {FilePath, Offset, Length, Text}; } 371 372 private: 373 bool MergeSecond; 374 375 // Amount of characters that elements from 'Second' need to be shifted by in 376 // order to refer to the original text. 377 int Delta; 378 379 // Sum of all deltas (text-length - length) of elements from 'First' merged 380 // into this element. This is used to update 'Delta' once the 381 // MergedReplacement is completed. 382 int DeltaFirst; 383 384 // Data of the actually merged replacement. FilePath and Offset aren't changed 385 // as the element is only extended to the right. 386 const StringRef FilePath; 387 const unsigned Offset; 388 unsigned Length; 389 std::string Text; 390 }; 391 } // namespace 392 393 Replacements mergeReplacements(const Replacements &First, 394 const Replacements &Second) { 395 if (First.empty() || Second.empty()) 396 return First.empty() ? Second : First; 397 398 // Delta is the amount of characters that replacements from 'Second' need to 399 // be shifted so that their offsets refer to the original text. 400 int Delta = 0; 401 Replacements Result; 402 403 // Iterate over both sets and always add the next element (smallest total 404 // Offset) from either 'First' or 'Second'. Merge that element with 405 // subsequent replacements as long as they overlap. See more details in the 406 // comment on MergedReplacement. 407 for (auto FirstI = First.begin(), SecondI = Second.begin(); 408 FirstI != First.end() || SecondI != Second.end();) { 409 bool NextIsFirst = SecondI == Second.end() || 410 (FirstI != First.end() && 411 FirstI->getOffset() < SecondI->getOffset() + Delta); 412 MergedReplacement Merged(NextIsFirst ? *FirstI : *SecondI, NextIsFirst, 413 Delta); 414 ++(NextIsFirst ? FirstI : SecondI); 415 416 while ((Merged.mergeSecond() && SecondI != Second.end()) || 417 (!Merged.mergeSecond() && FirstI != First.end())) { 418 auto &I = Merged.mergeSecond() ? SecondI : FirstI; 419 if (Merged.endsBefore(*I)) 420 break; 421 Merged.merge(*I); 422 ++I; 423 } 424 Delta -= Merged.deltaFirst(); 425 Result.insert(Merged.asReplacement()); 426 } 427 return Result; 428 } 429 430 } // end namespace tooling 431 } // end namespace clang 432 433