blob: 6f08c41b4188728ccb2b1cf6c17b540f08273724 [file] [log] [blame]
//===--- UnusedReturnValueCheck.cpp - clang-tidy---------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "UnusedReturnValueCheck.h"
#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/OperatorKinds.h"
using namespace clang::ast_matchers;
using namespace clang::ast_matchers::internal;
namespace clang::tidy::bugprone {
namespace {
// Matches functions that are instantiated from a class template member function
// matching InnerMatcher. Functions not instantiated from a class template
// member function are matched directly with InnerMatcher.
AST_MATCHER_P(FunctionDecl, isInstantiatedFrom, Matcher<FunctionDecl>,
InnerMatcher) {
FunctionDecl *InstantiatedFrom = Node.getInstantiatedFromMemberFunction();
return InnerMatcher.matches(InstantiatedFrom ? *InstantiatedFrom : Node,
Finder, Builder);
}
constexpr std::initializer_list<OverloadedOperatorKind>
AssignmentOverloadedOperatorKinds = {
OO_Equal, OO_PlusEqual, OO_MinusEqual, OO_StarEqual,
OO_SlashEqual, OO_PercentEqual, OO_CaretEqual, OO_AmpEqual,
OO_PipeEqual, OO_LessLessEqual, OO_GreaterGreaterEqual, OO_PlusPlus,
OO_MinusMinus};
AST_MATCHER(FunctionDecl, isAssignmentOverloadedOperator) {
return llvm::is_contained(AssignmentOverloadedOperatorKinds,
Node.getOverloadedOperator());
}
} // namespace
UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
CheckedFunctions(utils::options::parseStringList(
Options.get("CheckedFunctions", "^::std::async$;"
"^::std::launder$;"
"^::std::remove$;"
"^::std::remove_if$;"
"^::std::unique$;"
"^::std::unique_ptr::release$;"
"^::std::basic_string::empty$;"
"^::std::vector::empty$;"
"^::std::back_inserter$;"
"^::std::distance$;"
"^::std::find$;"
"^::std::find_if$;"
"^::std::inserter$;"
"^::std::lower_bound$;"
"^::std::make_pair$;"
"^::std::map::count$;"
"^::std::map::find$;"
"^::std::map::lower_bound$;"
"^::std::multimap::equal_range$;"
"^::std::multimap::upper_bound$;"
"^::std::set::count$;"
"^::std::set::find$;"
"^::std::setfill$;"
"^::std::setprecision$;"
"^::std::setw$;"
"^::std::upper_bound$;"
"^::std::vector::at$;"
// C standard library
"^::bsearch$;"
"^::ferror$;"
"^::feof$;"
"^::isalnum$;"
"^::isalpha$;"
"^::isblank$;"
"^::iscntrl$;"
"^::isdigit$;"
"^::isgraph$;"
"^::islower$;"
"^::isprint$;"
"^::ispunct$;"
"^::isspace$;"
"^::isupper$;"
"^::iswalnum$;"
"^::iswprint$;"
"^::iswspace$;"
"^::isxdigit$;"
"^::memchr$;"
"^::memcmp$;"
"^::strcmp$;"
"^::strcoll$;"
"^::strncmp$;"
"^::strpbrk$;"
"^::strrchr$;"
"^::strspn$;"
"^::strstr$;"
"^::wcscmp$;"
// POSIX
"^::access$;"
"^::bind$;"
"^::connect$;"
"^::difftime$;"
"^::dlsym$;"
"^::fnmatch$;"
"^::getaddrinfo$;"
"^::getopt$;"
"^::htonl$;"
"^::htons$;"
"^::iconv_open$;"
"^::inet_addr$;"
"^::isascii$;"
"^::isatty$;"
"^::mmap$;"
"^::newlocale$;"
"^::openat$;"
"^::pathconf$;"
"^::pthread_equal$;"
"^::pthread_getspecific$;"
"^::pthread_mutex_trylock$;"
"^::readdir$;"
"^::readlink$;"
"^::recvmsg$;"
"^::regexec$;"
"^::scandir$;"
"^::semget$;"
"^::setjmp$;"
"^::shm_open$;"
"^::shmget$;"
"^::sigismember$;"
"^::strcasecmp$;"
"^::strsignal$;"
"^::ttyname$"))),
CheckedReturnTypes(utils::options::parseStringList(
Options.get("CheckedReturnTypes", "^::std::error_code$;"
"^::std::error_condition$;"
"^::std::errc$;"
"^::std::expected$;"
"^::boost::system::error_code$"))),
AllowCastToVoid(Options.get("AllowCastToVoid", false)) {}
UnusedReturnValueCheck::UnusedReturnValueCheck(
llvm::StringRef Name, ClangTidyContext *Context,
std::vector<StringRef> CheckedFunctions)
: UnusedReturnValueCheck(Name, Context, std::move(CheckedFunctions), {},
false) {}
UnusedReturnValueCheck::UnusedReturnValueCheck(
llvm::StringRef Name, ClangTidyContext *Context,
std::vector<StringRef> CheckedFunctions,
std::vector<StringRef> CheckedReturnTypes, bool AllowCastToVoid)
: ClangTidyCheck(Name, Context),
CheckedFunctions(std::move(CheckedFunctions)),
CheckedReturnTypes(std::move(CheckedReturnTypes)),
AllowCastToVoid(AllowCastToVoid) {}
void UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "CheckedFunctions",
utils::options::serializeStringList(CheckedFunctions));
Options.store(Opts, "CheckedReturnTypes",
utils::options::serializeStringList(CheckedReturnTypes));
Options.store(Opts, "AllowCastToVoid", AllowCastToVoid);
}
void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) {
auto MatchedDirectCallExpr =
expr(callExpr(callee(functionDecl(
// Don't match copy or move assignment operator.
unless(isAssignmentOverloadedOperator()),
// Don't match void overloads of checked functions.
unless(returns(voidType())),
anyOf(isInstantiatedFrom(matchers::matchesAnyListedName(
CheckedFunctions)),
returns(hasCanonicalType(hasDeclaration(
namedDecl(matchers::matchesAnyListedName(
CheckedReturnTypes)))))))))
.bind("match"));
auto CheckCastToVoid =
AllowCastToVoid ? castExpr(unless(hasCastKind(CK_ToVoid))) : castExpr();
auto MatchedCallExpr = expr(
anyOf(MatchedDirectCallExpr,
explicitCastExpr(unless(cxxFunctionalCastExpr()), CheckCastToVoid,
hasSourceExpression(MatchedDirectCallExpr))));
auto UnusedInCompoundStmt =
compoundStmt(forEach(MatchedCallExpr),
// The checker can't currently differentiate between the
// return statement and other statements inside GNU statement
// expressions, so disable the checker inside them to avoid
// false positives.
unless(hasParent(stmtExpr())));
auto UnusedInIfStmt =
ifStmt(eachOf(hasThen(MatchedCallExpr), hasElse(MatchedCallExpr)));
auto UnusedInWhileStmt = whileStmt(hasBody(MatchedCallExpr));
auto UnusedInDoStmt = doStmt(hasBody(MatchedCallExpr));
auto UnusedInForStmt =
forStmt(eachOf(hasLoopInit(MatchedCallExpr),
hasIncrement(MatchedCallExpr), hasBody(MatchedCallExpr)));
auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(MatchedCallExpr));
auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr));
Finder->addMatcher(
stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt,
UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt,
UnusedInCaseStmt)),
this);
}
void UnusedReturnValueCheck::check(const MatchFinder::MatchResult &Result) {
if (const auto *Matched = Result.Nodes.getNodeAs<CallExpr>("match")) {
diag(Matched->getBeginLoc(),
"the value returned by this function should not be disregarded; "
"neglecting it may lead to errors")
<< Matched->getSourceRange();
if (!AllowCastToVoid)
return;
diag(Matched->getBeginLoc(),
"cast the expression to void to silence this warning",
DiagnosticIDs::Note);
}
}
} // namespace clang::tidy::bugprone