blob: 444d0393cccd4d857590751b26e3e8fff88b415d [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,
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +000028 MacroArgUse &ArgUse) {
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000029 assert(SourceMgr.isMacroArgExpansion(Loc));
30 SourceLocation DefArgLoc = SourceMgr.getImmediateExpansionRange(Loc).first;
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +000031 SourceLocation ImmediateExpansionLoc =
32 SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
33 ExpansionLoc = ImmediateExpansionLoc;
34 while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
35 ExpansionLoc = SourceMgr.getImmediateExpansionRange(ExpansionLoc).first;
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000036 SmallString<20> Buf;
37 StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
38 Buf, SourceMgr, LangOpts);
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +000039 ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +000040 if (!ArgName.empty())
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +000041 ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
42 SourceMgr.getSpellingLoc(DefArgLoc)};
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000043}
44
45void EditedSource::startingCommit() {}
46
47void EditedSource::finishedCommit() {
48 for (auto &ExpArg : CurrCommitMacroArgExps) {
49 SourceLocation ExpLoc;
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +000050 MacroArgUse ArgUse;
51 std::tie(ExpLoc, ArgUse) = ExpArg;
52 auto &ArgUses = ExpansionToArgMap[ExpLoc.getRawEncoding()];
53 if (std::find(ArgUses.begin(), ArgUses.end(), ArgUse) == ArgUses.end())
54 ArgUses.push_back(ArgUse);
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000055 }
56 CurrCommitMacroArgExps.clear();
57}
58
Ted Kremenekf7639e12012-03-06 20:06:33 +000059StringRef EditedSource::copyString(const Twine &twine) {
Dmitri Gribenkof8579502013-01-12 19:30:44 +000060 SmallString<128> Data;
Ted Kremenekf7639e12012-03-06 20:06:33 +000061 return copyString(twine.toStringRef(Data));
62}
63
64bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
65 FileEditsTy::iterator FA = getActionForOffset(Offs);
66 if (FA != FileEdits.end()) {
67 if (FA->first != Offs)
68 return false; // position has been removed.
69 }
70
71 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000072 SourceLocation ExpLoc;
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +000073 MacroArgUse ArgUse;
74 deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000075 auto I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
76 if (I != ExpansionToArgMap.end() &&
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +000077 find_if(I->second, [&](const MacroArgUse &U) {
78 return ArgUse.Identifier == U.Identifier &&
79 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
80 std::tie(U.ImmediateExpansionLoc, U.UseLoc);
81 }) != I->second.end()) {
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +000082 // Trying to write in a macro argument input that has already been
83 // written by a previous commit for another expansion of the same macro
84 // argument name. For example:
85 //
86 // \code
87 // #define MAC(x) ((x)+(x))
88 // MAC(a)
89 // \endcode
90 //
91 // A commit modified the macro argument 'a' due to the first '(x)'
92 // expansion inside the macro definition, and a subsequent commit tried
93 // to modify 'a' again for the second '(x)' expansion. The edits of the
94 // second commit will be rejected.
95 return false;
96 }
Ted Kremenekf7639e12012-03-06 20:06:33 +000097 }
Ted Kremenekf7639e12012-03-06 20:06:33 +000098 return true;
99}
100
101bool EditedSource::commitInsert(SourceLocation OrigLoc,
102 FileOffset Offs, StringRef text,
103 bool beforePreviousInsertions) {
104 if (!canInsertInOffset(OrigLoc, Offs))
105 return false;
106 if (text.empty())
107 return true;
108
109 if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +0000110 MacroArgUse ArgUse;
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +0000111 SourceLocation ExpLoc;
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +0000112 deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +0000113 if (ArgUse.Identifier)
Alexander Shaposhnikov108ca942017-06-08 21:44:45 +0000114 CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000115 }
Alexander Shaposhnikov3dbef852017-06-20 20:46:58 +0000116
Ted Kremenekf7639e12012-03-06 20:06:33 +0000117 FileEdit &FA = FileEdits[Offs];
118 if (FA.Text.empty()) {
119 FA.Text = copyString(text);
120 return true;
121 }
122
Ted Kremenekf7639e12012-03-06 20:06:33 +0000123 if (beforePreviousInsertions)
Benjamin Kramer543c7432014-03-29 16:54:24 +0000124 FA.Text = copyString(Twine(text) + FA.Text);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000125 else
Benjamin Kramer543c7432014-03-29 16:54:24 +0000126 FA.Text = copyString(Twine(FA.Text) + text);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000127
Ted Kremenekf7639e12012-03-06 20:06:33 +0000128 return true;
129}
130
131bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
132 FileOffset Offs,
133 FileOffset InsertFromRangeOffs, unsigned Len,
134 bool beforePreviousInsertions) {
135 if (Len == 0)
136 return true;
137
Dmitri Gribenkof8579502013-01-12 19:30:44 +0000138 SmallString<128> StrVec;
Ted Kremenekf7639e12012-03-06 20:06:33 +0000139 FileOffset BeginOffs = InsertFromRangeOffs;
140 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
141 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
142 if (I != FileEdits.begin())
143 --I;
144
145 for (; I != FileEdits.end(); ++I) {
146 FileEdit &FA = I->second;
147 FileOffset B = I->first;
148 FileOffset E = B.getWithOffset(FA.RemoveLen);
149
Argyrios Kyrtzidisc1dfed62012-05-14 22:01:53 +0000150 if (BeginOffs == B)
151 break;
152
Ted Kremenekf7639e12012-03-06 20:06:33 +0000153 if (BeginOffs < E) {
Argyrios Kyrtzidisc1dfed62012-05-14 22:01:53 +0000154 if (BeginOffs > B) {
Ted Kremenekf7639e12012-03-06 20:06:33 +0000155 BeginOffs = E;
156 ++I;
157 }
158 break;
159 }
160 }
161
162 for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
163 FileEdit &FA = I->second;
164 FileOffset B = I->first;
165 FileOffset E = B.getWithOffset(FA.RemoveLen);
166
167 if (BeginOffs < B) {
168 bool Invalid = false;
169 StringRef text = getSourceText(BeginOffs, B, Invalid);
170 if (Invalid)
171 return false;
172 StrVec += text;
173 }
174 StrVec += FA.Text;
175 BeginOffs = E;
176 }
177
178 if (BeginOffs < EndOffs) {
179 bool Invalid = false;
180 StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
181 if (Invalid)
182 return false;
183 StrVec += text;
184 }
185
Yaron Keren92e1b622015-03-18 10:17:07 +0000186 return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000187}
188
189void EditedSource::commitRemove(SourceLocation OrigLoc,
190 FileOffset BeginOffs, unsigned Len) {
191 if (Len == 0)
192 return;
193
194 FileOffset EndOffs = BeginOffs.getWithOffset(Len);
195 FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
196 if (I != FileEdits.begin())
197 --I;
198
199 for (; I != FileEdits.end(); ++I) {
200 FileEdit &FA = I->second;
201 FileOffset B = I->first;
202 FileOffset E = B.getWithOffset(FA.RemoveLen);
203
204 if (BeginOffs < E)
205 break;
206 }
207
208 FileOffset TopBegin, TopEnd;
Craig Topper2145bc02014-05-09 08:15:10 +0000209 FileEdit *TopFA = nullptr;
Ted Kremenekf7639e12012-03-06 20:06:33 +0000210
211 if (I == FileEdits.end()) {
212 FileEditsTy::iterator
213 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
214 NewI->second.RemoveLen = Len;
215 return;
216 }
217
218 FileEdit &FA = I->second;
219 FileOffset B = I->first;
220 FileOffset E = B.getWithOffset(FA.RemoveLen);
221 if (BeginOffs < B) {
222 FileEditsTy::iterator
223 NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
224 TopBegin = BeginOffs;
225 TopEnd = EndOffs;
226 TopFA = &NewI->second;
227 TopFA->RemoveLen = Len;
228 } else {
229 TopBegin = B;
230 TopEnd = E;
231 TopFA = &I->second;
232 if (TopEnd >= EndOffs)
233 return;
234 unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
235 TopEnd = EndOffs;
236 TopFA->RemoveLen += diff;
Argyrios Kyrtzidis501d90b2013-04-06 01:13:17 +0000237 if (B == BeginOffs)
238 TopFA->Text = StringRef();
Ted Kremenekf7639e12012-03-06 20:06:33 +0000239 ++I;
240 }
241
242 while (I != FileEdits.end()) {
243 FileEdit &FA = I->second;
244 FileOffset B = I->first;
245 FileOffset E = B.getWithOffset(FA.RemoveLen);
246
247 if (B >= TopEnd)
248 break;
249
250 if (E <= TopEnd) {
251 FileEdits.erase(I++);
252 continue;
253 }
254
255 if (B < TopEnd) {
256 unsigned diff = E.getOffset() - TopEnd.getOffset();
257 TopEnd = E;
258 TopFA->RemoveLen += diff;
259 FileEdits.erase(I);
260 }
261
262 break;
263 }
264}
265
266bool EditedSource::commit(const Commit &commit) {
267 if (!commit.isCommitable())
268 return false;
269
Argyrios Kyrtzidis822e2882015-09-11 20:09:11 +0000270 struct CommitRAII {
271 EditedSource &Editor;
272 CommitRAII(EditedSource &Editor) : Editor(Editor) {
273 Editor.startingCommit();
274 }
275 ~CommitRAII() {
276 Editor.finishedCommit();
277 }
278 } CommitRAII(*this);
279
Ted Kremenekf7639e12012-03-06 20:06:33 +0000280 for (edit::Commit::edit_iterator
281 I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
282 const edit::Commit::Edit &edit = *I;
283 switch (edit.Kind) {
284 case edit::Commit::Act_Insert:
285 commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
286 break;
287 case edit::Commit::Act_InsertFromRange:
288 commitInsertFromRange(edit.OrigLoc, edit.Offset,
289 edit.InsertFromRangeOffs, edit.Length,
290 edit.BeforePrev);
291 break;
292 case edit::Commit::Act_Remove:
293 commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
294 break;
295 }
296 }
297
298 return true;
299}
300
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000301// \brief Returns true if it is ok to make the two given characters adjacent.
302static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
Jordan Rosea7d03842013-02-08 22:30:41 +0000303 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000304 // making two '<' adjacent.
Jordan Rosea7d03842013-02-08 22:30:41 +0000305 return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
306 Lexer::isIdentifierBodyChar(right, LangOpts));
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000307}
308
309/// \brief Returns true if it is ok to eliminate the trailing whitespace between
310/// the given characters.
311static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
312 const LangOptions &LangOpts) {
313 if (!canBeJoined(left, right, LangOpts))
314 return false;
Jordan Rosea7d03842013-02-08 22:30:41 +0000315 if (isWhitespace(left) || isWhitespace(right))
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000316 return true;
317 if (canBeJoined(beforeWSpace, right, LangOpts))
318 return false; // the whitespace was intentional, keep it.
319 return true;
320}
321
322/// \brief Check the range that we are going to remove and:
323/// -Remove any trailing whitespace if possible.
324/// -Insert a space if removing the range is going to mess up the source tokens.
325static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
326 SourceLocation Loc, FileOffset offs,
327 unsigned &len, StringRef &text) {
328 assert(len && text.empty());
329 SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
330 if (BeginTokLoc != Loc)
331 return; // the range is not at the beginning of a token, keep the range.
332
333 bool Invalid = false;
334 StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
335 if (Invalid)
336 return;
337
338 unsigned begin = offs.getOffset();
339 unsigned end = begin + len;
340
Benjamin Kramer1009df42014-09-15 11:47:10 +0000341 // Do not try to extend the removal if we're at the end of the buffer already.
342 if (end == buffer.size())
343 return;
344
345 assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
346
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000347 // FIXME: Remove newline.
348
349 if (begin == 0) {
350 if (buffer[end] == ' ')
351 ++len;
352 return;
353 }
354
355 if (buffer[end] == ' ') {
Benjamin Kramer851f3102015-03-29 18:07:29 +0000356 assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
357 "buffer not zero-terminated!");
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000358 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
359 /*beforeWSpace=*/buffer[end-1],
Benjamin Kramer851f3102015-03-29 18:07:29 +0000360 /*right=*/buffer.data()[end + 1], // zero-terminated
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000361 LangOpts))
362 ++len;
363 return;
364 }
365
366 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
367 text = " ";
368}
369
Ted Kremenekf7639e12012-03-06 20:06:33 +0000370static void applyRewrite(EditsReceiver &receiver,
371 StringRef text, FileOffset offs, unsigned len,
Argyrios Kyrtzidis5312b662017-04-28 00:25:06 +0000372 const SourceManager &SM, const LangOptions &LangOpts,
373 bool shouldAdjustRemovals) {
Yaron Keren8b563662015-10-03 10:46:20 +0000374 assert(offs.getFID().isValid());
Ted Kremenekf7639e12012-03-06 20:06:33 +0000375 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
376 Loc = Loc.getLocWithOffset(offs.getOffset());
377 assert(Loc.isFileID());
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000378
Argyrios Kyrtzidis5312b662017-04-28 00:25:06 +0000379 if (text.empty() && shouldAdjustRemovals)
Argyrios Kyrtzidiscbfd4d22012-12-20 21:05:53 +0000380 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
381
Ted Kremenekf7639e12012-03-06 20:06:33 +0000382 CharSourceRange range = CharSourceRange::getCharRange(Loc,
383 Loc.getLocWithOffset(len));
384
385 if (text.empty()) {
386 assert(len);
387 receiver.remove(range);
388 return;
389 }
390
391 if (len)
392 receiver.replace(range, text);
393 else
394 receiver.insert(Loc, text);
395}
396
Argyrios Kyrtzidis5312b662017-04-28 00:25:06 +0000397void EditedSource::applyRewrites(EditsReceiver &receiver,
398 bool shouldAdjustRemovals) {
Dmitri Gribenkof8579502013-01-12 19:30:44 +0000399 SmallString<128> StrVec;
Ted Kremenekf7639e12012-03-06 20:06:33 +0000400 FileOffset CurOffs, CurEnd;
401 unsigned CurLen;
402
403 if (FileEdits.empty())
404 return;
405
406 FileEditsTy::iterator I = FileEdits.begin();
407 CurOffs = I->first;
408 StrVec = I->second.Text;
409 CurLen = I->second.RemoveLen;
410 CurEnd = CurOffs.getWithOffset(CurLen);
411 ++I;
412
413 for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
414 FileOffset offs = I->first;
415 FileEdit act = I->second;
416 assert(offs >= CurEnd);
417
418 if (offs == CurEnd) {
419 StrVec += act.Text;
420 CurLen += act.RemoveLen;
421 CurEnd.getWithOffset(act.RemoveLen);
422 continue;
423 }
424
Argyrios Kyrtzidis5312b662017-04-28 00:25:06 +0000425 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
426 shouldAdjustRemovals);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000427 CurOffs = offs;
428 StrVec = act.Text;
429 CurLen = act.RemoveLen;
430 CurEnd = CurOffs.getWithOffset(CurLen);
431 }
432
Argyrios Kyrtzidis5312b662017-04-28 00:25:06 +0000433 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
434 shouldAdjustRemovals);
Ted Kremenekf7639e12012-03-06 20:06:33 +0000435}
436
437void EditedSource::clearRewrites() {
438 FileEdits.clear();
439 StrAlloc.Reset();
440}
441
442StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
443 bool &Invalid) {
444 assert(BeginOffs.getFID() == EndOffs.getFID());
445 assert(BeginOffs <= EndOffs);
446 SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
447 BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
448 assert(BLoc.isFileID());
449 SourceLocation
450 ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
451 return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
452 SourceMgr, LangOpts, &Invalid);
453}
454
455EditedSource::FileEditsTy::iterator
456EditedSource::getActionForOffset(FileOffset Offs) {
457 FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
458 if (I == FileEdits.begin())
459 return FileEdits.end();
460 --I;
461 FileEdit &FA = I->second;
462 FileOffset B = I->first;
463 FileOffset E = B.getWithOffset(FA.RemoveLen);
464 if (Offs >= B && Offs < E)
465 return I;
466
467 return FileEdits.end();
468}