An option(al) to surprise you
In today's post I share a learning of a customer with you. A while back, a customer asked me to join a debugging session. They had an issue they didn't (fully) understand.
The base
What I will show you is a much down-stripped and, of course, altered version. It was about a message system, but that's not important. Have a look at the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
You can see a class enum for a state and a function Worker
. The function takes a State
and a const char*
message named data
. The function's job is to create a std::optional
containing the user data, a C-style string.
Using the functionality
The next piece is a debugging function called Log
:
1 2 3 4 5 |
|
Its sole job is to print the user's message or output <empty>
if there is no user message. Thanks to std::optional
s value_or
, the check for a valid message and the decision which string to output is easy. Of course, you can go for the longer with an if
and test the has_value
of the optional. However, I like this version, and for the issue, the version doesn't make a difference.
It doesn't (always) work
All right, so what they reported is that the program sometimes crashes like this:
1 2 |
|
Clearly a nullptr
that is misused. But where? Can you spot it on the code above?
Fighting through layers of code and understanding the application a bit better, it took me some time to figure things out.
Notice the two states GotValidInput
and GotValidInputWithData
. The first state has no input from the user. It sounds reasonable to treat this state as the one with user data since the std::optional
handles whether there is data stored or not. Well, ... Let's have a look at a hypothetical code that uses Worker
and Log
:
1 2 3 |
|
With no user input the function Worker
is called with a nullptr
. Still reasonable. Do you see the bug now?
nullptr
is a valid value
For our std::optional
nullptr
is a valid value of the type const char*
. Hence, setting the std::optional
to nullptr
makes the optional contain a value! That value is nullptr
. Once you access the value regardless of how in Log
a nullptr
-check is required!
A fixed version of Log
looks like this:
1 2 3 4 5 6 7 8 9 10 |
|
In the end, I can say I should have seen the issue earlier, but with thousands of lines of code around, it took me a bit longer than I wanted. Always be cautious with std::optional
and pointers. Containing a value doesn't imply that the value is not a nullptr.
Andreas