1 //===----- 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"
11 #include "clang/Basic/CharInfo.h"
12 #include "clang/Basic/SourceManager.h"
13 #include "clang/Edit/Commit.h"
14 #include "clang/Edit/EditsReceiver.h"
15 #include "clang/Lex/Lexer.h"
16 #include "llvm/ADT/SmallString.h"
17 #include "llvm/ADT/Twine.h"
18
19 using namespace clang;
20 using namespace edit;
21
remove(CharSourceRange range)22 void EditsReceiver::remove(CharSourceRange range) {
23 replace(range, StringRef());
24 }
25
deconstructMacroArgLoc(SourceLocation Loc,SourceLocation & ExpansionLoc,IdentifierInfo * & II)26 void 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
startingCommit()41 void EditedSource::startingCommit() {}
42
finishedCommit()43 void 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
copyString(const Twine & twine)56 StringRef EditedSource::copyString(const Twine &twine) {
57 SmallString<128> Data;
58 return copyString(twine.toStringRef(Data));
59 }
60
canInsertInOffset(SourceLocation OrigLoc,FileOffset Offs)61 bool 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)) {
69 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 }
90 }
91
92 return true;
93 }
94
commitInsert(SourceLocation OrigLoc,FileOffset Offs,StringRef text,bool beforePreviousInsertions)95 bool 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)) {
104 IdentifierInfo *II;
105 SourceLocation ExpLoc;
106 deconstructMacroArgLoc(OrigLoc, ExpLoc, II);
107 if (II)
108 CurrCommitMacroArgExps.emplace_back(ExpLoc, II);
109 }
110
111 FileEdit &FA = FileEdits[Offs];
112 if (FA.Text.empty()) {
113 FA.Text = copyString(text);
114 return true;
115 }
116
117 if (beforePreviousInsertions)
118 FA.Text = copyString(Twine(text) + FA.Text);
119 else
120 FA.Text = copyString(Twine(FA.Text) + text);
121
122 return true;
123 }
124
commitInsertFromRange(SourceLocation OrigLoc,FileOffset Offs,FileOffset InsertFromRangeOffs,unsigned Len,bool beforePreviousInsertions)125 bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
126 FileOffset Offs,
127 FileOffset InsertFromRangeOffs, unsigned Len,
128 bool beforePreviousInsertions) {
129 if (Len == 0)
130 return true;
131
132 SmallString<128> StrVec;
133 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
144 if (BeginOffs == B)
145 break;
146
147 if (BeginOffs < E) {
148 if (BeginOffs > B) {
149 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
180 return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
181 }
182
commitRemove(SourceLocation OrigLoc,FileOffset BeginOffs,unsigned Len)183 void 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;
203 FileEdit *TopFA = nullptr;
204
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;
231 if (B == BeginOffs)
232 TopFA->Text = StringRef();
233 ++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
commit(const Commit & commit)260 bool EditedSource::commit(const Commit &commit) {
261 if (!commit.isCommitable())
262 return false;
263
264 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
274 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
295 // \brief Returns true if it is ok to make the two given characters adjacent.
canBeJoined(char left,char right,const LangOptions & LangOpts)296 static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
297 // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
298 // making two '<' adjacent.
299 return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
300 Lexer::isIdentifierBodyChar(right, LangOpts));
301 }
302
303 /// \brief Returns true if it is ok to eliminate the trailing whitespace between
304 /// the given characters.
canRemoveWhitespace(char left,char beforeWSpace,char right,const LangOptions & LangOpts)305 static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
306 const LangOptions &LangOpts) {
307 if (!canBeJoined(left, right, LangOpts))
308 return false;
309 if (isWhitespace(left) || isWhitespace(right))
310 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.
adjustRemoval(const SourceManager & SM,const LangOptions & LangOpts,SourceLocation Loc,FileOffset offs,unsigned & len,StringRef & text)319 static 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
335 // 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
341 // FIXME: Remove newline.
342
343 if (begin == 0) {
344 if (buffer[end] == ' ')
345 ++len;
346 return;
347 }
348
349 if (buffer[end] == ' ') {
350 assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
351 "buffer not zero-terminated!");
352 if (canRemoveWhitespace(/*left=*/buffer[begin-1],
353 /*beforeWSpace=*/buffer[end-1],
354 /*right=*/buffer.data()[end + 1], // zero-terminated
355 LangOpts))
356 ++len;
357 return;
358 }
359
360 if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
361 text = " ";
362 }
363
applyRewrite(EditsReceiver & receiver,StringRef text,FileOffset offs,unsigned len,const SourceManager & SM,const LangOptions & LangOpts)364 static void applyRewrite(EditsReceiver &receiver,
365 StringRef text, FileOffset offs, unsigned len,
366 const SourceManager &SM, const LangOptions &LangOpts) {
367 assert(offs.getFID().isValid());
368 SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
369 Loc = Loc.getLocWithOffset(offs.getOffset());
370 assert(Loc.isFileID());
371
372 if (text.empty())
373 adjustRemoval(SM, LangOpts, Loc, offs, len, text);
374
375 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
applyRewrites(EditsReceiver & receiver)390 void EditedSource::applyRewrites(EditsReceiver &receiver) {
391 SmallString<128> StrVec;
392 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
417 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
418 CurOffs = offs;
419 StrVec = act.Text;
420 CurLen = act.RemoveLen;
421 CurEnd = CurOffs.getWithOffset(CurLen);
422 }
423
424 applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts);
425 }
426
clearRewrites()427 void EditedSource::clearRewrites() {
428 FileEdits.clear();
429 StrAlloc.Reset();
430 }
431
getSourceText(FileOffset BeginOffs,FileOffset EndOffs,bool & Invalid)432 StringRef 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
445 EditedSource::FileEditsTy::iterator
getActionForOffset(FileOffset Offs)446 EditedSource::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 }
459