- Moved checkCatchExceptionByValue from CheckOther to CheckExceptionSafety

- Fixed false positive: throw outerCatchVar; in inner catch is now correctly handled
- Added eTry and eCatch to Scope::isLocal -> Scopes inside catch are now detected by symbol database
This commit is contained in:
PKEuS 2012-02-02 16:17:42 +01:00
parent 2be85e9d37
commit d5c2c7db88
7 changed files with 153 additions and 141 deletions

View File

@ -144,9 +144,34 @@ void CheckExceptionSafety::checkRethrowCopy()
const unsigned int varid = i->classStart->tokAt(-2)->varId(); const unsigned int varid = i->classStart->tokAt(-2)->varId();
if (varid) { if (varid) {
const Token* rethrowTok = Token::findmatch(i->classStart->next(), "throw %varid% ;", i->classEnd->previous(), varid); for (const Token* tok = i->classStart->next(); tok && tok != i->classEnd; tok = tok->next()) {
if (rethrowTok) 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.
rethrowCopyError(rethrowTok, rethrowTok->strAt(1)); tok = tok->next()->link()->next()->link();
else if (Token::Match(tok, "throw %varid% ;", varid))
rethrowCopyError(tok, tok->strAt(1));
}
} }
} }
} }
//---------------------------------------------------------------------------
// try {} catch (std::exception err) {} <- Should be "std::exception& err"
//---------------------------------------------------------------------------
void CheckExceptionSafety::checkCatchExceptionByValue()
{
if (!_settings->isEnabled("style"))
return;
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
for (std::list<Scope>::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) {
if (i->type != Scope::eCatch)
continue;
// Find a pass-by-value declaration in the catch(), excluding basic types
// e.g. catch (std::exception err)
const Variable* var = symbolDatabase->getVariableFromVarId(i->classStart->tokAt(-2)->varId());
if (var && var->isClass() && !var->isPointer() && !var->isReference())
catchExceptionByValueError(i->classDef);
}
}

View File

@ -56,6 +56,7 @@ public:
checkExceptionSafety.destructors(); checkExceptionSafety.destructors();
checkExceptionSafety.deallocThrow(); checkExceptionSafety.deallocThrow();
checkExceptionSafety.checkRethrowCopy(); checkExceptionSafety.checkRethrowCopy();
checkExceptionSafety.checkCatchExceptionByValue();
} }
/** Don't throw exceptions in destructors */ /** Don't throw exceptions in destructors */
@ -67,6 +68,9 @@ public:
/** Don't rethrow a copy of the caught exception; use a bare throw instead */ /** Don't rethrow a copy of the caught exception; use a bare throw instead */
void checkRethrowCopy(); void checkRethrowCopy();
/** @brief %Check for exceptions that are caught by value instead of by reference */
void checkCatchExceptionByValue();
private: private:
/** Don't throw exceptions in destructors */ /** Don't throw exceptions in destructors */
void destructorsError(const Token * const tok) { void destructorsError(const Token * const tok) {
@ -84,12 +88,20 @@ private:
"To rethrow the caught exception without unnecessary copying or slicing, use a bare 'throw;'."); "To rethrow the caught exception without unnecessary copying or slicing, use a bare 'throw;'.");
} }
void catchExceptionByValueError(const Token *tok) {
reportError(tok, Severity::style,
"catchExceptionByValue", "Exception should be caught by reference.\n"
"The exception is caught as a value. It could be caught "
"as a (const) reference which is usually recommended in C++.");
}
/** Generate all possible errors (for --errorlist) */ /** Generate all possible errors (for --errorlist) */
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) { void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) {
CheckExceptionSafety c(0, settings, errorLogger); CheckExceptionSafety c(0, settings, errorLogger);
c.destructorsError(0); c.destructorsError(0);
c.deallocThrowError(0, "p"); c.deallocThrowError(0, "p");
c.rethrowCopyError(0, "varname"); c.rethrowCopyError(0, "varname");
c.catchExceptionByValueError(0);
} }
/** Short description of class (for --doc) */ /** Short description of class (for --doc) */
@ -102,7 +114,8 @@ private:
return "Checking exception safety\n" return "Checking exception safety\n"
"* Throwing exceptions in destructors\n" "* Throwing exceptions in destructors\n"
"* Throwing exception during invalid state\n" "* Throwing exception during invalid state\n"
"* Throwing a copy of a caught exception instead of rethrowing the original exception"; "* Throwing a copy of a caught exception instead of rethrowing the original exception\n"
"* exception caught by value instead of by reference";
} }
}; };
/// @} /// @}

View File

@ -1058,36 +1058,6 @@ void CheckOther::secondAlwaysTrueFalseWhenFirstTrueError(const Token *tok, const
reportError(tok, Severity::style, "secondAlwaysTrueFalseWhenFirstTrue", truefalse); reportError(tok, Severity::style, "secondAlwaysTrueFalseWhenFirstTrue", truefalse);
} }
//---------------------------------------------------------------------------
// try {} catch (std::exception err) {} <- Should be "std::exception& err"
//---------------------------------------------------------------------------
void CheckOther::checkCatchExceptionByValue()
{
if (!_settings->isEnabled("style"))
return;
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
for (std::list<Scope>::const_iterator i = symbolDatabase->scopeList.begin(); i != symbolDatabase->scopeList.end(); ++i) {
if (i->type != Scope::eCatch)
continue;
// Find a pass-by-value declaration in the catch(), excluding basic types
// e.g. catch (std::exception err)
const Variable* var = symbolDatabase->getVariableFromVarId(i->classStart->tokAt(-2)->varId());
if (var && var->isClass() && !var->isPointer() && !var->isReference())
catchExceptionByValueError(i->classDef);
}
}
void CheckOther::catchExceptionByValueError(const Token *tok)
{
reportError(tok, Severity::style,
"catchExceptionByValue", "Exception should be caught by reference.\n"
"The exception is caught as a value. It could be caught "
"as a (const) reference which is usually recommended in C++.");
}
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// strtol(str, 0, radix) <- radix must be 0 or 2-36 // strtol(str, 0, radix) <- radix must be 0 or 2-36
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------

View File

@ -96,7 +96,6 @@ public:
checkOther.checkCoutCerrMisusage(); checkOther.checkCoutCerrMisusage();
checkOther.checkIncorrectLogicOperator(); checkOther.checkIncorrectLogicOperator();
checkOther.checkMisusedScopedObject(); checkOther.checkMisusedScopedObject();
checkOther.checkCatchExceptionByValue();
checkOther.checkMemsetZeroBytes(); checkOther.checkMemsetZeroBytes();
checkOther.checkIncorrectStringCompare(); checkOther.checkIncorrectStringCompare();
checkOther.checkIncrementBoolean(); checkOther.checkIncrementBoolean();
@ -208,9 +207,6 @@ public:
/** @brief %Check for objects that are destroyed immediately */ /** @brief %Check for objects that are destroyed immediately */
void checkMisusedScopedObject(); void checkMisusedScopedObject();
/** @brief %Check for exceptions that are caught by value instead of by reference */
void checkCatchExceptionByValue();
/** @brief %Check for filling zero bytes with memset() */ /** @brief %Check for filling zero bytes with memset() */
void checkMemsetZeroBytes(); void checkMemsetZeroBytes();
@ -289,7 +285,6 @@ public:
void incorrectLogicOperatorError(const Token *tok, bool always); void incorrectLogicOperatorError(const Token *tok, bool always);
void secondAlwaysTrueFalseWhenFirstTrueError(const Token *tok, const std::string &truefalse); void secondAlwaysTrueFalseWhenFirstTrueError(const Token *tok, const std::string &truefalse);
void misusedScopeObjectError(const Token *tok, const std::string &varname); void misusedScopeObjectError(const Token *tok, const std::string &varname);
void catchExceptionByValueError(const Token *tok);
void memsetZeroBytesError(const Token *tok, const std::string &varname); void memsetZeroBytesError(const Token *tok, const std::string &varname);
void sizeofForArrayParameterError(const Token *tok); void sizeofForArrayParameterError(const Token *tok);
void sizeofForStrncmpError(const Token *tok); void sizeofForStrncmpError(const Token *tok);
@ -349,7 +344,6 @@ public:
c.invalidScanfError(0); c.invalidScanfError(0);
c.incorrectLogicOperatorError(0, true); c.incorrectLogicOperatorError(0, true);
c.secondAlwaysTrueFalseWhenFirstTrueError(0, "when first comparison is true, the 2nd comparison is always true"); c.secondAlwaysTrueFalseWhenFirstTrueError(0, "when first comparison is true, the 2nd comparison is always true");
c.catchExceptionByValueError(0);
c.memsetZeroBytesError(0, "varname"); c.memsetZeroBytesError(0, "varname");
c.clarifyCalculationError(0, "+"); c.clarifyCalculationError(0, "+");
c.clarifyConditionError(0, true, false); c.clarifyConditionError(0, true, false);
@ -418,7 +412,6 @@ public:
"* look for calculations inside sizeof()\n" "* look for calculations inside sizeof()\n"
"* assignment of a variable to itself\n" "* assignment of a variable to itself\n"
"* mutual exclusion over || always evaluating to true\n" "* mutual exclusion over || always evaluating to true\n"
"* exception caught by value instead of by reference\n"
"* Clarify calculation with parentheses\n" "* Clarify calculation with parentheses\n"
"* using increment on boolean\n" "* using increment on boolean\n"
"* comparison of a boolean with a non-zero integer\n" "* comparison of a boolean with a non-zero integer\n"

View File

@ -459,7 +459,8 @@ public:
bool isLocal() const { bool isLocal() const {
return (type == eIf || type == eElse || type == eElseIf || return (type == eIf || type == eElse || type == eElseIf ||
type == eFor || type == eWhile || type == eDo || type == eFor || type == eWhile || type == eDo ||
type == eSwitch || type == eUnconditional); type == eSwitch || type == eUnconditional ||
type == eTry || type == eCatch);
} }
/** /**

View File

@ -40,6 +40,8 @@ private:
TEST_CASE(rethrowCopy2); TEST_CASE(rethrowCopy2);
TEST_CASE(rethrowCopy3); TEST_CASE(rethrowCopy3);
TEST_CASE(rethrowCopy4); TEST_CASE(rethrowCopy4);
TEST_CASE(rethrowCopy5);
TEST_CASE(catchExceptionByValue);
} }
void check(const std::string &code, bool inconclusive = false) { void check(const std::string &code, bool inconclusive = false) {
@ -157,7 +159,7 @@ private:
" {\n" " {\n"
" foo();\n" " foo();\n"
" }\n" " }\n"
" catch(exception err)\n" " catch(exception& err)\n"
" {\n" " {\n"
" throw err;\n" " throw err;\n"
" }\n" " }\n"
@ -170,7 +172,7 @@ private:
" try {\n" " try {\n"
" foo();\n" " foo();\n"
" }\n" " }\n"
" catch(std::runtime_error err) {\n" " catch(std::runtime_error& err) {\n"
" throw err;\n" " throw err;\n"
" }\n" " }\n"
"}"); "}");
@ -183,7 +185,7 @@ private:
" {\n" " {\n"
" foo();\n" " foo();\n"
" }\n" " }\n"
" catch(exception err)\n" " catch(const exception& err)\n"
" {\n" " {\n"
" exception err2;\n" " exception err2;\n"
" throw err2;\n" " throw err2;\n"
@ -191,7 +193,110 @@ private:
"}\n"); "}\n");
ASSERT_EQUALS("", errout.str()); ASSERT_EQUALS("", errout.str());
} }
void rethrowCopy5() {
check("void f() {\n"
" try {\n"
" foo();\n"
" }\n"
" catch(const exception& outer) {\n"
" try {\n"
" foo(outer);\n"
" }\n"
" catch(const exception& inner) {\n"
" throw inner;\n"
" }\n"
" }\n"
"}");
ASSERT_EQUALS("[test.cpp:10]: (style) Throwing a copy of the caught exception instead of rethrowing the original exception\n", errout.str());
check("void f() {\n"
" try {\n"
" foo();\n"
" }\n"
" catch(const exception& outer) {\n"
" try {\n"
" foo(outer);\n"
" }\n"
" catch(const exception& inner) {\n"
" throw outer;\n"
" }\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
}
void catchExceptionByValue() {
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch( ::std::exception err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("[test.cpp:5]: (style) Exception should be caught by reference.\n", errout.str());
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch(const exception err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("[test.cpp:5]: (style) Exception should be caught by reference.\n", errout.str());
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch( ::std::exception& err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch(exception* err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch(const exception& err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch(int err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try {\n"
" bar();\n"
" }\n"
" catch(exception* const err) {\n"
" foo(err);\n"
" }\n"
"}");
ASSERT_EQUALS("", errout.str());
}
}; };
REGISTER_TEST(TestExceptionSafety) REGISTER_TEST(TestExceptionSafety)

View File

@ -114,8 +114,6 @@ private:
TEST_CASE(incorrectLogicOperator2); TEST_CASE(incorrectLogicOperator2);
TEST_CASE(secondAlwaysTrueFalseWhenFirstTrueError); TEST_CASE(secondAlwaysTrueFalseWhenFirstTrueError);
TEST_CASE(catchExceptionByValue);
TEST_CASE(memsetZeroBytes); TEST_CASE(memsetZeroBytes);
TEST_CASE(sizeofForArrayParameter); TEST_CASE(sizeofForArrayParameter);
@ -3065,99 +3063,6 @@ private:
} }
void catchExceptionByValue() {
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch( ::std::exception err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:6]: (style) Exception should be caught by reference.\n", errout.str());
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch(const exception err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:6]: (style) Exception should be caught by reference.\n", errout.str());
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch( ::std::exception& err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch(exception* err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch(const exception& err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch(int err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" try\n"
" {\n"
" foo();\n"
" }\n"
" catch(exception* const err)\n"
" {\n"
" throw err;\n"
" }\n"
"}\n"
);
ASSERT_EQUALS("", errout.str());
}
void memsetZeroBytes() { void memsetZeroBytes() {
check("void f() {\n" check("void f() {\n"
" memset(p, 10, 0x0);\n" " memset(p, 10, 0x0);\n"