479 lines · cpp
1//===- EditedSource.cpp - Collection of source edits ----------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/Edit/EditedSource.h"10#include "clang/Basic/CharInfo.h"11#include "clang/Basic/LLVM.h"12#include "clang/Basic/SourceLocation.h"13#include "clang/Basic/SourceManager.h"14#include "clang/Edit/Commit.h"15#include "clang/Edit/EditsReceiver.h"16#include "clang/Edit/FileOffset.h"17#include "clang/Lex/Lexer.h"18#include "llvm/ADT/STLExtras.h"19#include "llvm/ADT/StringRef.h"20#include "llvm/ADT/Twine.h"21#include <cassert>22#include <tuple>23#include <utility>24 25using namespace clang;26using namespace edit;27 28void EditsReceiver::remove(CharSourceRange range) {29 replace(range, StringRef());30}31 32void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,33 SourceLocation &ExpansionLoc,34 MacroArgUse &ArgUse) {35 assert(SourceMgr.isMacroArgExpansion(Loc));36 SourceLocation DefArgLoc =37 SourceMgr.getImmediateExpansionRange(Loc).getBegin();38 SourceLocation ImmediateExpansionLoc =39 SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();40 ExpansionLoc = ImmediateExpansionLoc;41 while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))42 ExpansionLoc =43 SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();44 SmallString<20> Buf;45 StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),46 Buf, SourceMgr, LangOpts);47 ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};48 if (!ArgName.empty())49 ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,50 SourceMgr.getSpellingLoc(DefArgLoc)};51}52 53void EditedSource::startingCommit() {}54 55void EditedSource::finishedCommit() {56 for (auto &ExpArg : CurrCommitMacroArgExps) {57 SourceLocation ExpLoc;58 MacroArgUse ArgUse;59 std::tie(ExpLoc, ArgUse) = ExpArg;60 auto &ArgUses = ExpansionToArgMap[ExpLoc];61 if (!llvm::is_contained(ArgUses, ArgUse))62 ArgUses.push_back(ArgUse);63 }64 CurrCommitMacroArgExps.clear();65}66 67StringRef EditedSource::copyString(const Twine &twine) {68 SmallString<128> Data;69 return copyString(twine.toStringRef(Data));70}71 72bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {73 FileEditsTy::iterator FA = getActionForOffset(Offs);74 if (FA != FileEdits.end()) {75 if (FA->first != Offs)76 return false; // position has been removed.77 }78 79 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {80 SourceLocation ExpLoc;81 MacroArgUse ArgUse;82 deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);83 auto I = ExpansionToArgMap.find(ExpLoc);84 if (I != ExpansionToArgMap.end() &&85 llvm::any_of(I->second, [&](const MacroArgUse &U) {86 return ArgUse.Identifier == U.Identifier &&87 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=88 std::tie(U.ImmediateExpansionLoc, U.UseLoc);89 })) {90 // Trying to write in a macro argument input that has already been91 // written by a previous commit for another expansion of the same macro92 // argument name. For example:93 //94 // \code95 // #define MAC(x) ((x)+(x))96 // MAC(a)97 // \endcode98 //99 // A commit modified the macro argument 'a' due to the first '(x)'100 // expansion inside the macro definition, and a subsequent commit tried101 // to modify 'a' again for the second '(x)' expansion. The edits of the102 // second commit will be rejected.103 return false;104 }105 }106 return true;107}108 109bool EditedSource::commitInsert(SourceLocation OrigLoc,110 FileOffset Offs, StringRef text,111 bool beforePreviousInsertions) {112 if (!canInsertInOffset(OrigLoc, Offs))113 return false;114 if (text.empty())115 return true;116 117 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {118 MacroArgUse ArgUse;119 SourceLocation ExpLoc;120 deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);121 if (ArgUse.Identifier)122 CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);123 }124 125 FileEdit &FA = FileEdits[Offs];126 if (FA.Text.empty()) {127 FA.Text = copyString(text);128 return true;129 }130 131 if (beforePreviousInsertions)132 FA.Text = copyString(Twine(text) + FA.Text);133 else134 FA.Text = copyString(Twine(FA.Text) + text);135 136 return true;137}138 139bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,140 FileOffset Offs,141 FileOffset InsertFromRangeOffs, unsigned Len,142 bool beforePreviousInsertions) {143 if (Len == 0)144 return true;145 146 SmallString<128> StrVec;147 FileOffset BeginOffs = InsertFromRangeOffs;148 FileOffset EndOffs = BeginOffs.getWithOffset(Len);149 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);150 if (I != FileEdits.begin())151 --I;152 153 for (; I != FileEdits.end(); ++I) {154 FileEdit &FA = I->second;155 FileOffset B = I->first;156 FileOffset E = B.getWithOffset(FA.RemoveLen);157 158 if (BeginOffs == B)159 break;160 161 if (BeginOffs < E) {162 if (BeginOffs > B) {163 BeginOffs = E;164 ++I;165 }166 break;167 }168 }169 170 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {171 FileEdit &FA = I->second;172 FileOffset B = I->first;173 FileOffset E = B.getWithOffset(FA.RemoveLen);174 175 if (BeginOffs < B) {176 bool Invalid = false;177 StringRef text = getSourceText(BeginOffs, B, Invalid);178 if (Invalid)179 return false;180 StrVec += text;181 }182 StrVec += FA.Text;183 BeginOffs = E;184 }185 186 if (BeginOffs < EndOffs) {187 bool Invalid = false;188 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);189 if (Invalid)190 return false;191 StrVec += text;192 }193 194 return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);195}196 197void EditedSource::commitRemove(SourceLocation OrigLoc,198 FileOffset BeginOffs, unsigned Len) {199 if (Len == 0)200 return;201 202 FileOffset EndOffs = BeginOffs.getWithOffset(Len);203 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);204 if (I != FileEdits.begin())205 --I;206 207 for (; I != FileEdits.end(); ++I) {208 FileEdit &FA = I->second;209 FileOffset B = I->first;210 FileOffset E = B.getWithOffset(FA.RemoveLen);211 212 if (BeginOffs < E)213 break;214 }215 216 FileOffset TopBegin, TopEnd;217 FileEdit *TopFA = nullptr;218 219 if (I == FileEdits.end()) {220 FileEditsTy::iterator221 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));222 NewI->second.RemoveLen = Len;223 return;224 }225 226 FileEdit &FA = I->second;227 FileOffset B = I->first;228 FileOffset E = B.getWithOffset(FA.RemoveLen);229 if (BeginOffs < B) {230 FileEditsTy::iterator231 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));232 TopBegin = BeginOffs;233 TopEnd = EndOffs;234 TopFA = &NewI->second;235 TopFA->RemoveLen = Len;236 } else {237 TopBegin = B;238 TopEnd = E;239 TopFA = &I->second;240 if (TopEnd >= EndOffs)241 return;242 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();243 TopEnd = EndOffs;244 TopFA->RemoveLen += diff;245 if (B == BeginOffs)246 TopFA->Text = StringRef();247 ++I;248 }249 250 while (I != FileEdits.end()) {251 FileEdit &FA = I->second;252 FileOffset B = I->first;253 FileOffset E = B.getWithOffset(FA.RemoveLen);254 255 if (B >= TopEnd)256 break;257 258 if (E <= TopEnd) {259 FileEdits.erase(I++);260 continue;261 }262 263 if (B < TopEnd) {264 unsigned diff = E.getOffset() - TopEnd.getOffset();265 TopEnd = E;266 TopFA->RemoveLen += diff;267 FileEdits.erase(I);268 }269 270 break;271 }272}273 274bool EditedSource::commit(const Commit &commit) {275 if (!commit.isCommitable())276 return false;277 278 struct CommitRAII {279 EditedSource &Editor;280 281 CommitRAII(EditedSource &Editor) : Editor(Editor) {282 Editor.startingCommit();283 }284 285 ~CommitRAII() {286 Editor.finishedCommit();287 }288 } CommitRAII(*this);289 290 for (edit::Commit::edit_iterator291 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {292 const edit::Commit::Edit &edit = *I;293 switch (edit.Kind) {294 case edit::Commit::Act_Insert:295 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);296 break;297 case edit::Commit::Act_InsertFromRange:298 commitInsertFromRange(edit.OrigLoc, edit.Offset,299 edit.InsertFromRangeOffs, edit.Length,300 edit.BeforePrev);301 break;302 case edit::Commit::Act_Remove:303 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);304 break;305 }306 }307 308 return true;309}310 311// Returns true if it is ok to make the two given characters adjacent.312static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {313 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like314 // making two '<' adjacent.315 return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&316 Lexer::isAsciiIdentifierContinueChar(right, LangOpts));317}318 319/// Returns true if it is ok to eliminate the trailing whitespace between320/// the given characters.321static bool canRemoveWhitespace(char left, char beforeWSpace, char right,322 const LangOptions &LangOpts) {323 if (!canBeJoined(left, right, LangOpts))324 return false;325 if (isWhitespace(left) || isWhitespace(right))326 return true;327 if (canBeJoined(beforeWSpace, right, LangOpts))328 return false; // the whitespace was intentional, keep it.329 return true;330}331 332/// Check the range that we are going to remove and:333/// -Remove any trailing whitespace if possible.334/// -Insert a space if removing the range is going to mess up the source tokens.335static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,336 SourceLocation Loc, FileOffset offs,337 unsigned &len, StringRef &text) {338 assert(len && text.empty());339 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);340 if (BeginTokLoc != Loc)341 return; // the range is not at the beginning of a token, keep the range.342 343 bool Invalid = false;344 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);345 if (Invalid)346 return;347 348 unsigned begin = offs.getOffset();349 unsigned end = begin + len;350 351 // Do not try to extend the removal if we're at the end of the buffer already.352 if (end == buffer.size())353 return;354 355 assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");356 357 // FIXME: Remove newline.358 359 if (begin == 0) {360 if (buffer[end] == ' ')361 ++len;362 return;363 }364 365 if (buffer[end] == ' ') {366 assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&367 "buffer not zero-terminated!");368 if (canRemoveWhitespace(/*left=*/buffer[begin-1],369 /*beforeWSpace=*/buffer[end-1],370 /*right=*/buffer.data()[end + 1], // zero-terminated371 LangOpts))372 ++len;373 return;374 }375 376 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))377 text = " ";378}379 380static void applyRewrite(EditsReceiver &receiver,381 StringRef text, FileOffset offs, unsigned len,382 const SourceManager &SM, const LangOptions &LangOpts,383 bool shouldAdjustRemovals) {384 assert(offs.getFID().isValid());385 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());386 Loc = Loc.getLocWithOffset(offs.getOffset());387 assert(Loc.isFileID());388 389 if (text.empty() && shouldAdjustRemovals)390 adjustRemoval(SM, LangOpts, Loc, offs, len, text);391 392 CharSourceRange range = CharSourceRange::getCharRange(Loc,393 Loc.getLocWithOffset(len));394 395 if (text.empty()) {396 assert(len);397 receiver.remove(range);398 return;399 }400 401 if (len)402 receiver.replace(range, text);403 else404 receiver.insert(Loc, text);405}406 407void EditedSource::applyRewrites(EditsReceiver &receiver,408 bool shouldAdjustRemovals) {409 SmallString<128> StrVec;410 FileOffset CurOffs, CurEnd;411 unsigned CurLen;412 413 if (FileEdits.empty())414 return;415 416 FileEditsTy::iterator I = FileEdits.begin();417 CurOffs = I->first;418 StrVec = I->second.Text;419 CurLen = I->second.RemoveLen;420 CurEnd = CurOffs.getWithOffset(CurLen);421 ++I;422 423 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {424 FileOffset offs = I->first;425 FileEdit act = I->second;426 assert(offs >= CurEnd);427 428 if (offs == CurEnd) {429 StrVec += act.Text;430 CurLen += act.RemoveLen;431 CurEnd.getWithOffset(act.RemoveLen);432 continue;433 }434 435 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,436 shouldAdjustRemovals);437 CurOffs = offs;438 StrVec = act.Text;439 CurLen = act.RemoveLen;440 CurEnd = CurOffs.getWithOffset(CurLen);441 }442 443 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,444 shouldAdjustRemovals);445}446 447void EditedSource::clearRewrites() {448 FileEdits.clear();449 StrAlloc.Reset();450}451 452StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,453 bool &Invalid) {454 assert(BeginOffs.getFID() == EndOffs.getFID());455 assert(BeginOffs <= EndOffs);456 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());457 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());458 assert(BLoc.isFileID());459 SourceLocation460 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());461 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),462 SourceMgr, LangOpts, &Invalid);463}464 465EditedSource::FileEditsTy::iterator466EditedSource::getActionForOffset(FileOffset Offs) {467 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);468 if (I == FileEdits.begin())469 return FileEdits.end();470 --I;471 FileEdit &FA = I->second;472 FileOffset B = I->first;473 FileOffset E = B.getWithOffset(FA.RemoveLen);474 if (Offs >= B && Offs < E)475 return I;476 477 return FileEdits.end();478}479