diff --git a/lib/checkexceptionsafety.cpp b/lib/checkexceptionsafety.cpp index 9d7ba51a5..abe0c60d3 100644 --- a/lib/checkexceptionsafety.cpp +++ b/lib/checkexceptionsafety.cpp @@ -178,44 +178,48 @@ void CheckExceptionSafety::checkCatchExceptionByValue() } -//-------------------------------------------------------------------------- -// void func() noexcept { throw x; } -//-------------------------------------------------------------------------- -void CheckExceptionSafety::noexceptThrows() +static const Token * functionThrowsRecursive(const Function * function, std::set & 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 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); } } } diff --git a/lib/checkexceptionsafety.h b/lib/checkexceptionsafety.h index 1ecd3ee10..12b5345ed 100644 --- a/lib/checkexceptionsafety.h +++ b/lib/checkexceptionsafety.h @@ -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"; } }; diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index f7fae721d..53e5ce425 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -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(tok)->isAttributeDestructor(i->tokenDef->isAttributeDestructor()); const_cast(tok)->isAttributePure(i->tokenDef->isAttributePure()); const_cast(tok)->isAttributeConst(i->tokenDef->isAttributeConst()); + const_cast(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; diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 730408f47..0f1c98cc2 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -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 diff --git a/lib/token.cpp b/lib/token.cpp index d978facc0..16310f287 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -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; diff --git a/lib/token.h b/lib/token.h index 01b126eac..2eb2b5f22 100644 --- a/lib/token.h +++ b/lib/token.h @@ -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. */ diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index e063569e2..719b826bf 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -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(); } diff --git a/lib/tokenlist.cpp b/lib/tokenlist.cpp index dcbc75aba..6fd0d1b77 100644 --- a/lib/tokenlist.cpp +++ b/lib/tokenlist.cpp @@ -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; } diff --git a/test/testexceptionsafety.cpp b/test/testexceptionsafety.cpp index 1fc1636cc..12c2e4535 100644 --- a/test/testexceptionsafety.cpp +++ b/test/testexceptionsafety.cpp @@ -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) diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 75a03e1df..2df61ea5a 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -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) diff --git a/test/testtokenize.cpp b/test/testtokenize.cpp index 9259b7d3f..b0455610c 100644 --- a/test/testtokenize.cpp +++ b/test/testtokenize.cpp @@ -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 \n" "void fn2 (T t = []{return 1;}())\n"