Repository navigation
Fix #11168 FP returnDanglingLifetime (local iterator points at global struct) - #8979
chrchr-github wants to merge 3 commits into
Conversation
dmcppcheck
left a comment
There was a problem hiding this comment.
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.
| 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)))) |
There was a problem hiding this comment.
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.
|
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/ 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
left a comment
There was a problem hiding this comment.
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 returnReferenceand5:25 returnDanglingLifetime - pr: neither is reported (false negative). The array case
f4is 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]andb.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.
| return false; | ||
| if (!dot->astOperand2()) | ||
| return true; | ||
| if (dot->astOperand2()->varId() && tok->isUnaryOp("*")) |
There was a problem hiding this comment.
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.
No description provided.