From f8bc875500d33ae102f3f7a1971abe484b0dfec5 Mon Sep 17 00:00:00 2001 From: Emil Dotchevski Date: Mon, 15 Dec 2025 00:35:41 -0500 Subject: [PATCH] - Refactored TLS implementation, including Win32 support across DLL bondaries - on_error now allocates dynamic storage (if enabled) before the stack unwind --- include/boost/leaf/config/tls.hpp | 20 ++-- include/boost/leaf/config/tls_array.hpp | 19 ++-- include/boost/leaf/config/tls_cpp11.hpp | 35 ++---- include/boost/leaf/config/tls_globals.hpp | 27 +---- include/boost/leaf/config/tls_win32.hpp | 26 ++--- include/boost/leaf/context.hpp | 8 +- include/boost/leaf/error.hpp | 129 +++++++++------------- include/boost/leaf/on_error.hpp | 7 ++ meson.build | 7 ++ test/Jamfile.v2 | 4 + test/so_dll_lib1.hpp | 10 +- test/so_dll_lib2.hpp | 10 +- test/so_dll_test.cpp | 6 +- test/so_dll_test.hpp | 2 +- 14 files changed, 128 insertions(+), 182 deletions(-) diff --git a/include/boost/leaf/config/tls.hpp b/include/boost/leaf/config/tls.hpp index 1326bc5..a495383 100644 --- a/include/boost/leaf/config/tls.hpp +++ b/include/boost/leaf/config/tls.hpp @@ -31,30 +31,32 @@ namespace tls // This function may not fail. unsigned read_current_error_id() noexcept; - // Write p to the TLS for T. The TLS may be allocated dynamically on the - // first call to write_ptr_alloc, but subsequent calls must reuse the - // same TLS. + // Reserve TLS storage for T. The TLS may be allocated dynamically on the + // first call to reserve_ptr, but subsequent calls must reuse the same + // TLS. On platforms where allocation is not needed, this function is + // still defined but does nothing. // // This function may throw on allocation failure. template - void write_ptr_alloc( T * p ); + void reserve_ptr(); - // Write p to the TLS previously allocated for T by a successful call to - // write_ptr_alloc. + // Write p to the TLS previously reserved for T by a call to reserve_ptr. + // It is illegal to call write_ptr without a prior successful call to + // reserve_ptr. // // This function may not fail. template void write_ptr( T * p ) noexcept; // Read the T * value previously written in the TLS for T. Returns nullptr - // if TLS for T has not yet been allocated. + // if TLS for T has not yet been reserved. // // This function may not fail. template T * read_ptr() noexcept; -} +} // namespace tls -} } +} } // namespace boost::leaf #if defined(BOOST_LEAF_TLS_FREERTOS) # include diff --git a/include/boost/leaf/config/tls_array.hpp b/include/boost/leaf/config/tls_array.hpp index b9e7a09..e0ab42e 100644 --- a/include/boost/leaf/config/tls_array.hpp +++ b/include/boost/leaf/config/tls_array.hpp @@ -100,16 +100,16 @@ namespace detail BOOST_LEAF_CFG_TLS_INDEX_TYPE tls_index::idx = BOOST_LEAF_CFG_TLS_ARRAY_START_INDEX + 1; template - struct BOOST_LEAF_SYMBOL_VISIBLE alloc_tls_index + struct BOOST_LEAF_SYMBOL_VISIBLE reserve_tls_index { static BOOST_LEAF_CFG_TLS_INDEX_TYPE const idx; }; template - BOOST_LEAF_CFG_TLS_INDEX_TYPE const alloc_tls_index::idx = tls_index::idx = index_counter<>::next(); -} + BOOST_LEAF_CFG_TLS_INDEX_TYPE const reserve_tls_index::idx = tls_index::idx = index_counter<>::next(); +} // namespace detail -} } +} } // namespace boost::leaf //////////////////////////////////////// @@ -137,12 +137,9 @@ namespace tls } template - BOOST_LEAF_ALWAYS_INLINE void write_ptr_alloc( T * p ) + BOOST_LEAF_ALWAYS_INLINE void reserve_ptr() { - int tls_idx = detail::alloc_tls_index::idx; - --tls_idx; - write_void_ptr(tls_idx, p); - BOOST_LEAF_ASSERT(read_void_ptr(tls_idx) == p); + (void) detail::reserve_tls_index::idx; } template @@ -165,6 +162,8 @@ namespace tls --tls_idx; return reinterpret_cast(read_void_ptr(tls_idx)); } -} +} // namespace tls + +} } // namespace boost::leaf #endif // #ifndef BOOST_LEAF_CONFIG_TLS_ARRAY_HPP_INCLUDED diff --git a/include/boost/leaf/config/tls_cpp11.hpp b/include/boost/leaf/config/tls_cpp11.hpp index cc2b6cb..f5aa5c7 100644 --- a/include/boost/leaf/config/tls_cpp11.hpp +++ b/include/boost/leaf/config/tls_cpp11.hpp @@ -38,15 +38,17 @@ namespace detail template thread_local T * ptr::p; - struct current_error_id_storage + template + struct BOOST_LEAF_SYMBOL_VISIBLE current_error_id_storage { static thread_local unsigned x; }; - thread_local unsigned current_error_id_storage::x; -} + template + thread_local unsigned current_error_id_storage::x; +} // namespace detail -} } +} } // namespace boost::leaf //////////////////////////////////////// @@ -63,30 +65,7 @@ namespace tls BOOST_LEAF_ALWAYS_INLINE void write_current_error_id( unsigned x ) noexcept { - detail::current_error_id_storage::x = x; - } - - BOOST_LEAF_ALWAYS_INLINE unsigned read_current_error_id() noexcept - { - return detail::current_error_id_storage::x; - } - - template - BOOST_LEAF_ALWAYS_INLINE void write_ptr_alloc( T * p ) - { - detail::ptr::p = p; - } - - template - BOOST_LEAF_ALWAYS_INLINE void write_ptr( T * p ) noexcept - { - detail::ptr::p = p; - } - - template - BOOST_LEAF_ALWAYS_INLINE T * read_ptr() noexcept - { - return detail::ptr::p; + detail::current_error_id_storage<>::x = x; } BOOST_LEAF_ALWAYS_INLINE unsigned read_current_error_id() noexcept diff --git a/include/boost/leaf/config/tls_globals.hpp b/include/boost/leaf/config/tls_globals.hpp index 806f490..66f3d57 100644 --- a/include/boost/leaf/config/tls_globals.hpp +++ b/include/boost/leaf/config/tls_globals.hpp @@ -42,9 +42,9 @@ namespace detail template unsigned current_error_id_storage::x = 0; -} +} // namespace detail -} } +} } // namespace boost::leaf //////////////////////////////////////// @@ -69,29 +69,6 @@ namespace tls return detail::current_error_id_storage<>::x; } - template - BOOST_LEAF_ALWAYS_INLINE void write_ptr_alloc( T * p ) - { - detail::ptr::p = p; - } - - template - BOOST_LEAF_ALWAYS_INLINE void write_ptr( T * p ) noexcept - { - detail::ptr::p = p; - } - - template - BOOST_LEAF_ALWAYS_INLINE T * read_ptr() noexcept - { - return detail::ptr::p; - } - - BOOST_LEAF_ALWAYS_INLINE unsigned read_current_error_id() noexcept - { - return detail::current_error_id_storage<>::x; - } - template BOOST_LEAF_ALWAYS_INLINE void reserve_ptr() { diff --git a/include/boost/leaf/config/tls_win32.hpp b/include/boost/leaf/config/tls_win32.hpp index 61da3ae..cb67841 100644 --- a/include/boost/leaf/config/tls_win32.hpp +++ b/include/boost/leaf/config/tls_win32.hpp @@ -211,7 +211,7 @@ namespace detail { return error_id_storage_; } - }; + }; // class slot_map class module_state { @@ -300,6 +300,7 @@ namespace detail } else if (dwReason == DLL_PROCESS_DETACH) { + BOOST_LEAF_ASSERT(sm_ || tls_failures_); if (sm_) { sm_->release(); @@ -307,7 +308,7 @@ namespace detail } } } - }; + }; // class module_state template struct module @@ -334,7 +335,7 @@ namespace detail #ifdef _MSC_VER #pragma section(".CRT$XLB", long, read) #pragma data_seg(push, ".CRT$XLB") - extern "C" PIMAGE_TLS_CALLBACK boost_leaf_tls_callback = tls_callback; + extern "C" __declspec(selectany) PIMAGE_TLS_CALLBACK boost_leaf_tls_callback = tls_callback; #pragma data_seg(pop) #ifdef _WIN64 #pragma comment(linker, "/INCLUDE:boost_leaf_tls_callback") @@ -342,11 +343,11 @@ namespace detail #pragma comment(linker, "/INCLUDE:_boost_leaf_tls_callback") #endif #elif defined(__GNUC__) - extern "C" __attribute__((used)) PIMAGE_TLS_CALLBACK boost_leaf_tls_callback __attribute__((section(".CRT$XLB"))) = tls_callback; + extern "C" __attribute__((used, weak)) PIMAGE_TLS_CALLBACK boost_leaf_tls_callback __attribute__((section(".CRT$XLB"))) = tls_callback; #endif -} +} // namespace detail -} } +} } // namespace boost::leaf //////////////////////////////////////// @@ -377,14 +378,11 @@ namespace tls } template - BOOST_LEAF_ALWAYS_INLINE void write_ptr_alloc(T * p) + BOOST_LEAF_ALWAYS_INLINE void reserve_ptr() { using namespace detail; thread_local DWORD const cached_slot = module<>::state.sm().get(type_hash()); - DWORD slot = cached_slot; - BOOST_LEAF_ASSERT(slot != TLS_OUT_OF_INDEXES); - BOOL r = TlsSetValue(slot, p); - BOOST_LEAF_ASSERT(r), (void) r; + BOOST_LEAF_ASSERT(cached_slot != TLS_OUT_OF_INDEXES), (void) cached_slot; } template @@ -412,8 +410,8 @@ namespace tls BOOST_LEAF_ASSERT(GetLastError() == ERROR_SUCCESS); return static_cast(value); } -} +} // namespace tls -} } +} } // namespace boost::leaf -#endif // BOOST_LEAF_CONFIG_TLS_WIN32_HPP_INCLUDED +#endif // #ifndef BOOST_LEAF_CONFIG_TLS_WIN32_HPP_INCLUDED diff --git a/include/boost/leaf/context.hpp b/include/boost/leaf/context.hpp index f89d9e1..63518f3 100644 --- a/include/boost/leaf/context.hpp +++ b/include/boost/leaf/context.hpp @@ -180,7 +180,7 @@ namespace detail template struct tuple_for_each { - BOOST_LEAF_CONSTEXPR static void activate( Tup & tup ) + BOOST_LEAF_CONSTEXPR static void activate( Tup & tup ) noexcept { static_assert(!std::is_same(tup))>::type>::value, "Bug in LEAF: context type deduction"); tuple_for_each::activate(tup); @@ -286,7 +286,7 @@ class context raii_deactivator & operator=( raii_deactivator const & ) = delete; context * ctx_; public: - explicit BOOST_LEAF_CONSTEXPR BOOST_LEAF_ALWAYS_INLINE raii_deactivator(context & ctx): + explicit BOOST_LEAF_CONSTEXPR BOOST_LEAF_ALWAYS_INLINE raii_deactivator(context & ctx) noexcept: ctx_(ctx.is_active() ? nullptr : &ctx) { if( ctx_ ) @@ -335,7 +335,7 @@ public: return tup_; } - BOOST_LEAF_CONSTEXPR void activate() + BOOST_LEAF_CONSTEXPR void activate() noexcept { using namespace detail; BOOST_LEAF_ASSERT(!is_active()); @@ -396,7 +396,7 @@ public: template BOOST_LEAF_CONSTEXPR R handle_error( error_id, H && ... ); - friend BOOST_LEAF_CONSTEXPR BOOST_LEAF_ALWAYS_INLINE raii_deactivator activate_context(context & ctx) + friend BOOST_LEAF_CONSTEXPR BOOST_LEAF_ALWAYS_INLINE raii_deactivator activate_context(context & ctx) noexcept { return raii_deactivator(ctx); } diff --git a/include/boost/leaf/error.hpp b/include/boost/leaf/error.hpp index acb1e23..2e2fe33 100644 --- a/include/boost/leaf/error.hpp +++ b/include/boost/leaf/error.hpp @@ -140,10 +140,10 @@ namespace detail BOOST_LEAF_ASSERT(tls::read_ptr>() != this); } - void activate() + void activate() noexcept { prev_ = tls::read_ptr>(); - tls::write_ptr_alloc>(this); + tls::write_ptr>(this); } void deactivate() const noexcept @@ -179,18 +179,12 @@ namespace detail { class preloaded_base; - template - struct capturing_slot_node_allocator; - class dynamic_allocator: capture_list { dynamic_allocator( dynamic_allocator const & ) = delete; dynamic_allocator & operator=( dynamic_allocator const & ) = delete; - template - friend struct capturing_slot_node_allocator; - preloaded_base * preloaded_list_; class capturing_node: @@ -325,7 +319,7 @@ namespace detail BOOST_LEAF_ASSERT(last_ != nullptr); BOOST_LEAF_ASSERT(*last_ == nullptr); BOOST_LEAF_ASSERT(tls::read_ptr>() == nullptr); - capturing_slot_node * csn = capturing_slot_node_allocator::new_(last_); + capturing_slot_node * csn = new capturing_slot_node(last_); csn->activate(); return csn; } @@ -365,21 +359,6 @@ namespace detail using capture_list::print; }; // class dynamic_allocator - template - struct capturing_slot_node_allocator - { - template - static dynamic_allocator::capturing_slot_node * new_( A && ... a ) - { - return new dynamic_allocator::capturing_slot_node(std::forward(a)...); - } - - static void delete_( dynamic_allocator::capturing_slot_node * p ) noexcept - { - delete p; - } - }; - template <> class slot { @@ -389,64 +368,30 @@ namespace detail dynamic_allocator da_; slot * prev_; - template - inline void dynamic_load_( int err_id, E && e ) - { - if( slot * sl = tls::read_ptr>() ) - if( dynamic_allocator * c = sl->has_value_any_key() ) - c->dynamic_load(err_id, std::forward(e)); - else - sl->load(err_id).dynamic_load(err_id, std::forward(e)); - } + public: - template - inline void dynamic_accumulate_( int err_id, F && f ) - { - if( slot * sl = tls::read_ptr>() ) - if( dynamic_allocator * c = sl->has_value(err_id) ) - (void) std::forward(f)(c->dynamic_load(err_id, E{})); - else - (void) std::forward(f)(sl->load(err_id).dynamic_load(err_id, E{})); - } - - template - inline void dynamic_load( int err_id, E && e ) noexcept(OnError) - { -#ifndef BOOST_LEAF_NO_EXCEPTIONS - if( OnError ) + slot() noexcept: + prev_(nullptr) { - try - { - dynamic_load_(err_id, std::forward(e)); - } - catch(...) - { - } + tls::reserve_ptr>(); } - else -#endif - dynamic_load_(err_id, std::forward(e)); - } - template - inline void dynamic_load_accumulate( int err_id, F && f ) noexcept(OnError) - { -#ifndef BOOST_LEAF_NO_EXCEPTIONS - if( OnError ) + slot( slot && x ) noexcept: + da_(std::move(x.da_)), + prev_(nullptr) { - try - { - dynamic_accumulate_(err_id, std::forward(f)); - } - catch(...) - { - } + BOOST_LEAF_ASSERT(x.prev_ == nullptr); + } + + ~slot() noexcept + { + BOOST_LEAF_ASSERT(tls::read_ptr>() != this); + } + + dynamic_allocator const & get() const noexcept + { + return da_; } - else -#endif - dynamic_accumulate_(err_id, std::forward(f)); - } -} dynamic_allocator & get() noexcept { @@ -599,6 +544,38 @@ namespace detail namespace detail { +#if BOOST_LEAF_CFG_CAPTURE + class preloaded_base + { + protected: + + preloaded_base() noexcept: + next_( + []( preloaded_base * this_ ) -> preloaded_base * + { + if( dynamic_allocator * da = get_dynamic_allocator() ) + return da->link_preloaded_item(this_); + return nullptr; + }(this)) + { + } + + ~preloaded_base() noexcept + { + if( dynamic_allocator * da = get_dynamic_allocator() ) + da->unlink_preloaded_item(next_); + else + BOOST_LEAF_ASSERT(next_ == nullptr); + } + + public: + + preloaded_base * const next_; + + virtual void reserve( dynamic_allocator & ) const = 0; + }; +#endif // #if BOOST_LEAF_CFG_CAPTURE + inline int current_id() noexcept { unsigned id = tls::read_current_error_id(); diff --git a/include/boost/leaf/on_error.hpp b/include/boost/leaf/on_error.hpp index 59147e5..79b079e 100644 --- a/include/boost/leaf/on_error.hpp +++ b/include/boost/leaf/on_error.hpp @@ -261,6 +261,13 @@ namespace detail bool moved_ = false; #endif +#if BOOST_LEAF_CFG_CAPTURE + void reserve( dynamic_allocator & da ) const override + { + tuple_for_each_preload::reserve(p_,da); + } +#endif + public: BOOST_LEAF_CONSTEXPR explicit preloaded( Item && ... i ): diff --git a/meson.build b/meson.build index 4fd008a..b0250da 100644 --- a/meson.build +++ b/meson.build @@ -238,6 +238,13 @@ if option_enable_unit_tests so_dll_test_lib2 = shared_library('so_dll_test_lib2', 'test/so_dll_lib2.cpp', dependencies: [leaf, dep_so_dll], gnu_symbol_visibility: 'hidden') test('so_dll_test', executable('so_dll_test', 'test/so_dll_test.cpp', dependencies: [leaf, dep_so_dll], link_with: [so_dll_test_lib1, so_dll_test_lib2])) + # Test that the DLL test works when linked statically as well. + # This is to verify that we don't get multiply defined symbol link errors when tls_win32.hpp is included in multiple compilation units.. + dep_so_dll_static = declare_dependency(compile_args: ['-DBOOST_LEAF_SO_DLL_TEST_STATIC']) + so_dll_static_lib1 = static_library('so_dll_static_lib1', 'test/so_dll_lib1.cpp', dependencies: [leaf, dep_so_dll, dep_so_dll_static]) + so_dll_static_lib2 = static_library('so_dll_static_lib2', 'test/so_dll_lib2.cpp', dependencies: [leaf, dep_so_dll, dep_so_dll_static]) + test('so_dll_static_test', executable('so_dll_static_test', 'test/so_dll_test.cpp', dependencies: [leaf, dep_so_dll, dep_so_dll_static], link_with: [so_dll_static_lib1, so_dll_static_lib2])) + endif ################################# diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 2b60e49..3a8fab8 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -137,6 +137,10 @@ lib so_dll_lib1 : so_dll_lib1.cpp : shared hidden w lib so_dll_lib2 : so_dll_lib2.cpp : shared hidden windows:BOOST_LEAF_CFG_WIN32=2 : : windows:BOOST_LEAF_CFG_WIN32=2 ; run so_dll_test.cpp so_dll_lib1 so_dll_lib2 : : : windows:BOOST_LEAF_CFG_WIN32=2 ; +lib so_dll_static_lib1 : so_dll_lib1.cpp : static BOOST_LEAF_SO_DLL_TEST_STATIC windows:BOOST_LEAF_CFG_WIN32=2 ; +lib so_dll_static_lib2 : so_dll_lib2.cpp : static BOOST_LEAF_SO_DLL_TEST_STATIC windows:BOOST_LEAF_CFG_WIN32=2 ; +run so_dll_test.cpp so_dll_static_lib1 so_dll_static_lib2 : : : BOOST_LEAF_SO_DLL_TEST_STATIC windows:BOOST_LEAF_CFG_WIN32=2 : so_dll_static_test ; + compile-fail _compile-fail-arg_boost_error_info_1.cpp ; compile-fail _compile-fail-arg_boost_error_info_2.cpp ; compile-fail _compile-fail-arg_catch_1.cpp ; diff --git a/test/so_dll_lib1.hpp b/test/so_dll_lib1.hpp index 79bf5b7..7e00dc4 100644 --- a/test/so_dll_lib1.hpp +++ b/test/so_dll_lib1.hpp @@ -8,19 +8,17 @@ #include "so_dll_test.hpp" #ifdef _WIN32 -# ifdef BOOST_LEAF_SO_DLL_TEST_BUILDING_LIB1 +# ifdef BOOST_LEAF_SO_DLL_TEST_STATIC +# define BOOST_LEAF_SO_DLL_TEST_LIB1_API +# elif defined(BOOST_LEAF_SO_DLL_TEST_BUILDING_LIB1) # define BOOST_LEAF_SO_DLL_TEST_LIB1_API __declspec(dllexport) # else # define BOOST_LEAF_SO_DLL_TEST_LIB1_API __declspec(dllimport) # endif #else -# define BOOST_LEAF_SO_DLL_TEST_LIB1_API BOOST_LEAF_SYMBOL_VISIBLE +# define BOOST_LEAF_SO_DLL_TEST_LIB1_API [[gnu::visibility("default")]] #endif -namespace boost { namespace leaf { - template class result; -} } - BOOST_LEAF_SO_DLL_TEST_LIB1_API boost::leaf::result hidden_result1(); #ifndef BOOST_LEAF_NO_EXCEPTIONS diff --git a/test/so_dll_lib2.hpp b/test/so_dll_lib2.hpp index 4946928..35cb138 100644 --- a/test/so_dll_lib2.hpp +++ b/test/so_dll_lib2.hpp @@ -8,19 +8,17 @@ #include "so_dll_test.hpp" #ifdef _WIN32 -# ifdef BOOST_LEAF_SO_DLL_TEST_BUILDING_LIB2 +# ifdef BOOST_LEAF_SO_DLL_TEST_STATIC +# define BOOST_LEAF_SO_DLL_TEST_LIB2_API +# elif defined(BOOST_LEAF_SO_DLL_TEST_BUILDING_LIB2) # define BOOST_LEAF_SO_DLL_TEST_LIB2_API __declspec(dllexport) # else # define BOOST_LEAF_SO_DLL_TEST_LIB2_API __declspec(dllimport) # endif #else -# define BOOST_LEAF_SO_DLL_TEST_LIB2_API BOOST_LEAF_SYMBOL_VISIBLE +# define BOOST_LEAF_SO_DLL_TEST_LIB2_API [[gnu::visibility("default")]] #endif -namespace boost { namespace leaf { - template class result; -} } - BOOST_LEAF_SO_DLL_TEST_LIB2_API boost::leaf::result hidden_result2(); #ifndef BOOST_LEAF_NO_EXCEPTIONS diff --git a/test/so_dll_test.cpp b/test/so_dll_test.cpp index 6963e71..c16f41c 100644 --- a/test/so_dll_test.cpp +++ b/test/so_dll_test.cpp @@ -126,7 +126,7 @@ int test_catch(void (*f)()) return 2; } } -#endif +#endif // #ifndef BOOST_LEAF_NO_EXCEPTIONS void test_single_thread() { @@ -138,7 +138,7 @@ void test_single_thread() BOOST_TEST_EQ(test_catch(hidden_throw1), 0); BOOST_TEST_EQ(test_exception(hidden_throw2, true), 0); BOOST_TEST_EQ(test_catch(hidden_throw2), 0); -#endif +#endif // #ifndef BOOST_LEAF_NO_EXCEPTIONS } void test_multithreaded() @@ -212,7 +212,7 @@ void test_multithreaded() for (auto & f : futures) BOOST_TEST_EQ(f.get(), 0); } -#endif +#endif // #ifndef BOOST_LEAF_NO_EXCEPTIONS } int main() diff --git a/test/so_dll_test.hpp b/test/so_dll_test.hpp index 1ab9a9b..9d6306e 100644 --- a/test/so_dll_test.hpp +++ b/test/so_dll_test.hpp @@ -14,7 +14,7 @@ // Note, under BOOST_LEAF_CFG_WIN32==2 (using Win32 TLS) we do not need // import/export for error types. We still need default visibility on POSIX. template -struct BOOST_LEAF_SYMBOL_VISIBLE my_info +struct [[gnu::visibility("default")]] my_info { int value;