2014-03-18 12:46:45 +08:00
|
|
|
//===--- ArgumentCommentCheck.cpp - clang-tidy ----------------------------===//
|
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "ArgumentCommentCheck.h"
|
|
|
|
#include "clang/AST/ASTContext.h"
|
|
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
|
|
#include "clang/Lex/Lexer.h"
|
|
|
|
#include "clang/Lex/Token.h"
|
2017-02-06 23:47:17 +08:00
|
|
|
#include "../utils/LexerUtils.h"
|
2014-03-18 12:46:45 +08:00
|
|
|
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace tidy {
|
2017-11-24 01:02:48 +08:00
|
|
|
namespace bugprone {
|
2014-03-18 12:46:45 +08:00
|
|
|
|
2014-09-12 16:53:36 +08:00
|
|
|
ArgumentCommentCheck::ArgumentCommentCheck(StringRef Name,
|
|
|
|
ClangTidyContext *Context)
|
|
|
|
: ClangTidyCheck(Name, Context),
|
2016-08-04 22:54:54 +08:00
|
|
|
StrictMode(Options.getLocalOrGlobal("StrictMode", 0) != 0),
|
2014-09-12 16:53:36 +08:00
|
|
|
IdentRE("^(/\\* *)([_A-Za-z][_A-Za-z0-9]*)( *= *\\*/)$") {}
|
2014-03-18 15:22:43 +08:00
|
|
|
|
2016-08-04 22:54:54 +08:00
|
|
|
void ArgumentCommentCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
|
|
Options.store(Opts, "StrictMode", StrictMode);
|
|
|
|
}
|
|
|
|
|
2014-03-18 12:46:45 +08:00
|
|
|
void ArgumentCommentCheck::registerMatchers(MatchFinder *Finder) {
|
2014-07-30 22:31:36 +08:00
|
|
|
Finder->addMatcher(
|
2015-09-17 21:31:25 +08:00
|
|
|
callExpr(unless(cxxOperatorCallExpr()),
|
2014-07-30 22:31:36 +08:00
|
|
|
// NewCallback's arguments relate to the pointed function, don't
|
|
|
|
// check them against NewCallback's parameter names.
|
|
|
|
// FIXME: Make this configurable.
|
2016-08-04 22:54:54 +08:00
|
|
|
unless(hasDeclaration(functionDecl(
|
|
|
|
hasAnyName("NewCallback", "NewPermanentCallback")))))
|
2014-07-30 22:31:36 +08:00
|
|
|
.bind("expr"),
|
|
|
|
this);
|
2015-09-17 21:31:25 +08:00
|
|
|
Finder->addMatcher(cxxConstructExpr().bind("expr"), this);
|
2014-03-18 12:46:45 +08:00
|
|
|
}
|
|
|
|
|
2016-06-08 23:27:46 +08:00
|
|
|
static std::vector<std::pair<SourceLocation, StringRef>>
|
|
|
|
getCommentsInRange(ASTContext *Ctx, CharSourceRange Range) {
|
2014-03-18 12:46:45 +08:00
|
|
|
std::vector<std::pair<SourceLocation, StringRef>> Comments;
|
|
|
|
auto &SM = Ctx->getSourceManager();
|
|
|
|
std::pair<FileID, unsigned> BeginLoc = SM.getDecomposedLoc(Range.getBegin()),
|
|
|
|
EndLoc = SM.getDecomposedLoc(Range.getEnd());
|
|
|
|
|
|
|
|
if (BeginLoc.first != EndLoc.first)
|
|
|
|
return Comments;
|
|
|
|
|
|
|
|
bool Invalid = false;
|
|
|
|
StringRef Buffer = SM.getBufferData(BeginLoc.first, &Invalid);
|
|
|
|
if (Invalid)
|
|
|
|
return Comments;
|
|
|
|
|
|
|
|
const char *StrData = Buffer.data() + BeginLoc.second;
|
|
|
|
|
|
|
|
Lexer TheLexer(SM.getLocForStartOfFile(BeginLoc.first), Ctx->getLangOpts(),
|
|
|
|
Buffer.begin(), StrData, Buffer.end());
|
|
|
|
TheLexer.SetCommentRetentionState(true);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
Token Tok;
|
|
|
|
if (TheLexer.LexFromRawLexer(Tok))
|
|
|
|
break;
|
2017-02-01 23:28:25 +08:00
|
|
|
if (Tok.getLocation() == Range.getEnd() || Tok.is(tok::eof))
|
2014-03-18 12:46:45 +08:00
|
|
|
break;
|
|
|
|
|
2017-02-01 23:28:25 +08:00
|
|
|
if (Tok.is(tok::comment)) {
|
2014-03-18 12:46:45 +08:00
|
|
|
std::pair<FileID, unsigned> CommentLoc =
|
|
|
|
SM.getDecomposedLoc(Tok.getLocation());
|
|
|
|
assert(CommentLoc.first == BeginLoc.first);
|
|
|
|
Comments.emplace_back(
|
|
|
|
Tok.getLocation(),
|
|
|
|
StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()));
|
2017-02-01 23:28:25 +08:00
|
|
|
} else {
|
|
|
|
// Clear comments found before the different token, e.g. comma.
|
|
|
|
Comments.clear();
|
2014-03-18 12:46:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Comments;
|
|
|
|
}
|
|
|
|
|
2017-02-06 23:47:17 +08:00
|
|
|
static std::vector<std::pair<SourceLocation, StringRef>>
|
|
|
|
getCommentsBeforeLoc(ASTContext *Ctx, SourceLocation Loc) {
|
|
|
|
std::vector<std::pair<SourceLocation, StringRef>> Comments;
|
|
|
|
while (Loc.isValid()) {
|
|
|
|
clang::Token Tok =
|
|
|
|
utils::lexer::getPreviousToken(*Ctx, Loc, /*SkipComments=*/false);
|
|
|
|
if (Tok.isNot(tok::comment))
|
|
|
|
break;
|
|
|
|
Loc = Tok.getLocation();
|
|
|
|
Comments.emplace_back(
|
|
|
|
Loc,
|
|
|
|
Lexer::getSourceText(CharSourceRange::getCharRange(
|
|
|
|
Loc, Loc.getLocWithOffset(Tok.getLength())),
|
|
|
|
Ctx->getSourceManager(), Ctx->getLangOpts()));
|
|
|
|
}
|
|
|
|
return Comments;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isLikelyTypo(llvm::ArrayRef<ParmVarDecl *> Params,
|
|
|
|
StringRef ArgName, unsigned ArgIndex) {
|
2016-08-04 22:54:54 +08:00
|
|
|
std::string ArgNameLowerStr = ArgName.lower();
|
|
|
|
StringRef ArgNameLower = ArgNameLowerStr;
|
|
|
|
// The threshold is arbitrary.
|
2014-03-18 12:46:45 +08:00
|
|
|
unsigned UpperBound = (ArgName.size() + 2) / 3 + 1;
|
2016-08-04 22:54:54 +08:00
|
|
|
unsigned ThisED = ArgNameLower.edit_distance(
|
2014-03-18 12:46:45 +08:00
|
|
|
Params[ArgIndex]->getIdentifier()->getName().lower(),
|
|
|
|
/*AllowReplacements=*/true, UpperBound);
|
|
|
|
if (ThisED >= UpperBound)
|
|
|
|
return false;
|
|
|
|
|
2014-08-04 18:11:47 +08:00
|
|
|
for (unsigned I = 0, E = Params.size(); I != E; ++I) {
|
|
|
|
if (I == ArgIndex)
|
2014-03-18 12:46:45 +08:00
|
|
|
continue;
|
2014-08-04 18:11:47 +08:00
|
|
|
IdentifierInfo *II = Params[I]->getIdentifier();
|
2014-03-18 12:46:45 +08:00
|
|
|
if (!II)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
const unsigned Threshold = 2;
|
|
|
|
// Other parameters must be an edit distance at least Threshold more away
|
|
|
|
// from this parameter. This gives us greater confidence that this is a typo
|
|
|
|
// of this parameter and not one with a similar name.
|
2016-08-04 22:54:54 +08:00
|
|
|
unsigned OtherED = ArgNameLower.edit_distance(II->getName().lower(),
|
|
|
|
/*AllowReplacements=*/true,
|
|
|
|
ThisED + Threshold);
|
2014-03-18 12:46:45 +08:00
|
|
|
if (OtherED < ThisED + Threshold)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-08-04 22:54:54 +08:00
|
|
|
static bool sameName(StringRef InComment, StringRef InDecl, bool StrictMode) {
|
|
|
|
if (StrictMode)
|
|
|
|
return InComment == InDecl;
|
|
|
|
InComment = InComment.trim('_');
|
|
|
|
InDecl = InDecl.trim('_');
|
|
|
|
// FIXME: compare_lower only works for ASCII.
|
|
|
|
return InComment.compare_lower(InDecl) == 0;
|
|
|
|
}
|
|
|
|
|
2017-02-07 19:39:56 +08:00
|
|
|
static bool looksLikeExpectMethod(const CXXMethodDecl *Expect) {
|
|
|
|
return Expect != nullptr && Expect->getLocation().isMacroID() &&
|
|
|
|
Expect->getNameInfo().getName().isIdentifier() &&
|
|
|
|
Expect->getName().startswith("gmock_");
|
|
|
|
}
|
|
|
|
static bool areMockAndExpectMethods(const CXXMethodDecl *Mock,
|
|
|
|
const CXXMethodDecl *Expect) {
|
|
|
|
assert(looksLikeExpectMethod(Expect));
|
|
|
|
return Mock != nullptr && Mock->getNextDeclInContext() == Expect &&
|
|
|
|
Mock->getNumParams() == Expect->getNumParams() &&
|
|
|
|
Mock->getLocation().isMacroID() &&
|
|
|
|
Mock->getNameInfo().getName().isIdentifier() &&
|
|
|
|
Mock->getName() == Expect->getName().substr(strlen("gmock_"));
|
|
|
|
}
|
|
|
|
|
2017-02-06 23:47:17 +08:00
|
|
|
// This uses implementation details of MOCK_METHODx_ macros: for each mocked
|
|
|
|
// method M it defines M() with appropriate signature and a method used to set
|
|
|
|
// up expectations - gmock_M() - with each argument's type changed the
|
2017-02-07 19:39:56 +08:00
|
|
|
// corresponding matcher. This function returns M when given either M or
|
|
|
|
// gmock_M.
|
|
|
|
static const CXXMethodDecl *findMockedMethod(const CXXMethodDecl *Method) {
|
|
|
|
if (looksLikeExpectMethod(Method)) {
|
|
|
|
const DeclContext *Ctx = Method->getDeclContext();
|
|
|
|
if (Ctx == nullptr || !Ctx->isRecord())
|
|
|
|
return nullptr;
|
|
|
|
for (const auto *D : Ctx->decls()) {
|
|
|
|
if (D->getNextDeclInContext() == Method) {
|
|
|
|
const auto *Previous = dyn_cast<CXXMethodDecl>(D);
|
|
|
|
return areMockAndExpectMethods(Previous, Method) ? Previous : nullptr;
|
2017-02-06 23:47:17 +08:00
|
|
|
}
|
|
|
|
}
|
2017-02-07 19:39:56 +08:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if (const auto *Next = dyn_cast_or_null<CXXMethodDecl>(
|
|
|
|
Method->getNextDeclInContext())) {
|
|
|
|
if (looksLikeExpectMethod(Next) && areMockAndExpectMethods(Method, Next))
|
|
|
|
return Method;
|
2017-02-06 23:47:17 +08:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// For gmock expectation builder method (the target of the call generated by
|
|
|
|
// `EXPECT_CALL(obj, Method(...))`) tries to find the real method being mocked
|
|
|
|
// (returns nullptr, if the mock method doesn't override anything). For other
|
|
|
|
// functions returns the function itself.
|
|
|
|
static const FunctionDecl *resolveMocks(const FunctionDecl *Func) {
|
|
|
|
if (const auto *Method = dyn_cast<CXXMethodDecl>(Func)) {
|
|
|
|
if (const auto *MockedMethod = findMockedMethod(Method)) {
|
|
|
|
// If mocked method overrides the real one, we can use its parameter
|
|
|
|
// names, otherwise we're out of luck.
|
|
|
|
if (MockedMethod->size_overridden_methods() > 0) {
|
|
|
|
return *MockedMethod->begin_overridden_methods();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Func;
|
|
|
|
}
|
|
|
|
|
2014-03-18 12:46:45 +08:00
|
|
|
void ArgumentCommentCheck::checkCallArgs(ASTContext *Ctx,
|
2017-02-06 23:47:17 +08:00
|
|
|
const FunctionDecl *OriginalCallee,
|
2014-03-18 12:46:45 +08:00
|
|
|
SourceLocation ArgBeginLoc,
|
|
|
|
llvm::ArrayRef<const Expr *> Args) {
|
2017-02-06 23:47:17 +08:00
|
|
|
const FunctionDecl *Callee = resolveMocks(OriginalCallee);
|
|
|
|
if (!Callee)
|
|
|
|
return;
|
|
|
|
|
2014-03-18 12:46:45 +08:00
|
|
|
Callee = Callee->getFirstDecl();
|
2017-02-06 23:47:17 +08:00
|
|
|
unsigned NumArgs = std::min<unsigned>(Args.size(), Callee->getNumParams());
|
|
|
|
if (NumArgs == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
auto makeFileCharRange = [Ctx](SourceLocation Begin, SourceLocation End) {
|
|
|
|
return Lexer::makeFileCharRange(CharSourceRange::getCharRange(Begin, End),
|
|
|
|
Ctx->getSourceManager(),
|
|
|
|
Ctx->getLangOpts());
|
|
|
|
};
|
|
|
|
|
|
|
|
for (unsigned I = 0; I < NumArgs; ++I) {
|
2016-08-04 22:54:54 +08:00
|
|
|
const ParmVarDecl *PVD = Callee->getParamDecl(I);
|
2014-03-18 12:46:45 +08:00
|
|
|
IdentifierInfo *II = PVD->getIdentifier();
|
|
|
|
if (!II)
|
|
|
|
continue;
|
2014-09-03 22:56:30 +08:00
|
|
|
if (auto Template = Callee->getTemplateInstantiationPattern()) {
|
|
|
|
// Don't warn on arguments for parameters instantiated from template
|
2017-02-06 23:47:17 +08:00
|
|
|
// parameter packs. If we find more arguments than the template
|
|
|
|
// definition has, it also means that they correspond to a parameter
|
|
|
|
// pack.
|
2016-08-04 22:54:54 +08:00
|
|
|
if (Template->getNumParams() <= I ||
|
|
|
|
Template->getParamDecl(I)->isParameterPack()) {
|
2014-09-03 22:56:30 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2014-03-18 12:46:45 +08:00
|
|
|
|
2017-02-06 23:47:17 +08:00
|
|
|
CharSourceRange BeforeArgument =
|
2018-08-10 06:42:26 +08:00
|
|
|
makeFileCharRange(ArgBeginLoc, Args[I]->getBeginLoc());
|
2017-02-06 23:47:17 +08:00
|
|
|
ArgBeginLoc = Args[I]->getLocEnd();
|
2014-03-18 12:46:45 +08:00
|
|
|
|
2017-02-06 23:47:17 +08:00
|
|
|
std::vector<std::pair<SourceLocation, StringRef>> Comments;
|
|
|
|
if (BeforeArgument.isValid()) {
|
|
|
|
Comments = getCommentsInRange(Ctx, BeforeArgument);
|
|
|
|
} else {
|
|
|
|
// Fall back to parsing back from the start of the argument.
|
|
|
|
CharSourceRange ArgsRange = makeFileCharRange(
|
2018-08-10 06:42:26 +08:00
|
|
|
Args[I]->getBeginLoc(), Args[NumArgs - 1]->getLocEnd());
|
2017-02-06 23:47:17 +08:00
|
|
|
Comments = getCommentsBeforeLoc(Ctx, ArgsRange.getBegin());
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto Comment : Comments) {
|
2014-03-18 12:46:45 +08:00
|
|
|
llvm::SmallVector<StringRef, 2> Matches;
|
2017-02-06 23:47:17 +08:00
|
|
|
if (IdentRE.match(Comment.second, &Matches) &&
|
|
|
|
!sameName(Matches[2], II->getName(), StrictMode)) {
|
|
|
|
{
|
|
|
|
DiagnosticBuilder Diag =
|
|
|
|
diag(Comment.first, "argument name '%0' in comment does not "
|
|
|
|
"match parameter name %1")
|
|
|
|
<< Matches[2] << II;
|
|
|
|
if (isLikelyTypo(Callee->parameters(), Matches[2], I)) {
|
|
|
|
Diag << FixItHint::CreateReplacement(
|
|
|
|
Comment.first, (Matches[1] + II->getName() + Matches[3]).str());
|
2014-03-18 12:46:45 +08:00
|
|
|
}
|
2017-02-06 23:47:17 +08:00
|
|
|
}
|
|
|
|
diag(PVD->getLocation(), "%0 declared here", DiagnosticIDs::Note) << II;
|
|
|
|
if (OriginalCallee != Callee) {
|
|
|
|
diag(OriginalCallee->getLocation(),
|
|
|
|
"actual callee (%0) is declared here", DiagnosticIDs::Note)
|
|
|
|
<< OriginalCallee;
|
2014-03-18 12:46:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ArgumentCommentCheck::check(const MatchFinder::MatchResult &Result) {
|
2016-12-14 23:29:23 +08:00
|
|
|
const auto *E = Result.Nodes.getNodeAs<Expr>("expr");
|
2016-08-04 22:54:54 +08:00
|
|
|
if (const auto *Call = dyn_cast<CallExpr>(E)) {
|
2014-03-18 12:46:45 +08:00
|
|
|
const FunctionDecl *Callee = Call->getDirectCallee();
|
|
|
|
if (!Callee)
|
|
|
|
return;
|
|
|
|
|
|
|
|
checkCallArgs(Result.Context, Callee, Call->getCallee()->getLocEnd(),
|
|
|
|
llvm::makeArrayRef(Call->getArgs(), Call->getNumArgs()));
|
|
|
|
} else {
|
2016-08-04 22:54:54 +08:00
|
|
|
const auto *Construct = cast<CXXConstructExpr>(E);
|
2017-02-06 23:47:17 +08:00
|
|
|
if (Construct->getNumArgs() == 1 &&
|
|
|
|
Construct->getArg(0)->getSourceRange() == Construct->getSourceRange()) {
|
|
|
|
// Ignore implicit construction.
|
|
|
|
return;
|
|
|
|
}
|
2014-03-18 12:46:45 +08:00
|
|
|
checkCallArgs(
|
|
|
|
Result.Context, Construct->getConstructor(),
|
|
|
|
Construct->getParenOrBraceRange().getBegin(),
|
|
|
|
llvm::makeArrayRef(Construct->getArgs(), Construct->getNumArgs()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-24 01:02:48 +08:00
|
|
|
} // namespace bugprone
|
2014-03-18 12:46:45 +08:00
|
|
|
} // namespace tidy
|
|
|
|
} // namespace clang
|