Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions include/boost/archive/basic_xml_oarchive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
// basic_xml_oarchive.hpp

// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
// 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)
Expand Down Expand Up @@ -78,6 +79,15 @@ class BOOST_SYMBOL_VISIBLE basic_xml_oarchive :
BOOST_ARCHIVE_OR_WARCHIVE_DECL void
end_preamble();

// True once every element that was opened has been closed, i.e. the
// document is complete and its root element can be closed safely. Used
// by the derived destructor to tell an interrupted serialization (an
// element still open) apart from an unrelated exception unwinding past
// an already-complete archive.
bool document_complete() const {
return 0 == depth;
}

// Anything not an attribute and not a name-value pair is an
// error and should be trapped here.
template<class T>
Expand Down
18 changes: 15 additions & 3 deletions include/boost/archive/impl/xml_oarchive_impl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// xml_oarchive_impl.ipp:

// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
// 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)
Expand All @@ -20,6 +21,7 @@ namespace std{
#endif

#include <boost/core/uncaught_exceptions.hpp>
#include <boost/core/no_exceptions_support.hpp>
#include <boost/archive/iterators/xml_escape.hpp>
#include <boost/archive/iterators/ostream_iterator.hpp>

Expand Down Expand Up @@ -129,10 +131,20 @@ xml_oarchive_impl<Archive>::save_binary(const void *address, std::size_t count){
template<class Archive>
BOOST_ARCHIVE_DECL
xml_oarchive_impl<Archive>::~xml_oarchive_impl(){
if(boost::core::uncaught_exceptions() > 0)
// The closing root tag is written here, at destruction. Skip it only
// when serialization was genuinely interrupted, i.e. when an exception
// is unwinding the stack and an element is still open, so the document
// is already truncated. When the document is complete, we must still
// attempt to close it, even while unwinding.
if(boost::core::uncaught_exceptions() > 0 && ! this->document_complete()){
return;
if(0 == (this->get_flags() & no_header)){
this->put("</boost_serialization>\n");
}
if(0 == (this->get_flags() & no_header) && os.good()){
BOOST_TRY {
this->put("</boost_serialization>\n");
}
BOOST_CATCH(...) {}
BOOST_CATCH_END
}
}

Expand Down
14 changes: 11 additions & 3 deletions include/boost/archive/impl/xml_woarchive_impl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// xml_woarchive_impl.ipp:

// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
// 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)
Expand Down Expand Up @@ -32,6 +33,7 @@ namespace std{
#endif

#include <boost/core/uncaught_exceptions.hpp>
#include <boost/core/no_exceptions_support.hpp>

#include <boost/archive/xml_woarchive.hpp>
#include <boost/archive/detail/utf8_codecvt_facet.hpp>
Expand Down Expand Up @@ -138,10 +140,16 @@ xml_woarchive_impl<Archive>::xml_woarchive_impl(
template<class Archive>
BOOST_WARCHIVE_DECL
xml_woarchive_impl<Archive>::~xml_woarchive_impl(){
if(boost::core::uncaught_exceptions() > 0)
// See the note in xml_oarchive_impl::~xml_oarchive_impl.
if(boost::core::uncaught_exceptions() > 0 && ! this->document_complete()){
return;
if(0 == (this->get_flags() & no_header)){
os << L"</boost_serialization>";
}
if(0 == (this->get_flags() & no_header) && os.good()){
BOOST_TRY {
os << L"</boost_serialization>";
}
BOOST_CATCH(...) {}
BOOST_CATCH_END
}
}

Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ if ! $(BOOST_ARCHIVE_LIST) {
[ 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_save_during_unwind ]
[ test-bsl-run test_xml_trailing_whitespace ]
[ test-bsl-run test_xml_missing_nvp ]
[ test-bsl-run test_mult_archive_types : : : [ requires std_wstreambuf ] ]
Expand Down
58 changes: 58 additions & 0 deletions test/test_xml_save_during_unwind.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_xml_save_during_unwind.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 #188. The XML output archive writes the closing
// </boost_serialization> tag in its destructor. It used to skip that write
// whenever an exception was unwinding the stack (uncaught_exceptions() > 0),
// which silently corrupted an otherwise complete archive when it happened to
// be destroyed during unwinding for an unrelated reason. A complete document
// must be closed regardless; only a genuinely interrupted one (an element still
// open) may be left unterminated.

#include <sstream>
#include <stdexcept>
#include <string>

#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#include <boost/serialization/nvp.hpp>

#include "test_tools.hpp"

int test_main(int /* argc */, char * /* argv */ []){
// Serialize a complete value, then throw an unrelated exception so the
// archive is destroyed while the stack unwinds. `os` is declared outside
// the try block so it outlives the archive and we can inspect what the
// destructor wrote.
std::ostringstream os;
try {
boost::archive::xml_oarchive oa(os);
const int x = 42;
oa << boost::serialization::make_nvp("x", x);
throw std::runtime_error("unrelated");
}
catch(const std::runtime_error &){}

const std::string content = os.str();

// The complete document must have been closed despite the unwinding.
BOOST_CHECK(
content.find("</boost_serialization>") != std::string::npos
);

// And it must load back cleanly.
int y = 0;
{
std::istringstream is(content);
boost::archive::xml_iarchive ia(is);
ia >> boost::serialization::make_nvp("x", y);
}
BOOST_CHECK(42 == y);

return EXIT_SUCCESS;
}