blob: 6c26c97e0e92941403403c995ff2edf8244b2b7c [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZTEST_FUZZTEST_INTERNAL_ANY_H_
#define FUZZTEST_FUZZTEST_INTERNAL_ANY_H_
#include <cstddef>
#include <tuple>
#include <type_traits>
#include <utility>
#include "./fuzztest/internal/logging.h"
#include "./fuzztest/internal/meta.h"
namespace fuzztest::internal {
// Base class for both implementations of Any below, and should not be used
// directly. The caller to the constructor decides if they want a copy operation
// or not.
class AnyBase {
public:
template <typename T>
explicit AnyBase(std::in_place_t, std::true_type, T* value) {
static constexpr VTable kVTable = {type_id<T>, DestroyImpl<T>, CopyImpl<T>};
vtable_ = &kVTable;
value_ = const_cast<void*>(static_cast<const void*>(value));
}
template <typename T>
explicit AnyBase(std::in_place_t, std::false_type, T* value) {
static constexpr VTable kVTable = {type_id<T>, DestroyImpl<T>, nullptr};
vtable_ = &kVTable;
value_ = const_cast<void*>(static_cast<const void*>(value));
}
bool has_value() const {
FUZZTEST_INTERNAL_CHECK((vtable_ == nullptr) == (value_ == nullptr),
"Inconsistent state between value and vtable.");
return value_ != nullptr;
}
template <typename T>
bool Has() const {
return has_value() && vtable_->type_id == type_id<T>;
}
template <typename T>
T& GetAs() & {
FUZZTEST_INTERNAL_CHECK_PRECONDITION(has_value(), "Object is empty!");
FUZZTEST_INTERNAL_CHECK_PRECONDITION(Has<T>(), "Wrong type!");
return *static_cast<T*>(value_);
}
template <typename T>
const T& GetAs() const& {
FUZZTEST_INTERNAL_CHECK_PRECONDITION(has_value(), "Object is empty!");
FUZZTEST_INTERNAL_CHECK_PRECONDITION(Has<T>(), "Wrong type!");
return *static_cast<T*>(value_);
}
template <typename T>
T&& GetAs() && {
FUZZTEST_INTERNAL_CHECK_PRECONDITION(has_value(), "Object is empty!");
FUZZTEST_INTERNAL_CHECK_PRECONDITION(Has<T>(), "Wrong type!");
return std::move(*static_cast<T*>(value_));
}
template <typename T>
const T&& GetAs() const&& {
FUZZTEST_INTERNAL_CHECK_PRECONDITION(has_value(), "Object is empty!");
FUZZTEST_INTERNAL_CHECK_PRECONDITION(Has<T>(), "Wrong type!");
return std::move(*static_cast<const T*>(value_));
}
protected:
AnyBase() : vtable_(nullptr), value_(nullptr) {}
AnyBase(AnyBase&& other) noexcept
: vtable_(std::exchange(other.vtable_, nullptr)),
value_(std::exchange(other.value_, nullptr)) {}
AnyBase& operator=(AnyBase&& other) {
if (this == &other) return *this;
Destroy();
vtable_ = std::exchange(other.vtable_, nullptr);
value_ = std::exchange(other.value_, nullptr);
return *this;
}
~AnyBase() { Destroy(); }
void Destroy() {
if (has_value()) vtable_->destroy(value_);
}
void CopyFrom(const AnyBase& other) {
FUZZTEST_INTERNAL_CHECK(!has_value(), "CopyFrom called on a full object");
if (other.has_value()) {
vtable_ = other.vtable_;
value_ = vtable_->copy(other.value_);
}
}
private:
struct VTable {
TypeId type_id;
void (*destroy)(void*);
void* (*copy)(void*);
};
template <typename T>
static void DestroyImpl(void* p) {
delete static_cast<T*>(p);
}
template <typename T>
static void* CopyImpl(void* p) {
return new T(*static_cast<T*>(p));
}
const VTable* vtable_;
void* value_;
};
// These classes are similar to `std::any` but we implement our own because:
// - We need a move only implementation of it for certain cases (MoveOnlyAny),
// and `std::any` requires copyability.
// - The implicit conversions of `std::any` are dangerous and easy to use
// wrong. The conversions here are explicit and require an
// `std::in_place_type<T>` tag.
// - `std::any` causes compile time issues when mixed with other generic
// types like std::pair/std::tuple/etc, because of their aggressive SFINAE
// checks.
//
// To access the object you use Has<T>()/GetAs<T>() instead of any_cast.
class MoveOnlyAny : private AnyBase {
public:
template <typename T, typename... U>
explicit MoveOnlyAny(std::in_place_type_t<T>, U&&... args)
: AnyBase(std::in_place, std::false_type{},
new T(std::forward<U>(args)...)) {}
MoveOnlyAny() = default;
MoveOnlyAny(const MoveOnlyAny& other) = delete;
MoveOnlyAny(MoveOnlyAny&& other) = default;
MoveOnlyAny& operator=(const MoveOnlyAny& other) = delete;
MoveOnlyAny& operator=(MoveOnlyAny&& other) = default;
~MoveOnlyAny() = default;
using AnyBase::GetAs;
using AnyBase::Has;
using AnyBase::has_value;
};
class CopyableAny : private AnyBase {
public:
template <typename T, typename... U>
explicit CopyableAny(std::in_place_type_t<T>, U&&... args)
: AnyBase(std::in_place, std::true_type{},
new T(std::forward<U>(args)...)) {}
CopyableAny() = default;
CopyableAny(const CopyableAny& other) { CopyFrom(other); }
CopyableAny(CopyableAny&& other) = default;
CopyableAny& operator=(const CopyableAny& other) {
*this = CopyableAny(other);
return *this;
}
CopyableAny& operator=(CopyableAny&& other) = default;
~CopyableAny() = default;
using AnyBase::GetAs;
using AnyBase::Has;
using AnyBase::has_value;
};
} // namespace fuzztest::internal
#endif // FUZZTEST_FUZZTEST_INTERNAL_ANY_H_