variant_exception_safety_test.cc
Go to the documentation of this file.
00001 // Copyright 2017 The Abseil Authors.
00002 //
00003 // Licensed under the Apache License, Version 2.0 (the "License");
00004 // you may not use this file except in compliance with the License.
00005 // You may obtain a copy of the License at
00006 //
00007 //      https://www.apache.org/licenses/LICENSE-2.0
00008 //
00009 // Unless required by applicable law or agreed to in writing, software
00010 // distributed under the License is distributed on an "AS IS" BASIS,
00011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00012 // See the License for the specific language governing permissions and
00013 // limitations under the License.
00014 
00015 #include "absl/types/variant.h"
00016 
00017 #include <iostream>
00018 #include <memory>
00019 #include <utility>
00020 #include <vector>
00021 
00022 #include "gmock/gmock.h"
00023 #include "gtest/gtest.h"
00024 #include "absl/base/config.h"
00025 #include "absl/base/internal/exception_safety_testing.h"
00026 #include "absl/memory/memory.h"
00027 
00028 // See comment in absl/base/config.h
00029 #if !defined(ABSL_INTERNAL_MSVC_2017_DBG_MODE)
00030 
00031 namespace absl {
00032 namespace {
00033 
00034 using ::testing::MakeExceptionSafetyTester;
00035 using ::testing::strong_guarantee;
00036 using ::testing::TestNothrowOp;
00037 using ::testing::TestThrowingCtor;
00038 
00039 using Thrower = testing::ThrowingValue<>;
00040 using CopyNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowCopy>;
00041 using MoveNothrow = testing::ThrowingValue<testing::TypeSpec::kNoThrowMove>;
00042 using ThrowingAlloc = testing::ThrowingAllocator<Thrower>;
00043 using ThrowerVec = std::vector<Thrower, ThrowingAlloc>;
00044 using ThrowingVariant =
00045     absl::variant<Thrower, CopyNothrow, MoveNothrow, ThrowerVec>;
00046 
00047 struct ConversionException {};
00048 
00049 template <class T>
00050 struct ExceptionOnConversion {
00051   operator T() const {  // NOLINT
00052     throw ConversionException();
00053   }
00054 };
00055 
00056 // Forces a variant into the valueless by exception state.
00057 void ToValuelessByException(ThrowingVariant& v) {  // NOLINT
00058   try {
00059     v.emplace<Thrower>();
00060     v.emplace<Thrower>(ExceptionOnConversion<Thrower>());
00061   } catch (const ConversionException&) {
00062     // This space intentionally left blank.
00063   }
00064 }
00065 
00066 // Check that variant is still in a usable state after an exception is thrown.
00067 testing::AssertionResult VariantInvariants(ThrowingVariant* v) {
00068   using testing::AssertionFailure;
00069   using testing::AssertionSuccess;
00070 
00071   // Try using the active alternative
00072   if (absl::holds_alternative<Thrower>(*v)) {
00073     auto& t = absl::get<Thrower>(*v);
00074     t = Thrower{-100};
00075     if (t.Get() != -100) {
00076       return AssertionFailure() << "Thrower should be assigned -100";
00077     }
00078   } else if (absl::holds_alternative<ThrowerVec>(*v)) {
00079     auto& tv = absl::get<ThrowerVec>(*v);
00080     tv.clear();
00081     tv.emplace_back(-100);
00082     if (tv.size() != 1 || tv[0].Get() != -100) {
00083       return AssertionFailure() << "ThrowerVec should be {Thrower{-100}}";
00084     }
00085   } else if (absl::holds_alternative<CopyNothrow>(*v)) {
00086     auto& t = absl::get<CopyNothrow>(*v);
00087     t = CopyNothrow{-100};
00088     if (t.Get() != -100) {
00089       return AssertionFailure() << "CopyNothrow should be assigned -100";
00090     }
00091   } else if (absl::holds_alternative<MoveNothrow>(*v)) {
00092     auto& t = absl::get<MoveNothrow>(*v);
00093     t = MoveNothrow{-100};
00094     if (t.Get() != -100) {
00095       return AssertionFailure() << "MoveNothrow should be assigned -100";
00096     }
00097   }
00098 
00099   // Try making variant valueless_by_exception
00100   if (!v->valueless_by_exception()) ToValuelessByException(*v);
00101   if (!v->valueless_by_exception()) {
00102     return AssertionFailure() << "Variant should be valueless_by_exception";
00103   }
00104   try {
00105     auto unused = absl::get<Thrower>(*v);
00106     static_cast<void>(unused);
00107     return AssertionFailure() << "Variant should not contain Thrower";
00108   } catch (const absl::bad_variant_access&) {
00109   } catch (...) {
00110     return AssertionFailure() << "Unexpected exception throw from absl::get";
00111   }
00112 
00113   // Try using the variant
00114   v->emplace<Thrower>(100);
00115   if (!absl::holds_alternative<Thrower>(*v) ||
00116       absl::get<Thrower>(*v) != Thrower(100)) {
00117     return AssertionFailure() << "Variant should contain Thrower(100)";
00118   }
00119   v->emplace<ThrowerVec>({Thrower(100)});
00120   if (!absl::holds_alternative<ThrowerVec>(*v) ||
00121       absl::get<ThrowerVec>(*v)[0] != Thrower(100)) {
00122     return AssertionFailure()
00123            << "Variant should contain ThrowerVec{Thrower(100)}";
00124   }
00125   return AssertionSuccess();
00126 }
00127 
00128 template <typename... Args>
00129 Thrower ExpectedThrower(Args&&... args) {
00130   return Thrower(42, args...);
00131 }
00132 
00133 ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; }
00134 ThrowingVariant ValuelessByException() {
00135   ThrowingVariant v;
00136   ToValuelessByException(v);
00137   return v;
00138 }
00139 ThrowingVariant WithThrower() { return Thrower(39); }
00140 ThrowingVariant WithThrowerVec() {
00141   return ThrowerVec{Thrower(1), Thrower(2), Thrower(3)};
00142 }
00143 ThrowingVariant WithCopyNoThrow() { return CopyNothrow(39); }
00144 ThrowingVariant WithMoveNoThrow() { return MoveNothrow(39); }
00145 
00146 TEST(VariantExceptionSafetyTest, DefaultConstructor) {
00147   TestThrowingCtor<ThrowingVariant>();
00148 }
00149 
00150 TEST(VariantExceptionSafetyTest, CopyConstructor) {
00151   {
00152     ThrowingVariant v(ExpectedThrower());
00153     TestThrowingCtor<ThrowingVariant>(v);
00154   }
00155   {
00156     ThrowingVariant v(ExpectedThrowerVec());
00157     TestThrowingCtor<ThrowingVariant>(v);
00158   }
00159   {
00160     ThrowingVariant v(ValuelessByException());
00161     TestThrowingCtor<ThrowingVariant>(v);
00162   }
00163 }
00164 
00165 TEST(VariantExceptionSafetyTest, MoveConstructor) {
00166   {
00167     ThrowingVariant v(ExpectedThrower());
00168     TestThrowingCtor<ThrowingVariant>(std::move(v));
00169   }
00170   {
00171     ThrowingVariant v(ExpectedThrowerVec());
00172     TestThrowingCtor<ThrowingVariant>(std::move(v));
00173   }
00174   {
00175     ThrowingVariant v(ValuelessByException());
00176     TestThrowingCtor<ThrowingVariant>(std::move(v));
00177   }
00178 }
00179 
00180 TEST(VariantExceptionSafetyTest, ValueConstructor) {
00181   TestThrowingCtor<ThrowingVariant>(ExpectedThrower());
00182   TestThrowingCtor<ThrowingVariant>(ExpectedThrowerVec());
00183 }
00184 
00185 TEST(VariantExceptionSafetyTest, InPlaceTypeConstructor) {
00186   TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<Thrower>{},
00187                                     ExpectedThrower());
00188   TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<ThrowerVec>{},
00189                                     ExpectedThrowerVec());
00190 }
00191 
00192 TEST(VariantExceptionSafetyTest, InPlaceIndexConstructor) {
00193   TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<0>{},
00194                                     ExpectedThrower());
00195   TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<3>{},
00196                                     ExpectedThrowerVec());
00197 }
00198 
00199 TEST(VariantExceptionSafetyTest, CopyAssign) {
00200   // variant& operator=(const variant& rhs);
00201   // Let j be rhs.index()
00202   {
00203     // - neither *this nor rhs holds a value
00204     const ThrowingVariant rhs = ValuelessByException();
00205     ThrowingVariant lhs = ValuelessByException();
00206     EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
00207   }
00208   {
00209     // - *this holds a value but rhs does not
00210     const ThrowingVariant rhs = ValuelessByException();
00211     ThrowingVariant lhs = WithThrower();
00212     EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
00213   }
00214   // - index() == j
00215   {
00216     const ThrowingVariant rhs(ExpectedThrower());
00217     auto tester =
00218         MakeExceptionSafetyTester()
00219             .WithInitialValue(WithThrower())
00220             .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
00221     EXPECT_TRUE(tester.WithContracts(VariantInvariants).Test());
00222     EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
00223   }
00224   {
00225     const ThrowingVariant rhs(ExpectedThrowerVec());
00226     auto tester =
00227         MakeExceptionSafetyTester()
00228             .WithInitialValue(WithThrowerVec())
00229             .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
00230     EXPECT_TRUE(tester.WithContracts(VariantInvariants).Test());
00231     EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
00232   }
00233   // libstdc++ std::variant has bugs on copy assignment regarding exception
00234   // safety.
00235 #if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
00236   // index() != j
00237   // if is_nothrow_copy_constructible_v<Tj> or
00238   // !is_nothrow_move_constructible<Tj> is true, equivalent to
00239   // emplace<j>(get<j>(rhs))
00240   {
00241     // is_nothrow_copy_constructible_v<Tj> == true
00242     // should not throw because emplace() invokes Tj's copy ctor
00243     // which should not throw.
00244     const ThrowingVariant rhs(CopyNothrow{});
00245     ThrowingVariant lhs = WithThrower();
00246     EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
00247   }
00248   {
00249     // is_nothrow_copy_constructible<Tj> == false &&
00250     // is_nothrow_move_constructible<Tj> == false
00251     // should provide basic guarantee because emplace() invokes Tj's copy ctor
00252     // which may throw.
00253     const ThrowingVariant rhs(ExpectedThrower());
00254     auto tester =
00255         MakeExceptionSafetyTester()
00256             .WithInitialValue(WithCopyNoThrow())
00257             .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
00258     EXPECT_TRUE(tester
00259                     .WithContracts(VariantInvariants,
00260                                    [](ThrowingVariant* lhs) {
00261                                      return lhs->valueless_by_exception();
00262                                    })
00263                     .Test());
00264     EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
00265   }
00266 #endif  // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
00267   {
00268     // is_nothrow_copy_constructible_v<Tj> == false &&
00269     // is_nothrow_move_constructible_v<Tj> == true
00270     // should provide strong guarantee because it is equivalent to
00271     // operator=(variant(rhs)) which creates a temporary then invoke the move
00272     // ctor which shouldn't throw.
00273     const ThrowingVariant rhs(MoveNothrow{});
00274     EXPECT_TRUE(MakeExceptionSafetyTester()
00275                     .WithInitialValue(WithThrower())
00276                     .WithContracts(VariantInvariants, strong_guarantee)
00277                     .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
00278   }
00279 }
00280 
00281 TEST(VariantExceptionSafetyTest, MoveAssign) {
00282   // variant& operator=(variant&& rhs);
00283   // Let j be rhs.index()
00284   {
00285     // - neither *this nor rhs holds a value
00286     ThrowingVariant rhs = ValuelessByException();
00287     ThrowingVariant lhs = ValuelessByException();
00288     EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
00289   }
00290   {
00291     // - *this holds a value but rhs does not
00292     ThrowingVariant rhs = ValuelessByException();
00293     ThrowingVariant lhs = WithThrower();
00294     EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
00295   }
00296   {
00297     // - index() == j
00298     // assign get<j>(std::move(rhs)) to the value contained in *this.
00299     // If an exception is thrown during call to Tj's move assignment, the state
00300     // of the contained value is as defined by the exception safety guarantee of
00301     // Tj's move assignment; index() will be j.
00302     ThrowingVariant rhs(ExpectedThrower());
00303     size_t j = rhs.index();
00304     // Since Thrower's move assignment has basic guarantee, so should variant's.
00305     auto tester = MakeExceptionSafetyTester()
00306                       .WithInitialValue(WithThrower())
00307                       .WithOperation([&](ThrowingVariant* lhs) {
00308                         auto copy = rhs;
00309                         *lhs = std::move(copy);
00310                       });
00311     EXPECT_TRUE(tester
00312                     .WithContracts(
00313                         VariantInvariants,
00314                         [&](ThrowingVariant* lhs) { return lhs->index() == j; })
00315                     .Test());
00316     EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
00317   }
00318   {
00319     // libstdc++ introduced a regression between 2018-09-25 and 2019-01-06.
00320     // The fix is targeted for gcc-9.
00321     // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87431#c7
00322     // https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=267614
00323 #if !(defined(ABSL_HAVE_STD_VARIANT) && \
00324       defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8)
00325     // - otherwise (index() != j), equivalent to
00326     // emplace<j>(get<j>(std::move(rhs)))
00327     // - If an exception is thrown during the call to Tj's move construction
00328     // (with j being rhs.index()), the variant will hold no value.
00329     ThrowingVariant rhs(CopyNothrow{});
00330     EXPECT_TRUE(MakeExceptionSafetyTester()
00331                     .WithInitialValue(WithThrower())
00332                     .WithContracts(VariantInvariants,
00333                                    [](ThrowingVariant* lhs) {
00334                                      return lhs->valueless_by_exception();
00335                                    })
00336                     .Test([&](ThrowingVariant* lhs) {
00337                       auto copy = rhs;
00338                       *lhs = std::move(copy);
00339                     }));
00340 #endif  // !(defined(ABSL_HAVE_STD_VARIANT) &&
00341         //   defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8)
00342   }
00343 }
00344 
00345 TEST(VariantExceptionSafetyTest, ValueAssign) {
00346   // template<class T> variant& operator=(T&& t);
00347   // Let Tj be the type that is selected by overload resolution to be assigned.
00348   {
00349     // If *this holds a Tj, assigns std::forward<T>(t) to the value contained in
00350     // *this. If  an exception is thrown during the assignment of
00351     // std::forward<T>(t) to the value contained in *this, the state of the
00352     // contained value and t are as defined by the exception safety guarantee of
00353     // the assignment expression; valueless_by_exception() will be false.
00354     // Since Thrower's copy/move assignment has basic guarantee, so should
00355     // variant's.
00356     Thrower rhs = ExpectedThrower();
00357     // copy assign
00358     auto copy_tester =
00359         MakeExceptionSafetyTester()
00360             .WithInitialValue(WithThrower())
00361             .WithOperation([rhs](ThrowingVariant* lhs) { *lhs = rhs; });
00362     EXPECT_TRUE(copy_tester
00363                     .WithContracts(VariantInvariants,
00364                                    [](ThrowingVariant* lhs) {
00365                                      return !lhs->valueless_by_exception();
00366                                    })
00367                     .Test());
00368     EXPECT_FALSE(copy_tester.WithContracts(strong_guarantee).Test());
00369     // move assign
00370     auto move_tester = MakeExceptionSafetyTester()
00371                            .WithInitialValue(WithThrower())
00372                            .WithOperation([&](ThrowingVariant* lhs) {
00373                              auto copy = rhs;
00374                              *lhs = std::move(copy);
00375                            });
00376     EXPECT_TRUE(move_tester
00377                     .WithContracts(VariantInvariants,
00378                                    [](ThrowingVariant* lhs) {
00379                                      return !lhs->valueless_by_exception();
00380                                    })
00381                     .Test());
00382 
00383     EXPECT_FALSE(move_tester.WithContracts(strong_guarantee).Test());
00384   }
00385   // Otherwise (*this holds something else), if is_nothrow_constructible_v<Tj,
00386   // T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to
00387   // emplace<j>(std::forward<T>(t)).
00388   // We simplify the test by letting T = `const Tj&`  or `Tj&&`, so we can reuse
00389   // the CopyNothrow and MoveNothrow types.
00390 
00391   // if is_nothrow_constructible_v<Tj, T>
00392   // (i.e. is_nothrow_copy/move_constructible_v<Tj>) is true, emplace() just
00393   // invokes the copy/move constructor and it should not throw.
00394   {
00395     const CopyNothrow rhs;
00396     ThrowingVariant lhs = WithThrower();
00397     EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
00398   }
00399   {
00400     MoveNothrow rhs;
00401     ThrowingVariant lhs = WithThrower();
00402     EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
00403   }
00404   // if is_nothrow_constructible_v<Tj, T> == false &&
00405   // is_nothrow_move_constructible<Tj> == false
00406   // emplace() invokes the copy/move constructor which may throw so it should
00407   // provide basic guarantee and variant object might not hold a value.
00408   {
00409     Thrower rhs = ExpectedThrower();
00410     // copy
00411     auto copy_tester =
00412         MakeExceptionSafetyTester()
00413             .WithInitialValue(WithCopyNoThrow())
00414             .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
00415     EXPECT_TRUE(copy_tester
00416                     .WithContracts(VariantInvariants,
00417                                    [](ThrowingVariant* lhs) {
00418                                      return lhs->valueless_by_exception();
00419                                    })
00420                     .Test());
00421     EXPECT_FALSE(copy_tester.WithContracts(strong_guarantee).Test());
00422     // move
00423     auto move_tester = MakeExceptionSafetyTester()
00424                            .WithInitialValue(WithCopyNoThrow())
00425                            .WithOperation([](ThrowingVariant* lhs) {
00426                              *lhs = ExpectedThrower(testing::nothrow_ctor);
00427                            });
00428     EXPECT_TRUE(move_tester
00429                     .WithContracts(VariantInvariants,
00430                                    [](ThrowingVariant* lhs) {
00431                                      return lhs->valueless_by_exception();
00432                                    })
00433                     .Test());
00434     EXPECT_FALSE(move_tester.WithContracts(strong_guarantee).Test());
00435   }
00436   // Otherwise (if is_nothrow_constructible_v<Tj, T> == false &&
00437   // is_nothrow_move_constructible<Tj> == true),
00438   // equivalent to operator=(variant(std::forward<T>(t)))
00439   // This should have strong guarantee because it creates a temporary variant
00440   // and operator=(variant&&) invokes Tj's move ctor which doesn't throw.
00441   // libstdc++ std::variant has bugs on conversion assignment regarding
00442   // exception safety.
00443 #if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
00444   {
00445     MoveNothrow rhs;
00446     EXPECT_TRUE(MakeExceptionSafetyTester()
00447                     .WithInitialValue(WithThrower())
00448                     .WithContracts(VariantInvariants, strong_guarantee)
00449                     .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
00450   }
00451 #endif  // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
00452 }
00453 
00454 TEST(VariantExceptionSafetyTest, Emplace) {
00455   // If an exception during the initialization of the contained value, the
00456   // variant might not hold a value. The standard requires emplace() to provide
00457   // only basic guarantee.
00458   {
00459     Thrower args = ExpectedThrower();
00460     auto tester = MakeExceptionSafetyTester()
00461                       .WithInitialValue(WithThrower())
00462                       .WithOperation([&args](ThrowingVariant* v) {
00463                         v->emplace<Thrower>(args);
00464                       });
00465     EXPECT_TRUE(tester
00466                     .WithContracts(VariantInvariants,
00467                                    [](ThrowingVariant* v) {
00468                                      return v->valueless_by_exception();
00469                                    })
00470                     .Test());
00471     EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
00472   }
00473 }
00474 
00475 TEST(VariantExceptionSafetyTest, Swap) {
00476   // if both are valueless_by_exception(), no effect
00477   {
00478     ThrowingVariant rhs = ValuelessByException();
00479     ThrowingVariant lhs = ValuelessByException();
00480     EXPECT_TRUE(TestNothrowOp([&]() { lhs.swap(rhs); }));
00481   }
00482   // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs))
00483   // where i is index().
00484   {
00485     ThrowingVariant rhs = ExpectedThrower();
00486     EXPECT_TRUE(MakeExceptionSafetyTester()
00487                     .WithInitialValue(WithThrower())
00488                     .WithContracts(VariantInvariants)
00489                     .Test([&](ThrowingVariant* lhs) {
00490                       auto copy = rhs;
00491                       lhs->swap(copy);
00492                     }));
00493   }
00494   // Otherwise, exchanges the value of rhs and *this. The exception safety
00495   // involves variant in moved-from state which is not specified in the
00496   // standard, and since swap is 3-step it's impossible for it to provide a
00497   // overall strong guarantee. So, we are only checking basic guarantee here.
00498   {
00499     ThrowingVariant rhs = ExpectedThrower();
00500     EXPECT_TRUE(MakeExceptionSafetyTester()
00501                     .WithInitialValue(WithCopyNoThrow())
00502                     .WithContracts(VariantInvariants)
00503                     .Test([&](ThrowingVariant* lhs) {
00504                       auto copy = rhs;
00505                       lhs->swap(copy);
00506                     }));
00507   }
00508   {
00509     ThrowingVariant rhs = ExpectedThrower();
00510     EXPECT_TRUE(MakeExceptionSafetyTester()
00511                     .WithInitialValue(WithCopyNoThrow())
00512                     .WithContracts(VariantInvariants)
00513                     .Test([&](ThrowingVariant* lhs) {
00514                       auto copy = rhs;
00515                       copy.swap(*lhs);
00516                     }));
00517   }
00518 }
00519 
00520 }  // namespace
00521 }  // namespace absl
00522 
00523 #endif  // !defined(ABSL_INTERNAL_MSVC_2017_DBG_MODE)


abseil_cpp
Author(s):
autogenerated on Wed Jun 19 2019 19:42:16