`cplusplus.Move` treats the left-hand side of an explicit-object move assignment as moved-from
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Description
The Clang Static Analyzer's `cplusplus.Move` checker appears to model a C++23 explicit-object move-assignment operator incorrectly.
For an operator declared as:
```cpp
auto operator=(this Owner& self, Owner&& other) noexcept -> Owner&;
```
the analyzer marks the explicit object argument (`self`, the left-hand side of the assignment) as moved-from after an expression such as:
```cpp
target = std::move(source);
```
It should mark `source` as moved-from. `target` has just received a new value and remains valid.
This produces false positives when `target` is subsequently moved. It also makes `scan-build --status-bugs` fail otherwise successful builds.
## Minimal reproducer
[godbolt link](https://godbolt.org/z/q9GG4jbqT)
```cpp
#include
struct Owner {
int* pointer{};
constexpr Owner() noexcept = default;
constexpr Owner(Owner&& other) noexcept
: pointer{std::exchange(other.pointer, nullptr)} {
}
constexpr auto operator=(this Owner& self, Owner&& other) noexcept -> Owner& {
self.pointer = std::exchange(other.pointer, nullptr);
return self;
}
};
struct Holder {
Owner value;
constexpr explicit Holder(Owner&& source) noexcept
: value{std::move(source)} {
}
};
void reproducer() {
Owner target{};
Owner source{};
target = std::move(source);
Holder holder{std::move(target)};
}
```
## Command
```sh
clang++ --analyze \
-Xanalyzer -analyzer-checker=cplusplus.Move \
-Xanalyzer -analyzer-output=text \
-std=c++23 repro.cc
```
The same issue is visible when running a CMake build through `scan-build --status-bugs`.
## Actual result
The analyzer reports that `target` is moved-from after the move-assignment expression and warns when it is subsequently moved into `Holder`:
```text
warning: Moved-from object 'target' is moved [cplusplus.Move]
note: Object 'target' is moved
target = std::move(source);
^~~~~~~~~~~~~~~~~~~~~~~~~~
note: Calling constructor for 'Holder'
Holder holder{std::move(target)};
^~~~~~~~~~~~~~~~~~~~~~~~~
note: Moved-from object 'target' is moved
: value{std::move(source)} {
^~~~~~~~~~~~~~~~~~~~~~~~
```
## Expected result
No diagnostic should be emitted for the later move from `target`.
After `target = std::move(source)`, `source` is the moved-from object. The move-assignment operator has assigned a new valid state to `target`.
## Comparison with an implicit-object move-assignment operator
Replacing only the move-assignment operator with the traditional implicit-object form removes the diagnostic:
```cpp
constexpr auto operator=(Owner&& other) noexcept -> Owner& {
pointer = std::exchange(other.pointer, nullptr);
return *this;
}
```
This suggests that the problem is specifically in the analyzer's modeling of the explicit object parameter rather than in its general modeling of move assignment.
## Versions observed
- Clang 21.1.8, installed from Ubuntu 26.04 packages and invoked through `scan-build`, reports the false positive in the original project.
- Clang 23.0.0git (`4c4c1db7c69a6fda6cfa6bc6066bb09a433edc89`), invoked directly with `clang++ --analyze` on Windows, reproduces it with the minimal example above.
The implicit-object variant produces no warning under the same Clang 23 analyzer invocation.
Contributor guide
Research direction
Start with the minimal reproducer in repro.cc and run the shown clang++ --analyze command with the cplusplus.Move checker. Trace how the checker models explicit-object move assignment, then add regression coverage showing that source is moved-from while target remains valid and that no later diagnostic is emitted.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100