Coverage Report

Created: 2024-01-17 10:31

/src/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
0
void EditsReceiver::remove(CharSourceRange range) {
31
0
  replace(range, StringRef());
32
0
}
33
34
void EditedSource::deconstructMacroArgLoc(SourceLocation Loc,
35
                                          SourceLocation &ExpansionLoc,
36
0
                                          MacroArgUse &ArgUse) {
37
0
  assert(SourceMgr.isMacroArgExpansion(Loc));
38
0
  SourceLocation DefArgLoc =
39
0
      SourceMgr.getImmediateExpansionRange(Loc).getBegin();
40
0
  SourceLocation ImmediateExpansionLoc =
41
0
      SourceMgr.getImmediateExpansionRange(DefArgLoc).getBegin();
42
0
  ExpansionLoc = ImmediateExpansionLoc;
43
0
  while (SourceMgr.isMacroBodyExpansion(ExpansionLoc))
44
0
    ExpansionLoc =
45
0
        SourceMgr.getImmediateExpansionRange(ExpansionLoc).getBegin();
46
0
  SmallString<20> Buf;
47
0
  StringRef ArgName = Lexer::getSpelling(SourceMgr.getSpellingLoc(DefArgLoc),
48
0
                                         Buf, SourceMgr, LangOpts);
49
0
  ArgUse = MacroArgUse{nullptr, SourceLocation(), SourceLocation()};
50
0
  if (!ArgName.empty())
51
0
    ArgUse = {&IdentTable.get(ArgName), ImmediateExpansionLoc,
52
0
              SourceMgr.getSpellingLoc(DefArgLoc)};
53
0
}
54
55
0
void EditedSource::startingCommit() {}
56
57
0
void EditedSource::finishedCommit() {
58
0
  for (auto &ExpArg : CurrCommitMacroArgExps) {
59
0
    SourceLocation ExpLoc;
60
0
    MacroArgUse ArgUse;
61
0
    std::tie(ExpLoc, ArgUse) = ExpArg;
62
0
    auto &ArgUses = ExpansionToArgMap[ExpLoc];
63
0
    if (!llvm::is_contained(ArgUses, ArgUse))
64
0
      ArgUses.push_back(ArgUse);
65
0
  }
66
0
  CurrCommitMacroArgExps.clear();
67
0
}
68
69
0
StringRef EditedSource::copyString(const Twine &twine) {
70
0
  SmallString<128> Data;
71
0
  return copyString(twine.toStringRef(Data));
72
0
}
73
74
0
bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
75
0
  FileEditsTy::iterator FA = getActionForOffset(Offs);
76
0
  if (FA != FileEdits.end()) {
77
0
    if (FA->first != Offs)
78
0
      return false; // position has been removed.
79
0
  }
80
81
0
  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
82
0
    SourceLocation ExpLoc;
83
0
    MacroArgUse ArgUse;
84
0
    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
85
0
    auto I = ExpansionToArgMap.find(ExpLoc);
86
0
    if (I != ExpansionToArgMap.end() &&
87
0
        llvm::any_of(I->second, [&](const MacroArgUse &U) {
88
0
          return ArgUse.Identifier == U.Identifier &&
89
0
                 std::tie(ArgUse.ImmediateExpansionLoc, ArgUse.UseLoc) !=
90
0
                     std::tie(U.ImmediateExpansionLoc, U.UseLoc);
91
0
        })) {
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
0
      return false;
106
0
    }
107
0
  }
108
0
  return true;
109
0
}
110
111
bool EditedSource::commitInsert(SourceLocation OrigLoc,
112
                                FileOffset Offs, StringRef text,
113
0
                                bool beforePreviousInsertions) {
114
0
  if (!canInsertInOffset(OrigLoc, Offs))
115
0
    return false;
116
0
  if (text.empty())
117
0
    return true;
118
119
0
  if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
120
0
    MacroArgUse ArgUse;
121
0
    SourceLocation ExpLoc;
122
0
    deconstructMacroArgLoc(OrigLoc, ExpLoc, ArgUse);
123
0
    if (ArgUse.Identifier)
124
0
      CurrCommitMacroArgExps.emplace_back(ExpLoc, ArgUse);
125
0
  }
126
127
0
  FileEdit &FA = FileEdits[Offs];
128
0
  if (FA.Text.empty()) {
129
0
    FA.Text = copyString(text);
130
0
    return true;
131
0
  }
132
133
0
  if (beforePreviousInsertions)
134
0
    FA.Text = copyString(Twine(text) + FA.Text);
135
0
  else
136
0
    FA.Text = copyString(Twine(FA.Text) + text);
137
138
0
  return true;
139
0
}
140
141
bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
142
                                   FileOffset Offs,
143
                                   FileOffset InsertFromRangeOffs, unsigned Len,
144
0
                                   bool beforePreviousInsertions) {
145
0
  if (Len == 0)
146
0
    return true;
147
148
0
  SmallString<128> StrVec;
149
0
  FileOffset BeginOffs = InsertFromRangeOffs;
150
0
  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
151
0
  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
152
0
  if (I != FileEdits.begin())
153
0
    --I;
154
155
0
  for (; I != FileEdits.end(); ++I) {
156
0
    FileEdit &FA = I->second;
157
0
    FileOffset B = I->first;
158
0
    FileOffset E = B.getWithOffset(FA.RemoveLen);
159
160
0
    if (BeginOffs == B)
161
0
      break;
162
163
0
    if (BeginOffs < E) {
164
0
      if (BeginOffs > B) {
165
0
        BeginOffs = E;
166
0
        ++I;
167
0
      }
168
0
      break;
169
0
    }
170
0
  }
171
172
0
  for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
173
0
    FileEdit &FA = I->second;
174
0
    FileOffset B = I->first;
175
0
    FileOffset E = B.getWithOffset(FA.RemoveLen);
176
177
0
    if (BeginOffs < B) {
178
0
      bool Invalid = false;
179
0
      StringRef text = getSourceText(BeginOffs, B, Invalid);
180
0
      if (Invalid)
181
0
        return false;
182
0
      StrVec += text;
183
0
    }
184
0
    StrVec += FA.Text;
185
0
    BeginOffs = E;
186
0
  }
187
188
0
  if (BeginOffs < EndOffs) {
189
0
    bool Invalid = false;
190
0
    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
191
0
    if (Invalid)
192
0
      return false;
193
0
    StrVec += text;
194
0
  }
195
196
0
  return commitInsert(OrigLoc, Offs, StrVec, beforePreviousInsertions);
197
0
}
198
199
void EditedSource::commitRemove(SourceLocation OrigLoc,
200
0
                                FileOffset BeginOffs, unsigned Len) {
201
0
  if (Len == 0)
202
0
    return;
203
204
0
  FileOffset EndOffs = BeginOffs.getWithOffset(Len);
205
0
  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
206
0
  if (I != FileEdits.begin())
207
0
    --I;
208
209
0
  for (; I != FileEdits.end(); ++I) {
210
0
    FileEdit &FA = I->second;
211
0
    FileOffset B = I->first;
212
0
    FileOffset E = B.getWithOffset(FA.RemoveLen);
213
214
0
    if (BeginOffs < E)
215
0
      break;
216
0
  }
217
218
0
  FileOffset TopBegin, TopEnd;
219
0
  FileEdit *TopFA = nullptr;
220
221
0
  if (I == FileEdits.end()) {
222
0
    FileEditsTy::iterator
223
0
      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
224
0
    NewI->second.RemoveLen = Len;
225
0
    return;
226
0
  }
227
228
0
  FileEdit &FA = I->second;
229
0
  FileOffset B = I->first;
230
0
  FileOffset E = B.getWithOffset(FA.RemoveLen);
231
0
  if (BeginOffs < B) {
232
0
    FileEditsTy::iterator
233
0
      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
234
0
    TopBegin = BeginOffs;
235
0
    TopEnd = EndOffs;
236
0
    TopFA = &NewI->second;
237
0
    TopFA->RemoveLen = Len;
238
0
  } else {
239
0
    TopBegin = B;
240
0
    TopEnd = E;
241
0
    TopFA = &I->second;
242
0
    if (TopEnd >= EndOffs)
243
0
      return;
244
0
    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
245
0
    TopEnd = EndOffs;
246
0
    TopFA->RemoveLen += diff;
247
0
    if (B == BeginOffs)
248
0
      TopFA->Text = StringRef();
249
0
    ++I;
250
0
  }
251
252
0
  while (I != FileEdits.end()) {
253
0
    FileEdit &FA = I->second;
254
0
    FileOffset B = I->first;
255
0
    FileOffset E = B.getWithOffset(FA.RemoveLen);
256
257
0
    if (B >= TopEnd)
258
0
      break;
259
260
0
    if (E <= TopEnd) {
261
0
      FileEdits.erase(I++);
262
0
      continue;
263
0
    }
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
0
  }
274
0
}
275
276
0
bool EditedSource::commit(const Commit &commit) {
277
0
  if (!commit.isCommitable())
278
0
    return false;
279
280
0
  struct CommitRAII {
281
0
    EditedSource &Editor;
282
283
0
    CommitRAII(EditedSource &Editor) : Editor(Editor) {
284
0
      Editor.startingCommit();
285
0
    }
286
287
0
    ~CommitRAII() {
288
0
      Editor.finishedCommit();
289
0
    }
290
0
  } CommitRAII(*this);
291
292
0
  for (edit::Commit::edit_iterator
293
0
         I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
294
0
    const edit::Commit::Edit &edit = *I;
295
0
    switch (edit.Kind) {
296
0
    case edit::Commit::Act_Insert:
297
0
      commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
298
0
      break;
299
0
    case edit::Commit::Act_InsertFromRange:
300
0
      commitInsertFromRange(edit.OrigLoc, edit.Offset,
301
0
                            edit.InsertFromRangeOffs, edit.Length,
302
0
                            edit.BeforePrev);
303
0
      break;
304
0
    case edit::Commit::Act_Remove:
305
0
      commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
306
0
      break;
307
0
    }
308
0
  }
309
310
0
  return true;
311
0
}
312
313
// Returns true if it is ok to make the two given characters adjacent.
314
0
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
0
  return !(Lexer::isAsciiIdentifierContinueChar(left, LangOpts) &&
318
0
           Lexer::isAsciiIdentifierContinueChar(right, LangOpts));
319
0
}
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
0
                                const LangOptions &LangOpts) {
325
0
  if (!canBeJoined(left, right, LangOpts))
326
0
    return false;
327
0
  if (isWhitespace(left) || isWhitespace(right))
328
0
    return true;
329
0
  if (canBeJoined(beforeWSpace, right, LangOpts))
330
0
    return false; // the whitespace was intentional, keep it.
331
0
  return true;
332
0
}
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
0
                          unsigned &len, StringRef &text) {
340
0
  assert(len && text.empty());
341
0
  SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
342
0
  if (BeginTokLoc != Loc)
343
0
    return; // the range is not at the beginning of a token, keep the range.
344
345
0
  bool Invalid = false;
346
0
  StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
347
0
  if (Invalid)
348
0
    return;
349
350
0
  unsigned begin = offs.getOffset();
351
0
  unsigned end = begin + len;
352
353
  // Do not try to extend the removal if we're at the end of the buffer already.
354
0
  if (end == buffer.size())
355
0
    return;
356
357
0
  assert(begin < buffer.size() && end < buffer.size() && "Invalid range!");
358
359
  // FIXME: Remove newline.
360
361
0
  if (begin == 0) {
362
0
    if (buffer[end] == ' ')
363
0
      ++len;
364
0
    return;
365
0
  }
366
367
0
  if (buffer[end] == ' ') {
368
0
    assert((end + 1 != buffer.size() || buffer.data()[end + 1] == 0) &&
369
0
           "buffer not zero-terminated!");
370
0
    if (canRemoveWhitespace(/*left=*/buffer[begin-1],
371
0
                            /*beforeWSpace=*/buffer[end-1],
372
0
                            /*right=*/buffer.data()[end + 1], // zero-terminated
373
0
                            LangOpts))
374
0
      ++len;
375
0
    return;
376
0
  }
377
378
0
  if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
379
0
    text = " ";
380
0
}
381
382
static void applyRewrite(EditsReceiver &receiver,
383
                         StringRef text, FileOffset offs, unsigned len,
384
                         const SourceManager &SM, const LangOptions &LangOpts,
385
0
                         bool shouldAdjustRemovals) {
386
0
  assert(offs.getFID().isValid());
387
0
  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
388
0
  Loc = Loc.getLocWithOffset(offs.getOffset());
389
0
  assert(Loc.isFileID());
390
391
0
  if (text.empty() && shouldAdjustRemovals)
392
0
    adjustRemoval(SM, LangOpts, Loc, offs, len, text);
393
394
0
  CharSourceRange range = CharSourceRange::getCharRange(Loc,
395
0
                                                     Loc.getLocWithOffset(len));
396
397
0
  if (text.empty()) {
398
0
    assert(len);
399
0
    receiver.remove(range);
400
0
    return;
401
0
  }
402
403
0
  if (len)
404
0
    receiver.replace(range, text);
405
0
  else
406
0
    receiver.insert(Loc, text);
407
0
}
408
409
void EditedSource::applyRewrites(EditsReceiver &receiver,
410
0
                                 bool shouldAdjustRemovals) {
411
0
  SmallString<128> StrVec;
412
0
  FileOffset CurOffs, CurEnd;
413
0
  unsigned CurLen;
414
415
0
  if (FileEdits.empty())
416
0
    return;
417
418
0
  FileEditsTy::iterator I = FileEdits.begin();
419
0
  CurOffs = I->first;
420
0
  StrVec = I->second.Text;
421
0
  CurLen = I->second.RemoveLen;
422
0
  CurEnd = CurOffs.getWithOffset(CurLen);
423
0
  ++I;
424
425
0
  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
426
0
    FileOffset offs = I->first;
427
0
    FileEdit act = I->second;
428
0
    assert(offs >= CurEnd);
429
430
0
    if (offs == CurEnd) {
431
0
      StrVec += act.Text;
432
0
      CurLen += act.RemoveLen;
433
0
      CurEnd.getWithOffset(act.RemoveLen);
434
0
      continue;
435
0
    }
436
437
0
    applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
438
0
                 shouldAdjustRemovals);
439
0
    CurOffs = offs;
440
0
    StrVec = act.Text;
441
0
    CurLen = act.RemoveLen;
442
0
    CurEnd = CurOffs.getWithOffset(CurLen);
443
0
  }
444
445
0
  applyRewrite(receiver, StrVec, CurOffs, CurLen, SourceMgr, LangOpts,
446
0
               shouldAdjustRemovals);
447
0
}
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
0
                                      bool &Invalid) {
456
0
  assert(BeginOffs.getFID() == EndOffs.getFID());
457
0
  assert(BeginOffs <= EndOffs);
458
0
  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
459
0
  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
460
0
  assert(BLoc.isFileID());
461
0
  SourceLocation
462
0
    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
463
0
  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
464
0
                              SourceMgr, LangOpts, &Invalid);
465
0
}
466
467
EditedSource::FileEditsTy::iterator
468
0
EditedSource::getActionForOffset(FileOffset Offs) {
469
0
  FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
470
0
  if (I == FileEdits.begin())
471
0
    return FileEdits.end();
472
0
  --I;
473
0
  FileEdit &FA = I->second;
474
0
  FileOffset B = I->first;
475
0
  FileOffset E = B.getWithOffset(FA.RemoveLen);
476
0
  if (Offs >= B && Offs < E)
477
0
    return I;
478
479
0
  return FileEdits.end();
480
0
}