From fc39a4d1bbd8add6104d2fbc563dd579e3201c7c Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Mon, 26 Sep 2022 11:20:42 -0500 Subject: [PATCH] Fix 11171: False positive: missing return statement when std::enable_if_t is used (#4504) * Fix 11171: False positive: missing return statement when std::enable_if_t is used * Format * Return false * Workaround bug in cppcheck --- lib/checkfunctions.cpp | 2 - lib/symboldatabase.cpp | 119 ++++++++++++++++++++++++++--------------- test/testfunctions.cpp | 32 ++++++++++- 3 files changed, 106 insertions(+), 47 deletions(-) diff --git a/lib/checkfunctions.cpp b/lib/checkfunctions.cpp index c5ee392c6..bd18f6b98 100644 --- a/lib/checkfunctions.cpp +++ b/lib/checkfunctions.cpp @@ -310,8 +310,6 @@ void CheckFunctions::checkMissingReturn() continue; if (Token::Match(function->retDef, "%name% (") && function->retDef->isUpperCaseName()) continue; - if (mTokenizer->isCPP() && Token::Match(function->retDef, "std :: enable_if|enable_if_t")) - continue; if (Function::returnsVoid(function, true)) continue; const Token *errorToken = checkMissingReturnScope(scope->bodyEnd, mSettings->library); diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 55feedf1a..b7b409f32 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -2490,10 +2490,13 @@ const Token *Function::setFlags(const Token *tok1, const Scope *scope) tok1 = tok1->link()->previous(); } - // Function template - else if (tok1->link() && tok1->str() == ">" && Token::simpleMatch(tok1->link()->previous(), "template <")) { - templateDef = tok1->link()->previous(); - break; + else if (tok1->link() && tok1->str() == ">") { + // Function template + if (Token::simpleMatch(tok1->link()->previous(), "template <")) { + templateDef = tok1->link()->previous(); + break; + } + tok1 = tok1->link(); } } return tok1; @@ -2853,60 +2856,88 @@ static bool isUnknownType(const Token* start, const Token* end) return false; } -bool Function::returnsConst(const Function* function, bool unknown) +static const Token* getEnableIfReturnType(const Token* start) { - if (!function) - return false; - if (function->type != Function::eFunction) - return false; - const Token* defEnd = function->returnDefEnd(); - if (Token::findsimplematch(function->retDef, "const", defEnd)) - return true; - // Check for unknown types, which could be a const - if (isUnknownType(function->retDef, defEnd)) - return unknown; - return false; + if (!start) + return nullptr; + for (const Token* tok = start->next(); precedes(tok, start->link()); tok = tok->next()) { + if (tok->link() && Token::Match(tok, "(|[|{|<")) { + tok = tok->link(); + continue; + } + if (Token::simpleMatch(tok, ",")) + return tok->next(); + } + return nullptr; } -bool Function::returnsReference(const Function* function, bool unknown) -{ - if (!function) - return false; - if (function->type != Function::eFunction) - return false; - const Token* defEnd = function->returnDefEnd(); - if (defEnd->strAt(-1) == "&") - return true; - // Check for unknown types, which could be a reference - if (isUnknownType(function->retDef, defEnd)) - return unknown; - return false; -} - -bool Function::returnsVoid(const Function* function, bool unknown) +template +static bool checkReturns(const Function* function, bool unknown, bool emptyEnableIf, Predicate pred) { if (!function) return false; if (function->type != Function::eFunction && function->type != Function::eOperatorEqual) return false; + const Token* defStart = function->retDef; const Token* defEnd = function->returnDefEnd(); - if (defEnd->strAt(-1) == "void") - return true; - // Check for unknown types, which could be a void type - if (isUnknownType(function->retDef, defEnd)) + if (!defStart) return unknown; - if (unknown) { - // void STDCALL foo() - const Token *def; - bool isVoid = false; - for (def = function->retDef; def && def->isName(); def = def->next()) - isVoid |= (def->str() == "void"); - if (isVoid && def && !Token::Match(def, "*|&|&&")) - return true; + if (!defEnd) + return unknown; + if (defEnd == defStart) + return unknown; + if (pred(defStart, defEnd)) + return true; + if (Token::Match(defEnd->tokAt(-1), "*|&|&&")) + return false; + // void STDCALL foo() + while (defEnd->previous() != defStart && Token::Match(defEnd->tokAt(-2), "%name%|> %name%") && + !Token::Match(defEnd->tokAt(-2), "const|volatile")) + defEnd = defEnd->previous(); + // enable_if + const Token* enableIfEnd = nullptr; + if (Token::simpleMatch(defEnd->previous(), ">")) + enableIfEnd = defEnd->previous(); + else if (Token::simpleMatch(defEnd->tokAt(-3), "> :: type")) + enableIfEnd = defEnd->tokAt(-3); + if (enableIfEnd && enableIfEnd->link() && + Token::Match(enableIfEnd->link()->previous(), "enable_if|enable_if_t|EnableIf")) { + if (const Token* start = getEnableIfReturnType(enableIfEnd->link())) { + defStart = start; + defEnd = enableIfEnd; + } else { + return emptyEnableIf; + } } + assert(defEnd != defStart); + if (pred(defStart, defEnd)) + return true; + if (isUnknownType(defStart, defEnd)) + return unknown; return false; } +bool Function::returnsConst(const Function* function, bool unknown) +{ + return checkReturns(function, unknown, false, [](const Token* defStart, const Token* defEnd) { + return Token::findsimplematch(defStart, "const", defEnd); + }); +} + +bool Function::returnsReference(const Function* function, bool unknown) +{ + return checkReturns(function, unknown, false, [](UNUSED const Token* defStart, const Token* defEnd) { + return Token::simpleMatch(defEnd->previous(), "&"); + }); +} + +bool Function::returnsVoid(const Function* function, bool unknown) +{ + return checkReturns(function, unknown, true, [](UNUSED const Token* defStart, const Token* defEnd) { + return Token::simpleMatch(defEnd->previous(), "void"); + }); +} + std::vector Function::findReturns(const Function* f) { std::vector result; diff --git a/test/testfunctions.cpp b/test/testfunctions.cpp index 0e3606c6b..1c361d759 100644 --- a/test/testfunctions.cpp +++ b/test/testfunctions.cpp @@ -1726,7 +1726,37 @@ private: "[test.cpp:10]: (error) Found a exit path from function with non-void return type that has missing return statement\n", errout.str()); - check("std::enable_if_t f() {}"); // #11171 + // #11171 + check("std::enable_if_t f() {}"); + ASSERT_EQUALS("", errout.str()); + + check("std::enable_if_t f() {}"); + ASSERT_EQUALS( + "[test.cpp:1]: (error) Found a exit path from function with non-void return type that has missing return statement\n", + errout.str()); + + check("template std::enable_if_t{}, int> f(T) {}"); + ASSERT_EQUALS( + "[test.cpp:1]: (error) Found a exit path from function with non-void return type that has missing return statement\n", + errout.str()); + + check("template std::enable_if_t{}> f(T) {}"); + ASSERT_EQUALS("", errout.str()); + + check("typename std::enable_if::type f() {}"); + ASSERT_EQUALS("", errout.str()); + + check("typename std::enable_if::type f() {}"); + ASSERT_EQUALS( + "[test.cpp:1]: (error) Found a exit path from function with non-void return type that has missing return statement\n", + errout.str()); + + check("template typename std::enable_if{}, int>::type f(T) {}"); + ASSERT_EQUALS( + "[test.cpp:1]: (error) Found a exit path from function with non-void return type that has missing return statement\n", + errout.str()); + + check("template typename std::enable_if{}>::type f(T) {}"); ASSERT_EQUALS("", errout.str()); }