Coverage Report

Created: 2023-09-21 18:56

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Edit/EditedSource.cpp
Line
Count
Source (jump to first uncovered line)
1
//===- EditedSource.cpp - Collection of source 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/EditedSource.h"
10
#include "clang/Basic/CharInfo.h"
11
#include "clang/Basic/LLVM.h"
12
#include "clang/Basic/SourceLocation.h"
13
#include "clang/Basic/SourceManager.h"
14
#include "clang/Edit/Commit.h"
15
#include "clang/Edit/EditsReceiver.h"
16
#include "clang/Edit/FileOffset.h"
17
#include "clang/Lex/Lexer.h"
18
#include "llvm/ADT/STLExtras.h"
19
#include "llvm/ADT/SmallString.h"
20
#include "llvm/ADT/StringRef.h"
21
#include "llvm/ADT/Twine.h"
22
#include <algorithm>
23
#include <cassert>
24
#include <tuple>
25
#include <utility>
26
27
using namespace clang;
28
using namespace edit;
29
30
1.08k
void EditsReceiver::remove(CharSourceRange range) {
31
1.08k
  replace(range, StringRef());
32
1.08k
}
33
34
void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
35
                                          SourceLocation &ExpansionLoc,
36
155
                                          MacroArgUse &ArgUse) {
37
155
  assert(SourceMgr.isMacroArgExpansion(Loc));
38
155
  SourceLocation DefArgLoc =
39
155
      SourceMgr.getImmediateExpansionRange(Loc).getBegin();
40
155
  SourceLocation ImmediateExpansionLoc =
41
155
      SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();
42
155
  ExpansionLoc = ImmediateExpansionLoc;
43
201
  while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
44
46
    ExpansionLoc =
45
46
        SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
46
155
  SmallString<20> Buf;
47
155
  StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
48
155
                                         Buf, SourceMgr, LangOpts);
49
155
  ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
50
155
  if (!ArgName.empty())
51
155
    ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
52
155
              SourceMgr.getSpellingLoc(DefArgLoc)};
53
155
}
54
55
5.74k
void EditedSource::startingCommit() {}
56
57
5.74k
void EditedSource::finishedCommit() {
58
5.74k
  for (auto &ExpArg : CurrCommitMacroArgExps) {
59
70
    SourceLocation ExpLoc;
60
70
    MacroArgUse ArgUse;
61
70
    std::tie(ExpLoc, ArgUse) = ExpArg;
62
70
    auto &ArgUses = ExpansionToArgMap[ExpLoc];
63
70
    if (!llvm::is_contained(ArgUses, ArgUse))
64
40
      ArgUses.push_back(ArgUse);
65
70
  }
66
5.74k
  CurrCommitMacroArgExps.clear();
67
5.74k
}
68
69
130
StringRef EditedSource::copyString(const Twine &twine) {
70
130
  SmallString<128> Data;
71
130
  return copyString(twine.toStringRef(Data));
72
130
}
73
74
7.58k
bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
75
7.58k
  FileEditsTy::iterator FA = getActionForOffset(Offs);
76
7.58k
  if (FA != FileEdits.end()) {
77
2.88k
    if (FA->first != Offs)
78
0
      return false; // position has been removed.
79
2.88k
  }
80
81
7.58k
  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
82
85
    SourceLocation ExpLoc;
83
85
    MacroArgUse ArgUse;
84
85
    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
85
85
    auto I = ExpansionToArgMap.find(ExpLoc);
86
85
    if (I != ExpansionToArgMap.end() &&
87
85
        
llvm::any_of(I->second, [&](const MacroArgUse &U) 24
{
88
25
          return ArgUse.Identifier == U.Identifier &&
89
25
                 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
90
21
                     std::tie(U.ImmediateExpansionLoc, U.UseLoc);
91
25
        })) {
92
      // Trying to write in a macro argument input that has already been
93
      // written by a previous commit for another expansion of the same macro
94
      // argument name. For example:
95
      //
96
      // \code
97
      //   #define MAC(x) ((x)+(x))
98
      //   MAC(a)
99
      // \endcode
100
      //
101
      // A commit modified the macro argument 'a' due to the first '(x)'
102
      // expansion inside the macro definition, and a subsequent commit tried
103
      // to modify 'a' again for the second '(x)' expansion. The edits of the
104
      // second commit will be rejected.
105
15
      return false;
106
15
    }
107
85
  }
108
7.56k
  return true;
109
7.58k
}
110
111
bool EditedSource::commitInsert(SourceLocation OrigLoc,
112
                                FileOffset Offs, StringRef text,
113
5.78k
                                bool beforePreviousInsertions) {
114
5.78k
  if (!canInsertInOffset(OrigLoc, Offs))
115
15
    return false;
116
5.76k
  if (text.empty())
117
0
    return true;
118
119
5.76k
  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
120
70
    MacroArgUse ArgUse;
121
70
    SourceLocation ExpLoc;
122
70
    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
123
70
    if (ArgUse.Identifier)
124
70
      CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
125
70
  }
126
127
5.76k
  FileEdit &FA = FileEdits[Offs];
128
5.76k
  if (FA.Text.empty()) {
129
5.63k
    FA.Text = copyString(text);
130
5.63k
    return true;
131
5.63k
  }
132
133
130
  if (beforePreviousInsertions)
134
28
    FA.Text = copyString(Twine(text) + FA.Text);
135
102
  else
136
102
    FA.Text = copyString(Twine(FA.Text) + text);
137
138
130
  return true;
139
5.76k
}
140
141
bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
142
                                   FileOffset Offs,
143
                                   FileOffset InsertFromRangeOffs, unsigned Len,
144
129
                                   bool beforePreviousInsertions) {
145
129
  if (Len == 0)
146
0
    return true;
147
148
129
  SmallString<128> StrVec;
149
129
  FileOffset BeginOffs = InsertFromRangeOffs;
150
129
  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
151
129
  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
152
129
  if (I != FileEdits.begin())
153
72
    --I;
154
155
172
  for (; I != FileEdits.end(); 
++I43
) {
156
103
    FileEdit &FA = I->second;
157
103
    FileOffset B = I->first;
158
103
    FileOffset E = B.getWithOffset(FA.RemoveLen);
159
160
103
    if (BeginOffs == B)
161
29
      break;
162
163
74
    if (BeginOffs < E) {
164
31
      if (BeginOffs > B) {
165
0
        BeginOffs = E;
166
0
        ++I;
167
0
      }
168
31
      break;
169
31
    }
170
74
  }
171
172
166
  for (; I != FileEdits.end() && 
EndOffs > I->first73
;
++I37
) {
173
37
    FileEdit &FA = I->second;
174
37
    FileOffset B = I->first;
175
37
    FileOffset E = B.getWithOffset(FA.RemoveLen);
176
177
37
    if (BeginOffs < B) {
178
4
      bool Invalid = false;
179
4
      StringRef text = getSourceText(BeginOffs, B, Invalid);
180
4
      if (Invalid)
181
0
        return false;
182
4
      StrVec += text;
183
4
    }
184
37
    StrVec += FA.Text;
185
37
    BeginOffs = E;
186
37
  }
187
188
129
  if (BeginOffs < EndOffs) {
189
124
    bool Invalid = false;
190
124
    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
191
124
    if (Invalid)
192
0
      return false;
193
124
    StrVec += text;
194
124
  }
195
196
129
  return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
197
129
}
198
199
void EditedSource::commitRemove(SourceLocation OrigLoc,
200
4.23k
                                FileOffset BeginOffs, unsigned Len) {
201
4.23k
  if (Len == 0)
202
0
    return;
203
204
4.23k
  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
205
4.23k
  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
206
4.23k
  if (I != FileEdits.begin())
207
1.96k
    --I;
208
209
6.19k
  for (; I != FileEdits.end(); 
++I1.95k
) {
210
2.35k
    FileEdit &FA = I->second;
211
2.35k
    FileOffset B = I->first;
212
2.35k
    FileOffset E = B.getWithOffset(FA.RemoveLen);
213
214
2.35k
    if (BeginOffs < E)
215
396
      break;
216
2.35k
  }
217
218
4.23k
  FileOffset TopBegin, TopEnd;
219
4.23k
  FileEdit *TopFA = nullptr;
220
221
4.23k
  if (I == FileEdits.end()) {
222
3.84k
    FileEditsTy::iterator
223
3.84k
      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
224
3.84k
    NewI->second.RemoveLen = Len;
225
3.84k
    return;
226
3.84k
  }
227
228
396
  FileEdit &FA = I->second;
229
396
  FileOffset B = I->first;
230
396
  FileOffset E = B.getWithOffset(FA.RemoveLen);
231
396
  if (BeginOffs < B) {
232
383
    FileEditsTy::iterator
233
383
      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
234
383
    TopBegin = BeginOffs;
235
383
    TopEnd = EndOffs;
236
383
    TopFA = &NewI->second;
237
383
    TopFA->RemoveLen = Len;
238
383
  } else {
239
13
    TopBegin = B;
240
13
    TopEnd = E;
241
13
    TopFA = &I->second;
242
13
    if (TopEnd >= EndOffs)
243
10
      return;
244
3
    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
245
3
    TopEnd = EndOffs;
246
3
    TopFA->RemoveLen += diff;
247
3
    if (B == BeginOffs)
248
2
      TopFA->Text = StringRef();
249
3
    ++I;
250
3
  }
251
252
428
  
while (386
I != FileEdits.end()) {
253
421
    FileEdit &FA = I->second;
254
421
    FileOffset B = I->first;
255
421
    FileOffset E = B.getWithOffset(FA.RemoveLen);
256
257
421
    if (B >= TopEnd)
258
379
      break;
259
260
42
    if (E <= TopEnd) {
261
42
      FileEdits.erase(I++);
262
42
      continue;
263
42
    }
264
265
0
    if (B < TopEnd) {
266
0
      unsigned diff = E.getOffset() - TopEnd.getOffset();
267
0
      TopEnd = E;
268
0
      TopFA->RemoveLen += diff;
269
0
      FileEdits.erase(I);
270
0
    }
271
272
0
    break;
273
42
  }
274
386
}
275
276
5.76k
bool EditedSource::commit(const Commit &commit) {
277
5.76k
  if (!commit.isCommitable())
278
15
    return false;
279
280
5.74k
  struct CommitRAII {
281
5.74k
    EditedSource &Editor;
282
283
5.74k
    CommitRAII(EditedSource &Editor) : Editor(Editor) {
284
5.74k
      Editor.startingCommit();
285
5.74k
    }
286
287
5.74k
    ~CommitRAII() {
288
5.74k
      Editor.finishedCommit();
289
5.74k
    }
290
5.74k
  } CommitRAII(*this);
291
292
5.74k
  for (edit::Commit::edit_iterator
293
15.7k
         I = commit.edit_begin(), E = commit.edit_end(); I != E; 
++I10.0k
) {
294
10.0k
    const edit::Commit::Edit &edit = *I;
295
10.0k
    switch (edit.Kind) {
296
5.65k
    case edit::Commit::Act_Insert:
297
5.65k
      commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
298
5.65k
      break;
299
129
    case edit::Commit::Act_InsertFromRange:
300
129
      commitInsertFromRange(edit.OrigLoc, edit.Offset,
301
129
                            edit.InsertFromRangeOffs, edit.Length,
302
129
                            edit.BeforePrev);
303
129
      break;
304
4.23k
    case edit::Commit::Act_Remove:
305
4.23k
      commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
306
4.23k
      break;
307
10.0k
    }
308
10.0k
  }
309
310
5.74k
  return true;
311
5.74k
}
312
313
// Returns true if it is ok to make the two given characters adjacent.
314
1.08k
static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
315
  // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
316
  // making two '<' adjacent.
317
1.08k
  return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&
318
1.08k
           
Lexer::isAsciiIdentifierContinueChar(right, LangOpts)178
);
319
1.08k
}
320
321
/// Returns true if it is ok to eliminate the trailing whitespace between
322
/// the given characters.
323
static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
324
247
                                const LangOptions &LangOpts) {
325
247
  if (!canBeJoined(left, right, LangOpts))
326
11
    return false;
327
236
  if (isWhitespace(left) || 
isWhitespace(right)46
)
328
190
    return true;
329
46
  if (canBeJoined(beforeWSpace, right, LangOpts))
330
31
    return false; // the whitespace was intentional, keep it.
331
15
  return true;
332
46
}
333
334
/// Check the range that we are going to remove and:
335
/// -Remove any trailing whitespace if possible.
336
/// -Insert a space if removing the range is going to mess up the source tokens.
337
static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
338
                          SourceLocation Loc, FileOffset offs,
339
1.09k
                          unsigned &len, StringRef &text) {
340
1.09k
  assert(len && text.empty());
341
1.09k
  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
342
1.09k
  if (BeginTokLoc != Loc)
343
57
    return; // the range is not at the beginning of a token, keep the range.
344
345
1.03k
  bool Invalid = false;
346
1.03k
  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
347
1.03k
  if (Invalid)
348
0
    return;
349
350
1.03k
  unsigned begin = offs.getOffset();
351
1.03k
  unsigned end = begin + len;
352
353
  // Do not try to extend the removal if we're at the end of the buffer already.
354
1.03k
  if (end == buffer.size())
355
0
    return;
356
357
1.03k
  assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
358
359
  // FIXME: Remove newline.
360
361
1.03k
  if (begin == 0) {
362
0
    if (buffer[end] == ' ')
363
0
      ++len;
364
0
    return;
365
0
  }
366
367
1.03k
  if (buffer[end] == ' ') {
368
247
    assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
369
247
           "buffer not zero-terminated!");
370
247
    if (canRemoveWhitespace(/*left=*/buffer[begin-1],
371
247
                            /*beforeWSpace=*/buffer[end-1],
372
247
                            /*right=*/buffer.data()[end + 1], // zero-terminated
373
247
                            LangOpts))
374
205
      ++len;
375
247
    return;
376
247
  }
377
378
787
  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
379
2
    text = " ";
380
787
}
381
382
static void applyRewrite(EditsReceiver &receiver,
383
                         StringRef text, FileOffset offs, unsigned len,
384
                         const SourceManager &SM, const LangOptions &LangOpts,
385
6.69k
                         bool shouldAdjustRemovals) {
386
6.69k
  assert(offs.getFID().isValid());
387
6.69k
  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
388
6.69k
  Loc = Loc.getLocWithOffset(offs.getOffset());
389
6.69k
  assert(Loc.isFileID());
390
391
6.69k
  if (text.empty() && 
shouldAdjustRemovals1.09k
)
392
1.09k
    adjustRemoval(SM, LangOpts, Loc, offs, len, text);
393
394
6.69k
  CharSourceRange range = CharSourceRange::getCharRange(Loc,
395
6.69k
                                                     Loc.getLocWithOffset(len));
396
397
6.69k
  if (text.empty()) {
398
1.09k
    assert(len);
399
1.09k
    receiver.remove(range);
400
1.09k
    return;
401
1.09k
  }
402
403
5.59k
  if (len)
404
3.04k
    receiver.replace(range, text);
405
2.55k
  else
406
2.55k
    receiver.insert(Loc, text);
407
5.59k
}
408
409
void EditedSource::applyRewrites(EditsReceiver &receiver,
410
3.52k
                                 bool shouldAdjustRemovals) {
411
3.52k
  SmallString<128> StrVec;
412
3.52k
  FileOffset CurOffs, CurEnd;
413
3.52k
  unsigned CurLen;
414
415
3.52k
  if (FileEdits.empty())
416
10
    return;
417
418
3.51k
  FileEditsTy::iterator I = FileEdits.begin();
419
3.51k
  CurOffs = I->first;
420
3.51k
  StrVec = I->second.Text;
421
3.51k
  CurLen = I->second.RemoveLen;
422
3.51k
  CurEnd = CurOffs.getWithOffset(CurLen);
423
3.51k
  ++I;
424
425
6.97k
  for (FileEditsTy::iterator E = FileEdits.end(); I != E; 
++I3.45k
) {
426
3.45k
    FileOffset offs = I->first;
427
3.45k
    FileEdit act = I->second;
428
3.45k
    assert(offs >= CurEnd);
429
430
3.45k
    if (offs == CurEnd) {
431
279
      StrVec += act.Text;
432
279
      CurLen += act.RemoveLen;
433
279
      CurEnd.getWithOffset(act.RemoveLen);
434
279
      continue;
435
279
    }
436
437
3.17k
    applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
438
3.17k
                 shouldAdjustRemovals);
439
3.17k
    CurOffs = offs;
440
3.17k
    StrVec = act.Text;
441
3.17k
    CurLen = act.RemoveLen;
442
3.17k
    CurEnd = CurOffs.getWithOffset(CurLen);
443
3.17k
  }
444
445
3.51k
  applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
446
3.51k
               shouldAdjustRemovals);
447
3.51k
}
448
449
0
void EditedSource::clearRewrites() {
450
0
  FileEdits.clear();
451
0
  StrAlloc.Reset();
452
0
}
453
454
StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
455
128
                                      bool &Invalid) {
456
128
  assert(BeginOffs.getFID() == EndOffs.getFID());
457
128
  assert(BeginOffs <= EndOffs);
458
128
  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
459
128
  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
460
128
  assert(BLoc.isFileID());
461
128
  SourceLocation
462
128
    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
463
128
  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
464
128
                              SourceMgr, LangOpts, &Invalid);
465
128
}
466
467
EditedSource::FileEditsTy::iterator
468
7.58k
EditedSource::getActionForOffset(FileOffset Offs) {
469
7.58k
  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
470
7.58k
  if (I == FileEdits.begin())
471
1.56k
    return FileEdits.end();
472
6.01k
  --I;
473
6.01k
  FileEdit &FA = I->second;
474
6.01k
  FileOffset B = I->first;
475
6.01k
  FileOffset E = B.getWithOffset(FA.RemoveLen);
476
6.01k
  if (Offs >= B && Offs < E)
477
2.88k
    return I;
478
479
3.13k
  return FileEdits.end();
480
6.01k
}