Content

Not quite a Yegge long.

Mock Objects for C++, Part 5

Saturday 15 November 2008 - Filed under Code

This is the fifth part of an ongoing series on building Mock Object support for C++ by exploiting knowledge of the ABI.

Last time, we looked at how to recover argument and return types from the function signature, via a pile of templates.

The next step serves a dual purpose:

  1. Allowing us to return values of the correct type from the mock function
  2. Allowing us to find the correct offset for the beginning of the parameters on the stack

If you’re not familiar with the ABI for Microsoft’s compiler, that second purpose might not seem important. However, there’s some crucial subtlety here.

Note we’re only going to be dealing with _cdecl functions here. This is not the default for member functions, but it has the practical benefit of not requiring the mock function to clean up the stack. We could hack together a solution for any of the other standard calling conventions, but we’d lose the ability to leave methods without any expectations whatsoever, yet still have them *work*.

So, what’s this nonsense about the offset for the beginning of the parameters? It turns out that the stack layout is subtly different, depending on the return type of the function. Quickly summarizing the rules for returning values under _cdecl:

  1. If the return value is a float, return it in ST(0)
  2. If the return value is less than 8 bytes, and is of a POD type, return it in EAX or EAX:EDX, depending on size. Void functions return a garbage value in EAX.
  3. Otherwise, an extra hidden parameter is pushed after the normal parameters. This is a pointer to a caller-allocated area in the stack, where the return value is to be placed. The pointer is then returned in EAX.

The end result is that depending on whether a return value pointer is needed, the first real parameter either resides at esp+8 or esp+12. Initially, I built this to depend on that knowledge, but it turns out this is an absolute red herring, since we can talk the compiler into doing this stuff for us, with member templates in Vtbl:

template< typename R >
R _cdecl MockFunc( int dummyParam )
{
    int * argsBase = &dummyParam;
    // find, update expectations, check args…

    // todo: find a stored return value to use,
    //     based on the expectation that matched
    return R(); // call default ctor for now
}

That’s obviously not what you really want that function to *do*, but it handles all the compiler’s rules for return values, arg offsets, and whatever else, without needing any hardcoded hackery.

The next step is that as soon as an expectation is bound to a method, you instantiate MockFunc with the correct return type, and put a pointer to that version in the vtable. (Actually, there’s a bit more to it than that, as you’ll see if you read the source, but this is the key idea)

Quick note: In writing this up, a better way of doing parameter matching (less hax!) has come to mind. Expect an update to the published library sources in the next day or so, incorporating that (and some other enhancements I’ve made in the meantime).

2008-11-15  »  admin

Talkback x 2

  1. Thiago
    24 December 2008 @ 10:10 am

    There is another interesting framework, maybe useful in future parts:
    MockItNow – http://www.rorydriscoll.com/mockitnow/

    It has a different approach from AMOP and keep things simple.

  2. Peter Bindels
    24 December 2008 @ 11:10 pm

    As I’ve just reacted to your previous comment, there’s a much nicer way of doing this. Given a number of specializations of a template method, you can take the address of that method instantiated with the given argument types, let it put them in a tuple of sorts and send that along to your mocking base logic. If you also add a constant for each function (__LINE__ was my first guess – added a bug to MS Connect for it too as Microsoft considers it not constant) you can instantiate the template for each function separately and keep them separated (so your match matching will work properly too). That way it also works with stdcall functions, as well as thiscall and fastcall functions.

Share your thoughts

Re: Mock Objects for C++, Part 5







Tags you can use (optional):
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>