google / google/googletest

[Bug]: Mutex deadlock when expecting a function call

Open
#4,560 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
39.5k
Forks
10.9k
Avg merge
6d 13h
Merged PRs (30d)
1

Description

### Describe the issue

A test started freezing after a code change. I traced it to a recursive attempt to lock a mutex (and simplified the code to reproduce).

Picture this scenario:
There is a class that holds a pointer to an interface. Equality between objects of this class is defined in part by data obtained from this interface, such as getting an ID number of the interface implementation. There is also a second class that interacts with the first class. More precisely, the second class has a member function whose parameter is the first class.

Now picture a test suite for the second class. Since the first class is "just" support, it gets mocked in these tests. More precisely, the interface gets mocked. One of the tests in the suite expects that a certain mock will be provided to the member function as a parameter. No special matchers, just simple equality. However, this hangs when the function is called.

### Steps to reproduce the problem

```
#include
#include
#include

/**
* The first ingredient is a type that has a mockable member and whose
* equality operator depends on calling this member.
*/
struct Parameter {
// Instead of a pointer to an interface, this class has a `std::function`.
// It's the same principle, just simpler to write.
std::function toMock;

friend bool operator==(const Parameter& lhs, const Parameter& rhs) {
return lhs.toMock() == rhs.toMock();
}
};

/**
* The second ingredient is a mocked function that takes the first ingredient
* as a parameter.
*/
struct MockInterface {
MOCK_METHOD(void, foo, (Parameter));
};

/**
* We combine the ingredients in a test that expects the function of the
* second ingredient to be called with a specific object of the first
* ingredient.
*/
TEST(GTest, Deadlock) {
// Set up the mock
testing::MockFunction mock;
ON_CALL(mock, Call()).WillByDefault(testing::Return(2));

// Set up the parameter
Parameter data{.toMock = mock.AsStdFunction()};

// Expect the mock to be called with the parameter
MockInterface test;
EXPECT_CALL(test, foo(data)).Times(1);

// Invoke the function.
//
// 1. `FindMatchingExpectationLocked()` is called as part of finding a
// matching expectation. This function locks the `g_gmock_mutex` mutex.
//
// 2. `operator==` is used to compare the given argument to what was
// specified in `EXPECT_CALL`.
//
// 3. `toMock` is invoked, which redirects to the mocked function.
//
// 4. `SetOwnerAndName` is called as part of invoking the mocked function.
// This function also locks the `g_gmock_mutex` mutex. Well, tries to.
// Deadlock!
test.foo(data);
}
```

### What version of GoogleTest are you using?

1.14.0

### What operating system and version are you using?

Linux

### What compiler and version are you using?

gcc version 11.4.1

### What build system are you using?

cmake version 3.26.5

### Additional context

_No response_

Contributor guide

Open the contributing guide

Research direction

Start with the provided Linux/C++ reproducer and trace the expectation matching path through FindMatchingExpectationLocked() and SetOwnerAndName(). Confirm the mutex interactions around the parameter equality callback, then use the reproducer as regression coverage. Done means the expected foo(data) call completes without hanging and still matches once.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
testing-qa
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.