Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-15 08:22:21

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include <boost/test/unit_test.hpp>
0010 
0011 #include "Acts/Utilities/Any.hpp"
0012 
0013 #include <any>
0014 #include <array>
0015 #include <cstddef>
0016 #include <memory>
0017 #include <stdexcept>
0018 #include <type_traits>
0019 #include <utility>
0020 
0021 using namespace Acts;
0022 
0023 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
0024 #define CHECK_ANY_ALLOCATIONS()                         \
0025   do {                                                  \
0026     detail::_AnyAllocationReporter::checkAllocations(); \
0027   } while (0)
0028 #else
0029 #define CHECK_ANY_ALLOCATIONS() \
0030   do {                          \
0031   } while (0)
0032 #endif
0033 
0034 namespace ActsTests {
0035 
0036 BOOST_AUTO_TEST_SUITE(UtilitiesSuite)
0037 
0038 BOOST_AUTO_TEST_CASE(AnyConstructPrimitive) {
0039   {
0040     // small type
0041     Any a;
0042     BOOST_CHECK(!a);
0043 
0044     int v = 5;
0045     a = Any{v};
0046     BOOST_CHECK(!!a);
0047 
0048     BOOST_CHECK_EQUAL(a.as<int>(), v);
0049     BOOST_CHECK_NE(a.as<int>(), v + 1);
0050 
0051     BOOST_CHECK_THROW(a.as<float>(), std::bad_any_cast);
0052   }
0053   CHECK_ANY_ALLOCATIONS();
0054 
0055   {
0056     // type that is large
0057     Any a;
0058     BOOST_CHECK(!a);
0059 
0060     std::array<int, 2> v{1, 2};
0061     a = Any{v};
0062     BOOST_CHECK(!!a);
0063 
0064     BOOST_CHECK_EQUAL_COLLECTIONS(a.as<decltype(v)>().begin(),
0065                                   a.as<decltype(v)>().end(), v.begin(),
0066                                   v.end());
0067     BOOST_CHECK_THROW(a.as<float>(), std::bad_any_cast);
0068   }
0069   CHECK_ANY_ALLOCATIONS();
0070 
0071   {
0072     // type that is large
0073     Any a;
0074     BOOST_CHECK(!a);
0075 
0076     std::array<unsigned long, 5> v{1, 2, 3, 4, 5};
0077     a = Any{v};
0078     BOOST_CHECK(!!a);
0079 
0080     BOOST_CHECK_EQUAL_COLLECTIONS(a.as<decltype(v)>().begin(),
0081                                   a.as<decltype(v)>().end(), v.begin(),
0082                                   v.end());
0083     BOOST_CHECK_THROW(a.as<float>(), std::bad_any_cast);
0084   }
0085   CHECK_ANY_ALLOCATIONS();
0086 }
0087 
0088 BOOST_AUTO_TEST_CASE(AnyAsPtr) {
0089   {
0090     // small type: correct type returns non-null pointer
0091     Any a{42};
0092     int* p = a.asPtr<int>();
0093     BOOST_REQUIRE_NE(p, static_cast<int*>(nullptr));
0094     BOOST_CHECK_EQUAL(*p, 42);
0095 
0096     // wrong type returns nullptr
0097     BOOST_CHECK_EQUAL(a.asPtr<float>(), static_cast<float*>(nullptr));
0098     BOOST_CHECK_EQUAL(a.asPtr<double>(), static_cast<double*>(nullptr));
0099 
0100     // mutation through pointer
0101     *p = 99;
0102     BOOST_CHECK_EQUAL(a.as<int>(), 99);
0103   }
0104   CHECK_ANY_ALLOCATIONS();
0105 
0106   {
0107     // large (heap-allocated) type: correct type returns non-null pointer
0108     std::array<unsigned long, 5> v{10, 20, 30, 40, 50};
0109     Any a{v};
0110     auto* p = a.asPtr<std::array<unsigned long, 5>>();
0111     BOOST_REQUIRE_NE(p, static_cast<decltype(p)>(nullptr));
0112     BOOST_CHECK_EQUAL_COLLECTIONS(p->begin(), p->end(), v.begin(), v.end());
0113 
0114     // wrong type returns nullptr
0115     BOOST_CHECK_EQUAL(a.asPtr<int>(), static_cast<int*>(nullptr));
0116   }
0117   CHECK_ANY_ALLOCATIONS();
0118 
0119   {
0120     // empty Any returns nullptr
0121     Any a;
0122     BOOST_CHECK_EQUAL(a.asPtr<int>(), static_cast<int*>(nullptr));
0123     BOOST_CHECK_EQUAL(a.asPtr<float>(), static_cast<float*>(nullptr));
0124   }
0125   CHECK_ANY_ALLOCATIONS();
0126 
0127   {
0128     // const overload
0129     const Any a{3.14f};
0130     const float* p = a.asPtr<float>();
0131     BOOST_REQUIRE_NE(p, static_cast<const float*>(nullptr));
0132     BOOST_CHECK_EQUAL(*p, 3.14f);
0133 
0134     // wrong type on const Any
0135     BOOST_CHECK_EQUAL(a.asPtr<int>(), static_cast<const int*>(nullptr));
0136   }
0137   CHECK_ANY_ALLOCATIONS();
0138 
0139   {
0140     // const overload with empty Any
0141     const Any a;
0142     BOOST_CHECK_EQUAL(a.asPtr<int>(), static_cast<const int*>(nullptr));
0143   }
0144   CHECK_ANY_ALLOCATIONS();
0145 
0146   {
0147     // const overload with heap-allocated type
0148     std::array<int, 64> v{};
0149     v.fill(7);
0150     const Any a{v};
0151     const auto* p = a.asPtr<std::array<int, 64>>();
0152     BOOST_REQUIRE_NE(p, static_cast<decltype(p)>(nullptr));
0153     for (const auto& elem : *p) {
0154       BOOST_CHECK_EQUAL(elem, 7);
0155     }
0156     BOOST_CHECK_EQUAL(a.asPtr<float>(), static_cast<const float*>(nullptr));
0157   }
0158   CHECK_ANY_ALLOCATIONS();
0159 }
0160 
0161 BOOST_AUTO_TEST_CASE(AnyAssignConstructEmpty) {
0162   Any a;
0163   Any b;
0164   a = b;
0165   Any c{a};
0166   a = std::move(b);
0167   Any d{std::move(a)};
0168 
0169   BOOST_CHECK(!a);
0170   BOOST_CHECK(!b);
0171   BOOST_CHECK(!c);
0172   BOOST_CHECK(!d);
0173 
0174   CHECK_ANY_ALLOCATIONS();
0175 }
0176 
0177 BOOST_AUTO_TEST_CASE(AnyConstructCustom) {
0178   struct A {
0179     int value;
0180     A() { value = 76; }
0181   };
0182 
0183   Any a;
0184   BOOST_CHECK(!a);
0185   a = Any{A{}};
0186 
0187   BOOST_CHECK(!!a);
0188 
0189   BOOST_CHECK_EQUAL(a.as<A>().value, 76);
0190 
0191   CHECK_ANY_ALLOCATIONS();
0192 }
0193 
0194 BOOST_AUTO_TEST_CASE(AnyConstructCustomInPlace) {
0195   struct A {
0196     int value;
0197     explicit A(int v) { value = v; }
0198   };
0199 
0200   Any a{std::in_place_type<A>, 42};
0201   BOOST_CHECK(!!a);
0202   BOOST_CHECK_EQUAL(a.as<A>().value, 42);
0203 
0204   CHECK_ANY_ALLOCATIONS();
0205 }
0206 
0207 BOOST_AUTO_TEST_CASE(AnyMove) {
0208   {
0209     // small type
0210     Any a;
0211     BOOST_CHECK(!a);
0212 
0213     int v = 5;
0214     a = Any{v};
0215     BOOST_CHECK(!!a);
0216 
0217     Any b = std::move(a);
0218     BOOST_CHECK(!!b);
0219     BOOST_CHECK_EQUAL(b.as<int>(), 5);
0220 
0221     Any c;
0222     c = std::move(b);
0223     BOOST_CHECK(!!c);
0224     BOOST_CHECK_EQUAL(c.as<int>(), 5);
0225   }
0226 
0227   CHECK_ANY_ALLOCATIONS();
0228 }
0229 
0230 BOOST_AUTO_TEST_CASE(AnyCopy) {
0231   {
0232     // small type
0233     Any a;
0234     BOOST_CHECK(!a);
0235 
0236     int v = 5;
0237     a = Any{v};
0238     BOOST_CHECK(!!a);
0239 
0240     Any b = a;
0241     BOOST_CHECK(!!b);
0242     BOOST_CHECK_EQUAL(b.as<int>(), 5);
0243 
0244     Any c;
0245     c = a;
0246     BOOST_CHECK(!!c);
0247     BOOST_CHECK_EQUAL(c.as<int>(), 5);
0248   }
0249   CHECK_ANY_ALLOCATIONS();
0250 }
0251 
0252 struct D {
0253   bool* destroyed;
0254   explicit D(bool* d) : destroyed{d} {}
0255   ~D() { *destroyed = true; }
0256 };
0257 
0258 struct D2 {
0259   bool* destroyed{nullptr};
0260   std::array<char, 512> blob{};
0261 
0262   explicit D2(bool* d) : destroyed{d} {}
0263 
0264   ~D2() { *destroyed = true; }
0265 };
0266 
0267 BOOST_AUTO_TEST_CASE(AnyEmplace) {
0268   {
0269     Any a;
0270     auto& value = a.emplace<int>(42);
0271     BOOST_CHECK_EQUAL(value, 42);
0272     BOOST_CHECK_EQUAL(a.as<int>(), 42);
0273     value = 84;
0274     BOOST_CHECK_EQUAL(a.as<int>(), 84);
0275   }
0276   CHECK_ANY_ALLOCATIONS();
0277 
0278   {
0279     bool destroyed = false;
0280     Any a{std::in_place_type<D>, &destroyed};
0281     BOOST_CHECK(!destroyed);
0282     a.emplace<int>(7);
0283     BOOST_CHECK(destroyed);
0284     BOOST_CHECK_EQUAL(a.as<int>(), 7);
0285   }
0286   CHECK_ANY_ALLOCATIONS();
0287 
0288   {
0289     bool destroyed = false;
0290     Any a{std::in_place_type<D2>, &destroyed};
0291     BOOST_CHECK(!destroyed);
0292     bool destroyed2 = false;
0293     auto& ref = a.emplace<D2>(&destroyed2);
0294     BOOST_CHECK(destroyed);
0295     BOOST_CHECK(!destroyed2);
0296     BOOST_CHECK_EQUAL(ref.destroyed, &destroyed2);
0297     BOOST_CHECK_EQUAL(a.as<D2>().destroyed, &destroyed2);
0298   }
0299   CHECK_ANY_ALLOCATIONS();
0300 }
0301 
0302 BOOST_AUTO_TEST_CASE(AnyMoveTypeChange) {
0303   BOOST_TEST_CONTEXT("Small type") {
0304     bool destroyed = false;
0305     D d{&destroyed};
0306     Any a{std::move(d)};
0307     BOOST_CHECK(!destroyed);
0308 
0309     int value = 5;
0310     Any b{value};
0311     a = std::move(b);
0312     BOOST_CHECK(destroyed);
0313     BOOST_CHECK_EQUAL(a.as<int>(), value);
0314   }
0315 
0316   bool destroyed = false;
0317   BOOST_TEST_CONTEXT("Large type") {
0318     D2 d{&destroyed};
0319     Any a{std::move(d)};
0320     BOOST_CHECK(!destroyed);
0321 
0322     int value = 5;
0323     Any b{value};
0324     a = std::move(b);
0325     BOOST_CHECK(destroyed);
0326     BOOST_CHECK_EQUAL(a.as<int>(), value);
0327   }
0328 }
0329 
0330 BOOST_AUTO_TEST_CASE(AnyCopyTypeChange) {
0331   BOOST_TEST_CONTEXT("Small type") {
0332     bool destroyed = false;
0333     D d{&destroyed};
0334     Any a{std::move(d)};
0335     BOOST_CHECK(!destroyed);
0336 
0337     int value = 5;
0338     Any b{value};
0339     a = b;
0340     BOOST_CHECK(destroyed);
0341     BOOST_CHECK_EQUAL(a.as<int>(), value);
0342   }
0343 
0344   bool destroyed = false;
0345   BOOST_TEST_CONTEXT("Large type") {
0346     D2 d{&destroyed};
0347     Any a{std::move(d)};
0348     BOOST_CHECK(!destroyed);
0349 
0350     int value = 5;
0351     Any b{value};
0352     a = b;
0353     BOOST_CHECK(destroyed);
0354     BOOST_CHECK_EQUAL(a.as<int>(), value);
0355   }
0356 }
0357 
0358 BOOST_AUTO_TEST_CASE(AnyCopyTypeChangeToHeap) {
0359   // Copy-assigning a heap-allocated value over an Any that already holds a
0360   // value of a different type. Before the fix this read the stale contents of
0361   // the internal buffer as the copy destination (a freed pointer for the
0362   // heap->heap case, or the previous local value's bytes for local->heap),
0363   // resulting in a wild write / use-after-free.
0364   using Large = std::array<unsigned long, 5>;
0365 
0366   BOOST_TEST_CONTEXT("local -> heap") {
0367     Any a{42};
0368     Large v{1, 2, 3, 4, 5};
0369     Any b{v};
0370     a = b;
0371     BOOST_CHECK_EQUAL_COLLECTIONS(a.as<Large>().begin(), a.as<Large>().end(),
0372                                   v.begin(), v.end());
0373     // source is left intact
0374     BOOST_CHECK_EQUAL_COLLECTIONS(b.as<Large>().begin(), b.as<Large>().end(),
0375                                   v.begin(), v.end());
0376   }
0377   CHECK_ANY_ALLOCATIONS();
0378 
0379   BOOST_TEST_CONTEXT("heap -> heap (different type)") {
0380     std::array<int, 4> v1{1, 2, 3, 4};
0381     Large v2{6, 7, 8, 9, 10};
0382     Any a{v1};
0383     Any b{v2};
0384     a = b;
0385     BOOST_CHECK_EQUAL_COLLECTIONS(a.as<Large>().begin(), a.as<Large>().end(),
0386                                   v2.begin(), v2.end());
0387   }
0388   CHECK_ANY_ALLOCATIONS();
0389 }
0390 
0391 BOOST_AUTO_TEST_CASE(AnyDestroy) {
0392   {  // small type
0393     bool destroyed = false;
0394     D d{&destroyed};
0395     BOOST_CHECK(!destroyed);
0396     {
0397       Any a{std::move(d)};
0398       BOOST_CHECK(!destroyed);
0399     }
0400     BOOST_CHECK(destroyed);
0401   }
0402   CHECK_ANY_ALLOCATIONS();
0403 
0404   {  // large type
0405     bool destroyed = false;
0406     D2 d{&destroyed};
0407     BOOST_CHECK(!destroyed);
0408     {
0409       Any a{std::move(d)};
0410       BOOST_CHECK(!destroyed);
0411     }
0412     BOOST_CHECK(destroyed);
0413   }
0414   CHECK_ANY_ALLOCATIONS();
0415 }
0416 
0417 BOOST_AUTO_TEST_CASE(AnyDestroyCopy) {
0418   {  // small type
0419     bool destroyed = false;
0420 
0421     {
0422       Any b;
0423       {
0424         Any a{std::in_place_type<D>, &destroyed};
0425         BOOST_CHECK(!destroyed);
0426         b = a;
0427         BOOST_CHECK(!destroyed);
0428       }
0429       BOOST_CHECK(destroyed);  // a destroyed, should be true
0430       destroyed = false;
0431       BOOST_CHECK(!destroyed);
0432     }
0433     BOOST_CHECK(destroyed);  // b destroyed, should be true again
0434   }
0435   CHECK_ANY_ALLOCATIONS();
0436 
0437   {  // large type
0438     bool destroyed = false;
0439 
0440     {
0441       Any b;
0442       {
0443         Any a{std::in_place_type<D2>, &destroyed};
0444         BOOST_CHECK(!destroyed);
0445         b = a;
0446         BOOST_CHECK(!destroyed);
0447       }
0448       BOOST_CHECK(destroyed);  // a destroyed, should be true
0449       destroyed = false;
0450       BOOST_CHECK(!destroyed);
0451     }
0452     BOOST_CHECK(destroyed);  // b destroyed, should be true again
0453   }
0454   CHECK_ANY_ALLOCATIONS();
0455 }
0456 
0457 BOOST_AUTO_TEST_CASE(AnyDestroyInPlace) {
0458   {  // small type
0459     bool destroyed = false;
0460     BOOST_CHECK(!destroyed);
0461     {
0462       Any a{std::in_place_type<D>, &destroyed};
0463       BOOST_CHECK(!destroyed);
0464     }
0465     BOOST_CHECK(destroyed);
0466   }
0467   CHECK_ANY_ALLOCATIONS();
0468 
0469   {  // large type
0470     bool destroyed = false;
0471     BOOST_CHECK(!destroyed);
0472     {
0473       Any a{std::in_place_type<D2>, &destroyed};
0474       BOOST_CHECK(!destroyed);
0475     }
0476     BOOST_CHECK(destroyed);
0477   }
0478   CHECK_ANY_ALLOCATIONS();
0479 }
0480 
0481 struct D3 {
0482   std::size_t* destroyed{nullptr};
0483   std::array<char, 512> blob{};
0484 
0485   explicit D3(std::size_t* d) : destroyed{d} {}
0486 
0487   ~D3() { (*destroyed)++; }
0488 };
0489 
0490 BOOST_AUTO_TEST_CASE(LeakCheck) {
0491   std::size_t destroyed = 0;
0492   for (std::size_t i = 0; i < 10000; i++) {
0493     {
0494       BOOST_CHECK_EQUAL(destroyed, i);
0495       Any a;
0496       BOOST_CHECK_EQUAL(destroyed, i);
0497       a = Any{std::in_place_type<D3>, &destroyed};
0498       BOOST_CHECK_EQUAL(destroyed, i);
0499     }
0500     BOOST_CHECK_EQUAL(destroyed, i + 1);
0501   }
0502   CHECK_ANY_ALLOCATIONS();
0503 }
0504 
0505 struct LifecycleCounters {
0506   std::size_t nDestroy = 0;
0507   std::size_t nCopyConstruct = 0;
0508   std::size_t nCopy = 0;
0509   std::size_t nMoveConstruct = 0;
0510   std::size_t nMove = 0;
0511 };
0512 
0513 template <std::size_t PADDING>
0514 struct Lifecycle;
0515 
0516 template <>
0517 struct Lifecycle<0> {
0518   LifecycleCounters* counters;
0519 
0520   explicit Lifecycle(LifecycleCounters* _counters) : counters{_counters} {}
0521 
0522   Lifecycle(Lifecycle&& o) {
0523     counters = o.counters;
0524     counters->nMoveConstruct++;
0525   }
0526 
0527   Lifecycle& operator=(Lifecycle&& o) {
0528     counters = o.counters;
0529     counters->nMove++;
0530     return *this;
0531   }
0532 
0533   Lifecycle(const Lifecycle& o) {
0534     counters = o.counters;
0535     counters->nCopyConstruct++;
0536   }
0537 
0538   Lifecycle& operator=(const Lifecycle& o) {
0539     counters = o.counters;
0540     counters->nCopy++;
0541     return *this;
0542   }
0543 
0544   ~Lifecycle() { counters->nDestroy++; }
0545 };
0546 
0547 template <std::size_t PADDING>
0548 struct Lifecycle : public Lifecycle<0> {
0549   std::array<char, PADDING> m_padding{};
0550 
0551   explicit Lifecycle(LifecycleCounters* _counters) : Lifecycle<0>(_counters) {}
0552 };
0553 
0554 template <std::size_t PADDING>
0555 struct LifecycleHandle {
0556   LifecycleCounters counters;
0557   Lifecycle<PADDING> inner;
0558 
0559   LifecycleHandle() : counters{}, inner{&counters} {}
0560 };
0561 
0562 #define checkCounters()                                                      \
0563   do {                                                                       \
0564     BOOST_REQUIRE_EQUAL(l.counters.nCopy, counters.nCopy);                   \
0565     BOOST_REQUIRE_EQUAL(l.counters.nCopyConstruct, counters.nCopyConstruct); \
0566     BOOST_REQUIRE_EQUAL(l.counters.nMove, counters.nMove);                   \
0567     BOOST_REQUIRE_EQUAL(l.counters.nMoveConstruct, counters.nMoveConstruct); \
0568     BOOST_REQUIRE_EQUAL(l.counters.nDestroy, counters.nDestroy);             \
0569   } while (0)
0570 
0571 #define makeCounter(counter, n) \
0572   do {                          \
0573     counter += n;               \
0574     checkCounters();            \
0575   } while (0)
0576 
0577 #define incCopyConstruct(n) makeCounter(counters.nCopyConstruct, n)
0578 #define incCopy(n) makeCounter(counters.nCopy, n)
0579 #define incMoveConstruct(n) makeCounter(counters.nMoveConstruct, n)
0580 #define incMove(n) makeCounter(counters.nMove, n)
0581 #define incDestroy(n) makeCounter(counters.nDestroy, n)
0582 
0583 BOOST_AUTO_TEST_CASE(LifeCycleSmall) {
0584   LifecycleCounters counters;
0585   LifecycleHandle<0> l;
0586 
0587   checkCounters();
0588 
0589   {
0590     const auto& o = l.inner;  // force copy
0591     Any a{o};
0592     incCopyConstruct(1);
0593 
0594     const auto& _a = a;  // force copy later
0595     {
0596       Any b{_a};
0597       incCopyConstruct(1);
0598     }
0599     incDestroy(1);
0600 
0601     {
0602       Any b;
0603       b = _a;
0604       incCopyConstruct(1);
0605       b = _a;
0606       incCopy(1);
0607     }
0608     incDestroy(1);
0609 
0610     {
0611       Any b{a};
0612       incCopyConstruct(1);
0613       b = a;
0614       incCopy(1);
0615     }
0616     incDestroy(1);
0617 
0618     {
0619       auto _a2 = a;
0620       incCopyConstruct(1);
0621       Any b;
0622       b = std::move(_a2);
0623       incMoveConstruct(1);
0624       auto _a3 = a;
0625       incCopyConstruct(1);
0626       b = std::move(_a3);
0627       incMove(1);
0628     }
0629     incDestroy(3);
0630   }
0631   incDestroy(1);
0632 
0633   checkCounters();
0634 
0635   CHECK_ANY_ALLOCATIONS();
0636 }
0637 
0638 BOOST_AUTO_TEST_CASE(LifeCycleHeap) {
0639   LifecycleCounters counters;
0640   LifecycleHandle<512> l;
0641 
0642   checkCounters();
0643 
0644   {
0645     const auto& o = l.inner;  // force copy
0646     Any a{o};
0647     incCopyConstruct(1);
0648 
0649     const auto& _a = a;  // force copy later
0650     {
0651       Any b{_a};
0652       incCopyConstruct(1);
0653     }
0654     incDestroy(1);
0655 
0656     {
0657       Any b;
0658       b = _a;
0659       incCopyConstruct(1);
0660       b = _a;
0661       incCopy(1);
0662     }
0663     incDestroy(1);
0664 
0665     {
0666       Any b{a};
0667       incCopyConstruct(1);
0668       b = a;
0669       incCopy(1);
0670     }
0671     incDestroy(1);
0672 
0673     {
0674       Any _a2 = a;
0675       incCopyConstruct(1);
0676       Any b;
0677       b = std::move(_a2);
0678       // no actual move
0679 
0680       Any _a3 = a;
0681       incCopyConstruct(1);
0682       b = std::move(_a3);
0683       // no actual move
0684       incDestroy(1);
0685     }
0686     incDestroy(1);
0687   }
0688   incDestroy(1);
0689 
0690   checkCounters();
0691 
0692   CHECK_ANY_ALLOCATIONS();
0693 }
0694 
0695 BOOST_AUTO_TEST_CASE(AnyMoveOnlyMoveOnlyTypes) {
0696   using MoveOnlyAny = Acts::AnyMoveOnly;
0697 
0698   using Ptr = std::unique_ptr<int>;
0699 
0700   // AnyMoveOnly can store move-only types
0701   {
0702     auto ptr = std::make_unique<int>(42);
0703     MoveOnlyAny a{std::move(ptr)};
0704     BOOST_CHECK(!!a);
0705     Ptr const* storedPtr = a.asPtr<Ptr>();
0706     BOOST_REQUIRE_NE(storedPtr, nullptr);
0707     BOOST_CHECK_NE(storedPtr->get(), nullptr);
0708     BOOST_CHECK_EQUAL(**storedPtr, 42);
0709   }
0710 
0711   // AnyMoveOnly is moveable
0712   {
0713     auto ptr = std::make_unique<int>(7);
0714     MoveOnlyAny a{std::move(ptr)};
0715     MoveOnlyAny b = std::move(a);
0716     // Note: moved-from Any may still report non-empty for local storage
0717     BOOST_CHECK(!!b);
0718     Ptr const* bPtr = b.asPtr<Ptr>();
0719     BOOST_REQUIRE_NE(bPtr, nullptr);
0720     int val = **bPtr;
0721     BOOST_CHECK_EQUAL(val, 7);
0722   }
0723 
0724   // AnyMoveOnly is not copyable
0725   static_assert(!std::is_copy_constructible_v<MoveOnlyAny>);
0726   static_assert(!std::is_copy_assignable_v<MoveOnlyAny>);
0727 }
0728 
0729 BOOST_AUTO_TEST_CASE(AnyTake) {
0730   // take() moves value out and leaves Any empty
0731   {
0732     Any a{42};
0733     BOOST_CHECK(!!a);
0734     int val = a.take<int>();
0735     BOOST_CHECK_EQUAL(val, 42);
0736     BOOST_CHECK(!a);
0737   }
0738 
0739   // take() with move-only type
0740   {
0741     auto ptr = std::make_unique<int>(99);
0742     Acts::AnyMoveOnly a{std::move(ptr)};
0743     BOOST_CHECK(!!a);
0744     auto taken = a.take<std::unique_ptr<int>>();
0745     BOOST_REQUIRE_NE(taken.get(), nullptr);
0746     BOOST_CHECK_EQUAL(*taken, 99);
0747     BOOST_CHECK(!a);
0748   }
0749 
0750   // take() throws on wrong type
0751   {
0752     Any a{42};
0753     BOOST_CHECK_THROW(a.take<float>(), std::bad_any_cast);
0754     BOOST_CHECK(!!a);
0755     BOOST_CHECK_EQUAL(a.as<int>(), 42);
0756   }
0757 
0758   // take() throws on empty
0759   {
0760     Any a;
0761     BOOST_CHECK_THROW(a.take<int>(), std::bad_any_cast);
0762   }
0763 }
0764 
0765 struct ThrowOnDemand {
0766   // larger than the small-buffer size, so this type is heap-allocated
0767   std::array<char, 64> blob{};
0768   explicit ThrowOnDemand(bool doThrow) {
0769     if (doThrow) {
0770       throw std::runtime_error{"boom"};
0771     }
0772   }
0773 };
0774 
0775 BOOST_AUTO_TEST_CASE(AnyEmplaceThrowingConstructor) {
0776   // If the constructor of the emplaced type throws, the previously held value
0777   // has already been destroyed, so the Any must be left empty rather than
0778   // claiming to hold a value whose storage was never initialised (which would
0779   // make the subsequent destruction operate on stale/freed memory).
0780   BOOST_TEST_CONTEXT("local") {
0781     struct SmallThrow {
0782       int x{0};
0783       explicit SmallThrow(bool doThrow) {
0784         if (doThrow) {
0785           throw std::runtime_error{"boom"};
0786         }
0787       }
0788     };
0789     Any a{std::in_place_type<SmallThrow>, false};
0790     BOOST_CHECK(!!a);
0791     BOOST_CHECK_THROW(a.emplace<SmallThrow>(true), std::runtime_error);
0792     BOOST_CHECK(!a);
0793   }
0794   CHECK_ANY_ALLOCATIONS();
0795 
0796   BOOST_TEST_CONTEXT("heap") {
0797     Any a{std::in_place_type<ThrowOnDemand>, false};
0798     BOOST_CHECK(!!a);
0799     BOOST_CHECK_THROW(a.emplace<ThrowOnDemand>(true), std::runtime_error);
0800     BOOST_CHECK(!a);
0801   }
0802   CHECK_ANY_ALLOCATIONS();
0803 }
0804 
0805 BOOST_AUTO_TEST_CASE(AnySelfAssign) {
0806   using Large = std::array<unsigned long, 5>;
0807 
0808   BOOST_TEST_CONTEXT("self copy-assign local") {
0809     Any a{7};
0810     Any& r = a;
0811     a = r;
0812     BOOST_CHECK_EQUAL(a.as<int>(), 7);
0813   }
0814   BOOST_TEST_CONTEXT("self copy-assign heap") {
0815     Large v{1, 2, 3, 4, 5};
0816     Any a{v};
0817     Any& r = a;
0818     a = r;
0819     BOOST_CHECK_EQUAL_COLLECTIONS(a.as<Large>().begin(), a.as<Large>().end(),
0820                                   v.begin(), v.end());
0821   }
0822   BOOST_TEST_CONTEXT("self move-assign local") {
0823     Any a{7};
0824     Any& r = a;
0825     a = std::move(r);
0826     BOOST_CHECK(!!a);
0827     BOOST_CHECK_EQUAL(a.as<int>(), 7);
0828   }
0829   BOOST_TEST_CONTEXT("self move-assign heap") {
0830     Large v{1, 2, 3, 4, 5};
0831     Any a{v};
0832     Any& r = a;
0833     a = std::move(r);
0834     BOOST_CHECK(!!a);
0835     BOOST_CHECK_EQUAL_COLLECTIONS(a.as<Large>().begin(), a.as<Large>().end(),
0836                                   v.begin(), v.end());
0837   }
0838   CHECK_ANY_ALLOCATIONS();
0839 }
0840 
0841 // Copying a heap-allocated value allocates and may run a throwing copy
0842 // constructor, so the copy operations must not be marked noexcept (otherwise
0843 // such a throw would call std::terminate). Moves only steal a pointer for
0844 // heap values, so they are noexcept (which lets std::vector<Any> move rather
0845 // than copy on reallocation) -- except in the allocation-tracking debug build
0846 // (_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS), where the tracking hooks may throw and
0847 // kAnyNoexcept is false.
0848 static_assert(!std::is_nothrow_copy_constructible_v<Any>);
0849 static_assert(!std::is_nothrow_copy_assignable_v<Any>);
0850 static_assert(std::is_nothrow_move_constructible_v<Any> ==
0851               detail::kAnyNoexcept);
0852 static_assert(std::is_nothrow_move_assignable_v<Any> == detail::kAnyNoexcept);
0853 
0854 struct HeapCopyThrows {
0855   // larger than the small-buffer size, so this type is heap-allocated
0856   std::array<int, 8> blob{};
0857   HeapCopyThrows() = default;
0858   HeapCopyThrows(const HeapCopyThrows& /*unused*/) {
0859     throw std::runtime_error{"copy boom"};
0860   }
0861   HeapCopyThrows& operator=(const HeapCopyThrows&) = default;
0862   HeapCopyThrows(HeapCopyThrows&&) = default;
0863   HeapCopyThrows& operator=(HeapCopyThrows&&) = default;
0864 };
0865 
0866 BOOST_AUTO_TEST_CASE(AnyCopyExceptionPropagates) {
0867   {
0868     Any a{std::in_place_type<HeapCopyThrows>};
0869 
0870     // copy construction propagates the stored type's throwing copy constructor
0871     BOOST_CHECK_THROW(Any b{a}, std::runtime_error);
0872 
0873     // copy assignment likewise propagates and leaves the target empty
0874     Any c;
0875     BOOST_CHECK_THROW(c = a, std::runtime_error);
0876     BOOST_CHECK(!c);
0877   }
0878   CHECK_ANY_ALLOCATIONS();
0879 }
0880 
0881 // ---------------------------------------------------------------------------
0882 // AnyOf<Base>: typed type-erasure over a common polymorphic base
0883 // ---------------------------------------------------------------------------
0884 
0885 namespace {
0886 
0887 struct AnyOfBase {
0888   virtual ~AnyOfBase() = default;
0889   virtual int value() const = 0;
0890 };
0891 
0892 // Small enough to live in the inline buffer (vtable ptr + int).
0893 struct DerivedSmall : public AnyOfBase {
0894   int v;
0895   explicit DerivedSmall(int x) : v{x} {}
0896   int value() const override { return v; }
0897 };
0898 
0899 // Large enough to force heap storage for the default sb_size of AnyOf (48).
0900 struct DerivedLarge : public AnyOfBase {
0901   std::array<std::int64_t, 16> pad{};
0902   int v;
0903   explicit DerivedLarge(int x) : v{x} { pad.fill(x); }
0904   int value() const override { return v; }
0905 };
0906 
0907 // AnyOfBase is the SECOND base, so its subobject sits at a non-zero offset
0908 // within the complete object. Exercises the upcast offset adjustment.
0909 struct OtherBase {
0910   virtual ~OtherBase() = default;
0911   std::int64_t tag = 0;
0912   virtual std::int64_t getTag() const { return tag; }
0913 };
0914 
0915 struct DerivedMI : public OtherBase, public AnyOfBase {
0916   int v;
0917   explicit DerivedMI(int x) : v{x} { tag = x; }
0918   int value() const override { return v; }
0919 };
0920 
0921 struct Unrelated {
0922   int v;
0923 };
0924 
0925 template <typename T>
0926 concept HasAsBase = requires(T t) { t.asBase(); };
0927 
0928 }  // namespace
0929 
0930 // Plain Any (Base == void) exposes no base accessors; AnyOf<Base> does.
0931 static_assert(!HasAsBase<Any>);
0932 static_assert(HasAsBase<AnyOf<AnyOfBase>>);
0933 
0934 // Storable iff convertible to AnyOfBase*; unrelated types are rejected.
0935 static_assert(std::is_constructible_v<AnyOf<AnyOfBase>, DerivedSmall>);
0936 static_assert(std::is_constructible_v<AnyOf<AnyOfBase>, DerivedLarge>);
0937 static_assert(std::is_constructible_v<AnyOf<AnyOfBase>, DerivedMI>);
0938 static_assert(!std::is_constructible_v<AnyOf<AnyOfBase>, Unrelated>);
0939 
0940 BOOST_AUTO_TEST_CASE(AnyOfBasicAccess) {
0941   {
0942     AnyOf<AnyOfBase> a{DerivedSmall{42}};
0943     BOOST_CHECK(static_cast<bool>(a));
0944 
0945     // base access via asBase / operator* / operator->
0946     BOOST_REQUIRE(a.asBase() != nullptr);
0947     BOOST_CHECK_EQUAL(a.asBase()->value(), 42);
0948     BOOST_CHECK_EQUAL((*a).value(), 42);
0949     BOOST_CHECK_EQUAL(a->value(), 42);
0950 
0951     // concrete type is still recoverable
0952     BOOST_REQUIRE(a.asPtr<DerivedSmall>() != nullptr);
0953     BOOST_CHECK_EQUAL(a.asPtr<DerivedSmall>()->v, 42);
0954     BOOST_CHECK(a.asPtr<DerivedLarge>() == nullptr);
0955   }
0956   CHECK_ANY_ALLOCATIONS();
0957 }
0958 
0959 BOOST_AUTO_TEST_CASE(AnyOfConstAccess) {
0960   {
0961     const AnyOf<AnyOfBase> a{DerivedSmall{7}};
0962     const AnyOfBase* base = a.asBase();
0963     BOOST_REQUIRE(base != nullptr);
0964     BOOST_CHECK_EQUAL(base->value(), 7);
0965     BOOST_CHECK_EQUAL((*a).value(), 7);
0966     BOOST_CHECK_EQUAL(a->value(), 7);
0967   }
0968   CHECK_ANY_ALLOCATIONS();
0969 }
0970 
0971 BOOST_AUTO_TEST_CASE(AnyOfEmpty) {
0972   {
0973     AnyOf<AnyOfBase> a;
0974     BOOST_CHECK(!a);
0975     BOOST_CHECK(a.asBase() == nullptr);
0976 
0977     const AnyOf<AnyOfBase> b;
0978     BOOST_CHECK(b.asBase() == nullptr);
0979   }
0980   CHECK_ANY_ALLOCATIONS();
0981 }
0982 
0983 BOOST_AUTO_TEST_CASE(AnyOfCopyMoveLocal) {
0984   {
0985     AnyOf<AnyOfBase> a{DerivedSmall{1}};
0986 
0987     // copy construct
0988     AnyOf<AnyOfBase> b{a};
0989     BOOST_CHECK_EQUAL(a->value(), 1);
0990     BOOST_CHECK_EQUAL(b->value(), 1);
0991     // independent copies
0992     BOOST_CHECK(a.asBase() != b.asBase());
0993 
0994     // copy assign
0995     AnyOf<AnyOfBase> c;
0996     c = a;
0997     BOOST_CHECK_EQUAL(c->value(), 1);
0998 
0999     // move construct
1000     AnyOf<AnyOfBase> d{std::move(b)};
1001     BOOST_CHECK_EQUAL(d->value(), 1);
1002 
1003     // move assign
1004     AnyOf<AnyOfBase> e;
1005     e = std::move(c);
1006     BOOST_CHECK_EQUAL(e->value(), 1);
1007   }
1008   CHECK_ANY_ALLOCATIONS();
1009 }
1010 
1011 BOOST_AUTO_TEST_CASE(AnyOfCopyMoveHeap) {
1012   {
1013     AnyOf<AnyOfBase> a{DerivedLarge{5}};
1014     BOOST_CHECK_EQUAL(a->value(), 5);
1015 
1016     // copy construct allocates a fresh, independent heap object
1017     AnyOf<AnyOfBase> b{a};
1018     BOOST_CHECK_EQUAL(b->value(), 5);
1019     BOOST_CHECK(a.asBase() != b.asBase());
1020 
1021     // copy assign
1022     AnyOf<AnyOfBase> c;
1023     c = a;
1024     BOOST_CHECK_EQUAL(c->value(), 5);
1025 
1026     // move construct steals the pointer
1027     AnyOf<AnyOfBase> d{std::move(b)};
1028     BOOST_CHECK_EQUAL(d->value(), 5);
1029     BOOST_CHECK(!b);
1030 
1031     // move assign
1032     AnyOf<AnyOfBase> e;
1033     e = std::move(c);
1034     BOOST_CHECK_EQUAL(e->value(), 5);
1035     BOOST_CHECK(!c);
1036   }
1037   CHECK_ANY_ALLOCATIONS();
1038 }
1039 
1040 BOOST_AUTO_TEST_CASE(AnyOfMultipleInheritanceOffset) {
1041   {
1042     AnyOf<AnyOfBase> a{DerivedMI{99}};
1043 
1044     DerivedMI* concrete = a.asPtr<DerivedMI>();
1045     BOOST_REQUIRE(concrete != nullptr);
1046 
1047     // Sanity: the AnyOfBase subobject is genuinely at a non-zero offset, so
1048     // this test actually exercises the offset adjustment.
1049     BOOST_REQUIRE(static_cast<void*>(static_cast<AnyOfBase*>(concrete)) !=
1050                   static_cast<void*>(concrete));
1051 
1052     // The function-pointer upcast must apply that same offset.
1053     BOOST_CHECK_EQUAL(a.asBase(), static_cast<AnyOfBase*>(concrete));
1054     BOOST_CHECK_EQUAL(a->value(), 99);
1055   }
1056   CHECK_ANY_ALLOCATIONS();
1057 }
1058 
1059 BOOST_AUTO_TEST_SUITE_END()
1060 
1061 }  // namespace ActsTests