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
4 changes: 0 additions & 4 deletions include/boost/archive/iterators/remove_whitespace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,6 @@ class remove_whitespace :
remove_whitespace(T start) :
super_t(Base(static_cast< T >(start)))
{}
// intel 7.1 doesn't like default copy constructor
remove_whitespace(const remove_whitespace & rhs) :
super_t(rhs.base_reference())
{}
};

} // namespace iterators
Expand Down
9 changes: 0 additions & 9 deletions include/boost/archive/iterators/transform_width.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,6 @@ class transform_width :
m_remaining_bits(0),
m_end_of_sequence(false)
{}
// intel 7.1 doesn't like default copy constructor
transform_width(const transform_width & rhs) :
super_t(rhs.base_reference()),
m_buffer_out_full(rhs.m_buffer_out_full),
m_buffer_out(rhs.m_buffer_out),
m_buffer_in(rhs.m_buffer_in),
m_remaining_bits(rhs.m_remaining_bits),
m_end_of_sequence(false)
{}
};

template<
Expand Down
4 changes: 0 additions & 4 deletions include/boost/archive/iterators/xml_escape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ class xml_escape
xml_escape(T start) :
super_t(Base(static_cast< T >(start)))
{}
// intel 7.1 doesn't like default copy constructor
xml_escape(const xml_escape & rhs) :
super_t(rhs.base_reference())
{}
};

template<class Base>
Expand Down
4 changes: 0 additions & 4 deletions include/boost/archive/iterators/xml_unescape.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@ class xml_unescape
xml_unescape(T start) :
super_t(Base(static_cast< T >(start)))
{}
// intel 7.1 doesn't like default copy constructor
xml_unescape(const xml_unescape & rhs) :
super_t(rhs.base_reference())
{}
};

template<class Base>
Expand Down
2 changes: 2 additions & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,13 @@ if ! $(BOOST_ARCHIVE_LIST) {
[ 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_escape_boundary : : ../build//boost_wserialization : [ requires std_wstreambuf ] ]
[ 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 ]
[ test-bsl-run test_iterators_copy ]
[ test-bsl-run test_smart_cast ]
[ test-bsl-run test_codecvt_null ]
[ test-bsl-run test_singleton ]
Expand Down
107 changes: 107 additions & 0 deletions test/test_iterators_copy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_iterators_copy.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 #229.

#include <algorithm>
#include <cstdlib>
#include <cstddef>
#include <list>

#include <boost/config.hpp>

#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/insert_linebreaks.hpp>
#include <boost/archive/iterators/remove_whitespace.hpp>
#include <boost/archive/iterators/transform_width.hpp>

#include "test_tools.hpp"

// Traverse [it, end) but copy construct the iterator at every step and
// continue from the copy, the way std::copy unwraps an iterator. The `== end`
// comparison is what makes transform_width set its end-of-sequence flag, so we
// must keep comparing against end for the copy to have that state to preserve.
template<class Iterator, class Output>
void drain_to_end_by_copy(Iterator it, Iterator end, Output out){
while(! (it == end)){
Iterator cur = it; // copy constructor under test
*out++ = *cur;
++cur;
it = cur;
}
}

// Read n elements, copy constructing the iterator at every step. Used for the
// decode pipeline, which has no end-of-sequence padding but does drive
// remove_whitespace (whose copy must preserve its cached-value flag).
template<class Iterator, class Output>
void drain_n_by_copy(Iterator it, std::size_t n, Output out){
for(std::size_t i = 0; i < n; ++i){
Iterator cur = it; // copy constructor under test
*out++ = *cur;
++cur;
it = cur;
}
}

template<class CharType>
void test_base64_copy(unsigned int size){
CharType rawdata[150];
for(unsigned int i = 0; i < size; ++i)
rawdata[i] = static_cast<CharType>(std::rand() & 0xff);

typedef boost::archive::iterators::insert_linebreaks<
boost::archive::iterators::base64_from_binary<
boost::archive::iterators::transform_width<
CharType *, 6, sizeof(CharType) * 8
>
>, 76
> encode;

// Straight encode (single iterator instance, as std::copy drives it).
std::list<CharType> plain;
std::copy(
encode(rawdata), encode(rawdata + size), std::back_inserter(plain)
);

// Same encode, but copy constructing the iterator at every step. Without
// a correct transform_width copy constructor the final (zero padded)
// group is produced from lost state, so the tails differ.
std::list<CharType> copied;
drain_to_end_by_copy(
encode(rawdata), encode(rawdata + size), std::back_inserter(copied)
);
BOOST_CHECK(plain == copied);

// Decode back to the original bytes, again copy constructing at every
// step. This drives remove_whitespace over the line breaks inserted
// above (present once the base64 exceeds 76 characters).
typedef boost::archive::iterators::transform_width<
boost::archive::iterators::binary_from_base64<
boost::archive::iterators::remove_whitespace<
typename std::list<CharType>::iterator
>
>, sizeof(CharType) * 8, 6
> decode;
std::list<CharType> decoded;
drain_n_by_copy(decode(plain.begin()), size, std::back_inserter(decoded));
BOOST_CHECK(std::equal(rawdata, rawdata + size, decoded.begin()));
}

int test_main(int /* argc */, char * /* argv */ []){
for(unsigned int s = 1; s <= 4; ++s)
test_base64_copy<char>(s);
test_base64_copy<char>(150);
#ifndef BOOST_NO_CWCHAR
for(unsigned int s = 1; s <= 4; ++s)
test_base64_copy<wchar_t>(s);
test_base64_copy<wchar_t>(150);
#endif
return EXIT_SUCCESS;
}
57 changes: 57 additions & 0 deletions test/test_xml_escape_boundary.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_xml_escape_boundary.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 #229. Writing a std::string that contains a
// character needing an XML escape (here '"' -> &quot;) whose expansion
// straddles the 32 character internal buffer of wchar_from_mb used to
// produce malformed output such as "&qu&quot;": the xml_escape iterator was
// copied in mid escape sequence and its hand-written copy constructor dropped
// the escape<> base's state, restarting the sequence. Check that a wide XML
// archive escapes such a string correctly and round-trips it.

#include <sstream>
#include <string>

#include <boost/archive/xml_woarchive.hpp>
#include <boost/archive/xml_wiarchive.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/string.hpp>

#include "test_tools.hpp"

int test_main(int /* argc */, char * /* argv */ []){
// The '"' sits at index 29, so its &quot; expansion crosses the 32 char
// wchar_from_mb buffer boundary.
const std::string instring("01234567890123456789012345678\"-here-is-the-error");

std::wstring archived;
{
std::wostringstream os;
{
boost::archive::xml_woarchive oa(os);
oa << boost::serialization::make_nvp("err", instring);
}
archived = os.str();
}

// The escape must appear exactly once and intact, never as the truncated
// "&qu&quot;" the bug produced.
BOOST_CHECK(archived.find(L"&qu&quot;") == std::wstring::npos);
BOOST_CHECK(archived.find(L"&quot;") != std::wstring::npos);

// And it must round-trip.
std::string result;
{
std::wistringstream is(archived);
boost::archive::xml_wiarchive ia(is);
ia >> boost::serialization::make_nvp("err", result);
}
BOOST_CHECK(instring == result);

return EXIT_SUCCESS;
}