Fixed #2079 (detect side effects in assert)

This commit is contained in:
Zachary Blair 2010-10-10 13:05:06 -07:00
parent 81aed3fbd7
commit d9967d4fd2
4 changed files with 108 additions and 1 deletions

View File

@ -229,6 +229,33 @@ void CheckOther::checkSelfAssignment()
} }
} }
//---------------------------------------------------------------------------
// int a = 1;
// assert(a = 2); // <- assert should not have a side-effect
//---------------------------------------------------------------------------
void CheckOther::checkAssignmentInAssert()
{
const char assertPattern[] = "assert ( %any%";
const Token *tok = Token::findmatch(_tokenizer->tokens(), assertPattern);
const Token *endTok = tok ? tok->next()->link() : NULL;
while (tok && endTok)
{
const Token* varTok = Token::findmatch(tok->tokAt(2), "%var% --|++|+=|-=|*=|/=|&=|^=|=", endTok);
if (varTok)
{
assignmentInAssertError(tok, varTok->str());
}
else if ((varTok = Token::findmatch(tok->tokAt(2), "--|++ %var%", endTok)))
{
assignmentInAssertError(tok, varTok->strAt(1));
}
tok = Token::findmatch(endTok->next(), assertPattern);
endTok = tok ? tok->next()->link() : NULL;
}
}
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// strtol(str, 0, radix) <- radix must be 0 or 2-36 // strtol(str, 0, radix) <- radix must be 0 or 2-36
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -4156,6 +4183,12 @@ void CheckOther::selfAssignmentError(const Token *tok, const std::string &varnam
"selfAssignment", "Redundant assignment of \"" + varname + "\" to itself"); "selfAssignment", "Redundant assignment of \"" + varname + "\" to itself");
} }
void CheckOther::assignmentInAssertError(const Token *tok, const std::string &varname)
{
reportError(tok, Severity::error,
"assignmentInAssert", "Assert statement modifies '" + varname + "' instead of just testing it");
}
void CheckOther::misusedScopeObjectError(const Token *tok, const std::string& varname) void CheckOther::misusedScopeObjectError(const Token *tok, const std::string& varname)
{ {
reportError(tok, Severity::error, reportError(tok, Severity::error,

View File

@ -63,6 +63,7 @@ public:
checkOther.sizeofsizeof(); checkOther.sizeofsizeof();
checkOther.sizeofCalculation(); checkOther.sizeofCalculation();
checkOther.checkRedundantAssignmentInSwitch(); checkOther.checkRedundantAssignmentInSwitch();
checkOther.checkAssignmentInAssert();
} }
/** @brief Run checks against the simplified token list */ /** @brief Run checks against the simplified token list */
@ -184,6 +185,9 @@ public:
/** @brief %Check for assigning a variable to itself*/ /** @brief %Check for assigning a variable to itself*/
void checkSelfAssignment(); void checkSelfAssignment();
/** @brief %Check for assignment to a variable in an assert test*/
void checkAssignmentInAssert();
/** @brief %Check for objects that are destroyed immediately */ /** @brief %Check for objects that are destroyed immediately */
void checkMisusedScopedObject(); void checkMisusedScopedObject();
@ -213,6 +217,7 @@ public:
void fflushOnInputStreamError(const Token *tok, const std::string &varname); void fflushOnInputStreamError(const Token *tok, const std::string &varname);
void redundantAssignmentInSwitchError(const Token *tok, const std::string &varname); void redundantAssignmentInSwitchError(const Token *tok, const std::string &varname);
void selfAssignmentError(const Token *tok, const std::string &varname); void selfAssignmentError(const Token *tok, const std::string &varname);
void assignmentInAssertError(const Token *tok, const std::string &varname);
void misusedScopeObjectError(const Token *tok, const std::string &varname); void misusedScopeObjectError(const Token *tok, const std::string &varname);
void getErrorMessages() void getErrorMessages()
@ -244,6 +249,7 @@ public:
sizeofCalculationError(0); sizeofCalculationError(0);
redundantAssignmentInSwitchError(0, "varname"); redundantAssignmentInSwitchError(0, "varname");
selfAssignmentError(0, "varname"); selfAssignmentError(0, "varname");
assignmentInAssertError(0, "varname");
invalidScanfError(0); invalidScanfError(0);
// optimisations // optimisations
@ -267,6 +273,7 @@ public:
"* using uninitialized variables and data\n" "* using uninitialized variables and data\n"
"* using fflush() on an input stream\n" "* using fflush() on an input stream\n"
"* scoped object destroyed immediately after construction\n" "* scoped object destroyed immediately after construction\n"
"* assignment in an assert statement\n"
// style // style
"* C-style pointer cast in cpp file\n" "* C-style pointer cast in cpp file\n"

View File

@ -8151,7 +8151,11 @@ void Tokenizer::simplifyAssignmentInFunctionCall()
{ {
if (tok->str() == "(") if (tok->str() == "(")
tok = tok->link(); tok = tok->link();
else if (Token::Match(tok, "[;{}] %var% ( %var% =") && Token::simpleMatch(tok->tokAt(2)->link(), ") ;"))
// Find 'foo(var='. Exclude 'assert(var=' to allow tests to check that assert(...) does not contain side-effects
else if (Token::Match(tok, "[;{}] %var% ( %var% =") &&
Token::simpleMatch(tok->tokAt(2)->link(), ") ;") &&
tok->strAt(1) != "assert")
{ {
const std::string funcname(tok->strAt(1)); const std::string funcname(tok->strAt(1));
const Token * const vartok = tok->tokAt(3); const Token * const vartok = tok->tokAt(3);

View File

@ -115,6 +115,8 @@ private:
TEST_CASE(testMisusedScopeObjectDoesNotPickLocalClassConstructors); TEST_CASE(testMisusedScopeObjectDoesNotPickLocalClassConstructors);
TEST_CASE(testMisusedScopeObjectDoesNotPickUsedObject); TEST_CASE(testMisusedScopeObjectDoesNotPickUsedObject);
TEST_CASE(trac2071); TEST_CASE(trac2071);
TEST_CASE(assignmentInAssert);
} }
void check(const char code[]) void check(const char code[])
@ -135,6 +137,7 @@ private:
checkOther.sizeofsizeof(); checkOther.sizeofsizeof();
checkOther.sizeofCalculation(); checkOther.sizeofCalculation();
checkOther.checkRedundantAssignmentInSwitch(); checkOther.checkRedundantAssignmentInSwitch();
checkOther.checkAssignmentInAssert();
// Simplify token list.. // Simplify token list..
tokenizer.simplifyTokenList(); tokenizer.simplifyTokenList();
@ -3169,6 +3172,66 @@ private:
); );
ASSERT_EQUALS("", errout.str()); ASSERT_EQUALS("", errout.str());
} }
void assignmentInAssert()
{
check("void f() {\n"
" int a = 0;\n"
" assert(a = 2);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:3]: (error) Assert statement modifies 'a' instead of just testing it\n", errout.str());
check("void f() {\n"
" int a = 0;\n"
" assert(a == 2);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("", errout.str());
check("void f() {\n"
" int a = 0;\n"
" int b = 0;\n"
" assert(a == 2 && b = 1);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:4]: (error) Assert statement modifies 'b' instead of just testing it\n", errout.str());
check("void f() {\n"
" int a = 0;\n"
" assert(a += 2);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:3]: (error) Assert statement modifies 'a' instead of just testing it\n", errout.str());
check("void f() {\n"
" int a = 0;\n"
" assert(a *= 2);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:3]: (error) Assert statement modifies 'a' instead of just testing it\n", errout.str());
check("void f() {\n"
" int a = 0;\n"
" assert(a -= 2);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:3]: (error) Assert statement modifies 'a' instead of just testing it\n", errout.str());
check("void f() {\n"
" int a = 0;\n"
" assert(a --);\n"
" return a;\n"
"}\n"
);
ASSERT_EQUALS("[test.cpp:3]: (error) Assert statement modifies 'a' instead of just testing it\n", errout.str());
}
}; };
REGISTER_TEST(TestOther) REGISTER_TEST(TestOther)