brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.4 KiB · 0ac8f71 Raw
275 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8///9/// \file10/// This file provides the implementation for deduplicating, detecting11/// 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/STLExtras.h"27#include "llvm/ADT/StringRef.h"28#include "llvm/ADT/StringSet.h"29#include "llvm/Support/FileSystem.h"30#include "llvm/Support/MemoryBuffer.h"31#include "llvm/Support/Path.h"32#include "llvm/Support/raw_ostream.h"33#include <array>34#include <optional>35 36using namespace llvm;37using namespace clang;38 39static void eatDiagnostics(const SMDiagnostic &, void *) {}40 41namespace clang {42namespace replace {43 44namespace detail {45 46static constexpr std::array<StringRef, 2> AllowedExtensions = {".yaml", ".yml"};47 48template <typename TranslationUnits>49static std::error_code collectReplacementsFromDirectory(50    const llvm::StringRef Directory, TranslationUnits &TUs,51    TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {52  using namespace llvm::sys::fs;53  using namespace llvm::sys::path;54 55  std::error_code ErrorCode;56 57  for (recursive_directory_iterator I(Directory, ErrorCode), E;58       I != E && !ErrorCode; I.increment(ErrorCode)) {59    if (filename(I->path())[0] == '.') {60      // Indicate not to descend into directories beginning with '.'61      I.no_push();62      continue;63    }64 65    if (!is_contained(AllowedExtensions, extension(I->path())))66      continue;67 68    TUFiles.push_back(I->path());69 70    ErrorOr<std::unique_ptr<MemoryBuffer>> Out =71        MemoryBuffer::getFile(I->path());72    if (std::error_code BufferError = Out.getError()) {73      errs() << "Error reading " << I->path() << ": " << BufferError.message()74             << "\n";75      continue;76    }77 78    yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);79    typename TranslationUnits::value_type TU;80    YIn >> TU;81    if (YIn.error()) {82      // File doesn't appear to be a header change description. Ignore it.83      continue;84    }85 86    // Only keep files that properly parse.87    TUs.push_back(TU);88  }89 90  return ErrorCode;91}92} // namespace detail93 94template <>95std::error_code collectReplacementsFromDirectory(96    const llvm::StringRef Directory, TUReplacements &TUs,97    TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {98  return detail::collectReplacementsFromDirectory(Directory, TUs, TUFiles,99                                                  Diagnostics);100}101 102template <>103std::error_code collectReplacementsFromDirectory(104    const llvm::StringRef Directory, TUDiagnostics &TUs,105    TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {106  return detail::collectReplacementsFromDirectory(Directory, TUs, TUFiles,107                                                  Diagnostics);108}109 110/// Extract replacements from collected TranslationUnitReplacements and111/// TranslationUnitDiagnostics and group them per file. Identical replacements112/// from diagnostics are deduplicated.113///114/// \param[in] TUs Collection of all found and deserialized115/// TranslationUnitReplacements.116/// \param[in] TUDs Collection of all found and deserialized117/// TranslationUnitDiagnostics.118/// \param[in] SM Used to deduplicate paths.119///120/// \returns A map mapping FileEntry to a set of Replacement targeting that121/// file.122static llvm::DenseMap<FileEntryRef, std::vector<tooling::Replacement>>123groupReplacements(const TUReplacements &TUs, const TUDiagnostics &TUDs,124                  const clang::SourceManager &SM) {125  llvm::StringSet<> Warned;126  llvm::DenseMap<FileEntryRef, std::vector<tooling::Replacement>>127      GroupedReplacements;128 129  // Deduplicate identical replacements in diagnostics unless they are from the130  // same TU.131  // FIXME: Find an efficient way to deduplicate on diagnostics level.132  llvm::DenseMap<const FileEntry *,133                 std::map<tooling::Replacement,134                          const tooling::TranslationUnitDiagnostics *>>135      DiagReplacements;136 137  auto AddToGroup = [&](const tooling::Replacement &R,138                        const tooling::TranslationUnitDiagnostics *SourceTU,139                        const std::optional<std::string> BuildDir) {140    // Use the file manager to deduplicate paths. FileEntries are141    // automatically canonicalized. Since relative paths can come from different142    // build directories, make them absolute immediately.143    SmallString<128> Path = R.getFilePath();144    if (BuildDir)145      llvm::sys::path::make_absolute(*BuildDir, Path);146    else147      SM.getFileManager().makeAbsolutePath(Path);148 149    if (auto Entry = SM.getFileManager().getOptionalFileRef(Path)) {150      if (SourceTU) {151        auto [It, Inserted] = DiagReplacements[*Entry].try_emplace(R, SourceTU);152        if (!Inserted && It->second != SourceTU)153          // This replacement is a duplicate of one suggested by another TU.154          return;155      }156      GroupedReplacements[*Entry].push_back(R);157    } else if (Warned.insert(Path).second) {158      errs() << "Described file '" << R.getFilePath()159             << "' doesn't exist. Ignoring...\n";160    }161  };162 163  for (const auto &TU : TUs)164    for (const tooling::Replacement &R : TU.Replacements)165      AddToGroup(R, nullptr, {});166 167  for (const auto &TU : TUDs)168    for (const auto &D : TU.Diagnostics)169      if (const auto *ChoosenFix = tooling::selectFirstFix(D)) {170        for (const auto &Fix : *ChoosenFix)171          for (const tooling::Replacement &R : Fix.second)172            AddToGroup(R, &TU, D.BuildDirectory);173      }174 175  // Sort replacements per file to keep consistent behavior when176  // clang-apply-replacements run on differents machine.177  for (auto &FileAndReplacements : GroupedReplacements) {178    llvm::sort(FileAndReplacements.second);179  }180 181  return GroupedReplacements;182}183 184bool mergeAndDeduplicate(const TUReplacements &TUs, const TUDiagnostics &TUDs,185                         FileToChangesMap &FileChanges,186                         clang::SourceManager &SM, bool IgnoreInsertConflict) {187  auto GroupedReplacements = groupReplacements(TUs, TUDs, SM);188  bool ConflictDetected = false;189 190  // To report conflicting replacements on corresponding file, all replacements191  // are stored into 1 big AtomicChange.192  for (const auto &FileAndReplacements : GroupedReplacements) {193    FileEntryRef Entry = FileAndReplacements.first;194    const SourceLocation BeginLoc =195        SM.getLocForStartOfFile(SM.getOrCreateFileID(Entry, SrcMgr::C_User));196    tooling::AtomicChange FileChange(Entry.getName(), Entry.getName());197    for (const auto &R : FileAndReplacements.second) {198      llvm::Error Err =199          FileChange.replace(SM, BeginLoc.getLocWithOffset(R.getOffset()),200                             R.getLength(), R.getReplacementText());201      if (Err) {202        // FIXME: This will report conflicts by pair using a file+offset format203        // which is not so much human readable.204        // A first improvement could be to translate offset to line+col. For205        // this and without loosing error message some modifications around206        // `tooling::ReplacementError` are need (access to207        // `getReplacementErrString`).208        // A better strategy could be to add a pretty printer methods for209        // conflict reporting. Methods that could be parameterized to report a210        // conflict in different format, file+offset, file+line+col, or even211        // more human readable using VCS conflict markers.212        // For now, printing directly the error reported by `AtomicChange` is213        // the easiest solution.214        errs() << llvm::toString(std::move(Err)) << "\n";215        if (IgnoreInsertConflict) {216          tooling::Replacements &Replacements = FileChange.getReplacements();217          unsigned NewOffset =218              Replacements.getShiftedCodePosition(R.getOffset());219          unsigned NewLength = Replacements.getShiftedCodePosition(220                                   R.getOffset() + R.getLength()) -221                               NewOffset;222          if (NewLength == R.getLength()) {223            tooling::Replacement RR = tooling::Replacement(224                R.getFilePath(), NewOffset, NewLength, R.getReplacementText());225            Replacements = Replacements.merge(tooling::Replacements(RR));226          } else {227            llvm::errs()228                << "Can't resolve conflict, skipping the replacement.\n";229            ConflictDetected = true;230          }231        } else232          ConflictDetected = true;233      }234    }235    FileChanges.try_emplace(Entry,236                            std::vector<tooling::AtomicChange>{FileChange});237  }238 239  return !ConflictDetected;240}241 242llvm::Expected<std::string>243applyChanges(StringRef File, const std::vector<tooling::AtomicChange> &Changes,244             const tooling::ApplyChangesSpec &Spec,245             DiagnosticsEngine &Diagnostics) {246  FileManager Files((FileSystemOptions()));247  SourceManager SM(Diagnostics, Files);248 249  llvm::ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =250      SM.getFileManager().getBufferForFile(File);251  if (!Buffer)252    return errorCodeToError(Buffer.getError());253  return tooling::applyAtomicChanges(File, Buffer.get()->getBuffer(), Changes,254                                     Spec);255}256 257bool deleteReplacementFiles(const TUReplacementFiles &Files,258                            clang::DiagnosticsEngine &Diagnostics) {259  bool Success = true;260  for (const auto &Filename : Files) {261    std::error_code Error = llvm::sys::fs::remove(Filename);262    if (Error) {263      Success = false;264      // FIXME: Use Diagnostics for outputting errors.265      errs() << "Error deleting file: " << Filename << "\n";266      errs() << Error.message() << "\n";267      errs() << "Please delete the file manually\n";268    }269  }270  return Success;271}272 273} // end namespace replace274} // end namespace clang275