mat007 / turtle

C++ mock object library for Boost

Home Page:http://turtle.sourceforge.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

is_valid does not allow lvalue references

Flamefire opened this issue · comments

BOOST_PP_ENUM(MOCK_NUM_ARGS, MOCK_RV_REF_ARG, _) ) const
makes it impossible to have functions taking e.g. const Foo& as this fails with

error: non-const lvalue reference to type '::boost::rv<const Foo&>' cannot bind to a value of unrelated type 'const Foo'

I guess that should just be regular params as per the signature?

Hi,

Do you have a sample code which fails? What compiler are you using?

Just mock a function bool handle(const Foo&) and pass it a reference (lvalue). Using C++98 obviously

But to be exact the code is invalid if I did not misunderstood it. Assume the above function as virtual in a base class (or just that this has to be mocked). Then the mock macro will create a wrapper handle_moc(const Foo& t){ ...; ...->is_valid(boost::forward<const Foo&>(t)); ...} and that is_valid is defined as is_valid(Foo&& a).

As can be seen is_valid only takes rvalue references although it should simply take whatever the mocked function takes. I even think the forward call is wrong:

  • handle(const Foo&) -> forwarded as const Foo&
  • handle(Foo&&) -> forwarded as Foo&&
  • handle(Foo) -> forwarded as Foo&&

Hence you are passing an instance (copy) as an rvalue, so consumers could move from it effectively destroying it. As only internal functions are called, this should not be a problem as they don't make use of that, but what if any of those internal functions decides to do valueCopy(forward(value)) and value is a smart pointer/vector...?

A possible bug is e.g. here: Values are forwarded to is_valid(

) and then again to functor if present (
BOOST_PP_ENUM(MOCK_NUM_ARGS, MOCK_FORWARD, _) );
). Hence the 2nd call might receive a moved-from value!

This is even worse than I thought. There are many forward and move calls in there and I think almost all of them are wrong.

From my understanding:

  • The mocked function signature should match exactly the real function signature
  • Arguments processed from there shall be passed by reference only (either const, or non-const ref depending in const-ness of the param)
  • Only valid move would be the capture/receive function
    • Note that this happens in the is_valid call and hence will make a passed functor fail here
    • Hence the is_valid should NEVER alter its arguments and only receive them by const-ref
    • The capturing should happen in the functor instead

I experimented a lot with this now and got so far that my GCC 5 compiles and validates it in C++98 mode except for 1 example which I have to look into

BUT: We can make this much easier: An essential part of this is boost::function which is used in the action class. This does NOT support move-only types (I created a test using Boost.Move in C++98) So you cannot create a boost::function with a signature taking an instance of a move-only type which means you cannot mock such a function.

So there is a choice to make now @mat007 :

  1. Do not support Move-Only types for C++98 and only conditionally use T&& instead of BOOST_RV_REF which would solve this issue too
  2. Rework the parameter passing now. As explained above unconditionally taking rvalue references is wrong. It should rather be a reference only in all classes except functor and function. And only 1 instance shall receive the rvalue reference (if it is moveable, ref otherwise)

So the Signature template param would need to be changed. IMO this would be the right way to go but is probably quite some work.

Any idea?

Thanks for diving into this @Flamefire !

I don't really mind deprecating support for 'old' stuff if this makes things simpler.
I honestly don't know if anyone still uses all that Boost move emulation, my take would be that it was merely during the transition phase when we played with move when it was first introduced…
Today if someone really wants move support they'll likely upgrade their compiler to at least C++11 instead of bothering with a partial move emulation.

So 1. looks appealing and simpler than 2. right?

The problem is that the current code is most likely not correct and definitely wrong in 1 place (possible use of moved from /forwarded value) so one need to (partially) do 2 anyway. Maybe correct usage of the types/moves already solved this problem too.

  1. Would be a quick(er) fix but would prevent usage if e.g. boost::unique_ptr as rvalue in interfaces. Not sure if this is possible anyway

@mat007 I solved this in #59 now with a combination of both. All boost 1.58 tests pass in C++11 and 98 now. Need the singleton to pass for boost master but I assume it would.