Skip to content
Open
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
116 changes: 114 additions & 2 deletions include/boost/archive/basic_text_iprimitive.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@
// in such cases. So we can't use basic_ostream<IStream::char_type> but rather
// use two template parameters

#include <ios>
#include <limits>
#include <locale>
#include <cstddef> // size_t
#include <streambuf>
#include <string>

#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
Expand All @@ -38,6 +42,7 @@ namespace std{
#endif

#include <boost/io/ios_state.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/static_assert.hpp>

#include <boost/detail/workaround.hpp>
Expand Down Expand Up @@ -83,16 +88,123 @@ class BOOST_SYMBOL_VISIBLE basic_text_iprimitive {
> locale_saver;
#endif

// Whether a value of T can be an infinity or a NaN, and so may reach
// us written as letters rather than as digits.
template<class T>
void load(T & t)
{
struct has_non_finite {
typedef typename mpl::bool_<
std::numeric_limits<T>::has_infinity
|| std::numeric_limits<T>::has_quiet_NaN
>::type type;
};

// Takes a leading sign, if there is one, and says whether it was a
// minus.
bool take_minus_sign(){
typedef typename IStream::traits_type traits_type;
typedef typename IStream::char_type char_type;

std::basic_streambuf<char_type, traits_type> * const sb = is.rdbuf();
if(NULL == sb){
return false;
}
is >> std::ws;
const typename traits_type::int_type c = sb->sgetc();
if(traits_type::eq_int_type(c, traits_type::to_int_type(char_type('-')))
|| traits_type::eq_int_type(c, traits_type::to_int_type(char_type('+')))){
return traits_type::eq_int_type(
sb->sbumpc(), traits_type::to_int_type(char_type('-'))
);
}
return false;
}

template<class T>
void load_impl(T & t, boost::mpl::bool_<false> &){
if(is >> t)
return;
boost::serialization::throw_exception(
archive_exception(archive_exception::input_stream_error)
);
}

// An infinity is written as "inf" and a NaN as "nan", because that is
// what the stream writes, and the extraction of a floating point number
// then refuses both, so an archive the library wrote itself would not
// load. Read the letters here rather than change what is written, so
// that archives already in existence start loading (issue #386).
template<class T>
void load_impl(T & t, boost::mpl::bool_<true> &){
typedef typename IStream::traits_type traits_type;
typedef typename IStream::char_type char_type;

const bool negative = take_minus_sign();
if(is >> t){
if(negative){
t = -t;
}
return;
}
is.clear();

// Only the letters are taken, and not everything up to the next
// space, because an XML archive ends a value with a tag. The
// terminator is left where it is for the same reason.
std::basic_streambuf<char_type, traits_type> * const sb = is.rdbuf();
std::string token;
for(;;){
const typename traits_type::int_type c = sb->sgetc();
if(traits_type::eq_int_type(c, traits_type::eof())){
break;
}
const char_type letter = traits_type::to_char_type(c);
if(! ((char_type('a') <= letter && letter <= char_type('z'))
|| (char_type('A') <= letter && letter <= char_type('Z')))){
break;
}
token += char(char(letter) | 0x20); // ASCII, so this lowers it
sb->sbumpc();
}
// A NaN may carry a parenthesised payload, as in the "nan(ind)" the
// Microsoft library writes, which has to come away with it.
if("nan" == token
&& traits_type::eq_int_type(
sb->sgetc(), traits_type::to_int_type(char_type('('))
)
){
while(! traits_type::eq_int_type(
sb->sbumpc(), traits_type::to_int_type(char_type(')'))
)){
if(traits_type::eq_int_type(sb->sgetc(), traits_type::eof())){
break;
}
}
}

if(("inf" == token || "infinity" == token)
&& std::numeric_limits<T>::has_infinity){
t = std::numeric_limits<T>::infinity();
}
else if("nan" == token && std::numeric_limits<T>::has_quiet_NaN){
t = std::numeric_limits<T>::quiet_NaN();
}
else{
boost::serialization::throw_exception(
archive_exception(archive_exception::input_stream_error)
);
}
if(negative){
t = -t;
}
}

template<class T>
void load(T & t)
{
typename has_non_finite<T>::type tag;
load_impl(t, tag);
}

void load(char & t)
{
short int i;
Expand Down
1 change: 1 addition & 0 deletions test/Jamfile.v2
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ test-suite "serialization" :
[ test-bsl-run_files test_non_default_ctor ]
[ test-bsl-run_files test_non_default_ctor2 ]
[ test-bsl-run_files test_null_ptr ]
[ test-bsl-run_files test_non_finite_floats ]
[ test-bsl-run_files test_nvp : A ]
[ test-bsl-run_files test_object ]
[ test-bsl-run_files test_primitive ]
Expand Down
101 changes: 101 additions & 0 deletions test/test_non_finite_floats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
// test_non_finite_floats.cpp

// 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)

// See http://www.boost.org for updates, documentation, and revision history.

// An infinity is written as "inf" and a NaN as "nan", because that is what
// the stream writes, and extraction of a floating point number then refuses
// both: it accepts digits and little else, in libstdc++ and in the Microsoft
// library alike. So the library used to write text archives which it could
// not read back.

// Reported by nim65s in
// https://github.com/boostorg/serialization/issues/386. Thanks!

#include <cstddef>
#include <cstdio>
#include <fstream>
#include <limits>

#include <boost/config.hpp>
#if defined(BOOST_NO_STDC_NAMESPACE)
namespace std{
using ::remove;
}
#endif

#include "test_tools.hpp"

#include <boost/serialization/nvp.hpp>

template<class T>
struct values {
T positive_infinity;
T negative_infinity;
T not_a_number;
T ordinary;

values() :
positive_infinity(std::numeric_limits<T>::infinity()),
negative_infinity(-std::numeric_limits<T>::infinity()),
not_a_number(std::numeric_limits<T>::quiet_NaN()),
ordinary(T(24.567))
{}

// Deliberately not the constructor above: this one is what the load
// fills in, and it must start from something finite so that a load which
// quietly does nothing cannot pass.
values(int) :
positive_infinity(0),
negative_infinity(0),
not_a_number(0),
ordinary(0)
{}

template<class Archive>
void serialize(Archive & ar, const unsigned int /* version */){
ar & boost::serialization::make_nvp("pos_inf", positive_infinity);
ar & boost::serialization::make_nvp("neg_inf", negative_infinity);
ar & boost::serialization::make_nvp("nan", not_a_number);
ar & boost::serialization::make_nvp("ordinary", ordinary);
}
};

template<class T>
void test_type(){
const char * testfile = boost::archive::tmpnam(NULL);
BOOST_REQUIRE(NULL != testfile);

const values<T> written;
{
test_ostream os(testfile, TEST_STREAM_FLAGS);
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
oa << boost::serialization::make_nvp("values", written);
}

values<T> read(0);
{
test_istream is(testfile, TEST_STREAM_FLAGS);
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
ia >> boost::serialization::make_nvp("values", read);
}

BOOST_CHECK(read.positive_infinity == std::numeric_limits<T>::infinity());
BOOST_CHECK(read.negative_infinity == -std::numeric_limits<T>::infinity());
// A NaN is equal to nothing, itself included, so that is the test.
BOOST_CHECK(read.not_a_number != read.not_a_number);
BOOST_CHECK(read.ordinary == written.ordinary);

std::remove(testfile);
}

int test_main(int /* argc */, char * /* argv */ []){
test_type<float>();
test_type<double>();
return EXIT_SUCCESS;
}