variant_exception_safety_test.cc
Go to the documentation of this file.
1 // Copyright 2017 The Abseil Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "absl/types/variant.h"
16 
17 #include <iostream>
18 #include <memory>
19 #include <utility>
20 #include <vector>
21 
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 #include "absl/base/config.h"
26 #include "absl/memory/memory.h"
27 
28 // See comment in absl/base/config.h
29 #if !defined(ABSL_INTERNAL_MSVC_2017_DBG_MODE)
30 
31 namespace absl {
32 namespace {
33 
38 
43 using ThrowerVec = std::vector<Thrower, ThrowingAlloc>;
44 using ThrowingVariant =
46 
47 struct ConversionException {};
48 
49 template <class T>
50 struct ExceptionOnConversion {
51  operator T() const { // NOLINT
52  throw ConversionException();
53  }
54 };
55 
56 // Forces a variant into the valueless by exception state.
57 void ToValuelessByException(ThrowingVariant& v) { // NOLINT
58  try {
59  v.emplace<Thrower>();
60  v.emplace<Thrower>(ExceptionOnConversion<Thrower>());
61  } catch (const ConversionException&) {
62  // This space intentionally left blank.
63  }
64 }
65 
66 // Check that variant is still in a usable state after an exception is thrown.
67 testing::AssertionResult VariantInvariants(ThrowingVariant* v) {
68  using testing::AssertionFailure;
69  using testing::AssertionSuccess;
70 
71  // Try using the active alternative
72  if (absl::holds_alternative<Thrower>(*v)) {
73  auto& t = absl::get<Thrower>(*v);
74  t = Thrower{-100};
75  if (t.Get() != -100) {
76  return AssertionFailure() << "Thrower should be assigned -100";
77  }
78  } else if (absl::holds_alternative<ThrowerVec>(*v)) {
79  auto& tv = absl::get<ThrowerVec>(*v);
80  tv.clear();
81  tv.emplace_back(-100);
82  if (tv.size() != 1 || tv[0].Get() != -100) {
83  return AssertionFailure() << "ThrowerVec should be {Thrower{-100}}";
84  }
85  } else if (absl::holds_alternative<CopyNothrow>(*v)) {
86  auto& t = absl::get<CopyNothrow>(*v);
87  t = CopyNothrow{-100};
88  if (t.Get() != -100) {
89  return AssertionFailure() << "CopyNothrow should be assigned -100";
90  }
91  } else if (absl::holds_alternative<MoveNothrow>(*v)) {
92  auto& t = absl::get<MoveNothrow>(*v);
93  t = MoveNothrow{-100};
94  if (t.Get() != -100) {
95  return AssertionFailure() << "MoveNothrow should be assigned -100";
96  }
97  }
98 
99  // Try making variant valueless_by_exception
100  if (!v->valueless_by_exception()) ToValuelessByException(*v);
101  if (!v->valueless_by_exception()) {
102  return AssertionFailure() << "Variant should be valueless_by_exception";
103  }
104  try {
105  auto unused = absl::get<Thrower>(*v);
106  static_cast<void>(unused);
107  return AssertionFailure() << "Variant should not contain Thrower";
108  } catch (const absl::bad_variant_access&) {
109  } catch (...) {
110  return AssertionFailure() << "Unexpected exception throw from absl::get";
111  }
112 
113  // Try using the variant
114  v->emplace<Thrower>(100);
115  if (!absl::holds_alternative<Thrower>(*v) ||
116  absl::get<Thrower>(*v) != Thrower(100)) {
117  return AssertionFailure() << "Variant should contain Thrower(100)";
118  }
119  v->emplace<ThrowerVec>({Thrower(100)});
120  if (!absl::holds_alternative<ThrowerVec>(*v) ||
121  absl::get<ThrowerVec>(*v)[0] != Thrower(100)) {
122  return AssertionFailure()
123  << "Variant should contain ThrowerVec{Thrower(100)}";
124  }
125  return AssertionSuccess();
126 }
127 
128 template <typename... Args>
129 Thrower ExpectedThrower(Args&&... args) {
130  return Thrower(42, args...);
131 }
132 
133 ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; }
134 ThrowingVariant ValuelessByException() {
135  ThrowingVariant v;
136  ToValuelessByException(v);
137  return v;
138 }
139 ThrowingVariant WithThrower() { return Thrower(39); }
140 ThrowingVariant WithThrowerVec() {
141  return ThrowerVec{Thrower(1), Thrower(2), Thrower(3)};
142 }
143 ThrowingVariant WithCopyNoThrow() { return CopyNothrow(39); }
144 ThrowingVariant WithMoveNoThrow() { return MoveNothrow(39); }
145 
146 TEST(VariantExceptionSafetyTest, DefaultConstructor) {
147  TestThrowingCtor<ThrowingVariant>();
148 }
149 
150 TEST(VariantExceptionSafetyTest, CopyConstructor) {
151  {
152  ThrowingVariant v(ExpectedThrower());
153  TestThrowingCtor<ThrowingVariant>(v);
154  }
155  {
156  ThrowingVariant v(ExpectedThrowerVec());
157  TestThrowingCtor<ThrowingVariant>(v);
158  }
159  {
160  ThrowingVariant v(ValuelessByException());
161  TestThrowingCtor<ThrowingVariant>(v);
162  }
163 }
164 
165 TEST(VariantExceptionSafetyTest, MoveConstructor) {
166  {
167  ThrowingVariant v(ExpectedThrower());
168  TestThrowingCtor<ThrowingVariant>(std::move(v));
169  }
170  {
171  ThrowingVariant v(ExpectedThrowerVec());
172  TestThrowingCtor<ThrowingVariant>(std::move(v));
173  }
174  {
175  ThrowingVariant v(ValuelessByException());
176  TestThrowingCtor<ThrowingVariant>(std::move(v));
177  }
178 }
179 
180 TEST(VariantExceptionSafetyTest, ValueConstructor) {
181  TestThrowingCtor<ThrowingVariant>(ExpectedThrower());
182  TestThrowingCtor<ThrowingVariant>(ExpectedThrowerVec());
183 }
184 
185 TEST(VariantExceptionSafetyTest, InPlaceTypeConstructor) {
186  TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<Thrower>{},
187  ExpectedThrower());
188  TestThrowingCtor<ThrowingVariant>(absl::in_place_type_t<ThrowerVec>{},
189  ExpectedThrowerVec());
190 }
191 
192 TEST(VariantExceptionSafetyTest, InPlaceIndexConstructor) {
193  TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<0>{},
194  ExpectedThrower());
195  TestThrowingCtor<ThrowingVariant>(absl::in_place_index_t<3>{},
196  ExpectedThrowerVec());
197 }
198 
199 TEST(VariantExceptionSafetyTest, CopyAssign) {
200  // variant& operator=(const variant& rhs);
201  // Let j be rhs.index()
202  {
203  // - neither *this nor rhs holds a value
204  const ThrowingVariant rhs = ValuelessByException();
205  ThrowingVariant lhs = ValuelessByException();
206  EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
207  }
208  {
209  // - *this holds a value but rhs does not
210  const ThrowingVariant rhs = ValuelessByException();
211  ThrowingVariant lhs = WithThrower();
212  EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
213  }
214  // - index() == j
215  {
216  const ThrowingVariant rhs(ExpectedThrower());
217  auto tester =
219  .WithInitialValue(WithThrower())
220  .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
221  EXPECT_TRUE(tester.WithContracts(VariantInvariants).Test());
222  EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
223  }
224  {
225  const ThrowingVariant rhs(ExpectedThrowerVec());
226  auto tester =
228  .WithInitialValue(WithThrowerVec())
229  .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
230  EXPECT_TRUE(tester.WithContracts(VariantInvariants).Test());
231  EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
232  }
233  // libstdc++ std::variant has bugs on copy assignment regarding exception
234  // safety.
235 #if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
236  // index() != j
237  // if is_nothrow_copy_constructible_v<Tj> or
238  // !is_nothrow_move_constructible<Tj> is true, equivalent to
239  // emplace<j>(get<j>(rhs))
240  {
241  // is_nothrow_copy_constructible_v<Tj> == true
242  // should not throw because emplace() invokes Tj's copy ctor
243  // which should not throw.
244  const ThrowingVariant rhs(CopyNothrow{});
245  ThrowingVariant lhs = WithThrower();
246  EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
247  }
248  {
249  // is_nothrow_copy_constructible<Tj> == false &&
250  // is_nothrow_move_constructible<Tj> == false
251  // should provide basic guarantee because emplace() invokes Tj's copy ctor
252  // which may throw.
253  const ThrowingVariant rhs(ExpectedThrower());
254  auto tester =
256  .WithInitialValue(WithCopyNoThrow())
257  .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
258  EXPECT_TRUE(tester
259  .WithContracts(VariantInvariants,
260  [](ThrowingVariant* lhs) {
261  return lhs->valueless_by_exception();
262  })
263  .Test());
264  EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
265  }
266 #endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
267  {
268  // is_nothrow_copy_constructible_v<Tj> == false &&
269  // is_nothrow_move_constructible_v<Tj> == true
270  // should provide strong guarantee because it is equivalent to
271  // operator=(variant(rhs)) which creates a temporary then invoke the move
272  // ctor which shouldn't throw.
273  const ThrowingVariant rhs(MoveNothrow{});
274  EXPECT_TRUE(MakeExceptionSafetyTester()
275  .WithInitialValue(WithThrower())
276  .WithContracts(VariantInvariants, strong_guarantee)
277  .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
278  }
279 }
280 
281 TEST(VariantExceptionSafetyTest, MoveAssign) {
282  // variant& operator=(variant&& rhs);
283  // Let j be rhs.index()
284  {
285  // - neither *this nor rhs holds a value
286  ThrowingVariant rhs = ValuelessByException();
287  ThrowingVariant lhs = ValuelessByException();
288  EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
289  }
290  {
291  // - *this holds a value but rhs does not
292  ThrowingVariant rhs = ValuelessByException();
293  ThrowingVariant lhs = WithThrower();
294  EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
295  }
296  {
297  // - index() == j
298  // assign get<j>(std::move(rhs)) to the value contained in *this.
299  // If an exception is thrown during call to Tj's move assignment, the state
300  // of the contained value is as defined by the exception safety guarantee of
301  // Tj's move assignment; index() will be j.
302  ThrowingVariant rhs(ExpectedThrower());
303  size_t j = rhs.index();
304  // Since Thrower's move assignment has basic guarantee, so should variant's.
305  auto tester = MakeExceptionSafetyTester()
306  .WithInitialValue(WithThrower())
307  .WithOperation([&](ThrowingVariant* lhs) {
308  auto copy = rhs;
309  *lhs = std::move(copy);
310  });
311  EXPECT_TRUE(tester
312  .WithContracts(
313  VariantInvariants,
314  [&](ThrowingVariant* lhs) { return lhs->index() == j; })
315  .Test());
316  EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
317  }
318  {
319  // libstdc++ introduced a regression between 2018-09-25 and 2019-01-06.
320  // The fix is targeted for gcc-9.
321  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87431#c7
322  // https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=267614
323 #if !(defined(ABSL_HAVE_STD_VARIANT) && \
324  defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8)
325  // - otherwise (index() != j), equivalent to
326  // emplace<j>(get<j>(std::move(rhs)))
327  // - If an exception is thrown during the call to Tj's move construction
328  // (with j being rhs.index()), the variant will hold no value.
329  ThrowingVariant rhs(CopyNothrow{});
330  EXPECT_TRUE(MakeExceptionSafetyTester()
331  .WithInitialValue(WithThrower())
332  .WithContracts(VariantInvariants,
333  [](ThrowingVariant* lhs) {
334  return lhs->valueless_by_exception();
335  })
336  .Test([&](ThrowingVariant* lhs) {
337  auto copy = rhs;
338  *lhs = std::move(copy);
339  }));
340 #endif // !(defined(ABSL_HAVE_STD_VARIANT) &&
341  // defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE == 8)
342  }
343 }
344 
345 TEST(VariantExceptionSafetyTest, ValueAssign) {
346  // template<class T> variant& operator=(T&& t);
347  // Let Tj be the type that is selected by overload resolution to be assigned.
348  {
349  // If *this holds a Tj, assigns std::forward<T>(t) to the value contained in
350  // *this. If an exception is thrown during the assignment of
351  // std::forward<T>(t) to the value contained in *this, the state of the
352  // contained value and t are as defined by the exception safety guarantee of
353  // the assignment expression; valueless_by_exception() will be false.
354  // Since Thrower's copy/move assignment has basic guarantee, so should
355  // variant's.
356  Thrower rhs = ExpectedThrower();
357  // copy assign
358  auto copy_tester =
360  .WithInitialValue(WithThrower())
361  .WithOperation([rhs](ThrowingVariant* lhs) { *lhs = rhs; });
362  EXPECT_TRUE(copy_tester
363  .WithContracts(VariantInvariants,
364  [](ThrowingVariant* lhs) {
365  return !lhs->valueless_by_exception();
366  })
367  .Test());
368  EXPECT_FALSE(copy_tester.WithContracts(strong_guarantee).Test());
369  // move assign
370  auto move_tester = MakeExceptionSafetyTester()
371  .WithInitialValue(WithThrower())
372  .WithOperation([&](ThrowingVariant* lhs) {
373  auto copy = rhs;
374  *lhs = std::move(copy);
375  });
376  EXPECT_TRUE(move_tester
377  .WithContracts(VariantInvariants,
378  [](ThrowingVariant* lhs) {
379  return !lhs->valueless_by_exception();
380  })
381  .Test());
382 
383  EXPECT_FALSE(move_tester.WithContracts(strong_guarantee).Test());
384  }
385  // Otherwise (*this holds something else), if is_nothrow_constructible_v<Tj,
386  // T> || !is_nothrow_move_constructible_v<Tj> is true, equivalent to
387  // emplace<j>(std::forward<T>(t)).
388  // We simplify the test by letting T = `const Tj&` or `Tj&&`, so we can reuse
389  // the CopyNothrow and MoveNothrow types.
390 
391  // if is_nothrow_constructible_v<Tj, T>
392  // (i.e. is_nothrow_copy/move_constructible_v<Tj>) is true, emplace() just
393  // invokes the copy/move constructor and it should not throw.
394  {
395  const CopyNothrow rhs;
396  ThrowingVariant lhs = WithThrower();
397  EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; }));
398  }
399  {
400  MoveNothrow rhs;
401  ThrowingVariant lhs = WithThrower();
402  EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); }));
403  }
404  // if is_nothrow_constructible_v<Tj, T> == false &&
405  // is_nothrow_move_constructible<Tj> == false
406  // emplace() invokes the copy/move constructor which may throw so it should
407  // provide basic guarantee and variant object might not hold a value.
408  {
409  Thrower rhs = ExpectedThrower();
410  // copy
411  auto copy_tester =
413  .WithInitialValue(WithCopyNoThrow())
414  .WithOperation([&rhs](ThrowingVariant* lhs) { *lhs = rhs; });
415  EXPECT_TRUE(copy_tester
416  .WithContracts(VariantInvariants,
417  [](ThrowingVariant* lhs) {
418  return lhs->valueless_by_exception();
419  })
420  .Test());
421  EXPECT_FALSE(copy_tester.WithContracts(strong_guarantee).Test());
422  // move
423  auto move_tester = MakeExceptionSafetyTester()
424  .WithInitialValue(WithCopyNoThrow())
425  .WithOperation([](ThrowingVariant* lhs) {
426  *lhs = ExpectedThrower(testing::nothrow_ctor);
427  });
428  EXPECT_TRUE(move_tester
429  .WithContracts(VariantInvariants,
430  [](ThrowingVariant* lhs) {
431  return lhs->valueless_by_exception();
432  })
433  .Test());
434  EXPECT_FALSE(move_tester.WithContracts(strong_guarantee).Test());
435  }
436  // Otherwise (if is_nothrow_constructible_v<Tj, T> == false &&
437  // is_nothrow_move_constructible<Tj> == true),
438  // equivalent to operator=(variant(std::forward<T>(t)))
439  // This should have strong guarantee because it creates a temporary variant
440  // and operator=(variant&&) invokes Tj's move ctor which doesn't throw.
441  // libstdc++ std::variant has bugs on conversion assignment regarding
442  // exception safety.
443 #if !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
444  {
445  MoveNothrow rhs;
446  EXPECT_TRUE(MakeExceptionSafetyTester()
447  .WithInitialValue(WithThrower())
448  .WithContracts(VariantInvariants, strong_guarantee)
449  .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; }));
450  }
451 #endif // !(defined(ABSL_HAVE_STD_VARIANT) && defined(__GLIBCXX__))
452 }
453 
454 TEST(VariantExceptionSafetyTest, Emplace) {
455  // If an exception during the initialization of the contained value, the
456  // variant might not hold a value. The standard requires emplace() to provide
457  // only basic guarantee.
458  {
459  Thrower args = ExpectedThrower();
460  auto tester = MakeExceptionSafetyTester()
461  .WithInitialValue(WithThrower())
462  .WithOperation([&args](ThrowingVariant* v) {
463  v->emplace<Thrower>(args);
464  });
465  EXPECT_TRUE(tester
466  .WithContracts(VariantInvariants,
467  [](ThrowingVariant* v) {
468  return v->valueless_by_exception();
469  })
470  .Test());
471  EXPECT_FALSE(tester.WithContracts(strong_guarantee).Test());
472  }
473 }
474 
475 TEST(VariantExceptionSafetyTest, Swap) {
476  // if both are valueless_by_exception(), no effect
477  {
478  ThrowingVariant rhs = ValuelessByException();
479  ThrowingVariant lhs = ValuelessByException();
480  EXPECT_TRUE(TestNothrowOp([&]() { lhs.swap(rhs); }));
481  }
482  // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs))
483  // where i is index().
484  {
485  ThrowingVariant rhs = ExpectedThrower();
486  EXPECT_TRUE(MakeExceptionSafetyTester()
487  .WithInitialValue(WithThrower())
488  .WithContracts(VariantInvariants)
489  .Test([&](ThrowingVariant* lhs) {
490  auto copy = rhs;
491  lhs->swap(copy);
492  }));
493  }
494  // Otherwise, exchanges the value of rhs and *this. The exception safety
495  // involves variant in moved-from state which is not specified in the
496  // standard, and since swap is 3-step it's impossible for it to provide a
497  // overall strong guarantee. So, we are only checking basic guarantee here.
498  {
499  ThrowingVariant rhs = ExpectedThrower();
500  EXPECT_TRUE(MakeExceptionSafetyTester()
501  .WithInitialValue(WithCopyNoThrow())
502  .WithContracts(VariantInvariants)
503  .Test([&](ThrowingVariant* lhs) {
504  auto copy = rhs;
505  lhs->swap(copy);
506  }));
507  }
508  {
509  ThrowingVariant rhs = ExpectedThrower();
510  EXPECT_TRUE(MakeExceptionSafetyTester()
511  .WithInitialValue(WithCopyNoThrow())
512  .WithContracts(VariantInvariants)
513  .Test([&](ThrowingVariant* lhs) {
514  auto copy = rhs;
515  copy.swap(*lhs);
516  }));
517  }
518 }
519 
520 } // namespace
521 } // namespace absl
522 
523 #endif // !defined(ABSL_INTERNAL_MSVC_2017_DBG_MODE)
int v
Definition: variant_test.cc:81
exceptions_internal::StrongGuaranteeTagType strong_guarantee
TEST(NotificationTest, SanityTest)
exceptions_internal::ExceptionSafetyTestBuilder MakeExceptionSafetyTester()
testing::ThrowingValue<> Thrower
void(*)(utility_internal::InPlaceTypeTag< T >) in_place_type_t
Definition: utility.h:189
Definition: algorithm.h:29
void Swap(T &lhs, T &rhs) noexcept(IsNothrowSwappable< T >::value)
Definition: type_traits.h:673
void TestThrowingCtor(Args &&...args)
void(*)(utility_internal::InPlaceIndexTag< I >) in_place_index_t
Definition: utility.h:206
exceptions_internal::NoThrowTag nothrow_ctor
testing::AssertionResult TestNothrowOp(const Operation &operation)
constexpr absl::remove_reference_t< T > && move(T &&t) noexcept
Definition: utility.h:219
ExceptionSafetyTestBuilder< DefaultFactory< T >, Operation, Contracts... > WithInitialValue(const T &t) const
std::vector< Thrower > ThrowerVec


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