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