Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAD_ACCESS when accessing mock object with CRTP #188

Closed
Hittherhod opened this issue Apr 9, 2020 · 4 comments
Closed

BAD_ACCESS when accessing mock object with CRTP #188

Hittherhod opened this issue Apr 9, 2020 · 4 comments

Comments

@Hittherhod
Copy link

So I have some CRTP code that I would like to mock, but I'm getting a bad access when trying to use the mocked object. My code can be boiled down to:

#include <doctest.h>
#include <doctest/trompeloeil.hpp>

template<typename Derived>
struct Base
{
    unsigned int get()
    {
        return static_cast<Derived*>(this)->get_implementation();
    }
};

struct Derived: Base<Derived>
{
    MAKE_MOCK0(get_implementation, unsigned int());
};

template<typename Derived>
struct Uut
{
    Uut(Base<Derived> _d): derived(_d) {}
    unsigned int do_it() {
        return derived.get();
    }
    Base<Derived> derived;
};

SCENARIO("Test") {
    GIVEN("This") {
        Derived me;
        Uut uut(me);
        WHEN("I do this") {
            REQUIRE_CALL(me, sample_implementation()).RETURN(5);
            THEN("It should be good") {
                REQUIRE(uut.do_it() == 5);
            }
        }
    }
}

I get bad access when the trompeloeil mock calls the find method (that starts at line 3021 in trompeloeil.hpp, specifically at the first instance of i.matches(param_name) on line 3024. This might be related to CRTP mocking as mentioned in #116, but I'm not sure. When I run this in the debugger, I get bad access when trying to access i. Based on what I see in the debugger, it looks like i is a valid call_matcher_base.

What makes this really weird is if I have the following test case:

SCENARIO("Test") {
    GIVEN("This") {
        Derived me;
        WHEN("I do this") {
            THEN("It should be good") {
                REQUIRE_CALL(me, sample_implementation()).RETURN(5);
                me.sample();
            }
        }
    }
}

This works without issue. It seems like the issue is storing the mock object and then using it makes something weird happen.

@rollbear
Copy link
Owner

rollbear commented Apr 9, 2020

OK. Long Easter weekend coming up, so I'll have plenty of time to look into it. Thanks for reporting.

@rollbear
Copy link
Owner

rollbear commented Apr 9, 2020

Hmm, you're slicing your object. Uut::derived is a copy of the Base part of your Derived object, so the cast in the CRTP implementation calls a Derived member function for an object that is not of type Derived.

Here's a very simple replication.

template <typename Derived>
struct base
{
  int func() {
    return static_cast<Derived*>(this)->f();
  }
};

struct D : base<D>
{
  int i = 3;
  int f() {
    return i;
  }
};

int func(base<D> obj) // note! copy of the base part of the full D object.
{
  return obj.func();
}

int main()
{
  D d;
  return func(d);
}

This error is caught with gcc/clang address-sanitizer, but its error pinpointing leaves a lot to desire. I would've expected the undefined-sanitizer to complain about the call after the cast, but it does not. Neither complains at all if the implemented function does not touch any member variables.

If you change func to take a reference instead, to avoid the slicing, it works fine.

One thing I do with CRTP classes to avoid this problem, is to make their copy/move constructors protected. Your Derived can still be copied (it can access the protected constructors) but you cannot easily slice the object.

template <typename Derived>
struct base
{
  base() = default;
  int func() {
    return static_cast<Derived*>(this)->f();
  }
protected:
  base(const base&) = default;
  base(base&&) = default;
};

The failing code then gets an error:

t.cpp:35:15: error: calling a protected constructor of class 'base<D>'
  return func(d);
              ^
t.cpp:14:3: note: declared protected here
  base(const base&) = default;
  ^
1 error generated.

Another solution is, of course, to keep the entire Derived object instead of a reference to base.

@rollbear
Copy link
Owner

rollbear commented Apr 9, 2020

Also note that if you change Uut::derived to be of type Derived, it will hold a copy of the object in the test, so you're placing the expectation on one object, and call another. If that is the functionality you need, you have to figure out some other technique for Uut to refer to the same mock object that you place expectations on in the test.

@Hittherhod
Copy link
Author

Okay, so I changed it to accept a pointer to the Base object and now it works as expected. I had thought that CRTP base classes for static polymorphism behaved differently than virtual classes, but all that I found didn't really use it in the way I did. Thank you for all your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants