Coverage Report

Created: 2022-07-16 07:03

/Users/buildslave/jenkins/workspace/coverage/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
38.5M
                                                        bool ParseAllComments) {
28
38.5M
  const size_t MinCommentLength = ParseAllComments ? 
2246
:
338.5M
;
29
38.5M
  if ((Comment.size() < MinCommentLength) || 
Comment[0] != '/'37.8M
)
30
638k
    return std::make_pair(RawComment::RCK_Invalid, false);
31
32
37.8M
  RawComment::CommentKind K;
33
37.8M
  if (Comment[1] == '/') {
34
16.3M
    if (Comment.size() < 3)
35
2
      return std::make_pair(RawComment::RCK_OrdinaryBCPL, false);
36
37
16.3M
    if (Comment[2] == '/')
38
271k
      K = RawComment::RCK_BCPLSlash;
39
16.1M
    else if (Comment[2] == '!')
40
34
      K = RawComment::RCK_BCPLExcl;
41
16.1M
    else
42
16.1M
      return std::make_pair(RawComment::RCK_OrdinaryBCPL, false);
43
21.4M
  } else {
44
21.4M
    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
21.4M
    if (Comment[1] != '*' ||
49
21.4M
        Comment[Comment.size() - 2] != '*' ||
50
21.4M
        
Comment[Comment.size() - 1] != '/'21.4M
)
51
12
      return std::make_pair(RawComment::RCK_Invalid, false);
52
53
21.4M
    if (Comment[2] == '*')
54
912
      K = RawComment::RCK_JavaDoc;
55
21.4M
    else if (Comment[2] == '!')
56
96
      K = RawComment::RCK_Qt;
57
21.4M
    else
58
21.4M
      return std::make_pair(RawComment::RCK_OrdinaryC, false);
59
21.4M
  }
60
272k
  const bool TrailingComment = (Comment.size() > 3) && 
(Comment[3] == '<')260k
;
61
272k
  return std::make_pair(K, TrailingComment);
62
37.8M
}
63
64
124k
bool mergedCommentIsTrailingComment(StringRef Comment) {
65
124k
  return (Comment.size() > 3) && (Comment[3] == '<');
66
124k
}
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
8
                               const RawComment &R2) {
72
8
  SourceLocation L1 = R1.getBeginLoc();
73
8
  SourceLocation L2 = R2.getBeginLoc();
74
8
  bool Invalid = false;
75
8
  unsigned C1 = SM.getPresumedColumnNumber(L1, &Invalid);
76
8
  if (!Invalid) {
77
8
    unsigned C2 = SM.getPresumedColumnNumber(L2, &Invalid);
78
8
    return !Invalid && (C1 == C2);
79
8
  }
80
0
  return false;
81
8
}
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
201
static bool onlyWhitespaceOnLineBefore(const char *Buffer, unsigned P) {
92
  // Search backwards until we see linefeed or carriage return.
93
393
  for (unsigned I = P; I != 0; 
--I192
) {
94
393
    char C = Buffer[I - 1];
95
393
    if (isVerticalWhitespace(C))
96
177
      return true;
97
216
    if (!isHorizontalWhitespace(C))
98
24
      return false;
99
216
  }
100
  // We hit the beginning of the buffer.
101
0
  return true;
102
201
}
103
104
/// Returns whether `K` is an ordinary comment kind.
105
582
static bool isOrdinaryKind(RawComment::CommentKind K) {
106
582
  return (K == RawComment::RCK_OrdinaryBCPL) ||
107
582
         
(K == RawComment::RCK_OrdinaryC)378
;
108
582
}
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
38.5M
    IsAlmostTrailingComment(false) {
115
  // Extract raw comment text, if possible.
116
38.5M
  if (SR.getBegin() == SR.getEnd() || getRawText(SourceMgr).empty()) {
117
2
    Kind = RCK_Invalid;
118
2
    return;
119
2
  }
120
121
  // Guess comment kind.
122
38.5M
  std::pair<CommentKind, bool> K =
123
38.5M
      getCommentKind(RawText, CommentOpts.ParseAllComments);
124
125
  // Guess whether an ordinary comment is trailing.
126
38.5M
  if (CommentOpts.ParseAllComments && 
isOrdinaryKind(K.first)246
) {
127
212
    FileID BeginFileID;
128
212
    unsigned BeginOffset;
129
212
    std::tie(BeginFileID, BeginOffset) =
130
212
        SourceMgr.getDecomposedLoc(Range.getBegin());
131
212
    if (BeginOffset != 0) {
132
201
      bool Invalid = false;
133
201
      const char *Buffer =
134
201
          SourceMgr.getBufferData(BeginFileID, &Invalid).data();
135
201
      IsTrailingComment |=
136
201
          (!Invalid && !onlyWhitespaceOnLineBefore(Buffer, BeginOffset));
137
201
    }
138
212
  }
139
140
38.5M
  if (!Merged) {
141
38.3M
    Kind = K.first;
142
38.3M
    IsTrailingComment |= K.second;
143
144
38.3M
    IsAlmostTrailingComment = RawText.startswith("//<") ||
145
38.3M
                                 
RawText.startswith("/*<")38.3M
;
146
38.3M
  } else {
147
124k
    Kind = RCK_Merged;
148
124k
    IsTrailingComment =
149
124k
        IsTrailingComment || 
mergedCommentIsTrailingComment(RawText)124k
;
150
124k
  }
151
38.5M
}
152
153
38.5M
StringRef RawComment::getRawTextSlow(const SourceManager &SourceMgr) const {
154
38.5M
  FileID BeginFileID;
155
38.5M
  FileID EndFileID;
156
38.5M
  unsigned BeginOffset;
157
38.5M
  unsigned EndOffset;
158
159
38.5M
  std::tie(BeginFileID, BeginOffset) =
160
38.5M
      SourceMgr.getDecomposedLoc(Range.getBegin());
161
38.5M
  std::tie(EndFileID, EndOffset) = SourceMgr.getDecomposedLoc(Range.getEnd());
162
163
38.5M
  const unsigned Length = EndOffset - BeginOffset;
164
38.5M
  if (Length < 2)
165
2
    return StringRef();
166
167
  // The comment can't begin in one file and end in another.
168
38.5M
  assert(BeginFileID == EndFileID);
169
170
0
  bool Invalid = false;
171
38.5M
  const char *BufferStart = SourceMgr.getBufferData(BeginFileID,
172
38.5M
                                                    &Invalid).data();
173
38.5M
  if (Invalid)
174
0
    return StringRef();
175
176
38.5M
  return StringRef(BufferStart + BeginOffset, Length);
177
38.5M
}
178
179
901
const char *RawComment::extractBriefText(const ASTContext &Context) const {
180
  // Lazily initialize RawText using the accessor before using it.
181
901
  (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
901
  llvm::BumpPtrAllocator Allocator;
187
188
901
  comments::Lexer L(Allocator, Context.getDiagnostics(),
189
901
                    Context.getCommentCommandTraits(),
190
901
                    Range.getBegin(),
191
901
                    RawText.begin(), RawText.end());
192
901
  comments::BriefParser P(L, Context.getCommentCommandTraits());
193
194
901
  const std::string Result = P.Parse();
195
901
  const unsigned BriefTextLength = Result.size();
196
901
  char *BriefTextPtr = new (Context) char[BriefTextLength + 1];
197
901
  memcpy(BriefTextPtr, Result.c_str(), BriefTextLength + 1);
198
901
  BriefText = BriefTextPtr;
199
901
  BriefTextValid = true;
200
201
901
  return BriefTextPtr;
202
901
}
203
204
comments::FullComment *RawComment::parse(const ASTContext &Context,
205
                                         const Preprocessor *PP,
206
3.21k
                                         const Decl *D) const {
207
  // Lazily initialize RawText using the accessor before using it.
208
3.21k
  (void)getRawText(Context.getSourceManager());
209
210
3.21k
  comments::Lexer L(Context.getAllocator(), Context.getDiagnostics(),
211
3.21k
                    Context.getCommentCommandTraits(),
212
3.21k
                    getSourceRange().getBegin(),
213
3.21k
                    RawText.begin(), RawText.end());
214
3.21k
  comments::Sema S(Context.getAllocator(), Context.getSourceManager(),
215
3.21k
                   Context.getDiagnostics(),
216
3.21k
                   Context.getCommentCommandTraits(),
217
3.21k
                   PP);
218
3.21k
  S.setDecl(D);
219
3.21k
  comments::Parser P(L, S, Context.getAllocator(), Context.getSourceManager(),
220
3.21k
                     Context.getDiagnostics(),
221
3.21k
                     Context.getCommentCommandTraits());
222
223
3.21k
  return P.parseFullComment();
224
3.21k
}
225
226
static bool onlyWhitespaceBetween(SourceManager &SM,
227
                                  SourceLocation Loc1, SourceLocation Loc2,
228
145k
                                  unsigned MaxNewlinesAllowed) {
229
145k
  std::pair<FileID, unsigned> Loc1Info = SM.getDecomposedLoc(Loc1);
230
145k
  std::pair<FileID, unsigned> Loc2Info = SM.getDecomposedLoc(Loc2);
231
232
  // Question does not make sense if locations are in different files.
233
145k
  if (Loc1Info.first != Loc2Info.first)
234
0
    return false;
235
236
145k
  bool Invalid = false;
237
145k
  const char *Buffer = SM.getBufferData(Loc1Info.first, &Invalid).data();
238
145k
  if (Invalid)
239
0
    return false;
240
241
145k
  unsigned NumNewlines = 0;
242
145k
  assert(Loc1Info.second <= Loc2Info.second && "Loc1 after Loc2!");
243
  // Look for non-whitespace characters and remember any newlines seen.
244
376k
  for (unsigned I = Loc1Info.second; I != Loc2Info.second; 
++I231k
) {
245
252k
    switch (Buffer[I]) {
246
21.0k
    default:
247
21.0k
      return false;
248
85.4k
    case ' ':
249
85.4k
    case '\t':
250
85.4k
    case '\f':
251
85.4k
    case '\v':
252
85.4k
      break;
253
0
    case '\r':
254
146k
    case '\n':
255
146k
      ++NumNewlines;
256
257
      // Check if we have found more than the maximum allowed number of
258
      // newlines.
259
146k
      if (NumNewlines > MaxNewlinesAllowed)
260
579
        return false;
261
262
      // Collapse \r\n and \n\r into a single newline.
263
145k
      if (I + 1 != Loc2Info.second &&
264
145k
          
(47.9k
Buffer[I + 1] == '\n'47.9k
||
Buffer[I + 1] == '\r'47.3k
) &&
265
145k
          
Buffer[I] != Buffer[I + 1]579
)
266
0
        ++I;
267
145k
      break;
268
252k
    }
269
252k
  }
270
271
124k
  return true;
272
145k
}
273
274
void RawCommentList::addComment(const RawComment &RC,
275
                                const CommentOptions &CommentOpts,
276
38.3M
                                llvm::BumpPtrAllocator &Allocator) {
277
38.3M
  if (RC.isInvalid())
278
638k
    return;
279
280
  // Ordinary comments are not interesting for us.
281
37.7M
  if (RC.isOrdinary() && 
!CommentOpts.ParseAllComments37.6M
)
282
37.6M
    return;
283
284
148k
  std::pair<FileID, unsigned> Loc =
285
148k
      SourceMgr.getDecomposedLoc(RC.getBeginLoc());
286
287
148k
  const FileID CommentFile = Loc.first;
288
148k
  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
148k
  if (OrderedComments[CommentFile].empty()) {
293
2.35k
    OrderedComments[CommentFile][CommentOffset] =
294
2.35k
        new (Allocator) RawComment(RC);
295
2.35k
    return;
296
2.35k
  }
297
298
146k
  const RawComment &C1 = *OrderedComments[CommentFile].rbegin()->second;
299
146k
  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
146k
  if ((C1.isTrailingComment() == C2.isTrailingComment() ||
315
146k
       
(677
C1.isTrailingComment()677
&&
!C2.isTrailingComment()336
&&
316
677
        
isOrdinaryKind(C2.getKind())336
&&
317
677
        
commentsStartOnSameColumn(SourceMgr, C1, C2)8
)) &&
318
146k
      onlyWhitespaceBetween(SourceMgr, C1.getEndLoc(), C2.getBeginLoc(),
319
145k
                            /*MaxNewlinesAllowed=*/1)) {
320
124k
    SourceRange MergedRange(C1.getBeginLoc(), C2.getEndLoc());
321
124k
    *OrderedComments[CommentFile].rbegin()->second =
322
124k
        RawComment(SourceMgr, MergedRange, CommentOpts, true);
323
124k
  } else {
324
22.2k
    OrderedComments[CommentFile][CommentOffset] =
325
22.2k
        new (Allocator) RawComment(RC);
326
22.2k
  }
327
146k
}
328
329
const std::map<unsigned, RawComment *> *
330
11.9k
RawCommentList::getCommentsInFile(FileID File) const {
331
11.9k
  auto CommentsInFile = OrderedComments.find(File);
332
11.9k
  if (CommentsInFile == OrderedComments.end())
333
5.70k
    return nullptr;
334
335
6.27k
  return &CommentsInFile->second;
336
11.9k
}
337
338
36.4k
bool RawCommentList::empty() const { return OrderedComments.empty(); }
339
340
unsigned RawCommentList::getCommentBeginLine(RawComment *C, FileID File,
341
137
                                             unsigned Offset) const {
342
137
  auto Cached = CommentBeginLine.find(C);
343
137
  if (Cached != CommentBeginLine.end())
344
20
    return Cached->second;
345
117
  const unsigned Line = SourceMgr.getLineNumber(File, Offset);
346
117
  CommentBeginLine[C] = Line;
347
117
  return Line;
348
137
}
349
350
5.81k
unsigned RawCommentList::getCommentEndOffset(RawComment *C) const {
351
5.81k
  auto Cached = CommentEndOffset.find(C);
352
5.81k
  if (Cached != CommentEndOffset.end())
353
2.68k
    return Cached->second;
354
3.13k
  const unsigned Offset =
355
3.13k
      SourceMgr.getDecomposedLoc(C->getSourceRange().getEnd()).second;
356
3.13k
  CommentEndOffset[C] = Offset;
357
3.13k
  return Offset;
358
5.81k
}
359
360
std::string RawComment::getFormattedText(const SourceManager &SourceMgr,
361
7
                                         DiagnosticsEngine &Diags) const {
362
7
  llvm::StringRef CommentText = getRawText(SourceMgr);
363
7
  if (CommentText.empty())
364
0
    return "";
365
366
7
  std::string Result;
367
7
  for (const RawComment::CommentLine &Line :
368
7
       getFormattedLines(SourceMgr, Diags))
369
29
    Result += Line.Text + "\n";
370
371
7
  auto LastChar = Result.find_last_not_of('\n');
372
7
  Result.erase(LastChar + 1, Result.size());
373
374
7
  return Result;
375
7
}
376
377
std::vector<RawComment::CommentLine>
378
RawComment::getFormattedLines(const SourceManager &SourceMgr,
379
13
                              DiagnosticsEngine &Diags) const {
380
13
  llvm::StringRef CommentText = getRawText(SourceMgr);
381
13
  if (CommentText.empty())
382
0
    return {};
383
384
13
  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
13
  CommentOptions DefOpts;
388
13
  comments::CommandTraits EmptyTraits(Allocator, DefOpts);
389
13
  comments::Lexer L(Allocator, Diags, EmptyTraits, getSourceRange().getBegin(),
390
13
                    CommentText.begin(), CommentText.end(),
391
13
                    /*ParseCommands=*/false);
392
393
13
  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
13
  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
13
  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
62
  auto LexLine = [&](bool IsFirstLine) -> bool {
414
62
    comments::Token Tok;
415
    // Lex the first token on the line. We handle it separately, because we to
416
    // fix up its indentation.
417
62
    L.lex(Tok);
418
62
    if (Tok.is(comments::tok::eof))
419
13
      return false;
420
49
    if (Tok.is(comments::tok::newline)) {
421
7
      PresumedLoc Loc = SourceMgr.getPresumedLoc(Tok.getLocation());
422
7
      if (Loc.getLine() != PreviousLine) {
423
3
        Result.emplace_back("", Loc, Loc);
424
3
        PreviousLine = Loc.getLine();
425
3
      }
426
7
      return true;
427
7
    }
428
42
    SmallString<124> Line;
429
42
    llvm::StringRef TokText = L.getSpelling(Tok, SourceMgr);
430
42
    bool LocInvalid = false;
431
42
    unsigned TokColumn =
432
42
        SourceMgr.getSpellingColumnNumber(Tok.getLocation(), &LocInvalid);
433
42
    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
42
    if (WhitespaceLen == StringRef::npos)
438
2
      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
42
    if (IsFirstLine)
442
10
      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
42
    unsigned SkipLen =
448
42
        IsFirstLine
449
42
            ? 
WhitespaceLen10
450
42
            : std::min<size_t>(
451
32
                  WhitespaceLen,
452
32
                  std::max<int>(static_cast<int>(IndentColumn) - TokColumn, 0));
453
42
    llvm::StringRef Trimmed = TokText.drop_front(SkipLen);
454
42
    Line += Trimmed;
455
    // Get the beginning location of the adjusted comment line.
456
42
    PresumedLoc Begin =
457
42
        SourceMgr.getPresumedLoc(Tok.getLocation().getLocWithOffset(SkipLen));
458
459
    // Lex all tokens in the rest of the line.
460
42
    for (L.lex(Tok); Tok.isNot(comments::tok::eof); 
L.lex(Tok)0
) {
461
42
      if (Tok.is(comments::tok::newline)) {
462
        // Get the ending location of the comment line.
463
42
        PresumedLoc End = SourceMgr.getPresumedLoc(Tok.getLocation());
464
42
        if (End.getLine() != PreviousLine) {
465
42
          Result.emplace_back(Line, Begin, End);
466
42
          PreviousLine = End.getLine();
467
42
        }
468
42
        return true;
469
42
      }
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
42
  };
477
478
  // Process first line separately to remember indent for the following lines.
479
13
  if (!LexLine(/*IsFirstLine=*/true))
480
0
    return Result;
481
  // Process the rest of the lines.
482
49
  
while (13
LexLine(/*IsFirstLine=*/false))
483
36
    ;
484
13
  return Result;
485
13
}