diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 4ebbc8644da..462dbf95825 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -3512,6 +3512,88 @@ bool isLeafDot(const Token* tok) return isLeafDot(parent); } +static const Variable* singlePlainDataMember(const Scope* scope) +{ + if (!scope || (scope->type != ScopeType::eStruct && scope->type != ScopeType::eClass) || + !scope->definedType || !scope->definedType->derivedFrom.empty() || scope->numConstructors != 0) + return nullptr; + if (std::any_of(scope->functionList.cbegin(), scope->functionList.cend(), [](const Function& function) { + return function.hasVirtualSpecifier() || function.hasOverrideSpecifier(); + })) + return nullptr; + if (std::any_of(scope->nestedList.cbegin(), scope->nestedList.cend(), [](const Scope* nested) { + return nested->type == ScopeType::eUnion; + })) + return nullptr; + const Variable* member = nullptr; + for (const Variable& var : scope->varlist) { + if (var.isStatic()) + continue; + if (member || !var.isPublic() || var.isArray() || var.isPointer() || var.isReference() || + var.isRValueReference() || var.isVolatile() || var.hasDefault()) + return nullptr; + member = &var; + } + return member; +} + +const Variable* getSingleMemberArrowWriteTarget(const Token* tok) +{ + if (!Token::Match(tok, ". %name%") || tok->originalName() != "->" || !tok->astOperand1()) + return nullptr; + // Exclude bindings, conditional/unevaluated operands and nested writes + // whose evaluation order is not established by this projection. + const Token* operation = tok->astParent(); + if (!operation || operation->astParent() || operation->astOperand1() != tok || + (!operation->isAssignmentOp() && !Token::Match(operation, "++|--"))) + return nullptr; + const Variable* receiver = tok->astOperand1()->variable(); + if (!receiver || !receiver->isLocal() || receiver->isPointer() || receiver->isArray() || receiver->isReference() || + receiver->isRValueReference() || receiver->isVolatile()) + return nullptr; + // A deferred lambda body does not initialize a captured outer object. + for (const Scope* enclosing = tok->scope(); enclosing && enclosing != receiver->scope(); enclosing = enclosing->nestedIn) { + if (enclosing->type == ScopeType::eLambda || enclosing->type == ScopeType::eFunction) + return nullptr; + } + const Scope* scope = receiver->typeScope(); + const Variable* member = singlePlainDataMember(scope); + if (!member) + return nullptr; + const Variable* leaf = singlePlainDataMember(member->typeScope()); + if (!leaf || !leaf->valueType() || !leaf->valueType()->isPrimitive() || + tok->astOperand2() != tok->next() || tok->strAt(1) != leaf->name() || + (tok->next()->variable() && tok->next()->variable() != leaf)) + return nullptr; + + const auto operators = scope->functionMap.equal_range("operator->"); + if (operators.first == operators.second) + return nullptr; + for (auto it = operators.first; it != operators.second; ++it) { + const Function* function = it->second; + if (!function->functionScope || !Function::returnsPointer(function) || + function->retType != member->type() || function->argCount() != 0 || function->isVolatile()) + return nullptr; + const Token* body = function->functionScope->bodyStart; + if (!Token::simpleMatch(body, "{ return &") || !body->tokAt(2)->isUnaryOp("&")) + return nullptr; + const Token* memberToken = body->tokAt(3); + if (Token::simpleMatch(memberToken, "this .")) + memberToken = memberToken->tokAt(2); + if (!Token::Match(memberToken, "%var% ; }") || memberToken->variable() != member || + memberToken->tokAt(2) != function->functionScope->bodyEnd) + return nullptr; + } + + // A member, free or friend operator& can change the returned address. + // Keep the proof independent of overload resolution for address-of. + if (std::any_of(scope->symdb.scopeList.cbegin(), scope->symdb.scopeList.cend(), [](const Scope& candidate) { + return candidate.functionMap.count("operator&") != 0; + })) + return nullptr; + return member; +} + ExprUsage getExprUsage(const Token* tok, int indirect, const Settings& settings) { const Token* parent = tok->astParent(); diff --git a/lib/astutils.h b/lib/astutils.h index 578763124ac..9ec54e8c094 100644 --- a/lib/astutils.h +++ b/lib/astutils.h @@ -439,6 +439,11 @@ bool isConstVarExpression(const Token* tok, const std::functiontypeScope(); + if (!scope || !var->type()->derivedFrom.empty()) + return false; + const auto operators = scope->functionMap.equal_range("operator->"); + if (operators.first == operators.second) + return false; + + // Do not assume which cv/ref-qualified overload is selected. + for (auto it = operators.first; it != operators.second; ++it) { + const Function* function = it->second; + if (!function->functionScope || !Function::returnsPointer(function)) + return false; + const Token* body = function->functionScope->bodyStart; + if (!Token::simpleMatch(body, "{ return &")) + return false; + const Token* memberToken = body->tokAt(3); + if (Token::simpleMatch(memberToken, "this .")) + memberToken = memberToken->tokAt(2); + if (!Token::Match(memberToken, "%var% ; }") || memberToken->tokAt(2) != function->functionScope->bodyEnd) + return false; + const Variable* member = memberToken->variable(); + if (!member || member->scope() != scope || !member->isMember() || member->isStatic() || + member->isPointer() || member->isReference() || member->isRValueReference()) + return false; + if (member->type() && member->type()->getFunction("operator&")) + return false; + } + return true; +} + static std::string getContainerName(const Token *containerToken) { if (!containerToken) @@ -455,6 +487,20 @@ void CheckStlImpl::iterators() const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase(); + // A free or friend unary operator& can change the meaning of returning &member. + bool hasNonMemberAddressOperator = false; + for (const Scope& scope : symbolDatabase->scopeList) { + const auto operators = scope.functionMap.equal_range("operator&"); + for (auto it = operators.first; it != operators.second; ++it) { + if (it->second->argCount() == 1 && (it->second->isFriend() || !scope.isClassOrStructOrUnion())) { + hasNonMemberAddressOperator = true; + break; + } + } + if (hasNonMemberAddressOperator) + break; + } + // Filling map of iterators id and their scope begin std::map iteratorScopeBeginInfo; for (const Variable* var : symbolDatabase->variableList()) { @@ -615,7 +661,10 @@ void CheckStlImpl::iterators() dereferenceErasedError(eraseToken, tok2, tok2->strAt(1), inconclusiveType); tok2 = tok2->next(); } else if (!validIterator && Token::Match(tok2, "%varid% . %name%", iteratorId)) { - dereferenceErasedError(eraseToken, tok2, tok2->str(), inconclusiveType); + // A known operator-> can expose the iterator object's own storage before assignment. + if (eraseToken || !inconclusiveType || tok2->next()->originalName() != "->" || hasNonMemberAddressOperator || + !iteratorArrowReturnsMemberAddress(var)) + dereferenceErasedError(eraseToken, tok2, tok2->str(), inconclusiveType); tok2 = tok2->tokAt(2); } diff --git a/lib/checkuninitvar.cpp b/lib/checkuninitvar.cpp index 3a5ddcb1ac0..35e66b53c32 100644 --- a/lib/checkuninitvar.cpp +++ b/lib/checkuninitvar.cpp @@ -1441,6 +1441,12 @@ int CheckUninitVarImpl::isFunctionParUsage(const Token *vartok, bool pointer, Al bool CheckUninitVarImpl::isMemberVariableAssignment(const Token *tok, const std::string &membervar) const { + if (const Variable* member = getSingleMemberArrowWriteTarget(tok->astParent())) { + const Token* access = tok->astParent(); + if (access->astOperand1() == tok && member->name() == membervar && + Token::simpleMatch(access->astParent(), "=") && astIsLHS(access)) + return true; + } if (Token::Match(tok, "%name% . %name%") && tok->strAt(2) == membervar) { if (Token::Match(tok->tokAt(3), "[=.[]")) return true; @@ -1671,7 +1677,13 @@ void CheckUninitVarImpl::valueFlowUninit() (tok->astParent()->next()->variable() || tok->astParent()->next()->isEnumerator())) continue; } - const ExprUsage usage = getExprUsage(tok, v->indirect, mSettings); + // For a proven singleton accessor, the scalar operation also + // describes the receiver's initialization state (including ++). + const Token* usageToken = tok; + if (v->indirect == 0 && getSingleMemberArrowWriteTarget(tok->astParent()) && + tok->astParent()->astOperand1() == tok) + usageToken = tok->astParent(); + const ExprUsage usage = getExprUsage(usageToken, v->indirect, mSettings); if (usage == ExprUsage::NotUsed || usage == ExprUsage::Inconclusive) continue; if (!v->subexpressions.empty() && usage == ExprUsage::PassedByReference) diff --git a/lib/vf_analyzers.cpp b/lib/vf_analyzers.cpp index 1a4d17aa849..e9cc59e0c69 100644 --- a/lib/vf_analyzers.cpp +++ b/lib/vf_analyzers.cpp @@ -570,6 +570,14 @@ struct ValueFlowAnalyzer : Analyzer { Action analyzeMatch(const Token* tok, Direction d) const { const Token* parent = tok->astParent(); + const ValueFlow::Value* value = getValue(tok); + if (value && value->isUninitValue() && value->indirect == 0 && + getSingleMemberArrowWriteTarget(parent) && parent->astOperand1() == tok) { + // The accessor only takes an address. For this singleton layout, + // the selected scalar and the receiver have the same init state. + const Token* operation = parent->astParent(); + return operation->str() == "=" ? Action::Invalid : Action::Read | Action::Invalid; + } if (d == Direction::Reverse && isGlobal() && !dependsOnThis() && Token::Match(parent, ". %name% (")) { Action a = isGlobalModified(parent->next()); if (a != Action::None) @@ -1505,6 +1513,8 @@ struct MemberExpressionAnalyzer : SubExpressionAnalyzer { { if (!Token::Match(tok, ". %var%")) return false; + if (const Variable* member = getSingleMemberArrowWriteTarget(tok)) + return !exact || member->name() == varname; if (!exact) return true; return tok->strAt(1) == varname; diff --git a/test/teststl.cpp b/test/teststl.cpp index 197cc3fafa7..640037ceb28 100644 --- a/test/teststl.cpp +++ b/test/teststl.cpp @@ -164,6 +164,8 @@ class TestStl : public TestFixture { TEST_CASE(dereferenceInvalidIterator); TEST_CASE(dereferenceInvalidIterator2); // #6572 + TEST_CASE(dereferenceSelfContainedIterator); + TEST_CASE(dereferenceSelfContainedIteratorOperators); TEST_CASE(dereference_auto); TEST_CASE(loopAlgoElementAssign); @@ -5779,7 +5781,7 @@ class TestStl : public TestFixture { " it->m_place = 0;\n" " return it;\n" "}\n", dinit(CheckOptions, $.inconclusive = true)); - ASSERT_EQUALS("[test.cpp:18:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + ASSERT_EQUALS("", errout_str()); check("int f(const std::vector& v) {\n" // #11895 " auto it = v.end();\n" @@ -5789,6 +5791,165 @@ class TestStl : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void dereferenceSelfContainedIterator() { + check("struct Value { int field; };\n" + "struct iterator {\n" + " Value item;\n" + " Value& operator*() { return item; }\n" + " Value* operator->() { return &this->item; }\n" + " const Value* operator->() const { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "iterator f() {\n" + " iterator it;\n" + " it->field = 0;\n" + " return it;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("", errout_str()); + + // Safe arrow access must not validate a different dereference operator. + check("struct Value { int field; };\n" + "struct iterator {\n" + " Value item; Value* ptr;\n" + " Value& operator*() { return *ptr; }\n" + " Value* operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + " *it;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:11:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + check("struct Value { int field; };\n" + "struct iterator {\n" + " Value* ptr;\n" + " Value& operator*() { return *ptr; }\n" + " Value* operator->() { return ptr; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:10:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + // One safe overload is insufficient when another overload has unknown semantics. + check("struct Value { int field; };\n" + "struct iterator {\n" + " Value item; Value* ptr;\n" + " Value& operator*() { return *ptr; }\n" + " Value* operator->() { return &item; }\n" + " const Value* operator->() const;\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:11:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + // The address-of operator can itself be overloaded. + check("struct Value { int field; Value* operator&(); };\n" + "struct iterator {\n" + " Value item;\n" + " Value& operator*() { return item; }\n" + " Value* operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:10:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + // Keep the existing invalidation heuristic after an erase operation. + check("struct Value { int field; };\n" + "struct iterator {\n" + " Value item;\n" + " Value& operator*() { return item; }\n" + " Value* operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "struct Container { void erase(iterator); };\n" + "void f(Container& c) {\n" + " iterator it{};\n" + " c.erase(it);\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:12:5] -> [test.cpp:11:5]: (error, inconclusive) Iterator 'it' used after element has been erased. [eraseDereference]\n", errout_str()); + } + + void dereferenceSelfContainedIteratorOperators() { + // free address of + check("struct Value { int field; };\n" + "Value* operator&(Value&) { return nullptr; }\n" + "struct iterator {\n" + " Value item;\n" + " Value& operator*() { return item; }\n" + " Value* operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:11:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + // inherited arrow + check("struct Value { int field; };\n" + "struct Base {\n" + " Value* operator->() const { return nullptr; }\n" + "};\n" + "struct iterator : Base {\n" + " using Base::operator->;\n" + " Value item;\n" + " iterator() {}\n" + " Value& operator*() { return item; }\n" + " Value* operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " const iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:15:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + // arrow proxy + check("struct Value { int field; };\n" + "struct Proxy {\n" + " Proxy(Value*) {}\n" + " Value* operator->() { return nullptr; }\n" + "};\n" + "struct iterator {\n" + " Value item;\n" + " Value& operator*() { return item; }\n" + " Proxy operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:14:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + // friend unary address of overload + check("struct Value { int field; friend Value* operator&(Value&) { return nullptr; } };\n" + "struct iterator {\n" + " Value item;\n" + " Value& operator*() { return item; }\n" + " Value* operator->() { return &item; }\n" + " iterator& operator++();\n" + "};\n" + "void f() {\n" + " iterator it;\n" + " it->field = 0;\n" + "}\n", dinit(CheckOptions, $.inconclusive = true)); + ASSERT_EQUALS("[test.cpp:10:5]: (error, inconclusive) Invalid iterator 'it' used. [eraseDereference]\n", errout_str()); + + } + void loopAlgoElementAssign() { check("void foo() {\n" " for(int& x:v)\n" diff --git a/test/testuninitvar.cpp b/test/testuninitvar.cpp index 1874e020fa8..5f1631f10f9 100644 --- a/test/testuninitvar.cpp +++ b/test/testuninitvar.cpp @@ -43,6 +43,13 @@ class TestUninitVar : public TestFixture { TEST_CASE(uninitvar_alloc); // data is allocated but not initialized TEST_CASE(uninitvar_arrays); // arrays TEST_CASE(uninitvar_class); // class/struct + TEST_CASE(uninitvar_ownedArrow); + TEST_CASE(uninitvar_ownedArrowEscapes); + TEST_CASE(valueFlowUninit_ownedArrow); + TEST_CASE(valueFlowUninit_ownedArrowWrites); + TEST_CASE(valueFlowUninit_ownedArrowReadModify); + TEST_CASE(valueFlowUninit_ownedArrowReceivers); + TEST_CASE(valueFlowUninit_ownedArrowConditional); TEST_CASE(uninitvar_enum); // enum variables TEST_CASE(uninitvar_if); // handling if TEST_CASE(uninitvar_loops); // handling for/while @@ -3677,6 +3684,172 @@ class TestUninitVar : public TestFixture { (checkuninitvar.valueFlowUninit)(); } + void uninitvar_ownedArrow() { // #6572 + checkUninitVar("struct CCommitPointer { int m_place; };\n" + "struct iterator {\n" + " CCommitPointer m_ptr;\n" + " CCommitPointer& operator*() { return m_ptr; }\n" + " CCommitPointer* operator->() { return &m_ptr; }\n" + " iterator& operator++() { ++m_ptr.m_place; return *this; }\n" + "};\n" + "iterator begin() {\n" + " iterator it;\n" + " it->m_place = 0;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void uninitvar_ownedArrowEscapes() { + checkUninitVar("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f(bool b) {\n" + " Cursor it;\n" + " b && (it->value = 1);\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:12]: (error) Uninitialized struct member: it.item [uninitStructMember]\n", errout_str()); + + checkUninitVar("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " (void)noexcept(it->value = 1);\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:12]: (error) Uninitialized struct member: it.item [uninitStructMember]\n", errout_str()); + + checkUninitVar("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " int& r = it->value;\n" + " ++r;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:7:12]: (error) Uninitialized struct member: it.item [uninitStructMember]\n", errout_str()); + + + } + + void valueFlowUninit_ownedArrow() { // #6572 + valueFlowUninit("struct CCommitPointer { int m_place; };\n" + "struct iterator {\n" + " CCommitPointer m_ptr;\n" + " CCommitPointer& operator*() { return m_ptr; }\n" + " CCommitPointer* operator->() { return &m_ptr; }\n" + " iterator& operator++() { ++m_ptr.m_place; return *this; }\n" + "};\n" + "iterator begin() {\n" + " iterator it;\n" + " it->m_place = 0;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void valueFlowUninit_ownedArrowWrites() { + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &this->item; }\n" + " const Item* operator->() const { return &item; } };\n" + "int f(bool b) {\n" + " Cursor it;\n" + " if (b) it->value = 1; else it->value = 2;\n" + " it->value += 1;\n" + " return it->value;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void valueFlowUninit_ownedArrowReadModify() { + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " it->value++;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:5]: (error) Uninitialized variable: it [uninitvar]\n", errout_str()); + + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " --it->value;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:7]: (error) Uninitialized variable: it [uninitvar]\n", errout_str()); + + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " it->value += 1;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:5]: (error) Uninitialized variable: it [uninitvar]\n", errout_str()); + } + + void valueFlowUninit_ownedArrowReceivers() { + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " it->value = it->value + 1;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:17]: (error) Uninitialized variable: it [uninitvar]\n", errout_str()); + + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor first, second;\n" + " first->value = second->value;\n" + " return first;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:20]: (error) Uninitialized variable: second [uninitvar]\n", errout_str()); + + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor first, second;\n" + " first->value = 1;\n" + " second->value = first->value;\n" + " return second;\n" + "}\n"); + ASSERT_EQUALS("", errout_str()); + } + + void valueFlowUninit_ownedArrowConditional() { + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f(bool b) {\n" + " Cursor it;\n" + " if (b) it->value = 1;\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:9] -> [test.cpp:6:12]: (warning) Uninitialized variable: it.item [uninitvar]\n", errout_str()); + + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "Cursor f() {\n" + " Cursor it;\n" + " auto l = [&it] { it->value = 1; };\n" + " return it;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:6:12]: (error) Uninitialized variable: it [uninitvar]\n", errout_str()); + + // Reference writes are not projected into the owning object. + valueFlowUninit("struct Item { int value; };\n" + "struct Cursor { Item item; Item* operator->() { return &item; } };\n" + "void f() {\n" + " Cursor it;\n" + " int& r = it->value;\n" + " ++r;\n" + "}\n"); + ASSERT_EQUALS("[test.cpp:5:14]: (error) Uninitialized variable: it [uninitvar]\n", errout_str()); + } + void uninitvar15() { // #13685 const char code[] = "int f() {\n" " int x;\n"