12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109 |
- // 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
- //
- // https://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 Y_ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_
- #define Y_ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_
- #include "y_absl/base/config.h"
- #ifdef Y_ABSL_HAVE_EXCEPTIONS
- #include <cstddef>
- #include <cstdint>
- #include <functional>
- #include <initializer_list>
- #include <iosfwd>
- #include <util/generic/string.h>
- #include <tuple>
- #include <unordered_map>
- #include "gtest/gtest.h"
- #include "y_absl/base/internal/pretty_function.h"
- #include "y_absl/memory/memory.h"
- #include "y_absl/meta/type_traits.h"
- #include "y_absl/strings/string_view.h"
- #include "y_absl/strings/substitute.h"
- #include "y_absl/utility/utility.h"
- namespace testing {
- enum class TypeSpec;
- enum class AllocSpec;
- constexpr TypeSpec operator|(TypeSpec a, TypeSpec b) {
- using T = y_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 = y_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 = y_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 = y_absl::underlying_type_t<AllocSpec>;
- return static_cast<AllocSpec>(static_cast<T>(a) & static_cast<T>(b));
- }
- namespace exceptions_internal {
- TString GetSpecString(TypeSpec);
- TString 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(y_absl::string_view msg) : msg_(msg) {}
- virtual ~TestException() {}
- virtual const char* what() const noexcept { return msg_.c_str(); }
- private:
- TString 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(y_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(y_absl::string_view msg, bool throw_bad_alloc = false);
- testing::AssertionResult FailureMessage(const TestException& e,
- int countdown) noexcept;
- struct TrackedAddress {
- bool is_alive;
- TString 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, TString 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 TString ErrorMessage(void* address,
- const TString& address_description,
- int countdown,
- const TString& error_description) {
- return y_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(TString 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(Y_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(Y_ABSL_PRETTY_FUNCTION);
- dummy_ = kDefaultValue;
- }
- ThrowingValue(const ThrowingValue& other) noexcept(
- IsSpecified(TypeSpec::kNoThrowCopy))
- : TrackedObject(GetInstanceString(other.dummy_)) {
- if (!IsSpecified(TypeSpec::kNoThrowCopy)) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- }
- dummy_ = other.dummy_;
- }
- ThrowingValue(ThrowingValue&& other) noexcept(
- IsSpecified(TypeSpec::kNoThrowMove))
- : TrackedObject(GetInstanceString(other.dummy_)) {
- if (!IsSpecified(TypeSpec::kNoThrowMove)) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- }
- dummy_ = other.dummy_;
- }
- explicit ThrowingValue(int i) : TrackedObject(GetInstanceString(i)) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ = i;
- }
- ThrowingValue(int i, exceptions_internal::NoThrowTag) noexcept
- : TrackedObject(GetInstanceString(i)), dummy_(i) {}
- // y_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(Y_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(Y_ABSL_PRETTY_FUNCTION);
- }
- dummy_ = other.dummy_;
- return *this;
- }
- // Arithmetic Operators
- ThrowingValue operator+(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ + other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator+() const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_, nothrow_ctor);
- }
- ThrowingValue operator-(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ - other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator-() const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(-dummy_, nothrow_ctor);
- }
- ThrowingValue& operator++() {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- ++dummy_;
- return *this;
- }
- ThrowingValue operator++(int) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- auto out = ThrowingValue(dummy_, nothrow_ctor);
- ++dummy_;
- return out;
- }
- ThrowingValue& operator--() {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- --dummy_;
- return *this;
- }
- ThrowingValue operator--(int) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- auto out = ThrowingValue(dummy_, nothrow_ctor);
- --dummy_;
- return out;
- }
- ThrowingValue operator*(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ * other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator/(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ / other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator%(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ % other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator<<(int shift) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ << shift, nothrow_ctor);
- }
- ThrowingValue operator>>(int shift) const {
- exceptions_internal::MaybeThrow(Y_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(Y_ABSL_PRETTY_FUNCTION);
- return a.dummy_ == b.dummy_;
- }
- friend ThrowingBool operator!=(const ThrowingValue& a,
- const ThrowingValue& b) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return a.dummy_ != b.dummy_;
- }
- friend ThrowingBool operator<(const ThrowingValue& a,
- const ThrowingValue& b) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return a.dummy_ < b.dummy_;
- }
- friend ThrowingBool operator<=(const ThrowingValue& a,
- const ThrowingValue& b) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return a.dummy_ <= b.dummy_;
- }
- friend ThrowingBool operator>(const ThrowingValue& a,
- const ThrowingValue& b) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return a.dummy_ > b.dummy_;
- }
- friend ThrowingBool operator>=(const ThrowingValue& a,
- const ThrowingValue& b) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return a.dummy_ >= b.dummy_;
- }
- // Logical Operators
- ThrowingBool operator!() const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return !dummy_;
- }
- ThrowingBool operator&&(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return dummy_ && other.dummy_;
- }
- ThrowingBool operator||(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return dummy_ || other.dummy_;
- }
- // Bitwise Logical Operators
- ThrowingValue operator~() const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(~dummy_, nothrow_ctor);
- }
- ThrowingValue operator&(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ & other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator|(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ | other.dummy_, nothrow_ctor);
- }
- ThrowingValue operator^(const ThrowingValue& other) const {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return ThrowingValue(dummy_ ^ other.dummy_, nothrow_ctor);
- }
- // Compound Assignment operators
- ThrowingValue& operator+=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ += other.dummy_;
- return *this;
- }
- ThrowingValue& operator-=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ -= other.dummy_;
- return *this;
- }
- ThrowingValue& operator*=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ *= other.dummy_;
- return *this;
- }
- ThrowingValue& operator/=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ /= other.dummy_;
- return *this;
- }
- ThrowingValue& operator%=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ %= other.dummy_;
- return *this;
- }
- ThrowingValue& operator&=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ &= other.dummy_;
- return *this;
- }
- ThrowingValue& operator|=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ |= other.dummy_;
- return *this;
- }
- ThrowingValue& operator^=(const ThrowingValue& other) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ ^= other.dummy_;
- return *this;
- }
- ThrowingValue& operator<<=(int shift) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- dummy_ <<= shift;
- return *this;
- }
- ThrowingValue& operator>>=(int shift) {
- exceptions_internal::MaybeThrow(Y_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(Y_ABSL_PRETTY_FUNCTION);
- return os << GetInstanceString(tv.dummy_);
- }
- friend std::istream& operator>>(std::istream& is, const ThrowingValue&) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return is;
- }
- // Memory management operators
- static void* operator new(size_t s) noexcept(
- IsSpecified(TypeSpec::kNoThrowNew)) {
- if (!IsSpecified(TypeSpec::kNoThrowNew)) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION, true);
- }
- return ::operator new(s);
- }
- static void* operator new[](size_t s) noexcept(
- IsSpecified(TypeSpec::kNoThrowNew)) {
- if (!IsSpecified(TypeSpec::kNoThrowNew)) {
- exceptions_internal::MaybeThrow(Y_ABSL_PRETTY_FUNCTION, true);
- }
- return ::operator new[](s);
- }
- template <typename... Args>
- static void* operator new(size_t s, Args&&... args) noexcept(
- IsSpecified(TypeSpec::kNoThrowNew)) {
- if (!IsSpecified(TypeSpec::kNoThrowNew)) {
- exceptions_internal::MaybeThrow(Y_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(Y_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.
- 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 TString GetInstanceString(int dummy) {
- return y_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 Y_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(Y_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(Y_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(Y_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)) {
- ReadStateAndMaybeThrow(Y_ABSL_PRETTY_FUNCTION);
- return *this;
- }
- 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 TString GetInstanceString(int dummy) {
- return y_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(y_absl::string_view msg) const {
- if (!IsSpecified(AllocSpec::kNoThrowAllocate)) {
- exceptions_internal::MaybeThrow(
- y_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 y_absl::make_unique<T>(t_); }
- private:
- T t_;
- };
- template <size_t LazyContractsCount, typename LazyFactory,
- typename LazyOperation>
- using EnableIfTestable = typename y_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 (passed 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<y_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, y_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...,
- y_absl::decay_t<MoreContracts>...>
- WithContracts(const MoreContracts&... more_contracts) const {
- return {
- factory_, operation_,
- std::tuple_cat(contracts_, std::tuple<y_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, y_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,
- y_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 // Y_ABSL_HAVE_EXCEPTIONS
- #endif // Y_ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_
|