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

Multiple inheritance in interfaces leads to segmentation faults with GMock #74

Open
mstaz opened this issue Apr 6, 2022 · 3 comments
Open

Comments

@mstaz
Copy link
Contributor

mstaz commented Apr 6, 2022

When mocking an interface that inherits from multiple other interfaces, segmentation fault happens on EXPECT_CALL or call itself.
Example:

struct a
{
	virtual ~a() = default;
	virtual void func_a() = 0;
};
struct b
{
	virtual ~b() = default;
	virtual void func_b() = 0;
};
struct ab : public a, public b
{
	~ab() override = default;
};

auto mock = std::make_shared<testing::StrictGMock<ab>>();
std::shared_ptr<ab> obj = object(mock);
EXPECT_CALL(*mock, (func_a)()); // works as expected
obj->func_a(); // works as expected
EXPECT_CALL(*mock, (func_b)()); // may already crash
obj->func_b(); // latest here crash happens

When mocking or calling a function on the second interface a crash happens. I played around a little bit and observed crashes on EXPECT_CALL as well as on function call itself.
To me it seems that offset calculation for vtable doesn't work reliable for such cases. However unfortunately I couldn't find the exact reason for the issue. It may also depend on the used compiler. I observed the issue with clang-13.

This may be a duplicate of issue #12, however I'm unsure as the description is not very detailed there.

@patrickroberts
Copy link

@mstaz this is addressed in the FAQ:

GMock can't mock classes with multiple or virtual inheritance

Multiple inheritance when using interfaces is much more difficult to support than single inheritance because the mock has to determine not only the offset of the virtual method within the vtable, but the offset of the vtable itself within the group, and doing that is not currently possible to my knowledge without a concrete class to perform the "pointer fixup" and account for the vtable offset.

@mstaz
Copy link
Contributor Author

mstaz commented Apr 6, 2022

I see, thanks for clarification. I wonder if it would be possible to have a compile time check for this then 🤔 But for that I guess it would be necessary to have traits that retrieve the base class which actually implements the function. However if that would be possible I guess also the offset of vtable could be easily calculated, e.g. by calculating the difference to casted pointer.

@patrickroberts
Copy link

patrickroberts commented Apr 6, 2022

I see, thanks for clarification. I wonder if it would be possible to have a compile time check for this then 🤔 But for that I guess it would be necessary to have traits that retrieve the base class which actually implements the function. However if that would be possible I guess also the offset of vtable could be easily calculated, e.g. by calculating the difference to casted pointer.

"Calculating the difference to the casted pointer", or "pointer fixup" as I mentioned before, is based on the virtual table construction when constructing an instance of a type derived from the respective secondary base interface you want to cast to. Unfortunately you can't just construct an arbitrary pointer to a derived interface and cast it to a base interface to compute the offset, because the pointer fixup depends on the virtual table being initialized at the address of the pointer value. Because of this, you need an actual concrete class in order to perform the correct pointer fixup. See demo.

However, it might be possible to use the Itanium C++ ABI (implemented by both gcc and clang) to walk the ABI-specific derived types of std::type_info and compute the vtable offset at runtime for each derived/base interface relationship. Note that supporting multiple inheritance would be a substantial code change in GMock because it currently only constructs one vtable for the mocked interface, while properly supporting multiple inheritance would require construction of the entire virtual table group for a derived interface.

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