GCC Code Coverage Report


Directory: libs/json/include/boost/json/
File: impl/object.hpp
Date: 2026-01-02 17:31:41
Exec Total Coverage
Lines: 168 168 100.0%
Functions: 69 71 97.2%
Branches: 70 72 97.2%

Line Branch Exec Source
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/boostorg/json
8 //
9
10 #ifndef BOOST_JSON_IMPL_OBJECT_HPP
11 #define BOOST_JSON_IMPL_OBJECT_HPP
12
13 #include <boost/core/detail/static_assert.hpp>
14 #include <boost/json/value.hpp>
15 #include <iterator>
16 #include <cmath>
17 #include <type_traits>
18 #include <utility>
19
20 namespace boost {
21 namespace json {
22
23 namespace detail {
24
25 // Objects with size less than or equal
26 // to this number will use a linear search
27 // instead of the more expensive hash function.
28 static
29 constexpr
30 std::size_t
31 small_object_size_ = 18;
32
33 BOOST_CORE_STATIC_ASSERT(
34 small_object_size_ < BOOST_JSON_MAX_STRUCTURED_SIZE);
35
36 } // detail
37
38 //----------------------------------------------------------
39
40 struct alignas(key_value_pair)
41 object::table
42 {
43 std::uint32_t size = 0;
44 std::uint32_t capacity = 0;
45 std::uintptr_t salt = 0;
46
47 #if defined(_MSC_VER) && BOOST_JSON_ARCH == 32
48 // VFALCO If we make key_value_pair smaller,
49 // then we might want to revisit this
50 // padding.
51 BOOST_CORE_STATIC_ASSERT( sizeof(key_value_pair) == 32 );
52 char pad[4] = {}; // silence warnings
53 #endif
54
55 constexpr table();
56
57 // returns true if we use a linear
58 // search instead of the hash table.
59 122172 bool is_small() const noexcept
60 {
61 122172 return capacity <=
62 122172 detail::small_object_size_;
63 }
64
65 key_value_pair&
66 294643 operator[](
67 std::size_t pos) noexcept
68 {
69 return reinterpret_cast<
70 key_value_pair*>(
71 294643 this + 1)[pos];
72 }
73
74 // VFALCO This is exported for tests
75 BOOST_JSON_DECL
76 std::size_t
77 digest(string_view key) const noexcept;
78
79 inline
80 index_t&
81 bucket(std::size_t hash) noexcept;
82
83 inline
84 index_t&
85 bucket(string_view key) noexcept;
86
87 inline
88 void
89 clear() noexcept;
90
91 static
92 inline
93 table*
94 allocate(
95 std::size_t capacity,
96 std::uintptr_t salt,
97 storage_ptr const& sp);
98
99 static
100 void
101 36348 deallocate(
102 table* p,
103 storage_ptr const& sp) noexcept
104 {
105
2/2
✓ Branch 0 taken 981 times.
✓ Branch 1 taken 35367 times.
36348 if(p->capacity == 0)
106 981 return;
107
2/2
✓ Branch 1 taken 388 times.
✓ Branch 2 taken 34979 times.
35367 if(! p->is_small())
108 388 sp->deallocate(p,
109 388 sizeof(table) + p->capacity * (
110 sizeof(key_value_pair) +
111 sizeof(index_t)));
112 else
113 34979 sp->deallocate(p,
114 34979 sizeof(table) + p->capacity *
115 sizeof(key_value_pair));
116 }
117 };
118
119 //----------------------------------------------------------
120
121 class object::revert_construct
122 {
123 object* obj_;
124
125 BOOST_JSON_DECL
126 void
127 destroy() noexcept;
128
129 public:
130 explicit
131 705 revert_construct(
132 object& obj) noexcept
133 705 : obj_(&obj)
134 {
135 705 }
136
137 705 ~revert_construct()
138 374 {
139
2/2
✓ Branch 0 taken 331 times.
✓ Branch 1 taken 374 times.
705 if(! obj_)
140 331 return;
141 374 destroy();
142 705 }
143
144 void
145 331 commit() noexcept
146 {
147 331 obj_ = nullptr;
148 331 }
149 };
150
151 //----------------------------------------------------------
152
153 class object::revert_insert
154 {
155 object* obj_;
156 table* t_ = nullptr;
157 std::size_t size_;
158
159 BOOST_JSON_DECL
160 void
161 destroy() noexcept;
162
163 public:
164 explicit
165 503 revert_insert(
166 object& obj,
167 std::size_t capacity)
168 503 : obj_(&obj)
169 503 , size_(obj_->size())
170 {
171
2/2
✓ Branch 1 taken 138 times.
✓ Branch 2 taken 365 times.
503 if( capacity > obj_->capacity() )
172 138 t_ = obj_->reserve_impl(capacity);
173 494 }
174
175 494 ~revert_insert()
176 230 {
177
2/2
✓ Branch 0 taken 264 times.
✓ Branch 1 taken 230 times.
494 if(! obj_)
178 264 return;
179
180 230 destroy();
181
2/2
✓ Branch 0 taken 117 times.
✓ Branch 1 taken 113 times.
230 if( t_ )
182 {
183 117 table::deallocate( obj_->t_, obj_->sp_ );
184 117 obj_->t_ = t_;
185 }
186 else
187 {
188 113 obj_->t_->size = static_cast<index_t>(size_);
189 }
190 494 }
191
192 void
193 264 commit() noexcept
194 {
195
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 264 times.
264 BOOST_ASSERT(obj_);
196
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 252 times.
264 if( t_ )
197 12 table::deallocate( t_, obj_->sp_ );
198 264 obj_ = nullptr;
199 264 }
200 };
201
202 //----------------------------------------------------------
203 //
204 // Iterators
205 //
206 //----------------------------------------------------------
207
208 auto
209 70786 object::
210 begin() noexcept ->
211 iterator
212 {
213 70786 return &(*t_)[0];
214 }
215
216 auto
217 53205 object::
218 begin() const noexcept ->
219 const_iterator
220 {
221 53205 return &(*t_)[0];
222 }
223
224 auto
225 3 object::
226 cbegin() const noexcept ->
227 const_iterator
228 {
229 3 return &(*t_)[0];
230 }
231
232 auto
233 46088 object::
234 end() noexcept ->
235 iterator
236 {
237 46088 return &(*t_)[t_->size];
238 }
239
240 auto
241 27785 object::
242 end() const noexcept ->
243 const_iterator
244 {
245 27785 return &(*t_)[t_->size];
246 }
247
248 auto
249 3 object::
250 cend() const noexcept ->
251 const_iterator
252 {
253 3 return &(*t_)[t_->size];
254 }
255
256 auto
257 2 object::
258 rbegin() noexcept ->
259 reverse_iterator
260 {
261 2 return reverse_iterator(end());
262 }
263
264 auto
265 2 object::
266 rbegin() const noexcept ->
267 const_reverse_iterator
268 {
269 2 return const_reverse_iterator(end());
270 }
271
272 auto
273 2 object::
274 crbegin() const noexcept ->
275 const_reverse_iterator
276 {
277 2 return const_reverse_iterator(end());
278 }
279
280 auto
281 2 object::
282 rend() noexcept ->
283 reverse_iterator
284 {
285 2 return reverse_iterator(begin());
286 }
287
288 auto
289 2 object::
290 rend() const noexcept ->
291 const_reverse_iterator
292 {
293 2 return const_reverse_iterator(begin());
294 }
295
296 auto
297 2 object::
298 crend() const noexcept ->
299 const_reverse_iterator
300 {
301 2 return const_reverse_iterator(begin());
302 }
303
304 //----------------------------------------------------------
305 //
306 // Capacity
307 //
308 //----------------------------------------------------------
309
310 bool
311 10462 object::
312 empty() const noexcept
313 {
314 10462 return t_->size == 0;
315 }
316
317 auto
318 44242 object::
319 size() const noexcept ->
320 std::size_t
321 {
322 44242 return t_->size;
323 }
324
325 constexpr
326 std::size_t
327 73119 object::
328 max_size() noexcept
329 {
330 // max_size depends on the address model
331 using min = std::integral_constant<std::size_t,
332 (std::size_t(-1) - sizeof(table)) /
333 (sizeof(key_value_pair) + sizeof(index_t))>;
334 return min::value < BOOST_JSON_MAX_STRUCTURED_SIZE ?
335 73119 min::value : BOOST_JSON_MAX_STRUCTURED_SIZE;
336 }
337
338 auto
339 18830 object::
340 capacity() const noexcept ->
341 std::size_t
342 {
343 18830 return t_->capacity;
344 }
345
346 void
347 4737 object::
348 reserve(std::size_t new_capacity)
349 {
350
2/2
✓ Branch 1 taken 3221 times.
✓ Branch 2 taken 1516 times.
4737 if( new_capacity <= capacity() )
351 3221 return;
352 1516 table* const old_table = reserve_impl(new_capacity);
353 1451 table::deallocate( old_table, sp_ );
354 }
355
356 //----------------------------------------------------------
357 //
358 // Lookup
359 //
360 //----------------------------------------------------------
361
362 value&
363 41 object::
364 at(string_view key, source_location const& loc) &
365 {
366 41 auto const& self = *this;
367 41 return const_cast< value& >( self.at(key, loc) );
368 }
369
370 value&&
371 5 object::
372 at(string_view key, source_location const& loc) &&
373 {
374 5 return std::move( at(key, loc) );
375 }
376
377 //----------------------------------------------------------
378
379 template<class P, class>
380 auto
381 6210 object::
382 insert(P&& p) ->
383 std::pair<iterator, bool>
384 {
385
3/3
✓ Branch 2 taken 44 times.
✓ Branch 5 taken 44 times.
✓ Branch 1 taken 1 times.
6800 key_value_pair v(
386 6210 std::forward<P>(p), sp_);
387
1/1
✓ Branch 3 taken 2812 times.
11496 return emplace_impl( v.key(), pilfer(v) );
388 5914 }
389
390 template<class M>
391 auto
392 34 object::
393 insert_or_assign(
394 string_view key, M&& m) ->
395 std::pair<iterator, bool>
396 {
397
1/1
✓ Branch 1 taken 10 times.
34 std::pair<iterator, bool> result = emplace_impl(
398 key, key, static_cast<M&&>(m) );
399
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
20 if( !result.second )
400 {
401
2/2
✓ Branch 2 taken 1 times.
✓ Branch 5 taken 1 times.
16 value(static_cast<M>(m), sp_).swap(
402 6 result.first->value());
403 }
404 18 return result;
405 }
406
407 template<class Arg>
408 auto
409 1779 object::
410 emplace(
411 string_view key,
412 Arg&& arg) ->
413 std::pair<iterator, bool>
414 {
415 1779 return emplace_impl( key, key, static_cast<Arg&&>(arg) );
416 }
417
418 //----------------------------------------------------------
419 //
420 // (private)
421 //
422 //----------------------------------------------------------
423
424 template<class InputIt>
425 void
426 155 object::
427 construct(
428 InputIt first,
429 InputIt last,
430 std::size_t min_capacity,
431 std::input_iterator_tag)
432 {
433
1/1
✓ Branch 1 taken 76 times.
155 reserve(min_capacity);
434 151 revert_construct r(*this);
435
2/2
✓ Branch 1 taken 750 times.
✓ Branch 2 taken 3 times.
1484 while(first != last)
436 {
437
1/1
✓ Branch 2 taken 677 times.
1478 insert(*first);
438 1333 ++first;
439 }
440 6 r.commit();
441 151 }
442
443 template<class InputIt>
444 void
445 163 object::
446 construct(
447 InputIt first,
448 InputIt last,
449 std::size_t min_capacity,
450 std::forward_iterator_tag)
451 {
452 163 auto n = static_cast<
453 163 std::size_t>(std::distance(
454 first, last));
455
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 6 times.
163 if( n < min_capacity)
456 152 n = min_capacity;
457
1/1
✓ Branch 1 taken 79 times.
163 reserve(n);
458 158 revert_construct r(*this);
459
3/3
✓ Branch 0 taken 764 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
1542 while(first != last)
460 {
461
1/1
✓ Branch 1 taken 692 times.
1528 insert(*first);
462 1384 ++first;
463 }
464 14 r.commit();
465 158 }
466
467 template<class InputIt>
468 void
469 94 object::
470 insert(
471 InputIt first,
472 InputIt last,
473 std::input_iterator_tag)
474 {
475 // Since input iterators cannot be rewound,
476 // we keep inserted elements on an exception.
477 //
478
2/2
✓ Branch 1 taken 867 times.
✓ Branch 2 taken 4 times.
871 while(first != last)
479 {
480 867 insert(*first);
481 777 ++first;
482 }
483 4 }
484
485 template<class InputIt>
486 void
487 159 object::
488 insert(
489 InputIt first,
490 InputIt last,
491 std::forward_iterator_tag)
492 {
493 159 auto const n =
494 static_cast<std::size_t>(
495 159 std::distance(first, last));
496 159 auto const n0 = size();
497
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 79 times.
159 if(n > max_size() - n0)
498 {
499 BOOST_STATIC_CONSTEXPR source_location loc = BOOST_CURRENT_LOCATION;
500 1 detail::throw_system_error( error::object_too_large, &loc );
501 }
502
1/1
✓ Branch 1 taken 77 times.
158 revert_insert r( *this, n0 + n );
503
2/2
✓ Branch 0 taken 734 times.
✓ Branch 1 taken 4 times.
1476 while(first != last)
504 {
505
1/1
✓ Branch 1 taken 661 times.
1468 insert(*first);
506 1322 ++first;
507 }
508 8 r.commit();
509 154 }
510
511 template< class... Args >
512 std::pair<object::iterator, bool>
513 7727 object::
514 emplace_impl( string_view key, Args&& ... args )
515 {
516 7727 std::pair<iterator, std::size_t> search_result(nullptr, 0);
517
2/2
✓ Branch 1 taken 3459 times.
✓ Branch 2 taken 527 times.
7727 if( !empty() )
518 {
519 6714 search_result = detail::find_in_object(*this, key);
520
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 3431 times.
6714 if( search_result.first )
521 54 return { search_result.first, false };
522 }
523
524 // we create the new value before reserving, in case it is a reference to
525 // a subobject of the current object
526
2/2
✓ Branch 1 taken 980 times.
✓ Branch 3 taken 2825 times.
8257 key_value_pair kv( static_cast<Args&&>(args)..., sp_ );
527 // the key might get deallocated too
528 7367 key = kv.key();
529
530 7367 std::size_t const old_capacity = capacity();
531
1/1
✓ Branch 2 taken 3778 times.
7367 reserve(size() + 1);
532
2/2
✓ Branch 2 taken 388 times.
✓ Branch 3 taken 123 times.
8296 if( (empty() && capacity() > detail::small_object_size_)
533
6/6
✓ Branch 0 taken 511 times.
✓ Branch 1 taken 3267 times.
✓ Branch 3 taken 601 times.
✓ Branch 4 taken 3054 times.
✓ Branch 5 taken 724 times.
✓ Branch 6 taken 3054 times.
8296 || (capacity() != old_capacity) )
534 1400 search_result.second = detail::digest(
535 1400 key.begin(), key.end(), t_->salt);
536
537
3/4
✓ Branch 1 taken 1587 times.
✓ Branch 2 taken 2191 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 1587 times.
7315 BOOST_ASSERT(
538 t_->is_small() ||
539 (search_result.second ==
540 detail::digest(key.begin(), key.end(), t_->salt)) );
541
542
1/1
✓ Branch 2 taken 3778 times.
7315 return { insert_impl(pilfer(kv), search_result.second), true };
543 7367 }
544
545 //----------------------------------------------------------
546
547 namespace detail {
548
549 34879 unchecked_object::
550 1086 ~unchecked_object()
551 {
552
2/2
✓ Branch 0 taken 33791 times.
✓ Branch 1 taken 1088 times.
34879 if(! data_)
553 33791 return;
554
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1086 times.
1088 if(sp_.is_not_shared_and_deallocate_is_trivial())
555 2 return;
556 1086 value* p = data_;
557
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 1086 times.
1146 while(size_--)
558 {
559 60 p[0].~value();
560 60 p[1].~value();
561 60 p += 2;
562 }
563 34879 }
564
565 } // detail
566
567 } // namespace json
568 } // namespace boost
569
570 #endif
571