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/StringRef.h" 20 #include "llvm/ADT/Twine.h" 21 #include <cassert> 22 #include <tuple> 23 #include <utility> 24 25 using namespace clang; 26 using namespace edit; 27 28 void EditsReceiver::remove(CharSourceRange range) { 29 replace(range, StringRef()); 30 } 31 32 void 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 53 void EditedSource::startingCommit() {} 54 55 void 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 67 StringRef EditedSource::copyString(const Twine &twine) { 68 SmallString<128> Data; 69 return copyString(twine.toStringRef(Data)); 70 } 71 72 bool 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 been 91 // written by a previous commit for another expansion of the same macro 92 // argument name. For example: 93 // 94 // \code 95 // #define MAC(x) ((x)+(x)) 96 // MAC(a) 97 // \endcode 98 // 99 // A commit modified the macro argument 'a' due to the first '(x)' 100 // expansion inside the macro definition, and a subsequent commit tried 101 // to modify 'a' again for the second '(x)' expansion. The edits of the 102 // second commit will be rejected. 103 return false; 104 } 105 } 106 return true; 107 } 108 109 bool 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 else 134 FA.Text = copyString(Twine(FA.Text) + text); 135 136 return true; 137 } 138 139 bool 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 197 void 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::iterator 221 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::iterator 231 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 274 bool 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_iterator 291 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. 312 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) { 313 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like 314 // 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 between 320 /// the given characters. 321 static 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. 335 static 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-terminated 371 LangOpts)) 372 ++len; 373 return; 374 } 375 376 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts)) 377 text = " "; 378 } 379 380 static 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 else 404 receiver.insert(Loc, text); 405 } 406 407 void 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 447 void EditedSource::clearRewrites() { 448 FileEdits.clear(); 449 StrAlloc.Reset(); 450 } 451 452 StringRef 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 SourceLocation 460 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset()); 461 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc), 462 SourceMgr, LangOpts, &Invalid); 463 } 464 465 EditedSource::FileEditsTy::iterator 466 EditedSource::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