diff --git a/lib/astutils.cpp b/lib/astutils.cpp index a19e8cf0c..56588ef66 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -178,7 +178,7 @@ static bool isInLoopCondition(const Token * tok) } /// If tok2 comes after tok1 -static bool precedes(const Token * tok1, const Token * tok2) +bool precedes(const Token * tok1, const Token * tok2) { if (!tok1) return false; diff --git a/lib/astutils.h b/lib/astutils.h index 9bc2b9c01..83f7e674c 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -59,6 +59,8 @@ const Token * astIsVariableComparison(const Token *tok, const std::string &comp, const Token * nextAfterAstRightmostLeaf(const Token * tok); +bool precedes(const Token * tok1, const Token * tok2); + bool isSameExpression(bool cpp, bool macro, const Token *tok1, const Token *tok2, const Library& library, bool pure, bool followVar, ErrorPath* errors=nullptr); bool isEqualKnownValue(const Token * const tok1, const Token * const tok2); diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index d485049a7..7755bf653 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -618,6 +618,20 @@ static bool isInScope(const Token * tok, const Scope * scope) return false; } +static bool isDeadScope(const Token * tok, const Scope * scope) +{ + if (!tok) + return false; + if (!scope) + return false; + const Variable * var = tok->variable(); + if (var && (!var->isLocal() || var->isStatic() || var->isExtern())) + return false; + if (tok->scope() && tok->scope()->bodyEnd != scope->bodyEnd && precedes(tok->scope()->bodyEnd, scope->bodyEnd)) + return true; + return false; +} + void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token * end) { if (!start) @@ -645,6 +659,9 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token errorReturnDanglingLifetime(tok, &val); break; } + } else if (isDeadScope(val.tokvalue, tok->scope())) { + errorInvalidLifetime(tok, &val); + break; } } const Token *lambdaEndToken = findLambdaEndToken(tok); @@ -707,6 +724,44 @@ void CheckAutoVariables::errorReturnDanglingLifetime(const Token *tok, const Val reportError(errorPath, Severity::error, "returnDanglingLifetime", msg + " that will be invalid when returning.", CWE562, false); } +void CheckAutoVariables::errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val) +{ + const Token *vartok = val->tokvalue; + ErrorPath errorPath = val->errorPath; + std::string msg = ""; + switch (val->lifetimeKind) { + case ValueFlow::Value::Object: + msg = "Using object"; + break; + case ValueFlow::Value::Lambda: + msg = "Using lambda"; + break; + case ValueFlow::Value::Iterator: + msg = "Using iterator"; + break; + } + if (vartok) { + errorPath.emplace_back(vartok, "Variable created here."); + const Variable * var = vartok->variable(); + if (var) { + switch (val->lifetimeKind) { + case ValueFlow::Value::Object: + msg += " that points to local variable"; + break; + case ValueFlow::Value::Lambda: + msg += " that captures local variable"; + break; + case ValueFlow::Value::Iterator: + msg += " to local container"; + break; + } + msg += " '" + var->name() + "'"; + } + } + errorPath.emplace_back(tok, ""); + reportError(errorPath, Severity::error, "invalidLifetime", msg + " that is out of scope.", CWE562, false); +} + void CheckAutoVariables::errorReturnReference(const Token *tok) { reportError(tok, Severity::error, "returnReference", "Reference to auto variable returned.", CWE562, false); diff --git a/lib/checkautovariables.h b/lib/checkautovariables.h index 5aa3d278c..63df288e8 100644 --- a/lib/checkautovariables.h +++ b/lib/checkautovariables.h @@ -93,6 +93,7 @@ private: void errorReturnPointerToLocalArray(const Token *tok); void errorAutoVariableAssignment(const Token *tok, bool inconclusive); void errorReturnDanglingLifetime(const Token *tok, const ValueFlow::Value* val); + void errorInvalidLifetime(const Token *tok, const ValueFlow::Value* val); void errorReturnReference(const Token *tok); void errorReturnTempReference(const Token *tok); void errorInvalidDeallocation(const Token *tok, const ValueFlow::Value *val); diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 15066f3e1..da23d4fd3 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -3837,6 +3837,9 @@ static void valueFlowSubFunction(TokenList *tokenlist, ErrorLogger *errorLogger, // passing value(s) to function std::list argvalues(getFunctionArgumentValues(argtok)); + // Dont forward lifetime values + argvalues.remove_if(std::mem_fn(&ValueFlow::Value::isLifetimeValue)); + if (argvalues.empty()) continue; diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index 2c4e2a383..1b7d9b2b5 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -124,6 +124,7 @@ private: TEST_CASE(danglingLifetimeLambda); TEST_CASE(danglingLifetimeContainer); TEST_CASE(danglingLifetime); + TEST_CASE(invalidLifetime); } @@ -1383,6 +1384,45 @@ private: ASSERT_EQUALS("", errout.str()); } + void invalidLifetime() { + check("void foo(int a) {\n" + " std::function f;\n" + " if (a > 0) {\n" + " int b = a + 1;\n" + " f = [&]{ return b; };\n" + " }\n" + " f();\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5] -> [test.cpp:4] -> [test.cpp:7]: (error) Using lambda that captures local variable 'b' that is out of scope.\n", errout.str()); + + check("void foo(int a) {\n" + " std::function f;\n" + " if (a > 0) {\n" + " int b = a + 1;\n" + " f = [&]{ return b; };\n" + " f();\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + + check("struct a {\n" + " b();\n" + " std::list c;\n" + "};\n" + "void a::b() {\n" + " c.end()\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + + check("void b(char f[], char c[]) {\n" + " std::string d(c); {\n" + " std::string e;\n" + " b(f, e.c_str())\n" + " }\n" + "}\n"); + ASSERT_EQUALS("", errout.str()); + } + }; REGISTER_TEST(TestAutoVariables)