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