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