Software Contracts, Part 8: Annotations outside the compiler - runtime enforced annotations.

Ok, it's taken 7 other posts, but we've finally gotten close to where I wanted to be when I started this series.

Remember my definition of an annotation: An Annotation is an addition to the source code for a program that allows an external translator to enforce the program's contract.

 

My first examples of annotations are annotations that are enforced by the compiler - utilizing a language's type system.  These annotations are highly useful because source code must be passed through the compiler before it's executed, thus allowing the compiler to correct the contract violation before it gets executed.

Sometimes the annotation (and thus the contract) is enforced at runtime.  My favorite example of this occurs with the MIDL compiler.

Here's a quick "what's wrong with this code":

foo.idl:

[    uuid(<uuid>),    version(1.1),    pointer_default(unique),]interface Foo{    HRESULT DoSomething([in] handle_t BindingHandle, [in, string]const wchar_t *StringParam, [out] int *ReturnedValue);}

foo.c:

:

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

The error is that the code in DoSomething is checking for the StringParam and ReturnedValue being NULL.  The contract for the DoSomething function as expressed in the IDL file above specifies that the StringParam and ReturnedValue are not optional[1].

For basic RPC, the RPC runtime library will enforce the functions contract as expressed in the IDL file (I'll talk about COM in a bit).    That means that on the client side, if you attempt to call DoSomething with an invalid parameter, the function will fail (it will raise an RPC exception code).  On the server side, the runtime library guarantees that StringParam points to a null terminated string and it will allocate the storage to hold the ReturnedValue parameter.  If you use the /robust flag (and if at all possible, you should always do this), the RPC runtime library will also protect your call from clients who bypass the client side runtime library - it will filter out all callers who don't provide input that matches the signature[2].

In this example, since the input and output parameters don't have the "unique" or "ptr" attribute, by default they're "ref" pointers[3].  That means that they're always passed by reference, and reference parameters may not be null.  As a result, checking for a null value is pointless, since t can never happen.

 

For COM, it's important to realize that this only happens when the RPC runtime library operates on the parameters.  The thing is, COM doesn't always get its hands on the function parameters.  In general, the RPC runtime library will only see the parameters for the function when the call has to cross a boundary (either an apartment, process, GIT or IDispatch boundary).  If you make a call to a method on an interface within your apartment, then for performance reasons, the RPC runtime library doesn't enter the picture.

Next: Other forms of runtime enforced contracts.

 

[1] For this example, I'm assuming that DoSomething is only called by the RPC runtime library - if it can be called from somewhere else, the checks may be appropriate.

[2] Please note: The client can still provide a StringParam that points to a 500K long string in an attempt to overflow a local buffer - the only thing that RPC ensures is that the input matches the  contract as expressed in the IDL file.  There are other ways of ensuring that the string passed in is "reasonable".

[3] Before I start seeing "Stupid Larry: See there in the interface definition - it says that the pointer default is "unique" - why are you saying that the pointers are "ref"?" in the comments:  It seems stupid, but that's the way it works - as MSDN documents, the pointer_default only applies to indirect pointers.  Top level pointers in parameter lists always default to "ref" pointers.