Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions lib/checkexceptionsafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,24 +296,22 @@ void CheckExceptionSafety::nothrowThrows()
if (!function)
continue;

// check noexcept and noexcept(true) functions
if (function->isNoExcept()) {
const Token *throws = functionThrows(function);
if (throws)
noexceptThrowError(throws);
bool isNoExcept = false, isEntryPoint = false;
if (function->isNoExcept() || // noexcept and noexcept(true) functions
(function->isThrow() && !function->throwArg) || // throw() functions
function->isAttributeNothrow()) { // __attribute__((nothrow)) or __declspec(nothrow) functions
isNoExcept = true;
}

// check throw() functions
else if (function->isThrow() && !function->throwArg) {
const Token *throws = functionThrows(function);
if (throws)
noexceptThrowError(throws);
else if (mSettings->library.isentrypoint(function->name())) {
isEntryPoint = true;
}
if (!isNoExcept && !isEntryPoint)
continue;

// check __attribute__((nothrow)) or __declspec(nothrow) functions
else if (function->isAttributeNothrow()) {
const Token *throws = functionThrows(function);
if (throws)
if (const Token* throws = functionThrows(function)) {
if (isEntryPoint)
entryPointThrowError(throws);
else
noexceptThrowError(throws);
}
}
Expand All @@ -324,6 +322,11 @@ void CheckExceptionSafety::noexceptThrowError(const Token * const tok)
reportError(tok, Severity::error, "throwInNoexceptFunction", "Exception thrown in function declared not to throw exceptions.", CWE398, Certainty::normal);
}

void CheckExceptionSafety::entryPointThrowError(const Token * const tok)
{
reportError(tok, Severity::error, "throwInEntryPoint", "Exception thrown in function that is an entry point.", CWE398, Certainty::normal);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest something like:

Unhandled exception is thrown in function that is an entry point.

We only warn if it's unhandled right?

This is undefined behavior right? To throw an exception that is not catched..

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing in main() results in program termination: https://en.cppreference.com/w/cpp/language/throw.html
I'll add a negative test.

Copy link
Copy Markdown
Collaborator

@danmar danmar Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so it's defined behavior. It's not 100% clear to me that it should have "error" severity or if "warning" / "portability" would be more proper.
If program termination is wanted this is not a bad option to achieve that. However there is a danger that some RAII classes will not be destroyed properly. And using some std::exit function or something like that could be more clear about the intent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the outcome is the same as for throwing from a noexcept function, so the severity should also be the same.

I suggest something like:
Unhandled exception is thrown in function that is an entry point.

Should we change the message for throwInNoexceptFunction as well?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the outcome is the same as for throwing from a noexcept function, so the severity should also be the same.

I agree .. it's not 100% clear to me so well we can keep it as is.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we change the message for throwInNoexceptFunction as well?

please feel free to do it because it's only when it's not handled it's a problem. A user might think we sloppily warn whenever exceptions are thrown.

}

//--------------------------------------------------------------------------
// void func() { functionWithExceptionSpecification(); }
//--------------------------------------------------------------------------
Expand Down Expand Up @@ -433,6 +436,7 @@ void CheckExceptionSafety::getErrorMessages(ErrorLogger *errorLogger, const Sett
c.rethrowCopyError(nullptr, "varname");
c.catchExceptionByValueError(nullptr);
c.noexceptThrowError(nullptr);
c.entryPointThrowError(nullptr);
c.unhandledExceptionSpecificationError(nullptr, nullptr, "funcname");
c.rethrowNoCurrentExceptionError(nullptr);
}
1 change: 1 addition & 0 deletions lib/checkexceptionsafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class CPPCHECKLIB CheckExceptionSafety : public Check {
void rethrowCopyError(const Token * tok, const std::string &varname);
void catchExceptionByValueError(const Token *tok);
void noexceptThrowError(const Token * tok);
void entryPointThrowError(const Token * tok);
/** Missing exception specification */
void unhandledExceptionSpecificationError(const Token * tok1, const Token * tok2, const std::string & funcname);
/** Rethrow without currently handled exception */
Expand Down
23 changes: 20 additions & 3 deletions test/testexceptionsafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class TestExceptionSafety : public TestFixture {
TEST_CASE(rethrowNoCurrentException2);
TEST_CASE(rethrowNoCurrentException3);
TEST_CASE(noFunctionCall);
TEST_CASE(entryPoint);
}

struct CheckOptions
Expand Down Expand Up @@ -404,7 +405,7 @@ class TestExceptionSafety : public TestFixture {
"{\n"
" f();\n"
"}\n", dinit(CheckOptions, $.inconclusive = true));
ASSERT_EQUALS("", errout_str());
ASSERT_EQUALS("[test.cpp:4:5]: (error) Exception thrown in function that is an entry point. [throwInEntryPoint]\n", errout_str());
}

void unhandledExceptionSpecification3() {
Expand All @@ -421,12 +422,16 @@ class TestExceptionSafety : public TestFixture {
"}\n";

check(code, dinit(CheckOptions, $.inconclusive = true));
ASSERT_EQUALS("[test.cpp:3:5] -> [test.cpp:1:6]: (style, inconclusive) Unhandled exception specification when calling function f(). [unhandledExceptionSpecification]\n"
ASSERT_EQUALS("[test.cpp:10:5]: (error) Exception thrown in function that is an entry point. [throwInEntryPoint]\n"
"[test.cpp:3:5] -> [test.cpp:1:6]: (style, inconclusive) Unhandled exception specification when calling function f(). [unhandledExceptionSpecification]\n"
"[test.cpp:6:5] -> [test.cpp:1:6]: (style, inconclusive) Unhandled exception specification when calling function f(). [unhandledExceptionSpecification]\n", errout_str());

const Settings s = settingsBuilder().library("gnu.cfg").build();
check(code, dinit(CheckOptions, $.inconclusive = true, $.s = &s));
ASSERT_EQUALS("", errout_str());
ASSERT_EQUALS("[test.cpp:3:5]: (error) Exception thrown in function that is an entry point. [throwInEntryPoint]\n"
"[test.cpp:6:5]: (error) Exception thrown in function that is an entry point. [throwInEntryPoint]\n"
"[test.cpp:10:5]: (error) Exception thrown in function that is an entry point. [throwInEntryPoint]\n",
errout_str());
}

void nothrowAttributeThrow() {
Expand Down Expand Up @@ -490,6 +495,18 @@ class TestExceptionSafety : public TestFixture {
"}\n");
ASSERT_EQUALS("", errout_str());
}

void entryPoint() {
check("void f(int i) {\n" // #14195
" if (i < 2)\n"
" throw 0;\n"
"}\n"
"int main(int argc, char* argv[]) {\n"
" f(argc);\n"
"}\n");
ASSERT_EQUALS("[test.cpp:6:5]: (error) Exception thrown in function that is an entry point. [throwInEntryPoint]\n",
errout_str());
}
};

REGISTER_TEST(TestExceptionSafety)