⚡ Sangsakala Proxy 🏠 Home
Skip to content

Fix #11168 FP returnDanglingLifetime (local iterator points at global struct) - #8979

Open
chrchr-github wants to merge 3 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_11168
Open

chrchr-github wants to merge 3 commits into
cppcheck-opensource:mainfrom
chrchr-github:chr_11168

Conversation

@chrchr-github

Copy link
Copy Markdown
Collaborator

No description provided.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

This PR causes false negatives when a member is an array. The new condition stops following the lifetime at . whenever the top token is a unary * and the right side of . has a varId. It never checks that the member is a pointer, so it also applies to array members. In that case *s.a is part of the local s. Real dangling returns such as return &*s.a;, return *s.a; (returning int&) and return &*s.b[0]; are now no longer reported; base cppcheck reports all three. The FP from #11168 (&*x.a where a is a pointer) is fixed.

A safer check would test that the member really is a pointer and not an array, for example vt->pointer > 0 together with !variable()->isArray(). Tests for the array case should be added to keep this behaviour from breaking again.

Testing

I built cppcheck and testrunner for both pr/ and base/. ./testrunner TestAutoVariables passes, and so does the full ./testrunner (5374 tests, 0 failures).

Test file scratch/a.cpp:

struct S { int a[10]; int b[3][3]; int* p; };
int* f1() { S s; return &*s.a; }
int* f2() { S s{}; return &*s.b[0]; }
int& f3() { S s{}; return *s.a; }
int* f4() { S s{}; return &s.a[0]; }
int* g1() { S s; return &*s.p; }

Command: cppcheck -q --enable=all a.cpp

line base pr
f1 &*s.a (array) returnDanglingLifetime (correct) nothing (FN)
f2 &*s.b[0] (2D array) returnDanglingLifetime (correct) nothing (FN)
f3 *s.a returned as int& returnReference (correct) nothing (FN)
f4 &s.a[0] returnDanglingLifetime returnDanglingLifetime
g1 &*s.p (pointer) returnDanglingLifetime (FP) nothing (fixed)

Iterator members (return *t.it; / &*t.it) are no longer reported either. That looks correct, because the iterator may point into some other container.


Automated review of cf0a3b9 by Claude. The pull request was built and tested locally.

Comment thread lib/valueflow.cpp Outdated
else if (vartok->str() == ".") {
if (!Token::simpleMatch(vartok->astOperand1(), ".") &&
!(vartok->astOperand2() && vartok->astOperand2()->valueType() && vartok->astOperand2()->valueType()->reference != Reference::None))
!(vartok->astOperand2() && ((vartok->astOperand2()->varId() && tok->isUnaryOp("*")) || (vartok->astOperand2()->valueType() && vartok->astOperand2()->valueType()->reference != Reference::None))))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

varId() && tok->isUnaryOp("*") is true for array members too, and there dereferencing gives an element of the local object. Example: struct S { int a[10]; }; int* f() { S s; return &*s.a; } and int& f() { S s{}; return *s.a; }. Both were reported before (returnDanglingLifetime / returnReference) and are silent with this PR. The same happens for &*s.b[0] with int b[3][3]. Consider checking that the member is a real pointer, e.g. vt && vt->pointer > 0 && !(var && var->isArray()), and add tests for the array cases.

@dmcppcheck

Copy link
Copy Markdown
Contributor

Test results for commit cf0a3b9 (tools/test-my-pr.py, main compared to this PR):

Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8979/
packages: 1000
Differing warnings: 1 (+0 -1)
AI review: 1 improvement (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8979/2026-10-29_12-38-03-cf0a3b9a23b1-review.txt)
Timing: +0.1% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8979/2026-10-29_12-38-03-cf0a3b9a23b1_timing.html)

Posted automatically by the cppcheck PR test runner. +N: warnings only with this PR, -N: warnings only with main. The AI review is written by Claude and can be wrong.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

New false negative: the check stops following the lifetime whenever the member is not an array, even when the member is an object stored inside the local. Examples are std::optional members and user types whose operator* returns a reference to their own data. return *s.o; and return &*s.o; with a local S s and a std::optional<int> o member were reported by base, and the PR reports neither. The same happens with a user wrapper struct W { int v; int& operator*() { return v; } };. Only raw pointers, plus smart pointers and iterators, really point outside the object. A safer condition would be to break only when the member's valueType is a pointer (pointer > 0 and not an array) or a smart pointer or iterator.

The array false negative from the earlier review is fixed, and the new tests pass. I found no other new problems.

Testing

I built cppcheck and testrunner in pr/ and cppcheck in base/. ./testrunner TestAutoVariables passed: 108 tests, 0 failures.

Optional member (scratch/a.cpp, excerpt):

#include <optional>
struct S { std::optional<int> o; int x; int* a[3]; int b[3]; };
int& f1() { S s; return *s.o; }
int* f2() { S s; return &*s.o; }
int* f4() { S s; return &*s.b; }

Command: cppcheck -q --enable=all --library=std a.cpp

  • base: 4:25 returnReference and 5:25 returnDanglingLifetime
  • pr: neither is reported (false negative). The array case f4 is still reported by both.

User wrapper member (scratch/b.cpp):

struct W { int v; int& operator*() { return v; } };
struct S { W w; };
int& f1() { S s; return *s.w; }
int* f2() { S s; return &*s.w; }

Command: cppcheck -q b.cpp

  • base: b.cpp:3:25: error: Reference to local variable returned. [returnReference] and b.cpp:4:25: error: Returning object that points to local variable 's' ... [returnDanglingLifetime]
  • pr: neither is reported (false negative).

A side note, not caused by this PR: int* a[3]; ... return &*s.a[i]; gives returnDanglingLifetime in both base and pr. That is a false positive, because * dereferences the pointer element s.a[i], not the array. isArray() is checked on a even though the * applies to the subscript.


Automated review of b599e41 by Claude. The pull request was built and tested locally.

Comment thread lib/valueflow.cpp
return false;
if (!dot->astOperand2())
return true;
if (dot->astOperand2()->varId() && tok->isUnaryOp("*"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

This breaks for every non-array member with a varId under a unary *, including class-type members whose operator* returns a reference into the member itself. With struct S { std::optional<int> o; };, both int& f() { S s; return *s.o; } and int* g() { S s; return &*s.o; } lose the returnReference and returnDanglingLifetime that base reports. The same happens with a user struct W { int v; int& operator*() { return v; } }; member. Consider breaking only when the member's valueType is a real pointer (pointer > 0 and not an array) or a smart pointer or iterator, and following the lifetime otherwise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants