After some effort, I convinced both the clang compiler and clang-tidy (static analyzer) to warn of a use-after-move situation. (see https://mcmap.net/q/1473991/-detect-use-after-move-during-compilation)
int main(int, char**) {
a_class a;
auto b = std::move(a);
a.f(); // warns here, for example "invalid invocation of method 'f' on object 'a' while it is in the 'consumed' state [-Werror,-Wconsumed]"
}
However, if I make the variable global (or static or lazily static), there is no more warning.
a_class a;
int main(int, char**) {
auto b = std::move(a);
a.f(); // no warns here!
}
See here: https://godbolt.org/z/3zW61qYfY
Is it possible to generalize some sort of use-after-move detection at compile-time for global variables? Or is it impossible, even in principle?
Note: please don't make this discussion about global object (I know it is a bad idea) or about the legality of using moved objects (I know some class are designed for that to be ok). The question is technical, about the compiler and tools to detect a certain bug-prone pattern in the program.
Full working code, compile with clang ... -Wconsumed -Werror -std=c++11
or use clang-tidy
.
The clang annotation (extensions) help the compiler detect the patterns.
#include<cassert>
#include<memory>
class [[clang::consumable(unconsumed)]] a_class {
std::unique_ptr<int> p_;
public:
[[clang::callable_when(unconsumed)]]
void f() {}
// private: [[clang::set_typestate(consumed)]] void invalidate() {} // not needed but good to know
};
a_class a;
int main(int, char**) {
// a_class a;
auto b = std::move(a);
a.f(); // global doesn't warn here
}
Most of the information I could find about this clang extension is from here: Andrea Kling's blog https://awesomekling.github.io/Catching-use-after-move-bugs-with-Clang-consumed-annotations/
std::unique_ptr
andptr->Foo()
, which is obviously wrong (in the UB sense) after a move. I am not sure what you intended, but I doubt it was this. – Wellbeingunique_ptr
which is also a nullptr. – Disgracefulstd::
moved from object still contains what's been moved. It's guaranteed to be in its empty state, ready to be used as is. It means you can use astd::string
as an empty string directly after a move - noclear()
needed. – Capersclear()
is needed because you have no guarantees, whatsoever, that a moved-fromstd::string
will be empty. If a short-string optimization is implemented, the moved-from string may be completely unchanged. – Disgracefulstd::string
s hold the complete contents of "Lord Of The Rings" (in static storage, with some kind of a "copy on write" flag set). This would be completely compliant with the C++ standard. – Disgracefulunique_ptr
does). I think you missed an Universal quantification somewhere: what's valid for all objects that aren't moved is also valid for every object that's moved-from. This explainsptr->Foo()
, which is invalid whenptr
is null. – Wellbeing-Wconsumed
, it seems that the implementation is quite incomplete (as also noted in the documentation). Clang is unable to diagnose the issue if even a single indirection is involved. So, a workaround might be to always use a non-reference local variable by means of a proxy and use a global getter rather than a variable, e.g. like this. Would this be viable in your use case? – Neubergera.f()
is only invalid iftest1()
was called beforetest2()
. In general, there is no way for the compiler to know this. So I'd argue that the best you can do in C++ is to warn about use-after-move only within individual functions, but never across functions. So, in the proxy approach, you should callget_a_class()
only once per function. – Neubergerclang::consumable
. I think this eventually will come to standard C++. – Occlusive[[clang::consumable(
unconsumed)]]
. But I could be wrong. – Occlusivex
in your example. I think that is how compilers work if I am not mistaken, – Occlusive