clang-tools 6.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 =
95 utils::lexer::getPreviousToken(*Ctx, Loc, /*SkipComments=*/false);
96 if (Tok.isNot(tok::comment))
97 break;
98 Loc = Tok.getLocation();
99 Comments.emplace_back(
100 Loc,
101 Lexer::getSourceText(CharSourceRange::getCharRange(
102 Loc, Loc.getLocWithOffset(Tok.getLength())),
103 Ctx->getSourceManager(), Ctx->getLangOpts()));
104 }
105 return Comments;
106 }
107
108 static bool isLikelyTypo(llvm::ArrayRef<ParmVarDecl *> Params,
109 StringRef ArgName, unsigned ArgIndex) {
110 std::string ArgNameLowerStr = ArgName.lower();
111 StringRef ArgNameLower = ArgNameLowerStr;
112 // The threshold is arbitrary.
113 unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
114 unsigned ThisED = ArgNameLower.edit_distance(
115 Params[ArgIndex]->getIdentifier()->getName().lower(),
116 /*AllowReplacements=*/true, UpperBound);
117 if (ThisED >= UpperBound)
118 return false;
119
120 for (unsigned I = 0, E = Params.size(); I != E; ++I) {
121 if (I == ArgIndex)
122 continue;
123 IdentifierInfo *II = Params[I]->getIdentifier();
124 if (!II)
125 continue;
126
127 const unsigned Threshold = 2;
128 // Other parameters must be an edit distance at least Threshold more away
129 // from this parameter. This gives us greater confidence that this is a typo
130 // of this parameter and not one with a similar name.
131 unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
132 /*AllowReplacements=*/true,
133 ThisED + Threshold);
134 if (OtherED < ThisED + Threshold)
135 return false;
136 }
137
138 return true;
139 }
140
141 static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode) {
142 if (StrictMode)
143 return InComment == InDecl;
144 InComment = InComment.trim('_');
145 InDecl = InDecl.trim('_');
146 // FIXME: compare_lower only works for ASCII.
147 return InComment.compare_lower(InDecl) == 0;
148 }
149
150 static bool looksLikeExpectMethod(const CXXMethodDecl *Expect) {
151 return Expect != nullptr && Expect->getLocation().isMacroID() &&
152 Expect->getNameInfo().getName().isIdentifier() &&
153 Expect->getName().startswith("gmock_");
154 }
155 static bool areMockAndExpectMethods(const CXXMethodDecl *Mock,
156 const CXXMethodDecl *Expect) {
157 assert(looksLikeExpectMethod(Expect));
158 return Mock != nullptr && Mock->getNextDeclInContext() == Expect &&
159 Mock->getNumParams() == Expect->getNumParams() &&
160 Mock->getLocation().isMacroID() &&
161 Mock->getNameInfo().getName().isIdentifier() &&
162 Mock->getName() == Expect->getName().substr(strlen("gmock_"));
163 }
164
165 // This uses implementation details of MOCK_METHODx_ macros: for each mocked
166 // method M it defines M() with appropriate signature and a method used to set
167 // up expectations - gmock_M() - with each argument's type changed the
168 // corresponding matcher. This function returns M when given either M or
169 // gmock_M.
170 static const CXXMethodDecl *findMockedMethod(const CXXMethodDecl *Method) {
171 if (looksLikeExpectMethod(Method)) {
172 const DeclContext *Ctx = Method->getDeclContext();
173 if (Ctx == nullptr || !Ctx->isRecord())
174 return nullptr;
175 for (const auto *D : Ctx->decls()) {
176 if (D->getNextDeclInContext() == Method) {
177 const auto *Previous = dyn_cast<CXXMethodDecl>(D);
178 return areMockAndExpectMethods(Previous, Method) ? Previous : nullptr;
179 }
180 }
181 return nullptr;
182 }
183 if (const auto *Next = dyn_cast_or_null<CXXMethodDecl>(
184 Method->getNextDeclInContext())) {
185 if (looksLikeExpectMethod(Next) && areMockAndExpectMethods(Method, Next))
186 return Method;
187 }
188 return nullptr;
189 }
190
191 // For gmock expectation builder method (the target of the call generated by
192 // `EXPECT_CALL(obj, Method(...))`) tries to find the real method being mocked
193 // (returns nullptr, if the mock method doesn't override anything). For other
194 // functions returns the function itself.
195 static const FunctionDecl *resolveMocks(const FunctionDecl *Func) {
196 if (const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
197 if (const auto *MockedMethod = findMockedMethod(Method)) {
198 // If mocked method overrides the real one, we can use its parameter
199 // names, otherwise we're out of luck.
200 if (MockedMethod->size_overridden_methods() > 0) {
201 return *MockedMethod->begin_overridden_methods();
202 }
203 return nullptr;
204 }
205 }
206 return Func;
207 }
208
209 void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
210 const FunctionDecl *OriginalCallee,
211 SourceLocation ArgBeginLoc,
212 llvm::ArrayRef<const Expr *> Args) {
213 const FunctionDecl *Callee = resolveMocks(OriginalCallee);
214 if (!Callee)
215 return;
216
217 Callee = Callee->getFirstDecl();
218 unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
219 if (NumArgs == 0)
220 return;
221
222 auto makeFileCharRange = [Ctx](SourceLocation Begin, SourceLocation End) {
223 return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
224 Ctx->getSourceManager(),
225 Ctx->getLangOpts());
226 };
227
228 for (unsigned I = 0; I < NumArgs; ++I) {
229 const ParmVarDecl *PVD = Callee->getParamDecl(I);
230 IdentifierInfo *II = PVD->getIdentifier();
231 if (!II)
232 continue;
233 if (auto Template = Callee->getTemplateInstantiationPattern()) {
234 // Don't warn on arguments for parameters instantiated from template
235 // parameter packs. If we find more arguments than the template
236 // definition has, it also means that they correspond to a parameter
237 // pack.
238 if (Template->getNumParams() <= I ||
239 Template->getParamDecl(I)->isParameterPack()) {
240 continue;
241 }
242 }
243
244 CharSourceRange BeforeArgument =
245 makeFileCharRange(ArgBeginLoc, Args[I]->getLocStart());
246 ArgBeginLoc = Args[I]->getLocEnd();
247
248 std::vector<std::pair<SourceLocation, StringRef>> Comments;
249 if (BeforeArgument.isValid()) {
250 Comments = getCommentsInRange(Ctx, BeforeArgument);
251 } else {
252 // Fall back to parsing back from the start of the argument.
253 CharSourceRange ArgsRange = makeFileCharRange(
254 Args[I]->getLocStart(), Args[NumArgs - 1]->getLocEnd());
255 Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin());
256 }
257
258 for (auto Comment : Comments) {
259 llvm::SmallVector<StringRef, 2> Matches;
260 if (IdentRE.match(Comment.second, &Matches) &&
261 !sameName(Matches[2], II->getName(), StrictMode)) {
262 {
263 DiagnosticBuilder Diag =
264 diag(Comment.first, "argument name '%0' in comment does not "
265 "match parameter name %1")
266 << Matches[2] << II;
267 if (isLikelyTypo(Callee->parameters(), Matches[2], I)) {
268 Diag << FixItHint::CreateReplacement(
269 Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
270 }
271 }
272 diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note) << II;
273 if (OriginalCallee != Callee) {
274 diag(OriginalCallee->getLocation(),
275 "actual callee (%0) is declared here", DiagnosticIDs::Note)
276 << OriginalCallee;
277 }
278 }
279 }
280 }
281 }
282
283 void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
284 const auto *E = Result.Nodes.getNodeAs<Expr>("expr");
285 if (const auto *Call = dyn_cast<CallExpr>(E)) {
286 const FunctionDecl *Callee = Call->getDirectCallee();
287 if (!Callee)
288 return;
289
290 checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
291 llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
292 } else {
293 const auto *Construct = cast<CXXConstructExpr>(E);
294 if (Construct->getNumArgs() == 1 &&
295 Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
296 // Ignore implicit construction.
297 return;
298 }
299 checkCallArgs(
300 Result.Context, Construct->getConstructor(),
301 Construct->getParenOrBraceRange().getBegin(),
302 llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
303 }
304 }
305
306 } // namespace bugprone
307 } // namespace tidy
308 } // namespace clang
Loc
SourceLocation Loc
&#39;#&#39; location in the include directive
Definition: IncludeOrderCheck.cpp:36
ArgumentCommentCheck.h
clang::ast_matchers
Definition: ClangTidyDiagnosticConsumer.h:27
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:449
clang::tidy::bugprone::looksLikeExpectMethod
static bool looksLikeExpectMethod(const CXXMethodDecl *Expect)
Definition: ArgumentCommentCheck.cpp:150
clang::tidy::bugprone::resolveMocks
static const FunctionDecl * resolveMocks(const FunctionDecl *Func)
Definition: ArgumentCommentCheck.cpp:195
Name
StringHandle Name
Definition: PreprocessorTracker.cpp:525
clang::tidy::utils::lexer::getPreviousToken
Token getPreviousToken(const ASTContext &Context, SourceLocation Location, 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:283
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:155
clang::tidy::bugprone::isLikelyTypo
static bool isLikelyTypo(llvm::ArrayRef< ParmVarDecl *> Params, StringRef ArgName, unsigned ArgIndex)
Definition: ArgumentCommentCheck.cpp:108
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
clang::tidy::bugprone::ArgumentCommentCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ArgumentCommentCheck.cpp:33
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:99
clang::tidy::bugprone::sameName
static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode)
Definition: ArgumentCommentCheck.cpp:141
clang
Definition: AndroidTidyModule.cpp:28
clang::tidy::bugprone::findMockedMethod
static const CXXMethodDecl * findMockedMethod(const CXXMethodDecl *Method)
Definition: ArgumentCommentCheck.cpp:170
Range
CharSourceRange Range
SourceRange for the file name.
Definition: IncludeOrderCheck.cpp:37
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:104
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:416