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
getFileLocation(SourceManager & SM) const23 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
getFileRange(SourceManager & SM) const30 CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
31 SourceLocation Loc = getFileLocation(SM);
32 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
33 }
34
getInsertFromRange(SourceManager & SM) const35 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
Commit(EditedSource & Editor)42 Commit::Commit(EditedSource &Editor)
43 : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
44 PPRec(Editor.getPPCondDirectiveRecord()),
45 Editor(&Editor) {}
46
insert(SourceLocation loc,StringRef text,bool afterToken,bool beforePreviousInsertions)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
insertFromRange(SourceLocation loc,CharSourceRange range,bool afterToken,bool beforePreviousInsertions)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
remove(CharSourceRange range)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
insertWrap(StringRef before,CharSourceRange range,StringRef after)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
replace(CharSourceRange range,StringRef text)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
replaceWithInner(CharSourceRange range,CharSourceRange replacementRange)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
replaceText(SourceLocation loc,StringRef text,StringRef replacementText)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
addInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)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
addInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset RangeOffs,unsigned RangeLen,bool beforePreviousInsertions)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
addRemove(SourceLocation OrigLoc,FileOffset Offs,unsigned Len)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
canInsert(SourceLocation loc,FileOffset & offs)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
canInsertAfterToken(SourceLocation loc,FileOffset & offs,SourceLocation & AfterLoc)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
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)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
canRemoveRange(CharSourceRange range,FileOffset & Offs,unsigned & Len)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
canReplaceText(SourceLocation loc,StringRef text,FileOffset & Offs,unsigned & Len)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
isAtStartOfMacroExpansion(SourceLocation loc,SourceLocation * MacroBegin) const339 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
340 SourceLocation *MacroBegin) const {
341 return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
342 }
343
isAtEndOfMacroExpansion(SourceLocation loc,SourceLocation * MacroEnd) const344 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
345 SourceLocation *MacroEnd) const {
346 return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
347 }
348