blob: 7ab7e91c30c083bd2909cb74903e602a447880cb [file] [log] [blame]
//===-- 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;
}