Compare commits

..

3 Commits

Author SHA1 Message Date
Gennaro Prota 92da55a05b Run the examples serially in CI
The example programs write to fixed temp filenames---demo and
demo_exception even share tmpdir()/demofile.txt---so running them under
`b2 -jN` with N > 1 races: concurrent processes clobber each other's
archive files and a reader picks up a half-written one, throwing
`archive_exception`. Therefore, run the examples under `-j1` (the tests
keep running in parallel).
2026-07-17 16:02:20 +02:00
Gennaro Prota f387cb6efc Skip the non-ASCII path round trip only on the wide-text archive
The wide *text* archive transcodes a narrow `std::string` through the
stream locale's `codecvt_null`, which cannot represent bytes outside the
ASCII range; it is the only archive in the test list that fails to
round-trip non-ASCII content. The others preserve it: narrow text and
XML store the bytes verbatim, binary stores them raw, and wide XML
installs a UTF-8 codecvt.

Rather than dropping the non-ASCII path from the test entirely, exercise
it against every archive but the wide-text one.
2026-07-17 12:02:31 +02:00
Gennaro Prota 438fdbeabc Disable std::filesystem serialization on GCC before 9
GCC's initial `<filesystem>` (before version 9) shipped in a separate
libstdc++fs and is unreliable: serializing a `std::filesystem::path`
segfaults at runtime in CI. Exclude those versions from the availability
guard so the feature is inert.

Refs issue #337.
2026-07-17 11:59:44 +02:00
11 changed files with 7 additions and 285 deletions
-1
View File
@@ -7,7 +7,6 @@ on:
- master
- develop
- feature/**
merge_group:
env:
UBSAN_OPTIONS: print_stacktrace=1
@@ -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 -2
View File
@@ -20,7 +20,6 @@
#include <optional>
#endif
#include <boost/move/utility_core.hpp>
#include <boost/serialization/item_version_type.hpp>
#include <boost/serialization/library_version_type.hpp>
#include <boost/serialization/version.hpp>
@@ -84,7 +83,7 @@ void load_impl(
}
typename OT::value_type t;
ar >> boost::serialization::make_nvp("value",t);
ot = boost::move(t);
ot = t;
}
} // detail
+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;
-3
View File
@@ -146,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 ]
-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
-60
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,59 +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
#include <boost/serialization/optional.hpp>
#ifndef BOOST_NO_CXX17_HDR_OPTIONAL
#include <optional>
@@ -156,11 +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
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;
}