blob: 6d140a8715ed969c8929f7574b60841f4d8a8bf6 [file] [log] [blame]
// 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/return_value_slot.h"
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
namespace crubit {
namespace {
constexpr int kUninitializedState = -1;
constexpr int kMovedAwayState = -2;
constexpr int kDestroyedState = -3;
struct MonitoringHelper {
MonitoringHelper() = delete;
MonitoringHelper(const MonitoringHelper&) = delete;
MonitoringHelper& operator=(const MonitoringHelper&) = delete;
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);
}
MonitoringHelper(MonitoringHelper&& other) {
state = other.state;
destroyed_states = other.destroyed_states;
destroyed_locations = other.destroyed_locations;
other.state = kMovedAwayState;
}
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, kDestroyedState);
CHECK_NE(other.state, kUninitializedState);
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 = kMovedAwayState;
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<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,
&destroyed_locations);
{
// At this point `slot` is in an uninitialized state.
ReturnValueSlot<MonitoringHelper> slot;
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,
&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();
// 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 `ReturnValueSlot::value_` in a
// 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));
EXPECT_THAT(destroyed_locations,
testing::ElementsAre(slot_ptr, testing::_, testing::_));
EXPECT_EQ(kReturnedValue, return_value.state);
}
EXPECT_EQ(kReturnedValue, return_value.state);
}
} // namespace
} // namespace crubit