forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			401 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
//===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
 | 
						|
//
 | 
						|
//                     The LLVM Compiler Infrastructure
 | 
						|
//
 | 
						|
// This file is distributed under the University of Illinois Open Source
 | 
						|
// License. See LICENSE.TXT for details.
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
///
 | 
						|
/// \file
 | 
						|
/// \brief This file provides the implementation for deduplicating, detecting
 | 
						|
/// conflicts in, and applying collections of Replacements.
 | 
						|
///
 | 
						|
/// FIXME: Use Diagnostics for output instead of llvm::errs().
 | 
						|
///
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
#include "clang-apply-replacements/Tooling/ApplyReplacements.h"
 | 
						|
#include "clang/Basic/LangOptions.h"
 | 
						|
#include "clang/Basic/SourceManager.h"
 | 
						|
#include "clang/Format/Format.h"
 | 
						|
#include "clang/Lex/Lexer.h"
 | 
						|
#include "clang/Rewrite/Core/Rewriter.h"
 | 
						|
#include "clang/Tooling/DiagnosticsYaml.h"
 | 
						|
#include "clang/Tooling/ReplacementsYaml.h"
 | 
						|
#include "llvm/ADT/ArrayRef.h"
 | 
						|
#include "llvm/Support/FileSystem.h"
 | 
						|
#include "llvm/Support/MemoryBuffer.h"
 | 
						|
#include "llvm/Support/Path.h"
 | 
						|
#include "llvm/Support/raw_ostream.h"
 | 
						|
 | 
						|
using namespace llvm;
 | 
						|
using namespace clang;
 | 
						|
 | 
						|
static void eatDiagnostics(const SMDiagnostic &, void *) {}
 | 
						|
 | 
						|
namespace clang {
 | 
						|
namespace replace {
 | 
						|
 | 
						|
std::error_code collectReplacementsFromDirectory(
 | 
						|
    const llvm::StringRef Directory, TUReplacements &TUs,
 | 
						|
    TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
 | 
						|
  using namespace llvm::sys::fs;
 | 
						|
  using namespace llvm::sys::path;
 | 
						|
 | 
						|
  std::error_code ErrorCode;
 | 
						|
 | 
						|
  for (recursive_directory_iterator I(Directory, ErrorCode), E;
 | 
						|
       I != E && !ErrorCode; I.increment(ErrorCode)) {
 | 
						|
    if (filename(I->path())[0] == '.') {
 | 
						|
      // Indicate not to descend into directories beginning with '.'
 | 
						|
      I.no_push();
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (extension(I->path()) != ".yaml")
 | 
						|
      continue;
 | 
						|
 | 
						|
    TUFiles.push_back(I->path());
 | 
						|
 | 
						|
    ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
 | 
						|
        MemoryBuffer::getFile(I->path());
 | 
						|
    if (std::error_code BufferError = Out.getError()) {
 | 
						|
      errs() << "Error reading " << I->path() << ": " << BufferError.message()
 | 
						|
             << "\n";
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
 | 
						|
    tooling::TranslationUnitReplacements TU;
 | 
						|
    YIn >> TU;
 | 
						|
    if (YIn.error()) {
 | 
						|
      // File doesn't appear to be a header change description. Ignore it.
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Only keep files that properly parse.
 | 
						|
    TUs.push_back(TU);
 | 
						|
  }
 | 
						|
 | 
						|
  return ErrorCode;
 | 
						|
}
 | 
						|
 | 
						|
std::error_code
 | 
						|
collectReplacementsFromDirectory(const llvm::StringRef Directory,
 | 
						|
                                 TUDiagnostics &TUs, TUReplacementFiles &TUFiles,
 | 
						|
                                 clang::DiagnosticsEngine &Diagnostics) {
 | 
						|
  using namespace llvm::sys::fs;
 | 
						|
  using namespace llvm::sys::path;
 | 
						|
 | 
						|
  std::error_code ErrorCode;
 | 
						|
 | 
						|
  for (recursive_directory_iterator I(Directory, ErrorCode), E;
 | 
						|
       I != E && !ErrorCode; I.increment(ErrorCode)) {
 | 
						|
    if (filename(I->path())[0] == '.') {
 | 
						|
      // Indicate not to descend into directories beginning with '.'
 | 
						|
      I.no_push();
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    if (extension(I->path()) != ".yaml")
 | 
						|
      continue;
 | 
						|
 | 
						|
    TUFiles.push_back(I->path());
 | 
						|
 | 
						|
    ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
 | 
						|
        MemoryBuffer::getFile(I->path());
 | 
						|
    if (std::error_code BufferError = Out.getError()) {
 | 
						|
      errs() << "Error reading " << I->path() << ": " << BufferError.message()
 | 
						|
             << "\n";
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
 | 
						|
    tooling::TranslationUnitDiagnostics TU;
 | 
						|
    YIn >> TU;
 | 
						|
    if (YIn.error()) {
 | 
						|
      // File doesn't appear to be a header change description. Ignore it.
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
 | 
						|
    // Only keep files that properly parse.
 | 
						|
    TUs.push_back(TU);
 | 
						|
  }
 | 
						|
 | 
						|
  return ErrorCode;
 | 
						|
}
 | 
						|
 | 
						|
/// \brief Dumps information for a sequence of conflicting Replacements.
 | 
						|
///
 | 
						|
/// \param[in] File FileEntry for the file the conflicting Replacements are
 | 
						|
/// for.
 | 
						|
/// \param[in] ConflictingReplacements List of conflicting Replacements.
 | 
						|
/// \param[in] SM SourceManager used for reporting.
 | 
						|
static void reportConflict(
 | 
						|
    const FileEntry *File,
 | 
						|
    const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements,
 | 
						|
    SourceManager &SM) {
 | 
						|
  FileID FID = SM.translateFile(File);
 | 
						|
  if (FID.isInvalid())
 | 
						|
    FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
 | 
						|
 | 
						|
  // FIXME: Output something a little more user-friendly (e.g. unified diff?)
 | 
						|
  errs() << "The following changes conflict:\n";
 | 
						|
  for (const tooling::Replacement &R : ConflictingReplacements) {
 | 
						|
    if (R.getLength() == 0) {
 | 
						|
      errs() << "  Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
 | 
						|
             << SM.getColumnNumber(FID, R.getOffset()) << " "
 | 
						|
             << R.getReplacementText() << "\n";
 | 
						|
    } else {
 | 
						|
      if (R.getReplacementText().empty())
 | 
						|
        errs() << "  Remove ";
 | 
						|
      else
 | 
						|
        errs() << "  Replace ";
 | 
						|
 | 
						|
      errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
 | 
						|
             << SM.getColumnNumber(FID, R.getOffset()) << "-"
 | 
						|
             << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
 | 
						|
             << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
 | 
						|
 | 
						|
      if (R.getReplacementText().empty())
 | 
						|
        errs() << "\n";
 | 
						|
      else
 | 
						|
        errs() << " with \"" << R.getReplacementText() << "\"\n";
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// FIXME: Remove this function after changing clang-apply-replacements to use
 | 
						|
// Replacements class.
 | 
						|
bool applyAllReplacements(const std::vector<tooling::Replacement> &Replaces,
 | 
						|
                          Rewriter &Rewrite) {
 | 
						|
  bool Result = true;
 | 
						|
  for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
 | 
						|
    if (I->isApplicable()) {
 | 
						|
      Result = I->apply(Rewrite) && Result;
 | 
						|
    } else {
 | 
						|
      Result = false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return Result;
 | 
						|
}
 | 
						|
 | 
						|
// FIXME: moved from libToolingCore. remove this when std::vector<Replacement>
 | 
						|
// is replaced with tooling::Replacements class.
 | 
						|
static void deduplicate(std::vector<tooling::Replacement> &Replaces,
 | 
						|
                        std::vector<tooling::Range> &Conflicts) {
 | 
						|
  if (Replaces.empty())
 | 
						|
    return;
 | 
						|
 | 
						|
  auto LessNoPath = [](const tooling::Replacement &LHS,
 | 
						|
                       const tooling::Replacement &RHS) {
 | 
						|
    if (LHS.getOffset() != RHS.getOffset())
 | 
						|
      return LHS.getOffset() < RHS.getOffset();
 | 
						|
    if (LHS.getLength() != RHS.getLength())
 | 
						|
      return LHS.getLength() < RHS.getLength();
 | 
						|
    return LHS.getReplacementText() < RHS.getReplacementText();
 | 
						|
  };
 | 
						|
 | 
						|
  auto EqualNoPath = [](const tooling::Replacement &LHS,
 | 
						|
                        const tooling::Replacement &RHS) {
 | 
						|
    return LHS.getOffset() == RHS.getOffset() &&
 | 
						|
           LHS.getLength() == RHS.getLength() &&
 | 
						|
           LHS.getReplacementText() == RHS.getReplacementText();
 | 
						|
  };
 | 
						|
 | 
						|
  // Deduplicate. We don't want to deduplicate based on the path as we assume
 | 
						|
  // that all replacements refer to the same file (or are symlinks).
 | 
						|
  std::sort(Replaces.begin(), Replaces.end(), LessNoPath);
 | 
						|
  Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath),
 | 
						|
                 Replaces.end());
 | 
						|
 | 
						|
  // Detect conflicts
 | 
						|
  tooling::Range ConflictRange(Replaces.front().getOffset(),
 | 
						|
                               Replaces.front().getLength());
 | 
						|
  unsigned ConflictStart = 0;
 | 
						|
  unsigned ConflictLength = 1;
 | 
						|
  for (unsigned i = 1; i < Replaces.size(); ++i) {
 | 
						|
    tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
 | 
						|
    if (ConflictRange.overlapsWith(Current)) {
 | 
						|
      // Extend conflicted range
 | 
						|
      ConflictRange =
 | 
						|
          tooling::Range(ConflictRange.getOffset(),
 | 
						|
                         std::max(ConflictRange.getLength(),
 | 
						|
                                  Current.getOffset() + Current.getLength() -
 | 
						|
                                      ConflictRange.getOffset()));
 | 
						|
      ++ConflictLength;
 | 
						|
    } else {
 | 
						|
      if (ConflictLength > 1)
 | 
						|
        Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
 | 
						|
      ConflictRange = Current;
 | 
						|
      ConflictStart = i;
 | 
						|
      ConflictLength = 1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (ConflictLength > 1)
 | 
						|
    Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
 | 
						|
}
 | 
						|
 | 
						|
/// \brief Deduplicates and tests for conflicts among the replacements for each
 | 
						|
/// file in \c Replacements. Any conflicts found are reported.
 | 
						|
///
 | 
						|
/// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
 | 
						|
///
 | 
						|
/// \param[in,out] Replacements Container of all replacements grouped by file
 | 
						|
/// to be deduplicated and checked for conflicts.
 | 
						|
/// \param[in] SM SourceManager required for conflict reporting.
 | 
						|
///
 | 
						|
/// \returns \parblock
 | 
						|
///          \li true if conflicts were detected
 | 
						|
///          \li false if no conflicts were detected
 | 
						|
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements,
 | 
						|
                                          SourceManager &SM) {
 | 
						|
  bool conflictsFound = false;
 | 
						|
 | 
						|
  for (auto &FileAndReplacements : Replacements) {
 | 
						|
    const FileEntry *Entry = FileAndReplacements.first;
 | 
						|
    auto &Replacements = FileAndReplacements.second;
 | 
						|
    assert(Entry != nullptr && "No file entry!");
 | 
						|
 | 
						|
    std::vector<tooling::Range> Conflicts;
 | 
						|
    deduplicate(FileAndReplacements.second, Conflicts);
 | 
						|
 | 
						|
    if (Conflicts.empty())
 | 
						|
      continue;
 | 
						|
 | 
						|
    conflictsFound = true;
 | 
						|
 | 
						|
    errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
 | 
						|
 | 
						|
    for (const tooling::Range &Conflict : Conflicts) {
 | 
						|
      auto ConflictingReplacements = llvm::makeArrayRef(
 | 
						|
          &Replacements[Conflict.getOffset()], Conflict.getLength());
 | 
						|
      reportConflict(Entry, ConflictingReplacements, SM);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return conflictsFound;
 | 
						|
}
 | 
						|
 | 
						|
bool mergeAndDeduplicate(const TUReplacements &TUs,
 | 
						|
                         FileToReplacementsMap &GroupedReplacements,
 | 
						|
                         clang::SourceManager &SM) {
 | 
						|
 | 
						|
  // Group all replacements by target file.
 | 
						|
  std::set<StringRef> Warned;
 | 
						|
  for (const auto &TU : TUs) {
 | 
						|
    for (const tooling::Replacement &R : TU.Replacements) {
 | 
						|
      // Use the file manager to deduplicate paths. FileEntries are
 | 
						|
      // automatically canonicalized.
 | 
						|
      if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
 | 
						|
        GroupedReplacements[Entry].push_back(R);
 | 
						|
      } else if (Warned.insert(R.getFilePath()).second) {
 | 
						|
          errs() << "Described file '" << R.getFilePath()
 | 
						|
                 << "' doesn't exist. Ignoring...\n";
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Ask clang to deduplicate and report conflicts.
 | 
						|
  return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
 | 
						|
}
 | 
						|
 | 
						|
bool mergeAndDeduplicate(const TUDiagnostics &TUs,
 | 
						|
                         FileToReplacementsMap &GroupedReplacements,
 | 
						|
                         clang::SourceManager &SM) {
 | 
						|
 | 
						|
  // Group all replacements by target file.
 | 
						|
  std::set<StringRef> Warned;
 | 
						|
  for (const auto &TU : TUs) {
 | 
						|
    for (const auto &D : TU.Diagnostics) {
 | 
						|
      for (const auto &Fix : D.Fix) {
 | 
						|
        for (const tooling::Replacement &R : Fix.second) {
 | 
						|
          // Use the file manager to deduplicate paths. FileEntries are
 | 
						|
          // automatically canonicalized.
 | 
						|
          if (const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath())) {
 | 
						|
            GroupedReplacements[Entry].push_back(R);
 | 
						|
          } else if (Warned.insert(R.getFilePath()).second) {
 | 
						|
              errs() << "Described file '" << R.getFilePath()
 | 
						|
                     << "' doesn't exist. Ignoring...\n";
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Ask clang to deduplicate and report conflicts.
 | 
						|
  return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
 | 
						|
}
 | 
						|
 | 
						|
bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
 | 
						|
                       clang::Rewriter &Rewrites) {
 | 
						|
 | 
						|
  // Apply all changes
 | 
						|
  //
 | 
						|
  // FIXME: No longer certain GroupedReplacements is really the best kind of
 | 
						|
  // data structure for applying replacements. Rewriter certainly doesn't care.
 | 
						|
  // However, until we nail down the design of ReplacementGroups, might as well
 | 
						|
  // leave this as is.
 | 
						|
  for (const auto &FileAndReplacements : GroupedReplacements) {
 | 
						|
    if (!applyAllReplacements(FileAndReplacements.second, Rewrites))
 | 
						|
      return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
RangeVector calculateChangedRanges(
 | 
						|
    const std::vector<clang::tooling::Replacement> &Replaces) {
 | 
						|
  RangeVector ChangedRanges;
 | 
						|
 | 
						|
  // Generate the new ranges from the replacements.
 | 
						|
  int Shift = 0;
 | 
						|
  for (const tooling::Replacement &R : Replaces) {
 | 
						|
    unsigned Offset = R.getOffset() + Shift;
 | 
						|
    unsigned Length = R.getReplacementText().size();
 | 
						|
    Shift += Length - R.getLength();
 | 
						|
    ChangedRanges.push_back(tooling::Range(Offset, Length));
 | 
						|
  }
 | 
						|
 | 
						|
  return ChangedRanges;
 | 
						|
}
 | 
						|
 | 
						|
bool writeFiles(const clang::Rewriter &Rewrites) {
 | 
						|
 | 
						|
  for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end();
 | 
						|
       BufferI != BufferE; ++BufferI) {
 | 
						|
    StringRef FileName =
 | 
						|
        Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
 | 
						|
 | 
						|
    std::error_code EC;
 | 
						|
    llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text);
 | 
						|
    if (EC) {
 | 
						|
      errs() << "Warning: Could not write to " << EC.message() << "\n";
 | 
						|
      continue;
 | 
						|
    }
 | 
						|
    BufferI->second.write(FileStream);
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool deleteReplacementFiles(const TUReplacementFiles &Files,
 | 
						|
                            clang::DiagnosticsEngine &Diagnostics) {
 | 
						|
  bool Success = true;
 | 
						|
  for (const auto &Filename : Files) {
 | 
						|
    std::error_code Error = llvm::sys::fs::remove(Filename);
 | 
						|
    if (Error) {
 | 
						|
      Success = false;
 | 
						|
      // FIXME: Use Diagnostics for outputting errors.
 | 
						|
      errs() << "Error deleting file: " << Filename << "\n";
 | 
						|
      errs() << Error.message() << "\n";
 | 
						|
      errs() << "Please delete the file manually\n";
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return Success;
 | 
						|
}
 | 
						|
 | 
						|
} // end namespace replace
 | 
						|
} // end namespace clang
 |