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