1 //===- Commit.cpp - A unit of 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/Commit.h" 11 #include "clang/Basic/LLVM.h" 12 #include "clang/Basic/SourceLocation.h" 13 #include "clang/Basic/SourceManager.h" 14 #include "clang/Edit/EditedSource.h" 15 #include "clang/Edit/FileOffset.h" 16 #include "clang/Lex/Lexer.h" 17 #include "clang/Lex/PPConditionalDirectiveRecord.h" 18 #include "llvm/ADT/StringRef.h" 19 #include <cassert> 20 #include <utility> 21 22 using namespace clang; 23 using namespace edit; 24 25 SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const { 26 SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID()); 27 Loc = Loc.getLocWithOffset(Offset.getOffset()); 28 assert(Loc.isFileID()); 29 return Loc; 30 } 31 32 CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const { 33 SourceLocation Loc = getFileLocation(SM); 34 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length)); 35 } 36 37 CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const { 38 SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID()); 39 Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset()); 40 assert(Loc.isFileID()); 41 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length)); 42 } 43 44 Commit::Commit(EditedSource &Editor) 45 : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()), 46 PPRec(Editor.getPPCondDirectiveRecord()), 47 Editor(&Editor) {} 48 49 bool Commit::insert(SourceLocation loc, StringRef text, 50 bool afterToken, bool beforePreviousInsertions) { 51 if (text.empty()) 52 return true; 53 54 FileOffset Offs; 55 if ((!afterToken && !canInsert(loc, Offs)) || 56 ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { 57 IsCommitable = false; 58 return false; 59 } 60 61 addInsert(loc, Offs, text, beforePreviousInsertions); 62 return true; 63 } 64 65 bool Commit::insertFromRange(SourceLocation loc, 66 CharSourceRange range, 67 bool afterToken, bool beforePreviousInsertions) { 68 FileOffset RangeOffs; 69 unsigned RangeLen; 70 if (!canRemoveRange(range, RangeOffs, RangeLen)) { 71 IsCommitable = false; 72 return false; 73 } 74 75 FileOffset Offs; 76 if ((!afterToken && !canInsert(loc, Offs)) || 77 ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { 78 IsCommitable = false; 79 return false; 80 } 81 82 if (PPRec && 83 PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) { 84 IsCommitable = false; 85 return false; 86 } 87 88 addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions); 89 return true; 90 } 91 92 bool Commit::remove(CharSourceRange range) { 93 FileOffset Offs; 94 unsigned Len; 95 if (!canRemoveRange(range, Offs, Len)) { 96 IsCommitable = false; 97 return false; 98 } 99 100 addRemove(range.getBegin(), Offs, Len); 101 return true; 102 } 103 104 bool Commit::insertWrap(StringRef before, CharSourceRange range, 105 StringRef after) { 106 bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false, 107 /*beforePreviousInsertions=*/true); 108 bool commitableAfter; 109 if (range.isTokenRange()) 110 commitableAfter = insertAfterToken(range.getEnd(), after); 111 else 112 commitableAfter = insert(range.getEnd(), after); 113 114 return commitableBefore && commitableAfter; 115 } 116 117 bool Commit::replace(CharSourceRange range, StringRef text) { 118 if (text.empty()) 119 return remove(range); 120 121 FileOffset Offs; 122 unsigned Len; 123 if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) { 124 IsCommitable = false; 125 return false; 126 } 127 128 addRemove(range.getBegin(), Offs, Len); 129 addInsert(range.getBegin(), Offs, text, false); 130 return true; 131 } 132 133 bool Commit::replaceWithInner(CharSourceRange range, 134 CharSourceRange replacementRange) { 135 FileOffset OuterBegin; 136 unsigned OuterLen; 137 if (!canRemoveRange(range, OuterBegin, OuterLen)) { 138 IsCommitable = false; 139 return false; 140 } 141 142 FileOffset InnerBegin; 143 unsigned InnerLen; 144 if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) { 145 IsCommitable = false; 146 return false; 147 } 148 149 FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen); 150 FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen); 151 if (OuterBegin.getFID() != InnerBegin.getFID() || 152 InnerBegin < OuterBegin || 153 InnerBegin > OuterEnd || 154 InnerEnd > OuterEnd) { 155 IsCommitable = false; 156 return false; 157 } 158 159 addRemove(range.getBegin(), 160 OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset()); 161 addRemove(replacementRange.getEnd(), 162 InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset()); 163 return true; 164 } 165 166 bool Commit::replaceText(SourceLocation loc, StringRef text, 167 StringRef replacementText) { 168 if (text.empty() || replacementText.empty()) 169 return true; 170 171 FileOffset Offs; 172 unsigned Len; 173 if (!canReplaceText(loc, replacementText, Offs, Len)) { 174 IsCommitable = false; 175 return false; 176 } 177 178 addRemove(loc, Offs, Len); 179 addInsert(loc, Offs, text, false); 180 return true; 181 } 182 183 void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text, 184 bool beforePreviousInsertions) { 185 if (text.empty()) 186 return; 187 188 Edit data; 189 data.Kind = Act_Insert; 190 data.OrigLoc = OrigLoc; 191 data.Offset = Offs; 192 data.Text = text.copy(StrAlloc); 193 data.BeforePrev = beforePreviousInsertions; 194 CachedEdits.push_back(data); 195 } 196 197 void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs, 198 FileOffset RangeOffs, unsigned RangeLen, 199 bool beforePreviousInsertions) { 200 if (RangeLen == 0) 201 return; 202 203 Edit data; 204 data.Kind = Act_InsertFromRange; 205 data.OrigLoc = OrigLoc; 206 data.Offset = Offs; 207 data.InsertFromRangeOffs = RangeOffs; 208 data.Length = RangeLen; 209 data.BeforePrev = beforePreviousInsertions; 210 CachedEdits.push_back(data); 211 } 212 213 void Commit::addRemove(SourceLocation OrigLoc, 214 FileOffset Offs, unsigned Len) { 215 if (Len == 0) 216 return; 217 218 Edit data; 219 data.Kind = Act_Remove; 220 data.OrigLoc = OrigLoc; 221 data.Offset = Offs; 222 data.Length = Len; 223 CachedEdits.push_back(data); 224 } 225 226 bool Commit::canInsert(SourceLocation loc, FileOffset &offs) { 227 if (loc.isInvalid()) 228 return false; 229 230 if (loc.isMacroID()) 231 isAtStartOfMacroExpansion(loc, &loc); 232 233 const SourceManager &SM = SourceMgr; 234 loc = SM.getTopMacroCallerLoc(loc); 235 236 if (loc.isMacroID()) 237 if (!isAtStartOfMacroExpansion(loc, &loc)) 238 return false; 239 240 if (SM.isInSystemHeader(loc)) 241 return false; 242 243 std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); 244 if (locInfo.first.isInvalid()) 245 return false; 246 offs = FileOffset(locInfo.first, locInfo.second); 247 return canInsertInOffset(loc, offs); 248 } 249 250 bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs, 251 SourceLocation &AfterLoc) { 252 if (loc.isInvalid()) 253 254 return false; 255 256 SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc); 257 unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts); 258 AfterLoc = loc.getLocWithOffset(tokLen); 259 260 if (loc.isMacroID()) 261 isAtEndOfMacroExpansion(loc, &loc); 262 263 const SourceManager &SM = SourceMgr; 264 loc = SM.getTopMacroCallerLoc(loc); 265 266 if (loc.isMacroID()) 267 if (!isAtEndOfMacroExpansion(loc, &loc)) 268 return false; 269 270 if (SM.isInSystemHeader(loc)) 271 return false; 272 273 loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts); 274 if (loc.isInvalid()) 275 return false; 276 277 std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); 278 if (locInfo.first.isInvalid()) 279 return false; 280 offs = FileOffset(locInfo.first, locInfo.second); 281 return canInsertInOffset(loc, offs); 282 } 283 284 bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { 285 for (const auto &act : CachedEdits) 286 if (act.Kind == Act_Remove) { 287 if (act.Offset.getFID() == Offs.getFID() && 288 Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length)) 289 return false; // position has been removed. 290 } 291 292 if (!Editor) 293 return true; 294 return Editor->canInsertInOffset(OrigLoc, Offs); 295 } 296 297 bool Commit::canRemoveRange(CharSourceRange range, 298 FileOffset &Offs, unsigned &Len) { 299 const SourceManager &SM = SourceMgr; 300 range = Lexer::makeFileCharRange(range, SM, LangOpts); 301 if (range.isInvalid()) 302 return false; 303 304 if (range.getBegin().isMacroID() || range.getEnd().isMacroID()) 305 return false; 306 if (SM.isInSystemHeader(range.getBegin()) || 307 SM.isInSystemHeader(range.getEnd())) 308 return false; 309 310 if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange())) 311 return false; 312 313 std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin()); 314 std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd()); 315 if (beginInfo.first != endInfo.first || 316 beginInfo.second > endInfo.second) 317 return false; 318 319 Offs = FileOffset(beginInfo.first, beginInfo.second); 320 Len = endInfo.second - beginInfo.second; 321 return true; 322 } 323 324 bool Commit::canReplaceText(SourceLocation loc, StringRef text, 325 FileOffset &Offs, unsigned &Len) { 326 assert(!text.empty()); 327 328 if (!canInsert(loc, Offs)) 329 return false; 330 331 // Try to load the file buffer. 332 bool invalidTemp = false; 333 StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp); 334 if (invalidTemp) 335 return false; 336 337 Len = text.size(); 338 return file.substr(Offs.getOffset()).startswith(text); 339 } 340 341 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc, 342 SourceLocation *MacroBegin) const { 343 return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin); 344 } 345 346 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc, 347 SourceLocation *MacroEnd) const { 348 return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd); 349 } 350