297 lines
8.6 KiB
ReStructuredText
297 lines
8.6 KiB
ReStructuredText
.. title:: clang-tidy - bugprone-unchecked-optional-access
|
|
|
|
bugprone-unchecked-optional-access
|
|
==================================
|
|
|
|
*Note*: This check uses a flow-sensitive static analysis to produce its
|
|
results. Therefore, it may be more resource intensive (RAM, CPU) than the
|
|
average clang-tidy check.
|
|
|
|
This check identifies unsafe accesses to values contained in
|
|
``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``, or
|
|
``folly::Optional<T>`` objects. Below we will refer to all these types
|
|
collectively as ``optional<T>``.
|
|
|
|
An access to the value of an ``optional<T>`` occurs when one of its ``value``,
|
|
``operator*``, or ``operator->`` member functions is invoked. To align with
|
|
common misconceptions, the check considers these member functions as equivalent,
|
|
even though there are subtle differences related to exceptions versus undefined
|
|
behavior. See *Additional notes*, below, for more information on this topic.
|
|
|
|
An access to the value of an ``optional<T>`` is considered safe if and only if
|
|
code in the local scope (for example, a function body) ensures that the
|
|
``optional<T>`` has a value in all possible execution paths that can reach the
|
|
access. That should happen either through an explicit check, using the
|
|
``optional<T>::has_value`` member function, or by constructing the
|
|
``optional<T>`` in a way that shows that it unambiguously holds a value (e.g
|
|
using ``std::make_optional`` which always returns a populated
|
|
``std::optional<T>``).
|
|
|
|
Below we list some examples, starting with unsafe optional access patterns,
|
|
followed by safe access patterns.
|
|
|
|
Unsafe access patterns
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Access the value without checking if it exists
|
|
----------------------------------------------
|
|
|
|
The check flags accesses to the value that are not locally guarded by
|
|
existence check:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(std::optional<int> opt) {
|
|
use(*opt); // unsafe: it is unclear whether `opt` has a value.
|
|
}
|
|
|
|
Access the value in the wrong branch
|
|
------------------------------------
|
|
|
|
The check is aware of the state of an optional object in different
|
|
branches of the code. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(std::optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
} else {
|
|
use(opt.value()); // unsafe: it is clear that `opt` does *not* have a value.
|
|
}
|
|
}
|
|
|
|
Assume a function result to be stable
|
|
-------------------------------------
|
|
|
|
The check is aware that function results might not be stable. That is,
|
|
consecutive calls to the same function might return different values.
|
|
For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(Foo foo) {
|
|
if (foo.opt().has_value()) {
|
|
use(*foo.opt()); // unsafe: it is unclear whether `foo.opt()` has a value.
|
|
}
|
|
}
|
|
|
|
Rely on invariants of uncommon APIs
|
|
-----------------------------------
|
|
|
|
The check is unaware of invariants of uncommon APIs. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(Foo foo) {
|
|
if (foo.HasProperty("bar")) {
|
|
use(*foo.GetProperty("bar")); // unsafe: it is unclear whether `foo.GetProperty("bar")` has a value.
|
|
}
|
|
}
|
|
|
|
Check if a value exists, then pass the optional to another function
|
|
-------------------------------------------------------------------
|
|
|
|
The check relies on local reasoning. The check and value access must
|
|
both happen in the same function. An access is considered unsafe even if
|
|
the caller of the function performing the access ensures that the
|
|
optional has a value. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void g(std::optional<int> opt) {
|
|
use(*opt); // unsafe: it is unclear whether `opt` has a value.
|
|
}
|
|
|
|
void f(std::optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
g(opt);
|
|
}
|
|
}
|
|
|
|
Safe access patterns
|
|
~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Check if a value exists, then access the value
|
|
----------------------------------------------
|
|
|
|
The check recognizes all straightforward ways for checking if a value
|
|
exists and accessing the value contained in an optional object. For
|
|
example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(std::optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
use(*opt);
|
|
}
|
|
}
|
|
|
|
|
|
Check if a value exists, then access the value from a copy
|
|
----------------------------------------------------------
|
|
|
|
The criteria that the check uses is semantic, not syntactic. It
|
|
recognizes when a copy of the optional object being accessed is known to
|
|
have a value. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(std::optional<int> opt1) {
|
|
if (opt1.has_value()) {
|
|
std::optional<int> opt2 = opt1;
|
|
use(*opt2);
|
|
}
|
|
}
|
|
|
|
|
|
Ensure that a value exists using common macros
|
|
----------------------------------------------
|
|
|
|
The check is aware of common macros like ``CHECK`` and ``DCHECK``. Those can be
|
|
used to ensure that an optional object has a value. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(std::optional<int> opt) {
|
|
DCHECK(opt.has_value());
|
|
use(*opt);
|
|
}
|
|
|
|
Ensure that a value exists, then access the value in a correlated branch
|
|
------------------------------------------------------------------------
|
|
|
|
The check is aware of correlated branches in the code and can figure out
|
|
when an optional object is ensured to have a value on all execution
|
|
paths that lead to an access. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(std::optional<int> opt) {
|
|
bool safe = false;
|
|
if (opt.has_value() && SomeOtherCondition()) {
|
|
safe = true;
|
|
}
|
|
// ... more code...
|
|
if (safe) {
|
|
use(*opt);
|
|
}
|
|
}
|
|
|
|
Stabilize function results
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
Since function results are not assumed to be stable across calls, it is best to
|
|
store the result of the function call in a local variable and use that variable
|
|
to access the value. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(Foo foo) {
|
|
if (const auto& foo_opt = foo.opt(); foo_opt.has_value()) {
|
|
use(*foo_opt);
|
|
}
|
|
}
|
|
|
|
Do not rely on uncommon-API invariants
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
When uncommon APIs guarantee that an optional has contents, do not rely on it --
|
|
instead, check explicitly that the optional object has a value. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void f(Foo foo) {
|
|
if (const auto& property = foo.GetProperty("bar")) {
|
|
use(*property);
|
|
}
|
|
}
|
|
|
|
instead of the `HasProperty`, `GetProperty` pairing we saw above.
|
|
|
|
Do not rely on caller-performed checks
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
If you know that all of a function's callers have checked that an optional
|
|
argument has a value, either change the function to take the value directly or
|
|
check the optional again in the local scope of the callee. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
void g(int val) {
|
|
use(val);
|
|
}
|
|
|
|
void f(std::optional<int> opt) {
|
|
if (opt.has_value()) {
|
|
g(*opt);
|
|
}
|
|
}
|
|
|
|
and
|
|
|
|
.. code-block:: c++
|
|
|
|
struct S {
|
|
std::optional<int> opt;
|
|
int x;
|
|
};
|
|
|
|
void g(const S &s) {
|
|
if (s.opt.has_value() && s.x > 10) {
|
|
use(*s.opt);
|
|
}
|
|
|
|
void f(S s) {
|
|
if (s.opt.has_value()) {
|
|
g(s);
|
|
}
|
|
}
|
|
|
|
Additional notes
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
Aliases created via ``using`` declarations
|
|
------------------------------------------
|
|
|
|
The check is aware of aliases of optional types that are created via
|
|
``using`` declarations. For example:
|
|
|
|
.. code-block:: c++
|
|
|
|
using OptionalInt = std::optional<int>;
|
|
|
|
void f(OptionalInt opt) {
|
|
use(opt.value()); // unsafe: it is unclear whether `opt` has a value.
|
|
}
|
|
|
|
Lambdas
|
|
-------
|
|
|
|
The check does not currently report unsafe optional accesses in lambdas.
|
|
A future version will expand the scope to lambdas, following the rules
|
|
outlined above. It is best to follow the same principles when using
|
|
optionals in lambdas.
|
|
|
|
Access with ``operator*()`` vs. ``value()``
|
|
-------------------------------------------
|
|
|
|
Given that ``value()`` has well-defined behavior (either throwing an exception
|
|
or terminating the program), why treat it the same as ``operator*()`` which
|
|
causes undefined behavior (UB)? That is, why is it considered unsafe to access
|
|
an optional with ``value()``, if it's not provably populated with a value? For
|
|
that matter, why is ``CHECK()`` followed by ``operator*()`` any better than
|
|
``value()``, given that they are semantically equivalent (on configurations that
|
|
disable exceptions)?
|
|
|
|
The answer is that we assume most users do not realize the difference between
|
|
``value()`` and ``operator*()``. Shifting to ``operator*()`` and some form of
|
|
explicit value-presence check or explicit program termination has two
|
|
advantages:
|
|
|
|
* Readability. The check, and any potential side effects like program
|
|
shutdown, are very clear in the code. Separating access from checks can
|
|
actually make the checks more obvious.
|
|
|
|
* Performance. A single check can cover many or even all accesses within
|
|
scope. This gives the user the best of both worlds -- the safety of a
|
|
dynamic check, but without incurring redundant costs.
|