diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 64a48285fdc..6c98b9c78a7 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -1952,6 +1952,35 @@ const Token* ValueFlow::getEndOfExprScope(const Token* tok, const Scope* default return end; } +static void getLhsLifetimeParentsImpl(const Token* lhs, const Library& library, std::vector& result) +{ + if (!lhs) + return; + + if (Token::simpleMatch(lhs, "[")) { + getLhsLifetimeParentsImpl(lhs->astOperand1(), library, result); + } else if (Token::simpleMatch(lhs, ".") && lhs->originalName() != "->") { + const Token* obj = lhs->astOperand1(); + if (Token::simpleMatch(obj, "[") && obj->exprId() > 0) + result.push_back(obj); + getLhsLifetimeParentsImpl(obj, library, result); + } else { + const Token* tok = getParentLifetime(lhs, library); + if (tok && tok->exprId() > 0) { + const Variable* var = tok->variable(); + if (!var || var->isLocal() || var->isArgument()) + result.push_back(tok); + } + } +} + +static std::vector getLhsLifetimeParents(const Token* lhs, const Library& library) +{ + std::vector result; + getLhsLifetimeParentsImpl(lhs, library, result); + return result; +} + static void valueFlowForwardLifetime(Token * tok, const TokenList &tokenlist, ErrorLogger &errorLogger, const Settings &settings) { // Forward lifetimes to constructed variable @@ -2004,14 +2033,9 @@ static void valueFlowForwardLifetime(Token * tok, const TokenList &tokenlist, Er if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::Address) val.lifetimeKind = ValueFlow::Value::LifetimeKind::SubObject; } - // TODO: handle `[` - if (Token::simpleMatch(parent->astOperand1(), ".")) { - const Token* parentLifetime = - getParentLifetime(parent->astOperand1()->astOperand2(), settings.library); - if (parentLifetime && parentLifetime->exprId() > 0) { - valueFlowForward(nextExpression, endOfVarScope, parentLifetime, std::move(values), tokenlist, errorLogger, settings); - } - } + std::vector parents = getLhsLifetimeParents(parent->astOperand1(), settings.library); + for (const Token *p : parents) + valueFlowForward(nextExpression, endOfVarScope, p, values, tokenlist, errorLogger, settings); } // Constructor } else if (Token::simpleMatch(parent, "{") && !isScopeBracket(parent)) { diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index 9fe3e126660..2867f147d81 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -4452,6 +4452,99 @@ class TestAutoVariables : public TestFixture { "}\n"); ASSERT_EQUALS("[test.cpp:5:14] -> [test.cpp:5:18] -> [test.cpp:6:7]: (error) Using pointer that is a temporary. [danglingTemporaryLifetime]\n", errout_str()); + + check("struct A { const int* data[2]; };\n" + "A g() {\n" + " int x = 0;\n" + " A a;\n" + " a.data[0] = &x;\n" + " return a;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:17] -> [test.cpp:3:9] -> [test.cpp:6:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* data[2]; };\n" + "A g() {\n" + " int x = 0;\n" + " A a[2];\n" + " a[0].data[0] = &x;\n" + " return a[0];\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:20] -> [test.cpp:3:9] -> [test.cpp:6:13]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* data[2]; };\n" + "A* g() {\n" + " int x = 0;\n" + " static A arr[2];\n" + " arr[0].data[0] = &x;\n" + " return arr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:22] -> [test.cpp:3:9] -> [test.cpp:6:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "A g(int i) {\n" + " int x = 0;\n" + " A arr[2];\n" + " arr[i].p = &x;\n" + " return arr[i];\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:16] -> [test.cpp:3:9] -> [test.cpp:6:15]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "A* g(int i) {\n" + " int x = 0;\n" + " static A arr[2];\n" + " arr[i].p = &x;\n" + " return arr;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:16] -> [test.cpp:3:9] -> [test.cpp:6:12]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "struct B { A arr[2]; };\n" + "B g(int i) {\n" + " int x = 0;\n" + " B b;\n" + " b.arr[i].p = &x;\n" + " return b;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:18] -> [test.cpp:4:9] -> [test.cpp:7:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { const int* p; };\n" + "A* g() {\n" + " int x = 0;\n" + " static A a;\n" + " A* ap = &a;\n" + " ap->p = &x;\n" + " (*ap).p = &x;\n" + " return ap;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + + check("struct A { int* a; int* b; };\n" + "static A g;\n" + "int* f() {\n" + " int x = 0;\n" + " g.a = &x;\n" + " return g.b;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:11] -> [test.cpp:4:9] -> [test.cpp:5:6]: (error) Non-local variable 'g.a' will use pointer to local variable 'x'. [danglingLifetime]\n", + errout_str()); + + check("struct A { int* a; int* b; };\n" + "static A g;\n" + "int* f() {\n" + " int x = 0;\n" + " g.a = &x;\n" + " return g.a;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:11] -> [test.cpp:4:9] -> [test.cpp:5:6]: (error) Non-local variable 'g.a' will use pointer to local variable 'x'. [danglingLifetime]\n" + "[test.cpp:5:11] -> [test.cpp:4:9] -> [test.cpp:6:13]: (error) Returning pointer to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); } void danglingLifetimeClassMemberFunctions()