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