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
9 changes: 8 additions & 1 deletion include/boost/archive/impl/xml_iarchive_impl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,14 @@ xml_iarchive_impl<Archive>::~xml_iarchive_impl(){
if(boost::core::uncaught_exceptions() > 0)
return;
if(0 == (this->get_flags() & no_header)){
gimpl->windup(is);
// 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
}
}
} // namespace archive
Expand Down
9 changes: 8 additions & 1 deletion include/boost/archive/impl/xml_wiarchive_impl.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,14 @@ xml_wiarchive_impl<Archive>::~xml_wiarchive_impl(){
if(boost::core::uncaught_exceptions() > 0)
return;
if(0 == (this->get_flags() & no_header)){
gimpl->windup(is);
// 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
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/basic_xml_grammar.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ 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(
Expand All @@ -199,8 +208,6 @@ bool basic_xml_grammar<CharType>::my_parse(
)
);
}
if(is.eof())
return false;
arg += result;
if(result == delimiter)
break;
Expand Down
2 changes: 2 additions & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ 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_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 ]
Expand Down
58 changes: 58 additions & 0 deletions test/test_xml_missing_nvp.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_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 changes: 59 additions & 0 deletions test/test_xml_trailing_whitespace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/////////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;
}