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