Software Contracts, Part 2 - There are two sides to every contract.

One thing that's critically important to understand when thinking about software contracts is that just like in real life, every contract has at least two sides to it. 

For real world contracts (that is agreements between two or more parties), the contract embodies responsibilities for each party involved in the contract.  For example, if I enter into a contract to buy your house, my responsibility is to pay you the money for the house, your responsibility is to transfer ownership of the house to me.

Just like real world contracts, software contracts typically two sides (they can have more than two sides, but those situations are relatively rare).  Typically each software contract includes responsibilities for both the caller of a function and the function itself.

And it's critically important to understand your responsibilities w.r.t. the contract for a function.  Consider the contract for the GetFileAttributesEx function.  This function takes three parameters: The first is a path to the filename, the second is an infolevel, the third is an lpvoid pointer.

From the point of view of the caller, the GetFileAttributesEx API will retrieve a WIN32_FILE_ATTRIBUTE_DATA structure for the file corresponding to the filename parameter.  The caller knows that the GetFileAttributesEx API will either return a non zero value, in which case the memory pointed to by the lpvoid pointer will be filled in with the WIN32_FILE_ATTRIBUTE_DATA structure (assuming that the infolevel is GetFileExInfoStandard, the only documented infolevel).

However it's important to consider the other side of the contract.  From the point of view of the GetFileAttributesEx API, the caller is required to provide:

  • A valid pointer to a null terminated string that contains the name of the file to check in the filename parameter.
  • The value of GetFileExInfoStandard for the infolevel parameter.
  • A valid pointer to a block of memory that is at least sizeof(WIN32_FILE_ATTRIBUTE_DATA) bytes in length in the lpvoid pointer parameter.

If the caller does not provide all of those pieces of information, then the caller has violated their half of the contract, and the GetFileAttributesEx API is under no obligation to honor it's half of the contract.

For an example like this, both halves of the contract are blindingly obvious, but there are other examples that aren't as obvious.

 

A great example of a situation where a caller unknowingly violated the contract for an API was discovered during testing for Windows Vista.  During one of our test passes of Vista, the unit test application for the PlaySound API started crashing deep inside the bowels of the PlaySound API.  Of course I was asked to investigate (since I own the PlaySound API).

I was mystified by the failure - we were crashing while trying to access the samples for a sound being played.  In addition, the test didn't always fail, which was really quite strange.  I dug a bit deeper into the test application and realized that the test was effectively doing:

memory = LoadResource(resourceId);
PlaySound(memory, SND_MEMORY | SND_ASYNC);
FreeResource(memory);

The problem here is that when the test case called FreeResource memory, they violated a part of the PlaySound contract.  You see, when you call PlaySound with the SND_ASYNC flag and the SND_MEMORY flag together, the PlaySound contract requires that the memory remain valid until the sound has completed playing.

It turns out that this part of the PlaySound contract is NOT explicit - nowhere in the current PlaySound documentation does it say that the memory must remain valid while the sound is being played (as of this writing - I've filed a bug report on it).

But even though this behavior is not explicit, it's still a part of the contract, and must be honored by the caller.