Changes imported from Abseil "staging" branch:

- 8bcd472c6f1a7c8a3a7aac07d7e5e3f90685fe5e Create an exception-safety testing framework for Abseil by Jon Cohen <cohenjon@google.com>
  - 17cc4bb19ba86e2bc45e9381bd07450c06134903 Fix typo. by Abseil Team <absl-team@google.com>

GitOrigin-RevId: 8bcd472c6f1a7c8a3a7aac07d7e5e3f90685fe5e
Change-Id: Ia1f4f12d25c375e0af34fea052a4a82dc964eeff
This commit is contained in:
Abseil Team 2017-10-30 15:55:37 -07:00 committed by misterg
parent c8bd28c58b
commit 8db6cfd1ea
7 changed files with 1297 additions and 67 deletions

View file

@ -226,6 +226,37 @@ cc_library(
],
)
cc_library(
name = "pretty_function",
hdrs = ["internal/pretty_function.h"],
)
cc_library(
name = "exception_safety_testing",
testonly = 1,
srcs = ["internal/exception_safety_testing.cc"],
hdrs = ["internal/exception_safety_testing.h"],
copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG,
deps = [
":config",
":pretty_function",
"//absl/meta:type_traits",
"//absl/strings",
"@com_google_googletest//:gtest",
],
)
cc_test(
name = "exception_safety_testing_test",
srcs = ["exception_safety_testing_test.cc"],
copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG,
deps = [
":exception_safety_testing",
"//absl/memory",
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "invoke_test",
size = "small",

View file

@ -260,7 +260,7 @@
// ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED
//
// Tells the UndefinedSanitizer to ignore a given function. Useful for cases
// where certain behavior (eg. devision by zero) is being used intentionally.
// where certain behavior (eg. division by zero) is being used intentionally.
// NOTE: GCC supports UndefinedBehaviorSanitizer(ubsan) since 4.9.
// https://gcc.gnu.org/gcc-4.9/changes.html
#if defined(__GNUC__) && \

View file

@ -0,0 +1,546 @@
#include "absl/base/internal/exception_safety_testing.h"
#include <cstddef>
#include <exception>
#include <iostream>
#include <list>
#include <vector>
#include "gtest/gtest-spi.h"
#include "gtest/gtest.h"
#include "absl/memory/memory.h"
namespace absl {
namespace {
using ::absl::exceptions_internal::TestException;
void SetCountdown() { exceptions_internal::countdown = 0; }
void UnsetCountdown() { exceptions_internal::countdown = -1; }
// EXPECT_NO_THROW can't inspect the thrown inspection in general.
template <typename F>
void ExpectNoThrow(const F& f) {
try {
f();
} catch (TestException e) {
ADD_FAILURE() << "Unexpected exception thrown from " << e.what();
}
}
class ThrowingValueTest : public ::testing::Test {
protected:
void SetUp() override { UnsetCountdown(); }
private:
AllocInspector clouseau_;
};
TEST_F(ThrowingValueTest, Throws) {
SetCountdown();
EXPECT_THROW(ThrowingValue<> bomb, TestException);
// It's not guaranteed that every operator only throws *once*. The default
// ctor only throws once, though, so use it to make sure we only throw when
// the countdown hits 0
exceptions_internal::countdown = 2;
ExpectNoThrow([]() { ThrowingValue<> bomb; });
ExpectNoThrow([]() { ThrowingValue<> bomb; });
EXPECT_THROW(ThrowingValue<> bomb, TestException);
}
// Tests that an operation throws when the countdown is at 0, doesn't throw when
// the countdown doesn't hit 0, and doesn't modify the state of the
// ThrowingValue if it throws
template <typename F>
void TestOp(F&& f) {
UnsetCountdown();
ExpectNoThrow(f);
SetCountdown();
EXPECT_THROW(f(), TestException);
UnsetCountdown();
}
TEST_F(ThrowingValueTest, ThrowingCtors) {
ThrowingValue<> bomb;
TestOp([]() { ThrowingValue<> bomb(1); });
TestOp([&]() { ThrowingValue<> bomb1 = bomb; });
TestOp([&]() { ThrowingValue<> bomb1 = std::move(bomb); });
}
TEST_F(ThrowingValueTest, ThrowingAssignment) {
ThrowingValue<> bomb, bomb1;
TestOp([&]() { bomb = bomb1; });
TestOp([&]() { bomb = std::move(bomb1); });
}
TEST_F(ThrowingValueTest, ThrowingComparisons) {
ThrowingValue<> bomb1, bomb2;
TestOp([&]() { return bomb1 == bomb2; });
TestOp([&]() { return bomb1 != bomb2; });
TestOp([&]() { return bomb1 < bomb2; });
TestOp([&]() { return bomb1 <= bomb2; });
TestOp([&]() { return bomb1 > bomb2; });
TestOp([&]() { return bomb1 >= bomb2; });
}
TEST_F(ThrowingValueTest, ThrowingArithmeticOps) {
ThrowingValue<> bomb1(1), bomb2(2);
TestOp([&bomb1]() { +bomb1; });
TestOp([&bomb1]() { -bomb1; });
TestOp([&bomb1]() { ++bomb1; });
TestOp([&bomb1]() { bomb1++; });
TestOp([&bomb1]() { --bomb1; });
TestOp([&bomb1]() { bomb1--; });
TestOp([&]() { bomb1 + bomb2; });
TestOp([&]() { bomb1 - bomb2; });
TestOp([&]() { bomb1* bomb2; });
TestOp([&]() { bomb1 / bomb2; });
TestOp([&]() { bomb1 << 1; });
TestOp([&]() { bomb1 >> 1; });
}
TEST_F(ThrowingValueTest, ThrowingLogicalOps) {
ThrowingValue<> bomb1, bomb2;
TestOp([&bomb1]() { !bomb1; });
TestOp([&]() { bomb1&& bomb2; });
TestOp([&]() { bomb1 || bomb2; });
}
TEST_F(ThrowingValueTest, ThrowingBitwiseOps) {
ThrowingValue<> bomb1, bomb2;
TestOp([&bomb1]() { ~bomb1; });
TestOp([&]() { bomb1& bomb2; });
TestOp([&]() { bomb1 | bomb2; });
TestOp([&]() { bomb1 ^ bomb2; });
}
TEST_F(ThrowingValueTest, ThrowingCompoundAssignmentOps) {
ThrowingValue<> bomb1(1), bomb2(2);
TestOp([&]() { bomb1 += bomb2; });
TestOp([&]() { bomb1 -= bomb2; });
TestOp([&]() { bomb1 *= bomb2; });
TestOp([&]() { bomb1 /= bomb2; });
TestOp([&]() { bomb1 %= bomb2; });
TestOp([&]() { bomb1 &= bomb2; });
TestOp([&]() { bomb1 |= bomb2; });
TestOp([&]() { bomb1 ^= bomb2; });
TestOp([&]() { bomb1 *= bomb2; });
}
TEST_F(ThrowingValueTest, ThrowingStreamOps) {
ThrowingValue<> bomb;
TestOp([&]() { std::cin >> bomb; });
TestOp([&]() { std::cout << bomb; });
}
TEST_F(ThrowingValueTest, ThrowingAllocatingOps) {
// make_unique calls unqualified operator new, so these exercise the
// ThrowingValue overloads.
TestOp([]() { return absl::make_unique<ThrowingValue<>>(1); });
TestOp([]() { return absl::make_unique<ThrowingValue<>[]>(2); });
}
TEST_F(ThrowingValueTest, NonThrowingMoveCtor) {
ThrowingValue<NoThrow::kMoveCtor> nothrow_ctor;
SetCountdown();
ExpectNoThrow([&nothrow_ctor]() {
ThrowingValue<NoThrow::kMoveCtor> nothrow1 = std::move(nothrow_ctor);
});
}
TEST_F(ThrowingValueTest, NonThrowingMoveAssign) {
ThrowingValue<NoThrow::kMoveAssign> nothrow_assign1, nothrow_assign2;
SetCountdown();
ExpectNoThrow([&nothrow_assign1, &nothrow_assign2]() {
nothrow_assign1 = std::move(nothrow_assign2);
});
}
TEST_F(ThrowingValueTest, ThrowingSwap) {
ThrowingValue<> bomb1, bomb2;
TestOp([&]() { std::swap(bomb1, bomb2); });
ThrowingValue<NoThrow::kMoveCtor> bomb3, bomb4;
TestOp([&]() { std::swap(bomb3, bomb4); });
ThrowingValue<NoThrow::kMoveAssign> bomb5, bomb6;
TestOp([&]() { std::swap(bomb5, bomb6); });
}
TEST_F(ThrowingValueTest, NonThrowingSwap) {
ThrowingValue<NoThrow::kMoveAssign | NoThrow::kMoveCtor> bomb1, bomb2;
ExpectNoThrow([&]() { std::swap(bomb1, bomb2); });
}
TEST_F(ThrowingValueTest, NonThrowingAllocation) {
ThrowingValue<NoThrow::kAllocation>* allocated;
ThrowingValue<NoThrow::kAllocation>* array;
ExpectNoThrow([&allocated]() {
allocated = new ThrowingValue<NoThrow::kAllocation>(1);
delete allocated;
});
ExpectNoThrow([&array]() {
array = new ThrowingValue<NoThrow::kAllocation>[2];
delete[] array;
});
}
TEST_F(ThrowingValueTest, NonThrowingDelete) {
auto* allocated = new ThrowingValue<>(1);
auto* array = new ThrowingValue<>[2];
SetCountdown();
ExpectNoThrow([allocated]() { delete allocated; });
SetCountdown();
ExpectNoThrow([array]() { delete[] array; });
}
using Storage =
absl::aligned_storage_t<sizeof(ThrowingValue<>), alignof(ThrowingValue<>)>;
TEST_F(ThrowingValueTest, NonThrowingPlacementDelete) {
constexpr int kArrayLen = 2;
// We intentionally create extra space to store the tag allocated by placement
// new[].
constexpr int kStorageLen = 4;
Storage buf;
Storage array_buf[kStorageLen];
auto* placed = new (&buf) ThrowingValue<>(1);
auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen];
SetCountdown();
ExpectNoThrow([placed, &buf]() {
placed->~ThrowingValue<>();
ThrowingValue<>::operator delete(placed, &buf);
});
SetCountdown();
ExpectNoThrow([&, placed_array]() {
for (int i = 0; i < kArrayLen; ++i) placed_array[i].~ThrowingValue<>();
ThrowingValue<>::operator delete[](placed_array, &array_buf);
});
}
TEST_F(ThrowingValueTest, NonThrowingDestructor) {
auto* allocated = new ThrowingValue<>();
SetCountdown();
ExpectNoThrow([allocated]() { delete allocated; });
}
TEST(ThrowingBoolTest, ThrowingBool) {
UnsetCountdown();
ThrowingBool t = true;
// Test that it's contextually convertible to bool
if (t) { // NOLINT(whitespace/empty_if_body)
}
EXPECT_TRUE(t);
TestOp([&]() { (void)!t; });
}
class ThrowingAllocatorTest : public ::testing::Test {
protected:
void SetUp() override { UnsetCountdown(); }
private:
AllocInspector borlu_;
};
TEST_F(ThrowingAllocatorTest, MemoryManagement) {
// Just exercise the memory management capabilities under LSan to make sure we
// don't leak.
ThrowingAllocator<int> int_alloc;
int* ip = int_alloc.allocate(1);
int_alloc.deallocate(ip, 1);
int* i_array = int_alloc.allocate(2);
int_alloc.deallocate(i_array, 2);
ThrowingAllocator<ThrowingValue<>> ef_alloc;
ThrowingValue<>* efp = ef_alloc.allocate(1);
ef_alloc.deallocate(efp, 1);
ThrowingValue<>* ef_array = ef_alloc.allocate(2);
ef_alloc.deallocate(ef_array, 2);
}
TEST_F(ThrowingAllocatorTest, CallsGlobalNew) {
ThrowingAllocator<ThrowingValue<>, NoThrow::kNoThrow> nothrow_alloc;
ThrowingValue<>* ptr;
SetCountdown();
// This will only throw if ThrowingValue::new is called.
ExpectNoThrow([&]() { ptr = nothrow_alloc.allocate(1); });
nothrow_alloc.deallocate(ptr, 1);
}
TEST_F(ThrowingAllocatorTest, ThrowingConstructors) {
ThrowingAllocator<int> int_alloc;
int* ip = nullptr;
SetCountdown();
EXPECT_THROW(ip = int_alloc.allocate(1), TestException);
ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
*ip = 1;
SetCountdown();
EXPECT_THROW(int_alloc.construct(ip, 2), TestException);
EXPECT_EQ(*ip, 1);
int_alloc.deallocate(ip, 1);
}
TEST_F(ThrowingAllocatorTest, NonThrowingConstruction) {
{
ThrowingAllocator<int, NoThrow::kNoThrow> int_alloc;
int* ip = nullptr;
SetCountdown();
ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
SetCountdown();
ExpectNoThrow([&]() { int_alloc.construct(ip, 2); });
EXPECT_EQ(*ip, 2);
int_alloc.deallocate(ip, 1);
}
UnsetCountdown();
{
ThrowingAllocator<int> int_alloc;
int* ip = nullptr;
ExpectNoThrow([&]() { ip = int_alloc.allocate(1); });
ExpectNoThrow([&]() { int_alloc.construct(ip, 2); });
EXPECT_EQ(*ip, 2);
int_alloc.deallocate(ip, 1);
}
UnsetCountdown();
{
ThrowingAllocator<ThrowingValue<NoThrow::kIntCtor>, NoThrow::kNoThrow>
ef_alloc;
ThrowingValue<NoThrow::kIntCtor>* efp;
SetCountdown();
ExpectNoThrow([&]() { efp = ef_alloc.allocate(1); });
SetCountdown();
ExpectNoThrow([&]() { ef_alloc.construct(efp, 2); });
EXPECT_EQ(efp->Get(), 2);
ef_alloc.destroy(efp);
ef_alloc.deallocate(efp, 1);
}
UnsetCountdown();
{
ThrowingAllocator<int> a;
SetCountdown();
ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = a; });
SetCountdown();
ExpectNoThrow([&]() { ThrowingAllocator<double> a1 = std::move(a); });
}
}
TEST_F(ThrowingAllocatorTest, ThrowingAllocatorConstruction) {
ThrowingAllocator<int> a;
TestOp([]() { ThrowingAllocator<int> a; });
TestOp([&]() { a.select_on_container_copy_construction(); });
}
TEST_F(ThrowingAllocatorTest, State) {
ThrowingAllocator<int> a1, a2;
EXPECT_NE(a1, a2);
auto a3 = a1;
EXPECT_EQ(a3, a1);
int* ip = a1.allocate(1);
EXPECT_EQ(a3, a1);
a3.deallocate(ip, 1);
EXPECT_EQ(a3, a1);
}
TEST_F(ThrowingAllocatorTest, InVector) {
std::vector<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> v;
for (int i = 0; i < 20; ++i) v.push_back({});
for (int i = 0; i < 20; ++i) v.pop_back();
}
TEST_F(ThrowingAllocatorTest, InList) {
std::list<ThrowingValue<>, ThrowingAllocator<ThrowingValue<>>> l;
for (int i = 0; i < 20; ++i) l.push_back({});
for (int i = 0; i < 20; ++i) l.pop_back();
for (int i = 0; i < 20; ++i) l.push_front({});
for (int i = 0; i < 20; ++i) l.pop_front();
}
struct CallOperator {
template <typename T>
void operator()(T* t) const {
(*t)();
}
};
struct FailsBasicGuarantee {
void operator()() {
--i;
ThrowingValue<> bomb;
++i;
}
bool operator!=(const FailsBasicGuarantee& other) const {
return i != other.i;
}
friend bool AbslCheckInvariants(const FailsBasicGuarantee& g) {
return g.i >= 0;
}
int i = 0;
};
TEST(ExceptionCheckTest, BasicGuaranteeFailure) {
FailsBasicGuarantee g;
EXPECT_FALSE(TestBasicGuarantee(&g, CallOperator{}));
}
struct FollowsBasicGuarantee {
void operator()() {
++i;
ThrowingValue<> bomb;
}
bool operator!=(const FollowsBasicGuarantee& other) const {
return i != other.i;
}
friend bool AbslCheckInvariants(const FollowsBasicGuarantee& g) {
return g.i >= 0;
}
int i = 0;
};
TEST(ExceptionCheckTest, BasicGuarantee) {
FollowsBasicGuarantee g;
EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{}));
}
TEST(ExceptionCheckTest, StrongGuaranteeFailure) {
{
FailsBasicGuarantee g;
EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{}));
}
{
FollowsBasicGuarantee g;
EXPECT_FALSE(TestStrongGuarantee(&g, CallOperator{}));
}
}
struct FollowsStrongGuarantee {
void operator()() { ThrowingValue<> bomb; }
bool operator!=(const FollowsStrongGuarantee& other) const {
return i != other.i;
}
friend bool AbslCheckInvariants(const FollowsStrongGuarantee& g) {
return g.i >= 0;
}
int i = 0;
};
TEST(ExceptionCheckTest, StrongGuarantee) {
FollowsStrongGuarantee g;
EXPECT_TRUE(TestBasicGuarantee(&g, CallOperator{}));
EXPECT_TRUE(TestStrongGuarantee(&g, CallOperator{}));
}
template <typename T>
struct InstructionCounter {
void operator()() {
++counter;
T b1;
static_cast<void>(b1);
++counter;
T b2;
static_cast<void>(b2);
++counter;
T b3;
static_cast<void>(b3);
++counter;
}
bool operator!=(const InstructionCounter<ThrowingValue<>>& other) const {
return false;
}
friend bool AbslCheckInvariants(const InstructionCounter&) { return true; }
static int counter;
};
template <typename T>
int InstructionCounter<T>::counter = 0;
TEST(ExceptionCheckTest, Exhaustiveness) {
InstructionCounter<int> int_factory;
EXPECT_TRUE(TestBasicGuarantee(&int_factory, CallOperator{}));
EXPECT_EQ(InstructionCounter<int>::counter, 4);
InstructionCounter<ThrowingValue<>> bomb_factory;
EXPECT_TRUE(TestBasicGuarantee(&bomb_factory, CallOperator{}));
EXPECT_EQ(InstructionCounter<ThrowingValue<>>::counter, 10);
InstructionCounter<ThrowingValue<>>::counter = 0;
EXPECT_TRUE(TestStrongGuarantee(&bomb_factory, CallOperator{}));
EXPECT_EQ(InstructionCounter<ThrowingValue<>>::counter, 10);
}
struct Tracked : private exceptions_internal::TrackedObject {
Tracked() : TrackedObject(ABSL_PRETTY_FUNCTION) {}
};
TEST(AllocInspectorTest, Pass) {
AllocInspector javert;
Tracked t;
}
TEST(AllocInspectorTest, NotDestroyed) {
absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage;
EXPECT_NONFATAL_FAILURE(
{
AllocInspector gadget;
new (&storage) Tracked;
},
"not destroyed");
}
TEST(AllocInspectorTest, DestroyedTwice) {
EXPECT_NONFATAL_FAILURE(
{
Tracked t;
t.~Tracked();
},
"destroyed improperly");
}
TEST(AllocInspectorTest, ConstructedTwice) {
absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage;
EXPECT_NONFATAL_FAILURE(
{
new (&storage) Tracked;
new (&storage) Tracked;
},
"re-constructed");
}
} // namespace
} // namespace absl

View file

@ -0,0 +1,21 @@
#include "absl/base/internal/exception_safety_testing.h"
#include "gtest/gtest.h"
#include "absl/meta/type_traits.h"
namespace absl {
namespace exceptions_internal {
int countdown = -1;
void MaybeThrow(absl::string_view msg) {
if (countdown-- == 0) throw TestException(msg);
}
testing::AssertionResult FailureMessage(const TestException& e,
int countdown) noexcept {
return testing::AssertionFailure()
<< "Exception number " << countdown + 1 << " thrown from " << e.what();
}
} // namespace exceptions_internal
} // namespace absl

View file

@ -0,0 +1,679 @@
// Utilities for testing exception-safety
#ifndef ABSL_BASE_INTERNAL_EXCEPTION_TESTING_H_
#define ABSL_BASE_INTERNAL_EXCEPTION_TESTING_H_
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iosfwd>
#include <string>
#include <unordered_map>
#include "gtest/gtest.h"
#include "absl/base/config.h"
#include "absl/base/internal/pretty_function.h"
#include "absl/meta/type_traits.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
namespace absl {
struct AllocInspector;
// A configuration enum for Throwing*. Operations whose flags are set will
// throw, everything else won't. This isn't meant to be exhaustive, more flags
// can always be made in the future.
enum class NoThrow : uint8_t {
kNone = 0,
kMoveCtor = 1,
kMoveAssign = 1 << 1,
kAllocation = 1 << 2,
kIntCtor = 1 << 3,
kNoThrow = static_cast<uint8_t>(-1)
};
constexpr NoThrow operator|(NoThrow a, NoThrow b) {
using T = absl::underlying_type_t<NoThrow>;
return static_cast<NoThrow>(static_cast<T>(a) | static_cast<T>(b));
}
constexpr NoThrow operator&(NoThrow a, NoThrow b) {
using T = absl::underlying_type_t<NoThrow>;
return static_cast<NoThrow>(static_cast<T>(a) & static_cast<T>(b));
}
namespace exceptions_internal {
constexpr bool ThrowingAllowed(NoThrow flags, NoThrow flag) {
return !static_cast<bool>(flags & flag);
}
// 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) {}
absl::string_view what() const { return msg_; }
private:
std::string msg_;
};
extern int countdown;
void MaybeThrow(absl::string_view msg);
testing::AssertionResult FailureMessage(const TestException& e,
int countdown) noexcept;
class TrackedObject {
protected:
explicit TrackedObject(absl::string_view child_ctor) {
if (!GetAllocs().emplace(this, child_ctor).second) {
ADD_FAILURE() << "Object at address " << static_cast<void*>(this)
<< " re-constructed in ctor " << child_ctor;
}
}
TrackedObject(const TrackedObject&) = delete;
TrackedObject(TrackedObject&&) = delete;
static std::unordered_map<TrackedObject*, absl::string_view>& GetAllocs() {
static auto* m =
new std::unordered_map<TrackedObject*, absl::string_view>();
return *m;
}
~TrackedObject() noexcept {
if (GetAllocs().erase(this) == 0) {
ADD_FAILURE() << "Object at address " << static_cast<void*>(this)
<< " destroyed improperly";
}
}
friend struct ::absl::AllocInspector;
};
} // namespace exceptions_internal
// A test class which is contextually 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)
explicit operator bool() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return b_;
}
private:
bool b_;
};
// 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 flag template arguments. That is, to make an
// ThrowingValue which has a noexcept move constructor and noexcept move
// assignment, use
// ThrowingValue<absl::NoThrow::kMoveCtor | absl::NoThrow::kMoveAssign>.
template <NoThrow Flags = NoThrow::kNone>
class ThrowingValue : private exceptions_internal::TrackedObject {
public:
ThrowingValue() : TrackedObject(ABSL_PRETTY_FUNCTION) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = 0;
}
ThrowingValue(const ThrowingValue& other)
: TrackedObject(ABSL_PRETTY_FUNCTION) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = other.dummy_;
}
ThrowingValue(ThrowingValue&& other) noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveCtor))
: TrackedObject(ABSL_PRETTY_FUNCTION) {
if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveCtor)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
dummy_ = other.dummy_;
}
explicit ThrowingValue(int i) noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kIntCtor))
: TrackedObject(ABSL_PRETTY_FUNCTION) {
if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kIntCtor)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
dummy_ = i;
}
// absl expects nothrow destructors
~ThrowingValue() noexcept = default;
ThrowingValue& operator=(const ThrowingValue& other) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = other.dummy_;
return *this;
}
ThrowingValue& operator=(ThrowingValue&& other) noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveAssign)) {
if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kMoveAssign)) {
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_, NoThrowTag{});
}
ThrowingValue operator+() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_, NoThrowTag{});
}
ThrowingValue operator-(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ - other.dummy_, NoThrowTag{});
}
ThrowingValue operator-() const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(-dummy_, NoThrowTag{});
}
ThrowingValue& operator++() {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
++dummy_;
return *this;
}
ThrowingValue operator++(int) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
auto out = ThrowingValue(dummy_, NoThrowTag{});
++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_, NoThrowTag{});
--dummy_;
return out;
}
ThrowingValue operator*(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ * other.dummy_, NoThrowTag{});
}
ThrowingValue operator/(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ / other.dummy_, NoThrowTag{});
}
ThrowingValue operator%(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ % other.dummy_, NoThrowTag{});
}
ThrowingValue operator<<(int shift) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ << shift, NoThrowTag{});
}
ThrowingValue operator>>(int shift) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ >> shift, NoThrowTag{});
}
// Comparison Operators
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_, NoThrowTag{});
}
ThrowingValue operator&(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ & other.dummy_, NoThrowTag{});
}
ThrowingValue operator|(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ | other.dummy_, NoThrowTag{});
}
ThrowingValue operator^(const ThrowingValue& other) const {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return ThrowingValue(dummy_ ^ other.dummy_, NoThrowTag{});
}
// 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&) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
return os;
}
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(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) {
if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
return ::operator new(s, std::forward<Args>(args)...);
}
template <typename... Args>
static void* operator new[](size_t s, Args&&... args) noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) {
if (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kAllocation)) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
}
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:
struct NoThrowTag {};
ThrowingValue(int i, NoThrowTag) noexcept
: TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(i) {}
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 <NoThrow N, typename T>
void operator,(const ThrowingValue<N>& ef, T&& t) = delete;
template <NoThrow N, typename T>
void operator,(T&& t, const ThrowingValue<N>& ef) = delete;
// An allocator type which is instrumented to throw at a controlled time, or not
// to throw, using NoThrow. 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, NoThrow Flags = NoThrow::kNone>
class ThrowingAllocator : private exceptions_internal::TrackedObject {
static_assert(Flags == NoThrow::kNone || Flags == NoThrow::kNoThrow,
"Invalid flag");
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, Flags == NoThrow::kNoThrow>;
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(ABSL_PRETTY_FUNCTION) {
exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION);
dummy_ = std::make_shared<const int>(next_id_++);
}
template <typename U>
ThrowingAllocator( // NOLINT
const ThrowingAllocator<U, Flags>& other) noexcept
: TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {}
ThrowingAllocator(const ThrowingAllocator& other) noexcept
: TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {}
template <typename U>
ThrowingAllocator( // NOLINT
ThrowingAllocator<U, Flags>&& other) noexcept
: TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {}
ThrowingAllocator(ThrowingAllocator&& other) noexcept
: TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {}
~ThrowingAllocator() noexcept = default;
template <typename U>
ThrowingAllocator& operator=(
const ThrowingAllocator<U, Flags>& other) noexcept {
dummy_ = other.State();
return *this;
}
template <typename U>
ThrowingAllocator& operator=(ThrowingAllocator<U, Flags>&& other) noexcept {
dummy_ = std::move(other.State());
return *this;
}
template <typename U>
struct rebind {
using other = ThrowingAllocator<U, Flags>;
};
pointer allocate(size_type n) noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) {
ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION);
return static_cast<pointer>(::operator new(n * sizeof(T)));
}
pointer allocate(size_type n, const_void_pointer) noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) {
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(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) {
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(!exceptions_internal::ThrowingAllowed(Flags,
NoThrow::kNoThrow)) {
ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION);
return std::numeric_limits<difference_type>::max() / sizeof(value_type);
}
ThrowingAllocator select_on_container_copy_construction() noexcept(
!exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) {
auto& out = *this;
ReadStateAndMaybeThrow(ABSL_PRETTY_FUNCTION);
return out;
}
template <typename U>
bool operator==(const ThrowingAllocator<U, Flags>& other) const noexcept {
return dummy_ == other.dummy_;
}
template <typename U>
bool operator!=(const ThrowingAllocator<U, Flags>& other) const noexcept {
return dummy_ != other.dummy_;
}
template <typename U, NoThrow B>
friend class ThrowingAllocator;
private:
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 (exceptions_internal::ThrowingAllowed(Flags, NoThrow::kNoThrow)) {
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, NoThrow Throws>
int ThrowingAllocator<T, Throws>::next_id_ = 0;
// Inspects the constructions and destructions of anything inheriting from
// TrackedObject. Place this as a member variable in a test fixture to ensure
// that every ThrowingValue was constructed and destroyed correctly.
struct AllocInspector {
AllocInspector() = default;
~AllocInspector() {
auto& allocs = exceptions_internal::TrackedObject::GetAllocs();
for (const auto& kv : allocs) {
ADD_FAILURE() << "Object at address " << static_cast<void*>(kv.first)
<< " constructed from " << kv.second << " not destroyed";
}
allocs.clear();
}
};
// Tests that performing operation Op on a T follows the basic exception safety
// guarantee.
//
// Parameters:
// * T: the type under test.
// * FunctionFromTPtrToVoid: A functor exercising the function under test. It
// should take a T* and return void.
//
// There must also be a function named `AbslCheckInvariants` in an associated
// namespace of T which takes a const T& and returns true if the T's class
// invariants hold, and false if they don't.
template <typename T, typename FunctionFromTPtrToVoid>
testing::AssertionResult TestBasicGuarantee(T* t, FunctionFromTPtrToVoid&& op) {
for (int countdown = 0;; ++countdown) {
exceptions_internal::countdown = countdown;
try {
op(t);
break;
} catch (const exceptions_internal::TestException& e) {
if (!AbslCheckInvariants(*t)) {
return exceptions_internal::FailureMessage(e, countdown)
<< " broke invariants.";
}
}
}
exceptions_internal::countdown = -1;
return testing::AssertionSuccess();
}
// Tests that performing operation Op on a T follows the strong exception safety
// guarantee.
//
// Parameters:
// * T: the type under test. T must be copy-constructable and
// equality-comparible.
// * FunctionFromTPtrToVoid: A functor exercising the function under test. It
// should take a T* and return void.
//
// There must also be a function named `AbslCheckInvariants` in an associated
// namespace of T which takes a const T& and returns true if the T's class
// invariants hold, and false if they don't.
template <typename T, typename FunctionFromTPtrToVoid>
testing::AssertionResult TestStrongGuarantee(T* t,
FunctionFromTPtrToVoid&& op) {
exceptions_internal::countdown = -1;
for (auto countdown = 0;; ++countdown) {
T dup = *t;
exceptions_internal::countdown = countdown;
try {
op(t);
break;
} catch (const exceptions_internal::TestException& e) {
if (!AbslCheckInvariants(*t)) {
return exceptions_internal::FailureMessage(e, countdown)
<< " broke invariants.";
}
if (dup != *t)
return exceptions_internal::FailureMessage(e, countdown)
<< " changed state.";
}
}
exceptions_internal::countdown = -1;
return testing::AssertionSuccess();
}
} // namespace absl
#endif // ABSL_BASE_INTERNAL_EXCEPTION_SAFETY_TESTING_H_

View file

@ -0,0 +1,19 @@
#ifndef ABSL_BASE_INTERNAL_PRETTY_FUNCTION_H_
#define ABSL_BASE_INTERNAL_PRETTY_FUNCTION_H_
// ABSL_PRETTY_FUNCTION
//
// In C++11, __func__ gives the undecorated name of the current function. That
// is, "main", not "int main()". Various compilers give extra macros to get the
// decorated function name, including return type and arguments, to
// differentiate between overload sets. ABSL_PRETTY_FUNCTION is a portable
// version of these macros which forwards to the correct macro on each compiler.
#if defined(_MSC_VER)
#define ABSL_PRETTY_FUNCTION __FUNCSIG__
#elif defined(__GNUC__)
#define ABSL_PRETTY_FUNCTION __PRETTY_FUNCTION__
#else
#error "Unsupported compiler"
#endif
#endif // ABSL_BASE_INTERNAL_PRETTY_FUNCTION_H_

View file

@ -1,66 +0,0 @@
// 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.
#ifndef ABSL_STRINGS_ASCII_CTYPE_H_
#define ABSL_STRINGS_ASCII_CTYPE_H_
#include "absl/strings/ascii.h"
inline bool ascii_isalpha(unsigned char c) {
return absl::ascii_isalpha(c);
}
inline bool ascii_isalnum(unsigned char c) {
return absl::ascii_isalnum(c);
}
inline bool ascii_isspace(unsigned char c) {
return absl::ascii_isspace(c);
}
inline bool ascii_ispunct(unsigned char c) {
return absl::ascii_ispunct(c);
}
inline bool ascii_isblank(unsigned char c) {
return absl::ascii_isblank(c);
}
inline bool ascii_iscntrl(unsigned char c) {
return absl::ascii_iscntrl(c);
}
inline bool ascii_isxdigit(unsigned char c) {
return absl::ascii_isxdigit(c);
}
inline bool ascii_isdigit(unsigned char c) {
return absl::ascii_isdigit(c);
}
inline bool ascii_isprint(unsigned char c) {
return absl::ascii_isprint(c);
}
inline bool ascii_isgraph(unsigned char c) {
return absl::ascii_isgraph(c);
}
inline bool ascii_isupper(unsigned char c) {
return absl::ascii_isupper(c);
}
inline bool ascii_islower(unsigned char c) {
return absl::ascii_islower(c);
}
inline bool ascii_isascii(unsigned char c) {
return absl::ascii_isascii(c);
}
inline char ascii_tolower(unsigned char c) {
return absl::ascii_tolower(c);
}
inline char ascii_toupper(unsigned char c) {
return absl::ascii_toupper(c);
}
#endif // ABSL_STRINGS_ASCII_CTYPE_H_