blob: 34cbb6ca35158fbb7b2b449904efeb090594f66c [file] [log] [blame] [edit]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "support/internal/slot.h"
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/base/attributes.h"
#include "absl/log/check.h"
namespace crubit {
namespace {
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
constexpr int kUninitializedState = -1;
constexpr int kMoveConstructedFromState = -2;
constexpr int kMoveAssignedFromState = -3;
constexpr int kRelocatedState = -4;
constexpr int kDestroyedState = -5;
struct ABSL_ATTRIBUTE_TRIVIAL_ABI MonitoringHelper {
MonitoringHelper() = delete;
MonitoringHelper(const MonitoringHelper&) = delete;
MonitoringHelper& operator=(const MonitoringHelper&) = delete;
explicit MonitoringHelper(int new_state, std::vector<int>* destroyed_states,
std::vector<void*>* destroyed_locations)
: state(new_state),
destroyed_states(destroyed_states),
destroyed_locations(destroyed_locations) {
CHECK_NE(state, kMoveConstructedFromState);
CHECK_NE(state, kMoveAssignedFromState);
CHECK_NE(state, kRelocatedState);
CHECK_NE(state, kDestroyedState);
}
MonitoringHelper(MonitoringHelper&& other) {
state = other.state;
destroyed_states = other.destroyed_states;
destroyed_locations = other.destroyed_locations;
other.state = kMoveConstructedFromState;
}
MonitoringHelper& operator=(MonitoringHelper&& other) {
// `operator=` runs on an initialized object, and receives an initialized
// object (but either may be moved-from).
CHECK_NE(state, kUninitializedState);
CHECK_NE(state, kRelocatedState);
CHECK_NE(state, kDestroyedState);
CHECK_NE(other.state, kUninitializedState);
CHECK_NE(other.state, kRelocatedState);
CHECK_NE(other.state, kDestroyedState);
// 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 = kMoveAssignedFromState;
return *this;
}
~MonitoringHelper() {
// The destructor runs on an initialized object (but it may be moved-from).
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<void*>* destroyed_locations;
};
// The same as MonitoringHelper, but with an UnsafeRelocateTag constructor.
struct ABSL_ATTRIBUTE_TRIVIAL_ABI RelocatableMonitoringHelper
: public MonitoringHelper {
explicit RelocatableMonitoringHelper(int new_state,
std::vector<int>* destroyed_states,
std::vector<void*>* destroyed_locations)
: MonitoringHelper(new_state, destroyed_states, destroyed_locations) {}
RelocatableMonitoringHelper(crubit::UnsafeRelocateTag,
RelocatableMonitoringHelper&& other)
: MonitoringHelper(std::move(other)) {
other.state = kRelocatedState;
}
};
template <typename T>
class SlotTest : public testing::Test {};
using MyTypes = ::testing::Types<MonitoringHelper, RelocatableMonitoringHelper>;
TYPED_TEST_SUITE(SlotTest, MyTypes);
TYPED_TEST(SlotTest, Test) {
std::vector<int> destroyed_states;
std::vector<void*> destroyed_locations;
constexpr int kInitialValue = 1;
constexpr int kReturnedValue = 2;
TypeParam return_value(kInitialValue, &destroyed_states,
&destroyed_locations);
{
// At this point `slot` is in an uninitialized state.
Slot<TypeParam> slot;
TypeParam* 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, IsEmpty());
EXPECT_THAT(destroyed_locations, IsEmpty());
// Initialize the memory.
new (slot_ptr)
TypeParam(kReturnedValue, &destroyed_states, &destroyed_locations);
EXPECT_THAT(destroyed_states, IsEmpty());
EXPECT_THAT(destroyed_locations, IsEmpty());
// Move the return value from `slot` to `return_value`.
return_value = std::move(slot).AssumeInitAndTakeValue();
if constexpr (std::is_same_v<TypeParam, MonitoringHelper>) {
// Move assignment will destroy fields of the original lhs value that gets
// overwritten by the assignment - this is where `kInitialValue` comes
// from.
//
// AssumeInitAndTakeValue will destroy `Slot::value_` in a
// kMovedAwayState. This is asserted by checking that `slot_ptr` is
// covered by `destroyed_locations`.
//
// Additionally, the temporary return value of AssumeInitAndTakeValue
// will be destroyed (in a kMoveAssignedFromState).
EXPECT_THAT(destroyed_states,
ElementsAre(kMoveConstructedFromState, kInitialValue,
kMoveAssignedFromState));
EXPECT_THAT(destroyed_locations, ElementsAre(slot_ptr, &return_value, _));
} else {
// When there is an UnsafeRelocateTag constructor, we don't need the moved
// value inside of AssumeInitAndTakeValue. So the only destroyed values
// are the assignment target and the temporary.
EXPECT_THAT(destroyed_states, ElementsAre(kInitialValue, _));
EXPECT_THAT(destroyed_locations, ElementsAre(&return_value, _));
}
EXPECT_EQ(kReturnedValue, return_value.state);
}
EXPECT_EQ(kReturnedValue, return_value.state);
}
} // namespace
} // namespace crubit