2009-10-19 20:57:11 +02:00
/*
* Cppcheck - A tool for static C / C + + code analysis
2022-02-05 11:45:17 +01:00
* Copyright ( C ) 2007 - 2022 Cppcheck team .
2009-10-19 20:57:11 +02:00
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
//---------------------------------------------------------------------------
# include "checkexceptionsafety.h"
2017-05-27 04:33:47 +02:00
# include "settings.h"
2011-12-09 19:53:00 +01:00
# include "symboldatabase.h"
2022-01-27 19:03:20 +01:00
# include "token.h"
2009-10-19 20:57:11 +02:00
2022-01-27 19:03:20 +01:00
# include <list>
2017-05-27 04:33:47 +02:00
# include <set>
# include <utility>
2022-01-27 19:03:20 +01:00
# include <vector>
2017-05-27 04:33:47 +02:00
2009-10-19 20:57:11 +02:00
//---------------------------------------------------------------------------
// Register CheckExceptionSafety..
2011-10-13 20:53:06 +02:00
namespace {
CheckExceptionSafety instance ;
2009-10-19 20:57:11 +02:00
}
//---------------------------------------------------------------------------
void CheckExceptionSafety : : destructors ( )
{
2021-02-24 22:00:06 +01:00
if ( ! mSettings - > severity . isEnabled ( Severity : : warning ) )
2014-07-19 03:29:50 +02:00
return ;
2018-06-16 16:10:28 +02:00
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
2009-10-19 20:57:11 +02:00
2011-12-09 19:53:00 +01:00
// Perform check..
2018-07-14 09:49:03 +02:00
for ( const Scope * scope : symbolDatabase - > functionScopes ) {
2014-07-14 10:20:00 +02:00
const Function * function = scope - > function ;
2018-07-14 09:49:03 +02:00
if ( ! function )
continue ;
// only looking for destructors
if ( function - > type = = Function : : eDestructor ) {
// Inspect this destructor.
for ( const Token * tok = scope - > bodyStart - > next ( ) ; tok ! = scope - > bodyEnd ; tok = tok - > next ( ) ) {
// Skip try blocks
if ( Token : : simpleMatch ( tok , " try { " ) ) {
tok = tok - > next ( ) - > link ( ) ;
}
2012-06-17 14:33:18 +02:00
2018-07-14 09:49:03 +02:00
// Skip uncaught exceptions
else if ( Token : : simpleMatch ( tok , " if ( ! std :: uncaught_exception ( ) ) { " )) {
tok = tok - > next ( ) - > link ( ) ; // end of if ( ... )
tok = tok - > next ( ) - > link ( ) ; // end of { ... }
}
2014-05-20 06:10:34 +02:00
2018-07-14 09:49:03 +02:00
// throw found within a destructor
else if ( tok - > str ( ) = = " throw " ) {
destructorsError ( tok , scope - > className ) ;
break ;
2011-12-09 19:53:00 +01:00
}
2009-10-19 20:57:11 +02:00
}
}
}
}
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : destructorsError ( const Token * const tok , const std : : string & className )
{
2021-06-02 07:00:10 +02:00
reportError ( tok , Severity : : warning , " exceptThrowInDestructor " ,
" Class " + className + " is not safe, destructor throws exception \n "
" The class " + className + " is not safe because its destructor "
" throws an exception. If " + className + " is used and an exception "
" is thrown that is caught in an outer scope the program will terminate. " , CWE398 , Certainty : : normal ) ;
}
2009-11-08 09:54:08 +01:00
void CheckExceptionSafety : : deallocThrow ( )
{
2021-02-24 22:00:06 +01:00
if ( ! mSettings - > severity . isEnabled ( Severity : : warning ) )
2012-01-21 19:11:06 +01:00
return ;
2021-02-24 22:00:06 +01:00
const bool printInconclusive = mSettings - > certainty . isEnabled ( Certainty : : inconclusive ) ;
2018-06-16 16:10:28 +02:00
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
2011-12-09 19:53:00 +01:00
2010-12-31 13:58:17 +01:00
// Deallocate a global/member pointer and then throw exception
// the pointer will be a dead pointer
2018-07-14 09:49:03 +02:00
for ( const Scope * scope : symbolDatabase - > functionScopes ) {
2018-04-27 22:36:30 +02:00
for ( const Token * tok = scope - > bodyStart - > next ( ) ; tok ! = scope - > bodyEnd ; tok = tok - > next ( ) ) {
2012-10-14 17:40:17 +02:00
// only looking for delete now
if ( tok - > str ( ) ! = " delete " )
continue ;
// Check if this is something similar with: "delete p;"
tok = tok - > next ( ) ;
if ( Token : : simpleMatch ( tok , " [ ] " ) )
tok = tok - > tokAt ( 2 ) ;
2018-04-27 22:36:30 +02:00
if ( ! tok | | tok = = scope - > bodyEnd )
2012-10-14 17:40:17 +02:00
break ;
if ( ! Token : : Match ( tok , " %var% ; " ) )
continue ;
// we only look for global variables
2013-02-06 06:39:58 +01:00
const Variable * var = tok - > variable ( ) ;
2012-10-14 17:40:17 +02:00
if ( ! var | | ! ( var - > isGlobal ( ) | | var - > isStatic ( ) ) )
continue ;
2013-02-06 06:39:58 +01:00
const unsigned int varid ( tok - > varId ( ) ) ;
2012-10-14 17:40:17 +02:00
// Token where throw occurs
2014-07-14 10:20:00 +02:00
const Token * throwToken = nullptr ;
2012-10-14 17:40:17 +02:00
// is there a throw after the deallocation?
2018-04-27 22:36:30 +02:00
const Token * const end2 = tok - > scope ( ) - > bodyEnd ;
2012-10-14 17:40:17 +02:00
for ( const Token * tok2 = tok ; tok2 ! = end2 ; tok2 = tok2 - > next ( ) ) {
// Throw after delete -> Dead pointer
if ( tok2 - > str ( ) = = " throw " ) {
2015-04-10 14:18:52 +02:00
if ( printInconclusive ) { // For inconclusive checking, throw directly.
2012-10-14 17:40:17 +02:00
deallocThrowError ( tok2 , tok - > str ( ) ) ;
break ;
}
2014-07-14 10:20:00 +02:00
throwToken = tok2 ;
2012-01-21 19:11:06 +01:00
}
2009-11-08 09:54:08 +01:00
2012-10-14 17:40:17 +02:00
// Variable is assigned -> Bail out
else if ( Token : : Match ( tok2 , " %varid% = " , varid ) ) {
2014-07-14 10:20:00 +02:00
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.
deallocThrowError ( throwToken , tok2 - > str ( ) ) ;
2012-10-14 17:40:17 +02:00
break ;
}
// Variable passed to function. Assume it becomes assigned -> Bail out
else if ( Token : : Match ( tok2 , " [,(] &| %varid% [,)] " , varid ) ) // TODO: No bailout if passed by value or as const reference
break ;
2009-11-08 09:54:08 +01:00
}
}
}
}
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : deallocThrowError ( const Token * const tok , const std : : string & varname )
{
2021-06-02 07:00:10 +02:00
reportError ( tok , Severity : : warning , " exceptDeallocThrow " , " Exception thrown in invalid state, ' " +
varname + " ' points at deallocated memory. " , CWE398 , Certainty : : normal ) ;
}
2011-02-05 10:11:09 +01:00
//---------------------------------------------------------------------------
// catch(const exception & err)
// {
// throw err; // <- should be just "throw;"
// }
//---------------------------------------------------------------------------
void CheckExceptionSafety : : checkRethrowCopy ( )
{
2021-02-24 22:00:06 +01:00
if ( ! mSettings - > severity . isEnabled ( Severity : : style ) )
2011-02-05 10:11:09 +01:00
return ;
2018-06-16 16:10:28 +02:00
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
2011-02-05 10:11:09 +01:00
2018-07-14 09:49:03 +02:00
for ( const Scope & scope : symbolDatabase - > scopeList ) {
if ( scope . type ! = Scope : : eCatch )
2012-01-26 16:50:59 +01:00
continue ;
2018-07-14 09:49:03 +02:00
const unsigned int varid = scope . bodyStart - > tokAt ( - 2 ) - > varId ( ) ;
2012-01-26 16:50:59 +01:00
if ( varid ) {
2018-07-14 09:49:03 +02:00
for ( const Token * tok = scope . bodyStart - > next ( ) ; tok & & tok ! = scope . bodyEnd ; tok = tok - > next ( ) ) {
2015-05-28 21:12:02 +02:00
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.
2012-02-02 16:17:42 +01:00
tok = tok - > next ( ) - > link ( ) - > next ( ) - > link ( ) ;
2015-05-28 21:12:02 +02:00
if ( ! tok )
break ;
2020-06-22 12:26:41 +02:00
} else if ( Token : : Match ( tok , " %varid% . " , varid ) ) {
const Token * parent = tok - > astParent ( ) ;
while ( Token : : simpleMatch ( parent - > astParent ( ) , " . " ) )
parent = parent - > astParent ( ) ;
if ( Token : : Match ( parent - > astParent ( ) , " %assign%|++|--|( " ) & & parent = = parent - > astParent ( ) - > astOperand1 ( ) )
break ;
2015-05-28 21:12:02 +02:00
} else if ( Token : : Match ( tok , " throw %varid% ; " , varid ) )
2012-02-02 16:17:42 +01:00
rethrowCopyError ( tok , tok - > strAt ( 1 ) ) ;
}
2012-01-26 16:50:59 +01:00
}
2011-02-05 10:11:09 +01:00
}
}
2012-02-02 16:17:42 +01:00
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : rethrowCopyError ( const Token * const tok , const std : : string & varname )
{
2021-06-02 07:00:10 +02:00
reportError ( tok , Severity : : style , " exceptRethrowCopy " ,
" Throwing a copy of the caught exception instead of rethrowing the original exception. \n "
" Rethrowing an exception with 'throw " + varname + " ;' creates an unnecessary copy of ' " + varname + " '. "
" To rethrow the caught exception without unnecessary copying or slicing, use a bare 'throw;'. " , CWE398 , Certainty : : normal ) ;
}
2012-02-02 16:17:42 +01:00
//---------------------------------------------------------------------------
// try {} catch (std::exception err) {} <- Should be "std::exception& err"
//---------------------------------------------------------------------------
void CheckExceptionSafety : : checkCatchExceptionByValue ( )
{
2021-02-24 22:00:06 +01:00
if ( ! mSettings - > severity . isEnabled ( Severity : : style ) )
2012-02-02 16:17:42 +01:00
return ;
2018-06-16 16:10:28 +02:00
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
2012-02-02 16:17:42 +01:00
2018-07-14 09:49:03 +02:00
for ( const Scope & scope : symbolDatabase - > scopeList ) {
if ( scope . type ! = Scope : : eCatch )
2012-02-02 16:17:42 +01:00
continue ;
// Find a pass-by-value declaration in the catch(), excluding basic types
// e.g. catch (std::exception err)
2018-07-14 09:49:03 +02:00
const Variable * var = scope . bodyStart - > tokAt ( - 2 ) - > variable ( ) ;
2012-02-02 16:17:42 +01:00
if ( var & & var - > isClass ( ) & & ! var - > isPointer ( ) & & ! var - > isReference ( ) )
2018-07-14 09:49:03 +02:00
catchExceptionByValueError ( scope . classDef ) ;
2012-02-02 16:17:42 +01:00
}
}
2014-04-10 16:17:10 +02:00
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : catchExceptionByValueError ( const Token * tok )
{
2021-06-02 07:00:10 +02:00
reportError ( tok , Severity : : style ,
" catchExceptionByValue " , " Exception should be caught by reference. \n "
" The exception is caught by value. It could be caught "
" as a (const) reference which is usually recommended in C++. " , CWE398 , Certainty : : normal ) ;
}
2014-04-10 16:17:10 +02:00
2014-04-20 20:40:55 +02:00
static const Token * functionThrowsRecursive ( const Function * function , std : : set < const Function * > & recursive )
2014-04-10 16:17:10 +02:00
{
2014-04-20 20:40:55 +02:00
// check for recursion and bail if found
2014-04-21 22:13:02 +02:00
if ( ! recursive . insert ( function ) . second )
2014-04-20 20:40:55 +02:00
return nullptr ;
2014-04-21 08:01:01 +02:00
if ( ! function - > functionScope )
return nullptr ;
2018-04-27 22:36:30 +02:00
for ( const Token * tok = function - > functionScope - > bodyStart - > next ( ) ;
tok ! = function - > functionScope - > bodyEnd ; tok = tok - > next ( ) ) {
2021-05-22 08:36:28 +02:00
if ( Token : : simpleMatch ( tok , " try { " ) )
tok = tok - > linkAt ( 1 ) ; // skip till start of catch clauses
2014-04-20 20:40:55 +02:00
if ( tok - > str ( ) = = " throw " ) {
return tok ;
} else if ( tok - > function ( ) ) {
const Function * called = tok - > function ( ) ;
// check if called function has an exception specification
2015-01-08 05:45:31 +01:00
if ( called - > isThrow ( ) & & called - > throwArg ) {
2014-04-20 20:40:55 +02:00
return tok ;
2015-01-08 05:45:31 +01:00
} else if ( called - > isNoExcept ( ) & & called - > noexceptArg & &
2014-04-20 20:40:55 +02:00
called - > noexceptArg - > str ( ) ! = " true " ) {
return tok ;
} else if ( functionThrowsRecursive ( called , recursive ) ) {
return tok ;
2014-04-10 16:17:10 +02:00
}
}
}
2014-04-20 20:40:55 +02:00
return nullptr ;
}
static const Token * functionThrows ( const Function * function )
{
2021-05-22 08:36:28 +02:00
std : : set < const Function * > recursive ;
2014-04-20 20:40:55 +02:00
return functionThrowsRecursive ( function , recursive ) ;
2014-04-10 16:17:10 +02:00
}
//--------------------------------------------------------------------------
2014-04-20 20:40:55 +02:00
// void func() noexcept { throw x; }
2014-04-10 16:17:10 +02:00
// void func() throw() { throw x; }
2014-04-20 20:40:55 +02:00
// void func() __attribute__((nothrow)); void func() { throw x; }
2014-04-10 16:17:10 +02:00
//--------------------------------------------------------------------------
void CheckExceptionSafety : : nothrowThrows ( )
{
2018-06-16 16:10:28 +02:00
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
2014-04-10 16:17:10 +02:00
2018-07-14 09:49:03 +02:00
for ( const Scope * scope : symbolDatabase - > functionScopes ) {
2014-07-14 10:20:00 +02:00
const Function * function = scope - > function ;
if ( ! function )
continue ;
2014-04-20 20:40:55 +02:00
2015-01-09 20:18:09 +01:00
// check noexcept and noexcept(true) functions
2015-01-08 05:45:31 +01:00
if ( function - > isNoExcept ( ) & &
2014-07-14 10:20:00 +02:00
( ! function - > noexceptArg | | function - > noexceptArg - > str ( ) = = " true " ) ) {
const Token * throws = functionThrows ( function ) ;
2014-04-20 20:40:55 +02:00
if ( throws )
noexceptThrowError ( throws ) ;
}
// check throw() functions
2015-01-08 05:45:31 +01:00
else if ( function - > isThrow ( ) & & ! function - > throwArg ) {
2014-07-14 10:20:00 +02:00
const Token * throws = functionThrows ( function ) ;
2014-04-20 20:40:55 +02:00
if ( throws )
2015-01-09 20:18:09 +01:00
noexceptThrowError ( throws ) ;
2014-04-20 20:40:55 +02:00
}
2015-01-09 20:18:09 +01:00
// check __attribute__((nothrow)) or __declspec(nothrow) functions
2014-07-14 10:20:00 +02:00
else if ( function - > isAttributeNothrow ( ) ) {
const Token * throws = functionThrows ( function ) ;
2014-04-20 20:40:55 +02:00
if ( throws )
2015-01-09 20:18:09 +01:00
noexceptThrowError ( throws ) ;
2014-05-04 20:47:20 +02:00
}
2014-04-20 08:58:36 +02:00
}
}
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : noexceptThrowError ( const Token * const tok )
{
2021-06-02 07:00:10 +02:00
reportError ( tok , Severity : : error , " throwInNoexceptFunction " , " Exception thrown in function declared not to throw exceptions. " , CWE398 , Certainty : : normal ) ;
}
2014-04-20 08:58:36 +02:00
//--------------------------------------------------------------------------
// void func() { functionWithExceptionSpecification(); }
//--------------------------------------------------------------------------
void CheckExceptionSafety : : unhandledExceptionSpecification ( )
{
2021-02-24 22:00:06 +01:00
if ( ! mSettings - > severity . isEnabled ( Severity : : style ) | | ! mSettings - > certainty . isEnabled ( Certainty : : inconclusive ) )
2014-04-30 19:33:17 +02:00
return ;
2018-06-16 16:10:28 +02:00
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
2014-04-20 08:58:36 +02:00
2018-07-14 09:49:03 +02:00
for ( const Scope * scope : symbolDatabase - > functionScopes ) {
2022-09-27 06:48:06 +02:00
// only check functions without exception specification
2015-01-08 05:45:31 +01:00
if ( scope - > function & & ! scope - > function - > isThrow ( ) & &
2014-05-01 13:41:01 +02:00
scope - > className ! = " main " & & scope - > className ! = " wmain " & &
scope - > className ! = " _tmain " & & scope - > className ! = " WinMain " ) {
2018-04-27 22:36:30 +02:00
for ( const Token * tok = scope - > function - > functionScope - > bodyStart - > next ( ) ;
tok ! = scope - > function - > functionScope - > bodyEnd ; tok = tok - > next ( ) ) {
2014-04-20 08:58:36 +02:00
if ( tok - > str ( ) = = " try " ) {
break ;
} else if ( tok - > function ( ) ) {
const Function * called = tok - > function ( ) ;
// check if called function has an exception specification
2015-01-08 05:45:31 +01:00
if ( called - > isThrow ( ) & & called - > throwArg ) {
2014-04-20 08:58:36 +02:00
unhandledExceptionSpecificationError ( tok , called - > tokenDef , scope - > function - > name ( ) ) ;
break ;
}
2014-04-10 16:17:10 +02:00
}
}
}
}
}
2021-05-31 10:39:24 +02:00
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : unhandledExceptionSpecificationError ( const Token * const tok1 , const Token * const tok2 , const std : : string & funcname )
{
2021-06-02 07:00:10 +02:00
const std : : string str1 ( tok1 ? tok1 - > str ( ) : " foo " ) ;
const std : : list < const Token * > locationList = { tok1 , tok2 } ;
reportError ( locationList , Severity : : style , " unhandledExceptionSpecification " ,
" Unhandled exception specification when calling function " + str1 + " (). \n "
" Unhandled exception specification when calling function " + str1 + " (). "
" Either use a try/catch around the function call, or add a exception specification for " + funcname + " () also. " , CWE703 , Certainty : : inconclusive ) ;
}
2021-05-31 10:39:24 +02:00
//--------------------------------------------------------------------------
// 7.6.18.4 If no exception is presently being handled, evaluating a throw-expression with no operand calls std :: terminate().
//--------------------------------------------------------------------------
void CheckExceptionSafety : : rethrowNoCurrentException ( )
{
const SymbolDatabase * const symbolDatabase = mTokenizer - > getSymbolDatabase ( ) ;
for ( const Scope * scope : symbolDatabase - > functionScopes ) {
const Function * function = scope - > function ;
if ( ! function )
continue ;
// Rethrow can be used in 'exception dispatcher' idiom which is FP in such case
// https://isocpp.org/wiki/faq/exceptions#throw-without-an-object
2021-07-02 17:41:51 +02:00
// We check the beginning of the function with idiom pattern
2021-06-02 16:59:34 +02:00
if ( Token : : simpleMatch ( function - > functionScope - > bodyStart - > next ( ) , " try { throw ; } catch ( " ) )
2021-05-31 10:39:24 +02:00
continue ;
for ( const Token * tok = function - > functionScope - > bodyStart - > next ( ) ;
2021-06-02 16:59:34 +02:00
tok ! = function - > functionScope - > bodyEnd ; tok = tok - > next ( ) ) {
2021-05-31 10:39:24 +02:00
if ( Token : : simpleMatch ( tok , " catch ( " ) ) {
tok = tok - > linkAt ( 1 ) ; // skip catch argument
if ( Token : : simpleMatch ( tok , " ) { " ) )
tok = tok - > linkAt ( 1 ) ; // skip catch scope
else
break ;
}
if ( Token : : simpleMatch ( tok , " throw ; " ) ) {
rethrowNoCurrentExceptionError ( tok ) ;
}
}
}
}
2021-06-02 16:59:34 +02:00
void CheckExceptionSafety : : rethrowNoCurrentExceptionError ( const Token * tok )
{
2021-05-31 10:39:24 +02:00
reportError ( tok , Severity : : error , " rethrowNoCurrentException " ,
" Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow. "
" If there is no current exception this calls std::terminate(). "
" More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object " ,
CWE480 , Certainty : : normal ) ;
}