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 | |