From c0ebd1799244b44664fb493a65a8322bf124ba41 Mon Sep 17 00:00:00 2001 From: Leander Schulten Date: Sat, 3 Oct 2026 20:01:16 +0200 Subject: [PATCH] Fix #15084 Wrong varid for lambda in constructor initializer list The lambda body in an initializer list was taken as the start of the constructor body, so following members got the parameter varid, lambda parameters leaked into the rest of the initializer list and the parameter varid leaked into later functions (FP uninitMemberVar, selfInitialization, functionStatic, constParameterPointer). The lambda is now parsed like a lambda in executable code. findTypeEnd() also finds the end of unlinked template brackets so that findLambdaEndScope() works for trailing return types like std::vector before createLinks2(). Co-Authored-By: Claude Opus 5.5 --- lib/token.cpp | 4 ++- lib/tokenize.cpp | 34 ++++++++++++++++++++++++++ test/testvarid.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/lib/token.cpp b/lib/token.cpp index eac69decee7..a224915b1fe 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -2683,8 +2683,10 @@ bool Token::Impl::getCppcheckAttribute(CppcheckAttributesType attrType, MathLib: Token* findTypeEnd(Token* tok) { while (Token::Match(tok, "%name%|.|::|*|&|&&|<|(|template|decltype|sizeof")) { - if (Token::Match(tok, "(|<")) + if (tok->str() == "(") tok = tok->link(); + else if (tok->str() == "<") // template brackets are not linked before Tokenizer::createLinks2() + tok = tok->link() ? tok->link() : tok->findClosingBracket(); if (!tok) return nullptr; tok = tok->next(); diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 7f92d66eb78..0b4de780642 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -4763,6 +4763,24 @@ static const std::unordered_set notstart_cpp = { NOTSTART_C, "delete", "friend", "new", "throw", "using", "virtual", "explicit", "const_cast", "dynamic_cast", "reinterpret_cast", "static_cast", "template" }; +// Returns the end of the lambda that starts at tok in a constructor initializer list, or nullptr +static const Token* findInitListLambdaEnd(const Token* tok) +{ + if (!Token::simpleMatch(tok, "[") || Token::Match(tok->previous(), "%name%|)|]|>")) + return nullptr; // array subscript or array size of a new expression + // array size of a new expression with pointer or reference type: new T*[n]{...} + for (const Token* prev = tok->previous(); Token::Match(prev, "*|&|&&|::|%name%|>"); prev = prev->previous()) { + if (prev->str() == "new") + return nullptr; + if (prev->str() == ">") { + prev = prev->findOpeningBracket(); + if (!prev) + break; + } + } + return findLambdaEndScope(tok); +} + void Tokenizer::setVarIdPass1() { const bool cpp = isCPP(); @@ -4778,6 +4796,7 @@ void Tokenizer::setVarIdPass1() std::stack functionDeclEndStack; const Token *functionDeclEndToken = nullptr; bool initlist = false; + std::stack initlistLambdaEnds; // ends of lambdas in constructor initializer lists bool inlineFunction = false; for (Token *tok = list.front(); tok; tok = tok->next()) { if (tok->isOp()) @@ -4813,6 +4832,13 @@ void Tokenizer::setVarIdPass1() variableMap.enterScope(); } } + } else if (const Token* lambdaEnd = initlist ? findInitListLambdaEnd(tok) : nullptr) { + // lambda in initializer list: parse it like a lambda in executable code, the + // extra scope holds its parameters and is left at the end of the lambda + initlistLambdaEnds.push(lambdaEnd); + scopeStack.emplace(/*isExecutable=*/ true, /*isStructInit=*/ false, /*isEnum=*/ false, variableMap.getVarId()); + variableMap.enterScope(); + initlist = false; } else if (!initlist && tok->str()=="(") { const Token * newFunctionDeclEnd = nullptr; if (!scopeStack.top().isExecutable) @@ -4903,6 +4929,14 @@ void Tokenizer::setVarIdPass1() } } } + + if (!initlistLambdaEnds.empty() && initlistLambdaEnds.top() == tok) { + // end of lambda in initializer list + initlistLambdaEnds.pop(); + scopeStack.pop(); + variableMap.leaveScope(); + initlist = true; + } } if ((!scopeStack.top().isStructInit && diff --git a/test/testvarid.cpp b/test/testvarid.cpp index 4186917cc35..dbff0ed160a 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -161,6 +161,7 @@ class TestVarID : public TestFixture { TEST_CASE(varid_initList); TEST_CASE(varid_initListWithBaseTemplate); TEST_CASE(varid_initListWithScope); + TEST_CASE(varid_initListWithLambda); TEST_CASE(varid_operator); TEST_CASE(varid_throw); TEST_CASE(varid_unknown_macro); // #2638 - unknown macro is not type @@ -2789,6 +2790,66 @@ class TestVarID : public TestFixture { tokenize(code1)); } + void varid_initListWithLambda() { + const char code1[] = "struct S {\n" + " int x;\n" + " int* p;\n" + " S(int* p) : x([p] { return *p; }()), p(p) {}\n" + " S(int* p, int* q) : x([p](int* q) { return *p + *q; }(q)), p(q) {}\n" + " S(int* p, char) : x([p]() noexcept { int v = *p; return v; }()), p{p} {}\n" + "};\n" + "struct T {\n" + " int* p;\n" + " int g();\n" + "};\n" + "int T::g() { return *p; }\n"; + ASSERT_EQUALS("1: struct S {\n" + "2: int x@1 ;\n" + "3: int * p@2 ;\n" + "4: S ( int * p@3 ) : x@1 ( [ p@3 ] { return * p@3 ; } ( ) ) , p@2 ( p@3 ) { }\n" + "5: S ( int * p@4 , int * q@5 ) : x@1 ( [ p@4 ] ( int * q@6 ) { return * p@4 + * q@6 ; } ( q@5 ) ) , p@2 ( q@5 ) { }\n" + "6: S ( int * p@7 , char ) : x@1 ( [ p@7 ] ( ) noexcept ( true ) { int v@8 ; v@8 = * p@7 ; return v@8 ; } ( ) ) , p@2 { p@7 } { }\n" + "7: } ;\n" + "8: struct T {\n" + "9: int * p@9 ;\n" + "10: int g ( ) ;\n" + "11: } ;\n" + "12: int T :: g ( ) { return * p@9 ; }\n", + tokenize(code1)); + + const char code2[] = "struct S {\n" + " int x;\n" + " int* p;\n" + " S(int* p) : x([p]() -> std::map { return {{*p, 1}}; }().size()), p(p) {}\n" + "};\n"; + ASSERT_EQUALS("1: struct S {\n" + "2: int x@1 ;\n" + "3: int * p@2 ;\n" + "4: S ( int * p@3 ) : x@1 ( [ p@3 ] ( ) . std :: map < int , int > { return { { * p@3 , 1 } } ; } ( ) . size ( ) ) , p@2 ( p@3 ) { }\n" + "5: } ;\n", + tokenize(code2)); + + const char code3[] = "enum { N = 2 };\n" // no lambda + "struct S {\n" + " int* q;\n" + " S(int b) : q(new int[2]{ N * b, 1 }) {}\n" + "};\n" + "struct T {\n" + " std::vector** r;\n" + " T(std::vector* c) : r(new std::vector*[2]{ N * c, c }) {}\n" + "};\n"; + ASSERT_EQUALS("1: enum Anonymous0 { N = 2 } ;\n" + "2: struct S {\n" + "3: int * q@1 ;\n" + "4: S ( int b@2 ) : q@1 ( new int [ 2 ] { N * b@2 , 1 } ) { }\n" + "5: } ;\n" + "6: struct T {\n" + "7: std :: vector < int > * * r@3 ;\n" + "8: T ( std :: vector < int > * c@4 ) : r@3 ( new std :: vector < int > * [ 2 ] { N * c@4 , c@4 } ) { }\n" + "9: } ;\n", + tokenize(code3)); + } + void varid_operator() { { const std::string actual = tokenize(