//===--- 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/AST/ASTContext.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, InnerMatcher) { FunctionDecl *InstantiatedFrom = Node.getInstantiatedFromMemberFunction(); return InnerMatcher.matches(InstantiatedFrom ? *InstantiatedFrom : Node, Finder, Builder); } constexpr std::initializer_list 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 CheckedFunctions) : UnusedReturnValueCheck(Name, Context, std::move(CheckedFunctions), {}, false) {} UnusedReturnValueCheck::UnusedReturnValueCheck( llvm::StringRef Name, ClangTidyContext *Context, std::vector CheckedFunctions, std::vector 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("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