blob: 5292a58a9ccd4dcbb2165c7d1765704a6150a8f9 [file] [log] [blame]
Ted Kremenekf7639e12012-03-06 20:06:33 +00001//===----- EditedSource.cpp - Collection of source 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/EditedSource.h"
Jordan Rosea7d03842013-02-08 22:30:41 +000011#include "clang/Basic/CharInfo.h"
Chandler Carruth3a022472012-12-04 09:13:33 +000012#include "clang/Basic/SourceManager.h"
Ted Kremenekf7639e12012-03-06 20:06:33 +000013#include "clang/Edit/Commit.h"
14#include "clang/Edit/EditsReceiver.h"
15#include "clang/Lex/Lexer.h"
Ted Kremenekf7639e12012-03-06 20:06:33 +000016#include "llvm/ADT/SmallString.h"
17#include "llvm/ADT/Twine.h"
18
19using namespace clang;
20using namespace edit;
21
22void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
24}
25
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000026void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
27 SourceLocation &ExpansionLoc,
28 IdentifierInfo *&II) {
29 assert(SourceMgr.isMacroArgExpansion(Loc));
30 SourceLocation DefArgLoc = SourceMgr.getImmediateExpansionRange(Loc).first;
31 ExpansionLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
32 SmallString<20> Buf;
33 StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
34 Buf, SourceMgr, LangOpts);
35 II = nullptr;
36 if (!ArgName.empty()) {
37 II = &IdentTable.get(ArgName);
38 }
39}
40
41void EditedSource::startingCommit() {}
42
43void EditedSource::finishedCommit() {
44 for (auto &ExpArg : CurrCommitMacroArgExps) {
45 SourceLocation ExpLoc;
46 IdentifierInfo *II;
47 std::tie(ExpLoc, II) = ExpArg;
48 auto &ArgNames = ExpansionToArgMap[ExpLoc.getRawEncoding()];
49 if (std::find(ArgNames.begin(), ArgNames.end(), II) == ArgNames.end()) {
50 ArgNames.push_back(II);
51 }
52 }
53 CurrCommitMacroArgExps.clear();
54}
55
Ted Kremenekf7639e12012-03-06 20:06:33 +000056StringRef EditedSource::copyString(const Twine &twine) {
Dmitri Gribenkof8579502013-01-12 19:30:44 +000057 SmallString<128> Data;
Ted Kremenekf7639e12012-03-06 20:06:33 +000058 return copyString(twine.toStringRef(Data));
59}
60
61bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
62 FileEditsTy::iterator FA = getActionForOffset(Offs);
63 if (FA != FileEdits.end()) {
64 if (FA->first != Offs)
65 return false; // position has been removed.
66 }
67
68 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000069 IdentifierInfo *II;
70 SourceLocation ExpLoc;
71 deconstructMacroArgLoc(OrigLoc, ExpLoc, II);
72 auto I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
73 if (I != ExpansionToArgMap.end() &&
74 std::find(I->second.begin(), I->second.end(), II) != I->second.end()) {
75 // Trying to write in a macro argument input that has already been
76 // written by a previous commit for another expansion of the same macro
77 // argument name. For example:
78 //
79 // \code
80 // #define MAC(x) ((x)+(x))
81 // MAC(a)
82 // \endcode
83 //
84 // A commit modified the macro argument 'a' due to the first '(x)'
85 // expansion inside the macro definition, and a subsequent commit tried
86 // to modify 'a' again for the second '(x)' expansion. The edits of the
87 // second commit will be rejected.
88 return false;
89 }
Ted Kremenekf7639e12012-03-06 20:06:33 +000090 }
91
92 return true;
93}
94
95bool EditedSource::commitInsert(SourceLocation OrigLoc,
96 FileOffset Offs, StringRef text,
97 bool beforePreviousInsertions) {
98 if (!canInsertInOffset(OrigLoc, Offs))
99 return false;
100 if (text.empty())
101 return true;
102
103 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +0000104 IdentifierInfo *II;
105 SourceLocation ExpLoc;
106 deconstructMacroArgLoc(OrigLoc, ExpLoc, II);
107 if (II)
108 CurrCommitMacroArgExps.emplace_back(ExpLoc, II);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000109 }
110
111 FileEdit &FA = FileEdits[Offs];
112 if (FA.Text.empty()) {
113 FA.Text = copyString(text);
114 return true;
115 }
116
Ted Kremenekf7639e12012-03-06 20:06:33 +0000117 if (beforePreviousInsertions)
Benjamin Kramer543c7432014-03-29 16:54:24 +0000118 FA.Text = copyString(Twine(text) + FA.Text);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000119 else
Benjamin Kramer543c7432014-03-29 16:54:24 +0000120 FA.Text = copyString(Twine(FA.Text) + text);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000121
Ted Kremenekf7639e12012-03-06 20:06:33 +0000122 return true;
123}
124
125bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
126 FileOffset Offs,
127 FileOffset InsertFromRangeOffs, unsigned Len,
128 bool beforePreviousInsertions) {
129 if (Len == 0)
130 return true;
131
Dmitri Gribenkof8579502013-01-12 19:30:44 +0000132 SmallString<128> StrVec;
Ted Kremenekf7639e12012-03-06 20:06:33 +0000133 FileOffset BeginOffs = InsertFromRangeOffs;
134 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
135 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
136 if (I != FileEdits.begin())
137 --I;
138
139 for (; I != FileEdits.end(); ++I) {
140 FileEdit &FA = I->second;
141 FileOffset B = I->first;
142 FileOffset E = B.getWithOffset(FA.RemoveLen);
143
Argyrios Kyrtzidisc1dfed62012-05-14 22:01:53 +0000144 if (BeginOffs == B)
145 break;
146
Ted Kremenekf7639e12012-03-06 20:06:33 +0000147 if (BeginOffs < E) {
Argyrios Kyrtzidisc1dfed62012-05-14 22:01:53 +0000148 if (BeginOffs > B) {
Ted Kremenekf7639e12012-03-06 20:06:33 +0000149 BeginOffs = E;
150 ++I;
151 }
152 break;
153 }
154 }
155
156 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
157 FileEdit &FA = I->second;
158 FileOffset B = I->first;
159 FileOffset E = B.getWithOffset(FA.RemoveLen);
160
161 if (BeginOffs < B) {
162 bool Invalid = false;
163 StringRef text = getSourceText(BeginOffs, B, Invalid);
164 if (Invalid)
165 return false;
166 StrVec += text;
167 }
168 StrVec += FA.Text;
169 BeginOffs = E;
170 }
171
172 if (BeginOffs < EndOffs) {
173 bool Invalid = false;
174 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
175 if (Invalid)
176 return false;
177 StrVec += text;
178 }
179
Yaron Keren92e1b622015-03-18 10:17:07 +0000180 return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000181}
182
183void EditedSource::commitRemove(SourceLocation OrigLoc,
184 FileOffset BeginOffs, unsigned Len) {
185 if (Len == 0)
186 return;
187
188 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
189 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
190 if (I != FileEdits.begin())
191 --I;
192
193 for (; I != FileEdits.end(); ++I) {
194 FileEdit &FA = I->second;
195 FileOffset B = I->first;
196 FileOffset E = B.getWithOffset(FA.RemoveLen);
197
198 if (BeginOffs < E)
199 break;
200 }
201
202 FileOffset TopBegin, TopEnd;
Craig Topper2145bc02014-05-09 08:15:10 +0000203 FileEdit *TopFA = nullptr;
Ted Kremenekf7639e12012-03-06 20:06:33 +0000204
205 if (I == FileEdits.end()) {
206 FileEditsTy::iterator
207 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
208 NewI->second.RemoveLen = Len;
209 return;
210 }
211
212 FileEdit &FA = I->second;
213 FileOffset B = I->first;
214 FileOffset E = B.getWithOffset(FA.RemoveLen);
215 if (BeginOffs < B) {
216 FileEditsTy::iterator
217 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
218 TopBegin = BeginOffs;
219 TopEnd = EndOffs;
220 TopFA = &NewI->second;
221 TopFA->RemoveLen = Len;
222 } else {
223 TopBegin = B;
224 TopEnd = E;
225 TopFA = &I->second;
226 if (TopEnd >= EndOffs)
227 return;
228 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
229 TopEnd = EndOffs;
230 TopFA->RemoveLen += diff;
Argyrios Kyrtzidis501d90b2013-04-06 01:13:17 +0000231 if (B == BeginOffs)
232 TopFA->Text = StringRef();
Ted Kremenekf7639e12012-03-06 20:06:33 +0000233 ++I;
234 }
235
236 while (I != FileEdits.end()) {
237 FileEdit &FA = I->second;
238 FileOffset B = I->first;
239 FileOffset E = B.getWithOffset(FA.RemoveLen);
240
241 if (B >= TopEnd)
242 break;
243
244 if (E <= TopEnd) {
245 FileEdits.erase(I++);
246 continue;
247 }
248
249 if (B < TopEnd) {
250 unsigned diff = E.getOffset() - TopEnd.getOffset();
251 TopEnd = E;
252 TopFA->RemoveLen += diff;
253 FileEdits.erase(I);
254 }
255
256 break;
257 }
258}
259
260bool EditedSource::commit(const Commit &commit) {
261 if (!commit.isCommitable())
262 return false;
263
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +0000264 struct CommitRAII {
265 EditedSource &Editor;
266 CommitRAII(EditedSource &Editor) : Editor(Editor) {
267 Editor.startingCommit();
268 }
269 ~CommitRAII() {
270 Editor.finishedCommit();
271 }
272 } CommitRAII(*this);
273
Ted Kremenekf7639e12012-03-06 20:06:33 +0000274 for (edit::Commit::edit_iterator
275 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
276 const edit::Commit::Edit &edit = *I;
277 switch (edit.Kind) {
278 case edit::Commit::Act_Insert:
279 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
280 break;
281 case edit::Commit::Act_InsertFromRange:
282 commitInsertFromRange(edit.OrigLoc, edit.Offset,
283 edit.InsertFromRangeOffs, edit.Length,
284 edit.BeforePrev);
285 break;
286 case edit::Commit::Act_Remove:
287 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
288 break;
289 }
290 }
291
292 return true;
293}
294
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000295// \brief Returns true if it is ok to make the two given characters adjacent.
296static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
Jordan Rosea7d03842013-02-08 22:30:41 +0000297 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000298 // making two '<' adjacent.
Jordan Rosea7d03842013-02-08 22:30:41 +0000299 return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
300 Lexer::isIdentifierBodyChar(right, LangOpts));
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000301}
302
303/// \brief Returns true if it is ok to eliminate the trailing whitespace between
304/// the given characters.
305static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
306 const LangOptions &LangOpts) {
307 if (!canBeJoined(left, right, LangOpts))
308 return false;
Jordan Rosea7d03842013-02-08 22:30:41 +0000309 if (isWhitespace(left) || isWhitespace(right))
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000310 return true;
311 if (canBeJoined(beforeWSpace, right, LangOpts))
312 return false; // the whitespace was intentional, keep it.
313 return true;
314}
315
316/// \brief Check the range that we are going to remove and:
317/// -Remove any trailing whitespace if possible.
318/// -Insert a space if removing the range is going to mess up the source tokens.
319static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
320 SourceLocation Loc, FileOffset offs,
321 unsigned &len, StringRef &text) {
322 assert(len && text.empty());
323 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
324 if (BeginTokLoc != Loc)
325 return; // the range is not at the beginning of a token, keep the range.
326
327 bool Invalid = false;
328 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
329 if (Invalid)
330 return;
331
332 unsigned begin = offs.getOffset();
333 unsigned end = begin + len;
334
Benjamin Kramer1009df42014-09-15 11:47:10 +0000335 // Do not try to extend the removal if we're at the end of the buffer already.
336 if (end == buffer.size())
337 return;
338
339 assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
340
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000341 // FIXME: Remove newline.
342
343 if (begin == 0) {
344 if (buffer[end] == ' ')
345 ++len;
346 return;
347 }
348
349 if (buffer[end] == ' ') {
Benjamin Kramer851f3102015-03-29 18:07:29 +0000350 assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
351 "buffer not zero-terminated!");
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000352 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
353 /*beforeWSpace=*/buffer[end-1],
Benjamin Kramer851f3102015-03-29 18:07:29 +0000354 /*right=*/buffer.data()[end + 1], // zero-terminated
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000355 LangOpts))
356 ++len;
357 return;
358 }
359
360 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
361 text = " ";
362}
363
Ted Kremenekf7639e12012-03-06 20:06:33 +0000364static void applyRewrite(EditsReceiver &receiver,
365 StringRef text, FileOffset offs, unsigned len,
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000366 const SourceManager &SM, const LangOptions &LangOpts) {
Yaron Keren8b563662015-10-03 10:46:20 +0000367 assert(offs.getFID().isValid());
Ted Kremenekf7639e12012-03-06 20:06:33 +0000368 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
369 Loc = Loc.getLocWithOffset(offs.getOffset());
370 assert(Loc.isFileID());
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000371
372 if (text.empty())
373 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
374
Ted Kremenekf7639e12012-03-06 20:06:33 +0000375 CharSourceRange range = CharSourceRange::getCharRange(Loc,
376 Loc.getLocWithOffset(len));
377
378 if (text.empty()) {
379 assert(len);
380 receiver.remove(range);
381 return;
382 }
383
384 if (len)
385 receiver.replace(range, text);
386 else
387 receiver.insert(Loc, text);
388}
389
390void EditedSource::applyRewrites(EditsReceiver &receiver) {
Dmitri Gribenkof8579502013-01-12 19:30:44 +0000391 SmallString<128> StrVec;
Ted Kremenekf7639e12012-03-06 20:06:33 +0000392 FileOffset CurOffs, CurEnd;
393 unsigned CurLen;
394
395 if (FileEdits.empty())
396 return;
397
398 FileEditsTy::iterator I = FileEdits.begin();
399 CurOffs = I->first;
400 StrVec = I->second.Text;
401 CurLen = I->second.RemoveLen;
402 CurEnd = CurOffs.getWithOffset(CurLen);
403 ++I;
404
405 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
406 FileOffset offs = I->first;
407 FileEdit act = I->second;
408 assert(offs >= CurEnd);
409
410 if (offs == CurEnd) {
411 StrVec += act.Text;
412 CurLen += act.RemoveLen;
413 CurEnd.getWithOffset(act.RemoveLen);
414 continue;
415 }
416
Yaron Keren92e1b622015-03-18 10:17:07 +0000417 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000418 CurOffs = offs;
419 StrVec = act.Text;
420 CurLen = act.RemoveLen;
421 CurEnd = CurOffs.getWithOffset(CurLen);
422 }
423
Yaron Keren92e1b622015-03-18 10:17:07 +0000424 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000425}
426
427void EditedSource::clearRewrites() {
428 FileEdits.clear();
429 StrAlloc.Reset();
430}
431
432StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
433 bool &Invalid) {
434 assert(BeginOffs.getFID() == EndOffs.getFID());
435 assert(BeginOffs <= EndOffs);
436 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
437 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
438 assert(BLoc.isFileID());
439 SourceLocation
440 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
441 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
442 SourceMgr, LangOpts, &Invalid);
443}
444
445EditedSource::FileEditsTy::iterator
446EditedSource::getActionForOffset(FileOffset Offs) {
447 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
448 if (I == FileEdits.begin())
449 return FileEdits.end();
450 --I;
451 FileEdit &FA = I->second;
452 FileOffset B = I->first;
453 FileOffset E = B.getWithOffset(FA.RemoveLen);
454 if (Offs >= B && Offs < E)
455 return I;
456
457 return FileEdits.end();
458}