clang-tools 8.0.0

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