For the last couple of days, I have been playing around with the Lua C API and have been writing a thin wrapper library for C++. The main purpose of this auxiliary library is to ensure that global interpreter resources such as the global state or the execution stack are kept consistent in the presence of exceptions — and, in particular, that none of these are leaked due to programming mistakes when handling error codes.

To illustrate this point, let's forget about Lua and consider a simpler case. Suppose we lost the ability to pass arguments and return values from functions in C++ and all we have is a stack that we pass around. With this in mind, we could implement a multiply function as follows:
void multiply(std::stack< int >& context) {
const int arg1 = context.top();
context.pop();
const int arg2 = context.top();
context.pop();
context.push(arg1 * arg2);
}
And we could call our function as this:
std::stack< int > context;
context.push(5);
context.push(6);
multiply(context);
const int result = s.top();
s.pop();
In fact, my friends, this is more-or-less what your C/C++ compiler is internally doing when converting code to assembly language. The way the stack is organized to perform calls is known as the calling conventions of an ABI (language/platform combination).

Anyway, back to our point. One important property of such a stack-based system is that any function that deals with the stack must leave it in a consistent state: if the function pushes temporary values (read: local variables) into the stack, such temporary values must be gone upon return no matter how the function terminates. Otherwise, the caller will not find the stack as it expects, which will surely cause trouble at a later stage. The above example works just fine because our function is extremely simple and does not put anything on the stack.

But things get messier when our functions can fail halfway through, and, in particular, if such failures are signaled by exceptions. In these cases, the function will abort abruptly and the function must take care to clean up any values that may still be left on the stack. Let's consider another example:
void magic(std::stack< int >& context) {
const int arg1 = context.top();
context.pop();
const int arg2 = context.top();
context.pop();

context.push(arg1 * arg2);
context.push(arg1 / arg2);
try {
... do something with the two values on top ...

context.push(arg1 - arg2);
try {
... do something with the three values on top ...
} catch (...) {
context.pop(); // arg1 - arg2
throw;
}
context.pop();
} catch (...) {
context.pop(); // arg1 / arg2
context.pop(); // arg1 * arg2
throw;
}
context.pop();
context.pop();
}
The above is a completely fictitious and useless function, but serves to illustrate the point. magic() starts by pushing two values on the stack and then performs some computation that reads these two values. It later pushes an additional value and does some more computations on the three temporary values that are on the top of the stack.

The "problem" is that the computation code can throw an exception. If it does, we must sanitize the stack to remove the two or three values we have already pushed. Otherwise, the caller will receive the exception, it will assume nothing has happened, and will leak values on the stack (bad thing). To prevent this, we have added a couple of try/catch clauses to capture these possible exceptions and to clean up the already-pushed values before exiting the function. Unfortunately, this gets old very quickly: having to add try/catch statements surrounding every call is boring, ugly, and hard to read (remember that, potentially, any statement can throw an exception). You can see this in the example above with the two nested try/catch blocks.

To mitigate this situation, we can apply a RAII-like technique to make popping elements on errors completely transparent and automated. If we can make it transparent, writing the code is easier and reading it is trivial; if we can make it automated, we can be certain that our error paths (rarely tested!) correctly clean up any global state. In C++, destructors are deterministically executed whenever a variable goes out of scope, so we can use this to our advantage to clean up temporary values. Let's consider this class:
class temp_stack {
std::stack< int >& _stack;
int _pop_count;

public:
temp_stack(std::stack< int >& stack_) :
_stack(stack_), _pop_count(0) {}

~temp_stack(void)
{
while (_pop_count-- > 0)
_stack.pop();
}

void push(int i)
{
_stack.push(i);
_pop_count++;
}
};
With this, we can rewrite our function as:
void magic(std::stack< int >& context) {
const int arg1 = context.top();
context.pop();
const int arg2 = context.top();
context.pop();

temp_stack temp(context);

temp_stack.push(arg1 * arg2);
temp_stack.push(arg1 / arg2);
... do something with the two values on top ...

temp_stack.push(arg1 - arg2);
... do something with the three values on top ...

// Yes, we can return now. No need to do manual pop()s!
}
Simple, huh? Our temp_stack function keeps track of how many elements have been pushed on the stack. Whenever the function terminates, be it due to reaching the end of the body or due to an exception thrown anywhere, the temp_stack destructor will remove all elements previously registered from the stack. This ensures that the function leaves the global state (the stack) as it was on entry — modulo the function parameters consumed as part of the calling conventions.

So how does all this play together with Lua? Well, Lua maintains a stack to communicate parameters and return values between C and Lua. Such stack can be managed in a similar way with a RAII class, which makes it very easy to write native functions that deal with the stack and clean it up correctly in all cases. I would like to show you some non-fictitious code right now, but it's not ready yet ;-) But when it is, it will be part of Kyua. Stay tuned!

And, to conclude: to make C++ code robust, wrap objects that need manual clean up (pointers, file descriptors, etc.) with small wrapper classes that perform such clean up on destruction. These classes are typically fully inlined and contain a single member field, so they do not impose any performance penalty. But, on the contrary, your code can avoid the need of many try/catch blocks, which are tricky to get right and hard to validate. (Unfortunately, this technique cannot be applied in, e.g. Java or Python, because the execution of the class destructors is completely non-deterministic and not guaranteed to happen whatsoever!)

Go to posts index

Comments from the original Blogger-hosted post: