Fixed #5697 (Check for throw in __attribute__((nothrow)) function)

This commit is contained in:
Robert Reif 2014-04-20 20:40:55 +02:00 committed by Daniel Marjamäki
parent 01d14d0388
commit 6ff5de2118
11 changed files with 210 additions and 60 deletions

View File

@ -178,44 +178,48 @@ void CheckExceptionSafety::checkCatchExceptionByValue()
}
//--------------------------------------------------------------------------
// void func() noexcept { throw x; }
//--------------------------------------------------------------------------
void CheckExceptionSafety::noexceptThrows()
static const Token * functionThrowsRecursive(const Function * function, std::set<const Function *> & recursive)
{
const SymbolDatabase* const symbolDatabase = _tokenizer->getSymbolDatabase();
// check for recursion and bail if found
if (recursive.find(function) != recursive.end())
return nullptr;
const std::size_t functions = symbolDatabase->functionScopes.size();
for (std::size_t i = 0; i < functions; ++i) {
const Scope * scope = symbolDatabase->functionScopes[i];
// only check 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() == "try") {
break;
} else if (tok->str() == "throw") {
noexceptThrowError(tok);
break;
} else if (tok->function()) {
const Function * called = tok->function();
// check if called function has an exception specification
if (called->isThrow && called->throwArg) {
noexceptThrowError(tok);
break;
} else if (called->isNoExcept && called->noexceptArg &&
called->noexceptArg->str() != "true") {
noexceptThrowError(tok);
break;
}
}
for (const Token *tok = function->functionScope->classStart->next();
tok != function->functionScope->classEnd; tok = tok->next()) {
if (tok->str() == "try") {
// just bail for now
break;
}
if (tok->str() == "throw") {
return tok;
} else if (tok->function()) {
const Function * called = tok->function();
// check if called function has an exception specification
if (called->isThrow && called->throwArg) {
return tok;
} else if (called->isNoExcept && called->noexceptArg &&
called->noexceptArg->str() != "true") {
return tok;
} else if (functionThrowsRecursive(called, recursive)) {
return tok;
}
}
}
return nullptr;
}
static const Token * functionThrows(const Function * function)
{
std::set<const Function *> recursive;
return functionThrowsRecursive(function, recursive);
}
//--------------------------------------------------------------------------
// void func() noexcept { throw x; }
// void func() throw() { throw x; }
// void func() __attribute__((nothrow)); void func() { throw x; }
//--------------------------------------------------------------------------
void CheckExceptionSafety::nothrowThrows()
{
@ -224,27 +228,27 @@ void CheckExceptionSafety::nothrowThrows()
const std::size_t functions = symbolDatabase->functionScopes.size();
for (std::size_t i = 0; i < functions; ++i) {
const Scope * scope = symbolDatabase->functionScopes[i];
// only check 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() == "try") {
break;
} else if (tok->str() == "throw") {
nothrowThrowError(tok);
break;
} else if (tok->function()) {
const Function * called = tok->function();
// check if called function has an exception specification
if (called->isThrow && called->throwArg) {
nothrowThrowError(tok);
break;
} else if (called->isNoExcept && called->noexceptArg &&
called->noexceptArg->str() != "true") {
nothrowThrowError(tok);
break;
}
}
}
// check noexcept functions
if (scope->function && scope->function->isNoExcept &&
(!scope->function->noexceptArg || scope->function->noexceptArg->str() == "true")) {
const Token *throws = functionThrows(scope->function);
if (throws)
noexceptThrowError(throws);
}
// check throw() functions
else if (scope->function && scope->function->isThrow && !scope->function->throwArg) {
const Token *throws = functionThrows(scope->function);
if (throws)
nothrowThrowError(throws);
}
// check __attribute__((nothrow)) functions
else if (scope->function && scope->function->isAttributeNothrow()) {
const Token *throws = functionThrows(scope->function);
if (throws)
nothrowAttributeThrowError(throws);
}
}
}

View File

@ -61,7 +61,6 @@ public:
checkExceptionSafety.deallocThrow();
checkExceptionSafety.checkRethrowCopy();
checkExceptionSafety.checkCatchExceptionByValue();
checkExceptionSafety.noexceptThrows();
checkExceptionSafety.nothrowThrows();
checkExceptionSafety.unhandledExceptionSpecification();
}
@ -78,10 +77,7 @@ 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 */
/** @brief %Check for functions that throw that shouldn't */
void nothrowThrows();
/** @brief %Check for unhandled exception specification */
@ -122,6 +118,11 @@ private:
reportError(tok, Severity::error, "exceptThrowInNoThrowFunction", "Exception thrown in throw() function.");
}
/** Don't throw exceptions in __attribute__((nothrow)) functions */
void nothrowAttributeThrowError(const Token * const tok) {
reportError(tok, Severity::error, "exceptThrowInAttributeNoThrowFunction", "Exception thrown in __attribute__((nothrow)) function.");
}
/** Missing exception specification */
void unhandledExceptionSpecificationError(const Token * const tok1, const Token * const tok2, const std::string & funcname) {
std::string str1(tok1 ? tok1->str() : "foo");
@ -143,6 +144,7 @@ private:
c.catchExceptionByValueError(0);
c.noexceptThrowError(0);
c.nothrowThrowError(0);
c.nothrowAttributeThrowError(0);
c.unhandledExceptionSpecificationError(0, 0, "funcname");
}
@ -160,6 +162,7 @@ private:
"* Exception caught by value instead of by reference\n"
"* Throwing exception in noexcept function\n"
"* Throwing exception in nothrow() function\n"
"* Throwing exception in __attribute__((nothrow)) function\n"
"* Unhandled exception specification when calling function foo()\n";
}
};

View File

@ -657,6 +657,7 @@ SymbolDatabase::SymbolDatabase(const Tokenizer *tokenizer, const Settings *setti
// regular function
else {
Function* function = addGlobalFunction(scope, tok, argStart, funcStart);
if (!function)
_tokenizer->syntaxError(tok);
@ -1363,6 +1364,7 @@ Function* SymbolDatabase::addGlobalFunction(Scope*& scope, const Token*& tok, co
const_cast<Token *>(tok)->isAttributeDestructor(i->tokenDef->isAttributeDestructor());
const_cast<Token *>(tok)->isAttributePure(i->tokenDef->isAttributePure());
const_cast<Token *>(tok)->isAttributeConst(i->tokenDef->isAttributeConst());
const_cast<Token *>(tok)->isAttributeNothrow(i->tokenDef->isAttributeNothrow());
break;
}
}
@ -1911,6 +1913,9 @@ void SymbolDatabase::printOut(const char *title) const
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::cerr << " isAttributeConst: " << (func->isAttributeConst() ? "true" : "false") << std::endl;
std::cerr << " isAttributePure: " << (func->isAttributePure() ? "true" : "false") << std::endl;
std::cerr << " isAttributeNothrow: " << (func->isAttributeNothrow() ? "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;

View File

@ -590,6 +590,9 @@ public:
bool isAttributeConst() const {
return tokenDef->isAttributeConst();
}
bool isAttributeNothrow() const {
return tokenDef->isAttributeNothrow();
}
const Token *tokenDef; // function name token in class definition
const Token *argDef; // function argument start '(' in class definition

View File

@ -56,6 +56,7 @@ Token::Token(Token **t) :
_isAttributeUnused(false),
_isAttributePure(false),
_isAttributeConst(false),
_isAttributeNothrow(false),
_astOperand1(nullptr),
_astOperand2(nullptr),
_astParent(nullptr)
@ -196,6 +197,7 @@ void Token::deleteThis()
_isAttributeUnused = _next->_isAttributeUnused;
_isAttributePure = _next->_isAttributePure;
_isAttributeConst = _next->_isAttributeConst;
_isAttributeNothrow = _next->_isAttributeNothrow;
_varId = _next->_varId;
_fileIndex = _next->_fileIndex;
_linenr = _next->_linenr;
@ -223,6 +225,7 @@ void Token::deleteThis()
_isAttributeUnused = _previous->_isAttributeUnused;
_isAttributePure = _previous->_isAttributePure;
_isAttributeConst = _previous->_isAttributeConst;
_isAttributeNothrow = _previous->_isAttributeNothrow;
_varId = _previous->_varId;
_fileIndex = _previous->_fileIndex;
_linenr = _previous->_linenr;

View File

@ -312,6 +312,12 @@ public:
void isAttributeConst(bool value) {
_isAttributeConst = value;
}
bool isAttributeNothrow() const {
return _isAttributeNothrow;
}
void isAttributeNothrow(bool value) {
_isAttributeNothrow = value;
}
static const Token *findsimplematch(const Token *tok, const char pattern[]);
static const Token *findsimplematch(const Token *tok, const char pattern[], const Token *end);
@ -695,6 +701,7 @@ private:
bool _isAttributeUnused; // __attribute__((unused))
bool _isAttributePure; // __attribute__((pure))
bool _isAttributeConst; // __attribute__((const))
bool _isAttributeNothrow; // __attribute__((nothrow))
/** Updates internal property cache like _isName or _isBoolean.
Called after any _str() modification. */

View File

@ -120,6 +120,7 @@ Token *Tokenizer::copyTokens(Token *dest, const Token *first, const Token *last,
tok2->isAttributeUnused(tok->isAttributeUnused());
tok2->isAttributePure(tok->isAttributePure());
tok2->isAttributeConst(tok->isAttributeConst());
tok2->isAttributeNothrow(tok->isAttributeNothrow());
tok2->varId(tok->varId());
// Check for links and fix them up
@ -9057,7 +9058,7 @@ void Tokenizer::simplifyAttribute()
{
for (Token *tok = list.front(); tok; tok = tok->next()) {
while (Token::Match(tok, "__attribute__|__attribute (") && tok->next()->link() && tok->next()->link()->next()) {
if (Token::simpleMatch(tok->tokAt(2), "( constructor")) {
if (Token::Match(tok->tokAt(2), "( constructor|__constructor__")) {
// prototype for constructor is: void func(void);
if (tok->next()->link()->next()->str() == "void") // __attribute__((constructor)) void func() {}
tok->next()->link()->next()->next()->isAttributeConstructor(true);
@ -9067,7 +9068,7 @@ void Tokenizer::simplifyAttribute()
tok->next()->link()->next()->isAttributeConstructor(true);
}
if (Token::simpleMatch(tok->tokAt(2), "( destructor")) {
else if (Token::Match(tok->tokAt(2), "( destructor|__destructor__")) {
// prototype for destructor is: void func(void);
if (tok->next()->link()->next()->str() == "void") // __attribute__((destructor)) void func() {}
tok->next()->link()->next()->next()->isAttributeDestructor(true);
@ -9077,7 +9078,7 @@ void Tokenizer::simplifyAttribute()
tok->next()->link()->next()->isAttributeDestructor(true);
}
if (Token::simpleMatch(tok->tokAt(2), "( unused )")) {
else if (Token::Match(tok->tokAt(2), "( unused|__unused__ )")) {
// check if after variable name
if (Token::Match(tok->next()->link()->next(), ";|=")) {
if (Token::Match(tok->previous(), "%type%"))
@ -9089,26 +9090,57 @@ void Tokenizer::simplifyAttribute()
tok->next()->link()->next()->isAttributeUnused(true);
}
if (Token::simpleMatch(tok->tokAt(2), "( pure )")) {
else if (Token::Match(tok->tokAt(2), "( pure|__pure__ )")) {
// type func(...) __attribute__((pure));
if (tok->previous() && tok->previous()->link() && Token::Match(tok->previous()->link()->previous(), "%var% ("))
tok->previous()->link()->previous()->isAttributePure(true);
// type __attribute__((pure)) func() { }
else if (Token::Match(tok->next()->link(), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2)->linkAt(2), ") %var% ("))
tok->next()->link()->linkAt(2)->linkAt(2)->next()->isAttributePure(true);
else if (Token::Match(tok->next()->link(), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2), ") %var% ("))
tok->next()->link()->linkAt(2)->next()->isAttributePure(true);
else if (Token::Match(tok->next()->link(), ") %var% ("))
tok->next()->link()->next()->isAttributePure(true);
}
if (Token::simpleMatch(tok->tokAt(2), "( const )")) {
else if (Token::Match(tok->tokAt(2), "( const|__const__ )")) {
// type func(...) __attribute__((const));
if (tok->previous() && tok->previous()->link() && Token::Match(tok->previous()->link()->previous(), "%var% ("))
tok->previous()->link()->previous()->isAttributeConst(true);
// type __attribute__((const)) func() { }
else if (Token::Match(tok->next()->link(), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2)->linkAt(2), ") %var% ("))
tok->next()->link()->linkAt(2)->linkAt(2)->next()->isAttributeConst(true);
else if (Token::Match(tok->next()->link(), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2), ") %var% ("))
tok->next()->link()->linkAt(2)->next()->isAttributeConst(true);
else if (Token::Match(tok->next()->link(), ") %var% ("))
tok->next()->link()->next()->isAttributeConst(true);
}
else if (Token::Match(tok->tokAt(2), "( nothrow|__nothrow__")) {
// type func(...) __attribute__((nothrow));
if (tok->previous() && tok->previous()->link() && Token::Match(tok->previous()->link()->previous(), "%var% ("))
tok->previous()->link()->previous()->isAttributeNothrow(true);
// type __attribute__((nothrow)) func() { }
else if (Token::Match(tok->next()->link(), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2)->linkAt(2), ") %var% ("))
tok->next()->link()->linkAt(2)->linkAt(2)->next()->isAttributeNothrow(true);
else if (Token::Match(tok->next()->link(), ") __attribute__|__attribute (") &&
Token::Match(tok->next()->link()->linkAt(2), ") %var% ("))
tok->next()->link()->linkAt(2)->next()->isAttributeNothrow(true);
else if (Token::Match(tok->next()->link(), ") %var% ("))
tok->next()->link()->next()->isAttributeNothrow(true);
}
Token::eraseTokens(tok, tok->next()->link()->next());
tok->deleteThis();
}

View File

@ -153,6 +153,7 @@ void TokenList::addtoken(const Token * tok, const unsigned int lineno, const uns
_back->isAttributeUnused(tok->isAttributeUnused());
_back->isAttributePure(tok->isAttributePure());
_back->isAttributeConst(tok->isAttributeConst());
_back->isAttributeNothrow(tok->isAttributeNothrow());
}
//---------------------------------------------------------------------------
// InsertTokens - Copy and insert tokens
@ -187,6 +188,7 @@ void TokenList::insertTokens(Token *dest, const Token *src, unsigned int n)
dest->isAttributeUnused(src->isAttributeUnused());
dest->isAttributePure(src->isAttributePure());
dest->isAttributeConst(src->isAttributeConst());
dest->isAttributeNothrow(src->isAttributeNothrow());
src = src->next();
--n;
}

View File

@ -356,6 +356,19 @@ private:
"}\n");
ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:1]: (warning) Unhandled exception specification when calling function myThrowingFoo().\n", errout.str());
}
void nothrowAttributeThrow() {
check("void func1() throw(int) { throw 1; }\n"
"void func2() __attribute((nothrow)); void func1() { throw 1; }\n"
"void func3() __attribute((nothrow)); void func1() { func1(); }\n");
ASSERT_EQUALS("[test.cpp:2]: (error) Exception thrown in __attribute__((nothrow)) function.\n"
"[test.cpp:3]: (error) Exception thrown in __attribute__((nothrow)) function.\n", errout.str());
// avoid false positives
check("const char *func() __attribute((nothrow)); void func1() { return 0; }\n");
ASSERT_EQUALS("", errout.str());
}
};
REGISTER_TEST(TestExceptionSafety)

View File

@ -234,6 +234,8 @@ private:
TEST_CASE(noexceptFunction2);
TEST_CASE(noexceptFunction3);
TEST_CASE(noexceptFunction4);
TEST_CASE(nothrowAttributeFunction);
}
void array() const {
@ -2144,6 +2146,20 @@ private:
}
}
void nothrowAttributeFunction() {
GET_SYMBOL_DB("void func() __attribute__((nothrow));\n"
"void func() { }\n");
ASSERT_EQUALS("", errout.str());
ASSERT_EQUALS(true, db != nullptr); // not null
if (db) {
const Function *func = findFunctionByName("func", &db->scopeList.front());
ASSERT_EQUALS(true, func != nullptr);
if (func)
ASSERT_EQUALS(true, func->isAttributeNothrow());
}
}
};
REGISTER_TEST(TestSymbolDatabase)

View File

@ -444,6 +444,8 @@ private:
TEST_CASE(removedeclspec);
TEST_CASE(removeattribute);
TEST_CASE(functionAttributeBefore);
TEST_CASE(functionAttributeAfter);
TEST_CASE(cpp0xtemplate1);
TEST_CASE(cpp0xtemplate2);
TEST_CASE(cpp0xtemplate3);
@ -7089,6 +7091,66 @@ private:
ASSERT_EQUALS("int vecint ;", tokenizeAndStringify("int __attribute((mode(SI))) __attribute((vector_size (16))) vecint;"));
}
void functionAttributeBefore() {
const char code[] = "void __attribute__((pure)) __attribute__((nothrow)) __attribute__((const)) func1();\n"
"void __attribute__((__pure__)) __attribute__((__nothrow__)) __attribute__((__const__)) func2();\n"
"void __attribute__((nothrow)) __attribute__((pure)) __attribute__((const)) func3();\n"
"void __attribute__((__nothrow__)) __attribute__((__pure__)) __attribute__((__const__)) func4();";
const char expected[] = "void func1 ( ) ; void func2 ( ) ; void func3 ( ) ; void func4 ( ) ;";
errout.str("");
Settings settings;
// tokenize..
Tokenizer tokenizer(&settings, this);
std::istringstream istr(code);
tokenizer.tokenize(istr, "test.cpp");
// Expected result..
ASSERT_EQUALS(expected, tokenizer.tokens()->stringifyList(0, false));
const Token * func1 = Token::findsimplematch(tokenizer.tokens(), "func1");
const Token * func2 = Token::findsimplematch(tokenizer.tokens(), "func2");
const Token * func3 = Token::findsimplematch(tokenizer.tokens(), "func3");
const Token * func4 = Token::findsimplematch(tokenizer.tokens(), "func4");
ASSERT(func1 && func1->isAttributePure() && func1->isAttributeNothrow() && func1->isAttributeConst());
ASSERT(func2 && func2->isAttributePure() && func2->isAttributeNothrow() && func2->isAttributeConst());
ASSERT(func3 && func3->isAttributePure() && func3->isAttributeNothrow() && func3->isAttributeConst());
ASSERT(func4 && func4->isAttributePure() && func4->isAttributeNothrow() && func4->isAttributeConst());
}
void functionAttributeAfter() {
const char code[] = "void func1() __attribute__((pure)) __attribute__((nothrow)) __attribute__((const));\n"
"void func2() __attribute__((__pure__)) __attribute__((__nothrow__)) __attribute__((__const__));\n"
"void func3() __attribute__((nothrow)) __attribute__((pure)) __attribute__((const));\n"
"void func4() __attribute__((__nothrow__)) __attribute__((__pure__)) __attribute__((__const__));";
const char expected[] = "void func1 ( ) ; void func2 ( ) ; void func3 ( ) ; void func4 ( ) ;";
errout.str("");
Settings settings;
// tokenize..
Tokenizer tokenizer(&settings, this);
std::istringstream istr(code);
tokenizer.tokenize(istr, "test.cpp");
// Expected result..
ASSERT_EQUALS(expected, tokenizer.tokens()->stringifyList(0, false));
const Token * func1 = Token::findsimplematch(tokenizer.tokens(), "func1");
const Token * func2 = Token::findsimplematch(tokenizer.tokens(), "func2");
const Token * func3 = Token::findsimplematch(tokenizer.tokens(), "func3");
const Token * func4 = Token::findsimplematch(tokenizer.tokens(), "func4");
ASSERT(func1 && func1->isAttributePure() && func1->isAttributeNothrow() && func1->isAttributeConst());
ASSERT(func2 && func2->isAttributePure() && func2->isAttributeNothrow() && func2->isAttributeConst());
ASSERT(func3 && func3->isAttributePure() && func3->isAttributeNothrow() && func3->isAttributeConst());
ASSERT(func4 && func4->isAttributePure() && func4->isAttributeNothrow() && func4->isAttributeConst());
}
void cpp0xtemplate1() {
const char *code = "template <class T>\n"
"void fn2 (T t = []{return 1;}())\n"