Visitar URL original
Fix #15156: Enhancement: Add support for move-assignment for operatorEqToSelf by olabetskyi · Pull Request #8977 · cppcheck-opensource/cppcheck · GitHub
Skip to content

Fix #15156: Enhancement: Add support for move-assignment for operatorEqToSelf - #8977

Open
olabetskyi wants to merge 1 commit into
cppcheck-opensource:mainfrom
olabetskyi:fix_15156
Open

olabetskyi wants to merge 1 commit into
cppcheck-opensource:mainfrom
olabetskyi:fix_15156

Conversation

@olabetskyi

Copy link
Copy Markdown
Collaborator

No description provided.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 != this guard
    • if (this == &o) return *this;
    • if (this == &o) {} else {...}
    • std::swap(p, o.p)
  • Reported, correctly:
    • delete p; p = std::exchange(o.p, nullptr);, which leaves a dangling p on self-move
    • const F&& with new
    • 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.

Comment thread lib/checkclass.cpp
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() == "*")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread test/testclass.cpp
checkOpertorEqToSelf(
"class Foo\n"
"{\n"
" int32_t * p;\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

@dmcppcheck

Copy link
Copy Markdown

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/
packages: 100
Differing warnings: 0 (+0 -0)
Timing: +0.2% (http://ec2-16-170-140-253.eu-north-1.compute.amazonaws.com/pr-8977/2026-10-29_09-52-21-431ea32fb683_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.

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