Coverage Report

Created: 2024-01-17 10:31

/src/llvm-project/clang/lib/AST/RawCommentList.cpp
Line
Count
Source (jump to first uncovered line)
1
//===--- RawCommentList.cpp - Processing raw comments -----------*- C++ -*-===//
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/AST/RawCommentList.h"
10
#include "clang/AST/ASTContext.h"
11
#include "clang/AST/Comment.h"
12
#include "clang/AST/CommentBriefParser.h"
13
#include "clang/AST/CommentCommandTraits.h"
14
#include "clang/AST/CommentLexer.h"
15
#include "clang/AST/CommentParser.h"
16
#include "clang/AST/CommentSema.h"
17
#include "clang/Basic/CharInfo.h"
18
#include "llvm/ADT/STLExtras.h"
19
#include "llvm/ADT/StringExtras.h"
20
#include "llvm/Support/Allocator.h"
21
22
using namespace clang;
23
24
namespace {
25
/// Get comment kind and bool describing if it is a trailing comment.
26
std::pair<RawComment::CommentKind, bool> getCommentKind(StringRef Comment,
27
943
                                                        bool ParseAllComments) {
28
943
  const size_t MinCommentLength = ParseAllComments ? 2 : 3;
29
943
  if ((Comment.size() < MinCommentLength) || Comment[0] != '/')
30
7
    return std::make_pair(RawComment::RCK_Invalid, false);
31
32
936
  RawComment::CommentKind K;
33
936
  if (Comment[1] == '/') {
34
855
    if (Comment.size() < 3)
35
0
      return std::make_pair(RawComment::RCK_OrdinaryBCPL, false);
36
37
855
    if (Comment[2] == '/')
38
252
      K = RawComment::RCK_BCPLSlash;
39
603
    else if (Comment[2] == '!')
40
2
      K = RawComment::RCK_BCPLExcl;
41
601
    else
42
601
      return std::make_pair(RawComment::RCK_OrdinaryBCPL, false);
43
855
  } else {
44
81
    assert(Comment.size() >= 4);
45
46
    // Comment lexer does not understand escapes in comment markers, so pretend
47
    // that this is not a comment.
48
81
    if (Comment[1] != '*' ||
49
81
        Comment[Comment.size() - 2] != '*' ||
50
81
        Comment[Comment.size() - 1] != '/')
51
0
      return std::make_pair(RawComment::RCK_Invalid, false);
52
53
81
    if (Comment[2] == '*')
54
0
      K = RawComment::RCK_JavaDoc;
55
81
    else if (Comment[2] == '!')
56
1
      K = RawComment::RCK_Qt;
57
80
    else
58
80
      return std::make_pair(RawComment::RCK_OrdinaryC, false);
59
81
  }
60
255
  const bool TrailingComment = (Comment.size() > 3) && (Comment[3] == '<');
61
255
  return std::make_pair(K, TrailingComment);
62
936
}
63
64
0
bool mergedCommentIsTrailingComment(StringRef Comment) {
65
0
  return (Comment.size() > 3) && (Comment[3] == '<');
66
0
}
67
68
/// Returns true if R1 and R2 both have valid locations that start on the same
69
/// column.
70
bool commentsStartOnSameColumn(const SourceManager &SM, const RawComment &R1,
71
0
                               const RawComment &R2) {
72
0
  SourceLocation L1 = R1.getBeginLoc();
73
0
  SourceLocation L2 = R2.getBeginLoc();
74
0
  bool Invalid = false;
75
0
  unsigned C1 = SM.getPresumedColumnNumber(L1, &Invalid);
76
0
  if (!Invalid) {
77
0
    unsigned C2 = SM.getPresumedColumnNumber(L2, &Invalid);
78
0
    return !Invalid && (C1 == C2);
79
0
  }
80
0
  return false;
81
0
}
82
} // unnamed namespace
83
84
/// Determines whether there is only whitespace in `Buffer` between `P`
85
/// and the previous line.
86
/// \param Buffer The buffer to search in.
87
/// \param P The offset from the beginning of `Buffer` to start from.
88
/// \return true if all of the characters in `Buffer` ranging from the closest
89
/// line-ending character before `P` (or the beginning of `Buffer`) to `P - 1`
90
/// are whitespace.
91
0
static bool onlyWhitespaceOnLineBefore(const char *Buffer, unsigned P) {
92
  // Search backwards until we see linefeed or carriage return.
93
0
  for (unsigned I = P; I != 0; --I) {
94
0
    char C = Buffer[I - 1];
95
0
    if (isVerticalWhitespace(C))
96
0
      return true;
97
0
    if (!isHorizontalWhitespace(C))
98
0
      return false;
99
0
  }
100
  // We hit the beginning of the buffer.
101
0
  return true;
102
0
}
103
104
/// Returns whether `K` is an ordinary comment kind.
105
0
static bool isOrdinaryKind(RawComment::CommentKind K) {
106
0
  return (K == RawComment::RCK_OrdinaryBCPL) ||
107
0
         (K == RawComment::RCK_OrdinaryC);
108
0
}
109
110
RawComment::RawComment(const SourceManager &SourceMgr, SourceRange SR,
111
                       const CommentOptions &CommentOpts, bool Merged) :
112
    Range(SR), RawTextValid(false), BriefTextValid(false),
113
    IsAttached(false), IsTrailingComment(false),
114
943
    IsAlmostTrailingComment(false) {
115
  // Extract raw comment text, if possible.
116
943
  if (SR.getBegin() == SR.getEnd() || getRawText(SourceMgr).empty()) {
117
0
    Kind = RCK_Invalid;
118
0
    return;
119
0
  }
120
121
  // Guess comment kind.
122
943
  std::pair<CommentKind, bool> K =
123
943
      getCommentKind(RawText, CommentOpts.ParseAllComments);
124
125
  // Guess whether an ordinary comment is trailing.
126
943
  if (CommentOpts.ParseAllComments && isOrdinaryKind(K.first)) {
127
0
    FileID BeginFileID;
128
0
    unsigned BeginOffset;
129
0
    std::tie(BeginFileID, BeginOffset) =
130
0
        SourceMgr.getDecomposedLoc(Range.getBegin());
131
0
    if (BeginOffset != 0) {
132
0
      bool Invalid = false;
133
0
      const char *Buffer =
134
0
          SourceMgr.getBufferData(BeginFileID, &Invalid).data();
135
0
      IsTrailingComment |=
136
0
          (!Invalid && !onlyWhitespaceOnLineBefore(Buffer, BeginOffset));
137
0
    }
138
0
  }
139
140
943
  if (!Merged) {
141
943
    Kind = K.first;
142
943
    IsTrailingComment |= K.second;
143
144
943
    IsAlmostTrailingComment =
145
943
        RawText.starts_with("//<") || RawText.starts_with("/*<");
146
943
  } else {
147
0
    Kind = RCK_Merged;
148
0
    IsTrailingComment =
149
0
        IsTrailingComment || mergedCommentIsTrailingComment(RawText);
150
0
  }
151
943
}
152
153
943
StringRef RawComment::getRawTextSlow(const SourceManager &SourceMgr) const {
154
943
  FileID BeginFileID;
155
943
  FileID EndFileID;
156
943
  unsigned BeginOffset;
157
943
  unsigned EndOffset;
158
159
943
  std::tie(BeginFileID, BeginOffset) =
160
943
      SourceMgr.getDecomposedLoc(Range.getBegin());
161
943
  std::tie(EndFileID, EndOffset) = SourceMgr.getDecomposedLoc(Range.getEnd());
162
163
943
  const unsigned Length = EndOffset - BeginOffset;
164
943
  if (Length < 2)
165
0
    return StringRef();
166
167
  // The comment can't begin in one file and end in another.
168
943
  assert(BeginFileID == EndFileID);
169
170
0
  bool Invalid = false;
171
943
  const char *BufferStart = SourceMgr.getBufferData(BeginFileID,
172
943
                                                    &Invalid).data();
173
943
  if (Invalid)
174
0
    return StringRef();
175
176
943
  return StringRef(BufferStart + BeginOffset, Length);
177
943
}
178
179
0
const char *RawComment::extractBriefText(const ASTContext &Context) const {
180
  // Lazily initialize RawText using the accessor before using it.
181
0
  (void)getRawText(Context.getSourceManager());
182
183
  // Since we will be copying the resulting text, all allocations made during
184
  // parsing are garbage after resulting string is formed.  Thus we can use
185
  // a separate allocator for all temporary stuff.
186
0
  llvm::BumpPtrAllocator Allocator;
187
188
0
  comments::Lexer L(Allocator, Context.getDiagnostics(),
189
0
                    Context.getCommentCommandTraits(),
190
0
                    Range.getBegin(),
191
0
                    RawText.begin(), RawText.end());
192
0
  comments::BriefParser P(L, Context.getCommentCommandTraits());
193
194
0
  const std::string Result = P.Parse();
195
0
  const unsigned BriefTextLength = Result.size();
196
0
  char *BriefTextPtr = new (Context) char[BriefTextLength + 1];
197
0
  memcpy(BriefTextPtr, Result.c_str(), BriefTextLength + 1);
198
0
  BriefText = BriefTextPtr;
199
0
  BriefTextValid = true;
200
201
0
  return BriefTextPtr;
202
0
}
203
204
comments::FullComment *RawComment::parse(const ASTContext &Context,
205
                                         const Preprocessor *PP,
206
0
                                         const Decl *D) const {
207
  // Lazily initialize RawText using the accessor before using it.
208
0
  (void)getRawText(Context.getSourceManager());
209
210
0
  comments::Lexer L(Context.getAllocator(), Context.getDiagnostics(),
211
0
                    Context.getCommentCommandTraits(),
212
0
                    getSourceRange().getBegin(),
213
0
                    RawText.begin(), RawText.end());
214
0
  comments::Sema S(Context.getAllocator(), Context.getSourceManager(),
215
0
                   Context.getDiagnostics(),
216
0
                   Context.getCommentCommandTraits(),
217
0
                   PP);
218
0
  S.setDecl(D);
219
0
  comments::Parser P(L, S, Context.getAllocator(), Context.getSourceManager(),
220
0
                     Context.getDiagnostics(),
221
0
                     Context.getCommentCommandTraits());
222
223
0
  return P.parseFullComment();
224
0
}
225
226
static bool onlyWhitespaceBetween(SourceManager &SM,
227
                                  SourceLocation Loc1, SourceLocation Loc2,
228
245
                                  unsigned MaxNewlinesAllowed) {
229
245
  std::pair<FileID, unsigned> Loc1Info = SM.getDecomposedLoc(Loc1);
230
245
  std::pair<FileID, unsigned> Loc2Info = SM.getDecomposedLoc(Loc2);
231
232
  // Question does not make sense if locations are in different files.
233
245
  if (Loc1Info.first != Loc2Info.first)
234
0
    return false;
235
236
245
  bool Invalid = false;
237
245
  const char *Buffer = SM.getBufferData(Loc1Info.first, &Invalid).data();
238
245
  if (Invalid)
239
0
    return false;
240
241
245
  unsigned NumNewlines = 0;
242
245
  assert(Loc1Info.second <= Loc2Info.second && "Loc1 after Loc2!");
243
  // Look for non-whitespace characters and remember any newlines seen.
244
490
  for (unsigned I = Loc1Info.second; I != Loc2Info.second; ++I) {
245
490
    switch (Buffer[I]) {
246
15
    default:
247
15
      return false;
248
0
    case ' ':
249
0
    case '\t':
250
0
    case '\f':
251
0
    case '\v':
252
0
      break;
253
49
    case '\r':
254
475
    case '\n':
255
475
      ++NumNewlines;
256
257
      // Check if we have found more than the maximum allowed number of
258
      // newlines.
259
475
      if (NumNewlines > MaxNewlinesAllowed)
260
230
        return false;
261
262
      // Collapse \r\n and \n\r into a single newline.
263
245
      if (I + 1 != Loc2Info.second &&
264
245
          (Buffer[I + 1] == '\n' || Buffer[I + 1] == '\r') &&
265
245
          Buffer[I] != Buffer[I + 1])
266
6
        ++I;
267
245
      break;
268
490
    }
269
490
  }
270
271
0
  return true;
272
245
}
273
274
void RawCommentList::addComment(const RawComment &RC,
275
                                const CommentOptions &CommentOpts,
276
943
                                llvm::BumpPtrAllocator &Allocator) {
277
943
  if (RC.isInvalid())
278
7
    return;
279
280
  // Ordinary comments are not interesting for us.
281
936
  if (RC.isOrdinary() && !CommentOpts.ParseAllComments)
282
681
    return;
283
284
255
  std::pair<FileID, unsigned> Loc =
285
255
      SourceMgr.getDecomposedLoc(RC.getBeginLoc());
286
287
255
  const FileID CommentFile = Loc.first;
288
255
  const unsigned CommentOffset = Loc.second;
289
290
  // If this is the first Doxygen comment, save it (because there isn't
291
  // anything to merge it with).
292
255
  if (OrderedComments[CommentFile].empty()) {
293
10
    OrderedComments[CommentFile][CommentOffset] =
294
10
        new (Allocator) RawComment(RC);
295
10
    return;
296
10
  }
297
298
245
  const RawComment &C1 = *OrderedComments[CommentFile].rbegin()->second;
299
245
  const RawComment &C2 = RC;
300
301
  // Merge comments only if there is only whitespace between them.
302
  // Can't merge trailing and non-trailing comments unless the second is
303
  // non-trailing ordinary in the same column, as in the case:
304
  //   int x; // documents x
305
  //          // more text
306
  // versus:
307
  //   int x; // documents x
308
  //   int y; // documents y
309
  // or:
310
  //   int x; // documents x
311
  //   // documents y
312
  //   int y;
313
  // Merge comments if they are on same or consecutive lines.
314
245
  if ((C1.isTrailingComment() == C2.isTrailingComment() ||
315
245
       (C1.isTrailingComment() && !C2.isTrailingComment() &&
316
0
        isOrdinaryKind(C2.getKind()) &&
317
0
        commentsStartOnSameColumn(SourceMgr, C1, C2))) &&
318
245
      onlyWhitespaceBetween(SourceMgr, C1.getEndLoc(), C2.getBeginLoc(),
319
245
                            /*MaxNewlinesAllowed=*/1)) {
320
0
    SourceRange MergedRange(C1.getBeginLoc(), C2.getEndLoc());
321
0
    *OrderedComments[CommentFile].rbegin()->second =
322
0
        RawComment(SourceMgr, MergedRange, CommentOpts, true);
323
245
  } else {
324
245
    OrderedComments[CommentFile][CommentOffset] =
325
245
        new (Allocator) RawComment(RC);
326
245
  }
327
245
}
328
329
const std::map<unsigned, RawComment *> *
330
0
RawCommentList::getCommentsInFile(FileID File) const {
331
0
  auto CommentsInFile = OrderedComments.find(File);
332
0
  if (CommentsInFile == OrderedComments.end())
333
0
    return nullptr;
334
335
0
  return &CommentsInFile->second;
336
0
}
337
338
0
bool RawCommentList::empty() const { return OrderedComments.empty(); }
339
340
unsigned RawCommentList::getCommentBeginLine(RawComment *C, FileID File,
341
0
                                             unsigned Offset) const {
342
0
  auto Cached = CommentBeginLine.find(C);
343
0
  if (Cached != CommentBeginLine.end())
344
0
    return Cached->second;
345
0
  const unsigned Line = SourceMgr.getLineNumber(File, Offset);
346
0
  CommentBeginLine[C] = Line;
347
0
  return Line;
348
0
}
349
350
0
unsigned RawCommentList::getCommentEndOffset(RawComment *C) const {
351
0
  auto Cached = CommentEndOffset.find(C);
352
0
  if (Cached != CommentEndOffset.end())
353
0
    return Cached->second;
354
0
  const unsigned Offset =
355
0
      SourceMgr.getDecomposedLoc(C->getSourceRange().getEnd()).second;
356
0
  CommentEndOffset[C] = Offset;
357
0
  return Offset;
358
0
}
359
360
std::string RawComment::getFormattedText(const SourceManager &SourceMgr,
361
0
                                         DiagnosticsEngine &Diags) const {
362
0
  llvm::StringRef CommentText = getRawText(SourceMgr);
363
0
  if (CommentText.empty())
364
0
    return "";
365
366
0
  std::string Result;
367
0
  for (const RawComment::CommentLine &Line :
368
0
       getFormattedLines(SourceMgr, Diags))
369
0
    Result += Line.Text + "\n";
370
371
0
  auto LastChar = Result.find_last_not_of('\n');
372
0
  Result.erase(LastChar + 1, Result.size());
373
374
0
  return Result;
375
0
}
376
377
std::vector<RawComment::CommentLine>
378
RawComment::getFormattedLines(const SourceManager &SourceMgr,
379
0
                              DiagnosticsEngine &Diags) const {
380
0
  llvm::StringRef CommentText = getRawText(SourceMgr);
381
0
  if (CommentText.empty())
382
0
    return {};
383
384
0
  llvm::BumpPtrAllocator Allocator;
385
  // We do not parse any commands, so CommentOptions are ignored by
386
  // comments::Lexer. Therefore, we just use default-constructed options.
387
0
  CommentOptions DefOpts;
388
0
  comments::CommandTraits EmptyTraits(Allocator, DefOpts);
389
0
  comments::Lexer L(Allocator, Diags, EmptyTraits, getSourceRange().getBegin(),
390
0
                    CommentText.begin(), CommentText.end(),
391
0
                    /*ParseCommands=*/false);
392
393
0
  std::vector<RawComment::CommentLine> Result;
394
  // A column number of the first non-whitespace token in the comment text.
395
  // We skip whitespace up to this column, but keep the whitespace after this
396
  // column. IndentColumn is calculated when lexing the first line and reused
397
  // for the rest of lines.
398
0
  unsigned IndentColumn = 0;
399
400
  // Record the line number of the last processed comment line.
401
  // For block-style comments, an extra newline token will be produced after
402
  // the end-comment marker, e.g.:
403
  //   /** This is a multi-line comment block.
404
  //       The lexer will produce two newline tokens here > */
405
  // previousLine will record the line number when we previously saw a newline
406
  // token and recorded a comment line. If we see another newline token on the
407
  // same line, don't record anything in between.
408
0
  unsigned PreviousLine = 0;
409
410
  // Processes one line of the comment and adds it to the result.
411
  // Handles skipping the indent at the start of the line.
412
  // Returns false when eof is reached and true otherwise.
413
0
  auto LexLine = [&](bool IsFirstLine) -> bool {
414
0
    comments::Token Tok;
415
    // Lex the first token on the line. We handle it separately, because we to
416
    // fix up its indentation.
417
0
    L.lex(Tok);
418
0
    if (Tok.is(comments::tok::eof))
419
0
      return false;
420
0
    if (Tok.is(comments::tok::newline)) {
421
0
      PresumedLoc Loc = SourceMgr.getPresumedLoc(Tok.getLocation());
422
0
      if (Loc.getLine() != PreviousLine) {
423
0
        Result.emplace_back("", Loc, Loc);
424
0
        PreviousLine = Loc.getLine();
425
0
      }
426
0
      return true;
427
0
    }
428
0
    SmallString<124> Line;
429
0
    llvm::StringRef TokText = L.getSpelling(Tok, SourceMgr);
430
0
    bool LocInvalid = false;
431
0
    unsigned TokColumn =
432
0
        SourceMgr.getSpellingColumnNumber(Tok.getLocation(), &LocInvalid);
433
0
    assert(!LocInvalid && "getFormattedText for invalid location");
434
435
    // Amount of leading whitespace in TokText.
436
0
    size_t WhitespaceLen = TokText.find_first_not_of(" \t");
437
0
    if (WhitespaceLen == StringRef::npos)
438
0
      WhitespaceLen = TokText.size();
439
    // Remember the amount of whitespace we skipped in the first line to remove
440
    // indent up to that column in the following lines.
441
0
    if (IsFirstLine)
442
0
      IndentColumn = TokColumn + WhitespaceLen;
443
444
    // Amount of leading whitespace we actually want to skip.
445
    // For the first line we skip all the whitespace.
446
    // For the rest of the lines, we skip whitespace up to IndentColumn.
447
0
    unsigned SkipLen =
448
0
        IsFirstLine
449
0
            ? WhitespaceLen
450
0
            : std::min<size_t>(
451
0
                  WhitespaceLen,
452
0
                  std::max<int>(static_cast<int>(IndentColumn) - TokColumn, 0));
453
0
    llvm::StringRef Trimmed = TokText.drop_front(SkipLen);
454
0
    Line += Trimmed;
455
    // Get the beginning location of the adjusted comment line.
456
0
    PresumedLoc Begin =
457
0
        SourceMgr.getPresumedLoc(Tok.getLocation().getLocWithOffset(SkipLen));
458
459
    // Lex all tokens in the rest of the line.
460
0
    for (L.lex(Tok); Tok.isNot(comments::tok::eof); L.lex(Tok)) {
461
0
      if (Tok.is(comments::tok::newline)) {
462
        // Get the ending location of the comment line.
463
0
        PresumedLoc End = SourceMgr.getPresumedLoc(Tok.getLocation());
464
0
        if (End.getLine() != PreviousLine) {
465
0
          Result.emplace_back(Line, Begin, End);
466
0
          PreviousLine = End.getLine();
467
0
        }
468
0
        return true;
469
0
      }
470
0
      Line += L.getSpelling(Tok, SourceMgr);
471
0
    }
472
0
    PresumedLoc End = SourceMgr.getPresumedLoc(Tok.getLocation());
473
0
    Result.emplace_back(Line, Begin, End);
474
    // We've reached the end of file token.
475
0
    return false;
476
0
  };
477
478
  // Process first line separately to remember indent for the following lines.
479
0
  if (!LexLine(/*IsFirstLine=*/true))
480
0
    return Result;
481
  // Process the rest of the lines.
482
0
  while (LexLine(/*IsFirstLine=*/false))
483
0
    ;
484
0
  return Result;
485
0
}