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