feat: analyze function side effects (#2901)

This commit is contained in:
miltolstoy 2020-11-26 18:34:42 +02:00 committed by GitHub
parent 1e8eb28390
commit 5d299016f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 584 additions and 13 deletions

View File

@ -1511,12 +1511,16 @@ bool CheckUnusedVar::isRecordTypeWithoutSideEffects(const Type* type)
if (initValueVar && !isVariableWithoutSideEffects(*initValueVar)) {
return withoutSideEffects = false;
}
if ((valueToken->tokType() == Token::Type::eFunction) ||
(valueToken->tokType() == Token::Type::eName) ||
if ((valueToken->tokType() == Token::Type::eName) ||
(valueToken->tokType() == Token::Type::eLambda) ||
(valueToken->tokType() == Token::Type::eOther)) {
return withoutSideEffects = false;
}
const Function* initValueFunc = valueToken->function();
if (initValueFunc && !isFunctionWithoutSideEffects(*initValueFunc, valueToken,
std::list<const Function*>{})) {
return withoutSideEffects = false;
}
}
}
}
@ -1586,3 +1590,71 @@ bool CheckUnusedVar::isEmptyType(const Type* type)
emptyType=false; // unknown types are assumed to be nonempty
return emptyType;
}
bool CheckUnusedVar::isFunctionWithoutSideEffects(const Function& func, const Token* functionUsageToken,
std::list<const Function*> checkedFuncs) {
// no body to analyze
if (!func.hasBody()) {
return false;
}
for (const Token* argsToken = functionUsageToken->next(); !Token::simpleMatch(argsToken, ")"); argsToken = argsToken->next()) {
const Variable* argVar = argsToken->variable();
if (argVar && argVar->isGlobal()) {
return false; // TODO: analyze global variable usage
}
}
bool sideEffectReturnFound = false;
for (Token* bodyToken = func.functionScope->bodyStart->next(); bodyToken != func.functionScope->bodyEnd;
bodyToken = bodyToken->next())
{
const Variable* bodyVariable = bodyToken->variable();
if (bodyVariable) {
// check variable for side-effects
if (!isVariableWithoutSideEffects(*bodyVariable)) {
return false;
}
// check if global variable is changed
if (bodyVariable->isGlobal()) {
return false; // TODO: analyze global variable usage
}
}
// check nested function
const Function* bodyFunction = bodyToken->function();
if (bodyFunction) {
if (std::find(checkedFuncs.begin(), checkedFuncs.end(), bodyFunction) != checkedFuncs.end()) { // recursion found
continue;
}
checkedFuncs.push_back(bodyFunction);
if (!isFunctionWithoutSideEffects(*bodyFunction, bodyToken, checkedFuncs)) {
return false;
}
}
// check returned value
if (Token::simpleMatch(bodyToken, "return")) {
const Token* returnValueToken = bodyToken->next();
// TODO: handle complex return expressions
if (!Token::simpleMatch(returnValueToken->next(), ";")) {
sideEffectReturnFound = true;
continue;
}
// simple one-token return
const Variable* returnVariable = returnValueToken->variable();
if (returnValueToken->isLiteral() ||
(returnVariable && isVariableWithoutSideEffects(*returnVariable))) {
continue;
}
sideEffectReturnFound = true;
}
// unknown name
if (bodyToken->isNameOnly()) {
return false;
}
}
return !sideEffectReturnFound;
}

View File

@ -35,6 +35,7 @@ class Tokenizer;
class Type;
class Variables;
class Variable;
class Function;
/// @addtogroup Checks
/// @{
@ -73,6 +74,8 @@ private:
bool isRecordTypeWithoutSideEffects(const Type* type);
bool isVariableWithoutSideEffects(const Variable& var);
bool isEmptyType(const Type* type);
bool isFunctionWithoutSideEffects(const Function& func, const Token* functionUsageToken,
std::list<const Function*> checkedFuncs);
// Error messages..
void unusedStructMemberError(const Token *tok, const std::string &structname, const std::string &varname, bool isUnion = false);

View File

@ -433,6 +433,9 @@ public:
bool isBoolean() const {
return mTokType == eBoolean;
}
bool isIncDecOp() const {
return mTokType == eIncDecOp;
}
bool isBinaryOp() const {
return astOperand1() != nullptr && astOperand2() != nullptr;
}

View File

@ -39,6 +39,7 @@ private:
LOAD_LIB_2(settings.library, "std.cfg");
TEST_CASE(isRecordTypeWithoutSideEffects);
TEST_CASE(cleanFunction);
TEST_CASE(emptyclass); // #5355 - False positive: Variable is not assigned a value.
TEST_CASE(emptystruct); // #5355 - False positive: Variable is not assigned a value.
@ -469,17 +470,6 @@ private:
"}");
ASSERT_EQUALS("", errout.str());
functionVariableUsage(
"class F {\n"
"public:\n"
" F() : x(unknownFunc()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" F f;\n"
"}");
ASSERT_EQUALS("", errout.str());
functionVariableUsage(
"class F {\n"
"public:\n"
@ -525,6 +515,509 @@ private:
ASSERT_EQUALS("", errout.str());
}
void cleanFunction()
{
// unknown function
functionVariableUsage(
"class F {\n"
"public:\n"
" F() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" F f;\n"
"}");
ASSERT_EQUALS("", errout.str());
// function forward declaration
functionVariableUsage(
"int func();\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// return literal
functionVariableUsage(
"int func() { return 1; }\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:8]: (style) Unused variable: c\n", errout.str());
// return variable without side effects
functionVariableUsage(
"int func() {\n"
" int x = 1;\n"
" return x;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:11]: (style) Unused variable: c\n", errout.str());
// return variable with side effects
functionVariableUsage(
"int func() {\n"
" unknown_type x = 1;\n"
" return x;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// return unknown variable
functionVariableUsage(
"int func() {\n"
" return unknown_var;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// return variable is global, but not changed
functionVariableUsage(
"int x = 1;\n"
"int func() {\n"
" return x;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
TODO_ASSERT_EQUALS("[test.cpp:11]: (style) Unused variable: c\n", "", errout.str());
// changing global variable in return
functionVariableUsage(
"int x = 1;\n"
"int func() {\n"
" return x++;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// changing global variable in function body
functionVariableUsage(
"int x = 1;\n"
"int func() {\n"
" x++;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
functionVariableUsage(
"int x = 1;\n"
"int func() {\n"
" --x;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
functionVariableUsage(
"int x = 1;\n"
"int func() {\n"
" x += 2;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
functionVariableUsage(
"int x = 1;\n"
"int func() {\n"
" x = 2;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// changing global array variable in function body
functionVariableUsage(
"int x[] = {0, 1, 3};\n"
"int func() {\n"
" x[0] = 4;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// changing local variable
functionVariableUsage(
"int func() {\n"
" int x = 1;\n"
" x = 2;\n"
" x++;\n"
" return x;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:13]: (style) Unused variable: c\n", errout.str());
// variable of user-defined class without side effects
functionVariableUsage(
"class A {};\n"
"A func() {\n"
" A a;\n"
" return a;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" A x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:12]: (style) Unused variable: c\n", errout.str());
// variable of user-defined class with side effects
functionVariableUsage(
"class A {\n"
"public:\n"
" unknown_type u{1};\n"
"};\n"
"int func() {\n"
" A a;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// unknown type variable
functionVariableUsage(
"int func() {\n"
" unknown_type a;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// nested clean function call
functionVariableUsage(
"int another_func() { return 1;}\n"
"int func() {\n"
" another_func();\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:12]: (style) Unused variable: c\n", errout.str());
// nested side-effects function call
functionVariableUsage(
"int global = 1;"
"int another_func() {\n"
" global++;\n"
" return global;}\n"
"int func() {\n"
" another_func();\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// unknown nested function
functionVariableUsage(
"int func() {\n"
" unknown_func();\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// clean function recursion
functionVariableUsage(
"int func(int i) {\n"
" if (i != 2) {\n"
" func(i++);\n"
" return 2;\n"
" }\n"
" return i;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:14]: (style) Unused variable: c\n", errout.str());
// indirect clean function recursion
functionVariableUsage(
"void another_func() {\n"
" func(0);\n"
"}\n"
"int func(int i) {\n"
" if (i != 2) {\n"
" another_func();\n"
" return 2;\n"
" }\n"
" return i;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:17]: (style) Unused variable: c\n", errout.str());
// side-effect function recursion
functionVariableUsage(
"int global = 1;\n"
"int func(int i) {\n"
" if (i != 2) {\n"
" global++;\n"
" func(i++);\n"
" return 2;\n"
" }\n"
" return i;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// multiple returns (side-effect & clean)
functionVariableUsage(
"int func(int i) {\n"
" if (i == 0) { return 0;}\n"
" else { return unknownSideEffectFunction(); }\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// multiple clean returns
functionVariableUsage(
"int func(int i) {\n"
" if (i == 0) { return 0;}\n"
" else { return i; }\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:11]: (style) Unused variable: c\n", errout.str());
// multiple side-effect returns
functionVariableUsage(
"int func(int i) {\n"
" if (i == 0) { return unknownSideEffectFunction();}\n"
" else { return unknown_var; }\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// argument return
functionVariableUsage(
"int func(int i) {\n"
" return i;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(0)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("[test.cpp:10]: (style) Unused variable: c\n", errout.str());
// global variable modifying through function argument
functionVariableUsage(
"char buf[10];\n"
"int func(char* p) {\n"
" *p = 0;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func(buf)) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// global variable modifying through local pointer
functionVariableUsage(
"int global = 1;\n"
"int func() {\n"
" int *p = &global;\n"
" *p = 0;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
// global struct variable
functionVariableUsage(
"struct S { int x; } s;\n"
"int func() {\n"
" s.x = 1;;\n"
" return 1;\n"
"}\n"
"class C {\n"
"public:\n"
" C() : x(func()) {}\n"
" int x;\n"
"};\n"
"void f() {\n"
" C c;\n"
"}");
ASSERT_EQUALS("", errout.str());
}
// #5355 - False positive: Variable is not assigned a value.
void emptyclass() {
functionVariableUsage("class Carla {\n"