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
getFileLocation(SourceManager & SM) const25 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
getFileRange(SourceManager & SM) const32 CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const {
33 SourceLocation Loc = getFileLocation(SM);
34 return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length));
35 }
36
getInsertFromRange(SourceManager & SM) const37 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
Commit(EditedSource & Editor)44 Commit::Commit(EditedSource &Editor)
45 : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()),
46 PPRec(Editor.getPPCondDirectiveRecord()),
47 Editor(&Editor) {}
48
insert(SourceLocation loc,StringRef text,bool afterToken,bool beforePreviousInsertions)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
insertFromRange(SourceLocation loc,CharSourceRange range,bool afterToken,bool beforePreviousInsertions)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
remove(CharSourceRange range)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
insertWrap(StringRef before,CharSourceRange range,StringRef after)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
replace(CharSourceRange range,StringRef text)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
replaceWithInner(CharSourceRange range,CharSourceRange replacementRange)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
replaceText(SourceLocation loc,StringRef text,StringRef replacementText)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
addInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)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
addInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset RangeOffs,unsigned RangeLen,bool beforePreviousInsertions)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
addRemove(SourceLocation OrigLoc,FileOffset Offs,unsigned Len)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
canInsert(SourceLocation loc,FileOffset & offs)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
canInsertAfterToken(SourceLocation loc,FileOffset & offs,SourceLocation & AfterLoc)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
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)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
canRemoveRange(CharSourceRange range,FileOffset & Offs,unsigned & Len)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
canReplaceText(SourceLocation loc,StringRef text,FileOffset & Offs,unsigned & Len)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
isAtStartOfMacroExpansion(SourceLocation loc,SourceLocation * MacroBegin) const341 bool Commit::isAtStartOfMacroExpansion(SourceLocation loc,
342 SourceLocation *MacroBegin) const {
343 return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin);
344 }
345
isAtEndOfMacroExpansion(SourceLocation loc,SourceLocation * MacroEnd) const346 bool Commit::isAtEndOfMacroExpansion(SourceLocation loc,
347 SourceLocation *MacroEnd) const {
348 return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd);
349 }
350