Add check for return value of boolean function (#1451)

* Add check for return value of boolean function

The rule for converting an integer to a boolean is that 0 is mapped to
false and everything else is mapped to true. There is nothing wrong with
the following code (according to the standards):

    bool f()
    {
        return -1;
    }

and neither gcc nor clang will warn about it. However, it's a bit
confusing. This commit adds a check that warns when a value other than 0
or 1 is returned from a boolean function (similar to the existing check
that functions with boolean arguments are only passed 0 or 1). Since the
code is perfectly legal, set the severity to "Style".

* Use early continue and remove some braces

* Add testcase with multiple returns

* Avoid null pointer dereference in case of return without operand

* Skip lambdas

Add TODO-test cases that shows FPs when the return type of lambdas are
specified explicitly (this is a problem with findLambdaEndToken).

* Enable testcases
This commit is contained in:
rikardfalkeborn 2018-11-01 11:08:16 +01:00 committed by Daniel Marjamäki
parent 88008fedb1
commit 869e4ba6ab
3 changed files with 136 additions and 1 deletions

View File

@ -27,6 +27,7 @@
#include "symboldatabase.h"
#include "token.h"
#include "tokenize.h"
#include "valueflow.h"
#include <cstddef>
#include <list>
@ -454,3 +455,31 @@ void CheckBool::assignBoolToFloatError(const Token *tok)
reportError(tok, Severity::style, "assignBoolToFloat",
"Boolean value assigned to floating point variable.", CWE704, false);
}
void CheckBool::returnValueOfFunctionReturningBool(void)
{
if (!mSettings->isEnabled(Settings::STYLE))
return;
const SymbolDatabase * const symbolDatabase = mTokenizer->getSymbolDatabase();
for (const Scope * scope : symbolDatabase->functionScopes) {
if (!(scope->function && Token::Match(scope->function->retDef, "bool|_Bool")))
continue;
for (const Token* tok = scope->bodyStart->next(); tok && (tok != scope->bodyEnd); tok = tok->next()) {
// Skip lambdas
const Token* tok2 = findLambdaEndToken(tok);
if (tok2)
tok = tok2;
else if (Token::simpleMatch(tok, "return") && tok->astOperand1() &&
(tok->astOperand1()->getValueGE(2, mSettings) || tok->astOperand1()->getValueLE(-1, mSettings)))
returnValueBoolError(tok);
}
}
}
void CheckBool::returnValueBoolError(const Token *tok)
{
reportError(tok, Severity::style, "returnNonBoolInBooleanFunction", "Non-boolean value returned from function returning bool");
}

View File

@ -58,6 +58,7 @@ public:
checkBool.checkComparisonOfBoolWithInt();
checkBool.checkAssignBoolToFloat();
checkBool.pointerArithBool();
checkBool.returnValueOfFunctionReturningBool();
}
/** @brief Run checks against the simplified token list */
@ -100,6 +101,9 @@ public:
void pointerArithBool();
void pointerArithBoolCond(const Token *tok);
/** @brief %Check if a function returning bool returns an integer other than 0 or 1 */
void returnValueOfFunctionReturningBool();
private:
// Error messages..
void comparisonOfFuncReturningBoolError(const Token *tok, const std::string &expression);
@ -112,6 +116,7 @@ private:
void bitwiseOnBooleanError(const Token *tok, const std::string &varname, const std::string &op);
void comparisonOfBoolExpressionWithIntError(const Token *tok, bool n0o1);
void pointerArithBoolError(const Token *tok);
void returnValueBoolError(const Token *tok);
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override {
CheckBool c(nullptr, settings, errorLogger);
@ -126,6 +131,7 @@ private:
c.comparisonOfBoolExpressionWithIntError(nullptr, true);
c.pointerArithBoolError(nullptr);
c.comparisonOfBoolWithInvalidComparator(nullptr, "expression");
c.returnValueBoolError(nullptr);
}
static std::string myName() {
@ -140,7 +146,8 @@ private:
"- comparison of a boolean value with boolean value using relational operator\n"
"- using bool in bitwise expression\n"
"- pointer addition in condition (either dereference is forgot or pointer overflow is required to make the condition false)\n"
"- Assigning bool value to pointer or float\n";
"- Assigning bool value to pointer or float\n"
"- Returning an integer other than 0 or 1 from a function with boolean return value\n";
}
};
/// @}

View File

@ -64,6 +64,8 @@ private:
// Converting pointer addition result to bool
TEST_CASE(pointerArithBool1);
TEST_CASE(returnNonBool);
}
void check(const char code[], bool experimental = false, const char filename[] = "test.cpp") {
@ -1008,6 +1010,103 @@ private:
"}");
ASSERT_EQUALS("[test.cpp:2]: (error) Converting pointer arithmetic result to bool. The bool is always true unless there is undefined behaviour.\n", errout.str());
}
void returnNonBool() {
check("bool f(void) {\n"
" return 0;\n"
"}");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
" return 1;\n"
"}");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
" return 2;\n"
"}");
ASSERT_EQUALS("[test.cpp:2]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
" return -1;\n"
"}");
ASSERT_EQUALS("[test.cpp:2]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
" return 1 + 1;\n"
"}");
ASSERT_EQUALS("[test.cpp:2]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
" int x = 0;\n"
" return x;\n"
"}");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
" int x = 10;\n"
" return x;\n"
"}");
ASSERT_EQUALS("[test.cpp:3]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
" return 2 < 1;\n"
"}");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
" int ret = 0;\n"
" if (a)\n"
" ret = 1;\n"
" return ret;\n"
"}");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
" int ret = 0;\n"
" if (a)\n"
" ret = 3;\n"
" return ret;\n"
"}");
ASSERT_EQUALS("[test.cpp:5]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
" if (a)\n"
" return 3;\n"
" return 4;\n"
"}");
ASSERT_EQUALS("[test.cpp:3]: (style) Non-boolean value returned from function returning bool\n"
"[test.cpp:4]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
" return;\n"
"}");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
"auto x = [](void) { return -1; };\n"
"return false;\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
"auto x = [](void) { return -1; };\n"
"return 2;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3]: (style) Non-boolean value returned from function returning bool\n", errout.str());
check("bool f(void) {\n"
"auto x = [](void) -> int { return -1; };\n"
"return false;\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("bool f(void) {\n"
"auto x = [](void) -> int { return -1; };\n"
"return 2;\n"
"}\n");
ASSERT_EQUALS("[test.cpp:3]: (style) Non-boolean value returned from function returning bool\n", errout.str());
}
};
REGISTER_TEST(TestBool)