Path: blob/main/contrib/llvm-project/clang/lib/Edit/EditedSource.cpp
35262 views
//===- EditedSource.cpp - Collection of source edits ----------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Edit/EditedSource.h"9#include "clang/Basic/CharInfo.h"10#include "clang/Basic/LLVM.h"11#include "clang/Basic/SourceLocation.h"12#include "clang/Basic/SourceManager.h"13#include "clang/Edit/Commit.h"14#include "clang/Edit/EditsReceiver.h"15#include "clang/Edit/FileOffset.h"16#include "clang/Lex/Lexer.h"17#include "llvm/ADT/STLExtras.h"18#include "llvm/ADT/SmallString.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/ADT/Twine.h"21#include <algorithm>22#include <cassert>23#include <tuple>24#include <utility>2526using namespace clang;27using namespace edit;2829void EditsReceiver::remove(CharSourceRange range) {30replace(range, StringRef());31}3233void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,34SourceLocation &ExpansionLoc,35MacroArgUse &ArgUse) {36assert(SourceMgr.isMacroArgExpansion(Loc));37SourceLocation DefArgLoc =38SourceMgr.getImmediateExpansionRange(Loc).getBegin();39SourceLocation ImmediateExpansionLoc =40SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();41ExpansionLoc = ImmediateExpansionLoc;42while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))43ExpansionLoc =44SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();45SmallString<20> Buf;46StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),47Buf, SourceMgr, LangOpts);48ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};49if (!ArgName.empty())50ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,51SourceMgr.getSpellingLoc(DefArgLoc)};52}5354void EditedSource::startingCommit() {}5556void EditedSource::finishedCommit() {57for (auto &ExpArg : CurrCommitMacroArgExps) {58SourceLocation ExpLoc;59MacroArgUse ArgUse;60std::tie(ExpLoc, ArgUse) = ExpArg;61auto &ArgUses = ExpansionToArgMap[ExpLoc];62if (!llvm::is_contained(ArgUses, ArgUse))63ArgUses.push_back(ArgUse);64}65CurrCommitMacroArgExps.clear();66}6768StringRef EditedSource::copyString(const Twine &twine) {69SmallString<128> Data;70return copyString(twine.toStringRef(Data));71}7273bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {74FileEditsTy::iterator FA = getActionForOffset(Offs);75if (FA != FileEdits.end()) {76if (FA->first != Offs)77return false; // position has been removed.78}7980if (SourceMgr.isMacroArgExpansion(OrigLoc)) {81SourceLocation ExpLoc;82MacroArgUse ArgUse;83deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);84auto I = ExpansionToArgMap.find(ExpLoc);85if (I != ExpansionToArgMap.end() &&86llvm::any_of(I->second, [&](const MacroArgUse &U) {87return ArgUse.Identifier == U.Identifier &&88std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=89std::tie(U.ImmediateExpansionLoc, U.UseLoc);90})) {91// Trying to write in a macro argument input that has already been92// written by a previous commit for another expansion of the same macro93// argument name. For example:94//95// \code96// #define MAC(x) ((x)+(x))97// MAC(a)98// \endcode99//100// A commit modified the macro argument 'a' due to the first '(x)'101// expansion inside the macro definition, and a subsequent commit tried102// to modify 'a' again for the second '(x)' expansion. The edits of the103// second commit will be rejected.104return false;105}106}107return true;108}109110bool EditedSource::commitInsert(SourceLocation OrigLoc,111FileOffset Offs, StringRef text,112bool beforePreviousInsertions) {113if (!canInsertInOffset(OrigLoc, Offs))114return false;115if (text.empty())116return true;117118if (SourceMgr.isMacroArgExpansion(OrigLoc)) {119MacroArgUse ArgUse;120SourceLocation ExpLoc;121deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);122if (ArgUse.Identifier)123CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);124}125126FileEdit &FA = FileEdits[Offs];127if (FA.Text.empty()) {128FA.Text = copyString(text);129return true;130}131132if (beforePreviousInsertions)133FA.Text = copyString(Twine(text) + FA.Text);134else135FA.Text = copyString(Twine(FA.Text) + text);136137return true;138}139140bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,141FileOffset Offs,142FileOffset InsertFromRangeOffs, unsigned Len,143bool beforePreviousInsertions) {144if (Len == 0)145return true;146147SmallString<128> StrVec;148FileOffset BeginOffs = InsertFromRangeOffs;149FileOffset EndOffs = BeginOffs.getWithOffset(Len);150FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);151if (I != FileEdits.begin())152--I;153154for (; I != FileEdits.end(); ++I) {155FileEdit &FA = I->second;156FileOffset B = I->first;157FileOffset E = B.getWithOffset(FA.RemoveLen);158159if (BeginOffs == B)160break;161162if (BeginOffs < E) {163if (BeginOffs > B) {164BeginOffs = E;165++I;166}167break;168}169}170171for (; I != FileEdits.end() && EndOffs > I->first; ++I) {172FileEdit &FA = I->second;173FileOffset B = I->first;174FileOffset E = B.getWithOffset(FA.RemoveLen);175176if (BeginOffs < B) {177bool Invalid = false;178StringRef text = getSourceText(BeginOffs, B, Invalid);179if (Invalid)180return false;181StrVec += text;182}183StrVec += FA.Text;184BeginOffs = E;185}186187if (BeginOffs < EndOffs) {188bool Invalid = false;189StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);190if (Invalid)191return false;192StrVec += text;193}194195return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);196}197198void EditedSource::commitRemove(SourceLocation OrigLoc,199FileOffset BeginOffs, unsigned Len) {200if (Len == 0)201return;202203FileOffset EndOffs = BeginOffs.getWithOffset(Len);204FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);205if (I != FileEdits.begin())206--I;207208for (; I != FileEdits.end(); ++I) {209FileEdit &FA = I->second;210FileOffset B = I->first;211FileOffset E = B.getWithOffset(FA.RemoveLen);212213if (BeginOffs < E)214break;215}216217FileOffset TopBegin, TopEnd;218FileEdit *TopFA = nullptr;219220if (I == FileEdits.end()) {221FileEditsTy::iterator222NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));223NewI->second.RemoveLen = Len;224return;225}226227FileEdit &FA = I->second;228FileOffset B = I->first;229FileOffset E = B.getWithOffset(FA.RemoveLen);230if (BeginOffs < B) {231FileEditsTy::iterator232NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));233TopBegin = BeginOffs;234TopEnd = EndOffs;235TopFA = &NewI->second;236TopFA->RemoveLen = Len;237} else {238TopBegin = B;239TopEnd = E;240TopFA = &I->second;241if (TopEnd >= EndOffs)242return;243unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();244TopEnd = EndOffs;245TopFA->RemoveLen += diff;246if (B == BeginOffs)247TopFA->Text = StringRef();248++I;249}250251while (I != FileEdits.end()) {252FileEdit &FA = I->second;253FileOffset B = I->first;254FileOffset E = B.getWithOffset(FA.RemoveLen);255256if (B >= TopEnd)257break;258259if (E <= TopEnd) {260FileEdits.erase(I++);261continue;262}263264if (B < TopEnd) {265unsigned diff = E.getOffset() - TopEnd.getOffset();266TopEnd = E;267TopFA->RemoveLen += diff;268FileEdits.erase(I);269}270271break;272}273}274275bool EditedSource::commit(const Commit &commit) {276if (!commit.isCommitable())277return false;278279struct CommitRAII {280EditedSource &Editor;281282CommitRAII(EditedSource &Editor) : Editor(Editor) {283Editor.startingCommit();284}285286~CommitRAII() {287Editor.finishedCommit();288}289} CommitRAII(*this);290291for (edit::Commit::edit_iterator292I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {293const edit::Commit::Edit &edit = *I;294switch (edit.Kind) {295case edit::Commit::Act_Insert:296commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);297break;298case edit::Commit::Act_InsertFromRange:299commitInsertFromRange(edit.OrigLoc, edit.Offset,300edit.InsertFromRangeOffs, edit.Length,301edit.BeforePrev);302break;303case edit::Commit::Act_Remove:304commitRemove(edit.OrigLoc, edit.Offset, edit.Length);305break;306}307}308309return true;310}311312// Returns true if it is ok to make the two given characters adjacent.313static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {314// FIXME: Should use TokenConcatenation to make sure we don't allow stuff like315// making two '<' adjacent.316return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&317Lexer::isAsciiIdentifierContinueChar(right, LangOpts));318}319320/// Returns true if it is ok to eliminate the trailing whitespace between321/// the given characters.322static bool canRemoveWhitespace(char left, char beforeWSpace, char right,323const LangOptions &LangOpts) {324if (!canBeJoined(left, right, LangOpts))325return false;326if (isWhitespace(left) || isWhitespace(right))327return true;328if (canBeJoined(beforeWSpace, right, LangOpts))329return false; // the whitespace was intentional, keep it.330return true;331}332333/// Check the range that we are going to remove and:334/// -Remove any trailing whitespace if possible.335/// -Insert a space if removing the range is going to mess up the source tokens.336static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,337SourceLocation Loc, FileOffset offs,338unsigned &len, StringRef &text) {339assert(len && text.empty());340SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);341if (BeginTokLoc != Loc)342return; // the range is not at the beginning of a token, keep the range.343344bool Invalid = false;345StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);346if (Invalid)347return;348349unsigned begin = offs.getOffset();350unsigned end = begin + len;351352// Do not try to extend the removal if we're at the end of the buffer already.353if (end == buffer.size())354return;355356assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");357358// FIXME: Remove newline.359360if (begin == 0) {361if (buffer[end] == ' ')362++len;363return;364}365366if (buffer[end] == ' ') {367assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&368"buffer not zero-terminated!");369if (canRemoveWhitespace(/*left=*/buffer[begin-1],370/*beforeWSpace=*/buffer[end-1],371/*right=*/buffer.data()[end + 1], // zero-terminated372LangOpts))373++len;374return;375}376377if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))378text = " ";379}380381static void applyRewrite(EditsReceiver &receiver,382StringRef text, FileOffset offs, unsigned len,383const SourceManager &SM, const LangOptions &LangOpts,384bool shouldAdjustRemovals) {385assert(offs.getFID().isValid());386SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());387Loc = Loc.getLocWithOffset(offs.getOffset());388assert(Loc.isFileID());389390if (text.empty() && shouldAdjustRemovals)391adjustRemoval(SM, LangOpts, Loc, offs, len, text);392393CharSourceRange range = CharSourceRange::getCharRange(Loc,394Loc.getLocWithOffset(len));395396if (text.empty()) {397assert(len);398receiver.remove(range);399return;400}401402if (len)403receiver.replace(range, text);404else405receiver.insert(Loc, text);406}407408void EditedSource::applyRewrites(EditsReceiver &receiver,409bool shouldAdjustRemovals) {410SmallString<128> StrVec;411FileOffset CurOffs, CurEnd;412unsigned CurLen;413414if (FileEdits.empty())415return;416417FileEditsTy::iterator I = FileEdits.begin();418CurOffs = I->first;419StrVec = I->second.Text;420CurLen = I->second.RemoveLen;421CurEnd = CurOffs.getWithOffset(CurLen);422++I;423424for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {425FileOffset offs = I->first;426FileEdit act = I->second;427assert(offs >= CurEnd);428429if (offs == CurEnd) {430StrVec += act.Text;431CurLen += act.RemoveLen;432CurEnd.getWithOffset(act.RemoveLen);433continue;434}435436applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,437shouldAdjustRemovals);438CurOffs = offs;439StrVec = act.Text;440CurLen = act.RemoveLen;441CurEnd = CurOffs.getWithOffset(CurLen);442}443444applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,445shouldAdjustRemovals);446}447448void EditedSource::clearRewrites() {449FileEdits.clear();450StrAlloc.Reset();451}452453StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,454bool &Invalid) {455assert(BeginOffs.getFID() == EndOffs.getFID());456assert(BeginOffs <= EndOffs);457SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());458BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());459assert(BLoc.isFileID());460SourceLocation461ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());462return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),463SourceMgr, LangOpts, &Invalid);464}465466EditedSource::FileEditsTy::iterator467EditedSource::getActionForOffset(FileOffset Offs) {468FileEditsTy::iterator I = FileEdits.upper_bound(Offs);469if (I == FileEdits.begin())470return FileEdits.end();471--I;472FileEdit &FA = I->second;473FileOffset B = I->first;474FileOffset E = B.getWithOffset(FA.RemoveLen);475if (Offs >= B && Offs < E)476return I;477478return FileEdits.end();479}480481482