Cppcheck
checkexceptionsafety.cpp
Go to the documentation of this file.
1 /*
2  * Cppcheck - A tool for static C/C++ code analysis
3  * Copyright (C) 2007-2024 Cppcheck team.
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 //---------------------------------------------------------------------------
20 #include "checkexceptionsafety.h"
21 
22 #include "errortypes.h"
23 #include "library.h"
24 #include "settings.h"
25 #include "symboldatabase.h"
26 #include "token.h"
27 
28 #include <list>
29 #include <set>
30 #include <utility>
31 #include <vector>
32 
33 //---------------------------------------------------------------------------
34 
35 // Register CheckExceptionSafety..
36 namespace {
37  CheckExceptionSafety instance;
38 }
39 
40 static const CWE CWE398(398U); // Indicator of Poor Code Quality
41 static const CWE CWE703(703U); // Improper Check or Handling of Exceptional Conditions
42 static const CWE CWE480(480U); // Use of Incorrect Operator
43 
44 //---------------------------------------------------------------------------
45 
47 {
49  return;
50 
51  logChecker("CheckExceptionSafety::destructors"); // warning
52 
53  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
54 
55  // Perform check..
56  for (const Scope * scope : symbolDatabase->functionScopes) {
57  const Function * function = scope->function;
58  if (!function)
59  continue;
60  // only looking for destructors
61  if (function->type == Function::eDestructor) {
62  // Inspect this destructor.
63  for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
64  // Skip try blocks
65  if (Token::simpleMatch(tok, "try {")) {
66  tok = tok->next()->link();
67  }
68 
69  // Skip uncaught exceptions
70  else if (Token::simpleMatch(tok, "if ( ! std :: uncaught_exception ( ) ) {")) {
71  tok = tok->next()->link(); // end of if ( ... )
72  tok = tok->next()->link(); // end of { ... }
73  }
74 
75  // throw found within a destructor
76  else if (tok->str() == "throw") {
77  destructorsError(tok, scope->className);
78  break;
79  }
80  }
81  }
82  }
83 }
84 
85 void CheckExceptionSafety::destructorsError(const Token * const tok, const std::string &className)
86 {
87  reportError(tok, Severity::warning, "exceptThrowInDestructor",
88  "Class " + className + " is not safe, destructor throws exception\n"
89  "The class " + className + " is not safe because its destructor "
90  "throws an exception. If " + className + " is used and an exception "
91  "is thrown that is caught in an outer scope the program will terminate.", CWE398, Certainty::normal);
92 }
93 
94 
96 {
98  return;
99 
100  logChecker("CheckExceptionSafety::deallocThrow"); // warning
101 
102  const bool printInconclusive = mSettings->certainty.isEnabled(Certainty::inconclusive);
103  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
104 
105  // Deallocate a global/member pointer and then throw exception
106  // the pointer will be a dead pointer
107  for (const Scope * scope : symbolDatabase->functionScopes) {
108  for (const Token *tok = scope->bodyStart->next(); tok != scope->bodyEnd; tok = tok->next()) {
109  // only looking for delete now
110  if (tok->str() != "delete")
111  continue;
112 
113  // Check if this is something similar with: "delete p;"
114  tok = tok->next();
115  if (Token::simpleMatch(tok, "[ ]"))
116  tok = tok->tokAt(2);
117  if (!tok || tok == scope->bodyEnd)
118  break;
119  if (!Token::Match(tok, "%var% ;"))
120  continue;
121 
122  // we only look for global variables
123  const Variable *var = tok->variable();
124  if (!var || !(var->isGlobal() || var->isStatic()))
125  continue;
126 
127  const unsigned int varid(tok->varId());
128 
129  // Token where throw occurs
130  const Token *throwToken = nullptr;
131 
132  // is there a throw after the deallocation?
133  const Token* const end2 = tok->scope()->bodyEnd;
134  for (const Token *tok2 = tok; tok2 != end2; tok2 = tok2->next()) {
135  // Throw after delete -> Dead pointer
136  if (tok2->str() == "throw") {
137  if (printInconclusive) { // For inconclusive checking, throw directly.
138  deallocThrowError(tok2, tok->str());
139  break;
140  }
141  throwToken = tok2;
142  }
143 
144  // Variable is assigned -> Bail out
145  else if (Token::Match(tok2, "%varid% =", varid)) {
146  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.
147  deallocThrowError(throwToken, tok2->str());
148  break;
149  }
150  // Variable passed to function. Assume it becomes assigned -> Bail out
151  else if (Token::Match(tok2, "[,(] &| %varid% [,)]", varid)) // TODO: No bailout if passed by value or as const reference
152  break;
153  }
154  }
155  }
156 }
157 
158 void CheckExceptionSafety::deallocThrowError(const Token * const tok, const std::string &varname)
159 {
160  reportError(tok, Severity::warning, "exceptDeallocThrow", "Exception thrown in invalid state, '" +
161  varname + "' points at deallocated memory.", CWE398, Certainty::normal);
162 }
163 
164 //---------------------------------------------------------------------------
165 // catch(const exception & err)
166 // {
167 // throw err; // <- should be just "throw;"
168 // }
169 //---------------------------------------------------------------------------
171 {
173  return;
174 
175  logChecker("CheckExceptionSafety::checkRethrowCopy"); // style
176 
177  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
178 
179  for (const Scope &scope : symbolDatabase->scopeList) {
180  if (scope.type != Scope::eCatch)
181  continue;
182 
183  const unsigned int varid = scope.bodyStart->tokAt(-2)->varId();
184  if (varid) {
185  for (const Token* tok = scope.bodyStart->next(); tok && tok != scope.bodyEnd; tok = tok->next()) {
186  if (Token::simpleMatch(tok, "catch (") && tok->next()->link() && tok->next()->link()->next()) { // Don't check inner catch - it is handled in another iteration of outer loop.
187  tok = tok->next()->link()->next()->link();
188  if (!tok)
189  break;
190  } else if (Token::Match(tok, "%varid% .", varid)) {
191  const Token *parent = tok->astParent();
192  while (Token::simpleMatch(parent->astParent(), "."))
193  parent = parent->astParent();
194  if (Token::Match(parent->astParent(), "%assign%|++|--|(") && parent == parent->astParent()->astOperand1())
195  break;
196  } else if (Token::Match(tok, "throw %varid% ;", varid))
197  rethrowCopyError(tok, tok->strAt(1));
198  }
199  }
200  }
201 }
202 
203 void CheckExceptionSafety::rethrowCopyError(const Token * const tok, const std::string &varname)
204 {
205  reportError(tok, Severity::style, "exceptRethrowCopy",
206  "Throwing a copy of the caught exception instead of rethrowing the original exception.\n"
207  "Rethrowing an exception with 'throw " + varname + ";' creates an unnecessary copy of '" + varname + "'. "
208  "To rethrow the caught exception without unnecessary copying or slicing, use a bare 'throw;'.", CWE398, Certainty::normal);
209 }
210 
211 //---------------------------------------------------------------------------
212 // try {} catch (std::exception err) {} <- Should be "std::exception& err"
213 //---------------------------------------------------------------------------
215 {
216  if (!mSettings->severity.isEnabled(Severity::style) && !mSettings->isPremiumEnabled("catchexceptionbyvalue"))
217  return;
218 
219  logChecker("CheckExceptionSafety::checkCatchExceptionByValue"); // style
220 
221  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
222 
223  for (const Scope &scope : symbolDatabase->scopeList) {
224  if (scope.type != Scope::eCatch)
225  continue;
226 
227  // Find a pass-by-value declaration in the catch(), excluding basic types
228  // e.g. catch (std::exception err)
229  const Variable *var = scope.bodyStart->tokAt(-2)->variable();
230  if (var && var->isClass() && !var->isPointer() && !var->isReference())
232  }
233 }
234 
236 {
238  "catchExceptionByValue", "Exception should be caught by reference.\n"
239  "The exception is caught by value. It could be caught "
240  "as a (const) reference which is usually recommended in C++.", CWE398, Certainty::normal);
241 }
242 
243 
244 static const Token * functionThrowsRecursive(const Function * function, std::set<const Function *> & recursive)
245 {
246  // check for recursion and bail if found
247  if (!recursive.insert(function).second)
248  return nullptr;
249 
250  if (!function->functionScope)
251  return nullptr;
252 
253  for (const Token *tok = function->functionScope->bodyStart->next();
254  tok != function->functionScope->bodyEnd; tok = tok->next()) {
255  if (Token::simpleMatch(tok, "try {"))
256  tok = tok->linkAt(1); // skip till start of catch clauses
257  if (tok->str() == "throw")
258  return tok;
259  if (tok->function()) {
260  const Function * called = tok->function();
261  // check if called function has an exception specification
262  if (called->isThrow() && called->throwArg)
263  return tok;
264  if (called->isNoExcept() && called->noexceptArg &&
265  called->noexceptArg->str() != "true")
266  return tok;
267  if (functionThrowsRecursive(called, recursive))
268  return tok;
269  }
270  }
271 
272  return nullptr;
273 }
274 
275 static const Token * functionThrows(const Function * function)
276 {
277  std::set<const Function *> recursive;
278 
279  return functionThrowsRecursive(function, recursive);
280 }
281 
282 //--------------------------------------------------------------------------
283 // void func() noexcept { throw x; }
284 // void func() throw() { throw x; }
285 // void func() __attribute__((nothrow)); void func() { throw x; }
286 //--------------------------------------------------------------------------
288 {
289  logChecker("CheckExceptionSafety::nothrowThrows");
290 
291  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
292 
293  for (const Scope * scope : symbolDatabase->functionScopes) {
294  const Function* function = scope->function;
295  if (!function)
296  continue;
297 
298  // check noexcept and noexcept(true) functions
299  if (function->isNoExcept() &&
300  (!function->noexceptArg || function->noexceptArg->str() == "true")) {
301  const Token *throws = functionThrows(function);
302  if (throws)
303  noexceptThrowError(throws);
304  }
305 
306  // check throw() functions
307  else if (function->isThrow() && !function->throwArg) {
308  const Token *throws = functionThrows(function);
309  if (throws)
310  noexceptThrowError(throws);
311  }
312 
313  // check __attribute__((nothrow)) or __declspec(nothrow) functions
314  else if (function->isAttributeNothrow()) {
315  const Token *throws = functionThrows(function);
316  if (throws)
317  noexceptThrowError(throws);
318  }
319  }
320 }
321 
323 {
324  reportError(tok, Severity::error, "throwInNoexceptFunction", "Exception thrown in function declared not to throw exceptions.", CWE398, Certainty::normal);
325 }
326 
327 //--------------------------------------------------------------------------
328 // void func() { functionWithExceptionSpecification(); }
329 //--------------------------------------------------------------------------
331 {
333  !mSettings->isPremiumEnabled("unhandledexceptionspecification"))
334  return;
335 
336  logChecker("CheckExceptionSafety::unhandledExceptionSpecification"); // style,inconclusive
337 
338  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
339 
340  for (const Scope * scope : symbolDatabase->functionScopes) {
341  // only check functions without exception specification
342  if (scope->function && !scope->function->isThrow() && !mSettings->library.isentrypoint(scope->className)) {
343  for (const Token *tok = scope->function->functionScope->bodyStart->next();
344  tok != scope->function->functionScope->bodyEnd; tok = tok->next()) {
345  if (tok->str() == "try")
346  break;
347  if (tok->function()) {
348  const Function * called = tok->function();
349  // check if called function has an exception specification
350  if (called->isThrow() && called->throwArg) {
352  break;
353  }
354  }
355  }
356  }
357  }
358 }
359 
360 void CheckExceptionSafety::unhandledExceptionSpecificationError(const Token * const tok1, const Token * const tok2, const std::string & funcname)
361 {
362  const std::string str1(tok1 ? tok1->str() : "foo");
363  const std::list<const Token*> locationList = { tok1, tok2 };
364  reportError(locationList, Severity::style, "unhandledExceptionSpecification",
365  "Unhandled exception specification when calling function " + str1 + "().\n"
366  "Unhandled exception specification when calling function " + str1 + "(). "
367  "Either use a try/catch around the function call, or add a exception specification for " + funcname + "() also.", CWE703, Certainty::inconclusive);
368 }
369 
370 //--------------------------------------------------------------------------
371 // 7.6.18.4 If no exception is presently being handled, evaluating a throw-expression with no operand calls std​::​​terminate().
372 //--------------------------------------------------------------------------
374 {
375  logChecker("CheckExceptionSafety::rethrowNoCurrentException");
376  const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
377  for (const Scope * scope : symbolDatabase->functionScopes) {
378  const Function* function = scope->function;
379  if (!function)
380  continue;
381 
382  // Rethrow can be used in 'exception dispatcher' idiom which is FP in such case
383  // https://isocpp.org/wiki/faq/exceptions#throw-without-an-object
384  // We check the beginning of the function with idiom pattern
385  if (Token::simpleMatch(function->functionScope->bodyStart->next(), "try { throw ; } catch ("))
386  continue;
387 
388  for (const Token *tok = function->functionScope->bodyStart->next();
389  tok != function->functionScope->bodyEnd; tok = tok->next()) {
390  if (Token::simpleMatch(tok, "catch (")) {
391  tok = tok->linkAt(1); // skip catch argument
392  if (Token::simpleMatch(tok, ") {"))
393  tok = tok->linkAt(1); // skip catch scope
394  else
395  break;
396  }
397  if (Token::simpleMatch(tok, "throw ;")) {
399  }
400  }
401  }
402 }
403 
405 {
406  reportError(tok, Severity::error, "rethrowNoCurrentException",
407  "Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow."
408  " If there is no current exception this calls std::terminate()."
409  " More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object",
411 }
static const CWE CWE398(398U)
static const Token * functionThrowsRecursive(const Function *function, std::set< const Function * > &recursive)
static const Token * functionThrows(const Function *function)
static const CWE CWE480(480U)
static const CWE CWE703(703U)
Check exception safety (exceptions shouldn't cause leaks nor corrupt data)
void unhandledExceptionSpecification()
Check for unhandled exception specification
void destructors()
Don't throw exceptions in destructors.
void nothrowThrows()
Check for functions that throw that shouldn't
void deallocThrowError(const Token *const tok, const std::string &varname)
void rethrowCopyError(const Token *const tok, const std::string &varname)
void noexceptThrowError(const Token *const tok)
void rethrowNoCurrentException()
Check for rethrow not from catch scope
void checkCatchExceptionByValue()
Check for exceptions that are caught by value instead of by reference
void catchExceptionByValueError(const Token *tok)
void deallocThrow()
deallocating memory and then throw (dead pointer)
void rethrowNoCurrentExceptionError(const Token *tok)
Rethrow without currently handled exception.
void checkRethrowCopy()
Don't rethrow a copy of the caught exception; use a bare throw instead.
void unhandledExceptionSpecificationError(const Token *const tok1, const Token *const tok2, const std::string &funcname)
Missing exception specification.
void destructorsError(const Token *const tok, const std::string &className)
Don't throw exceptions in destructors.
void reportError(const Token *tok, const Severity severity, const std::string &id, const std::string &msg)
report an error
Definition: check.h:138
const Settings *const mSettings
Definition: check.h:134
const Tokenizer *const mTokenizer
Definition: check.h:133
void logChecker(const char id[])
log checker
Definition: check.cpp:129
const std::string & name() const
const Scope * functionScope
scope of function body
bool isThrow() const
const Token * throwArg
throw token
const Token * tokenDef
function name token in class definition
const Token * noexceptArg
noexcept token
bool isNoExcept() const
bool isentrypoint(const std::string &func) const
Definition: library.h:427
ScopeType type
Function * function
function info for this function
const Token * classDef
class/struct/union/namespace token
const Token * bodyStart
'{' token
const Token * bodyEnd
'}' token
std::string className
Library library
Library.
Definition: settings.h:237
bool isPremiumEnabled(const char id[]) const
Is checker id enabled by premiumArgs.
Definition: settings.cpp:557
SimpleEnableGroup< Certainty > certainty
Definition: settings.h:359
SimpleEnableGroup< Severity > severity
Definition: settings.h:358
bool isEnabled(T flag) const
Definition: settings.h:66
std::vector< const Scope * > functionScopes
Fast access to function scopes.
std::list< Scope > scopeList
Information about all namespaces/classes/structures.
The token list that the TokenList generates is a linked-list of this class.
Definition: token.h:150
void str(T &&s)
Definition: token.h:179
static bool Match(const Token *tok, const char pattern[], nonneg int varid=0)
Match given token (or list of tokens) to a pattern list.
Definition: token.cpp:722
nonneg int varId() const
Definition: token.h:870
const Token * tokAt(int index) const
Definition: token.cpp:427
void scope(const Scope *s)
Associate this token with given scope.
Definition: token.h:1042
const Token * linkAt(int index) const
Definition: token.cpp:447
void variable(const Variable *v)
Associate this token with given variable.
Definition: token.h:1070
Token * next()
Definition: token.h:830
static bool simpleMatch(const Token *tok, const char(&pattern)[count])
Match given token (or list of tokens) to a pattern list.
Definition: token.h:252
void astParent(Token *tok)
Definition: token.cpp:1471
const SymbolDatabase * getSymbolDatabase() const
Definition: tokenize.h:563
Information about a member variable.
bool isClass() const
Is variable a user defined (or unknown) type.
bool isReference() const
Is reference variable.
bool isGlobal() const
Is variable global.
bool isPointer() const
Is pointer variable.
bool isStatic() const
Is variable static.
@ warning
Warning.
@ style
Style warning.
@ error
Programming error.