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