199 lines
7.3 KiB
C++
199 lines
7.3 KiB
C++
//===-- Core/FileOverrides.cpp --------------------------------------------===//
|
|
//
|
|
// 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 types and functionality for dealing with source
|
|
/// and header file content overrides.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Core/FileOverrides.h"
|
|
#include "clang/Basic/Diagnostic.h"
|
|
#include "clang/Basic/DiagnosticOptions.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Rewrite/Core/Rewriter.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "clang/Tooling/ReplacementsYaml.h"
|
|
#include "llvm/Support/FileSystem.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include "llvm/Support/system_error.h"
|
|
#include <algorithm>
|
|
|
|
using namespace clang;
|
|
using namespace clang::tooling;
|
|
|
|
bool generateReplacementsFileName(const llvm::StringRef MainSourceFile,
|
|
llvm::SmallVectorImpl<char> &Result,
|
|
llvm::SmallVectorImpl<char> &Error) {
|
|
using namespace llvm::sys;
|
|
|
|
Error.clear();
|
|
if (llvm::error_code EC = fs::createUniqueFile(
|
|
MainSourceFile + "_%%_%%_%%_%%_%%_%%.yaml", Result)) {
|
|
Error.append(EC.message().begin(), EC.message().end());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// \brief Comparator to be able to order tooling::Range based on their offset.
|
|
bool rangeLess(clang::tooling::Range A, clang::tooling::Range B) {
|
|
if (A.getOffset() == B.getOffset())
|
|
return A.getLength() < B.getLength();
|
|
return A.getOffset() < B.getOffset();
|
|
}
|
|
|
|
/// \brief Functor that returns the given range without its overlaps with the
|
|
/// replacement given in the constructor.
|
|
struct RangeReplacedAdjuster {
|
|
RangeReplacedAdjuster(const tooling::Replacement &Replace)
|
|
: Replace(Replace.getOffset(), Replace.getLength()),
|
|
ReplaceNewSize(Replace.getReplacementText().size()) {}
|
|
|
|
tooling::Range operator()(clang::tooling::Range Range) const {
|
|
if (!Range.overlapsWith(Replace))
|
|
return Range;
|
|
// range inside replacement -> make the range length null
|
|
if (Replace.contains(Range))
|
|
return tooling::Range(Range.getOffset(), 0);
|
|
// replacement inside range -> resize the range
|
|
if (Range.contains(Replace)) {
|
|
int Difference = ReplaceNewSize - Replace.getLength();
|
|
return tooling::Range(Range.getOffset(), Range.getLength() + Difference);
|
|
}
|
|
// beginning of the range replaced -> truncate range beginning
|
|
if (Range.getOffset() > Replace.getOffset()) {
|
|
unsigned ReplaceEnd = Replace.getOffset() + Replace.getLength();
|
|
unsigned RangeEnd = Range.getOffset() + Range.getLength();
|
|
return tooling::Range(ReplaceEnd, RangeEnd - ReplaceEnd);
|
|
}
|
|
// end of the range replaced -> truncate range end
|
|
if (Range.getOffset() < Replace.getOffset())
|
|
return tooling::Range(Range.getOffset(),
|
|
Replace.getOffset() - Range.getOffset());
|
|
llvm_unreachable("conditions not handled properly");
|
|
}
|
|
|
|
const tooling::Range Replace;
|
|
const unsigned ReplaceNewSize;
|
|
};
|
|
|
|
} // end anonymous namespace
|
|
|
|
void
|
|
ChangedRanges::adjustChangedRanges(const tooling::ReplacementsVec &Replaces) {
|
|
// first adjust existing ranges in case they overlap with the replacements
|
|
for (ReplacementsVec::const_iterator I = Replaces.begin(), E = Replaces.end();
|
|
I != E; ++I) {
|
|
const tooling::Replacement &Replace = *I;
|
|
|
|
std::transform(Ranges.begin(), Ranges.end(), Ranges.begin(),
|
|
RangeReplacedAdjuster(Replace));
|
|
}
|
|
|
|
// then shift existing ranges to reflect the new positions
|
|
for (RangeVec::iterator I = Ranges.begin(), E = Ranges.end(); I != E; ++I) {
|
|
unsigned ShiftedOffset =
|
|
tooling::shiftedCodePosition(Replaces, I->getOffset());
|
|
*I = tooling::Range(ShiftedOffset, I->getLength());
|
|
}
|
|
|
|
// then generate the new ranges from the replacements
|
|
for (ReplacementsVec::const_iterator I = Replaces.begin(), E = Replaces.end();
|
|
I != E; ++I) {
|
|
const tooling::Replacement &R = *I;
|
|
unsigned Offset = tooling::shiftedCodePosition(Replaces, R.getOffset());
|
|
unsigned Length = R.getReplacementText().size();
|
|
|
|
Ranges.push_back(tooling::Range(Offset, Length));
|
|
}
|
|
|
|
// cleanups unecessary ranges to finish
|
|
coalesceRanges();
|
|
}
|
|
|
|
void ChangedRanges::coalesceRanges() {
|
|
// sort the ranges by offset and then for each group of adjacent/overlapping
|
|
// ranges the first one in the group is extended to cover the whole group.
|
|
std::sort(Ranges.begin(), Ranges.end(), &rangeLess);
|
|
RangeVec::iterator FirstInGroup = Ranges.begin();
|
|
assert(!Ranges.empty() && "unexpected empty vector");
|
|
for (RangeVec::iterator I = Ranges.begin() + 1, E = Ranges.end(); I != E;
|
|
++I) {
|
|
unsigned GroupEnd = FirstInGroup->getOffset() + FirstInGroup->getLength();
|
|
|
|
// no contact
|
|
if (I->getOffset() > GroupEnd)
|
|
FirstInGroup = I;
|
|
else {
|
|
unsigned GrpBegin = FirstInGroup->getOffset();
|
|
unsigned GrpEnd = std::max(GroupEnd, I->getOffset() + I->getLength());
|
|
*FirstInGroup = tooling::Range(GrpBegin, GrpEnd - GrpBegin);
|
|
}
|
|
}
|
|
|
|
// remove the ranges that are covered by the first member of the group
|
|
Ranges.erase(std::unique(Ranges.begin(), Ranges.end(),
|
|
std::mem_fun_ref(&Range::contains)),
|
|
Ranges.end());
|
|
}
|
|
|
|
void FileOverrides::applyOverrides(clang::SourceManager &SM) const {
|
|
FileManager &FM = SM.getFileManager();
|
|
|
|
for (FileStateMap::const_iterator I = FileStates.begin(),
|
|
E = FileStates.end();
|
|
I != E; ++I) {
|
|
SM.overrideFileContents(FM.getFile(I->getKey()),
|
|
llvm::MemoryBuffer::getMemBuffer(I->getValue()));
|
|
}
|
|
}
|
|
|
|
void FileOverrides::adjustChangedRanges(
|
|
const clang::replace::FileToReplacementsMap &Replaces) {
|
|
|
|
for (replace::FileToReplacementsMap::const_iterator I = Replaces.begin(),
|
|
E = Replaces.end(); I != E; ++I) {
|
|
ChangeTracking[I->getKey()].adjustChangedRanges(I->getValue());
|
|
}
|
|
}
|
|
|
|
void FileOverrides::updateState(const clang::Rewriter &Rewrites) {
|
|
for (Rewriter::const_buffer_iterator BufferI = Rewrites.buffer_begin(),
|
|
BufferE = Rewrites.buffer_end();
|
|
BufferI != BufferE; ++BufferI) {
|
|
const char *FileName =
|
|
Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
|
|
const RewriteBuffer &RewriteBuf = BufferI->second;
|
|
FileStates[FileName].assign(RewriteBuf.begin(), RewriteBuf.end());
|
|
}
|
|
}
|
|
|
|
bool FileOverrides::writeToDisk(DiagnosticsEngine &Diagnostics) const {
|
|
bool Errors = false;
|
|
for (FileStateMap::const_iterator I = FileStates.begin(),
|
|
E = FileStates.end();
|
|
I != E; ++I) {
|
|
std::string ErrorInfo;
|
|
// The extra transform through std::string is to ensure null-termination
|
|
// of the filename stored as the key of the StringMap.
|
|
llvm::raw_fd_ostream FileStream(I->getKey().str().c_str(), ErrorInfo);
|
|
if (!ErrorInfo.empty()) {
|
|
llvm::errs() << "Failed to write new state for " << I->getKey() << ".\n";
|
|
Errors = true;
|
|
}
|
|
FileStream << I->getValue();
|
|
}
|
|
return !Errors;
|
|
}
|