Why is fz_var necessary?

As stated before fz_tryfz_catch are implemented using setjmplongjmp, and these can `lose' changes to variables.

For example:


\begin{lstlisting}
house_t *build_house(fz_context *ctx)
{
walls_t *w = NULL;
...
...haps by simply returning NULL. */
return NULL;
}
return h;
}
\end{lstlisting}

In the above code (as well as throughout MuPDF), we follow the convention that destructors always accept NULL. This makes cleanup code much simpler.

Reading through this code, it is fairly obvious what will happen if everything works correctly. First we'll make some walls, w, and a roof, r. Then we combine the walls and the roof, to get our house, h. As part of this process, the house would take references to the walls and roof as required. Next we tidy up our local references to the walls and the roof, and we return the completed house to our caller.

It's more interesting to consider what will happen if we have failures.

First let's consider what happens if the make_walls fails. This will fz_throw an exception, and control will jump immediately to the fz_always. This will drop w and r (both of which are still NULL). The fz_catch can then handle the error, either by returning NULL, to indicate failure, or perhaps by fz_rethrowing the error to an enclosing fz_tryfz_catch construct. No problems there.

So what happens when the failure occurs in make_roof? Let's run through the code again.

This time, make_walls succeeds, and w is set to this new value. Then make_roof fails, fz_throwing an exception, and control will jump immediately to the fz_always. This will then try to drop w (now a valid value) and r (which is still NULL). The fz_catch can then handle the error, either by returning NULL, to indicate failure, or perhaps by fz_rethrowing the error to an enclosing fz_tryfz_catch construct. All sounds quite plausible.

Unfortunately, if you try it, on some systems you will find that you have a memory leak (or worse). When drop_walls is called, sometimes you will find that w has `lost' its value.

This is due to an obscure part of the C specification that states that any changes to the values of local variables made between a setjmp and a longjmp can be lost. (In fact, the C specification goes further than this, and says that such variables become `undefined').

In fz_tryfz_catch terms, this means that any local variables set within the fz_try block can be `lost' when either fz_always or fz_catch are reached.

Fortunately, there is a fix for this, fz_var. By calling fz_var(w); before the fz_try we can `protect' variable w from such unwanted behaviour.

It's not really necessary to know how this works, but for those interested, a quick explanation. The `loss' of the value occurs because the compiler can postpone writing the value back into the storage location for the variable (or can choose to just hold it in a register). The call to fz_var passes the address of the variable out of scope; this forces the compiler not to hold it in a register. Further, the compiler has no way of knowing whether any functions it calls might access that location, so it needs to make sure that the variable value is written back on every function call - such as longjmp. Hence the variable is magically protected, and is guaranteed not to lose its value, whether an exception is thrown or not.

Calls to fz_var are very low cost (but are not NOPs), so erring on the side of caution and calling fz_var on more than you need to will probably not hurt.