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