1//===--- CommentSema.cpp - Doxygen comment semantic analysis --------------===//
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/CommentSema.h"
10#include "clang/AST/Attr.h"
11#include "clang/AST/CommentCommandTraits.h"
12#include "clang/AST/CommentDiagnostic.h"
13#include "clang/AST/Decl.h"
14#include "clang/AST/DeclTemplate.h"
15#include "clang/Basic/LLVM.h"
16#include "clang/Basic/SourceManager.h"
17#include "clang/Lex/Preprocessor.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/ADT/StringSwitch.h"
20
21namespace clang {
22namespace comments {
23
24namespace {
25#include "clang/AST/CommentHTMLTagsProperties.inc"
26} // end anonymous namespace
27
28Sema::Sema(llvm::BumpPtrAllocator &Allocator, const SourceManager &SourceMgr,
29 DiagnosticsEngine &Diags, CommandTraits &Traits,
30 const Preprocessor *PP) :
31 Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags), Traits(Traits),
32 PP(PP), ThisDeclInfo(nullptr), BriefCommand(nullptr),
33 HeaderfileCommand(nullptr) {
34}
35
36void Sema::setDecl(const Decl *D) {
37 if (!D)
38 return;
39
40 ThisDeclInfo = new (Allocator) DeclInfo;
41 ThisDeclInfo->CommentDecl = D;
42 ThisDeclInfo->IsFilled = false;
43}
44
45ParagraphComment *Sema::actOnParagraphComment(
46 ArrayRef<InlineContentComment *> Content) {
47 return new (Allocator) ParagraphComment(Content);
48}
49
50BlockCommandComment *Sema::actOnBlockCommandStart(
51 SourceLocation LocBegin,
52 SourceLocation LocEnd,
53 unsigned CommandID,
54 CommandMarkerKind CommandMarker) {
55 BlockCommandComment *BC = new (Allocator) BlockCommandComment(LocBegin, LocEnd,
56 CommandID,
57 CommandMarker);
58 checkContainerDecl(Comment: BC);
59 return BC;
60}
61
62void Sema::actOnBlockCommandArgs(BlockCommandComment *Command,
63 ArrayRef<BlockCommandComment::Argument> Args) {
64 Command->setArgs(Args);
65}
66
67void Sema::actOnBlockCommandFinish(BlockCommandComment *Command,
68 ParagraphComment *Paragraph) {
69 Command->setParagraph(Paragraph);
70 checkBlockCommandEmptyParagraph(Command);
71 checkBlockCommandDuplicate(Command);
72 if (ThisDeclInfo) {
73 // These checks only make sense if the comment is attached to a
74 // declaration.
75 checkReturnsCommand(Command);
76 checkDeprecatedCommand(Comment: Command);
77 }
78}
79
80ParamCommandComment *Sema::actOnParamCommandStart(
81 SourceLocation LocBegin,
82 SourceLocation LocEnd,
83 unsigned CommandID,
84 CommandMarkerKind CommandMarker) {
85 ParamCommandComment *Command =
86 new (Allocator) ParamCommandComment(LocBegin, LocEnd, CommandID,
87 CommandMarker);
88
89 if (!involvesFunctionType())
90 Diag(Loc: Command->getLocation(),
91 DiagID: diag::warn_doc_param_not_attached_to_a_function_decl)
92 << CommandMarker
93 << Command->getCommandNameRange(Traits);
94
95 return Command;
96}
97
98void Sema::checkFunctionDeclVerbatimLine(const BlockCommandComment *Comment) {
99 const CommandInfo *Info = Traits.getCommandInfo(CommandID: Comment->getCommandID());
100 if (!Info->IsFunctionDeclarationCommand)
101 return;
102
103 unsigned DiagSelect;
104 switch (Comment->getCommandID()) {
105 case CommandTraits::KCI_function:
106 DiagSelect = (!isAnyFunctionDecl() && !isFunctionTemplateDecl())? 1 : 0;
107 break;
108 case CommandTraits::KCI_functiongroup:
109 DiagSelect = (!isAnyFunctionDecl() && !isFunctionTemplateDecl())? 2 : 0;
110 break;
111 case CommandTraits::KCI_method:
112 DiagSelect = !isObjCMethodDecl() ? 3 : 0;
113 break;
114 case CommandTraits::KCI_methodgroup:
115 DiagSelect = !isObjCMethodDecl() ? 4 : 0;
116 break;
117 case CommandTraits::KCI_callback:
118 DiagSelect = !isFunctionPointerVarDecl() ? 5 : 0;
119 break;
120 default:
121 DiagSelect = 0;
122 break;
123 }
124 if (DiagSelect)
125 Diag(Loc: Comment->getLocation(), DiagID: diag::warn_doc_function_method_decl_mismatch)
126 << Comment->getCommandMarker()
127 << (DiagSelect-1) << (DiagSelect-1)
128 << Comment->getSourceRange();
129}
130
131void Sema::checkContainerDeclVerbatimLine(const BlockCommandComment *Comment) {
132 const CommandInfo *Info = Traits.getCommandInfo(CommandID: Comment->getCommandID());
133 if (!Info->IsRecordLikeDeclarationCommand)
134 return;
135 unsigned DiagSelect;
136 switch (Comment->getCommandID()) {
137 case CommandTraits::KCI_class:
138 DiagSelect =
139 (!isClassOrStructOrTagTypedefDecl() && !isClassTemplateDecl()) ? 1
140 : 0;
141 // Allow @class command on @interface declarations.
142 // FIXME. Currently, \class and @class are indistinguishable. So,
143 // \class is also allowed on an @interface declaration
144 if (DiagSelect && Comment->getCommandMarker() && isObjCInterfaceDecl())
145 DiagSelect = 0;
146 break;
147 case CommandTraits::KCI_interface:
148 DiagSelect = !isObjCInterfaceDecl() ? 2 : 0;
149 break;
150 case CommandTraits::KCI_protocol:
151 DiagSelect = !isObjCProtocolDecl() ? 3 : 0;
152 break;
153 case CommandTraits::KCI_struct:
154 DiagSelect = !isClassOrStructOrTagTypedefDecl() ? 4 : 0;
155 break;
156 case CommandTraits::KCI_union:
157 DiagSelect = !isUnionDecl() ? 5 : 0;
158 break;
159 default:
160 DiagSelect = 0;
161 break;
162 }
163 if (DiagSelect)
164 Diag(Loc: Comment->getLocation(), DiagID: diag::warn_doc_api_container_decl_mismatch)
165 << Comment->getCommandMarker()
166 << (DiagSelect-1) << (DiagSelect-1)
167 << Comment->getSourceRange();
168}
169
170void Sema::checkContainerDecl(const BlockCommandComment *Comment) {
171 const CommandInfo *Info = Traits.getCommandInfo(CommandID: Comment->getCommandID());
172 if (!Info->IsRecordLikeDetailCommand || isRecordLikeDecl())
173 return;
174 unsigned DiagSelect;
175 switch (Comment->getCommandID()) {
176 case CommandTraits::KCI_classdesign:
177 DiagSelect = 1;
178 break;
179 case CommandTraits::KCI_coclass:
180 DiagSelect = 2;
181 break;
182 case CommandTraits::KCI_dependency:
183 DiagSelect = 3;
184 break;
185 case CommandTraits::KCI_helper:
186 DiagSelect = 4;
187 break;
188 case CommandTraits::KCI_helperclass:
189 DiagSelect = 5;
190 break;
191 case CommandTraits::KCI_helps:
192 DiagSelect = 6;
193 break;
194 case CommandTraits::KCI_instancesize:
195 DiagSelect = 7;
196 break;
197 case CommandTraits::KCI_ownership:
198 DiagSelect = 8;
199 break;
200 case CommandTraits::KCI_performance:
201 DiagSelect = 9;
202 break;
203 case CommandTraits::KCI_security:
204 DiagSelect = 10;
205 break;
206 case CommandTraits::KCI_superclass:
207 DiagSelect = 11;
208 break;
209 default:
210 DiagSelect = 0;
211 break;
212 }
213 if (DiagSelect)
214 Diag(Loc: Comment->getLocation(), DiagID: diag::warn_doc_container_decl_mismatch)
215 << Comment->getCommandMarker()
216 << (DiagSelect-1)
217 << Comment->getSourceRange();
218}
219
220/// Turn a string into the corresponding PassDirection or -1 if it's not
221/// valid.
222static ParamCommandPassDirection getParamPassDirection(StringRef Arg) {
223 return llvm::StringSwitch<ParamCommandPassDirection>(Arg)
224 .Case(S: "[in]", Value: ParamCommandPassDirection::In)
225 .Case(S: "[out]", Value: ParamCommandPassDirection::Out)
226 .Cases(S0: "[in,out]", S1: "[out,in]", Value: ParamCommandPassDirection::InOut)
227 .Default(Value: static_cast<ParamCommandPassDirection>(-1));
228}
229
230void Sema::actOnParamCommandDirectionArg(ParamCommandComment *Command,
231 SourceLocation ArgLocBegin,
232 SourceLocation ArgLocEnd,
233 StringRef Arg) {
234 std::string ArgLower = Arg.lower();
235 ParamCommandPassDirection Direction = getParamPassDirection(Arg: ArgLower);
236
237 if (Direction == static_cast<ParamCommandPassDirection>(-1)) {
238 // Try again with whitespace removed.
239 llvm::erase_if(C&: ArgLower, P: clang::isWhitespace);
240 Direction = getParamPassDirection(Arg: ArgLower);
241
242 SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
243 if (Direction != static_cast<ParamCommandPassDirection>(-1)) {
244 const char *FixedName =
245 ParamCommandComment::getDirectionAsString(D: Direction);
246 Diag(Loc: ArgLocBegin, DiagID: diag::warn_doc_param_spaces_in_direction)
247 << ArgRange << FixItHint::CreateReplacement(RemoveRange: ArgRange, Code: FixedName);
248 } else {
249 Diag(Loc: ArgLocBegin, DiagID: diag::warn_doc_param_invalid_direction) << ArgRange;
250 Direction = ParamCommandPassDirection::In; // Sane fall back.
251 }
252 }
253 Command->setDirection(Direction,
254 /*Explicit=*/true);
255}
256
257void Sema::actOnParamCommandParamNameArg(ParamCommandComment *Command,
258 SourceLocation ArgLocBegin,
259 SourceLocation ArgLocEnd,
260 StringRef Arg) {
261 // Parser will not feed us more arguments than needed.
262 assert(Command->getNumArgs() == 0);
263
264 if (!Command->isDirectionExplicit()) {
265 // User didn't provide a direction argument.
266 Command->setDirection(Direction: ParamCommandPassDirection::In,
267 /* Explicit = */ false);
268 }
269 auto *A = new (Allocator)
270 Comment::Argument{.Range: SourceRange(ArgLocBegin, ArgLocEnd), .Text: Arg};
271 Command->setArgs(llvm::ArrayRef(A, 1));
272}
273
274void Sema::actOnParamCommandFinish(ParamCommandComment *Command,
275 ParagraphComment *Paragraph) {
276 Command->setParagraph(Paragraph);
277 checkBlockCommandEmptyParagraph(Command);
278}
279
280TParamCommandComment *Sema::actOnTParamCommandStart(
281 SourceLocation LocBegin,
282 SourceLocation LocEnd,
283 unsigned CommandID,
284 CommandMarkerKind CommandMarker) {
285 TParamCommandComment *Command =
286 new (Allocator) TParamCommandComment(LocBegin, LocEnd, CommandID,
287 CommandMarker);
288
289 if (!isTemplateOrSpecialization())
290 Diag(Loc: Command->getLocation(),
291 DiagID: diag::warn_doc_tparam_not_attached_to_a_template_decl)
292 << CommandMarker
293 << Command->getCommandNameRange(Traits);
294
295 return Command;
296}
297
298void Sema::actOnTParamCommandParamNameArg(TParamCommandComment *Command,
299 SourceLocation ArgLocBegin,
300 SourceLocation ArgLocEnd,
301 StringRef Arg) {
302 // Parser will not feed us more arguments than needed.
303 assert(Command->getNumArgs() == 0);
304
305 auto *A = new (Allocator)
306 Comment::Argument{.Range: SourceRange(ArgLocBegin, ArgLocEnd), .Text: Arg};
307 Command->setArgs(llvm::ArrayRef(A, 1));
308
309 if (!isTemplateOrSpecialization()) {
310 // We already warned that this \\tparam is not attached to a template decl.
311 return;
312 }
313
314 const TemplateParameterList *TemplateParameters =
315 ThisDeclInfo->TemplateParameters;
316 SmallVector<unsigned, 2> Position;
317 if (resolveTParamReference(Name: Arg, TemplateParameters, Position: &Position)) {
318 Command->setPosition(copyArray(Source: llvm::ArrayRef(Position)));
319 TParamCommandComment *&PrevCommand = TemplateParameterDocs[Arg];
320 if (PrevCommand) {
321 SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
322 Diag(Loc: ArgLocBegin, DiagID: diag::warn_doc_tparam_duplicate)
323 << Arg << ArgRange;
324 Diag(Loc: PrevCommand->getLocation(), DiagID: diag::note_doc_tparam_previous)
325 << PrevCommand->getParamNameRange();
326 }
327 PrevCommand = Command;
328 return;
329 }
330
331 SourceRange ArgRange(ArgLocBegin, ArgLocEnd);
332 Diag(Loc: ArgLocBegin, DiagID: diag::warn_doc_tparam_not_found)
333 << Arg << ArgRange;
334
335 if (!TemplateParameters || TemplateParameters->size() == 0)
336 return;
337
338 StringRef CorrectedName;
339 if (TemplateParameters->size() == 1) {
340 const NamedDecl *Param = TemplateParameters->getParam(Idx: 0);
341 const IdentifierInfo *II = Param->getIdentifier();
342 if (II)
343 CorrectedName = II->getName();
344 } else {
345 CorrectedName = correctTypoInTParamReference(Typo: Arg, TemplateParameters);
346 }
347
348 if (!CorrectedName.empty()) {
349 Diag(Loc: ArgLocBegin, DiagID: diag::note_doc_tparam_name_suggestion)
350 << CorrectedName
351 << FixItHint::CreateReplacement(RemoveRange: ArgRange, Code: CorrectedName);
352 }
353}
354
355void Sema::actOnTParamCommandFinish(TParamCommandComment *Command,
356 ParagraphComment *Paragraph) {
357 Command->setParagraph(Paragraph);
358 checkBlockCommandEmptyParagraph(Command);
359}
360
361InlineCommandComment *
362Sema::actOnInlineCommand(SourceLocation CommandLocBegin,
363 SourceLocation CommandLocEnd, unsigned CommandID,
364 ArrayRef<Comment::Argument> Args) {
365 StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
366
367 return new (Allocator)
368 InlineCommandComment(CommandLocBegin, CommandLocEnd, CommandID,
369 getInlineCommandRenderKind(Name: CommandName), Args);
370}
371
372InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
373 SourceLocation LocEnd,
374 StringRef CommandName) {
375 unsigned CommandID = Traits.registerUnknownCommand(CommandName)->getID();
376 return actOnUnknownCommand(LocBegin, LocEnd, CommandID);
377}
378
379InlineContentComment *Sema::actOnUnknownCommand(SourceLocation LocBegin,
380 SourceLocation LocEnd,
381 unsigned CommandID) {
382 ArrayRef<InlineCommandComment::Argument> Args;
383 return new (Allocator) InlineCommandComment(
384 LocBegin, LocEnd, CommandID, InlineCommandRenderKind::Normal, Args);
385}
386
387TextComment *Sema::actOnText(SourceLocation LocBegin,
388 SourceLocation LocEnd,
389 StringRef Text) {
390 return new (Allocator) TextComment(LocBegin, LocEnd, Text);
391}
392
393VerbatimBlockComment *Sema::actOnVerbatimBlockStart(SourceLocation Loc,
394 unsigned CommandID) {
395 StringRef CommandName = Traits.getCommandInfo(CommandID)->Name;
396 return new (Allocator) VerbatimBlockComment(
397 Loc,
398 Loc.getLocWithOffset(Offset: 1 + CommandName.size()),
399 CommandID);
400}
401
402VerbatimBlockLineComment *Sema::actOnVerbatimBlockLine(SourceLocation Loc,
403 StringRef Text) {
404 return new (Allocator) VerbatimBlockLineComment(Loc, Text);
405}
406
407void Sema::actOnVerbatimBlockFinish(
408 VerbatimBlockComment *Block,
409 SourceLocation CloseNameLocBegin,
410 StringRef CloseName,
411 ArrayRef<VerbatimBlockLineComment *> Lines) {
412 Block->setCloseName(Name: CloseName, LocBegin: CloseNameLocBegin);
413 Block->setLines(Lines);
414}
415
416VerbatimLineComment *Sema::actOnVerbatimLine(SourceLocation LocBegin,
417 unsigned CommandID,
418 SourceLocation TextBegin,
419 StringRef Text) {
420 VerbatimLineComment *VL = new (Allocator) VerbatimLineComment(
421 LocBegin,
422 TextBegin.getLocWithOffset(Offset: Text.size()),
423 CommandID,
424 TextBegin,
425 Text);
426 checkFunctionDeclVerbatimLine(Comment: VL);
427 checkContainerDeclVerbatimLine(Comment: VL);
428 return VL;
429}
430
431HTMLStartTagComment *Sema::actOnHTMLStartTagStart(SourceLocation LocBegin,
432 StringRef TagName) {
433 return new (Allocator) HTMLStartTagComment(LocBegin, TagName);
434}
435
436void Sema::actOnHTMLStartTagFinish(
437 HTMLStartTagComment *Tag,
438 ArrayRef<HTMLStartTagComment::Attribute> Attrs,
439 SourceLocation GreaterLoc,
440 bool IsSelfClosing) {
441 Tag->setAttrs(Attrs);
442 Tag->setGreaterLoc(GreaterLoc);
443 if (IsSelfClosing)
444 Tag->setSelfClosing();
445 else if (!isHTMLEndTagForbidden(Name: Tag->getTagName()))
446 HTMLOpenTags.push_back(Elt: Tag);
447}
448
449HTMLEndTagComment *Sema::actOnHTMLEndTag(SourceLocation LocBegin,
450 SourceLocation LocEnd,
451 StringRef TagName) {
452 HTMLEndTagComment *HET =
453 new (Allocator) HTMLEndTagComment(LocBegin, LocEnd, TagName);
454 if (isHTMLEndTagForbidden(Name: TagName)) {
455 Diag(Loc: HET->getLocation(), DiagID: diag::warn_doc_html_end_forbidden)
456 << TagName << HET->getSourceRange();
457 HET->setIsMalformed();
458 return HET;
459 }
460
461 bool FoundOpen = false;
462 for (SmallVectorImpl<HTMLStartTagComment *>::const_reverse_iterator
463 I = HTMLOpenTags.rbegin(), E = HTMLOpenTags.rend();
464 I != E; ++I) {
465 if ((*I)->getTagName() == TagName) {
466 FoundOpen = true;
467 break;
468 }
469 }
470 if (!FoundOpen) {
471 Diag(Loc: HET->getLocation(), DiagID: diag::warn_doc_html_end_unbalanced)
472 << HET->getSourceRange();
473 HET->setIsMalformed();
474 return HET;
475 }
476
477 while (!HTMLOpenTags.empty()) {
478 HTMLStartTagComment *HST = HTMLOpenTags.pop_back_val();
479 StringRef LastNotClosedTagName = HST->getTagName();
480 if (LastNotClosedTagName == TagName) {
481 // If the start tag is malformed, end tag is malformed as well.
482 if (HST->isMalformed())
483 HET->setIsMalformed();
484 break;
485 }
486
487 if (isHTMLEndTagOptional(Name: LastNotClosedTagName))
488 continue;
489
490 bool OpenLineInvalid;
491 const unsigned OpenLine = SourceMgr.getPresumedLineNumber(
492 Loc: HST->getLocation(),
493 Invalid: &OpenLineInvalid);
494 bool CloseLineInvalid;
495 const unsigned CloseLine = SourceMgr.getPresumedLineNumber(
496 Loc: HET->getLocation(),
497 Invalid: &CloseLineInvalid);
498
499 if (OpenLineInvalid || CloseLineInvalid || OpenLine == CloseLine) {
500 Diag(Loc: HST->getLocation(), DiagID: diag::warn_doc_html_start_end_mismatch)
501 << HST->getTagName() << HET->getTagName()
502 << HST->getSourceRange() << HET->getSourceRange();
503 HST->setIsMalformed();
504 } else {
505 Diag(Loc: HST->getLocation(), DiagID: diag::warn_doc_html_start_end_mismatch)
506 << HST->getTagName() << HET->getTagName()
507 << HST->getSourceRange();
508 Diag(Loc: HET->getLocation(), DiagID: diag::note_doc_html_end_tag)
509 << HET->getSourceRange();
510 HST->setIsMalformed();
511 }
512 }
513
514 return HET;
515}
516
517FullComment *Sema::actOnFullComment(
518 ArrayRef<BlockContentComment *> Blocks) {
519 FullComment *FC = new (Allocator) FullComment(Blocks, ThisDeclInfo);
520 resolveParamCommandIndexes(FC);
521
522 // Complain about HTML tags that are not closed.
523 while (!HTMLOpenTags.empty()) {
524 HTMLStartTagComment *HST = HTMLOpenTags.pop_back_val();
525 if (isHTMLEndTagOptional(Name: HST->getTagName()))
526 continue;
527
528 Diag(Loc: HST->getLocation(), DiagID: diag::warn_doc_html_missing_end_tag)
529 << HST->getTagName() << HST->getSourceRange();
530 HST->setIsMalformed();
531 }
532
533 return FC;
534}
535
536void Sema::checkBlockCommandEmptyParagraph(BlockCommandComment *Command) {
537 if (Traits.getCommandInfo(CommandID: Command->getCommandID())->IsEmptyParagraphAllowed)
538 return;
539
540 ParagraphComment *Paragraph = Command->getParagraph();
541 if (Paragraph->isWhitespace()) {
542 SourceLocation DiagLoc;
543 if (Command->getNumArgs() > 0)
544 DiagLoc = Command->getArgRange(Idx: Command->getNumArgs() - 1).getEnd();
545 if (!DiagLoc.isValid())
546 DiagLoc = Command->getCommandNameRange(Traits).getEnd();
547 Diag(Loc: DiagLoc, DiagID: diag::warn_doc_block_command_empty_paragraph)
548 << Command->getCommandMarker()
549 << Command->getCommandName(Traits)
550 << Command->getSourceRange();
551 }
552}
553
554void Sema::checkReturnsCommand(const BlockCommandComment *Command) {
555 if (!Traits.getCommandInfo(CommandID: Command->getCommandID())->IsReturnsCommand)
556 return;
557
558 assert(ThisDeclInfo && "should not call this check on a bare comment");
559
560 // We allow the return command for all @properties because it can be used
561 // to document the value that the property getter returns.
562 if (isObjCPropertyDecl())
563 return;
564 if (involvesFunctionType()) {
565 assert(!ThisDeclInfo->ReturnType.isNull() &&
566 "should have a valid return type");
567 if (ThisDeclInfo->ReturnType->isVoidType()) {
568 unsigned DiagKind;
569 switch (ThisDeclInfo->CommentDecl->getKind()) {
570 default:
571 if (ThisDeclInfo->IsObjCMethod)
572 DiagKind = 3;
573 else
574 DiagKind = 0;
575 break;
576 case Decl::CXXConstructor:
577 DiagKind = 1;
578 break;
579 case Decl::CXXDestructor:
580 DiagKind = 2;
581 break;
582 }
583 Diag(Loc: Command->getLocation(),
584 DiagID: diag::warn_doc_returns_attached_to_a_void_function)
585 << Command->getCommandMarker()
586 << Command->getCommandName(Traits)
587 << DiagKind
588 << Command->getSourceRange();
589 }
590 return;
591 }
592
593 Diag(Loc: Command->getLocation(),
594 DiagID: diag::warn_doc_returns_not_attached_to_a_function_decl)
595 << Command->getCommandMarker()
596 << Command->getCommandName(Traits)
597 << Command->getSourceRange();
598}
599
600void Sema::checkBlockCommandDuplicate(const BlockCommandComment *Command) {
601 const CommandInfo *Info = Traits.getCommandInfo(CommandID: Command->getCommandID());
602 const BlockCommandComment *PrevCommand = nullptr;
603 if (Info->IsBriefCommand) {
604 if (!BriefCommand) {
605 BriefCommand = Command;
606 return;
607 }
608 PrevCommand = BriefCommand;
609 } else if (Info->IsHeaderfileCommand) {
610 if (!HeaderfileCommand) {
611 HeaderfileCommand = Command;
612 return;
613 }
614 PrevCommand = HeaderfileCommand;
615 } else {
616 // We don't want to check this command for duplicates.
617 return;
618 }
619 StringRef CommandName = Command->getCommandName(Traits);
620 StringRef PrevCommandName = PrevCommand->getCommandName(Traits);
621 Diag(Loc: Command->getLocation(), DiagID: diag::warn_doc_block_command_duplicate)
622 << Command->getCommandMarker()
623 << CommandName
624 << Command->getSourceRange();
625 if (CommandName == PrevCommandName)
626 Diag(Loc: PrevCommand->getLocation(), DiagID: diag::note_doc_block_command_previous)
627 << PrevCommand->getCommandMarker()
628 << PrevCommandName
629 << PrevCommand->getSourceRange();
630 else
631 Diag(Loc: PrevCommand->getLocation(),
632 DiagID: diag::note_doc_block_command_previous_alias)
633 << PrevCommand->getCommandMarker()
634 << PrevCommandName
635 << CommandName;
636}
637
638void Sema::checkDeprecatedCommand(const BlockCommandComment *Command) {
639 if (!Traits.getCommandInfo(CommandID: Command->getCommandID())->IsDeprecatedCommand)
640 return;
641
642 assert(ThisDeclInfo && "should not call this check on a bare comment");
643
644 const Decl *D = ThisDeclInfo->CommentDecl;
645 if (!D)
646 return;
647
648 if (D->hasAttr<DeprecatedAttr>() ||
649 D->hasAttr<AvailabilityAttr>() ||
650 D->hasAttr<UnavailableAttr>())
651 return;
652
653 Diag(Loc: Command->getLocation(), DiagID: diag::warn_doc_deprecated_not_sync)
654 << Command->getSourceRange() << Command->getCommandMarker();
655
656 // Try to emit a fixit with a deprecation attribute.
657 if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Val: D)) {
658 // Don't emit a Fix-It for non-member function definitions. GCC does not
659 // accept attributes on them.
660 const DeclContext *Ctx = FD->getDeclContext();
661 if ((!Ctx || !Ctx->isRecord()) &&
662 FD->doesThisDeclarationHaveABody())
663 return;
664
665 const LangOptions &LO = FD->getLangOpts();
666 const bool DoubleSquareBracket = LO.CPlusPlus14 || LO.C23;
667 StringRef AttributeSpelling =
668 DoubleSquareBracket ? "[[deprecated]]" : "__attribute__((deprecated))";
669 if (PP) {
670 // Try to find a replacement macro:
671 // - In C23/C++14 we prefer [[deprecated]].
672 // - If not found or an older C/C++ look for __attribute__((deprecated)).
673 StringRef MacroName;
674 if (DoubleSquareBracket) {
675 TokenValue Tokens[] = {tok::l_square, tok::l_square,
676 PP->getIdentifierInfo(Name: "deprecated"),
677 tok::r_square, tok::r_square};
678 MacroName = PP->getLastMacroWithSpelling(Loc: FD->getLocation(), Tokens);
679 if (!MacroName.empty())
680 AttributeSpelling = MacroName;
681 }
682
683 if (MacroName.empty()) {
684 TokenValue Tokens[] = {
685 tok::kw___attribute, tok::l_paren,
686 tok::l_paren, PP->getIdentifierInfo(Name: "deprecated"),
687 tok::r_paren, tok::r_paren};
688 StringRef MacroName =
689 PP->getLastMacroWithSpelling(Loc: FD->getLocation(), Tokens);
690 if (!MacroName.empty())
691 AttributeSpelling = MacroName;
692 }
693 }
694
695 SmallString<64> TextToInsert = AttributeSpelling;
696 TextToInsert += " ";
697 SourceLocation Loc = FD->getSourceRange().getBegin();
698 Diag(Loc, DiagID: diag::note_add_deprecation_attr)
699 << FixItHint::CreateInsertion(InsertionLoc: Loc, Code: TextToInsert);
700 }
701}
702
703void Sema::resolveParamCommandIndexes(const FullComment *FC) {
704 if (!involvesFunctionType()) {
705 // We already warned that \\param commands are not attached to a function
706 // decl.
707 return;
708 }
709
710 SmallVector<ParamCommandComment *, 8> UnresolvedParamCommands;
711
712 // Comment AST nodes that correspond to \c ParamVars for which we have
713 // found a \\param command or NULL if no documentation was found so far.
714 SmallVector<ParamCommandComment *, 8> ParamVarDocs;
715
716 ArrayRef<const ParmVarDecl *> ParamVars = getParamVars();
717 ParamVarDocs.resize(N: ParamVars.size(), NV: nullptr);
718
719 // First pass over all \\param commands: resolve all parameter names.
720 for (Comment::child_iterator I = FC->child_begin(), E = FC->child_end();
721 I != E; ++I) {
722 ParamCommandComment *PCC = dyn_cast<ParamCommandComment>(Val: *I);
723 if (!PCC || !PCC->hasParamName())
724 continue;
725 StringRef ParamName = PCC->getParamNameAsWritten();
726
727 // Check that referenced parameter name is in the function decl.
728 const unsigned ResolvedParamIndex = resolveParmVarReference(Name: ParamName,
729 ParamVars);
730 if (ResolvedParamIndex == ParamCommandComment::VarArgParamIndex) {
731 PCC->setIsVarArgParam();
732 continue;
733 }
734 if (ResolvedParamIndex == ParamCommandComment::InvalidParamIndex) {
735 UnresolvedParamCommands.push_back(Elt: PCC);
736 continue;
737 }
738 PCC->setParamIndex(ResolvedParamIndex);
739 if (ParamVarDocs[ResolvedParamIndex]) {
740 SourceRange ArgRange = PCC->getParamNameRange();
741 Diag(Loc: ArgRange.getBegin(), DiagID: diag::warn_doc_param_duplicate)
742 << ParamName << ArgRange;
743 ParamCommandComment *PrevCommand = ParamVarDocs[ResolvedParamIndex];
744 Diag(Loc: PrevCommand->getLocation(), DiagID: diag::note_doc_param_previous)
745 << PrevCommand->getParamNameRange();
746 }
747 ParamVarDocs[ResolvedParamIndex] = PCC;
748 }
749
750 // Find parameter declarations that have no corresponding \\param.
751 SmallVector<const ParmVarDecl *, 8> OrphanedParamDecls;
752 for (unsigned i = 0, e = ParamVarDocs.size(); i != e; ++i) {
753 if (!ParamVarDocs[i])
754 OrphanedParamDecls.push_back(Elt: ParamVars[i]);
755 }
756
757 // Second pass over unresolved \\param commands: do typo correction.
758 // Suggest corrections from a set of parameter declarations that have no
759 // corresponding \\param.
760 for (unsigned i = 0, e = UnresolvedParamCommands.size(); i != e; ++i) {
761 const ParamCommandComment *PCC = UnresolvedParamCommands[i];
762
763 SourceRange ArgRange = PCC->getParamNameRange();
764 StringRef ParamName = PCC->getParamNameAsWritten();
765 Diag(Loc: ArgRange.getBegin(), DiagID: diag::warn_doc_param_not_found)
766 << ParamName << ArgRange;
767
768 // All parameters documented -- can't suggest a correction.
769 if (OrphanedParamDecls.size() == 0)
770 continue;
771
772 unsigned CorrectedParamIndex = ParamCommandComment::InvalidParamIndex;
773 if (OrphanedParamDecls.size() == 1) {
774 // If one parameter is not documented then that parameter is the only
775 // possible suggestion.
776 CorrectedParamIndex = 0;
777 } else {
778 // Do typo correction.
779 CorrectedParamIndex = correctTypoInParmVarReference(Typo: ParamName,
780 ParamVars: OrphanedParamDecls);
781 }
782 if (CorrectedParamIndex != ParamCommandComment::InvalidParamIndex) {
783 const ParmVarDecl *CorrectedPVD = OrphanedParamDecls[CorrectedParamIndex];
784 if (const IdentifierInfo *CorrectedII = CorrectedPVD->getIdentifier())
785 Diag(Loc: ArgRange.getBegin(), DiagID: diag::note_doc_param_name_suggestion)
786 << CorrectedII->getName()
787 << FixItHint::CreateReplacement(RemoveRange: ArgRange, Code: CorrectedII->getName());
788 }
789 }
790}
791
792bool Sema::involvesFunctionType() {
793 if (!ThisDeclInfo)
794 return false;
795 if (!ThisDeclInfo->IsFilled)
796 inspectThisDecl();
797 return ThisDeclInfo->involvesFunctionType();
798}
799
800bool Sema::isFunctionDecl() {
801 if (!ThisDeclInfo)
802 return false;
803 if (!ThisDeclInfo->IsFilled)
804 inspectThisDecl();
805 return ThisDeclInfo->getKind() == DeclInfo::FunctionKind;
806}
807
808bool Sema::isAnyFunctionDecl() {
809 return isFunctionDecl() && ThisDeclInfo->CurrentDecl &&
810 isa<FunctionDecl>(Val: ThisDeclInfo->CurrentDecl);
811}
812
813bool Sema::isFunctionOrMethodVariadic() {
814 if (!ThisDeclInfo)
815 return false;
816 if (!ThisDeclInfo->IsFilled)
817 inspectThisDecl();
818 return ThisDeclInfo->IsVariadic;
819}
820
821bool Sema::isObjCMethodDecl() {
822 return isFunctionDecl() && ThisDeclInfo->CurrentDecl &&
823 isa<ObjCMethodDecl>(Val: ThisDeclInfo->CurrentDecl);
824}
825
826bool Sema::isFunctionPointerVarDecl() {
827 if (!ThisDeclInfo)
828 return false;
829 if (!ThisDeclInfo->IsFilled)
830 inspectThisDecl();
831 if (ThisDeclInfo->getKind() == DeclInfo::VariableKind) {
832 if (const VarDecl *VD = dyn_cast_or_null<VarDecl>(Val: ThisDeclInfo->CurrentDecl)) {
833 QualType QT = VD->getType();
834 return QT->isFunctionPointerType();
835 }
836 }
837 return false;
838}
839
840bool Sema::isObjCPropertyDecl() {
841 if (!ThisDeclInfo)
842 return false;
843 if (!ThisDeclInfo->IsFilled)
844 inspectThisDecl();
845 return ThisDeclInfo->CurrentDecl->getKind() == Decl::ObjCProperty;
846}
847
848bool Sema::isTemplateOrSpecialization() {
849 if (!ThisDeclInfo)
850 return false;
851 if (!ThisDeclInfo->IsFilled)
852 inspectThisDecl();
853 return ThisDeclInfo->getTemplateKind() != DeclInfo::NotTemplate;
854}
855
856bool Sema::isRecordLikeDecl() {
857 if (!ThisDeclInfo)
858 return false;
859 if (!ThisDeclInfo->IsFilled)
860 inspectThisDecl();
861 return isUnionDecl() || isClassOrStructDecl() || isObjCInterfaceDecl() ||
862 isObjCProtocolDecl();
863}
864
865bool Sema::isUnionDecl() {
866 if (!ThisDeclInfo)
867 return false;
868 if (!ThisDeclInfo->IsFilled)
869 inspectThisDecl();
870 if (const RecordDecl *RD =
871 dyn_cast_or_null<RecordDecl>(Val: ThisDeclInfo->CurrentDecl))
872 return RD->isUnion();
873 return false;
874}
875static bool isClassOrStructDeclImpl(const Decl *D) {
876 if (auto *record = dyn_cast_or_null<RecordDecl>(Val: D))
877 return !record->isUnion();
878
879 return false;
880}
881
882bool Sema::isClassOrStructDecl() {
883 if (!ThisDeclInfo)
884 return false;
885 if (!ThisDeclInfo->IsFilled)
886 inspectThisDecl();
887
888 if (!ThisDeclInfo->CurrentDecl)
889 return false;
890
891 return isClassOrStructDeclImpl(D: ThisDeclInfo->CurrentDecl);
892}
893
894bool Sema::isClassOrStructOrTagTypedefDecl() {
895 if (!ThisDeclInfo)
896 return false;
897 if (!ThisDeclInfo->IsFilled)
898 inspectThisDecl();
899
900 if (!ThisDeclInfo->CurrentDecl)
901 return false;
902
903 if (isClassOrStructDeclImpl(D: ThisDeclInfo->CurrentDecl))
904 return true;
905
906 if (auto *ThisTypedefDecl = dyn_cast<TypedefDecl>(Val: ThisDeclInfo->CurrentDecl)) {
907 auto UnderlyingType = ThisTypedefDecl->getUnderlyingType();
908 if (auto ThisElaboratedType = dyn_cast<ElaboratedType>(Val&: UnderlyingType)) {
909 auto DesugaredType = ThisElaboratedType->desugar();
910 if (auto *DesugaredTypePtr = DesugaredType.getTypePtrOrNull()) {
911 if (auto *ThisRecordType = dyn_cast<RecordType>(Val: DesugaredTypePtr)) {
912 return isClassOrStructDeclImpl(D: ThisRecordType->getAsRecordDecl());
913 }
914 }
915 }
916 }
917
918 return false;
919}
920
921bool Sema::isClassTemplateDecl() {
922 if (!ThisDeclInfo)
923 return false;
924 if (!ThisDeclInfo->IsFilled)
925 inspectThisDecl();
926 return ThisDeclInfo->CurrentDecl &&
927 (isa<ClassTemplateDecl>(Val: ThisDeclInfo->CurrentDecl));
928}
929
930bool Sema::isFunctionTemplateDecl() {
931 if (!ThisDeclInfo)
932 return false;
933 if (!ThisDeclInfo->IsFilled)
934 inspectThisDecl();
935 return ThisDeclInfo->CurrentDecl &&
936 (isa<FunctionTemplateDecl>(Val: ThisDeclInfo->CurrentDecl));
937}
938
939bool Sema::isObjCInterfaceDecl() {
940 if (!ThisDeclInfo)
941 return false;
942 if (!ThisDeclInfo->IsFilled)
943 inspectThisDecl();
944 return ThisDeclInfo->CurrentDecl &&
945 isa<ObjCInterfaceDecl>(Val: ThisDeclInfo->CurrentDecl);
946}
947
948bool Sema::isObjCProtocolDecl() {
949 if (!ThisDeclInfo)
950 return false;
951 if (!ThisDeclInfo->IsFilled)
952 inspectThisDecl();
953 return ThisDeclInfo->CurrentDecl &&
954 isa<ObjCProtocolDecl>(Val: ThisDeclInfo->CurrentDecl);
955}
956
957ArrayRef<const ParmVarDecl *> Sema::getParamVars() {
958 if (!ThisDeclInfo->IsFilled)
959 inspectThisDecl();
960 return ThisDeclInfo->ParamVars;
961}
962
963void Sema::inspectThisDecl() {
964 ThisDeclInfo->fill();
965}
966
967unsigned Sema::resolveParmVarReference(StringRef Name,
968 ArrayRef<const ParmVarDecl *> ParamVars) {
969 for (unsigned i = 0, e = ParamVars.size(); i != e; ++i) {
970 const IdentifierInfo *II = ParamVars[i]->getIdentifier();
971 if (II && II->getName() == Name)
972 return i;
973 }
974 if (Name == "..." && isFunctionOrMethodVariadic())
975 return ParamCommandComment::VarArgParamIndex;
976 return ParamCommandComment::InvalidParamIndex;
977}
978
979namespace {
980class SimpleTypoCorrector {
981 const NamedDecl *BestDecl;
982
983 StringRef Typo;
984 const unsigned MaxEditDistance;
985
986 unsigned BestEditDistance;
987 unsigned BestIndex;
988 unsigned NextIndex;
989
990public:
991 explicit SimpleTypoCorrector(StringRef Typo)
992 : BestDecl(nullptr), Typo(Typo), MaxEditDistance((Typo.size() + 2) / 3),
993 BestEditDistance(MaxEditDistance + 1), BestIndex(0), NextIndex(0) {}
994
995 void addDecl(const NamedDecl *ND);
996
997 const NamedDecl *getBestDecl() const {
998 if (BestEditDistance > MaxEditDistance)
999 return nullptr;
1000
1001 return BestDecl;
1002 }
1003
1004 unsigned getBestDeclIndex() const {
1005 assert(getBestDecl());
1006 return BestIndex;
1007 }
1008};
1009
1010void SimpleTypoCorrector::addDecl(const NamedDecl *ND) {
1011 unsigned CurrIndex = NextIndex++;
1012
1013 const IdentifierInfo *II = ND->getIdentifier();
1014 if (!II)
1015 return;
1016
1017 StringRef Name = II->getName();
1018 unsigned MinPossibleEditDistance = abs(x: (int)Name.size() - (int)Typo.size());
1019 if (MinPossibleEditDistance > 0 &&
1020 Typo.size() / MinPossibleEditDistance < 3)
1021 return;
1022
1023 unsigned EditDistance = Typo.edit_distance(Other: Name, AllowReplacements: true, MaxEditDistance);
1024 if (EditDistance < BestEditDistance) {
1025 BestEditDistance = EditDistance;
1026 BestDecl = ND;
1027 BestIndex = CurrIndex;
1028 }
1029}
1030} // end anonymous namespace
1031
1032unsigned Sema::correctTypoInParmVarReference(
1033 StringRef Typo,
1034 ArrayRef<const ParmVarDecl *> ParamVars) {
1035 SimpleTypoCorrector Corrector(Typo);
1036 for (unsigned i = 0, e = ParamVars.size(); i != e; ++i)
1037 Corrector.addDecl(ND: ParamVars[i]);
1038 if (Corrector.getBestDecl())
1039 return Corrector.getBestDeclIndex();
1040 else
1041 return ParamCommandComment::InvalidParamIndex;
1042}
1043
1044namespace {
1045bool ResolveTParamReferenceHelper(
1046 StringRef Name,
1047 const TemplateParameterList *TemplateParameters,
1048 SmallVectorImpl<unsigned> *Position) {
1049 for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) {
1050 const NamedDecl *Param = TemplateParameters->getParam(Idx: i);
1051 const IdentifierInfo *II = Param->getIdentifier();
1052 if (II && II->getName() == Name) {
1053 Position->push_back(Elt: i);
1054 return true;
1055 }
1056
1057 if (const TemplateTemplateParmDecl *TTP =
1058 dyn_cast<TemplateTemplateParmDecl>(Val: Param)) {
1059 Position->push_back(Elt: i);
1060 if (ResolveTParamReferenceHelper(Name, TemplateParameters: TTP->getTemplateParameters(),
1061 Position))
1062 return true;
1063 Position->pop_back();
1064 }
1065 }
1066 return false;
1067}
1068} // end anonymous namespace
1069
1070bool Sema::resolveTParamReference(
1071 StringRef Name,
1072 const TemplateParameterList *TemplateParameters,
1073 SmallVectorImpl<unsigned> *Position) {
1074 Position->clear();
1075 if (!TemplateParameters)
1076 return false;
1077
1078 return ResolveTParamReferenceHelper(Name, TemplateParameters, Position);
1079}
1080
1081namespace {
1082void CorrectTypoInTParamReferenceHelper(
1083 const TemplateParameterList *TemplateParameters,
1084 SimpleTypoCorrector &Corrector) {
1085 for (unsigned i = 0, e = TemplateParameters->size(); i != e; ++i) {
1086 const NamedDecl *Param = TemplateParameters->getParam(Idx: i);
1087 Corrector.addDecl(ND: Param);
1088
1089 if (const TemplateTemplateParmDecl *TTP =
1090 dyn_cast<TemplateTemplateParmDecl>(Val: Param))
1091 CorrectTypoInTParamReferenceHelper(TemplateParameters: TTP->getTemplateParameters(),
1092 Corrector);
1093 }
1094}
1095} // end anonymous namespace
1096
1097StringRef Sema::correctTypoInTParamReference(
1098 StringRef Typo,
1099 const TemplateParameterList *TemplateParameters) {
1100 SimpleTypoCorrector Corrector(Typo);
1101 CorrectTypoInTParamReferenceHelper(TemplateParameters, Corrector);
1102 if (const NamedDecl *ND = Corrector.getBestDecl()) {
1103 const IdentifierInfo *II = ND->getIdentifier();
1104 assert(II && "SimpleTypoCorrector should not return this decl");
1105 return II->getName();
1106 }
1107 return StringRef();
1108}
1109
1110InlineCommandRenderKind Sema::getInlineCommandRenderKind(StringRef Name) const {
1111 assert(Traits.getCommandInfo(Name)->IsInlineCommand);
1112
1113 return llvm::StringSwitch<InlineCommandRenderKind>(Name)
1114 .Case(S: "b", Value: InlineCommandRenderKind::Bold)
1115 .Cases(S0: "c", S1: "p", Value: InlineCommandRenderKind::Monospaced)
1116 .Cases(S0: "a", S1: "e", S2: "em", Value: InlineCommandRenderKind::Emphasized)
1117 .Case(S: "anchor", Value: InlineCommandRenderKind::Anchor)
1118 .Default(Value: InlineCommandRenderKind::Normal);
1119}
1120
1121} // end namespace comments
1122} // end namespace clang
1123