| 1 | //===--- UnusedReturnValueCheck.cpp - clang-tidy---------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "UnusedReturnValueCheck.h" |
| 10 | #include "../utils/Matchers.h" |
| 11 | #include "../utils/OptionsUtils.h" |
| 12 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 13 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 14 | #include "clang/Basic/OperatorKinds.h" |
| 15 | |
| 16 | using namespace clang::ast_matchers; |
| 17 | using namespace clang::ast_matchers::internal; |
| 18 | |
| 19 | namespace clang::tidy::bugprone { |
| 20 | |
| 21 | namespace { |
| 22 | |
| 23 | // Matches functions that are instantiated from a class template member function |
| 24 | // matching InnerMatcher. Functions not instantiated from a class template |
| 25 | // member function are matched directly with InnerMatcher. |
| 26 | AST_MATCHER_P(FunctionDecl, isInstantiatedFrom, Matcher<FunctionDecl>, |
| 27 | InnerMatcher) { |
| 28 | FunctionDecl *InstantiatedFrom = Node.getInstantiatedFromMemberFunction(); |
| 29 | return InnerMatcher.matches(Node: InstantiatedFrom ? *InstantiatedFrom : Node, |
| 30 | Finder, Builder); |
| 31 | } |
| 32 | |
| 33 | constexpr std::initializer_list<OverloadedOperatorKind> |
| 34 | AssignmentOverloadedOperatorKinds = { |
| 35 | OO_Equal, OO_PlusEqual, OO_MinusEqual, OO_StarEqual, |
| 36 | OO_SlashEqual, OO_PercentEqual, OO_CaretEqual, OO_AmpEqual, |
| 37 | OO_PipeEqual, OO_LessLessEqual, OO_GreaterGreaterEqual, OO_PlusPlus, |
| 38 | OO_MinusMinus}; |
| 39 | |
| 40 | AST_MATCHER(FunctionDecl, isAssignmentOverloadedOperator) { |
| 41 | return llvm::is_contained(Set: AssignmentOverloadedOperatorKinds, |
| 42 | Element: Node.getOverloadedOperator()); |
| 43 | } |
| 44 | } // namespace |
| 45 | |
| 46 | UnusedReturnValueCheck::UnusedReturnValueCheck(llvm::StringRef Name, |
| 47 | ClangTidyContext *Context) |
| 48 | : ClangTidyCheck(Name, Context), |
| 49 | CheckedFunctions(utils::options::parseStringList( |
| 50 | Option: Options.get(LocalName: "CheckedFunctions" , Default: "^::std::async$;" |
| 51 | "^::std::launder$;" |
| 52 | "^::std::remove$;" |
| 53 | "^::std::remove_if$;" |
| 54 | "^::std::unique$;" |
| 55 | "^::std::unique_ptr::release$;" |
| 56 | "^::std::basic_string::empty$;" |
| 57 | "^::std::vector::empty$;" |
| 58 | "^::std::back_inserter$;" |
| 59 | "^::std::distance$;" |
| 60 | "^::std::find$;" |
| 61 | "^::std::find_if$;" |
| 62 | "^::std::inserter$;" |
| 63 | "^::std::lower_bound$;" |
| 64 | "^::std::make_pair$;" |
| 65 | "^::std::map::count$;" |
| 66 | "^::std::map::find$;" |
| 67 | "^::std::map::lower_bound$;" |
| 68 | "^::std::multimap::equal_range$;" |
| 69 | "^::std::multimap::upper_bound$;" |
| 70 | "^::std::set::count$;" |
| 71 | "^::std::set::find$;" |
| 72 | "^::std::setfill$;" |
| 73 | "^::std::setprecision$;" |
| 74 | "^::std::setw$;" |
| 75 | "^::std::upper_bound$;" |
| 76 | "^::std::vector::at$;" |
| 77 | // C standard library |
| 78 | "^::bsearch$;" |
| 79 | "^::ferror$;" |
| 80 | "^::feof$;" |
| 81 | "^::isalnum$;" |
| 82 | "^::isalpha$;" |
| 83 | "^::isblank$;" |
| 84 | "^::iscntrl$;" |
| 85 | "^::isdigit$;" |
| 86 | "^::isgraph$;" |
| 87 | "^::islower$;" |
| 88 | "^::isprint$;" |
| 89 | "^::ispunct$;" |
| 90 | "^::isspace$;" |
| 91 | "^::isupper$;" |
| 92 | "^::iswalnum$;" |
| 93 | "^::iswprint$;" |
| 94 | "^::iswspace$;" |
| 95 | "^::isxdigit$;" |
| 96 | "^::memchr$;" |
| 97 | "^::memcmp$;" |
| 98 | "^::strcmp$;" |
| 99 | "^::strcoll$;" |
| 100 | "^::strncmp$;" |
| 101 | "^::strpbrk$;" |
| 102 | "^::strrchr$;" |
| 103 | "^::strspn$;" |
| 104 | "^::strstr$;" |
| 105 | "^::wcscmp$;" |
| 106 | // POSIX |
| 107 | "^::access$;" |
| 108 | "^::bind$;" |
| 109 | "^::connect$;" |
| 110 | "^::difftime$;" |
| 111 | "^::dlsym$;" |
| 112 | "^::fnmatch$;" |
| 113 | "^::getaddrinfo$;" |
| 114 | "^::getopt$;" |
| 115 | "^::htonl$;" |
| 116 | "^::htons$;" |
| 117 | "^::iconv_open$;" |
| 118 | "^::inet_addr$;" |
| 119 | "^::isascii$;" |
| 120 | "^::isatty$;" |
| 121 | "^::mmap$;" |
| 122 | "^::newlocale$;" |
| 123 | "^::openat$;" |
| 124 | "^::pathconf$;" |
| 125 | "^::pthread_equal$;" |
| 126 | "^::pthread_getspecific$;" |
| 127 | "^::pthread_mutex_trylock$;" |
| 128 | "^::readdir$;" |
| 129 | "^::readlink$;" |
| 130 | "^::recvmsg$;" |
| 131 | "^::regexec$;" |
| 132 | "^::scandir$;" |
| 133 | "^::semget$;" |
| 134 | "^::setjmp$;" |
| 135 | "^::shm_open$;" |
| 136 | "^::shmget$;" |
| 137 | "^::sigismember$;" |
| 138 | "^::strcasecmp$;" |
| 139 | "^::strsignal$;" |
| 140 | "^::ttyname$" ))), |
| 141 | CheckedReturnTypes(utils::options::parseStringList( |
| 142 | Option: Options.get(LocalName: "CheckedReturnTypes" , Default: "^::std::error_code$;" |
| 143 | "^::std::error_condition$;" |
| 144 | "^::std::errc$;" |
| 145 | "^::std::expected$;" |
| 146 | "^::boost::system::error_code$" ))), |
| 147 | AllowCastToVoid(Options.get(LocalName: "AllowCastToVoid" , Default: false)) {} |
| 148 | |
| 149 | UnusedReturnValueCheck::UnusedReturnValueCheck( |
| 150 | llvm::StringRef Name, ClangTidyContext *Context, |
| 151 | std::vector<StringRef> CheckedFunctions) |
| 152 | : UnusedReturnValueCheck(Name, Context, std::move(CheckedFunctions), {}, |
| 153 | false) {} |
| 154 | |
| 155 | UnusedReturnValueCheck::UnusedReturnValueCheck( |
| 156 | llvm::StringRef Name, ClangTidyContext *Context, |
| 157 | std::vector<StringRef> CheckedFunctions, |
| 158 | std::vector<StringRef> CheckedReturnTypes, bool AllowCastToVoid) |
| 159 | : ClangTidyCheck(Name, Context), |
| 160 | CheckedFunctions(std::move(CheckedFunctions)), |
| 161 | CheckedReturnTypes(std::move(CheckedReturnTypes)), |
| 162 | AllowCastToVoid(AllowCastToVoid) {} |
| 163 | |
| 164 | void UnusedReturnValueCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 165 | Options.store(Options&: Opts, LocalName: "CheckedFunctions" , |
| 166 | Value: utils::options::serializeStringList(Strings: CheckedFunctions)); |
| 167 | Options.store(Options&: Opts, LocalName: "CheckedReturnTypes" , |
| 168 | Value: utils::options::serializeStringList(Strings: CheckedReturnTypes)); |
| 169 | Options.store(Options&: Opts, LocalName: "AllowCastToVoid" , Value: AllowCastToVoid); |
| 170 | } |
| 171 | |
| 172 | void UnusedReturnValueCheck::registerMatchers(MatchFinder *Finder) { |
| 173 | auto MatchedDirectCallExpr = |
| 174 | expr(callExpr(callee(InnerMatcher: functionDecl( |
| 175 | // Don't match copy or move assignment operator. |
| 176 | unless(isAssignmentOverloadedOperator()), |
| 177 | // Don't match void overloads of checked functions. |
| 178 | unless(returns(InnerMatcher: voidType())), |
| 179 | anyOf(isInstantiatedFrom(InnerMatcher: matchers::matchesAnyListedName( |
| 180 | NameList: CheckedFunctions)), |
| 181 | returns(InnerMatcher: hasCanonicalType(InnerMatcher: hasDeclaration( |
| 182 | InnerMatcher: namedDecl(matchers::matchesAnyListedName( |
| 183 | NameList: CheckedReturnTypes))))))))) |
| 184 | .bind(ID: "match" )); |
| 185 | |
| 186 | auto CheckCastToVoid = |
| 187 | AllowCastToVoid ? castExpr(unless(hasCastKind(Kind: CK_ToVoid))) : castExpr(); |
| 188 | auto MatchedCallExpr = expr( |
| 189 | anyOf(MatchedDirectCallExpr, |
| 190 | explicitCastExpr(unless(cxxFunctionalCastExpr()), CheckCastToVoid, |
| 191 | hasSourceExpression(InnerMatcher: MatchedDirectCallExpr)))); |
| 192 | |
| 193 | auto UnusedInCompoundStmt = |
| 194 | compoundStmt(forEach(MatchedCallExpr), |
| 195 | // The checker can't currently differentiate between the |
| 196 | // return statement and other statements inside GNU statement |
| 197 | // expressions, so disable the checker inside them to avoid |
| 198 | // false positives. |
| 199 | unless(hasParent(stmtExpr()))); |
| 200 | auto UnusedInIfStmt = |
| 201 | ifStmt(eachOf(hasThen(InnerMatcher: MatchedCallExpr), hasElse(InnerMatcher: MatchedCallExpr))); |
| 202 | auto UnusedInWhileStmt = whileStmt(hasBody(InnerMatcher: MatchedCallExpr)); |
| 203 | auto UnusedInDoStmt = doStmt(hasBody(InnerMatcher: MatchedCallExpr)); |
| 204 | auto UnusedInForStmt = |
| 205 | forStmt(eachOf(hasLoopInit(InnerMatcher: MatchedCallExpr), |
| 206 | hasIncrement(InnerMatcher: MatchedCallExpr), hasBody(InnerMatcher: MatchedCallExpr))); |
| 207 | auto UnusedInRangeForStmt = cxxForRangeStmt(hasBody(InnerMatcher: MatchedCallExpr)); |
| 208 | auto UnusedInCaseStmt = switchCase(forEach(MatchedCallExpr)); |
| 209 | |
| 210 | Finder->addMatcher( |
| 211 | NodeMatch: stmt(anyOf(UnusedInCompoundStmt, UnusedInIfStmt, UnusedInWhileStmt, |
| 212 | UnusedInDoStmt, UnusedInForStmt, UnusedInRangeForStmt, |
| 213 | UnusedInCaseStmt)), |
| 214 | Action: this); |
| 215 | } |
| 216 | |
| 217 | void UnusedReturnValueCheck::check(const MatchFinder::MatchResult &Result) { |
| 218 | if (const auto *Matched = Result.Nodes.getNodeAs<CallExpr>(ID: "match" )) { |
| 219 | diag(Loc: Matched->getBeginLoc(), |
| 220 | Description: "the value returned by this function should not be disregarded; " |
| 221 | "neglecting it may lead to errors" ) |
| 222 | << Matched->getSourceRange(); |
| 223 | |
| 224 | if (!AllowCastToVoid) |
| 225 | return; |
| 226 | |
| 227 | diag(Loc: Matched->getBeginLoc(), |
| 228 | Description: "cast the expression to void to silence this warning" , |
| 229 | Level: DiagnosticIDs::Note); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | } // namespace clang::tidy::bugprone |
| 234 | |