ApplyReplacements.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. //===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. ///
  9. /// \file
  10. /// This file provides the implementation for deduplicating, detecting
  11. /// conflicts in, and applying collections of Replacements.
  12. ///
  13. /// FIXME: Use Diagnostics for output instead of llvm::errs().
  14. ///
  15. //===----------------------------------------------------------------------===//
  16. #include "clang-apply-replacements/Tooling/ApplyReplacements.h"
  17. #include "clang/Basic/LangOptions.h"
  18. #include "clang/Basic/SourceManager.h"
  19. #include "clang/Format/Format.h"
  20. #include "clang/Lex/Lexer.h"
  21. #include "clang/Rewrite/Core/Rewriter.h"
  22. #include "clang/Tooling/Core/Diagnostic.h"
  23. #include "clang/Tooling/DiagnosticsYaml.h"
  24. #include "clang/Tooling/ReplacementsYaml.h"
  25. #include "llvm/ADT/ArrayRef.h"
  26. #include "llvm/ADT/Optional.h"
  27. #include "llvm/Support/FileSystem.h"
  28. #include "llvm/Support/MemoryBuffer.h"
  29. #include "llvm/Support/Path.h"
  30. #include "llvm/Support/raw_ostream.h"
  31. using namespace llvm;
  32. using namespace clang;
  33. static void eatDiagnostics(const SMDiagnostic &, void *) {}
  34. namespace clang {
  35. namespace replace {
  36. std::error_code collectReplacementsFromDirectory(
  37. const llvm::StringRef Directory, TUReplacements &TUs,
  38. TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
  39. using namespace llvm::sys::fs;
  40. using namespace llvm::sys::path;
  41. std::error_code ErrorCode;
  42. for (recursive_directory_iterator I(Directory, ErrorCode), E;
  43. I != E && !ErrorCode; I.increment(ErrorCode)) {
  44. if (filename(I->path())[0] == '.') {
  45. // Indicate not to descend into directories beginning with '.'
  46. I.no_push();
  47. continue;
  48. }
  49. if (extension(I->path()) != ".yaml")
  50. continue;
  51. TUFiles.push_back(I->path());
  52. ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
  53. MemoryBuffer::getFile(I->path());
  54. if (std::error_code BufferError = Out.getError()) {
  55. errs() << "Error reading " << I->path() << ": " << BufferError.message()
  56. << "\n";
  57. continue;
  58. }
  59. yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
  60. tooling::TranslationUnitReplacements TU;
  61. YIn >> TU;
  62. if (YIn.error()) {
  63. // File doesn't appear to be a header change description. Ignore it.
  64. continue;
  65. }
  66. // Only keep files that properly parse.
  67. TUs.push_back(TU);
  68. }
  69. return ErrorCode;
  70. }
  71. std::error_code collectReplacementsFromDirectory(
  72. const llvm::StringRef Directory, TUDiagnostics &TUs,
  73. TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
  74. using namespace llvm::sys::fs;
  75. using namespace llvm::sys::path;
  76. std::error_code ErrorCode;
  77. for (recursive_directory_iterator I(Directory, ErrorCode), E;
  78. I != E && !ErrorCode; I.increment(ErrorCode)) {
  79. if (filename(I->path())[0] == '.') {
  80. // Indicate not to descend into directories beginning with '.'
  81. I.no_push();
  82. continue;
  83. }
  84. if (extension(I->path()) != ".yaml")
  85. continue;
  86. TUFiles.push_back(I->path());
  87. ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
  88. MemoryBuffer::getFile(I->path());
  89. if (std::error_code BufferError = Out.getError()) {
  90. errs() << "Error reading " << I->path() << ": " << BufferError.message()
  91. << "\n";
  92. continue;
  93. }
  94. yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
  95. tooling::TranslationUnitDiagnostics TU;
  96. YIn >> TU;
  97. if (YIn.error()) {
  98. // File doesn't appear to be a header change description. Ignore it.
  99. continue;
  100. }
  101. // Only keep files that properly parse.
  102. TUs.push_back(TU);
  103. }
  104. return ErrorCode;
  105. }
  106. /// Extract replacements from collected TranslationUnitReplacements and
  107. /// TranslationUnitDiagnostics and group them per file. Identical replacements
  108. /// from diagnostics are deduplicated.
  109. ///
  110. /// \param[in] TUs Collection of all found and deserialized
  111. /// TranslationUnitReplacements.
  112. /// \param[in] TUDs Collection of all found and deserialized
  113. /// TranslationUnitDiagnostics.
  114. /// \param[in] SM Used to deduplicate paths.
  115. ///
  116. /// \returns A map mapping FileEntry to a set of Replacement targeting that
  117. /// file.
  118. static llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
  119. groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,
  120. const clang::SourceManager &SM) {
  121. std::set<StringRef> Warned;
  122. llvm::DenseMap<const FileEntry *, std::vector<tooling::Replacement>>
  123. GroupedReplacements;
  124. // Deduplicate identical replacements in diagnostics unless they are from the
  125. // same TU.
  126. // FIXME: Find an efficient way to deduplicate on diagnostics level.
  127. llvm::DenseMap<const FileEntry *,
  128. std::map<tooling::Replacement,
  129. const tooling::TranslationUnitDiagnostics *>>
  130. DiagReplacements;
  131. auto AddToGroup = [&](const tooling::Replacement &R,
  132. const tooling::TranslationUnitDiagnostics *SourceTU,
  133. const llvm::Optional<std::string> BuildDir) {
  134. // Use the file manager to deduplicate paths. FileEntries are
  135. // automatically canonicalized.
  136. auto PrevWorkingDir = SM.getFileManager().getFileSystemOpts().WorkingDir;
  137. if (BuildDir)
  138. SM.getFileManager().getFileSystemOpts().WorkingDir = std::move(*BuildDir);
  139. if (auto Entry = SM.getFileManager().getFile(R.getFilePath())) {
  140. if (SourceTU) {
  141. auto &Replaces = DiagReplacements[*Entry];
  142. auto It = Replaces.find(R);
  143. if (It == Replaces.end())
  144. Replaces.emplace(R, SourceTU);
  145. else if (It->second != SourceTU)
  146. // This replacement is a duplicate of one suggested by another TU.
  147. return;
  148. }
  149. GroupedReplacements[*Entry].push_back(R);
  150. } else if (Warned.insert(R.getFilePath()).second) {
  151. errs() << "Described file '" << R.getFilePath()
  152. << "' doesn't exist. Ignoring...\n";
  153. }
  154. SM.getFileManager().getFileSystemOpts().WorkingDir = PrevWorkingDir;
  155. };
  156. for (const auto &TU : TUs)
  157. for (const tooling::Replacement &R : TU.Replacements)
  158. AddToGroup(R, nullptr, {});
  159. for (const auto &TU : TUDs)
  160. for (const auto &D : TU.Diagnostics)
  161. if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {
  162. for (const auto &Fix : *ChoosenFix)
  163. for (const tooling::Replacement &R : Fix.second)
  164. AddToGroup(R, &TU, D.BuildDirectory);
  165. }
  166. // Sort replacements per file to keep consistent behavior when
  167. // clang-apply-replacements run on differents machine.
  168. for (auto &FileAndReplacements : GroupedReplacements) {
  169. llvm::sort(FileAndReplacements.second.begin(),
  170. FileAndReplacements.second.end());
  171. }
  172. return GroupedReplacements;
  173. }
  174. bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,
  175. FileToChangesMap &FileChanges,
  176. clang::SourceManager &SM) {
  177. auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);
  178. bool ConflictDetected = false;
  179. // To report conflicting replacements on corresponding file, all replacements
  180. // are stored into 1 big AtomicChange.
  181. for (const auto &FileAndReplacements : GroupedReplacements) {
  182. const FileEntry *Entry = FileAndReplacements.first;
  183. const SourceLocation BeginLoc =
  184. SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));
  185. tooling::AtomicChange FileChange(Entry->getName(), Entry->getName());
  186. for (const auto &R : FileAndReplacements.second) {
  187. llvm::Error Err =
  188. FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),
  189. R.getLength(), R.getReplacementText());
  190. if (Err) {
  191. // FIXME: This will report conflicts by pair using a file+offset format
  192. // which is not so much human readable.
  193. // A first improvement could be to translate offset to line+col. For
  194. // this and without loosing error message some modifications around
  195. // `tooling::ReplacementError` are need (access to
  196. // `getReplacementErrString`).
  197. // A better strategy could be to add a pretty printer methods for
  198. // conflict reporting. Methods that could be parameterized to report a
  199. // conflict in different format, file+offset, file+line+col, or even
  200. // more human readable using VCS conflict markers.
  201. // For now, printing directly the error reported by `AtomicChange` is
  202. // the easiest solution.
  203. errs() << llvm::toString(std::move(Err)) << "\n";
  204. ConflictDetected = true;
  205. }
  206. }
  207. FileChanges.try_emplace(Entry,
  208. std::vector<tooling::AtomicChange>{FileChange});
  209. }
  210. return !ConflictDetected;
  211. }
  212. llvm::Expected<std::string>
  213. applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,
  214. const tooling::ApplyChangesSpec &Spec,
  215. DiagnosticsEngine &Diagnostics) {
  216. FileManager Files((FileSystemOptions()));
  217. SourceManager SM(Diagnostics, Files);
  218. llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
  219. SM.getFileManager().getBufferForFile(File);
  220. if (!Buffer)
  221. return errorCodeToError(Buffer.getError());
  222. return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,
  223. Spec);
  224. }
  225. bool deleteReplacementFiles(const TUReplacementFiles &Files,
  226. clang::DiagnosticsEngine &Diagnostics) {
  227. bool Success = true;
  228. for (const auto &Filename : Files) {
  229. std::error_code Error = llvm::sys::fs::remove(Filename);
  230. if (Error) {
  231. Success = false;
  232. // FIXME: Use Diagnostics for outputting errors.
  233. errs() << "Error deleting file: " << Filename << "\n";
  234. errs() << Error.message() << "\n";
  235. errs() << "Please delete the file manually\n";
  236. }
  237. }
  238. return Success;
  239. }
  240. } // end namespace replace
  241. } // end namespace clang