aboutsummaryrefslogtreecommitdiff
path: root/third_party/abseil-cpp/absl/types/variant_exception_safety_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/abseil-cpp/absl/types/variant_exception_safety_test.cc')
-rw-r--r--third_party/abseil-cpp/absl/types/variant_exception_safety_test.cc532
1 files changed, 532 insertions, 0 deletions
diff --git a/third_party/abseil-cpp/absl/types/variant_exception_safety_test.cc b/third_party/abseil-cpp/absl/types/variant_exception_safety_test.cc
new file mode 100644
index 0000000000..439c6e1df3
--- /dev/null
+++ b/third_party/abseil-cpp/absl/types/variant_exception_safety_test.cc
@@ -0,0 +1,532 @@
+// 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.
+
+#include "absl/types/variant.h"
+
+#include "absl/base/config.h"
+
+// This test is a no-op when absl::variant is an alias for std::variant and when
+// exceptions are not enabled.
+#if !defined(ABSL_USES_STD_VARIANT) && defined(ABSL_HAVE_EXCEPTIONS)
+
+#include <iostream>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "absl/base/internal/exception_safety_testing.h"
+#include "absl/memory/memory.h"
+
+// See comment in absl/base/config.h
+#if !defined(ABSL_INTERNAL_MSVC_2017_DBG_MODE)
+
+namespace absl {
+ABSL_NAMESPACE_BEGIN
+namespace {
+
+using ::testing::MakeExceptionSafetyTester;
+using ::testing::strong_guarantee;
+using ::testing::TestNothrowOp;
+using ::testing::TestThrowingCtor;
+
+using Thrower = testing::ThrowingValue<>;
+using CopyNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowCopy>;
+using MoveNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowMove>;
+using ThrowingAlloc = testing::ThrowingAllocator<Thrower>;
+using ThrowerVec = std::vector<Thrower, ThrowingAlloc>;
+using ThrowingVariant =
+ absl::variant<Thrower, CopyNothrow, MoveNothrow, ThrowerVec>;
+
+struct ConversionException {};
+
+template <class T>
+struct ExceptionOnConversion {
+ operator T() const { // NOLINT
+ throw ConversionException();
+ }
+};
+
+// Forces a variant into the valueless by exception state.
+void ToValuelessByException(ThrowingVariant& v) { // NOLINT
+ try {
+ v.emplace<Thrower>();
+ v.emplace<Thrower>(ExceptionOnConversion<Thrower>());
+ } catch (const ConversionException&) {
+ // This space intentionally left blank.
+ }
+}
+
+// Check that variant is still in a usable state after an exception is thrown.
+testing::AssertionResult VariantInvariants(ThrowingVariant* v) {
+ using testing::AssertionFailure;
+ using testing::AssertionSuccess;
+
+ // Try using the active alternative
+ if (absl::holds_alternative<Thrower>(*v)) {
+ auto& t = absl::get<Thrower>(*v);
+ t = Thrower{-100};
+ if (t.Get() != -100) {
+ return AssertionFailure() << "Thrower should be assigned -100";
+ }
+ } else if (absl::holds_alternative<ThrowerVec>(*v)) {
+ auto& tv = absl::get<ThrowerVec>(*v);
+ tv.clear();
+ tv.emplace_back(-100);
+ if (tv.size() != 1 || tv[0].Get() != -100) {
+ return AssertionFailure() << "ThrowerVec should be {Thrower{-100}}";
+ }
+ } else if (absl::holds_alternative<CopyNothrow>(*v)) {
+ auto& t = absl::get<CopyNothrow>(*v);
+ t = CopyNothrow{-100};
+ if (t.Get() != -100) {
+ return AssertionFailure() << "CopyNothrow should be assigned -100";
+ }
+ } else if (absl::holds_alternative<MoveNothrow>(*v)) {
+ auto& t = absl::get<MoveNothrow>(*v);
+ t = MoveNothrow{-100};
+ if (t.Get() != -100) {
+ return AssertionFailure() << "MoveNothrow should be assigned -100";
+ }
+ }
+
+ // Try making variant valueless_by_exception
+ if (!v->valueless_by_exception()) ToValuelessByException(*v);
+ if (!v->valueless_by_exception()) {
+ return AssertionFailure() << "Variant should be valueless_by_exception";
+ }
+ try {
+ auto unused = absl::get<Thrower>(*v);
+ static_cast<void>(unused);
+ return AssertionFailure() << "Variant should not contain Thrower";
+ } catch (const absl::bad_variant_access&) {
+ } catch (...) {
+ return AssertionFailure() << "Unexpected exception throw from absl::get";
+ }
+
+ // Try using the variant
+ v->emplace<Thrower>(100);
+ if (!absl::holds_alternative<Thrower>(*v) ||
+ absl::get<Thrower>(*v) != Thrower(100)) {
+ return AssertionFailure() << "Variant should contain Thrower(100)";
+ }
+ v->emplace<ThrowerVec>({Thrower(100)});
+ if (!absl::holds_alternative<ThrowerVec>(*v) ||
+ absl::get<ThrowerVec>(*v)[0] != Thrower(100)) {
+ return AssertionFailure()
+ << "Variant should contain ThrowerVec{Thrower(100)}";
+ }
+ return AssertionSuccess();
+}
+
+template <typename... Args>
+Thrower ExpectedThrower(Args&&... args) {
+ return Thrower(42, args...);
+}
+
+ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; }
+ThrowingVariant ValuelessByException() {
+ ThrowingVariant v;
+ ToValuelessByException(v);
+ return v;
+}
+ThrowingVariant WithThrower() { return Thrower(39); }
+ThrowingVariant WithThrowerVec() {
+ return ThrowerVec{Thrower(1), Thrower(2), Thrower(3)};
+}
+ThrowingVariant WithCopyNoThrow() { return CopyNothrow(39); }
+ThrowingVariant WithMoveNoThrow() { return MoveNothrow(39); }
+
+TEST(VariantExceptionSafetyTest, DefaultConstructor) {
+ TestThrowingCtor<ThrowingVariant>();
+}
+
+TEST(VariantExceptionSafetyTest, CopyConstructor) {
+ {
+ ThrowingVariant v(ExpectedThrower());
+ TestThrowingCtor<ThrowingVariant>(v);
+ }
+ {
+ ThrowingVariant v(ExpectedThrowerVec());
+ TestThrowingCtor<ThrowingVariant>(v);
+ }
+ {
+ ThrowingVariant v(ValuelessByException());
+ TestThrowingCtor<ThrowingVariant>(v);
+ }
+}
+
+TEST(VariantExceptionSafetyTest, MoveConstructor) {
+ {
+ ThrowingVariant v(ExpectedThrower());
+ TestThrowingCtor<ThrowingVariant>(std::move(v));
+ }
+ {
+ ThrowingVariant v(ExpectedThrowerVec());
+ TestThrowingCtor<ThrowingVariant>(std::move(v));
+ }
+ {
+ ThrowingVariant v(ValuelessByException());
+ TestThrowingCtor<ThrowingVariant>(std::move(v));
+ }
+}
+
+TEST(VariantExceptionSafetyTest, ValueConstructor) {
+ TestThrowingCtor<ThrowingVariant>(ExpectedThrower());
+ TestThrowingCtor<ThrowingVariant>(ExpectedThrowerVec());
+}
+
+TEST(VariantExceptionSafetyTest, InPlaceTypeConstructor) {
+ TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<Thrower>{},
+ ExpectedThrower());
+ TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<ThrowerVec>{},
+ ExpectedThrowerVec());
+}
+
+TEST(VariantExceptionSafetyTest, InPlaceIndexConstructor) {
+ TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<0>{},
+ ExpectedThrower());
+ TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<3>{},
+ ExpectedThrowerVec());
+}
+
+TEST(VariantExceptionSafetyTest, CopyAssign) {
+ // variant& operator=(const variant& rhs);
+ // Let j be rhs.index()
+ {
+ // - neither *this nor rhs holds a value
+ const ThrowingVariant rhs = ValuelessByException();
+ ThrowingVariant lhs = ValuelessByException();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
+ }
+ {
+ // - *this holds a value but rhs does not
+ const ThrowingVariant rhs = ValuelessByException();
+ ThrowingVariant lhs = WithThrower();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
+ }
+ // - index() == j
+ {
+ const ThrowingVariant rhs(ExpectedThrower());
+ auto tester =
+ MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+ EXPECT_TRUE(tester.WithContracts(VariantInvariants).Test());
+ EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
+ }
+ {
+ const ThrowingVariant rhs(ExpectedThrowerVec());
+ auto tester =
+ MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrowerVec())
+ .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+ EXPECT_TRUE(tester.WithContracts(VariantInvariants).Test());
+ EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
+ }
+ // libstdc++ std::variant has bugs on copy assignment regarding exception
+ // safety.
+#if !(defined(ABSL_USES_STD_VARIANT) && defined(__GLIBCXX__))
+ // index() != j
+ // if is_nothrow_copy_constructible_v<Tj> or
+ // !is_nothrow_move_constructible<Tj> is true, equivalent to
+ // emplace<j>(get<j>(rhs))
+ {
+ // is_nothrow_copy_constructible_v<Tj> == true
+ // should not throw because emplace() invokes Tj's copy ctor
+ // which should not throw.
+ const ThrowingVariant rhs(CopyNothrow{});
+ ThrowingVariant lhs = WithThrower();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
+ }
+ {
+ // is_nothrow_copy_constructible<Tj> == false &&
+ // is_nothrow_move_constructible<Tj> == false
+ // should provide basic guarantee because emplace() invokes Tj's copy ctor
+ // which may throw.
+ const ThrowingVariant rhs(ExpectedThrower());
+ auto tester =
+ MakeExceptionSafetyTester()
+ .WithInitialValue(WithCopyNoThrow())
+ .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+ EXPECT_TRUE(tester
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* lhs) {
+ return lhs->valueless_by_exception();
+ })
+ .Test());
+ EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
+ }
+#endif // !(defined(ABSL_USES_STD_VARIANT) && defined(__GLIBCXX__))
+ {
+ // is_nothrow_copy_constructible_v<Tj> == false &&
+ // is_nothrow_move_constructible_v<Tj> == true
+ // should provide strong guarantee because it is equivalent to
+ // operator=(variant(rhs)) which creates a temporary then invoke the move
+ // ctor which shouldn't throw.
+ const ThrowingVariant rhs(MoveNothrow{});
+ EXPECT_TRUE(MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithContracts(VariantInvariants, strong_guarantee)
+ .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
+ }
+}
+
+TEST(VariantExceptionSafetyTest, MoveAssign) {
+ // variant& operator=(variant&& rhs);
+ // Let j be rhs.index()
+ {
+ // - neither *this nor rhs holds a value
+ ThrowingVariant rhs = ValuelessByException();
+ ThrowingVariant lhs = ValuelessByException();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
+ }
+ {
+ // - *this holds a value but rhs does not
+ ThrowingVariant rhs = ValuelessByException();
+ ThrowingVariant lhs = WithThrower();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
+ }
+ {
+ // - index() == j
+ // assign get<j>(std::move(rhs)) to the value contained in *this.
+ // If an exception is thrown during call to Tj's move assignment, the state
+ // of the contained value is as defined by the exception safety guarantee of
+ // Tj's move assignment; index() will be j.
+ ThrowingVariant rhs(ExpectedThrower());
+ size_t j = rhs.index();
+ // Since Thrower's move assignment has basic guarantee, so should variant's.
+ auto tester = MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithOperation([&](ThrowingVariant* lhs) {
+ auto copy = rhs;
+ *lhs = std::move(copy);
+ });
+ EXPECT_TRUE(tester
+ .WithContracts(
+ VariantInvariants,
+ [&](ThrowingVariant* lhs) { return lhs->index() == j; })
+ .Test());
+ EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
+ }
+ {
+ // libstdc++ introduced a regression between 2018-09-25 and 2019-01-06.
+ // The fix is targeted for gcc-9.
+ // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87431#c7
+ // https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=267614
+#if !(defined(ABSL_USES_STD_VARIANT) && \
+ defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8)
+ // - otherwise (index() != j), equivalent to
+ // emplace<j>(get<j>(std::move(rhs)))
+ // - If an exception is thrown during the call to Tj's move construction
+ // (with j being rhs.index()), the variant will hold no value.
+ ThrowingVariant rhs(CopyNothrow{});
+ EXPECT_TRUE(MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* lhs) {
+ return lhs->valueless_by_exception();
+ })
+ .Test([&](ThrowingVariant* lhs) {
+ auto copy = rhs;
+ *lhs = std::move(copy);
+ }));
+#endif // !(defined(ABSL_USES_STD_VARIANT) &&
+ // defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8)
+ }
+}
+
+TEST(VariantExceptionSafetyTest, ValueAssign) {
+ // template<class T> variant& operator=(T&& t);
+ // Let Tj be the type that is selected by overload resolution to be assigned.
+ {
+ // If *this holds a Tj, assigns std::forward<T>(t) to the value contained in
+ // *this. If an exception is thrown during the assignment of
+ // std::forward<T>(t) to the value contained in *this, the state of the
+ // contained value and t are as defined by the exception safety guarantee of
+ // the assignment expression; valueless_by_exception() will be false.
+ // Since Thrower's copy/move assignment has basic guarantee, so should
+ // variant's.
+ Thrower rhs = ExpectedThrower();
+ // copy assign
+ auto copy_tester =
+ MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithOperation([rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+ EXPECT_TRUE(copy_tester
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* lhs) {
+ return !lhs->valueless_by_exception();
+ })
+ .Test());
+ EXPECT_FALSE(copy_tester.WithContracts(strong_guarantee).Test());
+ // move assign
+ auto move_tester = MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithOperation([&](ThrowingVariant* lhs) {
+ auto copy = rhs;
+ *lhs = std::move(copy);
+ });
+ EXPECT_TRUE(move_tester
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* lhs) {
+ return !lhs->valueless_by_exception();
+ })
+ .Test());
+
+ EXPECT_FALSE(move_tester.WithContracts(strong_guarantee).Test());
+ }
+ // Otherwise (*this holds something else), if is_nothrow_constructible_v<Tj,
+ // T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to
+ // emplace<j>(std::forward<T>(t)).
+ // We simplify the test by letting T = `const Tj&` or `Tj&&`, so we can reuse
+ // the CopyNothrow and MoveNothrow types.
+
+ // if is_nothrow_constructible_v<Tj, T>
+ // (i.e. is_nothrow_copy/move_constructible_v<Tj>) is true, emplace() just
+ // invokes the copy/move constructor and it should not throw.
+ {
+ const CopyNothrow rhs;
+ ThrowingVariant lhs = WithThrower();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
+ }
+ {
+ MoveNothrow rhs;
+ ThrowingVariant lhs = WithThrower();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
+ }
+ // if is_nothrow_constructible_v<Tj, T> == false &&
+ // is_nothrow_move_constructible<Tj> == false
+ // emplace() invokes the copy/move constructor which may throw so it should
+ // provide basic guarantee and variant object might not hold a value.
+ {
+ Thrower rhs = ExpectedThrower();
+ // copy
+ auto copy_tester =
+ MakeExceptionSafetyTester()
+ .WithInitialValue(WithCopyNoThrow())
+ .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+ EXPECT_TRUE(copy_tester
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* lhs) {
+ return lhs->valueless_by_exception();
+ })
+ .Test());
+ EXPECT_FALSE(copy_tester.WithContracts(strong_guarantee).Test());
+ // move
+ auto move_tester = MakeExceptionSafetyTester()
+ .WithInitialValue(WithCopyNoThrow())
+ .WithOperation([](ThrowingVariant* lhs) {
+ *lhs = ExpectedThrower(testing::nothrow_ctor);
+ });
+ EXPECT_TRUE(move_tester
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* lhs) {
+ return lhs->valueless_by_exception();
+ })
+ .Test());
+ EXPECT_FALSE(move_tester.WithContracts(strong_guarantee).Test());
+ }
+ // Otherwise (if is_nothrow_constructible_v<Tj, T> == false &&
+ // is_nothrow_move_constructible<Tj> == true),
+ // equivalent to operator=(variant(std::forward<T>(t)))
+ // This should have strong guarantee because it creates a temporary variant
+ // and operator=(variant&&) invokes Tj's move ctor which doesn't throw.
+ // libstdc++ std::variant has bugs on conversion assignment regarding
+ // exception safety.
+#if !(defined(ABSL_USES_STD_VARIANT) && defined(__GLIBCXX__))
+ {
+ MoveNothrow rhs;
+ EXPECT_TRUE(MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithContracts(VariantInvariants, strong_guarantee)
+ .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
+ }
+#endif // !(defined(ABSL_USES_STD_VARIANT) && defined(__GLIBCXX__))
+}
+
+TEST(VariantExceptionSafetyTest, Emplace) {
+ // If an exception during the initialization of the contained value, the
+ // variant might not hold a value. The standard requires emplace() to provide
+ // only basic guarantee.
+ {
+ Thrower args = ExpectedThrower();
+ auto tester = MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithOperation([&args](ThrowingVariant* v) {
+ v->emplace<Thrower>(args);
+ });
+ EXPECT_TRUE(tester
+ .WithContracts(VariantInvariants,
+ [](ThrowingVariant* v) {
+ return v->valueless_by_exception();
+ })
+ .Test());
+ EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
+ }
+}
+
+TEST(VariantExceptionSafetyTest, Swap) {
+ // if both are valueless_by_exception(), no effect
+ {
+ ThrowingVariant rhs = ValuelessByException();
+ ThrowingVariant lhs = ValuelessByException();
+ EXPECT_TRUE(TestNothrowOp([&]() { lhs.swap(rhs); }));
+ }
+ // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs))
+ // where i is index().
+ {
+ ThrowingVariant rhs = ExpectedThrower();
+ EXPECT_TRUE(MakeExceptionSafetyTester()
+ .WithInitialValue(WithThrower())
+ .WithContracts(VariantInvariants)
+ .Test([&](ThrowingVariant* lhs) {
+ auto copy = rhs;
+ lhs->swap(copy);
+ }));
+ }
+ // Otherwise, exchanges the value of rhs and *this. The exception safety
+ // involves variant in moved-from state which is not specified in the
+ // standard, and since swap is 3-step it's impossible for it to provide a
+ // overall strong guarantee. So, we are only checking basic guarantee here.
+ {
+ ThrowingVariant rhs = ExpectedThrower();
+ EXPECT_TRUE(MakeExceptionSafetyTester()
+ .WithInitialValue(WithCopyNoThrow())
+ .WithContracts(VariantInvariants)
+ .Test([&](ThrowingVariant* lhs) {
+ auto copy = rhs;
+ lhs->swap(copy);
+ }));
+ }
+ {
+ ThrowingVariant rhs = ExpectedThrower();
+ EXPECT_TRUE(MakeExceptionSafetyTester()
+ .WithInitialValue(WithCopyNoThrow())
+ .WithContracts(VariantInvariants)
+ .Test([&](ThrowingVariant* lhs) {
+ auto copy = rhs;
+ copy.swap(*lhs);
+ }));
+ }
+}
+
+} // namespace
+ABSL_NAMESPACE_END
+} // namespace absl
+
+#endif // !defined(ABSL_INTERNAL_MSVC_2017_DBG_MODE)
+
+#endif // #if !defined(ABSL_USES_STD_VARIANT) && defined(ABSL_HAVE_EXCEPTIONS)