Compare commits

..

1 Commits

Author SHA1 Message Date
Gennaro Prota 10ecf35a6e Run CI on merge-queue candidates 2026-07-17 16:20:13 +02:00
17 changed files with 37 additions and 638 deletions
+2 -18
View File
@@ -244,15 +244,7 @@ jobs:
run: |
cd ../boost-root
export ADDRMD=${{matrix.address-model}}
./b2 -j3 libs/$LIBRARY/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release
- name: Run examples
run: |
cd ../boost-root
export ADDRMD=${{matrix.address-model}}
# The demos use fixed temp filenames, so run them serially (-j1)
# to avoid concurrent runs clobbering each other's archive files.
./b2 -j1 libs/$LIBRARY/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release
./b2 -j3 libs/$LIBRARY/test libs/$LIBRARY/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} ${ADDRMD:+address-model=$ADDRMD} variant=debug,release
windows:
strategy:
@@ -307,15 +299,7 @@ jobs:
shell: cmd
run: |
cd ../boost-root
b2 -j3 libs/%LIBRARY%/test toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
- name: Run examples
shell: cmd
run: |
cd ../boost-root
rem The demos use fixed temp filenames, so run them serially (-j1)
rem to avoid concurrent runs clobbering each other's archive files.
b2 -j1 libs/%LIBRARY%/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
b2 -j3 libs/%LIBRARY%/test libs/%LIBRARY%/example toolset=${{matrix.toolset}} cxxstd=${{matrix.cxxstd}} address-model=${{matrix.addrmd}} variant=debug,release embed-manifest-via=linker
posix-cmake-subdir:
strategy:
-15
View File
@@ -92,21 +92,6 @@ so that instead of the above, we could write:
BOOST_CLASS_VERSION(my_class, 2)
</code></pre>
which expands to the code above.
<p>
<code style="white-space: normal">BOOST_CLASS_VERSION</code> writes a full
specialization, so it applies to a concrete class. To assign a version to a
class <i>template</i> a partial specialization is needed instead, which
<code style="white-space: normal">BOOST_CLASS_TEMPLATE_VERSION</code>
provides. The template parameter list and the specialized type are each
passed parenthesized, as they normally contain commas:
<pre><code>
BOOST_CLASS_TEMPLATE_VERSION((class T, class U), (my_class&lt;T, U&gt;), 2)
</code></pre>
This assigns version 2 to every instantiation of
<code style="white-space: normal">my_class</code>. Non-type template
parameters are written the same way, for example
<code style="white-space: normal">(class T, std::size_t N)</code> paired with
<code style="white-space: normal">(my_class&lt;T, N&gt;)</code>.
<h3><a name="level">Implementation Level</a></h3>
In the same manner as the above, the "level" of implementation of serialization is
-2
View File
@@ -11,8 +11,6 @@ project libs/serialization/example
: requirements <library>../build//boost_serialization
;
import testing ;
import ../util/test :
run-template
run-invoke
@@ -189,14 +189,7 @@ xml_iarchive_impl<Archive>::~xml_iarchive_impl(){
if(boost::core::uncaught_exceptions() > 0)
return;
if(0 == (this->get_flags() & no_header)){
// windup() parses the trailing end tag; an exception must not escape
// this (implicitly noexcept) destructor and terminate the process.
// A stream error while consuming the trailer is not worth that. #99
BOOST_TRY {
gimpl->windup(is);
}
BOOST_CATCH(...) {}
BOOST_CATCH_END
gimpl->windup(is);
}
}
} // namespace archive
@@ -177,14 +177,7 @@ xml_wiarchive_impl<Archive>::~xml_wiarchive_impl(){
if(boost::core::uncaught_exceptions() > 0)
return;
if(0 == (this->get_flags() & no_header)){
// windup() parses the trailing end tag; an exception must not escape
// this (implicitly noexcept) destructor and terminate the process.
// A stream error while consuming the trailer is not worth that. #99
BOOST_TRY {
gimpl->windup(is);
}
BOOST_CATCH(...) {}
BOOST_CATCH_END
gimpl->windup(is);
}
}
+1 -5
View File
@@ -18,12 +18,8 @@
#include <boost/config.hpp>
// GCC's <filesystem> before version 9 is the initial, incomplete
// implementation (shipped in a separate libstdc++fs and buggy at runtime),
// so the feature is disabled there.
#if defined(__has_include)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L) \
&& ! (defined(__GNUC__) && ! defined(__clang__) && __GNUC__ < 9)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L)
# define BOOST_SERIALIZATION_HAS_STD_FILESYSTEM
# endif
#endif
+26 -83
View File
@@ -1,7 +1,6 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// (C) Copyright 2002-4 Pavel Vozenilek .
// 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)
@@ -21,17 +20,13 @@
#include <optional>
#endif
#include <boost/move/utility_core.hpp>
#include <boost/core/addressof.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/serialization/item_version_type.hpp>
#include <boost/serialization/library_version_type.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/split_free.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/serialization.hpp>
#include <boost/type_traits/is_pointer.hpp>
#include <boost/serialization/detail/is_default_constructible.hpp>
#include <boost/serialization/detail/stack_constructor.hpp>
// function specializations must be defined in the appropriate
// namespace - boost::serialization
@@ -39,91 +34,27 @@ namespace boost {
namespace serialization {
namespace detail {
// The value of an optional<T> is serialized like a single element of an
// STL container: when T is default constructible we serialize it in place,
// otherwise we route construction through save/load_construct_data so that
// types without a default constructor can be reconstructed on load. The
// default-constructible path is left untouched so that archives written by
// earlier versions of the library keep the same layout.
// save the value: T is default constructible
template<class Archive, class OT>
typename boost::enable_if<
typename detail::is_default_constructible<typename OT::value_type>,
void
>::type
save_value(Archive & ar, const OT & ot){
ar << boost::serialization::make_nvp("value", *ot);
}
// save the value: T is not default constructible
template<class Archive, class OT>
typename boost::disable_if<
typename detail::is_default_constructible<typename OT::value_type>,
void
>::type
save_value(Archive & ar, const OT & ot){
typedef typename OT::value_type value_type;
const value_type & v = *ot;
const boost::serialization::item_version_type item_version(
boost::serialization::version<value_type>::value
);
ar << BOOST_SERIALIZATION_NVP(item_version);
boost::serialization::save_construct_data_adl(
ar,
boost::addressof(v),
item_version
);
ar << boost::serialization::make_nvp("value", v);
}
// load the value: T is default constructible
template<class Archive, class OT>
typename boost::enable_if<
typename detail::is_default_constructible<typename OT::value_type>,
void
>::type
load_value(Archive & ar, OT & ot, const unsigned int version){
if(0 == version){
boost::serialization::item_version_type item_version(0);
boost::serialization::library_version_type library_version(
ar.get_library_version()
);
if(boost::serialization::library_version_type(3) < library_version){
ar >> BOOST_SERIALIZATION_NVP(item_version);
}
}
typename OT::value_type t;
ar >> boost::serialization::make_nvp("value", t);
ot = boost::move(t);
}
// load the value: T is not default constructible
template<class Archive, class OT>
typename boost::disable_if<
typename detail::is_default_constructible<typename OT::value_type>,
void
>::type
load_value(Archive & ar, OT & ot, const unsigned int /* version */){
typedef typename OT::value_type value_type;
boost::serialization::item_version_type item_version(0);
ar >> BOOST_SERIALIZATION_NVP(item_version);
detail::stack_construct<Archive, value_type> aux(ar, item_version);
ar >> boost::serialization::make_nvp("value", aux.reference());
ot = boost::move(aux.reference());
ar.reset_object_address(boost::addressof(*ot), aux.address());
}
// OT is of the form optional<T>
template<class Archive, class OT>
void save_impl(
Archive & ar,
const OT & ot
){
// It is an inherent limitation to the serialization of optional.hpp
// that the underlying type must be either a pointer or must have a
// default constructor. It's possible that this could change sometime
// in the future, but for now, one will have to work around it. This can
// be done by serialization the optional<T> as optional<T *>
#ifndef BOOST_NO_CXX11_HDR_TYPE_TRAITS
BOOST_STATIC_ASSERT(
boost::serialization::detail::is_default_constructible<typename OT::value_type>::value
|| boost::is_pointer<typename OT::value_type>::value
);
#endif
const bool tflag(ot);
ar << boost::serialization::make_nvp("initialized", tflag);
if (tflag){
save_value(ar, ot);
ar << boost::serialization::make_nvp("value", *ot);
}
}
@@ -140,7 +71,19 @@ void load_impl(
ot.reset();
return;
}
load_value(ar, ot, version);
if(0 == version){
boost::serialization::item_version_type item_version(0);
boost::serialization::library_version_type library_version(
ar.get_library_version()
);
if(boost::serialization::library_version_type(3) < library_version){
ar >> BOOST_SERIALIZATION_NVP(item_version);
}
}
typename OT::value_type t;
ar >> boost::serialization::make_nvp("value",t);
ot = t;
}
} // detail
-29
View File
@@ -72,7 +72,6 @@ const int version<T>::value;
#include <boost/mpl/less.hpp>
#include <boost/mpl/comparison.hpp>
#include <boost/preprocessor/punctuation/remove_parens.hpp>
// specify the current version number for the class
// version numbers limited to 8 bits !!!
@@ -103,32 +102,4 @@ struct version<T > \
} \
}
// Specify the current version number for a class template. Unlike
// BOOST_CLASS_VERSION, which writes a full specialization, this writes a
// partial one, so it works for a template rather than a concrete class.
// The template parameter list and the specialized type are each passed
// parenthesized (they usually contain commas):
//
// BOOST_CLASS_TEMPLATE_VERSION((class T, class U), (my_class<T, U>), 2)
//
// version numbers limited to 8 bits !!!
#define BOOST_CLASS_TEMPLATE_VERSION(TEMPLATE_PARAMETERS, TYPE, N) \
namespace boost { \
namespace serialization { \
template< BOOST_PP_REMOVE_PARENS(TEMPLATE_PARAMETERS) > \
struct version< BOOST_PP_REMOVE_PARENS(TYPE) > \
{ \
typedef mpl::int_<N> type; \
typedef mpl::integral_c_tag tag; \
BOOST_STATIC_CONSTANT(int, value = version::type::value); \
BOOST_MPL_ASSERT(( \
boost::mpl::less< \
boost::mpl::int_<N>, \
boost::mpl::int_<256> \
> \
)); \
}; \
} \
}
#endif // BOOST_SERIALIZATION_VERSION_HPP
+2 -2
View File
@@ -255,7 +255,7 @@ basic_oarchive_impl::save_object(
if(bos.class_info()){
if( ! co.m_initialized){
ar.vsave(class_id_optional_type(co.m_class_id));
ar.vsave(tracking_type(co.m_bos_ptr->tracking(m_flags)));
ar.vsave(tracking_type(bos.tracking(m_flags)));
ar.vsave(version_type(bos.version()));
(const_cast<cobject_type &>(co)).m_initialized = true;
}
@@ -343,7 +343,7 @@ basic_oarchive_impl::save_pointer(
}
}
if(bos.class_info()){
ar.vsave(tracking_type(co.m_bos_ptr->tracking(m_flags)));
ar.vsave(tracking_type(bos.tracking(m_flags)));
ar.vsave(version_type(bos.version()));
}
(const_cast<cobject_type &>(co)).m_initialized = true;
+2 -9
View File
@@ -191,15 +191,6 @@ bool basic_xml_grammar<CharType>::my_parse(
for(;;){
CharType result;
is.get(result);
// Reaching end of input while scanning for the next character is the
// normal way an archive ends (e.g. windup() consuming the trailer);
// it is not a stream error. get() sets both eofbit and failbit at end
// of stream, so test eof() *before* fail() -- otherwise the normal
// termination is misreported as input_stream_error, which is fatal
// when it surfaces in the (noexcept) archive destructor via windup().
// See #99.
if(is.eof())
return false;
if(is.fail()){
boost::serialization::throw_exception(
boost::archive::archive_exception(
@@ -208,6 +199,8 @@ bool basic_xml_grammar<CharType>::my_parse(
)
);
}
if(is.eof())
return false;
arg += result;
if(result == delimiter)
break;
-4
View File
@@ -64,7 +64,6 @@ test-suite "serialization" :
[ test-bsl-run_files test_binary ]
[ test-bsl-run_files test_class_info_save ]
[ test-bsl-run_files test_class_info_load ]
[ test-bsl-run_files test_class_template_version ]
[ test-bsl-run_files test_bitset ]
[ test-bsl-run_files test_complex ]
[ test-bsl-run_files test_contained_class : A ]
@@ -147,14 +146,11 @@ if ! $(BOOST_ARCHIVE_LIST) {
# we suppress tests of our dlls when using static libraries
[ test-bsl-run test_dll_simple : : dll_a : <link>static:<build>no ]
[ test-bsl-run test_dll_base_pointer : : dll_polymorphic_base dll_polymorphic_derived2 : <link>static:<build>no ]
# [ test-bsl-run test_dll_plugin : : dll_derived2 : <link>static:<build>no <target-os>linux:<linkflags>-ldl ]
[ test-bsl-run test_private_ctor ]
[ test-bsl-run test_reset_object_address : A ]
[ test-bsl-run test_void_cast ]
[ test-bsl-run test_xml_trailing_whitespace ]
[ test-bsl-run test_xml_missing_nvp ]
[ test-bsl-run test_mult_archive_types : : : [ requires std_wstreambuf ] ]
[ test-bsl-run test_iterators : : : [ requires std_wstreambuf ] ]
[ test-bsl-run test_iterators_base64 ]
-102
View File
@@ -1,102 +0,0 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_class_template_version.cpp
// Copyright 2026 Gennaro Prota
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// Tests BOOST_CLASS_TEMPLATE_VERSION, which assigns a serialization version
// to a class template through a partial specialization of the version trait.
#include <cstddef>
#include <cstdio>
#include <fstream>
#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
namespace std{
using ::remove;
}
#endif
#include <boost/static_assert.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/version.hpp>
#include "test_tools.hpp"
// a class template with two type parameters; serialize records the version
// it is given on load so the round trip can be checked
template<class T, class U>
struct pair_holder {
T first;
U second;
unsigned int loaded_version;
pair_holder() : first(), second(), loaded_version(0) {}
pair_holder(T f, U s) : first(f), second(s), loaded_version(0) {}
template<class Archive>
void serialize(Archive & ar, const unsigned int version){
loaded_version = version;
ar & boost::serialization::make_nvp("first", first);
ar & boost::serialization::make_nvp("second", second);
}
};
BOOST_CLASS_TEMPLATE_VERSION((class T, class U), (pair_holder<T, U>), 5)
// a class template mixing a type parameter and a non-type parameter
template<class T, std::size_t N>
struct sized_holder {
T value;
};
BOOST_CLASS_TEMPLATE_VERSION((class T, std::size_t N), (sized_holder<T, N>), 3)
// a template we do not version, to confirm the default is still 0
template<class T>
struct unversioned {
T value;
};
int test_main(int /* argc */, char * /* argv */[]){
// the macro must set the compile time version for every instantiation of
// the template, regardless of the actual arguments
BOOST_STATIC_ASSERT(
(boost::serialization::version<pair_holder<int, double> >::value == 5)
);
BOOST_STATIC_ASSERT(
(boost::serialization::version<pair_holder<char, long> >::value == 5)
);
// works for a non-type parameter too
BOOST_STATIC_ASSERT(
(boost::serialization::version<sized_holder<int, 4> >::value == 3)
);
// an unversioned template still defaults to 0
BOOST_STATIC_ASSERT(
(boost::serialization::version<unversioned<int> >::value == 0)
);
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);
const pair_holder<int, double> saved(7, 2.5);
{
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
oa << boost::serialization::make_nvp("ph", saved);
}
pair_holder<int, double> loaded;
{
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
ia >> boost::serialization::make_nvp("ph", loaded);
}
BOOST_CHECK(loaded.first == saved.first);
BOOST_CHECK(loaded.second == saved.second);
// the version set by the macro must be delivered to serialize on load
BOOST_CHECK(loaded.loaded_version == 5);
std::remove(testfile);
return EXIT_SUCCESS;
}
-75
View File
@@ -1,75 +0,0 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_dll_base_pointer.cpp
// Copyright 2026 Gennaro Prota
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// Regression test for issue #117. A derived class defined in a shared
// library is serialized and deserialized through a pointer to its abstract
// base, from an executable built with hidden symbol visibility.
// Historically this failed on GCC/ELF: the per type serializer singletons
// were duplicated across the shared library boundary, so the reader could not
// map the class id back to the derived type. The round trip must recover an
// object of the derived type.
#include <fstream>
#include <cstdio>
#include <typeinfo>
#include <boost/config.hpp>
#include "test_tools.hpp"
#ifndef BOOST_NO_CXX11_SMART_PTR
#include <boost/archive/polymorphic_binary_oarchive.hpp>
#include <boost/archive/polymorphic_binary_iarchive.hpp>
#include <boost/archive/tmpdir.hpp>
#include <boost/smart_ptr/make_unique.hpp>
#include <boost/serialization/unique_ptr.hpp>
#include <boost/serialization/nvp.hpp>
// polymorphic_derived2 (and its base, polymorphic_base) live in the shared
// library this test links against
#define POLYMORPHIC_DERIVED2_IMPORT
#include "polymorphic_derived2.hpp"
int test_main(int /* argc */, char * /* argv */ []){
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);
std::unique_ptr<polymorphic_base> saved =
boost::make_unique<polymorphic_derived2>();
{
std::ofstream os(testfile, std::ios::binary);
boost::archive::polymorphic_binary_oarchive oa(os);
boost::archive::polymorphic_oarchive & poa = oa;
poa << boost::serialization::make_nvp("ptr", saved);
}
std::unique_ptr<polymorphic_base> loaded;
{
std::ifstream is(testfile, std::ios::binary);
boost::archive::polymorphic_binary_iarchive ia(is);
boost::archive::polymorphic_iarchive & pia = ia;
pia >> boost::serialization::make_nvp("ptr", loaded);
}
BOOST_CHECK(0 != loaded.get());
// the object recovered through the base pointer must be the derived type
BOOST_CHECK(0 != dynamic_cast<polymorphic_derived2 *>(loaded.get()));
std::remove(testfile);
return EXIT_SUCCESS;
}
#else
int test_main(int /* argc */, char * /* argv */ []){
return EXIT_SUCCESS;
}
#endif // BOOST_NO_CXX11_SMART_PTR
+2 -26
View File
@@ -17,11 +17,8 @@
// `std::filesystem::path` is a C++17 feature; when it isn't available, this
// test has nothing to exercise and simply succeeds.
// Note the guard for GCC < 9, which matches the one in
// boost/serialization/filesystem.hpp: see the comment there for the why.
#if defined(__has_include)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L) \
&& ! (defined(__GNUC__) && ! defined(__clang__) && __GNUC__ < 9)
# if __has_include(<filesystem>) && (BOOST_CXX_VERSION >= 201703L)
# define BOOST_SERIALIZATION_TEST_STD_FILESYSTEM
# endif
#endif
@@ -32,11 +29,6 @@
#include <boost/serialization/filesystem.hpp>
#include <filesystem>
#include <type_traits>
#ifndef BOOST_NO_STD_WSTREAMBUF
#include <boost/archive/text_woarchive.hpp>
#endif
void check_roundtrip(const std::filesystem::path & original){
const char * testfile = boost::archive::tmpnam(NULL);
@@ -65,21 +57,6 @@ std::filesystem::path from_utf8(const std::string & utf8){
#endif
}
// The wide *text* archive is the one archive that cannot round-trip non-ASCII
// content: it transcodes the narrow UTF-8 std::string through the stream
// locale's codecvt (codecvt_null), which mangles bytes outside the ASCII
// range. Every other archive in the test list preserves them -- narrow text
// and XML store the bytes verbatim, binary stores them raw, and wide XML
// installs a UTF-8 codecvt. So the non-ASCII case is exercised everywhere
// except that single archive.
bool archive_round_trips_non_ascii(){
#ifndef BOOST_NO_STD_WSTREAMBUF
return ! std::is_same<test_oarchive, boost::archive::text_woarchive>::value;
#else
return true;
#endif
}
int test_main(int /* argc */, char * /* argv */ []){
check_roundtrip(std::filesystem::path()); // empty
check_roundtrip("foo/bar/baz.txt"); // relative
@@ -89,8 +66,7 @@ int test_main(int /* argc */, char * /* argv */ []){
// The escapes are the UTF-8 encoding of U+00E9, U+00EF and U+00FC
// (e-acute, i-diaeresis, u-diaeresis); written as `\x` so the source
// file stays pure ASCII and encoding-independent.
if(archive_round_trips_non_ascii())
check_roundtrip(from_utf8("caf\xC3\xA9/na\xC3\xAF" "ve/\xC3\xBC.txt"));
check_roundtrip(from_utf8("caf\xC3\xA9/na\xC3\xAF" "ve/\xC3\xBC.txt"));
return EXIT_SUCCESS;
}
-135
View File
@@ -2,7 +2,6 @@
// test_optional.cpp
// (C) Copyright 2004 Pavel Vozenilek
// 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)
@@ -93,130 +92,6 @@ int test(){
return EXIT_SUCCESS;
}
// A move-only value type: deleted copy, defaulted move. Loading an
// optional<M> must move the deserialized value into place rather than copy
// it.
#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) \
&& !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS) \
&& !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS)
#define BOOST_SERIALIZATION_TEST_OPTIONAL_MOVE_ONLY
struct M {
int m_x;
M() : m_x(0) {}
explicit M(int x) : m_x(x) {}
M(const M &) = delete;
M & operator=(const M &) = delete;
M(M &&) = default;
M & operator=(M &&) = default;
template<class Archive>
void serialize(Archive & ar, const unsigned int /* version */){
ar & boost::serialization::make_nvp("x", m_x);
}
};
template<template<class> class Optional>
int test_move_only(){
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);
Optional<M> o_empty;
Optional<M> o_value(M(42));
{
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
oa << boost::serialization::make_nvp("o_empty", o_empty);
oa << boost::serialization::make_nvp("o_value", o_value);
}
// start each target in the opposite state, so load must both reset and
// assign
Optional<M> o_empty_a(M(7));
Optional<M> o_value_a;
{
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
ia >> boost::serialization::make_nvp("o_empty", o_empty_a);
ia >> boost::serialization::make_nvp("o_value", o_value_a);
}
BOOST_CHECK(! o_empty_a);
BOOST_CHECK(static_cast<bool>(o_value_a) && 42 == o_value_a->m_x);
std::remove(testfile);
return EXIT_SUCCESS;
}
#endif // move-only support available
// A type without a default constructor. It is reconstructed on load through
// save_construct_data / load_construct_data, which is exactly what an
// optional<ND> now relies on (see issue #121). m_i is the constructor
// argument, m_x is ordinary serialized state.
struct ND {
int m_i;
int m_x;
ND(int i, int x) : m_i(i), m_x(x) {}
explicit ND(int i) : m_i(i), m_x(0) {}
template<class Archive>
void serialize(Archive & ar, const unsigned int /* version */){
ar & boost::serialization::make_nvp("x", m_x);
}
bool operator==(const ND & rhs) const {
return m_i == rhs.m_i && m_x == rhs.m_x;
}
};
namespace boost {
namespace serialization {
template<class Archive>
void save_construct_data(
Archive & ar, const ND * p, const unsigned int /* version */
){
ar << boost::serialization::make_nvp("i", p->m_i);
}
template<class Archive>
void load_construct_data(
Archive & ar, ND * p, const unsigned int /* version */
){
int i;
ar >> boost::serialization::make_nvp("i", i);
::new(p) ND(i);
}
} // serialization
} // boost
template<template<class> class Optional>
int test_non_default_ctor(){
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);
const Optional<ND> o_empty;
const Optional<ND> o_value(ND(7, 42));
{
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
oa << boost::serialization::make_nvp("o_empty", o_empty);
oa << boost::serialization::make_nvp("o_value", o_value);
}
// start each target in the opposite state, so load must both reset and
// reconstruct
Optional<ND> o_empty_a(ND(1, 1));
Optional<ND> o_value_a;
{
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
ia >> boost::serialization::make_nvp("o_empty", o_empty_a);
ia >> boost::serialization::make_nvp("o_value", o_value_a);
}
BOOST_CHECK(! o_empty_a);
BOOST_CHECK(static_cast<bool>(o_value_a));
BOOST_CHECK(o_value_a && *o_value == *o_value_a);
std::remove(testfile);
return EXIT_SUCCESS;
}
#include <boost/serialization/optional.hpp>
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
#include <optional>
@@ -227,15 +102,5 @@ int test_main( int /* argc */, char* /* argv */[] ){
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
test<std::optional>();
#endif
#ifdef BOOST_SERIALIZATION_TEST_OPTIONAL_MOVE_ONLY
test_move_only<boost::optional>();
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
test_move_only<std::optional>();
#endif
#endif
test_non_default_ctor<boost::optional>();
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
test_non_default_ctor<std::optional>();
#endif
return EXIT_SUCCESS;
}
-58
View File
@@ -1,58 +0,0 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_xml_missing_nvp.cpp
// Copyright 2026 Gennaro Prota
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// Regression test for issue #109. Attempting to read an nvp that is not
// present throws archive_exception, which the caller may catch. The failed
// read consumes the closing tag, so at destruction windup() reaches end of
// input -- the same Boost 1.66 my_parse regression as #82/#99, reached by a
// different path. The input archive must still destruct without terminating.
#include <sstream>
#include <string>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/archive_exception.hpp>
#include <boost/serialization/nvp.hpp>
#include "test_tools.hpp"
int test_main(int /* argc */, char * /* argv */ []){
// Build a valid XML archive holding a single value.
std::string content;
{
std::ostringstream os;
{
boost::archive::xml_oarchive oa(os);
const int x = 42;
oa << boost::serialization::make_nvp("x", x);
}
content = os.str();
}
int x = 0;
bool caught = false;
{
std::istringstream is(content);
boost::archive::xml_iarchive ia(is);
ia >> boost::serialization::make_nvp("x", x);
try {
int y = 0;
ia >> boost::serialization::make_nvp("not_there", y);
} catch (const boost::archive::archive_exception &){
caught = true;
}
// `ia` is destroyed here, after the caught exception. Before the fix,
// windup() hit end of input (the failed read consumed the closing
// tag) and threw out of the noexcept destructor, terminating.
}
BOOST_CHECK(42 == x);
BOOST_CHECK(caught);
return EXIT_SUCCESS;
}
-59
View File
@@ -1,59 +0,0 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_xml_trailing_whitespace.cpp
// Copyright 2026 Gennaro Prota
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// https://www.boost.org/LICENSE_1_0.txt)
// Regression test for issue #99. When an XML input archive is destroyed and
// its stream has no closing tag left -- only trailing whitespace before end
// of input (a truncated archive; the reported case had the stream "contain
// \r\n") -- windup() reaches end of input while scanning for the trailing
// tag. End of input there is normal termination, not a stream error, so the
// (implicitly noexcept) destructor must complete cleanly rather than throw,
// which used to terminate the process.
#include <sstream>
#include <string>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/archive/xml_iarchive.hpp>
#include <boost/serialization/nvp.hpp>
#include "test_tools.hpp"
int test_main(int /* argc */, char * /* argv */ []){
// Build a valid XML archive holding a single value.
std::string content;
{
std::ostringstream os;
{
boost::archive::xml_oarchive oa(os);
const int x = 42;
oa << boost::serialization::make_nvp("x", x);
}
content = os.str();
}
// Drop the closing document tag and leave only trailing whitespace, so the
// input archive's windup() reaches end of input at destruction instead of
// finding a tag.
const std::string::size_type close = content.rfind("</boost_serialization>");
BOOST_REQUIRE(std::string::npos != close);
content.erase(close);
content += "\r\n";
int y = 0;
{
std::istringstream is(content);
boost::archive::xml_iarchive ia(is);
ia >> boost::serialization::make_nvp("x", y);
// `ia` is destroyed here with only trailing whitespace left. Before
// the fix, windup() threw input_stream_error out of the noexcept
// destructor and terminated the process.
}
BOOST_CHECK(42 == y);
return EXIT_SUCCESS;
}