Skip to content

Commit fbfbf2d

Browse files
committed
Stop requiring an out of class definition for version traits
`version_type` took its argument by reference, so constructing one from `version<T>::value` could bound a reference to that static data member and ODR-used it. A trait written by hand with no out-of-class definition for the data member, then left an undefined reference behind on compilers which do not fold the constant away. Spelling the value as an `int` happened to work only because the conversion to `unsigned int` created a temporary for the reference to bind to. Taking the argument by value removes the ODR-use, so both `int` and `unsigned int` work. The specializations `BOOST_CLASS_VERSION` writes have no definition either, and were relying on the same accident. `object_id_type` had the same constructor and is changed along with it. Fixes #311.
1 parent 1b2007f commit fbfbf2d

3 files changed

Lines changed: 123 additions & 2 deletions

File tree

include/boost/archive/basic_archive.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class version_type {
4646
public:
4747
// should be private - but MPI fails if it's not!!!
4848
version_type(): t(0) {}
49-
explicit version_type(const unsigned int & t_) : t(t_){
49+
explicit version_type(const unsigned int t_) : t(t_){
5050
BOOST_ASSERT(t_ <= boost::integer_traits<base_type>::const_max);
5151
}
5252
version_type(const version_type & t_) :
@@ -119,7 +119,7 @@ class object_id_type {
119119
object_id_type(): t(0) {}
120120
// note: presumes that size_t >= unsigned int.
121121
// use explicit cast to silence useless warning
122-
explicit object_id_type(const std::size_t & t_) : t(static_cast<base_type>(t_)){
122+
explicit object_id_type(const std::size_t t_) : t(static_cast<base_type>(t_)){
123123
// make quadruple sure that we haven't lost any real integer
124124
// precision
125125
BOOST_ASSERT(t_ <= boost::integer_traits<base_type>::const_max);

test/Jamfile.v2

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ test-suite "serialization" :
117117
[ test-bsl-run_files test_unique_ptr ]
118118
[ test-bsl-run_files test_valarray ]
119119
[ test-bsl-run_files test_variant : A ]
120+
[ test-bsl-run_files test_version_value_type ]
120121
[ test-bsl-run_files test_vector : A ]
121122
[ test-bsl-run_files test_shared_ptr ]
122123
[ test-bsl-run_files test_shared_ptr_multi_base ]

test/test_version_value_type.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
2+
// test_version_value_type.cpp
3+
4+
// Copyright 2026 Gennaro Prota.
5+
// Distributed under the Boost Software License, Version 1.0.
6+
// (See accompanying file LICENSE_1_0.txt or copy at
7+
// http://www.boost.org/LICENSE_1_0.txt)
8+
9+
// See http://www.boost.org for updates, documentation, and revision history.
10+
11+
// A version trait written by hand has to work whichever integer type it
12+
// names for its value. The library declares the trait as an int, but
13+
// nothing stops a user from writing unsigned int, and doing so used to
14+
// leave the value with no definition to link against: it was passed to
15+
// version_type through a reference, which made a definition necessary.
16+
17+
// Reported by LowLevelMahn in
18+
// https://github.com/boostorg/serialization/issues/311, together with a
19+
// self contained example which linked with one integer type and not with
20+
// the other. Thanks!
21+
22+
// Note that this only ever failed on compilers which do not fold the
23+
// constant away, so it links either way on some of them.
24+
25+
#include <cstddef>
26+
#include <cstdio>
27+
#include <fstream>
28+
29+
#include <boost/config.hpp>
30+
#if defined(BOOST_NO_STDC_NAMESPACE)
31+
namespace std{
32+
using ::remove;
33+
}
34+
#endif
35+
36+
#include <boost/serialization/nvp.hpp>
37+
#include <boost/serialization/version.hpp>
38+
39+
#include "test_tools.hpp"
40+
41+
// the value spelled the way the library spells it
42+
struct signed_version {
43+
int value;
44+
};
45+
46+
// and spelled the way the report did, which is what used to fail
47+
template<typename T, std::size_t N>
48+
struct unsigned_version {
49+
int value;
50+
};
51+
52+
namespace boost {
53+
namespace serialization {
54+
55+
template<>
56+
struct version<signed_version>
57+
{
58+
BOOST_STATIC_CONSTANT(int, value = 1);
59+
};
60+
61+
// a partial specialization, as in the report
62+
template<typename T, std::size_t N>
63+
struct version<unsigned_version<T, N> >
64+
{
65+
BOOST_STATIC_CONSTANT(unsigned int, value = 2);
66+
};
67+
68+
template<class Archive>
69+
void serialize(Archive & ar, signed_version & t, const unsigned int file_version){
70+
BOOST_CHECK(1 == file_version);
71+
ar & BOOST_SERIALIZATION_NVP(t.value);
72+
}
73+
74+
template<class Archive, typename T, std::size_t N>
75+
void serialize(
76+
Archive & ar,
77+
unsigned_version<T, N> & t,
78+
const unsigned int file_version
79+
){
80+
BOOST_CHECK(2 == file_version);
81+
ar & BOOST_SERIALIZATION_NVP(t.value);
82+
}
83+
84+
} // namespace serialization
85+
} // namespace boost
86+
87+
typedef unsigned_version<int, 3> unsigned_version_type;
88+
89+
int
90+
test_main(int /* argc */, char * /* argv */ [])
91+
{
92+
const char * testfile = boost::archive::tmpnam(NULL);
93+
BOOST_REQUIRE(NULL != testfile);
94+
95+
{
96+
signed_version a;
97+
a.value = 11;
98+
unsigned_version_type b;
99+
b.value = 22;
100+
test_ostream os(testfile, TEST_STREAM_FLAGS);
101+
test_oarchive oa(os, TEST_ARCHIVE_FLAGS);
102+
oa << BOOST_SERIALIZATION_NVP(a);
103+
oa << BOOST_SERIALIZATION_NVP(b);
104+
}
105+
{
106+
signed_version a;
107+
a.value = 0;
108+
unsigned_version_type b;
109+
b.value = 0;
110+
test_istream is(testfile, TEST_STREAM_FLAGS);
111+
test_iarchive ia(is, TEST_ARCHIVE_FLAGS);
112+
ia >> BOOST_SERIALIZATION_NVP(a);
113+
ia >> BOOST_SERIALIZATION_NVP(b);
114+
BOOST_CHECK(11 == a.value);
115+
BOOST_CHECK(22 == b.value);
116+
}
117+
118+
std::remove(testfile);
119+
return EXIT_SUCCESS;
120+
}

0 commit comments

Comments
 (0)