Refactor `ReturnValueSlot.Test` to avoid reading destroyed objects.
The test wants to check that the object pointed by `slot_ptr` has been
destroyed. Before this CL the test would read the `slot_ptr->state`
field and assert that it is equal to `kDestroyedState`. Reading an
already destroyed object is UB and was caught by MSAN. After this CL,
the assertion is done through `destroyed_locations` instead.
PiperOrigin-RevId: 551219313
Change-Id: I4a5e085ad6a7ae8943721b93e52197fd11245a1c
diff --git a/support/internal/return_value_slot_test.cc b/support/internal/return_value_slot_test.cc
index 06ec89c..6d140a8 100644
--- a/support/internal/return_value_slot_test.cc
+++ b/support/internal/return_value_slot_test.cc
@@ -22,8 +22,11 @@
MonitoringHelper(const MonitoringHelper&) = delete;
MonitoringHelper& operator=(const MonitoringHelper&) = delete;
- explicit MonitoringHelper(int new_state, std::vector<int>* destroyed_states)
- : state(new_state), destroyed_states(destroyed_states) {
+ explicit MonitoringHelper(int new_state, std::vector<int>* destroyed_states,
+ std::vector<MonitoringHelper*>* destroyed_locations)
+ : state(new_state),
+ destroyed_states(destroyed_states),
+ destroyed_locations(destroyed_locations) {
CHECK_NE(state, kMovedAwayState);
CHECK_NE(state, kDestroyedState);
}
@@ -31,6 +34,7 @@
MonitoringHelper(MonitoringHelper&& other) {
state = other.state;
destroyed_states = other.destroyed_states;
+ destroyed_locations = other.destroyed_locations;
other.state = kMovedAwayState;
}
@@ -44,9 +48,11 @@
// Pretend to destroy old field values.
destroyed_states->push_back(state);
+ destroyed_locations->push_back(this);
state = other.state;
destroyed_states = other.destroyed_states;
+ destroyed_locations = other.destroyed_locations;
other.state = kMovedAwayState;
return *this;
}
@@ -56,20 +62,24 @@
CHECK_NE(state, kUninitializedState);
CHECK_NE(state, kDestroyedState);
destroyed_states->push_back(state);
+ destroyed_locations->push_back(this);
state = kDestroyedState;
}
int state;
std::vector<int>* destroyed_states;
+ std::vector<MonitoringHelper*>* destroyed_locations;
};
TEST(ReturnValueSlot, Test) {
std::vector<int> destroyed_states;
+ std::vector<MonitoringHelper*> destroyed_locations;
constexpr int kInitialValue = 1;
constexpr int kReturnedValue = 2;
- MonitoringHelper return_value(kInitialValue, &destroyed_states);
+ MonitoringHelper return_value(kInitialValue, &destroyed_states,
+ &destroyed_locations);
{
// At this point `slot` is in an uninitialized state.
@@ -77,12 +87,16 @@
MonitoringHelper* slot_ptr = slot.Get();
slot_ptr->state = kUninitializedState;
slot_ptr->destroyed_states = &destroyed_states;
+ slot_ptr->destroyed_locations = &destroyed_locations;
// No destructors should run up to this point.
EXPECT_THAT(destroyed_states, testing::IsEmpty());
+ EXPECT_THAT(destroyed_locations, testing::IsEmpty());
// Initialize the memory.
- new (slot_ptr) MonitoringHelper(kReturnedValue, &destroyed_states);
+ new (slot_ptr) MonitoringHelper(kReturnedValue, &destroyed_states,
+ &destroyed_locations);
EXPECT_THAT(destroyed_states, testing::IsEmpty());
+ EXPECT_THAT(destroyed_locations, testing::IsEmpty());
// Move the return value from `slot` to `return_value`.
return_value = std::move(slot).AssumeInitAndTakeValue();
@@ -90,17 +104,16 @@
// overwritten by the assignment - this is where `kInitialValue` comes from.
//
// AssumeInitAndTakeValue will destroy `ReturnValueSlot::value_` in a
- // kMovedAwayState.
+ // kMovedAwayState. This is asserted by checking that `slot_ptr` is covered
+ // by `destroyed_locations`.
//
// Additionally, a temporary `MonitoringHelper` value in a moved-away state
// will be destroyed.
EXPECT_THAT(
destroyed_states,
testing::ElementsAre(kMovedAwayState, kInitialValue, kMovedAwayState));
- // The value inside `ReturnValueSlot` (pointed to by `slot_ptr`) should be
- // in a `kDestroyedState` state at this point due to
- // `AssumeInitAndTakeValue()`.
- EXPECT_EQ(kDestroyedState, slot_ptr->state);
+ EXPECT_THAT(destroyed_locations,
+ testing::ElementsAre(slot_ptr, testing::_, testing::_));
EXPECT_EQ(kReturnedValue, return_value.state);
}