teak-llvm/clang/lib/Edit/EditedSource.cpp
Chandler Carruth 2946cd7010 Update the file headers across all of the LLVM projects in the monorepo
to reflect the new license.

We understand that people may be surprised that we're moving the header
entirely to discuss the new license. We checked this carefully with the
Foundation's lawyer and we believe this is the correct approach.

Essentially, all code in the project is now made available by the LLVM
project under our new license, so you will see that the license headers
include that license only. Some of our contributors have contributed
code under our old license, and accordingly, we have retained a copy of
our old license notice in the top-level files in each project and
repository.

llvm-svn: 351636
2019-01-19 08:50:56 +00:00

481 lines
14 KiB
C++

//===- EditedSource.cpp - Collection of source edits ----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/Edit/EditedSource.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Edit/Commit.h"
#include "clang/Edit/EditsReceiver.h"
#include "clang/Edit/FileOffset.h"
#include "clang/Lex/Lexer.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include <algorithm>
#include <cassert>
#include <tuple>
#include <utility>
using namespace clang;
using namespace edit;
void EditsReceiver::remove(CharSourceRange range) {
replace(range, StringRef());
}
void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
SourceLocation &ExpansionLoc,
MacroArgUse &ArgUse) {
assert(SourceMgr.isMacroArgExpansion(Loc));
SourceLocation DefArgLoc =
SourceMgr.getImmediateExpansionRange(Loc).getBegin();
SourceLocation ImmediateExpansionLoc =
SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();
ExpansionLoc = ImmediateExpansionLoc;
while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
ExpansionLoc =
SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
SmallString<20> Buf;
StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
Buf, SourceMgr, LangOpts);
ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
if (!ArgName.empty())
ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
SourceMgr.getSpellingLoc(DefArgLoc)};
}
void EditedSource::startingCommit() {}
void EditedSource::finishedCommit() {
for (auto &ExpArg : CurrCommitMacroArgExps) {
SourceLocation ExpLoc;
MacroArgUse ArgUse;
std::tie(ExpLoc, ArgUse) = ExpArg;
auto &ArgUses = ExpansionToArgMap[ExpLoc.getRawEncoding()];
if (std::find(ArgUses.begin(), ArgUses.end(), ArgUse) == ArgUses.end())
ArgUses.push_back(ArgUse);
}
CurrCommitMacroArgExps.clear();
}
StringRef EditedSource::copyString(const Twine &twine) {
SmallString<128> Data;
return copyString(twine.toStringRef(Data));
}
bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
FileEditsTy::iterator FA = getActionForOffset(Offs);
if (FA != FileEdits.end()) {
if (FA->first != Offs)
return false; // position has been removed.
}
if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
SourceLocation ExpLoc;
MacroArgUse ArgUse;
deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
auto I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
if (I != ExpansionToArgMap.end() &&
find_if(I->second, [&](const MacroArgUse &U) {
return ArgUse.Identifier == U.Identifier &&
std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
std::tie(U.ImmediateExpansionLoc, U.UseLoc);
}) != I->second.end()) {
// Trying to write in a macro argument input that has already been
// written by a previous commit for another expansion of the same macro
// argument name. For example:
//
// \code
// #define MAC(x) ((x)+(x))
// MAC(a)
// \endcode
//
// A commit modified the macro argument 'a' due to the first '(x)'
// expansion inside the macro definition, and a subsequent commit tried
// to modify 'a' again for the second '(x)' expansion. The edits of the
// second commit will be rejected.
return false;
}
}
return true;
}
bool EditedSource::commitInsert(SourceLocation OrigLoc,
FileOffset Offs, StringRef text,
bool beforePreviousInsertions) {
if (!canInsertInOffset(OrigLoc, Offs))
return false;
if (text.empty())
return true;
if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
MacroArgUse ArgUse;
SourceLocation ExpLoc;
deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
if (ArgUse.Identifier)
CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
}
FileEdit &FA = FileEdits[Offs];
if (FA.Text.empty()) {
FA.Text = copyString(text);
return true;
}
if (beforePreviousInsertions)
FA.Text = copyString(Twine(text) + FA.Text);
else
FA.Text = copyString(Twine(FA.Text) + text);
return true;
}
bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
FileOffset Offs,
FileOffset InsertFromRangeOffs, unsigned Len,
bool beforePreviousInsertions) {
if (Len == 0)
return true;
SmallString<128> StrVec;
FileOffset BeginOffs = InsertFromRangeOffs;
FileOffset EndOffs = BeginOffs.getWithOffset(Len);
FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
if (I != FileEdits.begin())
--I;
for (; I != FileEdits.end(); ++I) {
FileEdit &FA = I->second;
FileOffset B = I->first;
FileOffset E = B.getWithOffset(FA.RemoveLen);
if (BeginOffs == B)
break;
if (BeginOffs < E) {
if (BeginOffs > B) {
BeginOffs = E;
++I;
}
break;
}
}
for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
FileEdit &FA = I->second;
FileOffset B = I->first;
FileOffset E = B.getWithOffset(FA.RemoveLen);
if (BeginOffs < B) {
bool Invalid = false;
StringRef text = getSourceText(BeginOffs, B, Invalid);
if (Invalid)
return false;
StrVec += text;
}
StrVec += FA.Text;
BeginOffs = E;
}
if (BeginOffs < EndOffs) {
bool Invalid = false;
StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
if (Invalid)
return false;
StrVec += text;
}
return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
}
void EditedSource::commitRemove(SourceLocation OrigLoc,
FileOffset BeginOffs, unsigned Len) {
if (Len == 0)
return;
FileOffset EndOffs = BeginOffs.getWithOffset(Len);
FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
if (I != FileEdits.begin())
--I;
for (; I != FileEdits.end(); ++I) {
FileEdit &FA = I->second;
FileOffset B = I->first;
FileOffset E = B.getWithOffset(FA.RemoveLen);
if (BeginOffs < E)
break;
}
FileOffset TopBegin, TopEnd;
FileEdit *TopFA = nullptr;
if (I == FileEdits.end()) {
FileEditsTy::iterator
NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
NewI->second.RemoveLen = Len;
return;
}
FileEdit &FA = I->second;
FileOffset B = I->first;
FileOffset E = B.getWithOffset(FA.RemoveLen);
if (BeginOffs < B) {
FileEditsTy::iterator
NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
TopBegin = BeginOffs;
TopEnd = EndOffs;
TopFA = &NewI->second;
TopFA->RemoveLen = Len;
} else {
TopBegin = B;
TopEnd = E;
TopFA = &I->second;
if (TopEnd >= EndOffs)
return;
unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
TopEnd = EndOffs;
TopFA->RemoveLen += diff;
if (B == BeginOffs)
TopFA->Text = StringRef();
++I;
}
while (I != FileEdits.end()) {
FileEdit &FA = I->second;
FileOffset B = I->first;
FileOffset E = B.getWithOffset(FA.RemoveLen);
if (B >= TopEnd)
break;
if (E <= TopEnd) {
FileEdits.erase(I++);
continue;
}
if (B < TopEnd) {
unsigned diff = E.getOffset() - TopEnd.getOffset();
TopEnd = E;
TopFA->RemoveLen += diff;
FileEdits.erase(I);
}
break;
}
}
bool EditedSource::commit(const Commit &commit) {
if (!commit.isCommitable())
return false;
struct CommitRAII {
EditedSource &Editor;
CommitRAII(EditedSource &Editor) : Editor(Editor) {
Editor.startingCommit();
}
~CommitRAII() {
Editor.finishedCommit();
}
} CommitRAII(*this);
for (edit::Commit::edit_iterator
I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
const edit::Commit::Edit &edit = *I;
switch (edit.Kind) {
case edit::Commit::Act_Insert:
commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
break;
case edit::Commit::Act_InsertFromRange:
commitInsertFromRange(edit.OrigLoc, edit.Offset,
edit.InsertFromRangeOffs, edit.Length,
edit.BeforePrev);
break;
case edit::Commit::Act_Remove:
commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
break;
}
}
return true;
}
// Returns true if it is ok to make the two given characters adjacent.
static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
// FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
// making two '<' adjacent.
return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
Lexer::isIdentifierBodyChar(right, LangOpts));
}
/// Returns true if it is ok to eliminate the trailing whitespace between
/// the given characters.
static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
const LangOptions &LangOpts) {
if (!canBeJoined(left, right, LangOpts))
return false;
if (isWhitespace(left) || isWhitespace(right))
return true;
if (canBeJoined(beforeWSpace, right, LangOpts))
return false; // the whitespace was intentional, keep it.
return true;
}
/// Check the range that we are going to remove and:
/// -Remove any trailing whitespace if possible.
/// -Insert a space if removing the range is going to mess up the source tokens.
static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
SourceLocation Loc, FileOffset offs,
unsigned &len, StringRef &text) {
assert(len && text.empty());
SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
if (BeginTokLoc != Loc)
return; // the range is not at the beginning of a token, keep the range.
bool Invalid = false;
StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
if (Invalid)
return;
unsigned begin = offs.getOffset();
unsigned end = begin + len;
// Do not try to extend the removal if we're at the end of the buffer already.
if (end == buffer.size())
return;
assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
// FIXME: Remove newline.
if (begin == 0) {
if (buffer[end] == ' ')
++len;
return;
}
if (buffer[end] == ' ') {
assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
"buffer not zero-terminated!");
if (canRemoveWhitespace(/*left=*/buffer[begin-1],
/*beforeWSpace=*/buffer[end-1],
/*right=*/buffer.data()[end + 1], // zero-terminated
LangOpts))
++len;
return;
}
if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
text = " ";
}
static void applyRewrite(EditsReceiver &receiver,
StringRef text, FileOffset offs, unsigned len,
const SourceManager &SM, const LangOptions &LangOpts,
bool shouldAdjustRemovals) {
assert(offs.getFID().isValid());
SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
Loc = Loc.getLocWithOffset(offs.getOffset());
assert(Loc.isFileID());
if (text.empty() && shouldAdjustRemovals)
adjustRemoval(SM, LangOpts, Loc, offs, len, text);
CharSourceRange range = CharSourceRange::getCharRange(Loc,
Loc.getLocWithOffset(len));
if (text.empty()) {
assert(len);
receiver.remove(range);
return;
}
if (len)
receiver.replace(range, text);
else
receiver.insert(Loc, text);
}
void EditedSource::applyRewrites(EditsReceiver &receiver,
bool shouldAdjustRemovals) {
SmallString<128> StrVec;
FileOffset CurOffs, CurEnd;
unsigned CurLen;
if (FileEdits.empty())
return;
FileEditsTy::iterator I = FileEdits.begin();
CurOffs = I->first;
StrVec = I->second.Text;
CurLen = I->second.RemoveLen;
CurEnd = CurOffs.getWithOffset(CurLen);
++I;
for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
FileOffset offs = I->first;
FileEdit act = I->second;
assert(offs >= CurEnd);
if (offs == CurEnd) {
StrVec += act.Text;
CurLen += act.RemoveLen;
CurEnd.getWithOffset(act.RemoveLen);
continue;
}
applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
shouldAdjustRemovals);
CurOffs = offs;
StrVec = act.Text;
CurLen = act.RemoveLen;
CurEnd = CurOffs.getWithOffset(CurLen);
}
applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
shouldAdjustRemovals);
}
void EditedSource::clearRewrites() {
FileEdits.clear();
StrAlloc.Reset();
}
StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
bool &Invalid) {
assert(BeginOffs.getFID() == EndOffs.getFID());
assert(BeginOffs <= EndOffs);
SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
assert(BLoc.isFileID());
SourceLocation
ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
SourceMgr, LangOpts, &Invalid);
}
EditedSource::FileEditsTy::iterator
EditedSource::getActionForOffset(FileOffset Offs) {
FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
if (I == FileEdits.begin())
return FileEdits.end();
--I;
FileEdit &FA = I->second;
FileOffset B = I->first;
FileOffset E = B.getWithOffset(FA.RemoveLen);
if (Offs >= B && Offs < E)
return I;
return FileEdits.end();
}