1.. title:: clang-tidy - bugprone-unchecked-optional-access 2 3bugprone-unchecked-optional-access 4================================== 5 6*Note*: This check uses a flow-sensitive static analysis to produce its 7results. Therefore, it may be more resource intensive (RAM, CPU) than the 8average clang-tidy check. 9 10This check identifies unsafe accesses to values contained in 11``std::optional<T>``, ``absl::optional<T>``, ``base::Optional<T>``, 12``folly::Optional<T>``, ``bsl::optional``, or 13``BloombergLP::bdlb::NullableValue`` objects. Below we will refer to all these 14types 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 with 18common misconceptions, the check considers these member functions as equivalent, 19even though there are subtle differences related to exceptions versus undefined 20behavior. See *Additional notes*, below, for more information on this topic. 21 22An access to the value of an ``optional<T>`` is considered safe if and only if 23code in the local scope (for example, a function body) ensures that the 24``optional<T>`` has a value in all possible execution paths that can reach the 25access. That should happen either through an explicit check, using the 26``optional<T>::has_value`` member function, or by constructing the 27``optional<T>`` in a way that shows that it unambiguously holds a value (e.g 28using ``std::make_optional`` which always returns a populated 29``std::optional<T>``). 30 31Below we list some examples, starting with unsafe optional access patterns, 32followed by safe access patterns. 33 34Unsafe access patterns 35~~~~~~~~~~~~~~~~~~~~~~ 36 37Access the value without checking if it exists 38---------------------------------------------- 39 40The check flags accesses to the value that are not locally guarded by 41existence check: 42 43.. code-block:: c++ 44 45 void f(std::optional<int> opt) { 46 use(*opt); // unsafe: it is unclear whether `opt` has a value. 47 } 48 49Access the value in the wrong branch 50------------------------------------ 51 52The check is aware of the state of an optional object in different 53branches of the code. For example: 54 55.. code-block:: c++ 56 57 void f(std::optional<int> opt) { 58 if (opt.has_value()) { 59 } else { 60 use(opt.value()); // unsafe: it is clear that `opt` does *not* have a value. 61 } 62 } 63 64Assume a function result to be stable 65------------------------------------- 66 67The check is aware that function results might not be stable. That is, 68consecutive calls to the same function might return different values. 69For example: 70 71.. code-block:: c++ 72 73 void f(Foo foo) { 74 if (foo.opt().has_value()) { 75 use(*foo.opt()); // unsafe: it is unclear whether `foo.opt()` has a value. 76 } 77 } 78 79Exception: accessor methods 80``````````````````````````` 81 82The check assumes *accessor* methods of a class are stable, with a heuristic to 83determine which methods are accessors. Specifically, parameter-free ``const`` 84methods are treated as accessors. Note that this is not guaranteed to be safe 85-- but, it is widely used (safely) in practice, and so we have chosen to treat 86it as generally safe. Calls to non ``const`` methods are assumed to modify 87the state of the object and affect the stability of earlier accessor calls. 88 89Rely on invariants of uncommon APIs 90----------------------------------- 91 92The check is unaware of invariants of uncommon APIs. For example: 93 94.. code-block:: c++ 95 96 void f(Foo foo) { 97 if (foo.HasProperty("bar")) { 98 use(*foo.GetProperty("bar")); // unsafe: it is unclear whether `foo.GetProperty("bar")` has a value. 99 } 100 } 101 102Check if a value exists, then pass the optional to another function 103------------------------------------------------------------------- 104 105The check relies on local reasoning. The check and value access must 106both happen in the same function. An access is considered unsafe even if 107the caller of the function performing the access ensures that the 108optional has a value. For example: 109 110.. code-block:: c++ 111 112 void g(std::optional<int> opt) { 113 use(*opt); // unsafe: it is unclear whether `opt` has a value. 114 } 115 116 void f(std::optional<int> opt) { 117 if (opt.has_value()) { 118 g(opt); 119 } 120 } 121 122Safe access patterns 123~~~~~~~~~~~~~~~~~~~~ 124 125Check if a value exists, then access the value 126---------------------------------------------- 127 128The check recognizes all straightforward ways for checking if a value 129exists and accessing the value contained in an optional object. For 130example: 131 132.. code-block:: c++ 133 134 void f(std::optional<int> opt) { 135 if (opt.has_value()) { 136 use(*opt); 137 } 138 } 139 140 141Check if a value exists, then access the value from a copy 142---------------------------------------------------------- 143 144The criteria that the check uses is semantic, not syntactic. It 145recognizes when a copy of the optional object being accessed is known to 146have a value. For example: 147 148.. code-block:: c++ 149 150 void f(std::optional<int> opt1) { 151 if (opt1.has_value()) { 152 std::optional<int> opt2 = opt1; 153 use(*opt2); 154 } 155 } 156 157 158Ensure that a value exists using common macros 159---------------------------------------------- 160 161The check is aware of common macros like ``CHECK`` and ``DCHECK``. Those can be 162used to ensure that an optional object has a value. For example: 163 164.. code-block:: c++ 165 166 void f(std::optional<int> opt) { 167 DCHECK(opt.has_value()); 168 use(*opt); 169 } 170 171Ensure that a value exists, then access the value in a correlated branch 172------------------------------------------------------------------------ 173 174The check is aware of correlated branches in the code and can figure out 175when an optional object is ensured to have a value on all execution 176paths that lead to an access. For example: 177 178.. code-block:: c++ 179 180 void f(std::optional<int> opt) { 181 bool safe = false; 182 if (opt.has_value() && SomeOtherCondition()) { 183 safe = true; 184 } 185 // ... more code... 186 if (safe) { 187 use(*opt); 188 } 189 } 190 191Stabilize function results 192~~~~~~~~~~~~~~~~~~~~~~~~~~ 193 194Since function results are not assumed to be stable across calls, it is best to 195store the result of the function call in a local variable and use that variable 196to access the value. For example: 197 198.. code-block:: c++ 199 200 void f(Foo foo) { 201 if (const auto& foo_opt = foo.opt(); foo_opt.has_value()) { 202 use(*foo_opt); 203 } 204 } 205 206Do not rely on uncommon-API invariants 207~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 208 209When uncommon APIs guarantee that an optional has contents, do not rely on it -- 210instead, check explicitly that the optional object has a value. For example: 211 212.. code-block:: c++ 213 214 void f(Foo foo) { 215 if (const auto& property = foo.GetProperty("bar")) { 216 use(*property); 217 } 218 } 219 220instead of the `HasProperty`, `GetProperty` pairing we saw above. 221 222Do not rely on caller-performed checks 223~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 224 225If you know that all of a function's callers have checked that an optional 226argument has a value, either change the function to take the value directly or 227check the optional again in the local scope of the callee. For example: 228 229.. code-block:: c++ 230 231 void g(int val) { 232 use(val); 233 } 234 235 void f(std::optional<int> opt) { 236 if (opt.has_value()) { 237 g(*opt); 238 } 239 } 240 241and 242 243.. code-block:: c++ 244 245 struct S { 246 std::optional<int> opt; 247 int x; 248 }; 249 250 void g(const S &s) { 251 if (s.opt.has_value() && s.x > 10) { 252 use(*s.opt); 253 } 254 255 void f(S s) { 256 if (s.opt.has_value()) { 257 g(s); 258 } 259 } 260 261Additional notes 262~~~~~~~~~~~~~~~~ 263 264Aliases created via ``using`` declarations 265------------------------------------------ 266 267The check is aware of aliases of optional types that are created via 268``using`` declarations. For example: 269 270.. code-block:: c++ 271 272 using OptionalInt = std::optional<int>; 273 274 void f(OptionalInt opt) { 275 use(opt.value()); // unsafe: it is unclear whether `opt` has a value. 276 } 277 278Lambdas 279------- 280 281The check does not currently report unsafe optional accesses in lambdas. 282A future version will expand the scope to lambdas, following the rules 283outlined above. It is best to follow the same principles when using 284optionals in lambdas. 285 286Access with ``operator*()`` vs. ``value()`` 287------------------------------------------- 288 289Given that ``value()`` has well-defined behavior (either throwing an exception 290or terminating the program), why treat it the same as ``operator*()`` which 291causes undefined behavior (UB)? That is, why is it considered unsafe to access 292an optional with ``value()``, if it's not provably populated with a value? For 293that matter, why is ``CHECK()`` followed by ``operator*()`` any better than 294``value()``, given that they are semantically equivalent (on configurations that 295disable exceptions)? 296 297The answer is that we assume most users do not realize the difference between 298``value()`` and ``operator*()``. Shifting to ``operator*()`` and some form of 299explicit value-presence check or explicit program termination has two 300advantages: 301 302 * Readability. The check, and any potential side effects like program 303 shutdown, are very clear in the code. Separating access from checks can 304 actually make the checks more obvious. 305 306 * Performance. A single check can cover many or even all accesses within 307 scope. This gives the user the best of both worlds -- the safety of a 308 dynamic check, but without incurring redundant costs. 309