brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.1 KiB · 3423eaa Raw
331 lines · plain
1.. title:: clang-tidy - bugprone-unchecked-optional-access2 3bugprone-unchecked-optional-access4==================================5 6*Note*: This check uses a flow-sensitive static analysis to produce its7results. Therefore, it may be more resource intensive (RAM, CPU) than the8average clang-tidy check.9 10This check identifies unsafe accesses to values contained in11``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``,12``folly::Optional<T>``, ``bsl::optional``, or13``BloombergLP::bdlb::NullableValue`` objects. Below we will refer to all these14types collectively as ``optional<T>``.15 16An access to the value of an ``optional<T>`` occurs when one of its ``value``,17``operator*``, or ``operator->`` member functions is invoked.  To align with18common misconceptions, the check considers these member functions as19equivalent, even though there are subtle differences related to exceptions20versus undefined behavior. See *Additional notes*, below, for more information21on this topic.22 23An access to the value of an ``optional<T>`` is considered safe if and only if24code in the local scope (for example, a function body) ensures that the25``optional<T>`` has a value in all possible execution paths that can reach the26access. That should happen either through an explicit check, using the27``optional<T>::has_value`` member function, or by constructing the28``optional<T>`` in a way that shows that it unambiguously holds a value (e.g29using ``std::make_optional`` which always returns a populated30``std::optional<T>``).31 32Below we list some examples, starting with unsafe optional access patterns,33followed by safe access patterns.34 35Unsafe access patterns36~~~~~~~~~~~~~~~~~~~~~~37 38Access the value without checking if it exists39----------------------------------------------40 41The check flags accesses to the value that are not locally guarded by42existence check:43 44.. code-block:: c++45 46   void f(std::optional<int> opt) {47     use(*opt); // unsafe: it is unclear whether `opt` has a value.48   }49 50Access the value in the wrong branch51------------------------------------52 53The check is aware of the state of an optional object in different54branches of the code. For example:55 56.. code-block:: c++57 58   void f(std::optional<int> opt) {59     if (opt.has_value()) {60     } else {61       use(opt.value()); // unsafe: it is clear that `opt` does *not* have a value.62     }63   }64 65Assume a function result to be stable66-------------------------------------67 68The check is aware that function results might not be stable. That is,69consecutive calls to the same function might return different values.70For example:71 72.. code-block:: c++73 74   void f(Foo foo) {75     if (foo.take().has_value()) {76       use(*foo.take()); // unsafe: it is unclear whether `foo.take()` has a value.77     }78   }79 80Exception: accessor methods81```````````````````````````82 83The check assumes *accessor* methods of a class are stable, with a heuristic to84determine which methods are accessors. Specifically, parameter-free ``const``85methods and smart pointer-like APIs (non ``const`` overloads of ``*`` when86there is a parallel ``const`` overload) are treated as accessors. Note that87this is not guaranteed to be safe -- but, it is widely used (safely) in88practice. Calls to non ``const`` methods are assumed to modify the state of89the object and affect the stability of earlier accessor calls.90 91Rely on invariants of uncommon APIs92-----------------------------------93 94The check is unaware of invariants of uncommon APIs. For example:95 96.. code-block:: c++97 98   void f(Foo foo) {99     if (foo.HasProperty("bar")) {100       use(*foo.GetProperty("bar")); // unsafe: it is unclear whether `foo.GetProperty("bar")` has a value.101     }102   }103 104Check if a value exists, then pass the optional to another function105-------------------------------------------------------------------106 107The check relies on local reasoning. The check and value access must108both happen in the same function. An access is considered unsafe even if109the caller of the function performing the access ensures that the110optional has a value. For example:111 112.. code-block:: c++113 114   void g(std::optional<int> opt) {115     use(*opt); // unsafe: it is unclear whether `opt` has a value.116   }117 118   void f(std::optional<int> opt) {119     if (opt.has_value()) {120       g(opt);121     }122   }123 124Safe access patterns125~~~~~~~~~~~~~~~~~~~~126 127Check if a value exists, then access the value128----------------------------------------------129 130The check recognizes all straightforward ways for checking if a value131exists and accessing the value contained in an optional object. For132example:133 134.. code-block:: c++135 136   void f(std::optional<int> opt) {137     if (opt.has_value()) {138       use(*opt);139     }140   }141 142 143Check if a value exists, then access the value from a copy144----------------------------------------------------------145 146The criteria that the check uses is semantic, not syntactic. It147recognizes when a copy of the optional object being accessed is known to148have a value. For example:149 150.. code-block:: c++151 152   void f(std::optional<int> opt1) {153     if (opt1.has_value()) {154       std::optional<int> opt2 = opt1;155       use(*opt2);156     }157   }158 159 160Ensure that a value exists using common macros161----------------------------------------------162 163The check is aware of common macros like ``CHECK`` and ``DCHECK``. Those can be164used to ensure that an optional object has a value. For example:165 166.. code-block:: c++167 168   void f(std::optional<int> opt) {169     DCHECK(opt.has_value());170     use(*opt);171   }172 173Ensure that a value exists, then access the value in a correlated branch174------------------------------------------------------------------------175 176The check is aware of correlated branches in the code and can figure out177when an optional object is ensured to have a value on all execution178paths that lead to an access. For example:179 180.. code-block:: c++181 182   void f(std::optional<int> opt) {183     bool safe = false;184     if (opt.has_value() && SomeOtherCondition()) {185       safe = true;186     }187     // ... more code...188     if (safe) {189       use(*opt);190     }191   }192 193Stabilize function results194~~~~~~~~~~~~~~~~~~~~~~~~~~195 196Function results are not assumed to be stable across calls, except for197const accessor methods. For more complex accessors (non-const, or depend on198multiple params) it is best to store the result of the function call in a199local variable and use that variable to access the value. For example:200 201.. code-block:: c++202 203   void f(Foo foo) {204     if (const auto& foo_opt = foo.take(); foo_opt.has_value()) {205       use(*foo_opt);206     }207   }208 209Do not rely on uncommon-API invariants210~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~211 212When uncommon APIs guarantee that an optional has contents, do not rely on it213-- instead, check explicitly that the optional object has a value. For example:214 215.. code-block:: c++216 217   void f(Foo foo) {218     if (const auto& property = foo.GetProperty("bar")) {219       use(*property);220     }221   }222 223instead of the `HasProperty`, `GetProperty` pairing we saw above.224 225Do not rely on caller-performed checks226~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~227 228If you know that all of a function's callers have checked that an optional229argument has a value, either change the function to take the value directly or230check the optional again in the local scope of the callee. For example:231 232.. code-block:: c++233 234   void g(int val) {235     use(val);236   }237 238   void f(std::optional<int> opt) {239     if (opt.has_value()) {240       g(*opt);241     }242   }243 244and245 246.. code-block:: c++247 248   struct S {249     std::optional<int> opt;250     int x;251   };252 253   void g(const S &s) {254     if (s.opt.has_value() && s.x > 10) {255       use(*s.opt);256   }257 258   void f(S s) {259     if (s.opt.has_value()) {260       g(s);261     }262   }263 264Additional notes265~~~~~~~~~~~~~~~~266 267Aliases created via ``using`` declarations268------------------------------------------269 270The check is aware of aliases of optional types that are created via271``using`` declarations. For example:272 273.. code-block:: c++274 275   using OptionalInt = std::optional<int>;276 277   void f(OptionalInt opt) {278     use(opt.value()); // unsafe: it is unclear whether `opt` has a value.279   }280 281Lambdas282-------283 284The check does not currently report unsafe optional accesses in lambdas.285A future version will expand the scope to lambdas, following the rules286outlined above. It is best to follow the same principles when using287optionals in lambdas.288 289Access with ``operator*()`` vs. ``value()``290-------------------------------------------291 292Given that ``value()`` has well-defined behavior (either throwing an exception293or terminating the program), why treat it the same as ``operator*()`` which294causes undefined behavior (UB)? That is, why is it considered unsafe to access295an optional with ``value()``, if it's not provably populated with a value?  For296that matter, why is ``CHECK()`` followed by ``operator*()`` any better than297``value()``, given that they are semantically equivalent (on configurations298that disable exceptions)?299 300The answer is that we assume most users do not realize the difference between301``value()`` and ``operator*()``. Shifting to ``operator*()`` and some form of302explicit value-presence check or explicit program termination has two303advantages:304 305  * Readability. The check, and any potential side effects like program306    shutdown, are very clear in the code. Separating access from checks can307    actually make the checks more obvious.308 309  * Performance. A single check can cover many or even all accesses within310    scope. This gives the user the best of both worlds -- the safety of a311    dynamic check, but without incurring redundant costs.312 313Options314-------315 316.. option:: IgnoreSmartPointerDereference317 318   If set to `true`, the check ignores optionals that319   are reached through overloaded smart-pointer-like dereference (``operator*``,320   ``operator->``) on classes other than the optional type itself. This helps321   avoid false positives where the analysis cannot equate results across such322   calls. This does not cover access through ``operator[]``. Default is `false`.323 324.. option:: IgnoreValueCalls325 326   If set to `true`, the check does not diagnose calls327   to ``optional::value()``. Diagnostics for ``operator*()`` and328   ``operator->()`` remain enabled. This is useful for codebases that329   intentionally rely on ``value()`` for defined, guarded access while still330   flagging UB-prone operator dereferences. Default is `false`.331