/src/cppcheck/oss-fuzz/build/checkexceptionsafety.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | #include "matchcompiler.h" |
2 | | #include <string> |
3 | | #include <cstring> |
4 | | #include "errorlogger.h" |
5 | | #include "token.h" |
6 | | // pattern: try { |
7 | 0 | static inline bool match1(const Token* tok) { |
8 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("try"))) |
9 | 0 | return false; |
10 | 0 | tok = tok->next(); |
11 | 0 | if (!tok || !((tok->tokType() == Token::eBracket) && tok->str() == MatchCompiler::makeConstString("{"))) |
12 | 0 | return false; |
13 | 0 | return true; |
14 | 0 | } |
15 | | // pattern: if ( ! std :: uncaught_exception ( ) ) { |
16 | 0 | static inline bool match2(const Token* tok) { |
17 | 0 | if (!tok || !((tok->tokType() == Token::eKeyword) && tok->str() == MatchCompiler::makeConstString("if"))) |
18 | 0 | return false; |
19 | 0 | tok = tok->next(); |
20 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString("("))) |
21 | 0 | return false; |
22 | 0 | tok = tok->next(); |
23 | 0 | if (!tok || !((tok->tokType() == Token::eLogicalOp) && tok->str() == MatchCompiler::makeConstString("!"))) |
24 | 0 | return false; |
25 | 0 | tok = tok->next(); |
26 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("std"))) |
27 | 0 | return false; |
28 | 0 | tok = tok->next(); |
29 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("::"))) |
30 | 0 | return false; |
31 | 0 | tok = tok->next(); |
32 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("uncaught_exception"))) |
33 | 0 | return false; |
34 | 0 | tok = tok->next(); |
35 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString("("))) |
36 | 0 | return false; |
37 | 0 | tok = tok->next(); |
38 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString(")"))) |
39 | 0 | return false; |
40 | 0 | tok = tok->next(); |
41 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString(")"))) |
42 | 0 | return false; |
43 | 0 | tok = tok->next(); |
44 | 0 | if (!tok || !((tok->tokType() == Token::eBracket) && tok->str() == MatchCompiler::makeConstString("{"))) |
45 | 0 | return false; |
46 | 0 | return true; |
47 | 0 | } |
48 | | // pattern: [ ] |
49 | 0 | static inline bool match3(const Token* tok) { |
50 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp || tok->tokType() == Token::eLambda) && tok->str() == MatchCompiler::makeConstString("["))) |
51 | 0 | return false; |
52 | 0 | tok = tok->next(); |
53 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp || tok->tokType() == Token::eLambda) && tok->str() == MatchCompiler::makeConstString("]"))) |
54 | 0 | return false; |
55 | 0 | return true; |
56 | 0 | } |
57 | | // pattern: %var% ; |
58 | 0 | static inline bool match4(const Token* tok) { |
59 | 0 | if (!tok || !(tok->varId() != 0)) |
60 | 0 | return false; |
61 | 0 | tok = tok->next(); |
62 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString(";"))) |
63 | 0 | return false; |
64 | 0 | return true; |
65 | 0 | } |
66 | | // pattern: %varid% = |
67 | 0 | static inline bool match5(const Token* tok, const int varid) { |
68 | 0 | if (varid==0U) |
69 | 0 | throw InternalError(tok, "Internal error. Token::Match called with varid 0. Please report this to Cppcheck developers"); |
70 | 0 | if (!tok || !(tok->isName() && tok->varId() == varid)) |
71 | 0 | return false; |
72 | 0 | tok = tok->next(); |
73 | 0 | if (!tok || !((tok->tokType() == Token::eAssignmentOp) && tok->str() == MatchCompiler::makeConstString("="))) |
74 | 0 | return false; |
75 | 0 | return true; |
76 | 0 | } |
77 | | // pattern: [,(] &| %varid% [,)] |
78 | 0 | static inline bool match6(const Token* tok, const int varid) { |
79 | 0 | if (!tok || tok->str().size() != 1U || !strchr(",(", tok->str()[0])) |
80 | 0 | return false; |
81 | 0 | tok = tok->next(); |
82 | 0 | if (tok && (((tok->tokType() == Token::eBitOp) && tok->str() == MatchCompiler::makeConstString("&")))) |
83 | 0 | tok = tok->next(); |
84 | 0 | if (varid==0U) |
85 | 0 | throw InternalError(tok, "Internal error. Token::Match called with varid 0. Please report this to Cppcheck developers"); |
86 | 0 | if (!tok || !(tok->isName() && tok->varId() == varid)) |
87 | 0 | return false; |
88 | 0 | tok = tok->next(); |
89 | 0 | if (!tok || tok->str().size() != 1U || !strchr(",)", tok->str()[0])) |
90 | 0 | return false; |
91 | 0 | return true; |
92 | 0 | } |
93 | | // pattern: catch ( |
94 | 21.5k | static inline bool match7(const Token* tok) { |
95 | 21.5k | if (!tok || !(tok->str() == MatchCompiler::makeConstString("catch"))) |
96 | 21.5k | return false; |
97 | 0 | tok = tok->next(); |
98 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString("("))) |
99 | 0 | return false; |
100 | 0 | return true; |
101 | 0 | } |
102 | | // pattern: %varid% . |
103 | 0 | static inline bool match8(const Token* tok, const int varid) { |
104 | 0 | if (varid==0U) |
105 | 0 | throw InternalError(tok, "Internal error. Token::Match called with varid 0. Please report this to Cppcheck developers"); |
106 | 0 | if (!tok || !(tok->isName() && tok->varId() == varid)) |
107 | 0 | return false; |
108 | 0 | tok = tok->next(); |
109 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("."))) |
110 | 0 | return false; |
111 | 0 | return true; |
112 | 0 | } |
113 | | // pattern: . |
114 | 0 | static inline bool match9(const Token* tok) { |
115 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("."))) |
116 | 0 | return false; |
117 | 0 | return true; |
118 | 0 | } |
119 | | // pattern: %assign%|++|--|( |
120 | 0 | static inline bool match10(const Token* tok) { |
121 | 0 | if (!tok || !(tok->isAssignmentOp() || ((tok->tokType() == Token::eIncDecOp) && tok->str() == MatchCompiler::makeConstString("++")) || ((tok->tokType() == Token::eIncDecOp) && tok->str() == MatchCompiler::makeConstString("--")) || ((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString("(")))) |
122 | 0 | return false; |
123 | 0 | return true; |
124 | 0 | } |
125 | | // pattern: throw %varid% ; |
126 | 0 | static inline bool match11(const Token* tok, const int varid) { |
127 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("throw"))) |
128 | 0 | return false; |
129 | 0 | tok = tok->next(); |
130 | 0 | if (varid==0U) |
131 | 0 | throw InternalError(tok, "Internal error. Token::Match called with varid 0. Please report this to Cppcheck developers"); |
132 | 0 | if (!tok || !(tok->isName() && tok->varId() == varid)) |
133 | 0 | return false; |
134 | 0 | tok = tok->next(); |
135 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString(";"))) |
136 | 0 | return false; |
137 | 0 | return true; |
138 | 0 | } |
139 | | // pattern: try { throw ; } catch ( |
140 | 1.10k | static inline bool match12(const Token* tok) { |
141 | 1.10k | if (!tok || !(tok->str() == MatchCompiler::makeConstString("try"))) |
142 | 1.10k | return false; |
143 | 0 | tok = tok->next(); |
144 | 0 | if (!tok || !((tok->tokType() == Token::eBracket) && tok->str() == MatchCompiler::makeConstString("{"))) |
145 | 0 | return false; |
146 | 0 | tok = tok->next(); |
147 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("throw"))) |
148 | 0 | return false; |
149 | 0 | tok = tok->next(); |
150 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString(";"))) |
151 | 0 | return false; |
152 | 0 | tok = tok->next(); |
153 | 0 | if (!tok || !((tok->tokType() == Token::eBracket) && tok->str() == MatchCompiler::makeConstString("}"))) |
154 | 0 | return false; |
155 | 0 | tok = tok->next(); |
156 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString("catch"))) |
157 | 0 | return false; |
158 | 0 | tok = tok->next(); |
159 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString("("))) |
160 | 0 | return false; |
161 | 0 | return true; |
162 | 0 | } |
163 | | // pattern: ) { |
164 | 0 | static inline bool match13(const Token* tok) { |
165 | 0 | if (!tok || !((tok->tokType() == Token::eExtendedOp) && tok->str() == MatchCompiler::makeConstString(")"))) |
166 | 0 | return false; |
167 | 0 | tok = tok->next(); |
168 | 0 | if (!tok || !((tok->tokType() == Token::eBracket) && tok->str() == MatchCompiler::makeConstString("{"))) |
169 | 0 | return false; |
170 | 0 | return true; |
171 | 0 | } |
172 | | // pattern: throw ; |
173 | 21.5k | static inline bool match14(const Token* tok) { |
174 | 21.5k | if (!tok || !(tok->str() == MatchCompiler::makeConstString("throw"))) |
175 | 21.5k | return false; |
176 | 0 | tok = tok->next(); |
177 | 0 | if (!tok || !(tok->str() == MatchCompiler::makeConstString(";"))) |
178 | 0 | return false; |
179 | 0 | return true; |
180 | 0 | } |
181 | | /* |
182 | | * Cppcheck - A tool for static C/C++ code analysis |
183 | | * Copyright (C) 2007-2024 Cppcheck team. |
184 | | * |
185 | | * This program is free software: you can redistribute it and/or modify |
186 | | * it under the terms of the GNU General Public License as published by |
187 | | * the Free Software Foundation, either version 3 of the License, or |
188 | | * (at your option) any later version. |
189 | | * |
190 | | * This program is distributed in the hope that it will be useful, |
191 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
192 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
193 | | * GNU General Public License for more details. |
194 | | * |
195 | | * You should have received a copy of the GNU General Public License |
196 | | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
197 | | */ |
198 | | |
199 | | //--------------------------------------------------------------------------- |
200 | | #include "checkexceptionsafety.h" |
201 | | |
202 | | #include "astutils.h" |
203 | | #include "errortypes.h" |
204 | | #include "library.h" |
205 | | #include "settings.h" |
206 | | #include "symboldatabase.h" |
207 | | #include "token.h" |
208 | | #include "tokenize.h" |
209 | | |
210 | | #include <list> |
211 | | #include <set> |
212 | | #include <utility> |
213 | | #include <vector> |
214 | | |
215 | | //--------------------------------------------------------------------------- |
216 | | |
217 | | // Register CheckExceptionSafety.. |
218 | | namespace { |
219 | | CheckExceptionSafety instance; |
220 | | } |
221 | | |
222 | | static const CWE CWE398(398U); // Indicator of Poor Code Quality |
223 | | static const CWE CWE703(703U); // Improper Check or Handling of Exceptional Conditions |
224 | | static const CWE CWE480(480U); // Use of Incorrect Operator |
225 | | |
226 | | //--------------------------------------------------------------------------- |
227 | | |
228 | | void CheckExceptionSafety::destructors() |
229 | 763 | { |
230 | 763 | if (!mSettings->severity.isEnabled(Severity::warning)) |
231 | 0 | return; |
232 | | |
233 | 763 | logChecker("CheckExceptionSafety::destructors"); // warning |
234 | | |
235 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
236 | | |
237 | | // Perform check.. |
238 | 1.10k | for (const Scope * scope : symbolDatabase->functionScopes) { |
239 | 1.10k | const Function * function = scope->function; |
240 | 1.10k | if (!function) |
241 | 0 | continue; |
242 | | // only looking for destructors |
243 | 1.10k | if (function->type == Function::eDestructor) { |
244 | | // Inspect this destructor. |
245 | 0 | for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
246 | | // Skip try blocks |
247 | 0 | if (match1(tok)) { |
248 | 0 | tok = tok->linkAt(1); |
249 | 0 | } |
250 | | |
251 | | // Skip uncaught exceptions |
252 | 0 | else if (match2(tok)) { |
253 | 0 | tok = tok->linkAt(1); // end of if ( ... ) |
254 | 0 | tok = tok->linkAt(1); // end of { ... } |
255 | 0 | } |
256 | | // throw found within a destructor |
257 | 0 | else if (tok->str() == MatchCompiler::makeConstString("throw") && function->isNoExcept()) { |
258 | 0 | destructorsError(tok, scope->className); |
259 | 0 | break; |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } |
263 | 1.10k | } |
264 | 763 | } |
265 | | |
266 | | void CheckExceptionSafety::destructorsError(const Token * const tok, const std::string &className) |
267 | 0 | { |
268 | 0 | reportError(tok, Severity::warning, "exceptThrowInDestructor", |
269 | 0 | "Class " + className + " is not safe, destructor throws exception\n" |
270 | 0 | "The class " + className + " is not safe because its destructor " |
271 | 0 | "throws an exception. If " + className + " is used and an exception " |
272 | 0 | "is thrown that is caught in an outer scope the program will terminate.", CWE398, Certainty::normal); |
273 | 0 | } |
274 | | |
275 | | |
276 | | void CheckExceptionSafety::deallocThrow() |
277 | 763 | { |
278 | 763 | if (!mSettings->severity.isEnabled(Severity::warning)) |
279 | 0 | return; |
280 | | |
281 | 763 | logChecker("CheckExceptionSafety::deallocThrow"); // warning |
282 | | |
283 | 763 | const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive); |
284 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
285 | | |
286 | | // Deallocate a global/member pointer and then throw exception |
287 | | // the pointer will be a dead pointer |
288 | 1.10k | for (const Scope * scope : symbolDatabase->functionScopes) { |
289 | 22.6k | for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) { |
290 | | // only looking for delete now |
291 | 21.5k | if (tok->str() != MatchCompiler::makeConstString("delete")) |
292 | 21.5k | continue; |
293 | | |
294 | | // Check if this is something similar with: "delete p;" |
295 | 0 | tok = tok->next(); |
296 | 0 | if (match3(tok)) |
297 | 0 | tok = tok->tokAt(2); |
298 | 0 | if (!tok || tok == scope->bodyEnd) |
299 | 0 | break; |
300 | 0 | if (!match4(tok)) |
301 | 0 | continue; |
302 | | |
303 | | // we only look for global variables |
304 | 0 | const Variable *var = tok->variable(); |
305 | 0 | if (!var || !(var->isGlobal() || var->isStatic())) |
306 | 0 | continue; |
307 | | |
308 | 0 | const unsigned int varid(tok->varId()); |
309 | | |
310 | | // Token where throw occurs |
311 | 0 | const Token *throwToken = nullptr; |
312 | | |
313 | | // is there a throw after the deallocation? |
314 | 0 | const Token* const end2 = tok->scope()->bodyEnd; |
315 | 0 | for (const Token *tok2 = tok; tok2 != end2; tok2 = tok2->next()) { |
316 | | // Throw after delete -> Dead pointer |
317 | 0 | if (tok2->str() == MatchCompiler::makeConstString("throw")) { |
318 | 0 | if (printInconclusive) { // For inconclusive checking, throw directly. |
319 | 0 | deallocThrowError(tok2, tok->str()); |
320 | 0 | break; |
321 | 0 | } |
322 | 0 | throwToken = tok2; |
323 | 0 | } |
324 | | |
325 | | // Variable is assigned -> Bail out |
326 | 0 | else if (match5(tok2, varid)) { |
327 | 0 | if (throwToken) // For non-inconclusive checking, wait until we find an assignment to it. Otherwise we assume it is safe to leave a dead pointer. |
328 | 0 | deallocThrowError(throwToken, tok2->str()); |
329 | 0 | break; |
330 | 0 | } |
331 | | // Variable passed to function. Assume it becomes assigned -> Bail out |
332 | 0 | else if (match6(tok2, varid)) // TODO: No bailout if passed by value or as const reference |
333 | 0 | break; |
334 | 0 | } |
335 | 0 | } |
336 | 1.10k | } |
337 | 763 | } |
338 | | |
339 | | void CheckExceptionSafety::deallocThrowError(const Token * const tok, const std::string &varname) |
340 | 0 | { |
341 | 0 | reportError(tok, Severity::warning, "exceptDeallocThrow", "Exception thrown in invalid state, '" + |
342 | 0 | varname + "' points at deallocated memory.", CWE398, Certainty::normal); |
343 | 0 | } |
344 | | |
345 | | //--------------------------------------------------------------------------- |
346 | | // catch(const exception & err) |
347 | | // { |
348 | | // throw err; // <- should be just "throw;" |
349 | | // } |
350 | | //--------------------------------------------------------------------------- |
351 | | void CheckExceptionSafety::checkRethrowCopy() |
352 | 763 | { |
353 | 763 | if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("exceptRethrowCopy")) |
354 | 0 | return; |
355 | | |
356 | 763 | logChecker("CheckExceptionSafety::checkRethrowCopy"); // style |
357 | | |
358 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
359 | | |
360 | 2.79k | for (const Scope &scope : symbolDatabase->scopeList) { |
361 | 2.79k | if (scope.type != Scope::eCatch) |
362 | 2.79k | continue; |
363 | | |
364 | 0 | const unsigned int varid = scope.bodyStart->tokAt(-2)->varId(); |
365 | 0 | if (varid) { |
366 | 0 | for (const Token* tok = scope.bodyStart->next(); tok && tok != scope.bodyEnd; tok = tok->next()) { |
367 | 0 | if (match7(tok) && tok->linkAt(1) && tok->linkAt(1)->next()) { // Don't check inner catch - it is handled in another iteration of outer loop. |
368 | 0 | tok = tok->linkAt(1)->linkAt(1); |
369 | 0 | if (!tok) |
370 | 0 | break; |
371 | 0 | } else if (match8(tok, varid)) { |
372 | 0 | const Token *parent = tok->astParent(); |
373 | 0 | while (match9(parent->astParent())) |
374 | 0 | parent = parent->astParent(); |
375 | 0 | if (match10(parent->astParent()) && parent == parent->astParent()->astOperand1()) |
376 | 0 | break; |
377 | 0 | } else if (match11(tok, varid)) |
378 | 0 | rethrowCopyError(tok, tok->strAt(1)); |
379 | 0 | } |
380 | 0 | } |
381 | 0 | } |
382 | 763 | } |
383 | | |
384 | | void CheckExceptionSafety::rethrowCopyError(const Token * const tok, const std::string &varname) |
385 | 0 | { |
386 | 0 | reportError(tok, Severity::style, "exceptRethrowCopy", |
387 | 0 | "Throwing a copy of the caught exception instead of rethrowing the original exception.\n" |
388 | 0 | "Rethrowing an exception with 'throw " + varname + ";' creates an unnecessary copy of '" + varname + "'. " |
389 | 0 | "To rethrow the caught exception without unnecessary copying or slicing, use a bare 'throw;'.", CWE398, Certainty::normal); |
390 | 0 | } |
391 | | |
392 | | //--------------------------------------------------------------------------- |
393 | | // try {} catch (std::exception err) {} <- Should be "std::exception& err" |
394 | | //--------------------------------------------------------------------------- |
395 | | void CheckExceptionSafety::checkCatchExceptionByValue() |
396 | 763 | { |
397 | 763 | if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("catchExceptionByValue")) |
398 | 0 | return; |
399 | | |
400 | 763 | logChecker("CheckExceptionSafety::checkCatchExceptionByValue"); // style |
401 | | |
402 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
403 | | |
404 | 2.79k | for (const Scope &scope : symbolDatabase->scopeList) { |
405 | 2.79k | if (scope.type != Scope::eCatch) |
406 | 2.79k | continue; |
407 | | |
408 | | // Find a pass-by-value declaration in the catch(), excluding basic types |
409 | | // e.g. catch (std::exception err) |
410 | 0 | const Variable *var = scope.bodyStart->tokAt(-2)->variable(); |
411 | 0 | if (var && var->isClass() && !var->isPointer() && !var->isReference()) |
412 | 0 | catchExceptionByValueError(scope.classDef); |
413 | 0 | } |
414 | 763 | } |
415 | | |
416 | | void CheckExceptionSafety::catchExceptionByValueError(const Token *tok) |
417 | 0 | { |
418 | 0 | reportError(tok, Severity::style, |
419 | 0 | "catchExceptionByValue", "Exception should be caught by reference.\n" |
420 | 0 | "The exception is caught by value. It could be caught " |
421 | 0 | "as a (const) reference which is usually recommended in C++.", CWE398, Certainty::normal); |
422 | 0 | } |
423 | | |
424 | | static const Token * functionThrowsRecursive(const Function * function, std::set<const Function *> & recursive) |
425 | 0 | { |
426 | | // check for recursion and bail if found |
427 | 0 | if (!recursive.insert(function).second) |
428 | 0 | return nullptr; |
429 | | |
430 | 0 | if (!function->functionScope) |
431 | 0 | return nullptr; |
432 | | |
433 | 0 | for (const Token *tok = function->functionScope->bodyStart->next(); |
434 | 0 | tok != function->functionScope->bodyEnd; tok = tok->next()) { |
435 | 0 | tok = skipUnreachableBranch(tok); |
436 | 0 | if (match1(tok)) |
437 | 0 | tok = tok->linkAt(1); // skip till start of catch clauses |
438 | 0 | if (tok->str() == MatchCompiler::makeConstString("throw")) |
439 | 0 | return tok; |
440 | 0 | if (tok->function()) { |
441 | 0 | const Function * called = tok->function(); |
442 | | // check if called function has an exception specification |
443 | 0 | if (called->isThrow() && called->throwArg) |
444 | 0 | return tok; |
445 | 0 | if (called->isNoExcept() && called->noexceptArg && |
446 | 0 | called->noexceptArg->str() != MatchCompiler::makeConstString("true")) |
447 | 0 | return tok; |
448 | 0 | if (functionThrowsRecursive(called, recursive)) |
449 | 0 | return tok; |
450 | 0 | } |
451 | 0 | } |
452 | | |
453 | 0 | return nullptr; |
454 | 0 | } |
455 | | |
456 | | static const Token * functionThrows(const Function * function) |
457 | 0 | { |
458 | 0 | std::set<const Function *> recursive; |
459 | |
|
460 | 0 | return functionThrowsRecursive(function, recursive); |
461 | 0 | } |
462 | | |
463 | | //-------------------------------------------------------------------------- |
464 | | // void func() noexcept { throw x; } |
465 | | // void func() throw() { throw x; } |
466 | | // void func() __attribute__((nothrow)); void func() { throw x; } |
467 | | //-------------------------------------------------------------------------- |
468 | | void CheckExceptionSafety::nothrowThrows() |
469 | 763 | { |
470 | 763 | logChecker("CheckExceptionSafety::nothrowThrows"); |
471 | | |
472 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
473 | | |
474 | 1.10k | for (const Scope * scope : symbolDatabase->functionScopes) { |
475 | 1.10k | const Function* function = scope->function; |
476 | 1.10k | if (!function) |
477 | 0 | continue; |
478 | | |
479 | | // check noexcept and noexcept(true) functions |
480 | 1.10k | if (function->isNoExcept()) { |
481 | 0 | const Token *throws = functionThrows(function); |
482 | 0 | if (throws) |
483 | 0 | noexceptThrowError(throws); |
484 | 0 | } |
485 | | |
486 | | // check throw() functions |
487 | 1.10k | else if (function->isThrow() && !function->throwArg) { |
488 | 0 | const Token *throws = functionThrows(function); |
489 | 0 | if (throws) |
490 | 0 | noexceptThrowError(throws); |
491 | 0 | } |
492 | | |
493 | | // check __attribute__((nothrow)) or __declspec(nothrow) functions |
494 | 1.10k | else if (function->isAttributeNothrow()) { |
495 | 0 | const Token *throws = functionThrows(function); |
496 | 0 | if (throws) |
497 | 0 | noexceptThrowError(throws); |
498 | 0 | } |
499 | 1.10k | } |
500 | 763 | } |
501 | | |
502 | | void CheckExceptionSafety::noexceptThrowError(const Token * const tok) |
503 | 0 | { |
504 | 0 | reportError(tok, Severity::error, "throwInNoexceptFunction", "Exception thrown in function declared not to throw exceptions.", CWE398, Certainty::normal); |
505 | 0 | } |
506 | | |
507 | | //-------------------------------------------------------------------------- |
508 | | // void func() { functionWithExceptionSpecification(); } |
509 | | //-------------------------------------------------------------------------- |
510 | | void CheckExceptionSafety::unhandledExceptionSpecification() |
511 | 763 | { |
512 | 763 | if ((!mSettings->severity.isEnabled(Severity::style) || !mSettings->certainty.isEnabled(Certainty::inconclusive)) && |
513 | 763 | !mSettings->isPremiumEnabled("unhandledExceptionSpecification")) |
514 | 0 | return; |
515 | | |
516 | 763 | logChecker("CheckExceptionSafety::unhandledExceptionSpecification"); // style,inconclusive |
517 | | |
518 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
519 | | |
520 | 1.10k | for (const Scope * scope : symbolDatabase->functionScopes) { |
521 | | // only check functions without exception specification |
522 | 1.10k | if (scope->function && !scope->function->isThrow() && !mSettings->library.isentrypoint(scope->className)) { |
523 | 1.10k | for (const Token *tok = scope->function->functionScope->bodyStart->next(); |
524 | 22.6k | tok != scope->function->functionScope->bodyEnd; tok = tok->next()) { |
525 | 21.5k | if (tok->str() == MatchCompiler::makeConstString("try")) |
526 | 0 | break; |
527 | 21.5k | if (tok->function()) { |
528 | 0 | const Function * called = tok->function(); |
529 | | // check if called function has an exception specification |
530 | 0 | if (called->isThrow() && called->throwArg) { |
531 | 0 | unhandledExceptionSpecificationError(tok, called->tokenDef, scope->function->name()); |
532 | 0 | break; |
533 | 0 | } |
534 | 0 | } |
535 | 21.5k | } |
536 | 1.10k | } |
537 | 1.10k | } |
538 | 763 | } |
539 | | |
540 | | void CheckExceptionSafety::unhandledExceptionSpecificationError(const Token * const tok1, const Token * const tok2, const std::string & funcname) |
541 | 0 | { |
542 | 0 | const std::string str1(tok1 ? tok1->str() : "foo"); |
543 | 0 | const std::list<const Token*> locationList = { tok1, tok2 }; |
544 | 0 | reportError(locationList, Severity::style, "unhandledExceptionSpecification", |
545 | 0 | "Unhandled exception specification when calling function " + str1 + "().\n" |
546 | 0 | "Unhandled exception specification when calling function " + str1 + "(). " |
547 | 0 | "Either use a try/catch around the function call, or add a exception specification for " + funcname + "() also.", CWE703, Certainty::inconclusive); |
548 | 0 | } |
549 | | |
550 | | //-------------------------------------------------------------------------- |
551 | | // 7.6.18.4 If no exception is presently being handled, evaluating a throw-expression with no operand calls std::terminate(). |
552 | | //-------------------------------------------------------------------------- |
553 | | void CheckExceptionSafety::rethrowNoCurrentException() |
554 | 763 | { |
555 | 763 | logChecker("CheckExceptionSafety::rethrowNoCurrentException"); |
556 | 763 | const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase(); |
557 | 1.10k | for (const Scope * scope : symbolDatabase->functionScopes) { |
558 | 1.10k | const Function* function = scope->function; |
559 | 1.10k | if (!function) |
560 | 0 | continue; |
561 | | |
562 | | // Rethrow can be used in 'exception dispatcher' idiom which is FP in such case |
563 | | // https://isocpp.org/wiki/faq/exceptions#throw-without-an-object |
564 | | // We check the beginning of the function with idiom pattern |
565 | 1.10k | if (match12(function->functionScope->bodyStart->next())) |
566 | 0 | continue; |
567 | | |
568 | 1.10k | for (const Token *tok = function->functionScope->bodyStart->next(); |
569 | 22.6k | tok != function->functionScope->bodyEnd; tok = tok->next()) { |
570 | 21.5k | if (match7(tok)) { |
571 | 0 | tok = tok->linkAt(1); // skip catch argument |
572 | 0 | if (match13(tok)) |
573 | 0 | tok = tok->linkAt(1); // skip catch scope |
574 | 0 | else |
575 | 0 | break; |
576 | 0 | } |
577 | 21.5k | if (match14(tok)) { |
578 | 0 | rethrowNoCurrentExceptionError(tok); |
579 | 0 | } |
580 | 21.5k | } |
581 | 1.10k | } |
582 | 763 | } |
583 | | |
584 | | void CheckExceptionSafety::rethrowNoCurrentExceptionError(const Token *tok) |
585 | 0 | { |
586 | 0 | reportError(tok, Severity::error, "rethrowNoCurrentException", |
587 | 0 | "Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow." |
588 | 0 | " If there is no current exception this calls std::terminate()." |
589 | 0 | " More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object", |
590 | 0 | CWE480, Certainty::normal); |
591 | 0 | } |
592 | | |
593 | | void CheckExceptionSafety::runChecks(const Tokenizer &tokenizer, ErrorLogger *errorLogger) |
594 | 763 | { |
595 | 763 | if (tokenizer.isC()) |
596 | 0 | return; |
597 | | |
598 | 763 | CheckExceptionSafety checkExceptionSafety(&tokenizer, &tokenizer.getSettings(), errorLogger); |
599 | 763 | checkExceptionSafety.destructors(); |
600 | 763 | checkExceptionSafety.deallocThrow(); |
601 | 763 | checkExceptionSafety.checkRethrowCopy(); |
602 | 763 | checkExceptionSafety.checkCatchExceptionByValue(); |
603 | 763 | checkExceptionSafety.nothrowThrows(); |
604 | 763 | checkExceptionSafety.unhandledExceptionSpecification(); |
605 | 763 | checkExceptionSafety.rethrowNoCurrentException(); |
606 | 763 | } |
607 | | |
608 | | void CheckExceptionSafety::getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const |
609 | 0 | { |
610 | 0 | CheckExceptionSafety c(nullptr, settings, errorLogger); |
611 | 0 | c.destructorsError(nullptr, "Class"); |
612 | 0 | c.deallocThrowError(nullptr, "p"); |
613 | 0 | c.rethrowCopyError(nullptr, "varname"); |
614 | 0 | c.catchExceptionByValueError(nullptr); |
615 | 0 | c.noexceptThrowError(nullptr); |
616 | 0 | c.unhandledExceptionSpecificationError(nullptr, nullptr, "funcname"); |
617 | 0 | c.rethrowNoCurrentExceptionError(nullptr); |
618 | 0 | } |