sinojelly / mockcpp

Two C/C++ testing tools, mockcpp and testngpp.
Apache License 2.0
66 stars 39 forks source link

ProcStub may cause coredump when invoke a function be mocked concurrently #57

Open pottik opened 5 months ago

pottik commented 5 months ago

I found this problem in my job, to reproduce that problem, the simplified model is as follows:

int echo()
{
    return 0;
}

TEST(ProcStubTest, test1)
{
    MOCKER(echo)
        .stubs()
        .will(invoke(+[]{
            return 1;
        }));
    std::vector<std::thread> thds;
    for ( int i = 0; i < 4; i++ )
    {
        thds.emplace_back([]{
            for ( int i = 0; i < 100; i++ )
            {
                EXPECT_EQ(1, echo());
            }
        });
    }
    for ( auto&& t : thds )
    {
        t.join();
    }
    GlobalMockObject::verify();
}

problem: when invoke the function be mocked concurrently,the operator= of result (member of ProcStub object) will be invoked concurrently, however, it's not thread safe, may cause double free/accessing freed memory etc.. image image image

In order to test my conclusion, I add a mutex for ProcStub as follows:

MOCKCPP_NS_START
template <typename F>
struct ThreadSafeProcStub;
#define THREAD_SAFE_PROC_STUB(n)                                                              \
template <typename R DECL_TEMPLATE_ARGS(n)>                                                   \
struct ThreadSafeProcStub<R(DECL_ARGS(n))> : public ProcStubBase                              \
{                                                                                             \
    public:                                                                                   \
        typedef R (*Func)(DECL_ARGS(n));                                                      \
        ThreadSafeProcStub(Func f, std::string name) : ProcStubBase(name, (void*)f), func(f){}\
        Any& invoke(const Invocation& inv)                                                    \
        {                                                                                     \
            SIMPLE_REPEAT(n, MOCKCPP_CHECK_AND_ASSIGN_PARAMETER);                             \
            Any tmp = func(DECL_PARAMS(n));                                                   \
            std::lock_guard<std::mutex> lck{mtx};                                             \
            results.emplace_back(tmp);                                                        \
            return results.back();                                                            \
        }                                                                                     \
    private:                                                                                  \
        Func            func;                                                                 \
        std::mutex      mtx;                                                                  \
        std::list<Any>  results;                                                              \
};                                                                                            \
template <typename R DECL_TEMPLATE_ARGS(n)>                                                   \
Stub* invoke_thread_safe(R(*f)(DECL_ARGS(n)), const char* name = 0)                           \
{                                                                                             \
    return new ThreadSafeProcStub<R(DECL_ARGS(n))>(f, name ? name : "");                      \
}
THREAD_SAFE_PROC_STUB(0);
THREAD_SAFE_PROC_STUB(1);
THREAD_SAFE_PROC_STUB(2);
THREAD_SAFE_PROC_STUB(3);
THREAD_SAFE_PROC_STUB(4);
THREAD_SAFE_PROC_STUB(5);
THREAD_SAFE_PROC_STUB(6);
THREAD_SAFE_PROC_STUB(7);
THREAD_SAFE_PROC_STUB(8);
THREAD_SAFE_PROC_STUB(9);
THREAD_SAFE_PROC_STUB(10);
THREAD_SAFE_PROC_STUB(11);
THREAD_SAFE_PROC_STUB(12);
MOCKCPP_NS_END

int echo()
{
    return 0;
}

TEST(ProcStubTest, test1)
{
    MOCKER(echo)
        .stubs()
        .will(invoke_thread_safe(+[]{
            return 1;
        }));
    std::vector<std::thread> thds;
    for ( int i = 0; i < 4; i++ )
    {
        thds.emplace_back([]{
            for ( int i = 0; i < 100; i++ )
            {
                EXPECT_EQ(1, echo());
            }
        });
    }
    for ( auto&& t : thds )
    {
        t.join();
    }
    GlobalMockObject::verify();
}

It seems works and no longer cause coredump(although the implementation may not be so graceful).