write_global_data() is not exception-safe with respect to policy
initialization, so a throwing initialize() (or re-initialize(), the
documented dynamic-loading flow after a dlopen/dlclose) can leave shared
dispatch state pointing at freed memory.
Mechanism (include/boost/openmethod/initialize.hpp,
write_global_data(), ~line 1542):
- A local
new_dispatch_data vector is built (~1556).
- Every module's
class_info::static_vptr (~1639) and every overrider's
next pointer (~1484-1509) are patched to point into it.
- Only after that does
detail::initialize_policies<registry>::fn(...)
run (~1680) — this is where a policy's initialize can throw (e.g.
fast_perfect_hash's hash-factor search, with throw_error_handler, or a
plain bad_alloc).
- The commit (
new_dispatch_data.swap(static_::st.dispatch_data)) happens
last (~1682).
If the policy step throws, new_dispatch_data is destroyed while unwinding,
and every static_vptr/next pointer already patched into it is left
dangling.
Already mitigated: initialize() now resets static_::st.initialized = false at entry (was only set true on success), so require_initialized()
(the runtime_checks guard) correctly reports "not initialized" after a
failed re-initialize instead of a stale true. That closes the
false-positive-success half of the problem.
Still open: without runtime_checks (or even with it, between the
failed re-init and a subsequent successful one), the dangling pointers
themselves are unaddressed — a failed re-initialize clobbers the previous
working dispatch state, not just the new one.
Why it's not a quick fix: the obvious move — run
initialize_policies before the pointer-patching loops — is unsafe as-is:
vptr_vector::initialize reads the just-patched static_vptr values (via
iter->vptr()), so policy init currently depends on the patch having
already happened. A real fix needs the fallible policy work (mainly
fast_perfect_hash's search) to run into local state first, with the shared
mutations (static_vptr/next/slots_strides patches, the dispatch_data
swap, and each policy's committed state) applied only after every fallible
step succeeds — which crosses the current policy interface, where a
policy's initialize writes straight into shared state.
Pre-existing on develop (not introduced by the DLL state-sharing branch),
but the dynamic-loading feature makes re-initialization a documented,
common flow, which is what surfaced it.
write_global_data()is not exception-safe with respect to policyinitialization, so a throwing
initialize()(or re-initialize(), thedocumented dynamic-loading flow after a
dlopen/dlclose) can leave shareddispatch state pointing at freed memory.
Mechanism (
include/boost/openmethod/initialize.hpp,write_global_data(), ~line 1542):new_dispatch_datavector is built (~1556).class_info::static_vptr(~1639) and every overrider'snextpointer (~1484-1509) are patched to point into it.detail::initialize_policies<registry>::fn(...)run (~1680) — this is where a policy's
initializecan throw (e.g.fast_perfect_hash's hash-factor search, withthrow_error_handler, or aplain
bad_alloc).new_dispatch_data.swap(static_::st.dispatch_data)) happenslast (~1682).
If the policy step throws,
new_dispatch_datais destroyed while unwinding,and every
static_vptr/nextpointer already patched into it is leftdangling.
Already mitigated:
initialize()now resetsstatic_::st.initialized = falseat entry (was only settrueon success), sorequire_initialized()(the
runtime_checksguard) correctly reports "not initialized" after afailed re-initialize instead of a stale
true. That closes thefalse-positive-success half of the problem.
Still open: without
runtime_checks(or even with it, between thefailed re-init and a subsequent successful one), the dangling pointers
themselves are unaddressed — a failed re-initialize clobbers the previous
working dispatch state, not just the new one.
Why it's not a quick fix: the obvious move — run
initialize_policiesbefore the pointer-patching loops — is unsafe as-is:vptr_vector::initializereads the just-patchedstatic_vptrvalues (viaiter->vptr()), so policy init currently depends on the patch havingalready happened. A real fix needs the fallible policy work (mainly
fast_perfect_hash's search) to run into local state first, with the sharedmutations (
static_vptr/next/slots_stridespatches, thedispatch_dataswap, and each policy's committed state) applied only after every fallible
step succeeds — which crosses the current policy interface, where a
policy's
initializewrites straight into shared state.Pre-existing on
develop(not introduced by the DLL state-sharing branch),but the dynamic-loading feature makes re-initialization a documented,
common flow, which is what surfaced it.