From 570601ca180d1deed023994ce37c8b5c584fde79 Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Thu, 13 Aug 2026 10:28:37 +0200 Subject: [PATCH] Unregister only this module's type registration The type registry is a multiset ordered by `std::type_info`, so the registrations two modules make for the same type compare equal. `type_unregister` looked entries up by that ordering and erased every match, which meant the first module to be unloaded took the other modules' registrations with it. Serialization of that type then failed in a module still running, with `unregistered_class`, since `get_derived_extended_type_info` no longer found anything. Remedy: erase the one entry which is this object, the way `key_unregister` has always done it for the registry keyed by export name. Fixes #325. --- src/extended_type_info_typeid.cpp | 16 +++--- test/Jamfile.v2 | 1 + test/test_duplicate_type_registration.cpp | 64 +++++++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 test/test_duplicate_type_registration.cpp diff --git a/src/extended_type_info_typeid.cpp b/src/extended_type_info_typeid.cpp index a42725cd1..535083d75 100644 --- a/src/extended_type_info_typeid.cpp +++ b/src/extended_type_info_typeid.cpp @@ -102,14 +102,16 @@ extended_type_info_typeid_0::type_unregister() // BOOST_ASSERT(! singleton::is_destroyed()); if(! singleton::is_destroyed()){ tkmap & x = singleton::get_mutable_instance(); - - // remove all entries in map which corresponds to this type - // make sure that we don't use any invalidated iterators - while(true){ - const tkmap::iterator & it = x.find(this); - if(it == x.end()) + tkmap::iterator start = x.lower_bound(this); + const tkmap::iterator end = x.upper_bound(this); + // Entries compare equal when they describe the same type, and + // another module may well have registered that same type. Erase + // this entry alone, as key_unregister does. + for(; start != end; ++start){ + if(this == *start){ + x.erase(start); break; - x.erase(it); + } } } } diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index b79fb1576..7711379a8 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -155,6 +155,7 @@ if ! $(BOOST_ARCHIVE_LIST) { [ test-bsl-run test_dll_base_pointer : : dll_polymorphic_base dll_polymorphic_derived2 : static:no ] # [ test-bsl-run test_dll_plugin : : dll_derived2 : static:no linux:-ldl ] + [ test-bsl-run test_duplicate_type_registration ] [ test-bsl-run test_private_ctor ] [ test-bsl-run test_reset_object_address : A ] [ test-bsl-run test_void_cast ] diff --git a/test/test_duplicate_type_registration.cpp b/test/test_duplicate_type_registration.cpp new file mode 100644 index 000000000..7ffb3e36a --- /dev/null +++ b/test/test_duplicate_type_registration.cpp @@ -0,0 +1,64 @@ +/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8 +// test_duplicate_type_registration.cpp + +// Copyright 2026 Gennaro Prota. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for updates, documentation, and revision history. + +// Every module linked against the library registers the types it serializes, +// so the type registry holds one entry per module per type. A module going +// away has to take its own entry with it and leave the others alone, or +// serialization stops working in the modules still running. + +// Reported by gast128 in +// https://github.com/boostorg/serialization/issues/325, from two COM +// components in separate DLLs registering the same type: unloading one of +// them made the other throw unregistered_class. Both the diagnosis and the +// remedy in the report were right. Thanks for the careful write up! + +// singleton explicitly allows a class derived from it to be instantiated +// more than once, which is what stands in for the second module below. + +#include + +// The lightweight test of Boost.Core is used here in place of +// test_tools.hpp, whose main locks the singleton module while the test runs, +// so that the type registry cannot change. Loading and unloading a module +// changes it while the program runs, which is the very thing under test. +#include + +#include +#include + +struct base { + virtual ~base(){} +}; + +struct derived : base { +}; + +typedef boost::serialization::extended_type_info_typeid eti_derived; + +int +main(){ + const eti_derived & first = + boost::serialization::singleton::get_const_instance(); + + const derived d; + BOOST_TEST(NULL != first.get_derived_extended_type_info(d)); + + { + // A second module registers the same type. + const eti_derived second; + BOOST_TEST(NULL != first.get_derived_extended_type_info(d)); + BOOST_TEST(NULL != second.get_derived_extended_type_info(d)); + } + // The second module is unloaded here. + + BOOST_TEST(NULL != first.get_derived_extended_type_info(d)); + + return boost::report_errors(); +}