1 //===--- Refactoring.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 tools to support refactorings. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/Basic/DiagnosticOptions.h" 15 #include "clang/Basic/FileManager.h" 16 #include "clang/Basic/SourceManager.h" 17 #include "clang/Frontend/TextDiagnosticPrinter.h" 18 #include "clang/Lex/Lexer.h" 19 #include "clang/Rewrite/Core/Rewriter.h" 20 #include "clang/Tooling/Refactoring.h" 21 #include "llvm/Support/FileSystem.h" 22 #include "llvm/Support/Path.h" 23 #include "llvm/Support/raw_os_ostream.h" 24 25 namespace clang { 26 namespace tooling { 27 28 static const char * const InvalidLocation = ""; 29 30 Replacement::Replacement() 31 : FilePath(InvalidLocation) {} 32 33 Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length, 34 StringRef ReplacementText) 35 : FilePath(FilePath), ReplacementRange(Offset, Length), 36 ReplacementText(ReplacementText) {} 37 38 Replacement::Replacement(const SourceManager &Sources, SourceLocation Start, 39 unsigned Length, StringRef ReplacementText) { 40 setFromSourceLocation(Sources, Start, Length, ReplacementText); 41 } 42 43 Replacement::Replacement(const SourceManager &Sources, 44 const CharSourceRange &Range, 45 StringRef ReplacementText) { 46 setFromSourceRange(Sources, Range, ReplacementText); 47 } 48 49 bool Replacement::isApplicable() const { 50 return FilePath != InvalidLocation; 51 } 52 53 bool Replacement::apply(Rewriter &Rewrite) const { 54 SourceManager &SM = Rewrite.getSourceMgr(); 55 const FileEntry *Entry = SM.getFileManager().getFile(FilePath); 56 if (!Entry) 57 return false; 58 FileID ID; 59 // FIXME: Use SM.translateFile directly. 60 SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1); 61 ID = Location.isValid() ? 62 SM.getFileID(Location) : 63 SM.createFileID(Entry, SourceLocation(), SrcMgr::C_User); 64 // FIXME: We cannot check whether Offset + Length is in the file, as 65 // the remapping API is not public in the RewriteBuffer. 66 const SourceLocation Start = 67 SM.getLocForStartOfFile(ID). 68 getLocWithOffset(ReplacementRange.getOffset()); 69 // ReplaceText returns false on success. 70 // ReplaceText only fails if the source location is not a file location, in 71 // which case we already returned false earlier. 72 bool RewriteSucceeded = !Rewrite.ReplaceText( 73 Start, ReplacementRange.getLength(), ReplacementText); 74 assert(RewriteSucceeded); 75 return RewriteSucceeded; 76 } 77 78 std::string Replacement::toString() const { 79 std::string result; 80 llvm::raw_string_ostream stream(result); 81 stream << FilePath << ": " << ReplacementRange.getOffset() << ":+" 82 << ReplacementRange.getLength() << ":\"" << ReplacementText << "\""; 83 return result; 84 } 85 86 bool operator<(const Replacement &LHS, const Replacement &RHS) { 87 if (LHS.getOffset() != RHS.getOffset()) 88 return LHS.getOffset() < RHS.getOffset(); 89 if (LHS.getLength() != RHS.getLength()) 90 return LHS.getLength() < RHS.getLength(); 91 if (LHS.getFilePath() != RHS.getFilePath()) 92 return LHS.getFilePath() < RHS.getFilePath(); 93 return LHS.getReplacementText() < RHS.getReplacementText(); 94 } 95 96 bool operator==(const Replacement &LHS, const Replacement &RHS) { 97 return LHS.getOffset() == RHS.getOffset() && 98 LHS.getLength() == RHS.getLength() && 99 LHS.getFilePath() == RHS.getFilePath() && 100 LHS.getReplacementText() == RHS.getReplacementText(); 101 } 102 103 void Replacement::setFromSourceLocation(const SourceManager &Sources, 104 SourceLocation Start, unsigned Length, 105 StringRef ReplacementText) { 106 const std::pair<FileID, unsigned> DecomposedLocation = 107 Sources.getDecomposedLoc(Start); 108 const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first); 109 if (Entry) { 110 // Make FilePath absolute so replacements can be applied correctly when 111 // relative paths for files are used. 112 llvm::SmallString<256> FilePath(Entry->getName()); 113 std::error_code EC = llvm::sys::fs::make_absolute(FilePath); 114 this->FilePath = EC ? FilePath.c_str() : Entry->getName(); 115 } else { 116 this->FilePath = InvalidLocation; 117 } 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 SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin()); 128 SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd()); 129 std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin); 130 std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd); 131 if (Start.first != End.first) return -1; 132 if (Range.isTokenRange()) 133 End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, 134 LangOptions()); 135 return End.second - Start.second; 136 } 137 138 void Replacement::setFromSourceRange(const SourceManager &Sources, 139 const CharSourceRange &Range, 140 StringRef ReplacementText) { 141 setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()), 142 getRangeSize(Sources, Range), ReplacementText); 143 } 144 145 bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) { 146 bool Result = true; 147 for (Replacements::const_iterator I = Replaces.begin(), 148 E = Replaces.end(); 149 I != E; ++I) { 150 if (I->isApplicable()) { 151 Result = I->apply(Rewrite) && Result; 152 } else { 153 Result = false; 154 } 155 } 156 return Result; 157 } 158 159 // FIXME: Remove this function when Replacements is implemented as std::vector 160 // instead of std::set. 161 bool applyAllReplacements(const std::vector<Replacement> &Replaces, 162 Rewriter &Rewrite) { 163 bool Result = true; 164 for (std::vector<Replacement>::const_iterator I = Replaces.begin(), 165 E = Replaces.end(); 166 I != E; ++I) { 167 if (I->isApplicable()) { 168 Result = I->apply(Rewrite) && Result; 169 } else { 170 Result = false; 171 } 172 } 173 return Result; 174 } 175 176 std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) { 177 FileManager Files((FileSystemOptions())); 178 DiagnosticsEngine Diagnostics( 179 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs), 180 new DiagnosticOptions); 181 Diagnostics.setClient(new TextDiagnosticPrinter( 182 llvm::outs(), &Diagnostics.getDiagnosticOptions())); 183 SourceManager SourceMgr(Diagnostics, Files); 184 Rewriter Rewrite(SourceMgr, LangOptions()); 185 std::unique_ptr<llvm::MemoryBuffer> Buf = 186 llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>"); 187 const clang::FileEntry *Entry = 188 Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0); 189 SourceMgr.overrideFileContents(Entry, std::move(Buf)); 190 FileID ID = 191 SourceMgr.createFileID(Entry, SourceLocation(), clang::SrcMgr::C_User); 192 for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end(); 193 I != E; ++I) { 194 Replacement Replace("<stdin>", I->getOffset(), I->getLength(), 195 I->getReplacementText()); 196 if (!Replace.apply(Rewrite)) 197 return ""; 198 } 199 std::string Result; 200 llvm::raw_string_ostream OS(Result); 201 Rewrite.getEditBuffer(ID).write(OS); 202 OS.flush(); 203 return Result; 204 } 205 206 unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) { 207 unsigned NewPosition = Position; 208 for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E; 209 ++I) { 210 if (I->getOffset() >= Position) 211 break; 212 if (I->getOffset() + I->getLength() > Position) 213 NewPosition += I->getOffset() + I->getLength() - Position; 214 NewPosition += I->getReplacementText().size() - I->getLength(); 215 } 216 return NewPosition; 217 } 218 219 // FIXME: Remove this function when Replacements is implemented as std::vector 220 // instead of std::set. 221 unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces, 222 unsigned Position) { 223 unsigned NewPosition = Position; 224 for (std::vector<Replacement>::const_iterator I = Replaces.begin(), 225 E = Replaces.end(); 226 I != E; ++I) { 227 if (I->getOffset() >= Position) 228 break; 229 if (I->getOffset() + I->getLength() > Position) 230 NewPosition += I->getOffset() + I->getLength() - Position; 231 NewPosition += I->getReplacementText().size() - I->getLength(); 232 } 233 return NewPosition; 234 } 235 236 void deduplicate(std::vector<Replacement> &Replaces, 237 std::vector<Range> &Conflicts) { 238 if (Replaces.empty()) 239 return; 240 241 auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) { 242 if (LHS.getOffset() != RHS.getOffset()) 243 return LHS.getOffset() < RHS.getOffset(); 244 if (LHS.getLength() != RHS.getLength()) 245 return LHS.getLength() < RHS.getLength(); 246 return LHS.getReplacementText() < RHS.getReplacementText(); 247 }; 248 249 auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) { 250 return LHS.getOffset() == RHS.getOffset() && 251 LHS.getLength() == RHS.getLength() && 252 LHS.getReplacementText() == RHS.getReplacementText(); 253 }; 254 255 // Deduplicate. We don't want to deduplicate based on the path as we assume 256 // that all replacements refer to the same file (or are symlinks). 257 std::sort(Replaces.begin(), Replaces.end(), LessNoPath); 258 Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), 259 Replaces.end()); 260 261 // Detect conflicts 262 Range ConflictRange(Replaces.front().getOffset(), 263 Replaces.front().getLength()); 264 unsigned ConflictStart = 0; 265 unsigned ConflictLength = 1; 266 for (unsigned i = 1; i < Replaces.size(); ++i) { 267 Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); 268 if (ConflictRange.overlapsWith(Current)) { 269 // Extend conflicted range 270 ConflictRange = Range(ConflictRange.getOffset(), 271 std::max(ConflictRange.getLength(), 272 Current.getOffset() + Current.getLength() - 273 ConflictRange.getOffset())); 274 ++ConflictLength; 275 } else { 276 if (ConflictLength > 1) 277 Conflicts.push_back(Range(ConflictStart, ConflictLength)); 278 ConflictRange = Current; 279 ConflictStart = i; 280 ConflictLength = 1; 281 } 282 } 283 284 if (ConflictLength > 1) 285 Conflicts.push_back(Range(ConflictStart, ConflictLength)); 286 } 287 288 289 RefactoringTool::RefactoringTool(const CompilationDatabase &Compilations, 290 ArrayRef<std::string> SourcePaths) 291 : ClangTool(Compilations, SourcePaths) {} 292 293 Replacements &RefactoringTool::getReplacements() { return Replace; } 294 295 int RefactoringTool::runAndSave(FrontendActionFactory *ActionFactory) { 296 if (int Result = run(ActionFactory)) { 297 return Result; 298 } 299 300 LangOptions DefaultLangOptions; 301 IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions(); 302 TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts); 303 DiagnosticsEngine Diagnostics( 304 IntrusiveRefCntPtr<DiagnosticIDs>(new DiagnosticIDs()), 305 &*DiagOpts, &DiagnosticPrinter, false); 306 SourceManager Sources(Diagnostics, getFiles()); 307 Rewriter Rewrite(Sources, DefaultLangOptions); 308 309 if (!applyAllReplacements(Rewrite)) { 310 llvm::errs() << "Skipped some replacements.\n"; 311 } 312 313 return saveRewrittenFiles(Rewrite); 314 } 315 316 bool RefactoringTool::applyAllReplacements(Rewriter &Rewrite) { 317 return tooling::applyAllReplacements(Replace, Rewrite); 318 } 319 320 int RefactoringTool::saveRewrittenFiles(Rewriter &Rewrite) { 321 return Rewrite.overwriteChangedFiles() ? 1 : 0; 322 } 323 324 } // end namespace tooling 325 } // end namespace clang 326