Skip to content

Commit 8957955

Browse files
committed
restore object storage on failed range insert
1 parent c432951 commit 8957955

File tree

4 files changed

+81
-26
lines changed

4 files changed

+81
-26
lines changed

include/boost/json/impl/object.hpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class object::revert_construct
154154
class object::revert_insert
155155
{
156156
object* obj_;
157+
table* t_ = nullptr;
157158
std::size_t size_;
158159

159160
BOOST_JSON_DECL
@@ -163,24 +164,38 @@ class object::revert_insert
163164
public:
164165
explicit
165166
revert_insert(
166-
object& obj) noexcept
167+
object& obj,
168+
std::size_t capacity)
167169
: obj_(&obj)
168170
, size_(obj_->size())
169171
{
172+
if( capacity > obj_->capacity() )
173+
t_ = obj_->reserve_impl(capacity);
170174
}
171175

172176
~revert_insert()
173177
{
174178
if(! obj_)
175179
return;
180+
176181
destroy();
177-
obj_->t_->size = static_cast<
178-
index_t>(size_);
182+
if( t_ )
183+
{
184+
table::deallocate( obj_->t_, obj_->sp_ );
185+
obj_->t_ = t_;
186+
}
187+
else
188+
{
189+
obj_->t_->size = static_cast<index_t>(size_);
190+
}
179191
}
180192

181193
void
182194
commit() noexcept
183195
{
196+
BOOST_ASSERT(obj_);
197+
if( t_ )
198+
table::deallocate( t_, obj_->sp_ );
184199
obj_ = nullptr;
185200
}
186201
};
@@ -329,6 +344,16 @@ capacity() const noexcept ->
329344
return t_->capacity;
330345
}
331346

347+
void
348+
object::
349+
reserve(std::size_t new_capacity)
350+
{
351+
if( new_capacity <= capacity() )
352+
return;
353+
table* const old_table = reserve_impl(new_capacity);
354+
table::deallocate( old_table, sp_ );
355+
}
356+
332357
//----------------------------------------------------------
333358
//
334359
// Lookup
@@ -491,8 +516,7 @@ insert(
491516
BOOST_STATIC_CONSTEXPR source_location loc = BOOST_CURRENT_LOCATION;
492517
detail::throw_system_error( error::object_too_large, &loc );
493518
}
494-
reserve(n0 + n);
495-
revert_insert r(*this);
519+
revert_insert r( *this, n0 + n );
496520
while(first != last)
497521
{
498522
insert(*first);

include/boost/json/impl/object.ipp

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -452,8 +452,7 @@ insert(
452452
BOOST_STATIC_CONSTEXPR source_location loc = BOOST_CURRENT_LOCATION;
453453
detail::throw_system_error( error::object_too_large, &loc );
454454
}
455-
reserve(n0 + init.size());
456-
revert_insert r(*this);
455+
revert_insert r( *this, n0 + init.size() );
457456
if(t_->is_small())
458457
{
459458
for(auto& iv : init)
@@ -708,10 +707,10 @@ insert_impl(
708707
return pv;
709708
}
710709

711-
// rehash to at least `n` buckets
712-
void
710+
// allocate new table, copy elements there, and rehash them
711+
object::table*
713712
object::
714-
rehash(std::size_t new_capacity)
713+
reserve_impl(std::size_t new_capacity)
715714
{
716715
BOOST_ASSERT(
717716
new_capacity > t_->capacity);
@@ -726,8 +725,7 @@ rehash(std::size_t new_capacity)
726725
size() * sizeof(
727726
key_value_pair));
728727
t->size = t_->size;
729-
table::deallocate(t_, sp_);
730-
t_ = t;
728+
std::swap(t_, t);
731729

732730
if(! t_->is_small())
733731
{
@@ -744,6 +742,8 @@ rehash(std::size_t new_capacity)
744742
head = i;
745743
}
746744
}
745+
746+
return t;
747747
}
748748

749749
bool

include/boost/json/object.hpp

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -898,13 +898,9 @@ class object
898898
899899
@throw system_error `new_capacity > max_size()`
900900
*/
901+
inline
901902
void
902-
reserve(std::size_t new_capacity)
903-
{
904-
if(new_capacity <= capacity())
905-
return;
906-
rehash(new_capacity);
907-
}
903+
reserve(std::size_t new_capacity);
908904

909905
//------------------------------------------------------
910906
//
@@ -982,9 +978,9 @@ class object
982978
are two keys within the range that are equal to each other, only the
983979
first will be inserted.
984980
985-
If the size necessary to accomodate elements from the range exceeds
986-
@ref capacity(), a rehashing can occur. In that case all iterators and
987-
references are invalidated. Otherwise, they are not affected.
981+
Insertion may result in rehashing of the container. In that case all
982+
iterators and references are invalidated. Otherwise, they are not
983+
affected.
988984
989985
@par Precondition
990986
`first` and `last` are not iterators into `*this`.
@@ -999,7 +995,8 @@ class object
999995
Linear in `std::distance(first, last)`.
1000996
1001997
@par Exception Safety
1002-
Basic guarantee.
998+
Strong guarantee for forward iterators, basic guarantee for input
999+
iterators.
10031000
Calls to `memory_resource::allocate` may throw.
10041001
10051002
@param first An input iterator pointing to the first
@@ -1033,8 +1030,7 @@ class object
10331030
are two keys within the initializer list that are equal to each other,
10341031
only the first will be inserted.
10351032
1036-
If the size necessary to accomodate elements from the initializer list
1037-
exceeds @ref capacity(), a rehashing can occur. In that case all
1033+
Insertion may result in rehashing of the container. In that case all
10381034
iterators and references are invalidated. Otherwise, they are not
10391035
affected.
10401036
@@ -1607,8 +1603,8 @@ class object
16071603
std::size_t hash);
16081604

16091605
BOOST_JSON_DECL
1610-
void
1611-
rehash(std::size_t new_capacity);
1606+
table*
1607+
reserve_impl(std::size_t new_capacity);
16121608

16131609
BOOST_JSON_DECL
16141610
bool

test/object.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,28 @@
2828
namespace boost {
2929
namespace json {
3030

31+
namespace {
32+
33+
struct throws_on_convert
34+
{
35+
// this member only exists due to MSVC code analysis bug that marks lines
36+
// in callers of the type's operator key_value_pair() as unreachable (due
37+
// to exception thrown), even if that caller is a function template, and
38+
// the line is reachable in other instantiations
39+
bool should_throw = true;
40+
41+
throws_on_convert() = default;
42+
43+
operator key_value_pair()
44+
{
45+
if( should_throw )
46+
throw std::invalid_argument("");
47+
return key_value_pair( "", nullptr);
48+
}
49+
};
50+
51+
} // namespace
52+
3153
BOOST_STATIC_ASSERT( std::is_nothrow_destructible<object>::value );
3254
BOOST_STATIC_ASSERT( std::is_nothrow_move_constructible<object>::value );
3355

@@ -1620,6 +1642,19 @@ class object_test
16201642

16211643
o.insert( key_value_pair("0", nullptr) );
16221644
BOOST_TEST( capacity == o.capacity() );
1645+
1646+
// Check that insertion rolls back reserve when cannot insert all
1647+
// elements.
1648+
std::array<throws_on_convert, 10> input;
1649+
try
1650+
{
1651+
o.insert( input.begin(), input.end() );
1652+
}
1653+
catch( ... )
1654+
{
1655+
// ignore
1656+
}
1657+
BOOST_TEST( capacity == o.capacity() );
16231658
}
16241659

16251660
void

0 commit comments

Comments
 (0)