Having all the assertions turned on in production code? (Part II)
First of all, back to basics. Here's the list of most useful top three books I've ever read about assertions (not mentioning the CRT source code ;-)). I intentionally don't include any books about design by contract (DBC), formal methods, and formal verification.
- "Debugging Applications for Microsoft® .NET and Microsoft Windows®" by John Robbins. Open the book on page 85 ("Assert, Assert, Assert, and Assert").
- "Writing Solid Code" by Steve Maguire. Open the book on page 13 ("Assert Yourself").
- "No Bugs!: Delivering Error-Free Code in C and C++" by David Thielen. Open the book on page 39 ("Assert the World"). This book is pretty interesting in this sense that I picked it up for ca $4 from the used bookstore in Bellevue. It was published in 1992 (I was in 10th grade then ;-)) and offers personally for me kind of cool view how the software development at Microsoft was happening then.
Design issues
Let's see how the development will look like when all the assertions would be enabled in release builds and what problems it'll cause and what questions we would need to answer. One of the first questions I would have is: what should happen when assertion fires? Should we:
- Show the user detailed cryptic error message á la "NULL != p->Foo->Bar" and present the user with the classical "Abort, Retry, Ignore" choice? I bet this will confuse everyone and make them either complain about software or call support. Also what about non-interactive software like services for example?
- Don't show user anything technical and use logic similar to Dr. Watson, have a nice dialog explaining to user that some problems happened internally, and can we send the information related to this incident to Microsoft?
- Log all the instances when assertions fail to NT Event Log (or whenever user specifies?) and provide some actionable information if applicable (what it'll be?).
- Just terminate the process because internal assumptions are violated and we can't guarantee that software produces correct results?
- ...
I totally understand the point Niels Ferguson and Bruce Schneier are trying to make - "if internal assumptions are violated in (cryptographic) code then it's no longer safe to continue and it'll be safer just to terminate the current process/operation." For example: let's assume that we have a client and server both written by some financial institution. Server programmer writes an assertion that encryption stream cipher can't be RC2, but doesn't implement specific check in the code because his buddy next door is implementing the client piece of software which never would use RC2." Using the current model where assertions are only enabled in debug builds and somebody will implement a fake client and there's nothing in server code preventing the usage of RC2 we may have introduced a security hole. You can replace “this RC2 thing” with any other assumption you're making, but not writing specific error handler for.
Performance price
Nothing is free. Especially in the server world. The very simplistic model for an assertion is following:
if (SomeLogicalConditionIsViolated)
{
DisplayMessageToUser();
AskForDecisionFromUser();
PerformSomeAction();
}
The typical usages of assertions are for example internal method which validates all its arguments. We're pretty sure that caller honors the contact, but at the same time we're paranoid and we want to make sure that at least in debug builds the violation of the contract is caught immediately.
private bool SomeInternalMethod(String s, Int32 i)
{
Debug.Assert(null != s, "null != s");
Debug.Assert(0 < s.Length, "0 < s.Length");
Debug.Assert(0 < i, "0 < i");
...
return true;
}
Now let's assume for a moment that this is a code in the XML parser and the function is called for every node in parsing tree. Are we still willing to have all this error checking in place and pay the price for these function calls? It gets even better, I've written enough code in my life which performs complex calculations or pointer manipulation and I usually want to make sure that after iteration of the cycle the invariant is correct.
for (Int32 i = 0; i < nNodeCount; ++i)
{
...
Debug.Assert(true == VerifyTreeStructure());
}
Am I still allowed to pay this performance price?
Psychological issues
I don't pretend to be expert in programming or in designing secure software, but I already see a couple of things I don't like:
- If we would establish an absolute rule that every assertion must be enabled in production code then for every pre-condition, invariant or post-condition the programmer will write he/she will have this set of questions: "Can I afford this performance-wise?", "If this assertion is violated should my application really terminate?" I bet that there'll be number of instances when people just won't use assertions so liberally anymore.
- Probably after doing it some time we'll be back again on square one after somebody will propose that we should have "release assertions" and "debug assertions" which won't be much different from the situation currently where every decent developer is handling all the cases which may go wrong and writing assertions for validating the contracts. Then somebody eventually will raise the Orwellian question- should all assertions be handled equivalently? Are there some assertions which we should mark as "more important" than others?
These are my thoughts on subject and I'll be very interested to hear somebody else's opinion(s).
P. S. Criticizing kind of feels good; you can just bash everything and not offer any solutions. I think I'll soon start writing unskilled book reviews ;-)