Fixed #5638 (is there any plan to check noexcept correctness?)

This commit is contained in:
Robert Reif 2014-04-10 16:17:10 +02:00 committed by Daniel Marjamäki
parent 4ae204e46b
commit 847d28d283
8 changed files with 346 additions and 168 deletions

View File

@ -176,3 +176,48 @@ void CheckExceptionSafety::checkCatchExceptionByValue()
catchExceptionByValueError(i->classDef);
}
}
//--------------------------------------------------------------------------
// void func() noexcept { throw x; }
//--------------------------------------------------------------------------
void CheckExceptionSafety::noexceptThrows()
{
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
const std::size_t functions = symbolDatabase->functionScopes.size();
for (std::size_t i = 0; i < functions; ++i) {
const Scope * scope = symbolDatabase->functionScopes[i];
// onlycheck noexcept functions
if (scope->function && scope->function->isNoExcept &&
(!scope->function->noexceptArg || scope->function->noexceptArg->str() == "true")) {
for (const Token *tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
if (tok->str() != "throw") {
noexceptThrowError(tok);
}
}
}
}
}
//--------------------------------------------------------------------------
// void func() throw() { throw x; }
//--------------------------------------------------------------------------
void CheckExceptionSafety::nothrowThrows()
{
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
const std::size_t functions = symbolDatabase->functionScopes.size();
for (std::size_t i = 0; i < functions; ++i) {
const Scope * scope = symbolDatabase->functionScopes[i];
// onlycheck throw() functions
if (scope->function && scope->function->isThrow && !scope->function->throwArg) {
for (const Token *tok = scope->classStart->next(); tok != scope->classEnd; tok = tok->next()) {
if (tok->str() != "throw") {
nothrowThrowError(tok);
}
}
}
}
}

View File

@ -61,6 +61,8 @@ public:
checkExceptionSafety.deallocThrow();
checkExceptionSafety.checkRethrowCopy();
checkExceptionSafety.checkCatchExceptionByValue();
checkExceptionSafety.noexceptThrows();
checkExceptionSafety.nothrowThrows();
}
/** Don't throw exceptions in destructors */
@ -75,6 +77,12 @@ public:
/** @brief %Check for exceptions that are caught by value instead of by reference */
void checkCatchExceptionByValue();
/** @brief %Check for noexcept functions that throw */
void noexceptThrows();
/** @brief %Check for throw() functions that throw */
void nothrowThrows();
private:
/** Don't throw exceptions in destructors */
void destructorsError(const Token * const tok) {
@ -100,6 +108,16 @@ private:
"as a (const) reference which is usually recommended in C++.");
}
/** Don't throw exceptions in noexcept functions */
void noexceptThrowError(const Token * const tok) {
reportError(tok, Severity::error, "exceptThrowInNoexecptFunction", "Exception thrown in noexcept function.");
}
/** Don't throw exceptions in throw() functions */
void nothrowThrowError(const Token * const tok) {
reportError(tok, Severity::error, "exceptThrowInNoThrowFunction", "Exception thrown in throw() function.");
}
/** Generate all possible errors (for --errorlist) */
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const {
CheckExceptionSafety c(0, settings, errorLogger);
@ -107,6 +125,8 @@ private:
c.deallocThrowError(0, "p");
c.rethrowCopyError(0, "varname");
c.catchExceptionByValueError(0);
c.noexceptThrowError(0);
c.nothrowThrowError(0);
}
/** Short description of class (for --doc) */
@ -120,7 +140,9 @@ private:
"* Throwing exceptions in destructors\n"
"* Throwing exception during invalid state\n"
"* Throwing a copy of a caught exception instead of rethrowing the original exception\n"
"* Exception caught by value instead of by reference\n";
"* Exception caught by value instead of by reference\n"
"* Throwing exception in noexcept function\n"
"* Throwing exception in nothrow() function\n";
}
};
/// @}

View File

@ -458,6 +458,72 @@ SymbolDatabase::SymbolDatabase(const Tokenizer *tokenizer, const Settings *setti
scope->functionList.push_back(function);
}
// noexcept;
// const noexcept;
else if (Token::Match(end, ") const| noexcept ;")) {
function.isNoExcept = true;
if (end->next()->str() == "const")
tok = end->tokAt(3);
else
tok = end->tokAt(2);
scope->functionList.push_back(function);
}
// noexcept const;
else if (Token::simpleMatch(end, ") noexcept const ;")) {
function.isNoExcept = true;
tok = end->tokAt(3);
scope->functionList.push_back(function);
}
// noexcept(...);
// noexcept(...) const;
else if (Token::simpleMatch(end, ") noexcept (") &&
Token::Match(end->linkAt(2), ") const| ;")) {
function.isNoExcept = true;
if (end->linkAt(2)->strAt(1) == "const")
tok = end->linkAt(2)->tokAt(2);
else
tok = end->linkAt(2)->next();
scope->functionList.push_back(function);
}
// const noexcept(...);
else if (Token::simpleMatch(end, ") const noexcept (") &&
Token::simpleMatch(end->linkAt(3), ") ;")) {
function.isNoExcept = true;
tok = end->linkAt(3)->next();
scope->functionList.push_back(function);
}
// throw()
// const throw()
else if (Token::Match(end, ") const| throw (") &&
(end->next()->str() == "const" ? Token::Match(end->linkAt(3), ") ;") :
Token::Match(end->linkAt(2), ") ;"))) {
function.isThrow = true;
if (end->next()->str() == "const") {
if (end->strAt(4) != ")")
function.throwArg = end->tokAt(4);
tok = end->linkAt(3)->next();
} else {
if (end->strAt(3) != ")")
function.throwArg = end->tokAt(3);
tok = end->linkAt(2)->next();
}
scope->functionList.push_back(function);
}
// pure virtual function
else if (Token::Match(end, ") const| = %any% ;")) {
function.isPure = true;
@ -481,6 +547,28 @@ SymbolDatabase::SymbolDatabase(const Tokenizer *tokenizer, const Settings *setti
function.isInline = true;
function.hasBody = true;
if (Token::Match(end, ") const| noexcept")) {
int arg = 2;
if (end->strAt(1) == "const")
arg++;
if (end->strAt(arg) == "(")
function.noexceptArg = end->tokAt(arg + 1);
function.isNoExcept = true;
} else if (Token::Match(end, ") const| throw (")) {
int arg = 3;
if (end->strAt(1) == "const")
arg++;
if (end->strAt(arg) != ")")
function.throwArg = end->tokAt(arg);
function.isThrow = true;
}
// find start of function '{'
while (end && end->str() != "{" && end->str() != ";")
end = end->next();
@ -549,11 +637,13 @@ SymbolDatabase::SymbolDatabase(const Tokenizer *tokenizer, const Settings *setti
if (isFunction(tok, scope, &funcStart, &argStart)) {
bool retFuncPtr = Token::simpleMatch(argStart->link(), ") ) (");
const Token* scopeBegin = argStart->link()->next();
if (retFuncPtr)
scopeBegin = scopeBegin->next()->link()->next();
if (scopeBegin->isName()) { // Jump behind 'const' or unknown Macro
scopeBegin = scopeBegin->next();
if (scopeBegin->str() == "throw")
scopeBegin = scopeBegin->next();
if (scopeBegin->link() && scopeBegin->str() == "(") // Jump behind unknown macro of type THROW(...)
scopeBegin = scopeBegin->link()->next();
}
@ -576,6 +666,29 @@ SymbolDatabase::SymbolDatabase(const Tokenizer *tokenizer, const Settings *setti
else {
Function* function = addGlobalFunction(scope, tok, argStart, funcStart);
function->retFuncPtr = retFuncPtr;
// global functions can't be const but we have tests that are
if (Token::Match(argStart->link(), ") const| noexcept")) {
int arg = 2;
if (argStart->link()->strAt(1) == "const")
arg++;
if (argStart->link()->strAt(arg) == "(")
function->noexceptArg = argStart->link()->tokAt(arg + 1);
function->isNoExcept = true;
} else if (Token::Match(argStart->link(), ") const| throw (")) {
int arg = 3;
if (argStart->link()->strAt(1) == "const")
arg++;
if (argStart->link()->strAt(arg) != ")")
function->throwArg = argStart->link()->tokAt(arg);
function->isThrow = true;
}
}
// syntax error?
@ -598,6 +711,28 @@ SymbolDatabase::SymbolDatabase(const Tokenizer *tokenizer, const Settings *setti
if (newFunc) {
Function* func = addGlobalFunctionDecl(scope, tok, argStart, funcStart);
func->retFuncPtr = retFuncPtr;
if (Token::Match(argStart->link(), ") const| noexcept")) {
int arg = 2;
if (argStart->link()->strAt(1) == "const")
arg++;
if (argStart->link()->strAt(arg) == "(")
func->noexceptArg = argStart->link()->tokAt(arg + 1);
func->isNoExcept = true;
} else if (Token::Match(argStart->link(), ") const| throw (")) {
int arg = 3;
if (argStart->link()->strAt(1) == "const")
arg++;
if (argStart->link()->strAt(arg) != ")")
func->throwArg = argStart->link()->tokAt(arg);
func->isThrow = true;
}
}
tok = scopeBegin;
@ -965,11 +1100,16 @@ bool SymbolDatabase::isFunction(const Token *tok, const Scope* outerScope, const
tok->strAt(-1) == "::" || tok->strAt(-1) == "~" || // or a scope qualifier in front of tok
outerScope->isClassOrStruct())) { // or a ctor/dtor
const Token* tok2 = tok->next()->link()->next();
if ((Token::Match(tok2, "const| ;|{|=") ||
if (tok2 &&
(Token::Match(tok2, "const| ;|{|=") ||
(Token::Match(tok2, "%var% ;|{") && tok2->isUpperCaseName()) ||
(Token::Match(tok2, "%var% (") && tok2->isUpperCaseName() && tok2->next()->link()->strAt(1) == "{") ||
Token::Match(tok2, ": ::| %var% (|::|<|{") ||
Token::Match(tok2, "= delete|default ;"))) {
Token::Match(tok2, "= delete|default ;") ||
Token::Match(tok2, "const| noexcept const| {|:|;") ||
(Token::Match(tok2, "const| noexcept|throw (") &&
tok2->str() == "const" ? (tok2->tokAt(2) && Token::Match(tok2->tokAt(2)->link(), ") const| {|:|;")) :
(tok2->next() && Token::Match(tok2->next()->link(), ") const| {|:|;"))))) {
*funcStart = tok;
*argStart = tok->next();
return true;
@ -980,7 +1120,8 @@ bool SymbolDatabase::isFunction(const Token *tok, const Scope* outerScope, const
else if (Token::Match(tok, "%var% <") && Token::simpleMatch(tok->next()->link(), "> (")) {
const Token* tok2 = tok->next()->link()->next()->link();
if (Token::Match(tok2, ") const| ;|{|=") ||
Token::Match(tok2, ") : ::| %var% (|::|<|{")) {
Token::Match(tok2, ") : ::| %var% (|::|<|{") ||
Token::Match(tok->next()->link()->next()->link(), ") const| noexcept {|;|(")) {
*funcStart = tok;
*argStart = tok2->link();
return true;
@ -1772,8 +1913,12 @@ void SymbolDatabase::printOut(const char *title) const
std::cout << " isExplicit: " << (func->isExplicit ? "true" : "false") << std::endl;
std::cout << " isDefault: " << (func->isDefault ? "true" : "false") << std::endl;
std::cout << " isDelete: " << (func->isDelete ? "true" : "false") << std::endl;
std::cout << " isNoExcept: " << (func->isNoExcept ? "true" : "false") << std::endl;
std::cout << " isThrow: " << (func->isThrow ? "true" : "false") << std::endl;
std::cout << " isOperator: " << (func->isOperator ? "true" : "false") << std::endl;
std::cout << " retFuncPtr: " << (func->retFuncPtr ? "true" : "false") << std::endl;
std::cout << " noexceptArg: " << (func->noexceptArg ? func->noexceptArg->str() : "none") << std::endl;
std::cout << " throwArg: " << (func->throwArg ? func->throwArg->str() : "none") << std::endl;
std::cout << " tokenDef: " << func->tokenDef->str() << " " <<_tokenizer->list.fileLine(func->tokenDef) << std::endl;
std::cout << " argDef: " << _tokenizer->list.fileLine(func->argDef) << std::endl;
if (!func->isConstructor() && !func->isDestructor())

View File

@ -544,8 +544,12 @@ public:
isExplicit(false),
isDefault(false),
isDelete(false),
isNoExcept(false),
isThrow(false),
isOperator(false),
retFuncPtr(false) {
retFuncPtr(false),
noexceptArg(nullptr),
throwArg(nullptr) {
}
const std::string &name() const {
@ -610,8 +614,12 @@ public:
bool isExplicit; // is explicit
bool isDefault; // is default
bool isDelete; // is delete
bool isNoExcept; // is noexcept
bool isThrow; // is throw
bool isOperator; // is operator
bool retFuncPtr; // returns function pointer
const Token *noexceptArg;
const Token *throwArg;
static bool argsMatch(const Scope *info, const Token *first, const Token *second, const std::string &path, unsigned int depth);

View File

@ -3352,9 +3352,6 @@ bool Tokenizer::simplifyTokenList1(const char FileName[])
// Simplify the operator "?:"
simplifyConditionOperator();
// remove exception specifications..
removeExceptionSpecifications();
// Collapse operator name tokens into single token
// operator = => operator=
simplifyOperatorName();
@ -3680,7 +3677,7 @@ void Tokenizer::removeMacrosInGlobalScope()
if (tok->str() == "(") {
tok = tok->link();
if (Token::Match(tok, ") %type% {") &&
!Token::Match(tok->next(), "const|namespace|class|struct|union"))
!Token::Match(tok->next(), "const|namespace|class|struct|union|noexcept"))
tok->deleteNext();
}
@ -8620,30 +8617,6 @@ void Tokenizer::simplifyComma()
}
void Tokenizer::removeExceptionSpecifications()
{
if (isC())
return;
for (Token* tok = list.front(); tok; tok = tok->next()) {
if (Token::Match(tok, ") const| throw|noexcept (")) {
if (tok->next()->str() == "const") {
Token::eraseTokens(tok->next(), tok->linkAt(3));
tok = tok->next();
} else
Token::eraseTokens(tok, tok->linkAt(2));
tok->deleteNext();
} else if (Token::Match(tok, ") const| noexcept ;|{|const")) {
if (tok->next()->str() == "const")
tok->next()->deleteNext();
else
tok->deleteNext();
}
}
}
void Tokenizer::validate() const
{
std::stack<const Token *> linktok;

View File

@ -42,6 +42,8 @@ private:
TEST_CASE(rethrowCopy4);
TEST_CASE(rethrowCopy5);
TEST_CASE(catchExceptionByValue);
TEST_CASE(noexceptThrow);
TEST_CASE(nothrowThrow);
}
void check(const char code[], bool inconclusive = false) {
@ -308,6 +310,20 @@ private:
"}");
ASSERT_EQUALS("", errout.str());
}
void noexceptThrow() {
check("void func1() noexcept { throw 1; }\n"
"void func2() noexcept(true) { throw 1; }\n"
"void func3() noexcept(false) { throw 1; }\n");
ASSERT_EQUALS("[test.cpp:1]: (error) Exception thrown in noexcept function.\n"
"[test.cpp:2]: (error) Exception thrown in noexcept function.\n", errout.str());
}
void nothrowThrow() {
check("void func1() throw() { throw 1; }\n"
"void func2() throw(int) { throw 1; }\n");
ASSERT_EQUALS("[test.cpp:1]: (error) Exception thrown in throw() function.\n", errout.str());
}
};
REGISTER_TEST(TestExceptionSafety)

View File

@ -217,6 +217,11 @@ private:
TEST_CASE(findFunction1);
TEST_CASE(findFunction2); // mismatch: parameter passed by address => reference argument
TEST_CASE(noexceptFunction1);
TEST_CASE(noexceptFunction2);
TEST_CASE(noexceptFunction3);
TEST_CASE(noexceptFunction4);
}
void array() const {
@ -1980,6 +1985,104 @@ private:
ASSERT_EQUALS(true, callfunc != nullptr); // not null
ASSERT_EQUALS(false, (callfunc && callfunc->function())); // callfunc->function() should be null
}
#define FUNC(x) const Function *x = findFunctionByName(#x, &db->scopeList.front()); \
ASSERT_EQUALS(true, x != nullptr); \
if (x) ASSERT_EQUALS(true, x->isNoExcept);
void noexceptFunction1() {
GET_SYMBOL_DB("void func1() noexcept;\n"
"void func2() noexcept { }\n"
"void func3() noexcept(true);\n"
"void func4() noexcept(true) { }\n");
ASSERT_EQUALS("", errout.str());
ASSERT_EQUALS(true, db != nullptr); // not null
if (db) {
FUNC(func1);
FUNC(func2);
FUNC(func3);
FUNC(func4);
}
}
void noexceptFunction2() {
GET_SYMBOL_DB("template <class T> void self_assign(T& t) noexcept(noexcept(t = t)) {t = t; }\n");
ASSERT_EQUALS("", errout.str());
ASSERT_EQUALS(true, db != nullptr); // not null
if (db) {
FUNC(self_assign);
}
}
#define CLASS_FUNC(x, y) const Function *x = findFunctionByName(#x, y); \
ASSERT_EQUALS(true, x != nullptr); \
if (x) ASSERT_EQUALS(true, x->isNoExcept);
void noexceptFunction3() {
GET_SYMBOL_DB("struct Fred {\n"
" void func1() noexcept;\n"
" void func2() noexcept { }\n"
" void func3() noexcept(true);\n"
" void func4() noexcept(true) { }\n"
" void func5() const noexcept;\n"
" void func6() const noexcept { }\n"
" void func7() const noexcept(true);\n"
" void func8() const noexcept(true) { }\n"
" void func9() noexcept const;\n"
" void func10() noexcept const { }\n"
" void func11() noexcept(true) const;\n"
" void func12() noexcept(true) const { }\n"
"};");
ASSERT_EQUALS("", errout.str());
ASSERT_EQUALS(true, db != nullptr); // not null
if (db) {
const Scope *fred = db->findScopeByName("Fred");
ASSERT_EQUALS(true, fred != nullptr);
if (fred) {
CLASS_FUNC(func1, fred);
CLASS_FUNC(func2, fred);
CLASS_FUNC(func3, fred);
CLASS_FUNC(func4, fred);
CLASS_FUNC(func5, fred);
CLASS_FUNC(func6, fred);
CLASS_FUNC(func7, fred);
CLASS_FUNC(func8, fred);
CLASS_FUNC(func9, fred);
CLASS_FUNC(func10, fred);
CLASS_FUNC(func11, fred);
CLASS_FUNC(func12, fred);
}
}
}
void noexceptFunction4() {
GET_SYMBOL_DB("class A {\n"
"public:\n"
" A(A&& a) {\n"
" throw std::runtime_error(\"err\");\n"
" }\n"
"};\n"
"class B {\n"
" A a;\n"
" B(B&& b) noexcept\n"
" :a(std::move(b.a)) { }\n"
"};\n");
ASSERT_EQUALS("", errout.str());
ASSERT_EQUALS(true, db != nullptr); // not null
if (db) {
const Scope *b = db->findScopeByName("B");
ASSERT_EQUALS(true, b != nullptr);
if (b) {
CLASS_FUNC(B, b);
}
}
}
};
REGISTER_TEST(TestSymbolDatabase)

View File

@ -424,13 +424,6 @@ private:
TEST_CASE(createLinks);
TEST_CASE(signed1);
TEST_CASE(removeExceptionSpecification1);
TEST_CASE(removeExceptionSpecification2);
TEST_CASE(removeExceptionSpecification3);
TEST_CASE(removeExceptionSpecification4);
TEST_CASE(removeExceptionSpecification5);
TEST_CASE(removeExceptionSpecification6); // #4617
TEST_CASE(simplifyString);
TEST_CASE(simplifyConst);
TEST_CASE(switchCase);
@ -6860,133 +6853,6 @@ private:
}
}
void removeExceptionSpecification1() {
const char code[] = "class A\n"
"{\n"
"private:\n"
" void f() throw (std::runtime_error);\n"
"};\n"
"void A::f() throw (std::runtime_error)\n"
"{ }";
const char expected[] = "class A\n"
"{\n"
"private:\n"
"void f ( ) ;\n"
"} ;\n"
"void A :: f ( )\n"
"{ }";
ASSERT_EQUALS(expected, tokenizeAndStringify(code));
}
void removeExceptionSpecification2() {
const char code[] = "class A\n"
"{\n"
"private:\n"
" int value;\n"
"public:\n"
" A::A() throw ()\n"
" : value(0)\n"
" { }\n"
"};\n";
const char expected[] = "class A\n"
"{\n"
"private:\n"
"int value ;\n"
"public:\n"
"A :: A ( )\n"
": value ( 0 )\n"
"{ }\n"
"} ;";
ASSERT_EQUALS(expected, tokenizeAndStringify(code));
}
void removeExceptionSpecification3() {
const char code[] = "namespace A {\n"
" struct B {\n"
" B() throw ()\n"
" { }\n"
" };\n"
"};\n";
const char expected[] = "namespace A {\n"
"struct B {\n"
"B ( )\n"
"{ }\n"
"} ;\n"
"} ;";
ASSERT_EQUALS(expected, tokenizeAndStringify(code));
}
void removeExceptionSpecification4() {
const char code[] = "namespace {\n"
" void B() throw ();\n"
"};";
const char expected[] = "namespace {\n"
"void B ( ) ;\n"
"} ;";
ASSERT_EQUALS(expected, tokenizeAndStringify(code));
}
void removeExceptionSpecification5() {
ASSERT_EQUALS("void foo ( struct S ) ;",
tokenizeAndStringify("void foo (struct S) throw();"));
ASSERT_EQUALS("void foo ( struct S , int ) ;",
tokenizeAndStringify("void foo (struct S, int) throw();"));
ASSERT_EQUALS("void foo ( int , struct S ) ;",
tokenizeAndStringify("void foo (int, struct S) throw();"));
ASSERT_EQUALS("void foo ( struct S1 , struct S2 ) ;",
tokenizeAndStringify("void foo (struct S1, struct S2) throw();"));
}
void removeExceptionSpecification6() { // #4617
ASSERT_EQUALS("void foo ( ) ;",
tokenizeAndStringify("void foo () noexcept;"));
ASSERT_EQUALS("void foo ( ) { }",
tokenizeAndStringify("void foo () noexcept { }"));
ASSERT_EQUALS("void foo ( ) ;",
tokenizeAndStringify("void foo () noexcept(true);"));
ASSERT_EQUALS("void foo ( ) { }",
tokenizeAndStringify("void foo () noexcept(true) { }"));
ASSERT_EQUALS("void foo ( ) ;",
tokenizeAndStringify("void foo () noexcept(noexcept(true));"));
ASSERT_EQUALS("void foo ( ) { }",
tokenizeAndStringify("void foo () noexcept(noexcept(true)) { }"));
ASSERT_EQUALS("void foo ( ) const ;",
tokenizeAndStringify("void foo () const noexcept;"));
ASSERT_EQUALS("void foo ( ) const { }",
tokenizeAndStringify("void foo () const noexcept { }"));
ASSERT_EQUALS("void foo ( ) const ;",
tokenizeAndStringify("void foo () const noexcept(true);"));
ASSERT_EQUALS("void foo ( ) const { }",
tokenizeAndStringify("void foo () const noexcept(true) { }"));
ASSERT_EQUALS("void foo ( ) const ;",
tokenizeAndStringify("void foo () const noexcept(noexcept(true));"));
ASSERT_EQUALS("void foo ( ) const { }",
tokenizeAndStringify("void foo () const noexcept(noexcept(true)) { }"));
ASSERT_EQUALS("void foo ( ) const ;",
tokenizeAndStringify("void foo () noexcept const;"));
ASSERT_EQUALS("void foo ( ) const { }",
tokenizeAndStringify("void foo () noexcept const { }"));
ASSERT_EQUALS("void foo ( ) const ;",
tokenizeAndStringify("void foo () noexcept(true) const;"));
ASSERT_EQUALS("void foo ( ) const { }",
tokenizeAndStringify("void foo () noexcept(true) const { }"));
ASSERT_EQUALS("void foo ( ) const ;",
tokenizeAndStringify("void foo () noexcept(noexcept(true)) const;"));
ASSERT_EQUALS("void foo ( ) const { }",
tokenizeAndStringify("void foo () noexcept(noexcept(true)) const { }"));
}
void simplifyString() {
errout.str("");
Settings settings;