Software Contracts, Part 9: Annotations Outside the Compiler - More Runtime Enforced Annotations

Ok, if it's not become crystal clear that I'm writing this on-the-cuff, this post will finally put a nail in the coffin.

My last post discussed runtime enforced annotations and I used the RPC runtime library as an example of a runtime which enforces contractual annotations.

Of course I totally ignored the single most common example of a runtime enforced contractual annotation in favor of a relatively obscure (but near to my heart) example.

 

Of course, the most common example of a runtime enforced annotation is our old friend ASSERT (and it's brethren).  Assertions are typically used to ensure that invariants are in fact maintained, and a function's contract is always invariant.  Thus assertions can (and IMHO should) be used to enforce contracts. 

Remember my "what's wrong with this code" from yesterday?

:HRESULT DoSomething(handle_t BindingHandle, const wchar_t *StringParam, int *ReturnedValue){   if (StringParam == NULL || ReturnedValue == NULL)   {      return E_POINTER;   }   <DoSomething>}:

I asserted that the error check was incorrect because the RPC contract ensured that the StringParam and ReturnedValue parameters wouldn't be null.  Norman Diamond called me on it because it was possible that the function would be called incorrectly from inside the module.  Ultimately, he's right, there SHOULD be a check.  But it doesn't deserve to be a full-on check which returns an error, so the check ends up being just dead code.

Dead code is bad for two reasons:

First off, dead code can conceivably be used as an attack vehicle - because it's never been executed, it's never been tested, and a attacker might be able to find a way to use that dead code to exploit some vulnerability.

But more importantly, dead code increases the number of code paths through your system.  One way of measuring the effectiveness of your test strategy is to measure the number of code paths that are executed by your test suites - in theory, the more code paths executed by the tests, the higher the quality of the tests.  But if you have dead code (which thus cannot be executed by the tests), the dead code reduces the code coverage numbers for your tests, leading you to believe that (on this metric) your tests are worse than they actually are.

It's far better to enforce the contract for a situation like this with an assertion.

:

HRESULT DoSomething(handle_t BindingHandle, const wchar_t *StringParam, int *ReturnedValue)
{
   _ASSERT(StringParam != NULL && ReturnedValue != NULL);
   <DoSomething>
}
:

This is a far better implementation - it uses a runtime assertion to enforce the contract, but it doesn't increase the number of code paths.

One of the interesting aspects of assertions as a mechanism for runtime contract enforcement is that they normally aren't checked in all cases.  Instead the assertion is usually present only on debug builds.  Thus assertions don't affect the performance of (and number of code branches in) retail builds.

 

Next: Other kinds of annotations used to allow for contract enforcement.