clang-tools  14.0.0git
ArgumentCommentCheck.cpp
Go to the documentation of this file.
1 //===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===//
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 "ArgumentCommentCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
13 #include "clang/Lex/Token.h"
14 
15 #include "../utils/LexerUtils.h"
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace bugprone {
22 namespace {
23 AST_MATCHER(Decl, isFromStdNamespaceOrSystemHeader) {
24  if (const auto *D = Node.getDeclContext()->getEnclosingNamespaceContext())
25  if (D->isStdNamespace())
26  return true;
27  if (Node.getLocation().isInvalid())
28  return false;
29  return Node.getASTContext().getSourceManager().isInSystemHeader(
30  Node.getLocation());
31 }
32 } // namespace
33 
34 ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name,
35  ClangTidyContext *Context)
36  : ClangTidyCheck(Name, Context),
37  StrictMode(Options.getLocalOrGlobal("StrictMode", false)),
38  IgnoreSingleArgument(Options.get("IgnoreSingleArgument", false)),
39  CommentBoolLiterals(Options.get("CommentBoolLiterals", false)),
40  CommentIntegerLiterals(Options.get("CommentIntegerLiterals", false)),
41  CommentFloatLiterals(Options.get("CommentFloatLiterals", false)),
42  CommentStringLiterals(Options.get("CommentStringLiterals", false)),
43  CommentUserDefinedLiterals(
44  Options.get("CommentUserDefinedLiterals", false)),
45  CommentCharacterLiterals(Options.get("CommentCharacterLiterals", false)),
46  CommentNullPtrs(Options.get("CommentNullPtrs", false)),
47  IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
48 
50  Options.store(Opts, "StrictMode", StrictMode);
51  Options.store(Opts, "IgnoreSingleArgument", IgnoreSingleArgument);
52  Options.store(Opts, "CommentBoolLiterals", CommentBoolLiterals);
53  Options.store(Opts, "CommentIntegerLiterals", CommentIntegerLiterals);
54  Options.store(Opts, "CommentFloatLiterals", CommentFloatLiterals);
55  Options.store(Opts, "CommentStringLiterals", CommentStringLiterals);
56  Options.store(Opts, "CommentUserDefinedLiterals", CommentUserDefinedLiterals);
57  Options.store(Opts, "CommentCharacterLiterals", CommentCharacterLiterals);
58  Options.store(Opts, "CommentNullPtrs", CommentNullPtrs);
59 }
60 
61 void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) {
62  Finder->addMatcher(
63  callExpr(unless(cxxOperatorCallExpr()),
64  // NewCallback's arguments relate to the pointed function,
65  // don't check them against NewCallback's parameter names.
66  // FIXME: Make this configurable.
67  unless(hasDeclaration(functionDecl(
68  hasAnyName("NewCallback", "NewPermanentCallback")))),
69  // Ignore APIs from the standard library, since their names are
70  // not specified by the standard, and standard library
71  // implementations in practice have to use reserved names to
72  // avoid conflicts with same-named macros.
73  unless(hasDeclaration(isFromStdNamespaceOrSystemHeader())))
74  .bind("expr"),
75  this);
76  Finder->addMatcher(cxxConstructExpr(unless(hasDeclaration(
77  isFromStdNamespaceOrSystemHeader())))
78  .bind("expr"),
79  this);
80 }
81 
82 static std::vector<std::pair<SourceLocation, StringRef>>
83 getCommentsInRange(ASTContext *Ctx, CharSourceRange Range) {
84  std::vector<std::pair<SourceLocation, StringRef>> Comments;
85  auto &SM = Ctx->getSourceManager();
86  std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
87  EndLoc = SM.getDecomposedLoc(Range.getEnd());
88 
89  if (BeginLoc.first != EndLoc.first)
90  return Comments;
91 
92  bool Invalid = false;
93  StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
94  if (Invalid)
95  return Comments;
96 
97  const char *StrData = Buffer.data() + BeginLoc.second;
98 
99  Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
100  Buffer.begin(), StrData, Buffer.end());
101  TheLexer.SetCommentRetentionState(true);
102 
103  while (true) {
104  Token Tok;
105  if (TheLexer.LexFromRawLexer(Tok))
106  break;
107  if (Tok.getLocation() == Range.getEnd() || Tok.is(tok::eof))
108  break;
109 
110  if (Tok.is(tok::comment)) {
111  std::pair<FileID, unsigned> CommentLoc =
112  SM.getDecomposedLoc(Tok.getLocation());
113  assert(CommentLoc.first == BeginLoc.first);
114  Comments.emplace_back(
115  Tok.getLocation(),
116  StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
117  } else {
118  // Clear comments found before the different token, e.g. comma.
119  Comments.clear();
120  }
121  }
122 
123  return Comments;
124 }
125 
126 static std::vector<std::pair<SourceLocation, StringRef>>
127 getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc) {
128  std::vector<std::pair<SourceLocation, StringRef>> Comments;
129  while (Loc.isValid()) {
130  clang::Token Tok = utils::lexer::getPreviousToken(
131  Loc, Ctx->getSourceManager(), Ctx->getLangOpts(),
132  /*SkipComments=*/false);
133  if (Tok.isNot(tok::comment))
134  break;
135  Loc = Tok.getLocation();
136  Comments.emplace_back(
137  Loc,
138  Lexer::getSourceText(CharSourceRange::getCharRange(
139  Loc, Loc.getLocWithOffset(Tok.getLength())),
140  Ctx->getSourceManager(), Ctx->getLangOpts()));
141  }
142  return Comments;
143 }
144 
145 static bool isLikelyTypo(llvm::ArrayRef<ParmVarDecl *> Params,
146  StringRef ArgName, unsigned ArgIndex) {
147  std::string ArgNameLowerStr = ArgName.lower();
148  StringRef ArgNameLower = ArgNameLowerStr;
149  // The threshold is arbitrary.
150  unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
151  unsigned ThisED = ArgNameLower.edit_distance(
152  Params[ArgIndex]->getIdentifier()->getName().lower(),
153  /*AllowReplacements=*/true, UpperBound);
154  if (ThisED >= UpperBound)
155  return false;
156 
157  for (unsigned I = 0, E = Params.size(); I != E; ++I) {
158  if (I == ArgIndex)
159  continue;
160  IdentifierInfo *II = Params[I]->getIdentifier();
161  if (!II)
162  continue;
163 
164  const unsigned Threshold = 2;
165  // Other parameters must be an edit distance at least Threshold more away
166  // from this parameter. This gives us greater confidence that this is a
167  // typo of this parameter and not one with a similar name.
168  unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
169  /*AllowReplacements=*/true,
170  ThisED + Threshold);
171  if (OtherED < ThisED + Threshold)
172  return false;
173  }
174 
175  return true;
176 }
177 
178 static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode) {
179  if (StrictMode)
180  return InComment == InDecl;
181  InComment = InComment.trim('_');
182  InDecl = InDecl.trim('_');
183  // FIXME: compare_insensitive only works for ASCII.
184  return InComment.compare_insensitive(InDecl) == 0;
185 }
186 
187 static bool looksLikeExpectMethod(const CXXMethodDecl *Expect) {
188  return Expect != nullptr && Expect->getLocation().isMacroID() &&
189  Expect->getNameInfo().getName().isIdentifier() &&
190  Expect->getName().startswith("gmock_");
191 }
192 static bool areMockAndExpectMethods(const CXXMethodDecl *Mock,
193  const CXXMethodDecl *Expect) {
194  assert(looksLikeExpectMethod(Expect));
195  return Mock != nullptr && Mock->getNextDeclInContext() == Expect &&
196  Mock->getNumParams() == Expect->getNumParams() &&
197  Mock->getLocation().isMacroID() &&
198  Mock->getNameInfo().getName().isIdentifier() &&
199  Mock->getName() == Expect->getName().substr(strlen("gmock_"));
200 }
201 
202 // This uses implementation details of MOCK_METHODx_ macros: for each mocked
203 // method M it defines M() with appropriate signature and a method used to set
204 // up expectations - gmock_M() - with each argument's type changed the
205 // corresponding matcher. This function returns M when given either M or
206 // gmock_M.
207 static const CXXMethodDecl *findMockedMethod(const CXXMethodDecl *Method) {
208  if (looksLikeExpectMethod(Method)) {
209  const DeclContext *Ctx = Method->getDeclContext();
210  if (Ctx == nullptr || !Ctx->isRecord())
211  return nullptr;
212  for (const auto *D : Ctx->decls()) {
213  if (D->getNextDeclInContext() == Method) {
214  const auto *Previous = dyn_cast<CXXMethodDecl>(D);
215  return areMockAndExpectMethods(Previous, Method) ? Previous : nullptr;
216  }
217  }
218  return nullptr;
219  }
220  if (const auto *Next =
221  dyn_cast_or_null<CXXMethodDecl>(Method->getNextDeclInContext())) {
222  if (looksLikeExpectMethod(Next) && areMockAndExpectMethods(Method, Next))
223  return Method;
224  }
225  return nullptr;
226 }
227 
228 // For gmock expectation builder method (the target of the call generated by
229 // `EXPECT_CALL(obj, Method(...))`) tries to find the real method being mocked
230 // (returns nullptr, if the mock method doesn't override anything). For other
231 // functions returns the function itself.
232 static const FunctionDecl *resolveMocks(const FunctionDecl *Func) {
233  if (const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
234  if (const auto *MockedMethod = findMockedMethod(Method)) {
235  // If mocked method overrides the real one, we can use its parameter
236  // names, otherwise we're out of luck.
237  if (MockedMethod->size_overridden_methods() > 0) {
238  return *MockedMethod->begin_overridden_methods();
239  }
240  return nullptr;
241  }
242  }
243  return Func;
244 }
245 
246 // Given the argument type and the options determine if we should
247 // be adding an argument comment.
248 bool ArgumentCommentCheck::shouldAddComment(const Expr *Arg) const {
249  Arg = Arg->IgnoreImpCasts();
250  if (isa<UnaryOperator>(Arg))
251  Arg = cast<UnaryOperator>(Arg)->getSubExpr();
252  if (Arg->getExprLoc().isMacroID())
253  return false;
254  return (CommentBoolLiterals && isa<CXXBoolLiteralExpr>(Arg)) ||
255  (CommentIntegerLiterals && isa<IntegerLiteral>(Arg)) ||
256  (CommentFloatLiterals && isa<FloatingLiteral>(Arg)) ||
257  (CommentUserDefinedLiterals && isa<UserDefinedLiteral>(Arg)) ||
258  (CommentCharacterLiterals && isa<CharacterLiteral>(Arg)) ||
259  (CommentStringLiterals && isa<StringLiteral>(Arg)) ||
260  (CommentNullPtrs && isa<CXXNullPtrLiteralExpr>(Arg));
261 }
262 
263 void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
264  const FunctionDecl *OriginalCallee,
265  SourceLocation ArgBeginLoc,
266  llvm::ArrayRef<const Expr *> Args) {
267  const FunctionDecl *Callee = resolveMocks(OriginalCallee);
268  if (!Callee)
269  return;
270 
271  Callee = Callee->getFirstDecl();
272  unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
273  if ((NumArgs == 0) || (IgnoreSingleArgument && NumArgs == 1))
274  return;
275 
276  auto MakeFileCharRange = [Ctx](SourceLocation Begin, SourceLocation End) {
277  return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
278  Ctx->getSourceManager(),
279  Ctx->getLangOpts());
280  };
281 
282  for (unsigned I = 0; I < NumArgs; ++I) {
283  const ParmVarDecl *PVD = Callee->getParamDecl(I);
284  IdentifierInfo *II = PVD->getIdentifier();
285  if (!II)
286  continue;
287  if (FunctionDecl *Template = Callee->getTemplateInstantiationPattern()) {
288  // Don't warn on arguments for parameters instantiated from template
289  // parameter packs. If we find more arguments than the template
290  // definition has, it also means that they correspond to a parameter
291  // pack.
292  if (Template->getNumParams() <= I ||
293  Template->getParamDecl(I)->isParameterPack()) {
294  continue;
295  }
296  }
297 
298  CharSourceRange BeforeArgument =
299  MakeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
300  ArgBeginLoc = Args[I]->getEndLoc();
301 
302  std::vector<std::pair<SourceLocation, StringRef>> Comments;
303  if (BeforeArgument.isValid()) {
304  Comments = getCommentsInRange(Ctx, BeforeArgument);
305  } else {
306  // Fall back to parsing back from the start of the argument.
307  CharSourceRange ArgsRange =
308  MakeFileCharRange(Args[I]->getBeginLoc(), Args[I]->getEndLoc());
309  Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin());
310  }
311 
312  for (auto Comment : Comments) {
313  llvm::SmallVector<StringRef, 2> Matches;
314  if (IdentRE.match(Comment.second, &Matches) &&
315  !sameName(Matches[2], II->getName(), StrictMode)) {
316  {
317  DiagnosticBuilder Diag =
318  diag(Comment.first, "argument name '%0' in comment does not "
319  "match parameter name %1")
320  << Matches[2] << II;
321  if (isLikelyTypo(Callee->parameters(), Matches[2], I)) {
322  Diag << FixItHint::CreateReplacement(
323  Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
324  }
325  }
326  diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note) << II;
327  if (OriginalCallee != Callee) {
328  diag(OriginalCallee->getLocation(),
329  "actual callee (%0) is declared here", DiagnosticIDs::Note)
330  << OriginalCallee;
331  }
332  }
333  }
334 
335  // If the argument comments are missing for literals add them.
336  if (Comments.empty() && shouldAddComment(Args[I])) {
337  std::string ArgComment =
338  (llvm::Twine("/*") + II->getName() + "=*/").str();
339  DiagnosticBuilder Diag =
340  diag(Args[I]->getBeginLoc(),
341  "argument comment missing for literal argument %0")
342  << II
343  << FixItHint::CreateInsertion(Args[I]->getBeginLoc(), ArgComment);
344  }
345  }
346 }
347 
348 void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
349  const auto *E = Result.Nodes.getNodeAs<Expr>("expr");
350  if (const auto *Call = dyn_cast<CallExpr>(E)) {
351  const FunctionDecl *Callee = Call->getDirectCallee();
352  if (!Callee)
353  return;
354 
355  checkCallArgs(Result.Context, Callee, Call->getCallee()->getEndLoc(),
356  llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
357  } else {
358  const auto *Construct = cast<CXXConstructExpr>(E);
359  if (Construct->getNumArgs() > 0 &&
360  Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
361  // Ignore implicit construction.
362  return;
363  }
364  checkCallArgs(
365  Result.Context, Construct->getConstructor(),
366  Construct->getParenOrBraceRange().getBegin(),
367  llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
368  }
369 }
370 
371 } // namespace bugprone
372 } // namespace tidy
373 } // namespace clang
clang::tidy::bugprone::resolveMocks
static const FunctionDecl * resolveMocks(const FunctionDecl *Func)
Definition: ArgumentCommentCheck.cpp:232
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:38
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
clang::tidy::bugprone::ArgumentCommentCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: ArgumentCommentCheck.cpp:348
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
clang::tidy::bugprone::looksLikeExpectMethod
static bool looksLikeExpectMethod(const CXXMethodDecl *Expect)
Definition: ArgumentCommentCheck.cpp:187
clang::tidy::bugprone::getCommentsBeforeLoc
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc)
Definition: ArgumentCommentCheck.cpp:127
clang::tidy::bugprone::areMockAndExpectMethods
static bool areMockAndExpectMethods(const CXXMethodDecl *Mock, const CXXMethodDecl *Expect)
Definition: ArgumentCommentCheck.cpp:192
clang::tidy::bugprone::ArgumentCommentCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: ArgumentCommentCheck.cpp:49
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:112
Ctx
Context Ctx
Definition: TUScheduler.cpp:460
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::tidy::bugprone::getCommentsInRange
static std::vector< std::pair< SourceLocation, StringRef > > getCommentsInRange(ASTContext *Ctx, CharSourceRange Range)
Definition: ArgumentCommentCheck.cpp:83
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::utils::lexer::getPreviousToken
Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments)
Returns previous token or tok::unknown if not found.
Definition: LexerUtils.cpp:18
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::tidy::bugprone::ArgumentCommentCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ArgumentCommentCheck.cpp:61
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:416
ArgumentCommentCheck.h
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:72
Args
llvm::json::Object Args
Definition: Trace.cpp:139
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::bugprone::findMockedMethod
static const CXXMethodDecl * findMockedMethod(const CXXMethodDecl *Method)
Definition: ArgumentCommentCheck.cpp:207
clang::tidy::bugprone::isLikelyTypo
static bool isLikelyTypo(llvm::ArrayRef< ParmVarDecl * > Params, StringRef ArgName, unsigned ArgIndex)
Definition: ArgumentCommentCheck.cpp:145
clang::tidy::ClangTidyCheck::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidyCheck.cpp:120
clang::tidy::bugprone::sameName
static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode)
Definition: ArgumentCommentCheck.cpp:178