1 //===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===// 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 /// \file 11 /// \brief This file provides the implementation for deduplicating, detecting 12 /// conflicts in, and applying collections of Replacements. 13 /// 14 /// FIXME: Use Diagnostics for output instead of llvm::errs(). 15 /// 16 //===----------------------------------------------------------------------===// 17 #include "clang-apply-replacements/Tooling/ApplyReplacements.h" 18 #include "clang/Basic/LangOptions.h" 19 #include "clang/Basic/SourceManager.h" 20 #include "clang/Format/Format.h" 21 #include "clang/Lex/Lexer.h" 22 #include "clang/Rewrite/Core/Rewriter.h" 23 #include "clang/Tooling/DiagnosticsYaml.h" 24 #include "clang/Tooling/ReplacementsYaml.h" 25 #include "llvm/ADT/ArrayRef.h" 26 #include "llvm/Support/FileSystem.h" 27 #include "llvm/Support/MemoryBuffer.h" 28 #include "llvm/Support/Path.h" 29 #include "llvm/Support/raw_ostream.h" 30 31 using namespace llvm; 32 using namespace clang; 33 34 static void eatDiagnostics(const SMDiagnostic &, void *) {} 35 36 namespace clang { 37 namespace replace { 38 39 std::error_code collectReplacementsFromDirectory( 40 const llvm::StringRef Directory, TUReplacements &TUs, 41 TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) { 42 using namespace llvm::sys::fs; 43 using namespace llvm::sys::path; 44 45 std::error_code ErrorCode; 46 47 for (recursive_directory_iterator I(Directory, ErrorCode), E; 48 I != E && !ErrorCode; I.increment(ErrorCode)) { 49 if (filename(I->path())[0] == '.') { 50 // Indicate not to descend into directories beginning with '.' 51 I.no_push(); 52 continue; 53 } 54 55 if (extension(I->path()) != ".yaml") 56 continue; 57 58 TUFiles.push_back(I->path()); 59 60 ErrorOr<std::unique_ptr<MemoryBuffer>> Out = 61 MemoryBuffer::getFile(I->path()); 62 if (std::error_code BufferError = Out.getError()) { 63 errs() << "Error reading " << I->path() << ": " << BufferError.message() 64 << "\n"; 65 continue; 66 } 67 68 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); 69 tooling::TranslationUnitReplacements TU; 70 YIn >> TU; 71 if (YIn.error()) { 72 // File doesn't appear to be a header change description. Ignore it. 73 continue; 74 } 75 76 // Only keep files that properly parse. 77 TUs.push_back(TU); 78 } 79 80 return ErrorCode; 81 } 82 83 std::error_code 84 collectReplacementsFromDirectory(const llvm::StringRef Directory, 85 TUDiagnostics &TUs, TUReplacementFiles &TUFiles, 86 clang::DiagnosticsEngine &Diagnostics) { 87 using namespace llvm::sys::fs; 88 using namespace llvm::sys::path; 89 90 std::error_code ErrorCode; 91 92 for (recursive_directory_iterator I(Directory, ErrorCode), E; 93 I != E && !ErrorCode; I.increment(ErrorCode)) { 94 if (filename(I->path())[0] == '.') { 95 // Indicate not to descend into directories beginning with '.' 96 I.no_push(); 97 continue; 98 } 99 100 if (extension(I->path()) != ".yaml") 101 continue; 102 103 TUFiles.push_back(I->path()); 104 105 ErrorOr<std::unique_ptr<MemoryBuffer>> Out = 106 MemoryBuffer::getFile(I->path()); 107 if (std::error_code BufferError = Out.getError()) { 108 errs() << "Error reading " << I->path() << ": " << BufferError.message() 109 << "\n"; 110 continue; 111 } 112 113 yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics); 114 tooling::TranslationUnitDiagnostics TU; 115 YIn >> TU; 116 if (YIn.error()) { 117 // File doesn't appear to be a header change description. Ignore it. 118 continue; 119 } 120 121 // Only keep files that properly parse. 122 TUs.push_back(TU); 123 } 124 125 return ErrorCode; 126 } 127 128 /// \brief Dumps information for a sequence of conflicting Replacements. 129 /// 130 /// \param[in] File FileEntry for the file the conflicting Replacements are 131 /// for. 132 /// \param[in] ConflictingReplacements List of conflicting Replacements. 133 /// \param[in] SM SourceManager used for reporting. 134 static void reportConflict( 135 const FileEntry *File, 136 const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements, 137 SourceManager &SM) { 138 FileID FID = SM.translateFile(File); 139 if (FID.isInvalid()) 140 FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User); 141 142 // FIXME: Output something a little more user-friendly (e.g. unified diff?) 143 errs() << "The following changes conflict:\n"; 144 for (const tooling::Replacement &R : ConflictingReplacements) { 145 if (R.getLength() == 0) { 146 errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":" 147 << SM.getColumnNumber(FID, R.getOffset()) << " " 148 << R.getReplacementText() << "\n"; 149 } else { 150 if (R.getReplacementText().empty()) 151 errs() << " Remove "; 152 else 153 errs() << " Replace "; 154 155 errs() << SM.getLineNumber(FID, R.getOffset()) << ":" 156 << SM.getColumnNumber(FID, R.getOffset()) << "-" 157 << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":" 158 << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1); 159 160 if (R.getReplacementText().empty()) 161 errs() << "\n"; 162 else 163 errs() << " with \"" << R.getReplacementText() << "\"\n"; 164 } 165 } 166 } 167 168 // FIXME: Remove this function after changing clang-apply-replacements to use 169 // Replacements class. 170 bool applyAllReplacements(const std::vector<tooling::Replacement> &Replaces, 171 Rewriter &Rewrite) { 172 bool Result = true; 173 for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) { 174 if (I->isApplicable()) { 175 Result = I->apply(Rewrite) && Result; 176 } else { 177 Result = false; 178 } 179 } 180 return Result; 181 } 182 183 // FIXME: moved from libToolingCore. remove this when std::vector<Replacement> 184 // is replaced with tooling::Replacements class. 185 static void deduplicate(std::vector<tooling::Replacement> &Replaces, 186 std::vector<tooling::Range> &Conflicts) { 187 if (Replaces.empty()) 188 return; 189 190 auto LessNoPath = [](const tooling::Replacement &LHS, 191 const tooling::Replacement &RHS) { 192 if (LHS.getOffset() != RHS.getOffset()) 193 return LHS.getOffset() < RHS.getOffset(); 194 if (LHS.getLength() != RHS.getLength()) 195 return LHS.getLength() < RHS.getLength(); 196 return LHS.getReplacementText() < RHS.getReplacementText(); 197 }; 198 199 auto EqualNoPath = [](const tooling::Replacement &LHS, 200 const tooling::Replacement &RHS) { 201 return LHS.getOffset() == RHS.getOffset() && 202 LHS.getLength() == RHS.getLength() && 203 LHS.getReplacementText() == RHS.getReplacementText(); 204 }; 205 206 // Deduplicate. We don't want to deduplicate based on the path as we assume 207 // that all replacements refer to the same file (or are symlinks). 208 std::sort(Replaces.begin(), Replaces.end(), LessNoPath); 209 Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath), 210 Replaces.end()); 211 212 // Detect conflicts 213 tooling::Range ConflictRange(Replaces.front().getOffset(), 214 Replaces.front().getLength()); 215 unsigned ConflictStart = 0; 216 unsigned ConflictLength = 1; 217 for (unsigned i = 1; i < Replaces.size(); ++i) { 218 tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength()); 219 if (ConflictRange.overlapsWith(Current)) { 220 // Extend conflicted range 221 ConflictRange = 222 tooling::Range(ConflictRange.getOffset(), 223 std::max(ConflictRange.getLength(), 224 Current.getOffset() + Current.getLength() - 225 ConflictRange.getOffset())); 226 ++ConflictLength; 227 } else { 228 if (ConflictLength > 1) 229 Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength)); 230 ConflictRange = Current; 231 ConflictStart = i; 232 ConflictLength = 1; 233 } 234 } 235 236 if (ConflictLength > 1) 237 Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength)); 238 } 239 240 /// \brief Deduplicates and tests for conflicts among the replacements for each 241 /// file in \c Replacements. Any conflicts found are reported. 242 /// 243 /// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset(). 244 /// 245 /// \param[in,out] Replacements Container of all replacements grouped by file 246 /// to be deduplicated and checked for conflicts. 247 /// \param[in] SM SourceManager required for conflict reporting. 248 /// 249 /// \returns \parblock 250 /// \li true if conflicts were detected 251 /// \li false if no conflicts were detected 252 static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, 253 SourceManager &SM) { 254 bool conflictsFound = false; 255 256 for (auto &FileAndReplacements : Replacements) { 257 const FileEntry *Entry = FileAndReplacements.first; 258 auto &Replacements = FileAndReplacements.second; 259 assert(Entry != nullptr && "No file entry!"); 260 261 std::vector<tooling::Range> Conflicts; 262 deduplicate(FileAndReplacements.second, Conflicts); 263 264 if (Conflicts.empty()) 265 continue; 266 267 conflictsFound = true; 268 269 errs() << "There are conflicting changes to " << Entry->getName() << ":\n"; 270 271 for (const tooling::Range &Conflict : Conflicts) { 272 auto ConflictingReplacements = llvm::makeArrayRef( 273 &Replacements[Conflict.getOffset()], Conflict.getLength()); 274 reportConflict(Entry, ConflictingReplacements, SM); 275 } 276 } 277 278 return conflictsFound; 279 } 280 281 bool mergeAndDeduplicate(const TUReplacements &TUs, 282 FileToReplacementsMap &GroupedReplacements, 283 clang::SourceManager &SM) { 284 285 // Group all replacements by target file. 286 std::set<StringRef> Warned; 287 for (const auto &TU : TUs) { 288 for (const tooling::Replacement &R : TU.Replacements) { 289 // Use the file manager to deduplicate paths. FileEntries are 290 // automatically canonicalized. 291 if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) { 292 GroupedReplacements[Entry].push_back(R); 293 } else if (Warned.insert(R.getFilePath()).second) { 294 errs() << "Described file '" << R.getFilePath() 295 << "' doesn't exist. Ignoring...\n"; 296 } 297 } 298 } 299 300 // Ask clang to deduplicate and report conflicts. 301 return !deduplicateAndDetectConflicts(GroupedReplacements, SM); 302 } 303 304 bool mergeAndDeduplicate(const TUDiagnostics &TUs, 305 FileToReplacementsMap &GroupedReplacements, 306 clang::SourceManager &SM) { 307 308 // Group all replacements by target file. 309 std::set<StringRef> Warned; 310 for (const auto &TU : TUs) { 311 for (const auto &D : TU.Diagnostics) { 312 for (const auto &Fix : D.Fix) { 313 for (const tooling::Replacement &R : Fix.second) { 314 // Use the file manager to deduplicate paths. FileEntries are 315 // automatically canonicalized. 316 if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) { 317 GroupedReplacements[Entry].push_back(R); 318 } else if (Warned.insert(R.getFilePath()).second) { 319 errs() << "Described file '" << R.getFilePath() 320 << "' doesn't exist. Ignoring...\n"; 321 } 322 } 323 } 324 } 325 } 326 327 // Ask clang to deduplicate and report conflicts. 328 return !deduplicateAndDetectConflicts(GroupedReplacements, SM); 329 } 330 331 bool applyReplacements(const FileToReplacementsMap &GroupedReplacements, 332 clang::Rewriter &Rewrites) { 333 334 // Apply all changes 335 // 336 // FIXME: No longer certain GroupedReplacements is really the best kind of 337 // data structure for applying replacements. Rewriter certainly doesn't care. 338 // However, until we nail down the design of ReplacementGroups, might as well 339 // leave this as is. 340 for (const auto &FileAndReplacements : GroupedReplacements) { 341 if (!applyAllReplacements(FileAndReplacements.second, Rewrites)) 342 return false; 343 } 344 345 return true; 346 } 347 348 RangeVector calculateChangedRanges( 349 const std::vector<clang::tooling::Replacement> &Replaces) { 350 RangeVector ChangedRanges; 351 352 // Generate the new ranges from the replacements. 353 int Shift = 0; 354 for (const tooling::Replacement &R : Replaces) { 355 unsigned Offset = R.getOffset() + Shift; 356 unsigned Length = R.getReplacementText().size(); 357 Shift += Length - R.getLength(); 358 ChangedRanges.push_back(tooling::Range(Offset, Length)); 359 } 360 361 return ChangedRanges; 362 } 363 364 bool writeFiles(const clang::Rewriter &Rewrites) { 365 366 for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end(); 367 BufferI != BufferE; ++BufferI) { 368 StringRef FileName = 369 Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName(); 370 371 std::error_code EC; 372 llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text); 373 if (EC) { 374 errs() << "Warning: Could not write to " << EC.message() << "\n"; 375 continue; 376 } 377 BufferI->second.write(FileStream); 378 } 379 380 return true; 381 } 382 383 bool deleteReplacementFiles(const TUReplacementFiles &Files, 384 clang::DiagnosticsEngine &Diagnostics) { 385 bool Success = true; 386 for (const auto &Filename : Files) { 387 std::error_code Error = llvm::sys::fs::remove(Filename); 388 if (Error) { 389 Success = false; 390 // FIXME: Use Diagnostics for outputting errors. 391 errs() << "Error deleting file: " << Filename << "\n"; 392 errs() << Error.message() << "\n"; 393 errs() << "Please delete the file manually\n"; 394 } 395 } 396 return Success; 397 } 398 399 } // end namespace replace 400 } // end namespace clang 401