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