eranpeer / FakeIt

C++ mocking made easy. A simple yet very expressive, headers only library for c++ mocking.
MIT License
1.22k stars 170 forks source link

Fakeit unit test spyThenVerifyValueArg() throwing a debug assert in string destructor in getOriginalMethodCopyArgsInternal() #279

Closed malcolmdavey closed 1 year ago

malcolmdavey commented 2 years ago

Seems my build is detecting a bad string in the Fakeit unit tests. Haven't worked out the fix yet.

In std::function<R(arglist&...)> getOriginalMethodCopyArgsInternal(int) it's hitting a string destructor but the pointer is 0xcccccccc implying it isn't initialised, so maybe an invalid piece of memory has been cast to a string.

    void spyThenVerifyValueArg()
    {
        SomeClass obj;
        Mock<SomeClass> mock(obj);
        Spy(Method(mock,funcTakeByValue));

        SomeClass &i = mock.get();
        ASSERT_EQUAL("str_arg", i.funcTakeByValue("str_arg")); // bug detected here

        Verify(Method(mock,funcTakeByValue).Using("str_arg")).Once();
    }

call stack

    all_tests.exe!std::_Deallocate(void * _Ptr, unsigned __int64 _Count, unsigned __int64 _Sz) Line 99  C++
    all_tests.exe!std::allocator<char>::deallocate(char * _Ptr, unsigned __int64 _Count) Line 721   C++
    all_tests.exe!std::_Wrap_alloc<std::allocator<char>>::deallocate(char * _Ptr, unsigned __int64 _Count) Line 988 C++
    all_tests.exe!std::string::_Tidy(bool _Built, unsigned __int64 _Newsize) Line 2260  C++
    all_tests.exe!std::string::~basic_string<char,std::char_traits<char>,std::allocator<char>>() Line 1017  C++
>   all_tests.exe!fakeit::MockImpl<SpyingTests::SomeClass>::MethodMockingContextImpl<std::string,std::string>::getOriginalMethodCopyArgsInternal::__l2::<lambda>(std::string & <args_0>) Line 189   C++
    [External Code] 
    all_tests.exe!fakeit::apply_func<0>::applyTuple<std::string,std::string,std::string,std::function<std::string __cdecl(std::string & __ptr64)> & __ptr64>(std::function<std::string __cdecl(std::string &)> & f, std::tuple<std::string> & __formal, std::string & <args_0>) Line 26 C++
    all_tests.exe!fakeit::apply_func<1>::applyTuple<std::string,std::string,std::function<std::string __cdecl(std::string & __ptr64)> & __ptr64>(std::function<std::string __cdecl(std::string &)> & f, std::tuple<std::string> & t) Line 18    C++
    all_tests.exe!fakeit::TupleDispatcher::applyTuple<std::string,std::string,std::function<std::string __cdecl(std::string & __ptr64)> & __ptr64>(std::function<std::string __cdecl(std::string &)> & f, std::tuple<std::string> & t) Line 34  C++
    all_tests.exe!fakeit::TupleDispatcher::invoke<std::string,std::string,std::function<std::string __cdecl(std::string & __ptr64)> & __ptr64>(std::function<std::string __cdecl(std::string &)> & func, const std::tuple<std::string> & arguments) Line 40 C++
    all_tests.exe!fakeit::ReturnDelegateValue<std::string,std::string>::invoke(const std::tuple<std::string> & args) Line 99    C++
    all_tests.exe!fakeit::ActionSequence<std::string,std::string>::handleMethodInvocation(std::tuple<std::string> & args) Line 53   C++
    all_tests.exe!fakeit::RecordedMethodBody<std::string,std::string>::MatchedInvocationHandler::handleMethodInvocation(std::tuple<std::string> & args) Line 44 C++
    all_tests.exe!fakeit::RecordedMethodBody<std::string,std::string>::handleMethodInvocation(std::string & <args_0>) Line 140  C++
    all_tests.exe!fakeit::MethodProxyCreator<std::string,std::string>::methodProxy(unsigned int id, std::string & <args_0>) Line 45 C++
    all_tests.exe!fakeit::MethodProxyCreator<std::string,std::string>::methodProxyX<23>(std::string <args_0>) Line 50   C++
    all_tests.exe!SpyingTests::spyThenVerifyValueArg() Line 218 C++
    all_tests.exe!tpunit::TestFixture::tpunit_detail_do_method(tpunit::TestFixture::method * m) Line 419    C++
    all_tests.exe!tpunit::TestFixture::tpunit_detail_do_tests(tpunit::TestFixture * f) Line 444 C++
    all_tests.exe!tpunit::TestFixture::tpunit_detail_do_run() Line 403  C++
    all_tests.exe!tpunit::Tests::run() Line 487 C++
    all_tests.exe!main() Line 31    C++
FranckRJ commented 2 years ago

Which compiler did you used ?

malcolmdavey commented 2 years ago

Visual Studio - toolset v140 (2015) compiler. Debug x64 .

malcolmdavey commented 1 year ago

Just noticed that the PR builds for Fakeit are not running the tests for the windows compile. The crash happens inside this function, inside the lambda call, after m() has been called, but before the lambda has returned.

It seems to move the return result from m() into a new string before returning from the function, but then when it calls the destructor for the old string, the address is just wrong, and it crashes. Still haven't worked out the exact cause.

            template<typename ... T, typename std::enable_if<all_true<std::is_copy_constructible<T>::value...>::value, int>::type = 0>
            std::function<R(arglist&...)> getOriginalMethodCopyArgsInternal(int) {
                void *mPtr = MethodMockingContextBase<R, arglist...>::_mock.getOriginalMethod(_vMethod);
                C * instance = &(MethodMockingContextBase<R, arglist...>::_mock.get());
                return [=](arglist&... args) -> R {
                    auto m = union_cast<typename VTableMethodType<R,arglist...>::type>(mPtr);
                    return m(instance, args...);  <<< crashes here in string destructor after call to m()
                };
            }
FranckRJ commented 1 year ago

Should be fixed by https://github.com/eranpeer/FakeIt/pull/312, it will be available in FakeIt 2.3.3 or 2.4.0 depending on which is the first to be released.