clang-tools 10.0.0

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