about summary refs log tree commit diff
path: root/absl/types
diff options
context:
space:
mode:
Diffstat (limited to 'absl/types')
-rw-r--r--absl/types/BUILD.bazel15
-rw-r--r--absl/types/CMakeLists.txt14
-rw-r--r--absl/types/optional.h38
-rw-r--r--absl/types/variant.h2
-rw-r--r--absl/types/variant_exception_safety_test.cc519
5 files changed, 567 insertions, 21 deletions
diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel
index 0bdb2f78aee0..f8d53c263bab 100644
--- a/absl/types/BUILD.bazel
+++ b/absl/types/BUILD.bazel
@@ -223,3 +223,18 @@ cc_test(
         "@com_google_googletest//:gtest_main",
     ],
 )
+
+cc_test(
+    name = "variant_exception_safety_test",
+    size = "small",
+    srcs = [
+        "variant_exception_safety_test.cc",
+    ],
+    copts = ABSL_TEST_COPTS + ABSL_EXCEPTIONS_FLAG,
+    deps = [
+        ":variant",
+        "//absl/base:exception_safety_testing",
+        "//absl/memory",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt
index f51d126f8160..fbd8374031da 100644
--- a/absl/types/CMakeLists.txt
+++ b/absl/types/CMakeLists.txt
@@ -29,6 +29,9 @@ absl_header_library(
   TARGET
     absl_any
   PUBLIC_LIBRARIES
+    absl::bad_any_cast
+    absl::base
+    absl::meta
     absl::utility
   PRIVATE_COMPILE_FLAGS
     ${ABSL_EXCEPTIONS_FLAG}
@@ -59,7 +62,6 @@ absl_library(
   SOURCES
     ${BAD_ANY_CAST_SRC}
   PUBLIC_LIBRARIES
-    absl::base absl::any
   EXPORT_NAME
     bad_any_cast
 )
@@ -76,7 +78,11 @@ absl_library(
   SOURCES
     ${OPTIONAL_SRC}
   PUBLIC_LIBRARIES
+    absl::bad_optional_access
     absl::base
+    absl::memory
+    absl::meta
+    absl::utility
   EXPORT_NAME
     optional
 )
@@ -143,7 +149,11 @@ absl_test(
 
 # test any_exception_safety_test
 set(ANY_EXCEPTION_SAFETY_TEST_SRC "any_exception_safety_test.cc")
-set(ANY_EXCEPTION_SAFETY_TEST_PUBLIC_LIBRARIES absl::any absl::base absl::base_internal_exception_safety_testing)
+set(ANY_EXCEPTION_SAFETY_TEST_PUBLIC_LIBRARIES
+  absl::any
+  absl::base
+  absl_base_internal_exception_safety_testing
+)
 
 absl_test(
   TARGET
diff --git a/absl/types/optional.h b/absl/types/optional.h
index 98b29e591d15..4dcf479615bd 100644
--- a/absl/types/optional.h
+++ b/absl/types/optional.h
@@ -958,7 +958,8 @@ constexpr auto operator==(const optional<T>& x, const optional<U>& y)
     -> decltype(optional_internal::convertible_to_bool(*x == *y)) {
   return static_cast<bool>(x) != static_cast<bool>(y)
              ? false
-             : static_cast<bool>(x) == false ? true : *x == *y;
+             : static_cast<bool>(x) == false ? true
+                                             : static_cast<bool>(*x == *y);
 }
 
 // Returns: If bool(x) != bool(y), true; otherwise, if bool(x) == false, false;
@@ -968,31 +969,32 @@ constexpr auto operator!=(const optional<T>& x, const optional<U>& y)
     -> decltype(optional_internal::convertible_to_bool(*x != *y)) {
   return static_cast<bool>(x) != static_cast<bool>(y)
              ? true
-             : static_cast<bool>(x) == false ? false : *x != *y;
+             : static_cast<bool>(x) == false ? false
+                                             : static_cast<bool>(*x != *y);
 }
 // Returns: If !y, false; otherwise, if !x, true; otherwise *x < *y.
 template <typename T, typename U>
 constexpr auto operator<(const optional<T>& x, const optional<U>& y)
     -> decltype(optional_internal::convertible_to_bool(*x < *y)) {
-  return !y ? false : !x ? true : *x < *y;
+  return !y ? false : !x ? true : static_cast<bool>(*x < *y);
 }
 // Returns: If !x, false; otherwise, if !y, true; otherwise *x > *y.
 template <typename T, typename U>
 constexpr auto operator>(const optional<T>& x, const optional<U>& y)
     -> decltype(optional_internal::convertible_to_bool(*x > *y)) {
-  return !x ? false : !y ? true : *x > *y;
+  return !x ? false : !y ? true : static_cast<bool>(*x > *y);
 }
 // Returns: If !x, true; otherwise, if !y, false; otherwise *x <= *y.
 template <typename T, typename U>
 constexpr auto operator<=(const optional<T>& x, const optional<U>& y)
     -> decltype(optional_internal::convertible_to_bool(*x <= *y)) {
-  return !x ? true : !y ? false : *x <= *y;
+  return !x ? true : !y ? false : static_cast<bool>(*x <= *y);
 }
 // Returns: If !y, true; otherwise, if !x, false; otherwise *x >= *y.
 template <typename T, typename U>
 constexpr auto operator>=(const optional<T>& x, const optional<U>& y)
     -> decltype(optional_internal::convertible_to_bool(*x >= *y)) {
-  return !y ? true : !x ? false : *x >= *y;
+  return !y ? true : !x ? false : static_cast<bool>(*x >= *y);
 }
 
 // Comparison with nullopt [optional.nullops]
@@ -1054,62 +1056,62 @@ constexpr bool operator>=(nullopt_t, const optional<T>& x) noexcept {
 template <typename T, typename U>
 constexpr auto operator==(const optional<T>& x, const U& v)
     -> decltype(optional_internal::convertible_to_bool(*x == v)) {
-  return static_cast<bool>(x) ? *x == v : false;
+  return static_cast<bool>(x) ? static_cast<bool>(*x == v) : false;
 }
 template <typename T, typename U>
 constexpr auto operator==(const U& v, const optional<T>& x)
     -> decltype(optional_internal::convertible_to_bool(v == *x)) {
-  return static_cast<bool>(x) ? v == *x : false;
+  return static_cast<bool>(x) ? static_cast<bool>(v == *x) : false;
 }
 template <typename T, typename U>
 constexpr auto operator!=(const optional<T>& x, const U& v)
     -> decltype(optional_internal::convertible_to_bool(*x != v)) {
-  return static_cast<bool>(x) ? *x != v : true;
+  return static_cast<bool>(x) ? static_cast<bool>(*x != v) : true;
 }
 template <typename T, typename U>
 constexpr auto operator!=(const U& v, const optional<T>& x)
     -> decltype(optional_internal::convertible_to_bool(v != *x)) {
-  return static_cast<bool>(x) ? v != *x : true;
+  return static_cast<bool>(x) ? static_cast<bool>(v != *x) : true;
 }
 template <typename T, typename U>
 constexpr auto operator<(const optional<T>& x, const U& v)
     -> decltype(optional_internal::convertible_to_bool(*x < v)) {
-  return static_cast<bool>(x) ? *x < v : true;
+  return static_cast<bool>(x) ? static_cast<bool>(*x < v) : true;
 }
 template <typename T, typename U>
 constexpr auto operator<(const U& v, const optional<T>& x)
     -> decltype(optional_internal::convertible_to_bool(v < *x)) {
-  return static_cast<bool>(x) ? v < *x : false;
+  return static_cast<bool>(x) ? static_cast<bool>(v < *x) : false;
 }
 template <typename T, typename U>
 constexpr auto operator<=(const optional<T>& x, const U& v)
     -> decltype(optional_internal::convertible_to_bool(*x <= v)) {
-  return static_cast<bool>(x) ? *x <= v : true;
+  return static_cast<bool>(x) ? static_cast<bool>(*x <= v) : true;
 }
 template <typename T, typename U>
 constexpr auto operator<=(const U& v, const optional<T>& x)
     -> decltype(optional_internal::convertible_to_bool(v <= *x)) {
-  return static_cast<bool>(x) ? v <= *x : false;
+  return static_cast<bool>(x) ? static_cast<bool>(v <= *x) : false;
 }
 template <typename T, typename U>
 constexpr auto operator>(const optional<T>& x, const U& v)
     -> decltype(optional_internal::convertible_to_bool(*x > v)) {
-  return static_cast<bool>(x) ? *x > v : false;
+  return static_cast<bool>(x) ? static_cast<bool>(*x > v) : false;
 }
 template <typename T, typename U>
 constexpr auto operator>(const U& v, const optional<T>& x)
     -> decltype(optional_internal::convertible_to_bool(v > *x)) {
-  return static_cast<bool>(x) ? v > *x : true;
+  return static_cast<bool>(x) ? static_cast<bool>(v > *x) : true;
 }
 template <typename T, typename U>
 constexpr auto operator>=(const optional<T>& x, const U& v)
     -> decltype(optional_internal::convertible_to_bool(*x >= v)) {
-  return static_cast<bool>(x) ? *x >= v : false;
+  return static_cast<bool>(x) ? static_cast<bool>(*x >= v) : false;
 }
 template <typename T, typename U>
 constexpr auto operator>=(const U& v, const optional<T>& x)
     -> decltype(optional_internal::convertible_to_bool(v >= *x)) {
-  return static_cast<bool>(x) ? v >= *x : true;
+  return static_cast<bool>(x) ? static_cast<bool>(v >= *x) : true;
 }
 
 }  // namespace absl
diff --git a/absl/types/variant.h b/absl/types/variant.h
index 52a311e37d3b..7ae65abe0e6d 100644
--- a/absl/types/variant.h
+++ b/absl/types/variant.h
@@ -350,7 +350,7 @@ constexpr const variant_alternative_t<I, variant<Types...>>&& get(
 // get_if()
 //
 // Returns a pointer to the value currently stored within a given variant, if
-// present, using either a unique alternative type amonst the variant's set of
+// present, using either a unique alternative type amongst the variant's set of
 // alternative types, or the variant's index value. If such a value does not
 // exist, returns `nullptr`.
 //
diff --git a/absl/types/variant_exception_safety_test.cc b/absl/types/variant_exception_safety_test.cc
new file mode 100644
index 000000000000..377e4afac572
--- /dev/null
+++ b/absl/types/variant_exception_safety_test.cc
@@ -0,0 +1,519 @@
+// 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.
+#include "absl/types/variant.h"
+
+#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"
+
+namespace absl {
+namespace {
+
+using ::testing::MakeExceptionSafetyTester;
+using ::testing::nothrow_guarantee;
+using ::testing::strong_guarantee;
+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 (ConversionException& /*e*/) {
+    // This space intentionally left blank.
+  }
+}
+
+// Check that variant is still in a usable state after an exception is thrown.
+testing::AssertionResult CheckInvariants(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 (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();
+}
+
+Thrower ExpectedThrower() { return Thrower(42); }
+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();
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(ValuelessByException())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
+  }
+  {
+    // - *this holds a value but rhs does not
+    const ThrowingVariant rhs = ValuelessByException();
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(WithThrower())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
+  }
+  // - index() == j
+  {
+    const ThrowingVariant rhs(ExpectedThrower());
+    auto tester =
+        MakeExceptionSafetyTester()
+            .WithInitialValue(WithThrower())
+            .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+    EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test());
+    EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test());
+  }
+  {
+    const ThrowingVariant rhs(ExpectedThrowerVec());
+    auto tester =
+        MakeExceptionSafetyTester()
+            .WithInitialValue(WithThrowerVec())
+            .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
+    EXPECT_TRUE(tester.WithInvariants(CheckInvariants).Test());
+    EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test());
+  }
+  // libstdc++ std::variant has bugs on copy assignment regarding exception
+  // safety.
+#if !(defined(ABSL_HAVE_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{});
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(WithThrower())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([&rhs](ThrowingVariant* lhs) { *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
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* lhs) {
+                                      return lhs->valueless_by_exception();
+                                    })
+                    .Test());
+    EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test());
+  }
+#endif  // !(defined(ABSL_HAVE_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())
+                    .WithInvariants(CheckInvariants, 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();
+
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(ValuelessByException())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([rhs](ThrowingVariant* lhs) mutable {
+                      *lhs = std::move(rhs);
+                    }));
+  }
+  {
+    // - *this holds a value but rhs does not
+    ThrowingVariant rhs = ValuelessByException();
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(WithThrower())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([rhs](ThrowingVariant* lhs) mutable {
+                      *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([rhs](ThrowingVariant* lhs) mutable {
+                        *lhs = std::move(rhs);
+                      });
+    EXPECT_TRUE(tester
+                    .WithInvariants(
+                        CheckInvariants,
+                        [j](ThrowingVariant* lhs) { return lhs->index() == j; })
+                    .Test());
+    EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test());
+  }
+  {
+    // - 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())
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* lhs) {
+                                      return lhs->valueless_by_exception();
+                                    })
+                    .Test([rhs](ThrowingVariant* lhs) mutable {
+                      *lhs = std::move(rhs);
+                    }));
+  }
+}
+
+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
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* lhs) {
+                                      return !lhs->valueless_by_exception();
+                                    })
+                    .Test());
+    EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test());
+    // move assign
+    auto move_tester = MakeExceptionSafetyTester()
+                           .WithInitialValue(WithThrower())
+                           .WithOperation([rhs](ThrowingVariant* lhs) mutable {
+                             *lhs = std::move(rhs);
+                           });
+    EXPECT_TRUE(move_tester
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* lhs) {
+                                      return !lhs->valueless_by_exception();
+                                    })
+                    .Test());
+
+    EXPECT_FALSE(move_tester.WithInvariants(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;
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(WithThrower())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
+  }
+  {
+    MoveNothrow rhs;
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(WithThrower())
+                    .WithInvariants(nothrow_guarantee)
+                    .Test([rhs](ThrowingVariant* lhs) mutable {
+                      *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
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* lhs) {
+                                      return lhs->valueless_by_exception();
+                                    })
+                    .Test());
+    EXPECT_FALSE(copy_tester.WithInvariants(strong_guarantee).Test());
+    // move
+    auto move_tester = MakeExceptionSafetyTester()
+                           .WithInitialValue(WithCopyNoThrow())
+                           .WithOperation([rhs](ThrowingVariant* lhs) mutable {
+                             *lhs = std::move(rhs);
+                           });
+    EXPECT_TRUE(move_tester
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* lhs) {
+                                      return lhs->valueless_by_exception();
+                                    })
+                    .Test());
+    EXPECT_FALSE(move_tester.WithInvariants(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_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
+  {
+    MoveNothrow rhs;
+    EXPECT_TRUE(MakeExceptionSafetyTester()
+                    .WithInitialValue(WithThrower())
+                    .WithInvariants(CheckInvariants, strong_guarantee)
+                    .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
+  }
+#endif  // !(defined(ABSL_HAVE_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
+                    .WithInvariants(CheckInvariants,
+                                    [](ThrowingVariant* v) {
+                                      return v->valueless_by_exception();
+                                    })
+                    .Test());
+    EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test());
+  }
+}
+
+TEST(VariantExceptionSafetyTest, Swap) {
+  // if both are valueless_by_exception(), no effect
+  {
+    ThrowingVariant rhs = ValuelessByException();
+    EXPECT_TRUE(
+        MakeExceptionSafetyTester()
+            .WithInitialValue(ValuelessByException())
+            .WithInvariants(nothrow_guarantee)
+            .Test([rhs](ThrowingVariant* lhs) mutable { 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())
+            .WithInvariants(CheckInvariants)
+            .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); }));
+  }
+  // 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())
+            .WithInvariants(CheckInvariants)
+            .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); }));
+  }
+  {
+    ThrowingVariant rhs = ExpectedThrower();
+    EXPECT_TRUE(
+        MakeExceptionSafetyTester()
+            .WithInitialValue(WithCopyNoThrow())
+            .WithInvariants(CheckInvariants)
+            .Test([rhs](ThrowingVariant* lhs) mutable { rhs.swap(*lhs); }));
+  }
+}
+
+}  // namespace
+}  // namespace absl