brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.4 KiB · 07edd07 Raw
256 lines · plain
1.. title:: clang-tidy - bugprone-use-after-move2 3bugprone-use-after-move4=======================5 6Warns if an object is used after it has been moved, for example:7 8.. code-block:: c++9 10    std::string str = "Hello, world!\n";11    std::vector<std::string> messages;12    messages.emplace_back(std::move(str));13    std::cout << str;14 15The last line will trigger a warning that ``str`` is used after it has been16moved.17 18The check does not trigger a warning if the object is reinitialized after the19move and before the use. For example, no warning will be output for this code:20 21.. code-block:: c++22 23    messages.emplace_back(std::move(str));24    str = "Greetings, stranger!\n";25    std::cout << str;26 27Subsections below explain more precisely what exactly the check considers to be28a move, use, and reinitialization.29 30The check takes control flow into account. A warning is only emitted if the use31can be reached from the move. This means that the following code does not32produce a warning:33 34.. code-block:: c++35 36    if (condition) {37      messages.emplace_back(std::move(str));38    } else {39      std::cout << str;40    }41 42On the other hand, the following code does produce a warning:43 44.. code-block:: c++45 46    for (int i = 0; i < 10; ++i) {47      std::cout << str;48      messages.emplace_back(std::move(str));49    }50 51(The use-after-move happens on the second iteration of the loop.)52 53In some cases, the check may not be able to detect that two branches are54mutually exclusive. For example (assuming that ``i`` is an int):55 56.. code-block:: c++57 58    if (i == 1) {59      messages.emplace_back(std::move(str));60    }61    if (i == 2) {62      std::cout << str;63    }64 65In this case, the check will erroneously produce a warning, even though it is66not possible for both the move and the use to be executed. More formally, the67analysis is `flow-sensitive but not path-sensitive68<https://en.wikipedia.org/wiki/Data-flow_analysis#Sensitivities>`_.69 70Silencing erroneous warnings71----------------------------72 73An erroneous warning can be silenced by reinitializing the object after the74move:75 76.. code-block:: c++77 78    if (i == 1) {79      messages.emplace_back(std::move(str));80      str = "";81    }82    if (i == 2) {83      std::cout << str;84    }85 86If you want to avoid the overhead of actually reinitializing the object,87you can create a dummy function that causes the check to assume the object88was reinitialized:89 90.. code-block:: c++91 92    template <class T>93    void IS_INITIALIZED(T&) {}94 95You can use this as follows:96 97.. code-block:: c++98 99    if (i == 1) {100      messages.emplace_back(std::move(str));101    }102    if (i == 2) {103      IS_INITIALIZED(str);104      std::cout << str;105    }106 107The check will not output a warning in this case because passing the object108to a function as a non-const pointer or reference counts as a reinitialization109(see section `Reinitialization`_ below).110 111Unsequenced moves, uses, and reinitializations112----------------------------------------------113 114In many cases, C++ does not make any guarantees about the order in which115sub-expressions of a statement are evaluated. This means that in code like the116following, it is not guaranteed whether the use will happen before or after the117move:118 119.. code-block:: c++120 121    void f(int i, std::vector<int> v);122    std::vector<int> v = { 1, 2, 3 };123    f(v[1], std::move(v));124 125In this kind of situation, the check will note that the use and move are126unsequenced.127 128The check will also take sequencing rules into account when reinitializations129occur in the same statement as moves or uses. A reinitialization is only130considered to reinitialize a variable if it is guaranteed to be evaluated after131the move and before the use.132 133Move134----135 136The check currently only considers calls of ``std::move`` on local variables or137function parameters. It does not check moves of member variables or global138variables.139 140Any call of ``std::move`` on a variable is considered to cause a move of that141variable, even if the result of ``std::move`` is not passed to an rvalue142reference parameter.143 144This means that the check will flag a use-after-move even on a type that does145not define a move constructor or move assignment operator. This is intentional.146Developers may use ``std::move`` on such a type in the expectation that the147type will add move semantics in the future. If such a ``std::move`` has the148potential to cause a use-after-move, we want to warn about it even if the type149does not implement move semantics yet.150 151Furthermore, if the result of ``std::move`` *is* passed to an rvalue reference152parameter, this will always be considered to cause a move, even if the function153that consumes this parameter does not move from it, or if it does so only154conditionally. For example, in the following situation, the check will assume155that a move always takes place:156 157.. code-block:: c++158 159    std::vector<std::string> messages;160    void f(std::string &&str) {161      // Only remember the message if it isn't empty.162      if (!str.empty()) {163        messages.emplace_back(std::move(str));164      }165    }166    std::string str = "";167    f(std::move(str));168 169The check will assume that the last line causes a move, even though, in this170particular case, it does not. Again, this is intentional.171 172There is one special case: A call to ``std::move`` inside a ``try_emplace``173call is conservatively assumed not to move. This is to avoid spurious warnings,174as the check has no way to reason about the ``bool`` returned by ``try_emplace``.175 176When analyzing the order in which moves, uses and reinitializations happen (see177section `Unsequenced moves, uses, and reinitializations`_), the move is assumed178to occur in whichever function the result of the ``std::move`` is passed to.179 180The check also handles perfect-forwarding with ``std::forward`` so the181following code will also trigger a use-after-move warning.182 183.. code-block:: c++184 185  void consume(int);186 187  void f(int&& i) {188    consume(std::forward<int>(i));189    consume(std::forward<int>(i)); // use-after-move190  }191 192Use193---194 195Any occurrence of the moved variable that is not a reinitialization (see below)196is considered to be a use.197 198An exception to this are objects of type ``std::unique_ptr``,199``std::shared_ptr``, ``std::weak_ptr``, ``std::optional``, and ``std::any``.200An exception to this are objects of type ``std::unique_ptr``,201``std::shared_ptr``, ``std::weak_ptr``, ``std::optional``, and ``std::any``,202which can be reinitialized via ``reset``. For smart pointers specifically, the203moved-from objects have a well-defined state of being ``nullptr``s, and only204``operator*``, ``operator->`` and ``operator[]`` are considered bad accesses as205they would be dereferencing a ``nullptr``.206 207If multiple uses occur after a move, only the first of these is flagged.208 209Reinitialization210----------------211 212The check considers a variable to be reinitialized in the following cases:213 214  - The variable occurs on the left-hand side of an assignment.215 216  - The variable is passed to a function as a non-const pointer or non-const217    lvalue reference. (It is assumed that the variable may be an out-parameter218    for the function.)219 220  - ``clear()`` or ``assign()`` is called on the variable and the variable is221    of     one of the standard container types ``basic_string``, ``vector``,222    ``deque``, ``forward_list``, ``list``, ``set``, ``map``, ``multiset``,223    ``multimap``, ``unordered_set``, ``unordered_map``, ``unordered_multiset``,224    ``unordered_multimap``.225 226  - ``reset()`` is called on the variable and the variable is of type227    ``std::unique_ptr``, ``std::shared_ptr``, ``std::weak_ptr``,228    ``std::optional``, or ``std::any``.229 230  - A member function marked with the ``[[clang::reinitializes]]`` attribute is231    called on the variable.232 233If the variable in question is a struct and an individual member variable of234that struct is written to, the check does not consider this to be a235reinitialization -- even if, eventually, all member variables of the struct are236written to. For example:237 238.. code-block:: c++239 240    struct S {241      std::string str;242      int i;243    };244    S s = { "Hello, world!\n", 42 };245    S s_other = std::move(s);246    s.str = "Lorem ipsum";247    s.i = 99;248 249The check will not consider ``s`` to be reinitialized after the last line;250instead, the line that assigns to ``s.str`` will be flagged as a use-after-move.251This is intentional as this pattern of reinitializing a struct is error-prone.252For example, if an additional member variable is added to ``S``, it is easy to253forget to add the reinitialization for this additional member. Instead, it is254safer to assign to the entire struct in one go, and this will also avoid the255use-after-move warning.256