tvl-depot/absl/base/internal/exception_safety_testing.h
Abseil Team 7990fd459e Export of internal Abseil changes.
--
ee19e203eca970ff88e8f25ce4e19c32e143b988 by Jon Cohen <cohenjon@google.com>:

Exception safety testing no longer uses absl::optional

PiperOrigin-RevId: 220336204

--
460666eb0b316a8b4aeedc589644d53b05251bd1 by Derek Mauro <dmauro@google.com>:

Rework SwissTable SSE2 support
  - Use SSE2 on MSVC when available
    https://github.com/abseil/abseil-cpp/issues/210
  - Emulate _mm_cmpgt_epi8 with other SSE2 instructions when using
    -funsigned-char under GCC
    https://github.com/abseil/abseil-cpp/issues/209

PiperOrigin-RevId: 220312351

--
1f4318ecedf8d539b7b698eb803d613ad6b69278 by Abseil Team <absl-team@google.com>:

Change CollectPerfectRatios to use 10 trials to smooth out the outliers in the
sample.

PiperOrigin-RevId: 220286579

--
6755abc2673553a7f578bb29c6e9ca8d991bc9c8 by Abseil Team <absl-team@google.com>:

Internal change

PiperOrigin-RevId: 220274307

--
8645b6187329ebf0aaf3c2de2888ba44466cd879 by Abseil Team <absl-team@google.com>:

* #endif for a header guard should reference the guard macro in a comment

PiperOrigin-RevId: 220206868

--
3987a7ad11319230910931cd2468b60b3fd1b85c by Gennadiy Civil <misterg@google.com>:

Internal Change

PiperOrigin-RevId: 220136674

--
cc908c1db2ee0d4523dc813e33f600583bb986c5 by Abseil Team <absl-team@google.com>:

absl: fix backoff logic in SpinLockWait

There are 3 bugs in loop variable handling:
1. It starts with 0, but AbslInternalSpinLockDelay ignores loop == 0.
So it does not actually wait when it should.
2. loop is incremented after successful state changes,
but it should not (why would be increase backoff delay after that?).
3. loop is incremented after CAS failures,
but it should not (why would be increase backoff delay after that?).

Use the same handling of loop as used in SpinLock.

PiperOrigin-RevId: 220136079

--
a0a1c6ef5910ebd28e07215d7df03cc0da0b3eed by Abseil Team <absl-team@google.com>:

absl: relax unnecessarily strong memory ordering in SpinLock::SlowLock

We don't need to acquire visibility over anything when setting kSpinLockSleeper.
Replace the confusing and unnecessarily strong memory order with relaxed.

PiperOrigin-RevId: 220023380

--
c50858b51af28b9fca1a62616324f85f3e84ea74 by Tom Manshreck <shreck@google.com>:

Update comments in flat_hash_map, node_hash_{set, map} and the containers developer guide

PiperOrigin-RevId: 219938692

--
e87b7d1a5f61e165b1c44d3b16d8d967197cdfce by CJ Johnson <johnsoncj@google.com>:

Rearranges the public methods of InlinedVector and cleans up the comments

PiperOrigin-RevId: 219896257

--
f3234c466f792e0fc4bfd21fc7919dba5e679375 by CJ Johnson <johnsoncj@google.com>:

Adds branch prediction to exceptional early exit cases of inlined vector's API

PiperOrigin-RevId: 219887173

--
4dfccf1a81ca0425912d3da25a8470f78c532ce4 by CJ Johnson <johnsoncj@google.com>:

Fixes the InlinedVector public interface to use the allocator type references instead of assuming the type
Also cleans up some cruft in formatting and comments

PiperOrigin-RevId: 219878876

--
4bb6a2b892abb10bd6a424db7e94ed8640802470 by Tom Manshreck <shreck@google.com>:

Add comments on constructor and assignment operator support to flat_hash_set

PiperOrigin-RevId: 219825338

--
c23f973e2f7f4feea0da36bf8a9c3f8a8954bb74 by Abseil Team <absl-team@google.com>:

Import of CCTZ from GitHub.

PiperOrigin-RevId: 219823847
GitOrigin-RevId: ee19e203eca970ff88e8f25ce4e19c32e143b988
Change-Id: I288c927ca481dc57340420dbb4c278a05cf15e83
2018-11-06 16:06:39 -05:00

1094 lines
37 KiB
C++

// Copyright 2017 The Abseil Authors.
//
// 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.
// Utilities for testing exception-safety
#ifndef ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_
#define ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_
#include <cstddef>
#include <cstdint>
#include <functional>
#include <initializer_list>
#include <iosfwd>
#include <string>
#include <tuple>
#include <unordered_map>
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/pretty_function.h"
#include "absl/memory/memory.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "absl/utility/utility.h"
namespace testing {
enum class TypeSpec;
enum class AllocSpec;
constexpr TypeSpec operator|(TypeSpec a, TypeSpec b) {
using T = absl::underlying_type_t<TypeSpec>;
return static_cast<TypeSpec>(static_cast<T>(a) | static_cast<T>(b));
}
constexpr TypeSpec operator&(TypeSpec a, TypeSpec b) {
using T = absl::underlying_type_t<TypeSpec>;
return static_cast<TypeSpec>(static_cast<T>(a) & static_cast<T>(b));
}
constexpr AllocSpec operator|(AllocSpec a, AllocSpec b) {
using T = absl::underlying_type_t<AllocSpec>;
return static_cast<AllocSpec>(static_cast<T>(a) | static_cast<T>(b));
}
constexpr AllocSpec operator&(AllocSpec a, AllocSpec b) {
using T = absl::underlying_type_t<AllocSpec>;
return static_cast<AllocSpec>(static_cast<T>(a) & static_cast<T>(b));
}
namespace exceptions_internal {
std::string GetSpecString(TypeSpec);
std::string GetSpecString(AllocSpec);
struct NoThrowTag {};
struct StrongGuaranteeTagType {};
// A simple exception class. We throw this so that test code can catch
// exceptions specifically thrown by ThrowingValue.
class TestException {
public:
explicit TestException(absl::string_view msg) : msg_(msg) {}
virtual ~TestException() {}
virtual const char* what() const noexcept { return msg_.c_str(); }
private:
std::string msg_;
};
// TestBadAllocException exists because allocation functions must throw an
// exception which can be caught by a handler of std::bad_alloc. We use a child
// class of std::bad_alloc so we can customise the error message, and also
// derive from TestException so we don't accidentally end up catching an actual
// bad_alloc exception in TestExceptionSafety.
class TestBadAllocException : public std::bad_alloc, public TestException {
public:
explicit TestBadAllocException(absl::string_view msg) : TestException(msg) {}
using TestException::what;
};
extern int countdown;
// Allows the countdown variable to be set manually (defaulting to the initial
// value of 0)
inline void SetCountdown(int i = 0) { countdown = i; }
// Sets the countdown to the terminal value -1
inline void UnsetCountdown() { SetCountdown(-1); }
void MaybeThrow(absl::string_view msg, bool throw_bad_alloc = false);
testing::AssertionResult FailureMessage(const TestException& e,
int countdown) noexcept;
struct TrackedAddress {
bool is_alive;
std::string description;
};
// Inspects the constructions and destructions of anything inheriting from
// TrackedObject. This allows us to safely "leak" TrackedObjects, as
// ConstructorTracker will destroy everything left over in its destructor.
class ConstructorTracker {
public:
explicit ConstructorTracker(int count) : countdown_(count) {
assert(current_tracker_instance_ == nullptr);
current_tracker_instance_ = this;
}
~ConstructorTracker() {
assert(current_tracker_instance_ == this);
current_tracker_instance_ = nullptr;
for (auto& it : address_map_) {
void* address = it.first;
TrackedAddress& tracked_address = it.second;
if (tracked_address.is_alive) {
ADD_FAILURE() << ErrorMessage(address, tracked_address.description,
countdown_, "Object was not destroyed.");
}
}
}
static void ObjectConstructed(void* address, std::string description) {
if (!CurrentlyTracking()) return;
TrackedAddress& tracked_address =
current_tracker_instance_->address_map_[address];
if (tracked_address.is_alive) {
ADD_FAILURE() << ErrorMessage(
address, tracked_address.description,
current_tracker_instance_->countdown_,
"Object was re-constructed. Current object was constructed by " +
description);
}
tracked_address = {true, std::move(description)};
}
static void ObjectDestructed(void* address) {
if (!CurrentlyTracking()) return;
auto it = current_tracker_instance_->address_map_.find(address);
// Not tracked. Ignore.
if (it == current_tracker_instance_->address_map_.end()) return;
TrackedAddress& tracked_address = it->second;
if (!tracked_address.is_alive) {
ADD_FAILURE() << ErrorMessage(address, tracked_address.description,
current_tracker_instance_->countdown_,
"Object was re-destroyed.");
}
tracked_address.is_alive = false;
}
private:
static bool CurrentlyTracking() {
return current_tracker_instance_ != nullptr;
}
static std::string ErrorMessage(void* address, const std::string& address_description,
int countdown, const std::string& error_description) {
return absl::Substitute(
"With coundtown at $0:\n"
" $1\n"
" Object originally constructed by $2\n"
" Object address: $3\n",
countdown, error_description, address_description, address);
}
std::unordered_map<void*, TrackedAddress> address_map_;
int countdown_;
static ConstructorTracker* current_tracker_instance_;
};
class TrackedObject {
public:
TrackedObject(const TrackedObject&) = delete;
TrackedObject(TrackedObject&&) = delete;
protected:
explicit TrackedObject(std::string description) {
ConstructorTracker::ObjectConstructed(this, std::move(description));
}
~TrackedObject() noexcept { ConstructorTracker::ObjectDestructed(this); }
};
} // namespace exceptions_internal
extern exceptions_internal::NoThrowTag nothrow_ctor;
extern exceptions_internal::StrongGuaranteeTagType strong_guarantee;
// A test class which is convertible to bool. The conversion can be
// instrumented to throw at a controlled time.
class ThrowingBool {
public:
ThrowingBool(bool b) noexcept : b_(b) {} // NOLINT(runtime/explicit)
operator bool() const { // NOLINT
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return b_;
}
private:
bool b_;
};
/*
* Configuration enum for the ThrowingValue type that defines behavior for the
* lifetime of the instance. Use testing::nothrow_ctor to prevent the integer
* constructor from throwing.
*
* kEverythingThrows: Every operation can throw an exception
* kNoThrowCopy: Copy construction and copy assignment will not throw
* kNoThrowMove: Move construction and move assignment will not throw
* kNoThrowNew: Overloaded operators new and new[] will not throw
*/
enum class TypeSpec {
kEverythingThrows = 0,
kNoThrowCopy = 1,
kNoThrowMove = 1 << 1,
kNoThrowNew = 1 << 2,
};
/*
* A testing class instrumented to throw an exception at a controlled time.
*
* ThrowingValue implements a slightly relaxed version of the Regular concept --
* that is it's a value type with the expected semantics. It also implements
* arithmetic operations. It doesn't implement member and pointer operators
* like operator-> or operator[].
*
* ThrowingValue can be instrumented to have certain operations be noexcept by
* using compile-time bitfield template arguments. That is, to make an
* ThrowingValue which has noexcept move construction/assignment and noexcept
* copy construction/assignment, use the following:
* ThrowingValue<testing::kNoThrowMove | testing::kNoThrowCopy> my_thrwr{val};
*/
template <TypeSpec Spec = TypeSpec::kEverythingThrows>
class ThrowingValue : private exceptions_internal::TrackedObject {
static constexpr bool IsSpecified(TypeSpec spec) {
return static_cast<bool>(Spec & spec);
}
static constexpr int kDefaultValue = 0;
static constexpr int kBadValue = 938550620;
public:
ThrowingValue() : TrackedObject(GetInstanceString(kDefaultValue)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = kDefaultValue;
}
ThrowingValue(const ThrowingValue& other) noexcept(
IsSpecified(TypeSpec::kNoThrowCopy))
: TrackedObject(GetInstanceString(other.dummy_)) {
if (!IsSpecified(TypeSpec::kNoThrowCopy)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
dummy_ = other.dummy_;
}
ThrowingValue(ThrowingValue&& other) noexcept(
IsSpecified(TypeSpec::kNoThrowMove))
: TrackedObject(GetInstanceString(other.dummy_)) {
if (!IsSpecified(TypeSpec::kNoThrowMove)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
dummy_ = other.dummy_;
}
explicit ThrowingValue(int i) : TrackedObject(GetInstanceString(i)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = i;
}
ThrowingValue(int i, exceptions_internal::NoThrowTag) noexcept
: TrackedObject(GetInstanceString(i)), dummy_(i) {}
// absl expects nothrow destructors
~ThrowingValue() noexcept = default;
ThrowingValue& operator=(const ThrowingValue& other) noexcept(
IsSpecified(TypeSpec::kNoThrowCopy)) {
dummy_ = kBadValue;
if (!IsSpecified(TypeSpec::kNoThrowCopy)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
dummy_ = other.dummy_;
return *this;
}
ThrowingValue& operator=(ThrowingValue&& other) noexcept(
IsSpecified(TypeSpec::kNoThrowMove)) {
dummy_ = kBadValue;
if (!IsSpecified(TypeSpec::kNoThrowMove)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
dummy_ = other.dummy_;
return *this;
}
// Arithmetic Operators
ThrowingValue operator+(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ + other.dummy_, nothrow_ctor);
}
ThrowingValue operator+() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_, nothrow_ctor);
}
ThrowingValue operator-(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ - other.dummy_, nothrow_ctor);
}
ThrowingValue operator-() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(-dummy_, nothrow_ctor);
}
ThrowingValue& operator++() {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
++dummy_;
return *this;
}
ThrowingValue operator++(int) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
auto out = ThrowingValue(dummy_, nothrow_ctor);
++dummy_;
return out;
}
ThrowingValue& operator--() {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
--dummy_;
return *this;
}
ThrowingValue operator--(int) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
auto out = ThrowingValue(dummy_, nothrow_ctor);
--dummy_;
return out;
}
ThrowingValue operator*(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ * other.dummy_, nothrow_ctor);
}
ThrowingValue operator/(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ / other.dummy_, nothrow_ctor);
}
ThrowingValue operator%(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ % other.dummy_, nothrow_ctor);
}
ThrowingValue operator<<(int shift) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ << shift, nothrow_ctor);
}
ThrowingValue operator>>(int shift) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ >> shift, nothrow_ctor);
}
// Comparison Operators
// NOTE: We use `ThrowingBool` instead of `bool` because most STL
// types/containers requires T to be convertible to bool.
friend ThrowingBool operator==(const ThrowingValue& a,
const ThrowingValue& b) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return a.dummy_ == b.dummy_;
}
friend ThrowingBool operator!=(const ThrowingValue& a,
const ThrowingValue& b) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return a.dummy_ != b.dummy_;
}
friend ThrowingBool operator<(const ThrowingValue& a,
const ThrowingValue& b) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return a.dummy_ < b.dummy_;
}
friend ThrowingBool operator<=(const ThrowingValue& a,
const ThrowingValue& b) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return a.dummy_ <= b.dummy_;
}
friend ThrowingBool operator>(const ThrowingValue& a,
const ThrowingValue& b) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return a.dummy_ > b.dummy_;
}
friend ThrowingBool operator>=(const ThrowingValue& a,
const ThrowingValue& b) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return a.dummy_ >= b.dummy_;
}
// Logical Operators
ThrowingBool operator!() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return !dummy_;
}
ThrowingBool operator&&(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return dummy_ && other.dummy_;
}
ThrowingBool operator||(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return dummy_ || other.dummy_;
}
// Bitwise Logical Operators
ThrowingValue operator~() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(~dummy_, nothrow_ctor);
}
ThrowingValue operator&(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ & other.dummy_, nothrow_ctor);
}
ThrowingValue operator|(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ | other.dummy_, nothrow_ctor);
}
ThrowingValue operator^(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ ^ other.dummy_, nothrow_ctor);
}
// Compound Assignment operators
ThrowingValue& operator+=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ += other.dummy_;
return *this;
}
ThrowingValue& operator-=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ -= other.dummy_;
return *this;
}
ThrowingValue& operator*=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ *= other.dummy_;
return *this;
}
ThrowingValue& operator/=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ /= other.dummy_;
return *this;
}
ThrowingValue& operator%=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ %= other.dummy_;
return *this;
}
ThrowingValue& operator&=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ &= other.dummy_;
return *this;
}
ThrowingValue& operator|=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ |= other.dummy_;
return *this;
}
ThrowingValue& operator^=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ ^= other.dummy_;
return *this;
}
ThrowingValue& operator<<=(int shift) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ <<= shift;
return *this;
}
ThrowingValue& operator>>=(int shift) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ >>= shift;
return *this;
}
// Pointer operators
void operator&() const = delete; // NOLINT(runtime/operator)
// Stream operators
friend std::ostream& operator<<(std::ostream& os, const ThrowingValue& tv) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return os << GetInstanceString(tv.dummy_);
}
friend std::istream& operator>>(std::istream& is, const ThrowingValue&) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return is;
}
// Memory management operators
// Args.. allows us to overload regular and placement new in one shot
template <typename... Args>
static void* operator new(size_t s, Args&&... args) noexcept(
IsSpecified(TypeSpec::kNoThrowNew)) {
if (!IsSpecified(TypeSpec::kNoThrowNew)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION, true);
}
return ::operator new(s, std::forward<Args>(args)...);
}
template <typename... Args>
static void* operator new[](size_t s, Args&&... args) noexcept(
IsSpecified(TypeSpec::kNoThrowNew)) {
if (!IsSpecified(TypeSpec::kNoThrowNew)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION, true);
}
return ::operator new[](s, std::forward<Args>(args)...);
}
// Abseil doesn't support throwing overloaded operator delete. These are
// provided so a throwing operator-new can clean up after itself.
//
// We provide both regular and templated operator delete because if only the
// templated version is provided as we did with operator new, the compiler has
// no way of knowing which overload of operator delete to call. See
// http://en.cppreference.com/w/cpp/memory/new/operator_delete and
// http://en.cppreference.com/w/cpp/language/delete for the gory details.
void operator delete(void* p) noexcept { ::operator delete(p); }
template <typename... Args>
void operator delete(void* p, Args&&... args) noexcept {
::operator delete(p, std::forward<Args>(args)...);
}
void operator delete[](void* p) noexcept { return ::operator delete[](p); }
template <typename... Args>
void operator delete[](void* p, Args&&... args) noexcept {
return ::operator delete[](p, std::forward<Args>(args)...);
}
// Non-standard access to the actual contained value. No need for this to
// throw.
int& Get() noexcept { return dummy_; }
const int& Get() const noexcept { return dummy_; }
private:
static std::string GetInstanceString(int dummy) {
return absl::StrCat("ThrowingValue<",
exceptions_internal::GetSpecString(Spec), ">(", dummy,
")");
}
int dummy_;
};
// While not having to do with exceptions, explicitly delete comma operator, to
// make sure we don't use it on user-supplied types.
template <TypeSpec Spec, typename T>
void operator,(const ThrowingValue<Spec>&, T&&) = delete;
template <TypeSpec Spec, typename T>
void operator,(T&&, const ThrowingValue<Spec>&) = delete;
/*
* Configuration enum for the ThrowingAllocator type that defines behavior for
* the lifetime of the instance.
*
* kEverythingThrows: Calls to the member functions may throw
* kNoThrowAllocate: Calls to the member functions will not throw
*/
enum class AllocSpec {
kEverythingThrows = 0,
kNoThrowAllocate = 1,
};
/*
* An allocator type which is instrumented to throw at a controlled time, or not
* to throw, using AllocSpec. The supported settings are the default of every
* function which is allowed to throw in a conforming allocator possibly
* throwing, or nothing throws, in line with the ABSL_ALLOCATOR_THROWS
* configuration macro.
*/
template <typename T, AllocSpec Spec = AllocSpec::kEverythingThrows>
class ThrowingAllocator : private exceptions_internal::TrackedObject {
static constexpr bool IsSpecified(AllocSpec spec) {
return static_cast<bool>(Spec & spec);
}
public:
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using void_pointer = void*;
using const_void_pointer = const void*;
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using is_nothrow =
std::integral_constant<bool, Spec == AllocSpec::kNoThrowAllocate>;
using propagate_on_container_copy_assignment = std::true_type;
using propagate_on_container_move_assignment = std::true_type;
using propagate_on_container_swap = std::true_type;
using is_always_equal = std::false_type;
ThrowingAllocator() : TrackedObject(GetInstanceString(next_id_)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = std::make_shared<const int>(next_id_++);
}
template <typename U>
ThrowingAllocator(const ThrowingAllocator<U, Spec>& other) noexcept // NOLINT
: TrackedObject(GetInstanceString(*other.State())),
dummy_(other.State()) {}
// According to C++11 standard [17.6.3.5], Table 28, the move/copy ctors of
// allocator shall not exit via an exception, thus they are marked noexcept.
ThrowingAllocator(const ThrowingAllocator& other) noexcept
: TrackedObject(GetInstanceString(*other.State())),
dummy_(other.State()) {}
template <typename U>
ThrowingAllocator(ThrowingAllocator<U, Spec>&& other) noexcept // NOLINT
: TrackedObject(GetInstanceString(*other.State())),
dummy_(std::move(other.State())) {}
ThrowingAllocator(ThrowingAllocator&& other) noexcept
: TrackedObject(GetInstanceString(*other.State())),
dummy_(std::move(other.State())) {}
~ThrowingAllocator() noexcept = default;
ThrowingAllocator& operator=(const ThrowingAllocator& other) noexcept {
dummy_ = other.State();
return *this;
}
template <typename U>
ThrowingAllocator& operator=(
const ThrowingAllocator<U, Spec>& other) noexcept {
dummy_ = other.State();
return *this;
}
template <typename U>
ThrowingAllocator& operator=(ThrowingAllocator<U, Spec>&& other) noexcept {
dummy_ = std::move(other.State());
return *this;
}
template <typename U>
struct rebind {
using other = ThrowingAllocator<U, Spec>;
};
pointer allocate(size_type n) noexcept(
IsSpecified(AllocSpec::kNoThrowAllocate)) {
ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION);
return static_cast<pointer>(::operator new(n * sizeof(T)));
}
pointer allocate(size_type n, const_void_pointer) noexcept(
IsSpecified(AllocSpec::kNoThrowAllocate)) {
return allocate(n);
}
void deallocate(pointer ptr, size_type) noexcept {
ReadState();
::operator delete(static_cast<void*>(ptr));
}
template <typename U, typename... Args>
void construct(U* ptr, Args&&... args) noexcept(
IsSpecified(AllocSpec::kNoThrowAllocate)) {
ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION);
::new (static_cast<void*>(ptr)) U(std::forward<Args>(args)...);
}
template <typename U>
void destroy(U* p) noexcept {
ReadState();
p->~U();
}
size_type max_size() const noexcept {
return (std::numeric_limits<difference_type>::max)() / sizeof(value_type);
}
ThrowingAllocator select_on_container_copy_construction() noexcept(
IsSpecified(AllocSpec::kNoThrowAllocate)) {
auto& out = *this;
ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION);
return out;
}
template <typename U>
bool operator==(const ThrowingAllocator<U, Spec>& other) const noexcept {
return dummy_ == other.dummy_;
}
template <typename U>
bool operator!=(const ThrowingAllocator<U, Spec>& other) const noexcept {
return dummy_ != other.dummy_;
}
template <typename, AllocSpec>
friend class ThrowingAllocator;
private:
static std::string GetInstanceString(int dummy) {
return absl::StrCat("ThrowingAllocator<",
exceptions_internal::GetSpecString(Spec), ">(", dummy,
")");
}
const std::shared_ptr<const int>& State() const { return dummy_; }
std::shared_ptr<const int>& State() { return dummy_; }
void ReadState() {
// we know that this will never be true, but the compiler doesn't, so this
// should safely force a read of the value.
if (*dummy_ < 0) std::abort();
}
void ReadStateAndMaybeThrow(absl::string_view msg) const {
if (!IsSpecified(AllocSpec::kNoThrowAllocate)) {
exceptions_internal::MaybeThrow(
absl::Substitute("Allocator id $0 threw from $1", *dummy_, msg));
}
}
static int next_id_;
std::shared_ptr<const int> dummy_;
};
template <typename T, AllocSpec Spec>
int ThrowingAllocator<T, Spec>::next_id_ = 0;
// Tests for resource leaks by attempting to construct a T using args repeatedly
// until successful, using the countdown method. Side effects can then be
// tested for resource leaks.
template <typename T, typename... Args>
void TestThrowingCtor(Args&&... args) {
struct Cleanup {
~Cleanup() { exceptions_internal::UnsetCountdown(); }
} c;
for (int count = 0;; ++count) {
exceptions_internal::ConstructorTracker ct(count);
exceptions_internal::SetCountdown(count);
try {
T temp(std::forward<Args>(args)...);
static_cast<void>(temp);
break;
} catch (const exceptions_internal::TestException&) {
}
}
}
// Tests the nothrow guarantee of the provided nullary operation. If the an
// exception is thrown, the result will be AssertionFailure(). Otherwise, it
// will be AssertionSuccess().
template <typename Operation>
testing::AssertionResult TestNothrowOp(const Operation& operation) {
struct Cleanup {
Cleanup() { exceptions_internal::SetCountdown(); }
~Cleanup() { exceptions_internal::UnsetCountdown(); }
} c;
try {
operation();
return testing::AssertionSuccess();
} catch (const exceptions_internal::TestException&) {
return testing::AssertionFailure()
<< "TestException thrown during call to operation() when nothrow "
"guarantee was expected.";
} catch (...) {
return testing::AssertionFailure()
<< "Unknown exception thrown during call to operation() when "
"nothrow guarantee was expected.";
}
}
namespace exceptions_internal {
// Dummy struct for ExceptionSafetyTestBuilder<> partial state.
struct UninitializedT {};
template <typename T>
class DefaultFactory {
public:
explicit DefaultFactory(const T& t) : t_(t) {}
std::unique_ptr<T> operator()() const { return absl::make_unique<T>(t_); }
private:
T t_;
};
template <size_t LazyContractsCount, typename LazyFactory,
typename LazyOperation>
using EnableIfTestable = typename absl::enable_if_t<
LazyContractsCount != 0 &&
!std::is_same<LazyFactory, UninitializedT>::value &&
!std::is_same<LazyOperation, UninitializedT>::value>;
template <typename Factory = UninitializedT,
typename Operation = UninitializedT, typename... Contracts>
class ExceptionSafetyTestBuilder;
} // namespace exceptions_internal
/*
* Constructs an empty ExceptionSafetyTestBuilder. All
* ExceptionSafetyTestBuilder objects are immutable and all With[thing] mutation
* methods return new instances of ExceptionSafetyTestBuilder.
*
* In order to test a T for exception safety, a factory for that T, a testable
* operation, and at least one contract callback returning an assertion
* result must be applied using the respective methods.
*/
exceptions_internal::ExceptionSafetyTestBuilder<> MakeExceptionSafetyTester();
namespace exceptions_internal {
template <typename T>
struct IsUniquePtr : std::false_type {};
template <typename T, typename D>
struct IsUniquePtr<std::unique_ptr<T, D>> : std::true_type {};
template <typename Factory>
struct FactoryPtrTypeHelper {
using type = decltype(std::declval<const Factory&>()());
static_assert(IsUniquePtr<type>::value, "Factories must return a unique_ptr");
};
template <typename Factory>
using FactoryPtrType = typename FactoryPtrTypeHelper<Factory>::type;
template <typename Factory>
using FactoryElementType = typename FactoryPtrType<Factory>::element_type;
template <typename T>
class ExceptionSafetyTest {
using Factory = std::function<std::unique_ptr<T>()>;
using Operation = std::function<void(T*)>;
using Contract = std::function<AssertionResult(T*)>;
public:
template <typename... Contracts>
explicit ExceptionSafetyTest(const Factory& f, const Operation& op,
const Contracts&... contracts)
: factory_(f), operation_(op), contracts_{WrapContract(contracts)...} {}
AssertionResult Test() const {
for (int count = 0;; ++count) {
exceptions_internal::ConstructorTracker ct(count);
for (const auto& contract : contracts_) {
auto t_ptr = factory_();
try {
SetCountdown(count);
operation_(t_ptr.get());
// Unset for the case that the operation throws no exceptions, which
// would leave the countdown set and break the *next* exception safety
// test after this one.
UnsetCountdown();
return AssertionSuccess();
} catch (const exceptions_internal::TestException& e) {
if (!contract(t_ptr.get())) {
return AssertionFailure() << e.what() << " failed contract check";
}
}
}
}
}
private:
template <typename ContractFn>
Contract WrapContract(const ContractFn& contract) {
return [contract](T* t_ptr) { return AssertionResult(contract(t_ptr)); };
}
Contract WrapContract(StrongGuaranteeTagType) {
return [this](T* t_ptr) { return AssertionResult(*factory_() == *t_ptr); };
}
Factory factory_;
Operation operation_;
std::vector<Contract> contracts_;
};
/*
* Builds a tester object that tests if performing a operation on a T follows
* exception safety guarantees. Verification is done via contract assertion
* callbacks applied to T instances post-throw.
*
* Template parameters for ExceptionSafetyTestBuilder:
*
* - Factory: The factory object (passed in via tester.WithFactory(...) or
* tester.WithInitialValue(...)) must be invocable with the signature
* `std::unique_ptr<T> operator()() const` where T is the type being tested.
* It is used for reliably creating identical T instances to test on.
*
* - Operation: The operation object (passsed in via tester.WithOperation(...)
* or tester.Test(...)) must be invocable with the signature
* `void operator()(T*) const` where T is the type being tested. It is used
* for performing steps on a T instance that may throw and that need to be
* checked for exception safety. Each call to the operation will receive a
* fresh T instance so it's free to modify and destroy the T instances as it
* pleases.
*
* - Contracts...: The contract assertion callback objects (passed in via
* tester.WithContracts(...)) must be invocable with the signature
* `testing::AssertionResult operator()(T*) const` where T is the type being
* tested. Contract assertion callbacks are provided T instances post-throw.
* They must return testing::AssertionSuccess when the type contracts of the
* provided T instance hold. If the type contracts of the T instance do not
* hold, they must return testing::AssertionFailure. Execution order of
* Contracts... is unspecified. They will each individually get a fresh T
* instance so they are free to modify and destroy the T instances as they
* please.
*/
template <typename Factory, typename Operation, typename... Contracts>
class ExceptionSafetyTestBuilder {
public:
/*
* Returns a new ExceptionSafetyTestBuilder with an included T factory based
* on the provided T instance. The existing factory will not be included in
* the newly created tester instance. The created factory returns a new T
* instance by copy-constructing the provided const T& t.
*
* Preconditions for tester.WithInitialValue(const T& t):
*
* - The const T& t object must be copy-constructible where T is the type
* being tested. For non-copy-constructible objects, use the method
* tester.WithFactory(...).
*/
template <typename T>
ExceptionSafetyTestBuilder<DefaultFactory<T>, Operation, Contracts...>
WithInitialValue(const T& t) const {
return WithFactory(DefaultFactory<T>(t));
}
/*
* Returns a new ExceptionSafetyTestBuilder with the provided T factory
* included. The existing factory will not be included in the newly-created
* tester instance. This method is intended for use with types lacking a copy
* constructor. Types that can be copy-constructed should instead use the
* method tester.WithInitialValue(...).
*/
template <typename NewFactory>
ExceptionSafetyTestBuilder<absl::decay_t<NewFactory>, Operation, Contracts...>
WithFactory(const NewFactory& new_factory) const {
return {new_factory, operation_, contracts_};
}
/*
* Returns a new ExceptionSafetyTestBuilder with the provided testable
* operation included. The existing operation will not be included in the
* newly created tester.
*/
template <typename NewOperation>
ExceptionSafetyTestBuilder<Factory, absl::decay_t<NewOperation>, Contracts...>
WithOperation(const NewOperation& new_operation) const {
return {factory_, new_operation, contracts_};
}
/*
* Returns a new ExceptionSafetyTestBuilder with the provided MoreContracts...
* combined with the Contracts... that were already included in the instance
* on which the method was called. Contracts... cannot be removed or replaced
* once added to an ExceptionSafetyTestBuilder instance. A fresh object must
* be created in order to get an empty Contracts... list.
*
* In addition to passing in custom contract assertion callbacks, this method
* accepts `testing::strong_guarantee` as an argument which checks T instances
* post-throw against freshly created T instances via operator== to verify
* that any state changes made during the execution of the operation were
* properly rolled back.
*/
template <typename... MoreContracts>
ExceptionSafetyTestBuilder<Factory, Operation, Contracts...,
absl::decay_t<MoreContracts>...>
WithContracts(const MoreContracts&... more_contracts) const {
return {
factory_, operation_,
std::tuple_cat(contracts_, std::tuple<absl::decay_t<MoreContracts>...>(
more_contracts...))};
}
/*
* Returns a testing::AssertionResult that is the reduced result of the
* exception safety algorithm. The algorithm short circuits and returns
* AssertionFailure after the first contract callback returns an
* AssertionFailure. Otherwise, if all contract callbacks return an
* AssertionSuccess, the reduced result is AssertionSuccess.
*
* The passed-in testable operation will not be saved in a new tester instance
* nor will it modify/replace the existing tester instance. This is useful
* when each operation being tested is unique and does not need to be reused.
*
* Preconditions for tester.Test(const NewOperation& new_operation):
*
* - May only be called after at least one contract assertion callback and a
* factory or initial value have been provided.
*/
template <
typename NewOperation,
typename = EnableIfTestable<sizeof...(Contracts), Factory, NewOperation>>
testing::AssertionResult Test(const NewOperation& new_operation) const {
return TestImpl(new_operation, absl::index_sequence_for<Contracts...>());
}
/*
* Returns a testing::AssertionResult that is the reduced result of the
* exception safety algorithm. The algorithm short circuits and returns
* AssertionFailure after the first contract callback returns an
* AssertionFailure. Otherwise, if all contract callbacks return an
* AssertionSuccess, the reduced result is AssertionSuccess.
*
* Preconditions for tester.Test():
*
* - May only be called after at least one contract assertion callback, a
* factory or initial value and a testable operation have been provided.
*/
template <
typename LazyOperation = Operation,
typename = EnableIfTestable<sizeof...(Contracts), Factory, LazyOperation>>
testing::AssertionResult Test() const {
return Test(operation_);
}
private:
template <typename, typename, typename...>
friend class ExceptionSafetyTestBuilder;
friend ExceptionSafetyTestBuilder<> testing::MakeExceptionSafetyTester();
ExceptionSafetyTestBuilder() {}
ExceptionSafetyTestBuilder(const Factory& f, const Operation& o,
const std::tuple<Contracts...>& i)
: factory_(f), operation_(o), contracts_(i) {}
template <typename SelectedOperation, size_t... Indices>
testing::AssertionResult TestImpl(SelectedOperation selected_operation,
absl::index_sequence<Indices...>) const {
return ExceptionSafetyTest<FactoryElementType<Factory>>(
factory_, selected_operation, std::get<Indices>(contracts_)...)
.Test();
}
Factory factory_;
Operation operation_;
std::tuple<Contracts...> contracts_;
};
} // namespace exceptions_internal
} // namespace testing
#endif // ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_