From 9adab8ae03ea7cc523650024574741dada9203a1 Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Fri, 31 Oct 2025 21:22:37 +0100 Subject: [PATCH 01/10] Skip --- lib/checkautovariables.cpp | 2 ++ test/testautovariables.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index 8b9023d9473..88d91fab09c 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -612,6 +612,8 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()) && !astIsContainerView(tok->astParent())) continue; + if (tokvalue->str() == "=" && Token::simpleMatch(tokvalue->astOperand1(), ".")) + tokvalue = tokvalue->astOperand2(); if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) && isInScope(tokvalue->variable()->nameToken(), scope)) || isDeadTemporary(tokvalue, nullptr, mSettings->library)) { diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index a56e65ba5cd..e727548201c 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -3937,6 +3937,16 @@ class TestAutoVariables : public TestFixture { ASSERT_EQUALS( "[test.cpp:6:30] -> [test.cpp:6:30] -> [test.cpp:6:21] -> [test.cpp:5:21] -> [test.cpp:8:12]: (error) Returning object that points to local variable 'a' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); + + check("struct A { int& x; };\n" // #14247 + "A f() {\n" + " int x = 0;\n" + " A a{.x = x};\n" + " return a;\n" + "}\n"); + ASSERT_EQUALS( + "[test.cpp:4:12] -> [test.cpp:5:12]: (error) Returning object that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); } void danglingLifetimeInitList() { From bb849261cd7e97c2535227a3a1a79f44b74f483e Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Sat, 1 Nov 2025 19:48:00 +0100 Subject: [PATCH 02/10] Check for designated initializer --- lib/checkautovariables.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index 88d91fab09c..18a1f46e6e6 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -612,8 +612,8 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()) && !astIsContainerView(tok->astParent())) continue; - if (tokvalue->str() == "=" && Token::simpleMatch(tokvalue->astOperand1(), ".")) - tokvalue = tokvalue->astOperand2(); + if (tokvalue->str() == "=" && Token::simpleMatch(tokvalue->astOperand1(), ".") && !tokvalue->astOperand1()->astOperand2()) + tokvalue = tokvalue->astOperand2(); // designated initializer if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) && isInScope(tokvalue->variable()->nameToken(), scope)) || isDeadTemporary(tokvalue, nullptr, mSettings->library)) { From 7a1a3786068192f1de44b56692ef470ffcb38f26 Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Sun, 2 Nov 2025 13:59:59 +0100 Subject: [PATCH 03/10] Add test --- test/testautovariables.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index e727548201c..9687302d382 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -3947,6 +3947,14 @@ class TestAutoVariables : public TestFixture { ASSERT_EQUALS( "[test.cpp:4:12] -> [test.cpp:5:12]: (error) Returning object that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); + + check("struct A { int x; int& r};\n" + "A f(int& r) {\n" + " int x = 0;\n" + " A a{.x = x, .r = r};\n" + " return a;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void danglingLifetimeInitList() { From fd1879757453b8ee16ee4bf93499e1768712518b Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Sun, 2 Nov 2025 14:13:52 +0100 Subject: [PATCH 04/10] Add helper --- lib/astutils.cpp | 5 +++++ lib/astutils.h | 5 +++++ lib/checkautovariables.cpp | 4 ++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index c648e79a5b2..7ca319ef8ba 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1022,6 +1022,11 @@ const Token* isInLoopCondition(const Token* tok) return Token::Match(top->previous(), "for|while (") ? top : nullptr; } +bool isDesignatedInitializer(const Token* tok) +{ + return tok && tok->isUnaryOp("."); +} + /// If tok2 comes after tok1 bool precedes(const Token * tok1, const Token * tok2) { diff --git a/lib/astutils.h b/lib/astutils.h index bcb1f696e65..06f01acab11 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -265,6 +265,11 @@ bool isStructuredBindingVariable(const Variable* var); const Token* isInLoopCondition(const Token* tok); +/** + * Is token the dot of a designated initializer? + */ +bool isDesignatedInitializer(const Token* tok); + /** * Is token used as boolean, that is to say cast to a bool, or used as a condition in a if/while/for */ diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index 18a1f46e6e6..535bc30f6e1 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -612,8 +612,8 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()) && !astIsContainerView(tok->astParent())) continue; - if (tokvalue->str() == "=" && Token::simpleMatch(tokvalue->astOperand1(), ".") && !tokvalue->astOperand1()->astOperand2()) - tokvalue = tokvalue->astOperand2(); // designated initializer + if (tokvalue->str() == "=" && isDesignatedInitializer(tokvalue->astOperand1())) + tokvalue = tokvalue->astOperand2(); if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) && isInScope(tokvalue->variable()->nameToken(), scope)) || isDeadTemporary(tokvalue, nullptr, mSettings->library)) { From 152ccdb5314b3b6095e661533042448ecd2b1b1c Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Tue, 4 Nov 2025 18:17:05 +0100 Subject: [PATCH 05/10] Fix --- lib/checkautovariables.cpp | 2 -- lib/valueflow.cpp | 41 +++++++++++++++++++++++--------------- test/testautovariables.cpp | 2 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/checkautovariables.cpp b/lib/checkautovariables.cpp index 535bc30f6e1..8b9023d9473 100644 --- a/lib/checkautovariables.cpp +++ b/lib/checkautovariables.cpp @@ -612,8 +612,6 @@ void CheckAutoVariables::checkVarLifetimeScope(const Token * start, const Token if (tokvalue->exprId() == tok->exprId() && !(tok->variable() && tok->variable()->isArray()) && !astIsContainerView(tok->astParent())) continue; - if (tokvalue->str() == "=" && isDesignatedInitializer(tokvalue->astOperand1())) - tokvalue = tokvalue->astOperand2(); if ((tokvalue->variable() && !isEscapedReference(tokvalue->variable()) && isInScope(tokvalue->variable()->nameToken(), scope)) || isDeadTemporary(tokvalue, nullptr, mSettings->library)) { diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 632b2329add..edfa25136c4 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -2861,6 +2861,7 @@ static void valueFlowLifetimeClassConstructor(Token* tok, std::vector args = getArguments(tok); if (scope->numConstructors == 0) { auto it = scope->varlist.cbegin(); + const bool hasDesignatedInitializers = !args.empty() && isDesignatedInitializer(args[0]->astOperand1()); LifetimeStore::forEach( tokenlist, errorLogger, @@ -2868,23 +2869,31 @@ static void valueFlowLifetimeClassConstructor(Token* tok, args, "Passed to constructor of '" + t->name() + "'.", ValueFlow::Value::LifetimeKind::SubObject, - [&](LifetimeStore& ls) { - // Skip static variable - it = std::find_if(it, scope->varlist.cend(), [](const Variable& var) { - return !var.isStatic(); + [&](LifetimeStore &ls) + { + // Skip static variable + it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) + { return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); }); + if (it == scope->varlist.cend()) + return; + if (hasDesignatedInitializers) + ls.argtok = ls.argtok->astOperand2(); + const Variable &var = *it; + if (var.valueType() && var.valueType()->container && var.valueType()->container->stdStringLike && !var.valueType()->container->view) + return; // TODO: check in isLifetimeBorrowed()? + if (var.isReference() || var.isRValueReference()) + { + ls.byRef(tok, tokenlist, errorLogger, settings); + } + else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) + { + ls.byVal(tok, tokenlist, errorLogger, settings); + } + if (hasDesignatedInitializers) + it = scope->varlist.cbegin(); + else + it++; }); - if (it == scope->varlist.cend()) - return; - const Variable& var = *it; - if (var.valueType() && var.valueType()->container && var.valueType()->container->stdStringLike && !var.valueType()->container->view) - return; // TODO: check in isLifetimeBorrowed()? - if (var.isReference() || var.isRValueReference()) { - ls.byRef(tok, tokenlist, errorLogger, settings); - } else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) { - ls.byVal(tok, tokenlist, errorLogger, settings); - } - it++; - }); } else { const Function* constructor = findConstructor(scope, tok, args); valueFlowLifetimeUserConstructor(tok, constructor, t->name(), args, tokenlist, errorLogger, settings); diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index 9687302d382..322fb6d15d9 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -3945,7 +3945,7 @@ class TestAutoVariables : public TestFixture { " return a;\n" "}\n"); ASSERT_EQUALS( - "[test.cpp:4:12] -> [test.cpp:5:12]: (error) Returning object that will be invalid when returning. [returnDanglingLifetime]\n", + "[test.cpp:4:14] -> [test.cpp:3:9] -> [test.cpp:5:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", errout_str()); check("struct A { int x; int& r};\n" From 59e1c28f119a41d6ef2f24b57b6e01c430aa2424 Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Tue, 4 Nov 2025 18:51:49 +0100 Subject: [PATCH 06/10] Format --- lib/valueflow.cpp | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index edfa25136c4..b5601b34978 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -2870,30 +2870,30 @@ static void valueFlowLifetimeClassConstructor(Token* tok, "Passed to constructor of '" + t->name() + "'.", ValueFlow::Value::LifetimeKind::SubObject, [&](LifetimeStore &ls) + { + // Skip static variable + it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) + { return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); }); + if (it == scope->varlist.cend()) + return; + if (hasDesignatedInitializers) + ls.argtok = ls.argtok->astOperand2(); + const Variable &var = *it; + if (var.valueType() && var.valueType()->container && var.valueType()->container->stdStringLike && !var.valueType()->container->view) + return; // TODO: check in isLifetimeBorrowed()? + if (var.isReference() || var.isRValueReference()) { - // Skip static variable - it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) - { return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); }); - if (it == scope->varlist.cend()) - return; - if (hasDesignatedInitializers) - ls.argtok = ls.argtok->astOperand2(); - const Variable &var = *it; - if (var.valueType() && var.valueType()->container && var.valueType()->container->stdStringLike && !var.valueType()->container->view) - return; // TODO: check in isLifetimeBorrowed()? - if (var.isReference() || var.isRValueReference()) - { - ls.byRef(tok, tokenlist, errorLogger, settings); - } - else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) - { - ls.byVal(tok, tokenlist, errorLogger, settings); - } - if (hasDesignatedInitializers) - it = scope->varlist.cbegin(); - else - it++; - }); + ls.byRef(tok, tokenlist, errorLogger, settings); + } + else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) + { + ls.byVal(tok, tokenlist, errorLogger, settings); + } + if (hasDesignatedInitializers) + it = scope->varlist.cbegin(); + else + it++; + }); } else { const Function* constructor = findConstructor(scope, tok, args); valueFlowLifetimeUserConstructor(tok, constructor, t->name(), args, tokenlist, errorLogger, settings); From 6e5226f016a606b3de93228f47b1f34992d3a01e Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Tue, 4 Nov 2025 18:54:23 +0100 Subject: [PATCH 07/10] Format --- lib/valueflow.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index b5601b34978..323267f971d 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -2873,7 +2873,9 @@ static void valueFlowLifetimeClassConstructor(Token* tok, { // Skip static variable it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) - { return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); }); + { + return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); + }); if (it == scope->varlist.cend()) return; if (hasDesignatedInitializers) From 70537aa7ced2853f703d5a2a382434821e33859c Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Sat, 8 Nov 2025 18:32:53 +0100 Subject: [PATCH 08/10] Format --- lib/valueflow.cpp | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 323267f971d..196ebce067b 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -2869,11 +2869,9 @@ static void valueFlowLifetimeClassConstructor(Token* tok, args, "Passed to constructor of '" + t->name() + "'.", ValueFlow::Value::LifetimeKind::SubObject, - [&](LifetimeStore &ls) - { + [&](LifetimeStore &ls) { // Skip static variable - it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) - { + it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) { return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); }); if (it == scope->varlist.cend()) @@ -2883,12 +2881,10 @@ static void valueFlowLifetimeClassConstructor(Token* tok, const Variable &var = *it; if (var.valueType() && var.valueType()->container && var.valueType()->container->stdStringLike && !var.valueType()->container->view) return; // TODO: check in isLifetimeBorrowed()? - if (var.isReference() || var.isRValueReference()) - { + if (var.isReference() || var.isRValueReference()) { ls.byRef(tok, tokenlist, errorLogger, settings); } - else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) - { + else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) { ls.byVal(tok, tokenlist, errorLogger, settings); } if (hasDesignatedInitializers) From e8355eb800312a289f9cad2835ba5a20e9d87dc1 Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Sat, 8 Nov 2025 18:33:26 +0100 Subject: [PATCH 09/10] Add test --- test/testautovariables.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/testautovariables.cpp b/test/testautovariables.cpp index 322fb6d15d9..c12261b1b1a 100644 --- a/test/testautovariables.cpp +++ b/test/testautovariables.cpp @@ -3955,6 +3955,24 @@ class TestAutoVariables : public TestFixture { " return a;\n" "}\n"); ASSERT_EQUALS("", errout_str()); + + check("struct A { int& x; };\n" + "A f() {\n" + " int x = 0;\n" + " A a{ .x{x} };\n" + " return a;\n" + "}\n"); + ASSERT_EQUALS( + "[test.cpp:4:13] -> [test.cpp:3:9] -> [test.cpp:5:12]: (error) Returning object that points to local variable 'x' that will be invalid when returning. [returnDanglingLifetime]\n", + errout_str()); + + check("struct A { int x; int& r};\n" + "A f(int& r) {\n" + " int x = 0;\n" + " A a{ .x{x}, .r{r} };\n" + " return a;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); } void danglingLifetimeInitList() { From 35fd6772ef0f81bc79b9936183ebdd82d23723c1 Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Sat, 8 Nov 2025 18:40:59 +0100 Subject: [PATCH 10/10] Format --- lib/valueflow.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index 196ebce067b..7598d7349b0 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -2869,7 +2869,7 @@ static void valueFlowLifetimeClassConstructor(Token* tok, args, "Passed to constructor of '" + t->name() + "'.", ValueFlow::Value::LifetimeKind::SubObject, - [&](LifetimeStore &ls) { + [&](LifetimeStore& ls) { // Skip static variable it = std::find_if(it, scope->varlist.cend(), [&](const Variable &var) { return !var.isStatic() && (!hasDesignatedInitializers || var.name() == ls.argtok->astOperand1()->astOperand1()->str()); @@ -2883,8 +2883,7 @@ static void valueFlowLifetimeClassConstructor(Token* tok, return; // TODO: check in isLifetimeBorrowed()? if (var.isReference() || var.isRValueReference()) { ls.byRef(tok, tokenlist, errorLogger, settings); - } - else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) { + } else if (ValueFlow::isLifetimeBorrowed(ls.argtok, settings)) { ls.byVal(tok, tokenlist, errorLogger, settings); } if (hasDesignatedInitializers)