643 changes: 643 additions & 0 deletions clang-tools-extra/clang-tidy/misc/UseAfterMoveCheck.cpp

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions clang-tools-extra/clang-tidy/misc/UseAfterMoveCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===--- UseAfterMoveCheck.h - clang-tidy ---------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H

#include "../ClangTidy.h"

namespace clang {
namespace tidy {
namespace misc {

/// The check warns if an object is used after it has been moved, without an
/// intervening reinitialization.
///
/// For details, see the user-facing documentation:
/// http://clang.llvm.org/extra/clang-tidy/checks/misc-use-after-move.html
class UseAfterMoveCheck : public ClangTidyCheck {
public:
UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
};

} // namespace misc
} // namespace tidy
} // namespace clang

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_USEAFTERMOVECHECK_H
6 changes: 6 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ Improvements to clang-tidy
Warns when ``std::move`` is applied to a forwarding reference instead of
``std::forward``.

- New `misc-use-after-move
<http://clang.llvm.org/extra/clang-tidy/checks/misc-use-after-move.html>`_ check

Warns if an object is used after it has been moved, without an intervening
reinitialization.

- New `mpi-buffer-deref
<http://clang.llvm.org/extra/clang-tidy/checks/mpi-buffer-deref.html>`_ check

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Clang-Tidy Checks
misc-unused-parameters
misc-unused-raii
misc-unused-using-decls
misc-use-after-move
misc-virtual-near-miss
modernize-avoid-bind
modernize-deprecated-headers
Expand Down
197 changes: 197 additions & 0 deletions clang-tools-extra/docs/clang-tidy/checks/misc-use-after-move.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
.. title:: clang-tidy - misc-use-after-move

misc-use-after-move
===================

Warns if an object is used after it has been moved, for example:

.. code-block:: c++

std::string str = "Hello, world!\n";
std::vector<std::string> messages;
messages.emplace_back(std::move(str));
std::cout << str;

The last line will trigger a warning that ``str`` is used after it has been
moved.

The check does not trigger a warning if the object is reinitialized after the
move and before the use. For example, no warning will be output for this code:

.. code-block:: c++

messages.emplace_back(std::move(str));
str = "Greetings, stranger!\n";
std::cout << str;

The check takes control flow into account. A warning is only emitted if the use
can be reached from the move. This means that the following code does not
produce a warning:

.. code-block:: c++

if (condition) {
messages.emplace_back(std::move(str));
} else {
std::cout << str;
}

On the other hand, the following code does produce a warning:

.. code-block:: c++

for (int i = 0; i < 10; ++i) {
std::cout << str;
messages.emplace_back(std::move(str));
}

(The use-after-move happens on the second iteration of the loop.)

In some cases, the check may not be able to detect that two branches are
mutually exclusive. For example (assuming that ``i`` is an int):

.. code-block:: c++

if (i == 1) {
messages.emplace_back(std::move(str));
}
if (i == 2) {
std::cout << str;
}

In this case, the check will erroneously produce a warning, even though it is
not possible for both the move and the use to be executed.

An erroneous warning can be silenced by reinitializing the object after the
move:

.. code-block:: c++

if (i == 1) {
messages.emplace_back(std::move(str));
str = "";
}
if (i == 2) {
std::cout << str;
}

No warnings are emitted for objects of type ``std::unique_ptr`` and
``std::shared_ptr``, as they have defined move behavior. (Objects of these
classes are guaranteed to be empty after they have been moved from.)

Subsections below explain more precisely what exactly the check considers to be
a move, use, and reinitialization.

Unsequenced moves, uses, and reinitializations
----------------------------------------------

In many cases, C++ does not make any guarantees about the order in which
sub-expressions of a statement are evaluated. This means that in code like the
following, it is not guaranteed whether the use will happen before or after the
move:

.. code-block:: c++

void f(int i, std::vector<int> v);
std::vector<int> v = { 1, 2, 3 };
f(v[1], std::move(v));

In this kind of situation, the check will note that the use and move are
unsequenced.

The check will also take sequencing rules into account when reinitializations
occur in the same statement as moves or uses. A reinitialization is only
considered to reinitialize a variable if it is guaranteed to be evaluated after
the move and before the use.

Move
----

The check currently only considers calls of ``std::move`` on local variables or
function parameters. It does not check moves of member variables or global
variables.

Any call of ``std::move`` on a variable is considered to cause a move of that
variable, even if the result of ``std::move`` is not passed to an rvalue
reference parameter.

This means that the check will flag a use-after-move even on a type that does
not define a move constructor or move assignment operator. This is intentional.
Developers may use ``std::move`` on such a type in the expectation that the type
will add move semantics in the future. If such a ``std::move`` has the potential
to cause a use-after-move, we want to warn about it even if the type does not
implement move semantics yet.

Furthermore, if the result of ``std::move`` *is* passed to an rvalue reference
parameter, this will always be considered to cause a move, even if the function
that consumes this parameter does not move from it, or if it does so only
conditionally. For example, in the following situation, the check will assume
that a move always takes place:

.. code-block:: c++

std::vector<std::string> messages;
void f(std::string &&str) {
// Only remember the message if it isn't empty.
if (!str.empty()) {
messages.emplace_back(std::move(str));
}
}
std::string str = "";
f(std::move(str));

The check will assume that the last line causes a move, even though, in this
particular case, it does not. Again, this is intentional.

When analyzing the order in which moves, uses and reinitializations happen (see
section `Unsequenced moves, uses, and reinitializations`_), the move is assumed
to occur in whichever function the result of the ``std::move`` is passed to.

Use
---

Any occurrence of the moved variable that is not a reinitialization (see below)
is considered to be a use.

If multiple uses occur after a move, only the first of these is flagged.

Reinitialization
----------------

The check considers a variable to be reinitialized in the following cases:

- The variable occurs on the left-hand side of an assignment.

- The variable is passed to a function as a non-const pointer or non-const
lvalue reference. (It is assumed that the variable may be an out-parameter
for the function.)

- ``clear()`` or ``assign()`` is called on the variable and the variable is of
one of the standard container types ``basic_string``, ``vector``, ``deque``,
``forward_list``, ``list``, ``set``, ``map``, ``multiset``, ``multimap``,
``unordered_set``, ``unordered_map``, ``unordered_multiset``,
``unordered_multimap``.

If the variable in question is a struct and an individual member variable of
that struct is written to, the check does not consider this to be a
reinitialization -- even if, eventually, all member variables of the struct are
written to. For example:

.. code-block:: c++

struct S {
std::string str;
int i;
};
S s = { "Hello, world!\n", 42 };
S s_other = std::move(s);
s.str = "Lorem ipsum";
s.i = 99;

The check will not consider ``s`` to be reinitialized after the last line;
instead, the line that assigns to ``s.str`` will be flagged as a use-after-move.
This is intentional as this pattern of reinitializing a struct is error-prone.
For example, if an additional member variable is added to ``S``, it is easy to
forget to add the reinitialization for this additional member. Instead, it is
safer to assign to the entire struct in one go, and this will also avoid the
use-after-move warning.
1,039 changes: 1,039 additions & 0 deletions clang-tools-extra/test/clang-tidy/misc-use-after-move.cpp

Large diffs are not rendered by default.