feat: analyze function side effects (#2901)
This commit is contained in:
parent
1e8eb28390
commit
5d299016f1
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue