diff options
author | Benoit Germain <benoit.germain@ubisoft.com> | 2025-04-07 08:50:10 +0200 |
---|---|---|
committer | Benoit Germain <benoit.germain@ubisoft.com> | 2025-04-07 08:50:10 +0200 |
commit | 98351dc6e48dabc02a941872b0aad7f795e665bd (patch) | |
tree | aaa37853fca9437759dd2312debfed585700a608 /src | |
parent | 5e29202b78d4932db079b21f0324ea57d8902bda (diff) | |
download | lanes-98351dc6e48dabc02a941872b0aad7f795e665bd.tar.gz lanes-98351dc6e48dabc02a941872b0aad7f795e665bd.tar.bz2 lanes-98351dc6e48dabc02a941872b0aad7f795e665bd.zip |
Fix crash with multi keepers (invalid memory access on close)
Diffstat (limited to '')
-rw-r--r-- | src/keeper.cpp | 36 | ||||
-rw-r--r-- | src/keeper.hpp | 13 |
2 files changed, 15 insertions, 34 deletions
diff --git a/src/keeper.cpp b/src/keeper.cpp index cad9207..c8c470f 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp | |||
@@ -811,22 +811,6 @@ KeeperCallResult keeper_call(KeeperState const K_, keeper_api_t const func_, lua | |||
811 | // ################################################################################################# | 811 | // ################################################################################################# |
812 | // ################################################################################################# | 812 | // ################################################################################################# |
813 | 813 | ||
814 | void* Keeper::operator new[](size_t size_, Universe* U_) noexcept | ||
815 | { | ||
816 | // size_ is the memory for the element count followed by the elements themselves | ||
817 | return U_->internalAllocator.alloc(size_); | ||
818 | } | ||
819 | |||
820 | // ################################################################################################# | ||
821 | |||
822 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
823 | void Keeper::operator delete[](void* p_, Universe* U_) | ||
824 | { | ||
825 | U_->internalAllocator.free(p_, *static_cast<size_t*>(p_) * sizeof(Keeper) + sizeof(size_t)); | ||
826 | } | ||
827 | |||
828 | // ################################################################################################# | ||
829 | |||
830 | // only used by linda:dump() and linda:__towatch() for debugging purposes | 814 | // only used by linda:dump() and linda:__towatch() for debugging purposes |
831 | // table is populated as follows: | 815 | // table is populated as follows: |
832 | // { | 816 | // { |
@@ -927,7 +911,7 @@ void Keepers::DeleteKV::operator()(Keeper* const k_) const | |||
927 | for (auto& _k : std::span<Keeper>(k_, count)) { | 911 | for (auto& _k : std::span<Keeper>(k_, count)) { |
928 | _k.~Keeper(); | 912 | _k.~Keeper(); |
929 | } | 913 | } |
930 | U->internalAllocator.free(k_, count * sizeof(Keeper)); | 914 | U.internalAllocator.free(k_, count * sizeof(Keeper)); |
931 | } | 915 | } |
932 | 916 | ||
933 | // ################################################################################################# | 917 | // ################################################################################################# |
@@ -959,8 +943,8 @@ void Keepers::collectGarbage() | |||
959 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists | 943 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists |
960 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success | 944 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success |
961 | // which is early-outed with a keepers->nbKeepers null-check | 945 | // which is early-outed with a keepers->nbKeepers null-check |
962 | for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, _kv.nbKeepers }) { | 946 | for (Keeper& _k : std::span<Keeper>{ _kv.keepers.get(), _kv.nbKeepers }) { |
963 | _gcOneKeeper(_kv.keepers[_i]); | 947 | _gcOneKeeper(_k); |
964 | } | 948 | } |
965 | } | 949 | } |
966 | } | 950 | } |
@@ -996,9 +980,8 @@ void Keepers::close() | |||
996 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists | 980 | // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists |
997 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success | 981 | // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success |
998 | // which is early-outed with a keepers->nbKeepers null-check | 982 | // which is early-outed with a keepers->nbKeepers null-check |
999 | size_t const _nbKeepers{ std::exchange(_kv.nbKeepers, size_t{ 0 }) }; | 983 | for (Keeper& _k : std::span<Keeper>{ _kv.keepers.get(), std::exchange(_kv.nbKeepers, size_t{ 0 }) }) { |
1000 | for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, _nbKeepers }) { | 984 | if (!_closeOneKeeper(_k)) { |
1001 | if (!_closeOneKeeper(_kv.keepers[_i])) { | ||
1002 | // detected partial init: destroy only the mutexes that got initialized properly | 985 | // detected partial init: destroy only the mutexes that got initialized properly |
1003 | break; | 986 | break; |
1004 | } | 987 | } |
@@ -1137,11 +1120,14 @@ void Keepers::initialize(Universe& U_, lua_State* L_, size_t const nbKeepers_, i | |||
1137 | 1120 | ||
1138 | default: | 1121 | default: |
1139 | KV& _kv = keeper_array.emplace<KV>( | 1122 | KV& _kv = keeper_array.emplace<KV>( |
1140 | std::unique_ptr<Keeper[], DeleteKV>{ new(&U_) Keeper[nbKeepers_], DeleteKV{ &U_, nbKeepers_ } }, | 1123 | std::unique_ptr<Keeper, DeleteKV>{ static_cast<Keeper*>(U_.internalAllocator.alloc(sizeof(Keeper) * nbKeepers_)), DeleteKV{ U_, nbKeepers_ } }, |
1141 | nbKeepers_ | 1124 | nbKeepers_ |
1142 | ); | 1125 | ); |
1143 | for (size_t const _i : std::ranges::iota_view{ size_t{ 0 }, nbKeepers_ }) { | 1126 | // fak. std::ranges::views::enumerate is c++23 (would help having item and index iterated over simultaneously) |
1144 | _initOneKeeper(_kv.keepers[_i], static_cast<int>(_i)); | 1127 | int _i{}; |
1128 | for (Keeper& _k : std::span<Keeper>{ _kv.keepers.get(), nbKeepers_ }) { | ||
1129 | new (&_k) Keeper{}; | ||
1130 | _initOneKeeper(_k, _i++); | ||
1145 | } | 1131 | } |
1146 | } | 1132 | } |
1147 | } | 1133 | } |
diff --git a/src/keeper.hpp b/src/keeper.hpp index 5cebe07..f1083b3 100644 --- a/src/keeper.hpp +++ b/src/keeper.hpp | |||
@@ -26,12 +26,6 @@ struct Keeper | |||
26 | std::mutex mutex; | 26 | std::mutex mutex; |
27 | KeeperState K{ static_cast<lua_State*>(nullptr) }; | 27 | KeeperState K{ static_cast<lua_State*>(nullptr) }; |
28 | 28 | ||
29 | [[nodiscard]] | ||
30 | static void* operator new[](size_t size_, Universe* U_) noexcept; | ||
31 | // can't actually delete the operator because the compiler generates stack unwinding code that could call it in case of exception | ||
32 | static void operator delete[](void* p_, Universe* U_); | ||
33 | |||
34 | |||
35 | ~Keeper() = default; | 29 | ~Keeper() = default; |
36 | Keeper() = default; | 30 | Keeper() = default; |
37 | // non-copyable, non-movable | 31 | // non-copyable, non-movable |
@@ -51,14 +45,15 @@ struct Keepers | |||
51 | private: | 45 | private: |
52 | struct DeleteKV | 46 | struct DeleteKV |
53 | { | 47 | { |
54 | Universe* U{}; | 48 | Universe& U; |
55 | size_t count{}; | 49 | size_t count{}; |
56 | void operator()(Keeper* k_) const; | 50 | void operator()(Keeper* k_) const; |
57 | }; | 51 | }; |
58 | // can't use std::vector<Keeper> because Keeper contains a mutex, so we need a raw memory buffer | 52 | // can't use std::unique_ptr<Keeper[]> because of interactions with placement new and custom deleters |
53 | // and I'm not using std::vector<Keeper> because I don't have an allocator to plug on the Universe (yet) | ||
59 | struct KV | 54 | struct KV |
60 | { | 55 | { |
61 | std::unique_ptr<Keeper[], DeleteKV> keepers; | 56 | std::unique_ptr<Keeper, DeleteKV> keepers; |
62 | size_t nbKeepers{}; | 57 | size_t nbKeepers{}; |
63 | }; | 58 | }; |
64 | std::variant<std::monostate, Keeper, KV> keeper_array; | 59 | std::variant<std::monostate, Keeper, KV> keeper_array; |