Repository navigation
Fix #15156: Enhancement: Add support for move-assignment for operatorEqToSelf - #8977
olabetskyi wants to merge 1 commit into
Conversation
dmcppcheck
left a comment
There was a problem hiding this comment.
AI review
The main problem: the check now flags a move assignment whenever a deleted member is assigned later, without regard to where the new value comes from. A common move idiom is to take the source pointer into a temporary and null it before the delete, e.g. int* t = std::exchange(o.p, nullptr); delete p; p = t;. That is safe on self-move, and the PR reports it anyway. Copy assignment has the same heuristic and the same false positive with int* t = new int(*o.p); delete p; p = t;. It matters more for move assignment because the temporary pattern is the usual way to write a self-safe move.
The new test is a weak example. On self-move, delete p; p = o.p; o.p = nullptr; leaves p == nullptr. There is no double free and no dangling pointer; the object only loses its value. The test has no negative cases for move assignment either: no this != &o guard, no swap, no take-then-delete.
As before, if (this != std::addressof(o)) is not recognised as a self-check. This already applied to copy assignment, but the guard is common in move assignments, so they will now be flagged too.
Otherwise the one-line change does what it is meant to do. I found no crashes or other regressions.
Testing
I built cppcheck and testrunner in pr/. ./testrunner TestClass ran 202 tests with 0 failures, including the new operatorEqToSelf10.
I ran cppcheck -q --enable=warning on several move-assignment variants:
- Not reported, correctly:
- a
this != &o/&o != thisguard if (this == &o) return *this;if (this == &o) {} else {...}std::swap(p, o.p)
- a
- Reported, correctly:
delete p; p = std::exchange(o.p, nullptr);, which leaves a danglingpon self-moveconst F&&withnew- a class template
K<T>
- Reported, but the code is safe on self-move (false positive):
#include <utility>
class E3 { int*p; public:
E3& operator=(E3&& o) noexcept { int* t = std::exchange(o.p, nullptr); delete p; p = t; return *this; } };
class E2 { int*p; public:
E2& operator=(E2&& o) noexcept { int* t = o.p; o.p = nullptr; delete p; p = t; return *this; } };Command: ../pr/cppcheck -q --enable=warning c.cpp
Output:
c.cpp:3:7: warning: 'operator=' should check for assignment to self to avoid problems with dynamic memory. [operatorEqToSelf]
E2 gets the same warning (b.cpp:4:7).
- Not recognised as a self-check:
if (this != std::addressof(o)) {...}is still reported. Copy assignment already behaved this way.
I did not build base/. The rvalue-reference parameter was skipped entirely there, because the type walk-back stopped at &&, so base cannot report these move-assignment cases.
Automated review of 431ea32 by Claude. The pull request was built and tested locally.
| continue; | ||
| const Token* typeTok = func.argumentList.front().typeEndToken(); | ||
| while (typeTok->str() == "const" || typeTok->str() == "&" || typeTok->str() == "*") | ||
| while (typeTok->str() == "const" || typeTok->str() == "&" || typeTok->str() == "&&" || typeTok->str() == "*") |
There was a problem hiding this comment.
AI review
With && accepted here, the hasAllocation heuristic (delete member, then any later member =) now runs on move assignments. It gives false positives for the usual self-safe move idiom, where the source is taken into a temporary before the delete:
E3& operator=(E3&& o) noexcept { int* t = std::exchange(o.p, nullptr); delete p; p = t; return *this; }This is reported as operatorEqToSelf. The idiom is safe on self-move: t takes p, p becomes null, delete nullptr does nothing, and p = t restores it. if (this != std::addressof(o)), which is common in move assignments, is also not seen as a self-check, so those functions are now flagged too.
| checkOpertorEqToSelf( | ||
| "class Foo\n" | ||
| "{\n" | ||
| " int32_t * p;\n" |
There was a problem hiding this comment.
AI review
This example is not really unsafe on self-move. delete p; p = o.p; o.p = nullptr; ends with p == nullptr, with no double free and no dangling pointer. Using p = std::exchange(o.p, nullptr) after the delete, which does leave p dangling, would make a stronger positive test. Negative tests for move assignment are missing: a this != &o guard, std::swap, and the take-into-a-temporary-then-delete pattern.
|
Test results for commit 431ea32 (tools/test-my-pr.py, main compared to this PR): Test: http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8977/ 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. |
No description provided.