diff --git a/lib/checkstl.cpp b/lib/checkstl.cpp index 3a7672159..1abd04b40 100644 --- a/lib/checkstl.cpp +++ b/lib/checkstl.cpp @@ -1035,6 +1035,37 @@ static const Token* getLoopContainer(const Token* tok) return sepTok->astOperand2(); } +static const ValueFlow::Value* getInnerLifetime(const Token* tok, + nonneg int id, + ErrorPath* errorPath = nullptr, + int depth = 4) +{ + if (depth < 0) + return nullptr; + if (!tok) + return nullptr; + for (const ValueFlow::Value& val : tok->values()) { + if (!val.isLocalLifetimeValue()) + continue; + if (contains({ValueFlow::Value::LifetimeKind::Address, + ValueFlow::Value::LifetimeKind::SubObject, + ValueFlow::Value::LifetimeKind::Lambda}, + val.lifetimeKind)) { + if (val.capturetok) + return getInnerLifetime(val.capturetok, id, errorPath, depth - 1); + if (errorPath) + errorPath->insert(errorPath->end(), val.errorPath.begin(), val.errorPath.end()); + return getInnerLifetime(val.tokvalue, id, errorPath, depth - 1); + } + if (!val.tokvalue->variable()) + continue; + if (val.tokvalue->varId() != id) + continue; + return &val; + } + return nullptr; +} + void CheckStl::invalidContainer() { const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); @@ -1121,24 +1152,13 @@ void CheckStl::invalidContainer() } } } - for (const ValueFlow::Value& val : info.tok->values()) { - if (!val.isLocalLifetimeValue()) - continue; - if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::Address) - continue; - if (val.lifetimeKind == ValueFlow::Value::LifetimeKind::SubObject) - continue; - if (!val.tokvalue->variable()) - continue; - if (val.tokvalue->varId() != r.tok->varId()) - continue; - ErrorPath ep; - // Check the iterator is created before the change - if (val.tokvalue != tok && reaches(val.tokvalue, tok, library, &ep)) { - v = &val; - errorPath = ep; - return true; - } + ErrorPath ep; + const ValueFlow::Value* val = getInnerLifetime(info.tok, r.tok->varId(), &ep); + // Check the iterator is created before the change + if (val && val->tokvalue != tok && reaches(val->tokvalue, tok, library, &ep)) { + v = val; + errorPath = ep; + return true; } return false; }); diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 80c28a503..6a0b8a090 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -3758,6 +3758,7 @@ struct LifetimeStore { ValueFlow::Value value; value.valueType = ValueFlow::Value::ValueType::LIFETIME; value.tokvalue = lt.token; + value.capturetok = argtok; value.errorPath = er; value.lifetimeKind = type; value.setInconclusive(inconclusive || lt.inconclusive); @@ -3796,6 +3797,7 @@ struct LifetimeStore { value.lifetimeScope = v.lifetimeScope; value.path = v.path; value.tokvalue = lt.token; + value.capturetok = argtok; value.errorPath = std::move(er); value.lifetimeKind = type; value.setInconclusive(lt.inconclusive || v.isInconclusive() || inconclusive); @@ -8272,6 +8274,7 @@ ValueFlow::Value::Value(const Token* c, long long val, Bound b) path(0), wideintvalue(0), subexpressions(), + capturetok(nullptr), lifetimeKind(LifetimeKind::Object), lifetimeScope(LifetimeScope::Local), valueKind(ValueKind::Possible) diff --git a/lib/valueflow.h b/lib/valueflow.h index 0d66e04a6..6886d50c2 100644 --- a/lib/valueflow.h +++ b/lib/valueflow.h @@ -105,6 +105,7 @@ namespace ValueFlow { path(0), wideintvalue(val), subexpressions(), + capturetok(nullptr), lifetimeKind(LifetimeKind::Object), lifetimeScope(LifetimeScope::Local), valueKind(ValueKind::Possible) @@ -364,6 +365,9 @@ namespace ValueFlow { std::vector subexpressions; + // Set to where a lifetime is captured by value + const Token* capturetok; + enum class LifetimeKind { // Pointer points to a member of lifetime Object, diff --git a/test/teststl.cpp b/test/teststl.cpp index 206a6dea3..c08bd2bdc 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -5274,6 +5274,68 @@ private: "}\n", true); ASSERT_EQUALS("", errout.str()); + + // #10984 + check("void f() {\n" + " std::vector v;\n" + " auto g = [&v]{};\n" + " v.push_back(1);\n" + " g();\n" + "}\n", + true); + ASSERT_EQUALS("", errout.str()); + + check("void f(std::vector v) {\n" + " auto it = v.begin();\n" + " auto g = [&]{ std::cout << *it << std::endl;};\n" + " v.push_back(1);\n" + " g();\n" + "}\n", + true); + ASSERT_EQUALS( + "[test.cpp:2] -> [test.cpp:3] -> [test.cpp:4] -> [test.cpp:1] -> [test.cpp:5]: (error) Using iterator to local container 'v' that may be invalid.\n", + errout.str()); + + check("void f(std::vector v) {\n" + " auto it = v.begin();\n" + " auto g = [=]{ std::cout << *it << std::endl;};\n" + " v.push_back(1);\n" + " g();\n" + "}\n", + true); + ASSERT_EQUALS( + "[test.cpp:2] -> [test.cpp:4] -> [test.cpp:1] -> [test.cpp:5]: (error) Using iterator to local container 'v' that may be invalid.\n", + errout.str()); + + check("struct A {\n" + " int* p;\n" + " void g();\n" + "};\n" + "void f(std::vector v) {\n" + " auto it = v.begin();\n" + " A a{v.data()};\n" + " v.push_back(1);\n" + " a.g();\n" + "}\n", + true); + ASSERT_EQUALS( + "[test.cpp:7] -> [test.cpp:8] -> [test.cpp:5] -> [test.cpp:9]: (error) Using object that points to local variable 'v' that may be invalid.\n", + errout.str()); + + check("struct A {\n" + " int*& p;\n" + " void g();\n" + "};\n" + "void f(std::vector v) {\n" + " auto* p = v.data();\n" + " A a{p};\n" + " v.push_back(1);\n" + " a.g();\n" + "}\n", + true); + ASSERT_EQUALS( + "[test.cpp:6] -> [test.cpp:7] -> [test.cpp:8] -> [test.cpp:5] -> [test.cpp:9]: (error) Using object that points to local variable 'v' that may be invalid.\n", + errout.str()); } void invalidContainerLoop() {