Skip to content

Commit 06c4542

Browse files
authored
New check for rethrow without current handled exception (#3270)
1 parent 04c9a83 commit 06c4542

3 files changed

Lines changed: 74 additions & 1 deletion

File tree

lib/checkexceptionsafety.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,44 @@ void CheckExceptionSafety::unhandledExceptionSpecification()
304304
}
305305
}
306306
}
307+
308+
//--------------------------------------------------------------------------
309+
// 7.6.18.4 If no exception is presently being handled, evaluating a throw-expression with no operand calls std​::​​terminate().
310+
//--------------------------------------------------------------------------
311+
void CheckExceptionSafety::rethrowNoCurrentException()
312+
{
313+
const SymbolDatabase* const symbolDatabase = mTokenizer->getSymbolDatabase();
314+
for (const Scope * scope : symbolDatabase->functionScopes) {
315+
const Function* function = scope->function;
316+
if (!function)
317+
continue;
318+
319+
// Rethrow can be used in 'exception dispatcher' idiom which is FP in such case
320+
// https://isocpp.org/wiki/faq/exceptions#throw-without-an-object
321+
// We check the beggining of the function with idiom pattern
322+
if (Token::simpleMatch(function->functionScope->bodyStart->next(), "try { throw ; } catch (" ))
323+
continue;
324+
325+
for (const Token *tok = function->functionScope->bodyStart->next();
326+
tok != function->functionScope->bodyEnd; tok = tok->next()) {
327+
if (Token::simpleMatch(tok, "catch (")) {
328+
tok = tok->linkAt(1); // skip catch argument
329+
if (Token::simpleMatch(tok, ") {"))
330+
tok = tok->linkAt(1); // skip catch scope
331+
else
332+
break;
333+
}
334+
if (Token::simpleMatch(tok, "throw ;")) {
335+
rethrowNoCurrentExceptionError(tok);
336+
}
337+
}
338+
}
339+
}
340+
341+
void CheckExceptionSafety::rethrowNoCurrentExceptionError(const Token *tok) {
342+
reportError(tok, Severity::error, "rethrowNoCurrentException",
343+
"Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow."
344+
" If there is no current exception this calls std::terminate()."
345+
" More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object",
346+
CWE480, Certainty::normal);
347+
}

lib/checkexceptionsafety.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ErrorLogger;
3636
// CWE ID used:
3737
static const struct CWE CWE398(398U); // Indicator of Poor Code Quality
3838
static const struct CWE CWE703(703U); // Improper Check or Handling of Exceptional Conditions
39+
static const struct CWE CWE480(480U); // Use of Incorrect Operator
3940

4041

4142
/// @addtogroup Checks
@@ -72,6 +73,7 @@ class CPPCHECKLIB CheckExceptionSafety : public Check {
7273
checkExceptionSafety.checkCatchExceptionByValue();
7374
checkExceptionSafety.nothrowThrows();
7475
checkExceptionSafety.unhandledExceptionSpecification();
76+
checkExceptionSafety.rethrowNoCurrentException();
7577
}
7678

7779
/** Don't throw exceptions in destructors */
@@ -92,6 +94,9 @@ class CPPCHECKLIB CheckExceptionSafety : public Check {
9294
/** @brief %Check for unhandled exception specification */
9395
void unhandledExceptionSpecification();
9496

97+
/** @brief %Check for rethrow not from catch scope */
98+
void rethrowNoCurrentException();
99+
95100
private:
96101
/** Don't throw exceptions in destructors */
97102
void destructorsError(const Token * const tok, const std::string &className) {
@@ -135,6 +140,9 @@ class CPPCHECKLIB CheckExceptionSafety : public Check {
135140
"Either use a try/catch around the function call, or add a exception specification for " + funcname + "() also.", CWE703, Certainty::inconclusive);
136141
}
137142

143+
/** Rethrow without currently handled exception */
144+
void rethrowNoCurrentExceptionError(const Token *tok);
145+
138146
/** Generate all possible errors (for --errorlist) */
139147
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const OVERRIDE {
140148
CheckExceptionSafety c(nullptr, settings, errorLogger);
@@ -144,6 +152,7 @@ class CPPCHECKLIB CheckExceptionSafety : public Check {
144152
c.catchExceptionByValueError(nullptr);
145153
c.noexceptThrowError(nullptr);
146154
c.unhandledExceptionSpecificationError(nullptr, nullptr, "funcname");
155+
c.rethrowNoCurrentExceptionError(nullptr);
147156
}
148157

149158
/** Short description of class (for --doc) */
@@ -159,7 +168,8 @@ class CPPCHECKLIB CheckExceptionSafety : public Check {
159168
"- Throwing a copy of a caught exception instead of rethrowing the original exception\n"
160169
"- Exception caught by value instead of by reference\n"
161170
"- Throwing exception in noexcept, nothrow(), __attribute__((nothrow)) or __declspec(nothrow) function\n"
162-
"- Unhandled exception specification when calling function foo()\n";
171+
"- Unhandled exception specification when calling function foo()\n"
172+
"- Rethrow without currently handled exception\n";
163173
}
164174
};
165175
/// @}

test/testexceptionsafety.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class TestExceptionSafety : public TestFixture {
5151
TEST_CASE(nothrowAttributeThrow);
5252
TEST_CASE(nothrowAttributeThrow2); // #5703
5353
TEST_CASE(nothrowDeclspecThrow);
54+
TEST_CASE(rethrowNoCurrentException1);
55+
TEST_CASE(rethrowNoCurrentException2);
56+
TEST_CASE(rethrowNoCurrentException3);
5457
}
5558

5659
void check(const char code[], bool inconclusive = false) {
@@ -407,6 +410,25 @@ class TestExceptionSafety : public TestFixture {
407410
check("const char *func() __attribute((nothrow)); void func1() { return 0; }");
408411
ASSERT_EQUALS("", errout.str());
409412
}
413+
414+
void rethrowNoCurrentException1() {
415+
check("void func1(const bool flag) { try{ if(!flag) throw; } catch (int&) { ; } }");
416+
ASSERT_EQUALS("[test.cpp:1]: (error) Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow."
417+
" If there is no current exception this calls std::terminate(). More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object\n", errout.str());
418+
}
419+
420+
void rethrowNoCurrentException2() {
421+
check("void func1() { try{ ; } catch (...) { ; } throw; }");
422+
ASSERT_EQUALS("[test.cpp:1]: (error) Rethrowing current exception with 'throw;', it seems there is no current exception to rethrow."
423+
" If there is no current exception this calls std::terminate(). More: https://isocpp.org/wiki/faq/exceptions#throw-without-an-object\n", errout.str());
424+
}
425+
426+
void rethrowNoCurrentException3() {
427+
check("void on_error() { try { throw; } catch (const int &) { ; } catch (...) { ; } }\n" // exception dispatcher idiom
428+
"void func2() { try{ ; } catch (const int&) { throw; } ; }\n"
429+
"void func3() { throw 0; }");
430+
ASSERT_EQUALS("", errout.str());
431+
}
410432
};
411433

412434
REGISTER_TEST(TestExceptionSafety)

0 commit comments

Comments
 (0)