Coverage Report

Created: 2025-10-10 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/NNTree.cc
Line
Count
Source
1
#include <qpdf/assert_debug.h>
2
3
#include <qpdf/NNTree.hh>
4
5
#include <qpdf/QPDFNameTreeObjectHelper.hh>
6
#include <qpdf/QPDFNumberTreeObjectHelper.hh>
7
8
#include <qpdf/QPDFObjectHandle_private.hh>
9
#include <qpdf/QPDF_private.hh>
10
#include <qpdf/QTC.hh>
11
#include <qpdf/QUtil.hh>
12
13
#include <bit>
14
#include <exception>
15
#include <utility>
16
17
using namespace qpdf;
18
19
static std::string
20
get_description(QPDFObjectHandle const& node)
21
4.21k
{
22
4.21k
    std::string result("Name/Number tree node");
23
4.21k
    if (node.indirect()) {
24
4.18k
        result += " (object " + std::to_string(node.getObjectID()) + ")";
25
4.18k
    }
26
4.21k
    return result;
27
4.21k
}
28
29
void
30
NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg)
31
3.54k
{
32
3.54k
    qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg);
33
3.54k
    if (++error_count > 5 && qpdf.doc().reconstructed_xref()) {
34
234
        error(node, "too many errors - giving up");
35
234
    }
36
3.54k
}
37
38
void
39
NNTreeImpl::error(QPDFObjectHandle const& node, std::string const& msg) const
40
671
{
41
671
    throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg);
42
671
}
43
44
void
45
NNTreeIterator::updateIValue(bool allow_invalid)
46
69.9k
{
47
    // ivalue should never be used inside the class since we return a pointer/reference to it. Every
48
    // bit of code that ever changes what object the iterator points to should take care to call
49
    // updateIValue. Failure to do this means that any old references to *iter will point to
50
    // incorrect objects, though the next dereference of the iterator will fix it. This isn't
51
    // necessarily catastrophic, but it would be confusing. The test suite attempts to exercise
52
    // various cases to ensure we don't introduce that bug in the future, but sadly it's tricky to
53
    // verify by reasoning about the code that this constraint is always satisfied. Whenever we
54
    // update what the iterator points to, we should call setItemNumber, which calls this. If we
55
    // change what the iterator points to in some other way, such as replacing a value or removing
56
    // an item and making the iterator point at a different item in potentially the same position,
57
    // we must call updateIValue as well. These cases are handled, and for good measure, we also
58
    // call updateIValue in operator* and operator->.
59
60
69.9k
    Array items = node[impl.itemsKey()];
61
69.9k
    ivalue.first = items[item_number];
62
69.9k
    ivalue.second = items[item_number + 1];
63
69.9k
    if (ivalue.second) {
64
68.6k
        return;
65
68.6k
    }
66
67
1.35k
    if (item_number < 0 || !node) {
68
1.34k
        if (!allow_invalid) {
69
0
            throw std::logic_error(
70
0
                "attempt made to dereference an invalid name/number tree iterator");
71
0
        }
72
1.34k
        return;
73
1.34k
    }
74
5
    impl.error(node, "update ivalue: items array is too short");
75
5
}
76
77
Dictionary
78
NNTreeIterator::getNextKid(PathElement& pe, bool backward)
79
452
{
80
720
    while (true) {
81
675
        pe.kid_number += backward ? -1 : 1;
82
675
        Dictionary result = pe.node["/Kids"][pe.kid_number];
83
675
        if (result.contains("/Kids") || result.contains(impl.itemsKey())) {
84
205
            return result;
85
205
        }
86
470
        if (pe.kid_number < 0 || std::cmp_greater_equal(pe.kid_number, pe.node["/Kids"].size())) {
87
202
            return {};
88
202
        }
89
268
        impl.warn(pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
90
268
    }
91
452
}
92
void
93
NNTreeIterator::increment(bool backward)
94
29.6k
{
95
29.6k
    if (item_number < 0) {
96
0
        deepen(impl.tree_root, !backward, true);
97
0
        return;
98
0
    }
99
100
31.5k
    while (valid()) {
101
31.0k
        item_number += backward ? -2 : 2;
102
31.0k
        Array items = node[impl.itemsKey()];
103
31.0k
        if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
104
575
            setItemNumber(QPDFObjectHandle(), -1);
105
901
            while (!path.empty()) {
106
452
                auto& element = path.back();
107
452
                if (auto pe_node = getNextKid(element, backward)) {
108
205
                    if (deepen(pe_node, !backward, false)) {
109
126
                        break;
110
126
                    }
111
247
                } else {
112
247
                    path.pop_back();
113
247
                }
114
452
            }
115
575
        }
116
31.0k
        if (item_number >= 0) {
117
30.5k
            items = node[impl.itemsKey()];
118
30.5k
            if (std::cmp_greater_equal(item_number + 1, items.size())) {
119
133
                impl.warn(node, "items array doesn't have enough elements");
120
30.4k
            } else if (!impl.keyValid(items[item_number])) {
121
616
                impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type"));
122
29.8k
            } else if (!impl.value_valid(items[item_number + 1])) {
123
722
                impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid");
124
29.0k
            } else {
125
29.0k
                return;
126
29.0k
            }
127
30.5k
        }
128
31.0k
    }
129
29.6k
}
130
131
void
132
NNTreeIterator::resetLimits(Dictionary a_node, std::list<PathElement>::iterator parent)
133
7.43k
{
134
7.43k
    while (true) {
135
7.43k
        if (parent == path.end()) {
136
4.61k
            a_node.erase("/Limits");
137
4.61k
            return;
138
4.61k
        }
139
140
2.82k
        QPDFObjectHandle first;
141
2.82k
        QPDFObjectHandle last;
142
2.82k
        Array items = a_node[impl.itemsKey()];
143
2.82k
        size_t nitems = items.size();
144
2.82k
        if (nitems >= 2) {
145
2.70k
            first = items[0];
146
2.70k
            last = items[(nitems - 1u) & ~1u];
147
2.70k
        } else {
148
121
            Array kids = a_node["/Kids"];
149
121
            size_t nkids = kids.size();
150
121
            if (nkids > 0) {
151
121
                Array first_limits = kids[0]["/Limits"];
152
121
                if (first_limits.size() >= 2) {
153
121
                    first = first_limits[0];
154
121
                    last = kids[nkids - 1u]["/Limits"][1];
155
121
                }
156
121
            }
157
121
        }
158
2.82k
        if (!(first && last)) {
159
0
            impl.warn(a_node, "unable to determine limits");
160
2.82k
        } else {
161
2.82k
            Array olimits = a_node["/Limits"];
162
2.82k
            if (olimits.size() == 2) {
163
2.39k
                auto ofirst = olimits[0];
164
2.39k
                auto olast = olimits[1];
165
2.39k
                if (impl.keyValid(ofirst) && impl.keyValid(olast) &&
166
2.39k
                    impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) {
167
0
                    return;
168
0
                }
169
2.39k
            }
170
2.82k
            if (a_node != path.begin()->node) {
171
2.70k
                a_node.replaceKey("/Limits", Array({first, last}));
172
2.70k
            }
173
2.82k
        }
174
175
2.82k
        if (parent == path.begin()) {
176
2.82k
            return;
177
2.82k
        }
178
0
        a_node = parent->node;
179
0
        --parent;
180
0
    }
181
7.43k
}
182
183
void
184
NNTreeIterator::split(Dictionary to_split, std::list<PathElement>::iterator parent)
185
7.00k
{
186
    // Split some node along the path to the item pointed to by this iterator, and adjust the
187
    // iterator so it points to the same item.
188
189
    // In examples, for simplicity, /Nums is shown to just contain numbers instead of pairs. Imagine
190
    // this tree:
191
    //
192
    // root: << /Kids [ A B C D ] >>
193
    // A: << /Nums [ 1 2 3 4 ] >>
194
    // B: << /Nums [ 5 6 7 8 ] >>
195
    // C: << /Nums [ 9 10 11 12 ] >>
196
    // D: << /Kids [ E F ]
197
    // E: << /Nums [ 13 14 15 16 ] >>
198
    // F: << /Nums [ 17 18 19 20 ] >>
199
200
    // iter1 (points to 19)
201
    //   path:
202
    //   - { node: root: kid_number: 3 }
203
    //   - { node: D, kid_number: 1 }
204
    //   node: F
205
    //   item_number: 2
206
207
    // iter2 (points to 1)
208
    //   path:
209
    //   - { node: root, kid_number: 0}
210
    //   node: A
211
    //   item_number: 0
212
213
7.00k
    if (!valid()) {
214
0
        throw std::logic_error("NNTreeIterator::split called an invalid iterator");
215
0
    }
216
217
    // Find the array we actually need to split, which is either this node's kids or items.
218
7.00k
    Array kids = to_split["/Kids"];
219
7.00k
    size_t nkids = kids.size();
220
7.00k
    Array items = to_split[impl.itemsKey()];
221
7.00k
    size_t nitems = items.size();
222
223
7.00k
    Array first_half;
224
7.00k
    size_t n = 0;
225
7.00k
    std::string key;
226
7.00k
    size_t threshold = static_cast<size_t>(impl.split_threshold);
227
7.00k
    if (nkids > 0) {
228
121
        first_half = kids;
229
121
        n = nkids;
230
121
        key = "/Kids";
231
6.88k
    } else if (nitems > 0) {
232
6.88k
        first_half = items;
233
6.88k
        n = nitems;
234
6.88k
        threshold *= 2;
235
6.88k
        key = impl.itemsKey();
236
6.88k
    } else {
237
0
        throw std::logic_error("NNTreeIterator::split called on invalid node");
238
0
    }
239
240
7.00k
    if (n <= threshold) {
241
6.79k
        return;
242
6.79k
    }
243
244
215
    bool is_root = parent == path.end();
245
215
    bool is_leaf = nitems > 0;
246
247
    // CURRENT STATE: tree is in original state; iterator is valid and unchanged.
248
249
215
    if (is_root) {
250
        // What we want to do is to create a new node for the second half of the items and put it in
251
        // the parent's /Kids array right after the element that points to the current to_split
252
        // node, but if we're splitting root, there is no parent, so handle that first.
253
254
        // In the non-root case, parent points to the path element whose /Kids contains the first
255
        // half node, and the first half node is to_split. If we are splitting the root, we need to
256
        // push everything down a level, but we want to keep the actual root object the same so that
257
        // indirect references to it remain intact (and also in case it might be a direct object,
258
        // which it shouldn't be but that case probably exists in the wild). To achieve this, we
259
        // create a new node for the first half and then replace /Kids in the root to contain it.
260
        // Then we adjust the path so that the first element is root and the second element, if any,
261
        // is the new first half. In this way, we make the root case identical to the non-root case
262
        // so remaining logic can handle them in the same way.
263
264
94
        Dictionary first_node = impl.qpdf.makeIndirectObject(Dictionary({{key, first_half}}));
265
94
        auto new_kids = Array::empty();
266
94
        new_kids.push_back(first_node);
267
94
        to_split.erase("/Limits"); // already shouldn't be there for root
268
94
        to_split.erase(impl.itemsKey());
269
94
        to_split.replaceKey("/Kids", new_kids);
270
94
        if (is_leaf) {
271
93
            node = first_node;
272
93
        } else {
273
1
            auto next = path.begin();
274
1
            next->node = first_node;
275
1
        }
276
94
        this->path.emplace_front(to_split, 0);
277
94
        parent = path.begin();
278
94
        to_split = first_node;
279
94
    }
280
281
    // CURRENT STATE: parent is guaranteed to be defined, and we have the invariants that
282
    // parent[/Kids][kid_number] == to_split and (++parent).node == to_split.
283
284
    // Create a second half array, and transfer the second half of the items into the second half
285
    // array.
286
215
    auto second_half = Array::empty();
287
215
    auto start_idx = static_cast<int>((n / 2) & ~1u);
288
7.49k
    while (std::cmp_greater(first_half.size(), start_idx)) {
289
7.27k
        second_half.push_back(first_half[start_idx]);
290
7.27k
        first_half.erase(start_idx);
291
7.27k
    }
292
215
    resetLimits(to_split, parent);
293
294
    // Create a new node to contain the second half
295
215
    Dictionary second_node = impl.qpdf.makeIndirectObject(Dictionary({{key, second_half}}));
296
215
    resetLimits(second_node, parent);
297
298
    // CURRENT STATE: half the items from the kids or items array in the node being split have been
299
    // moved into a new node. The new node is not yet attached to the tree. The iterator may have a
300
    // path element or leaf node that is out of bounds.
301
302
    // We need to adjust the parent to add the second node to /Kids and, if needed, update
303
    // kid_number to traverse through it. We need to update to_split's path element, or the node if
304
    // this is a leaf, so that the kid/item number points to the right place.
305
306
215
    Array parent_kids = parent->node["/Kids"];
307
215
    if (!parent_kids) {
308
0
        impl.error(parent->node, "parent node has no /Kids array");
309
0
    }
310
215
    parent_kids.insert(parent->kid_number + 1, second_node);
311
215
    auto cur_elem = parent;
312
215
    ++cur_elem; // points to end() for leaf nodes
313
215
    int old_idx = (is_leaf ? item_number : cur_elem->kid_number);
314
215
    if (old_idx >= start_idx) {
315
214
        ++parent->kid_number;
316
214
        if (is_leaf) {
317
214
            setItemNumber(second_node, item_number - start_idx);
318
214
        } else {
319
0
            cur_elem->node = second_node;
320
0
            cur_elem->kid_number -= start_idx;
321
0
        }
322
214
    }
323
215
    if (!is_root) {
324
121
        auto next = parent->node;
325
121
        resetLimits(next, parent);
326
121
        --parent;
327
121
        split(next, parent);
328
121
    }
329
215
}
330
331
std::list<NNTreeIterator::PathElement>::iterator
332
NNTreeIterator::lastPathElement()
333
13.7k
{
334
13.7k
    return path.empty() ? path.end() : std::prev(path.end());
335
13.7k
}
336
337
void
338
NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
339
6.63k
{
340
6.63k
    if (!valid()) {
341
0
        impl.insertFirst(key, value);
342
0
        deepen(impl.tree_root, true, false);
343
0
        return;
344
0
    }
345
346
6.63k
    Array items = node[impl.itemsKey()];
347
6.63k
    if (!items) {
348
0
        impl.error(node, "node contains no items array");
349
0
    }
350
351
6.63k
    if (std::cmp_less(items.size(), item_number + 2)) {
352
0
        impl.error(node, "insert: items array is too short");
353
0
    }
354
6.63k
    if (!(key && value)) {
355
0
        impl.error(node, "insert: key or value is null");
356
0
    }
357
6.63k
    if (!impl.value_valid(value)) {
358
0
        impl.error(node, "insert: value is invalid");
359
0
    }
360
6.63k
    items.insert(item_number + 2, key);
361
6.63k
    items.insert(item_number + 3, value);
362
6.63k
    resetLimits(node, lastPathElement());
363
6.63k
    split(node, lastPathElement());
364
6.63k
    increment(false);
365
6.63k
}
366
367
void
368
NNTreeIterator::remove()
369
0
{
370
    // Remove this item, leaving the tree valid and this iterator pointing to the next item.
371
372
0
    if (!valid()) {
373
0
        throw std::logic_error("attempt made to remove an invalid iterator");
374
0
    }
375
0
    Array items = node[impl.itemsKey()];
376
0
    int nitems = static_cast<int>(items.size());
377
0
    if (std::cmp_greater(item_number + 2, nitems)) {
378
0
        impl.error(node, "found short items array while removing an item");
379
0
    }
380
381
0
    items.erase(item_number);
382
0
    items.erase(item_number);
383
0
    nitems -= 2;
384
385
0
    if (nitems > 0) {
386
        // There are still items left
387
388
0
        if (item_number == 0 || item_number == nitems) {
389
            // We removed either the first or last item of an items array that remains non-empty, so
390
            // we have to adjust limits.
391
0
            resetLimits(node, lastPathElement());
392
0
        }
393
394
0
        if (item_number == nitems) {
395
            // We removed the last item of a non-empty items array, so advance to the successor of
396
            // the previous item.
397
0
            item_number -= 2;
398
0
            increment(false);
399
0
        } else if (item_number < nitems) {
400
            // We don't have to do anything since the removed item's successor now occupies its
401
            // former location.
402
0
            updateIValue();
403
0
        } else {
404
            // We already checked to ensure this condition would not happen.
405
0
            throw std::logic_error("NNTreeIterator::remove: item_number > nitems after erase");
406
0
        }
407
0
        return;
408
0
    }
409
410
0
    if (path.empty()) {
411
        // Special case: if this is the root node, we can leave it empty.
412
0
        setItemNumber(impl.tree_root, -1);
413
0
        return;
414
0
    }
415
416
    // We removed the last item from this items array, so we need to remove this node from the
417
    // parent on up the tree. Then we need to position ourselves at the removed item's successor.
418
0
    while (true) {
419
0
        auto element = lastPathElement();
420
0
        auto parent = element;
421
0
        --parent;
422
0
        Array kids = element->node["/Kids"];
423
0
        kids.erase(element->kid_number);
424
0
        auto nkids = kids.size();
425
0
        if (nkids > 0) {
426
            // The logic here is similar to the items case.
427
0
            if (element->kid_number == 0 || std::cmp_equal(element->kid_number, nkids)) {
428
0
                resetLimits(element->node, parent);
429
0
            }
430
0
            if (std::cmp_equal(element->kid_number, nkids)) {
431
                // Move to the successor of the last child of the previous kid.
432
0
                setItemNumber({}, -1);
433
0
                --element->kid_number;
434
0
                deepen(kids[element->kid_number], false, true);
435
0
                if (valid()) {
436
0
                    increment(false);
437
0
                    QTC::TC("qpdf", "NNTree erased last kid/item in tree", valid() ? 0 : 1);
438
0
                }
439
0
            } else {
440
                // Next kid is in deleted kid's position
441
0
                deepen(kids.get(element->kid_number), true, true);
442
0
            }
443
0
            return;
444
0
        }
445
446
0
        if (parent == path.end()) {
447
            // We erased the very last item. Convert the root to an empty items array.
448
0
            element->node.erase("/Kids");
449
0
            element->node.replaceKey(impl.itemsKey(), Array::empty());
450
0
            path.clear();
451
0
            setItemNumber(impl.tree_root, -1);
452
0
            return;
453
0
        }
454
455
        // Walk up the tree and continue
456
0
        path.pop_back();
457
0
    }
458
0
}
459
460
bool
461
NNTreeIterator::operator==(NNTreeIterator const& other) const
462
26.1k
{
463
26.1k
    if (item_number == -1 && other.item_number == -1) {
464
2.60k
        return true;
465
2.60k
    }
466
23.5k
    if (path.size() != other.path.size()) {
467
13.4k
        return false;
468
13.4k
    }
469
10.0k
    auto tpi = path.begin();
470
10.0k
    auto opi = other.path.begin();
471
10.0k
    while (tpi != path.end()) {
472
0
        if (tpi->kid_number != opi->kid_number) {
473
0
            return false;
474
0
        }
475
0
        ++tpi;
476
0
        ++opi;
477
0
    }
478
10.0k
    return item_number == other.item_number;
479
10.0k
}
480
481
bool
482
NNTreeIterator::deepen(Dictionary a_node, bool first, bool allow_empty)
483
10.7k
{
484
    // Starting at this node, descend through the first or last kid until we reach a node with
485
    // items. If we succeed, return true; otherwise return false and leave path alone.
486
487
10.7k
    auto opath = path;
488
489
10.7k
    auto fail = [this, &opath](Dictionary const& failed_node, std::string const& msg) {
490
1.31k
        impl.warn(failed_node, msg);
491
1.31k
        path = opath;
492
1.31k
        return false;
493
1.31k
    };
494
495
10.7k
    QPDFObjGen::set seen;
496
10.7k
    for (auto const& i: path) {
497
270
        seen.add(i.node);
498
270
    }
499
14.5k
    while (true) {
500
14.5k
        if (!seen.add(a_node)) {
501
20
            return fail(a_node, "loop detected while traversing name/number tree");
502
20
        }
503
504
14.5k
        if (!a_node) {
505
0
            return fail(a_node, "non-dictionary node while traversing name/number tree");
506
0
        }
507
508
14.5k
        Array items = a_node[impl.itemsKey()];
509
14.5k
        int nitems = static_cast<int>(items.size());
510
14.5k
        if (nitems > 1) {
511
8.57k
            setItemNumber(a_node, first ? 0 : nitems - 2);
512
8.57k
            return true;
513
8.57k
        }
514
515
5.93k
        Array kids = a_node["/Kids"];
516
5.93k
        int nkids = static_cast<int>(kids.size());
517
5.93k
        if (nkids == 0) {
518
1.60k
            if (allow_empty && items) {
519
812
                setItemNumber(a_node, -1);
520
812
                return true;
521
812
            }
522
790
            return fail(
523
790
                a_node,
524
790
                "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids");
525
1.60k
        }
526
527
4.32k
        int kid_number = first ? 0 : nkids - 1;
528
4.32k
        addPathElement(a_node, kid_number);
529
4.32k
        Dictionary next = kids[kid_number];
530
4.32k
        if (!next) {
531
506
            return fail(a_node, "kid number " + std::to_string(kid_number) + " is invalid");
532
506
        }
533
3.82k
        if (!next.indirect()) {
534
3
            if (impl.auto_repair) {
535
3
                impl.warn(
536
3
                    a_node,
537
3
                    "converting kid number " + std::to_string(kid_number) +
538
3
                        " to an indirect object");
539
3
                next = impl.qpdf.makeIndirectObject(next);
540
3
                kids.set(kid_number, next);
541
3
            } else {
542
0
                impl.warn(
543
0
                    a_node,
544
0
                    "kid number " + std::to_string(kid_number) + " is not an indirect object");
545
0
            }
546
3
        }
547
548
3.82k
        a_node = next;
549
3.82k
    }
550
10.7k
}
551
552
NNTreeImpl::iterator
553
NNTreeImpl::begin()
554
10.4k
{
555
10.4k
    iterator result(*this);
556
10.4k
    result.deepen(tree_root, true, true);
557
10.4k
    return result;
558
10.4k
}
559
560
NNTreeImpl::iterator
561
NNTreeImpl::last()
562
0
{
563
0
    iterator result(*this);
564
0
    result.deepen(tree_root, false, true);
565
0
    return result;
566
0
}
567
568
int
569
NNTreeImpl::compareKeys(QPDFObjectHandle a, QPDFObjectHandle b) const
570
51.2k
{
571
    // We don't call this without calling keyValid first
572
51.2k
    qpdf_assert_debug(keyValid(a));
573
51.2k
    qpdf_assert_debug(keyValid(b));
574
51.2k
    if (key_type == ::ot_string) {
575
51.2k
        auto as = a.getUTF8Value();
576
51.2k
        auto bs = b.getUTF8Value();
577
51.2k
        return as < bs ? -1 : (as > bs ? 1 : 0);
578
51.2k
    }
579
0
    auto as = a.getIntValue();
580
0
    auto bs = b.getIntValue();
581
0
    return as < bs ? -1 : (as > bs ? 1 : 0);
582
51.2k
}
583
584
int
585
NNTreeImpl::binarySearch(
586
    QPDFObjectHandle const& key,
587
    Array const& items,
588
    size_t num_items,
589
    bool return_prev_if_not_found,
590
    bool search_kids) const
591
10.0k
{
592
10.0k
    size_t max_idx = std::bit_ceil(num_items);
593
594
10.0k
    int step = static_cast<int>(max_idx / 2);
595
10.0k
    int checks = static_cast<int>(std::bit_width(max_idx)); // AppImage gcc version returns size_t
596
10.0k
    int idx = step;
597
10.0k
    int found_idx = -1;
598
599
54.4k
    for (int i = 0; i < checks; ++i) {
600
44.6k
        int status = -1;
601
44.6k
        if (std::cmp_less(idx, num_items)) {
602
26.9k
            status = search_kids ? compareKeyKid(key, items, idx) : compareKeyItem(key, items, idx);
603
26.9k
            if (status == 0) {
604
324
                return idx;
605
324
            }
606
26.6k
            if (status > 0) {
607
25.6k
                found_idx = idx;
608
25.6k
            }
609
26.6k
        }
610
44.3k
        step = std::max(step / 2, 1);
611
44.3k
        idx += status * step;
612
44.3k
    }
613
9.76k
    return return_prev_if_not_found ? found_idx : -1;
614
10.0k
}
615
616
int
617
NNTreeImpl::compareKeyItem(QPDFObjectHandle const& key, Array const& items, int idx) const
618
22.0k
{
619
22.0k
    if (!keyValid(items[2 * idx])) {
620
17
        error(tree_root, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
621
17
    }
622
22.0k
    return compareKeys(key, items[2 * idx]);
623
22.0k
}
624
625
int
626
NNTreeImpl::compareKeyKid(QPDFObjectHandle const& key, Array const& kids, int idx) const
627
4.87k
{
628
4.87k
    Dictionary kid = kids[idx];
629
4.87k
    if (!kid) {
630
13
        error(tree_root, "invalid kid at index " + std::to_string(idx));
631
13
    }
632
4.87k
    Array limits = kid["/Limits"];
633
4.87k
    if (!(keyValid(limits[0]) && keyValid(limits[1]))) {
634
20
        error(kids[idx], "node is missing /Limits");
635
20
    }
636
4.87k
    if (compareKeys(key, limits[0]) < 0) {
637
176
        return -1;
638
176
    }
639
4.69k
    if (compareKeys(key, limits[1]) > 0) {
640
4.45k
        return 1;
641
4.45k
    }
642
237
    return 0;
643
4.69k
}
644
645
namespace
646
{
647
    struct Cmp
648
    {
649
        bool
650
        operator()(const QPDFObjectHandle& lhs, const QPDFObjectHandle& rhs) const
651
154k
        {
652
154k
            Integer l = lhs;
653
154k
            Integer r = rhs;
654
154k
            if (l && r) {
655
0
                return l.value() < r.value();
656
0
            }
657
154k
            return lhs.getUTF8Value() < rhs.getUTF8Value();
658
154k
        }
659
    };
660
} // namespace
661
662
void
663
NNTreeImpl::repair()
664
421
{
665
421
    auto new_node = Dictionary({{itemsKey(), Array::empty()}});
666
421
    NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false);
667
421
    std::map<QPDFObjectHandle, QPDFObjectHandle, Cmp> items;
668
22.1k
    for (auto const& [key, value]: *this) {
669
22.1k
        if (key && value && repl.keyValid(key) && repl.value_valid(value)) {
670
21.9k
            items.insert_or_assign(key, value);
671
21.9k
        }
672
22.1k
    }
673
6.88k
    for (auto const& [key, value]: items) {
674
6.88k
        repl.insert(key, value);
675
6.88k
    }
676
421
    tree_root.replaceKey("/Kids", new_node["/Kids"]);
677
421
    tree_root.replaceKey(itemsKey(), new_node[itemsKey()]);
678
421
}
679
680
NNTreeImpl::iterator
681
NNTreeImpl::find(QPDFObjectHandle const& key, bool return_prev_if_not_found)
682
8.77k
{
683
8.77k
    try {
684
8.77k
        return findInternal(key, return_prev_if_not_found);
685
8.77k
    } catch (QPDFExc& e) {
686
90
        if (auto_repair) {
687
90
            warn(tree_root, std::string("attempting to repair after error: ") + e.what());
688
90
            repair();
689
90
            return findInternal(key, return_prev_if_not_found);
690
90
        } else {
691
0
            throw;
692
0
        }
693
90
    }
694
8.77k
}
695
696
NNTreeImpl::iterator
697
NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found)
698
8.80k
{
699
8.80k
    auto first_item = begin();
700
8.80k
    if (!first_item.valid()) {
701
1.25k
        return end();
702
1.25k
    }
703
7.54k
    if (!keyValid(first_item->first)) {
704
0
        error(tree_root, "encountered invalid key in find");
705
0
    }
706
7.54k
    if (!value_valid(first_item->second)) {
707
0
        error(tree_root, "encountered invalid value in find");
708
0
    }
709
7.54k
    if (compareKeys(key, first_item->first) < 0) {
710
        // Before the first key
711
110
        return end();
712
110
    }
713
714
7.43k
    QPDFObjGen::set seen;
715
7.43k
    auto node = tree_root;
716
7.43k
    iterator result(*this);
717
718
10.1k
    while (true) {
719
10.0k
        if (!seen.add(node)) {
720
0
            error(node, "loop detected in find");
721
0
        }
722
723
10.0k
        Array items = node[itemsKey()];
724
10.0k
        size_t nitems = items.size();
725
10.0k
        if (nitems > 1) {
726
7.36k
            int idx = binarySearch(key, items, nitems / 2, return_prev_if_not_found, false);
727
7.36k
            if (idx >= 0) {
728
6.75k
                result.setItemNumber(node, 2 * idx);
729
6.75k
                if (!result.impl.keyValid(result.ivalue.first)) {
730
0
                    error(node, "encountered invalid key in find");
731
0
                }
732
6.75k
                if (!result.impl.value_valid(result.ivalue.second)) {
733
2
                    error(tree_root, "encountered invalid value in find");
734
2
                }
735
6.75k
            }
736
7.36k
            return result;
737
7.36k
        }
738
739
2.72k
        Array kids = node["/Kids"];
740
2.72k
        size_t nkids = kids.size();
741
2.72k
        if (nkids == 0) {
742
5
            error(node, "bad node during find");
743
5
        }
744
2.72k
        int idx = binarySearch(key, kids, nkids, true, true);
745
2.72k
        if (idx == -1) {
746
1
            error(node, "unexpected -1 from binary search of kids; limits may by wrong");
747
1
        }
748
2.72k
        result.addPathElement(node, idx);
749
2.72k
        node = kids[idx];
750
2.72k
    }
751
7.43k
}
752
753
NNTreeImpl::iterator
754
NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
755
254
{
756
254
    auto iter = begin();
757
254
    Array items = iter.node[items_key];
758
254
    if (!items) {
759
0
        error(tree_root, "unable to find a valid items node");
760
0
    }
761
254
    if (!(key && value)) {
762
0
        error(tree_root, "unable to insert null key or value");
763
0
    }
764
254
    if (!value_valid(value)) {
765
0
        error(tree_root, "attempting to insert an invalid value");
766
0
    }
767
254
    items.insert(0, key);
768
254
    items.insert(1, value);
769
254
    iter.setItemNumber(iter.node, 0);
770
254
    iter.resetLimits(iter.node, iter.lastPathElement());
771
254
    iter.split(iter.node, iter.lastPathElement());
772
254
    return iter;
773
254
}
774
775
NNTreeImpl::iterator
776
NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
777
6.88k
{
778
6.88k
    auto iter = find(key, true);
779
6.88k
    if (!iter.valid()) {
780
254
        return insertFirst(key, value);
781
6.63k
    } else if (compareKeys(key, iter->first) == 0) {
782
0
        Array items = iter.node[itemsKey()];
783
0
        items.set(iter.item_number + 1, value);
784
0
        iter.updateIValue();
785
6.63k
    } else {
786
6.63k
        iter.insertAfter(key, value);
787
6.63k
    }
788
6.63k
    return iter;
789
6.88k
}
790
791
bool
792
NNTreeImpl::remove(QPDFObjectHandle const& key, QPDFObjectHandle* value)
793
0
{
794
0
    auto iter = find(key, false);
795
0
    if (!iter.valid()) {
796
0
        return false;
797
0
    }
798
0
    if (value) {
799
0
        *value = iter->second;
800
0
    }
801
0
    iter.remove();
802
0
    return true;
803
0
}
804
805
bool
806
NNTreeImpl::validate(bool a_repair)
807
1.02k
{
808
1.02k
    bool first = true;
809
1.02k
    QPDFObjectHandle last_key;
810
1.02k
    try {
811
1.26k
        for (auto const& [key, value]: *this) {
812
1.26k
            if (!keyValid(key)) {
813
23
                error(tree_root, "invalid key in validate");
814
23
            }
815
1.26k
            if (!value_valid(value)) {
816
140
                error(tree_root, "invalid value in validate");
817
140
            }
818
1.26k
            if (first) {
819
342
                first = false;
820
920
            } else if (last_key && compareKeys(last_key, key) != -1) {
821
211
                error(tree_root, "keys are not sorted in validate");
822
211
            }
823
1.26k
            last_key = key;
824
1.26k
        }
825
1.02k
    } catch (QPDFExc& e) {
826
395
        if (a_repair) {
827
395
            warn(tree_root, std::string("attempting to repair after error: ") + e.what());
828
395
            repair();
829
395
        }
830
395
        return false;
831
395
    }
832
626
    return true;
833
1.02k
}
834
835
class QPDFNameTreeObjectHelper::Members
836
{
837
  public:
838
    Members(
839
        QPDFObjectHandle& oh,
840
        QPDF& q,
841
        std::function<bool(QPDFObjectHandle const&)> value_validator,
842
        bool auto_repair) :
843
1.02k
        impl(q, oh, ::ot_string, value_validator, auto_repair)
844
1.02k
    {
845
1.02k
    }
846
    Members(Members const&) = delete;
847
1.02k
    ~Members() = default;
848
849
    NNTreeImpl impl;
850
};
851
852
// Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer. For this specific
853
// class, see github issue #745.
854
1.02k
QPDFNameTreeObjectHelper::~QPDFNameTreeObjectHelper() = default;
855
856
QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(QPDFObjectHandle oh, QPDF& q, bool auto_repair) :
857
0
    QPDFNameTreeObjectHelper(
858
0
        oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast<bool>(o); }, auto_repair)
859
0
{
860
0
}
861
862
QPDFNameTreeObjectHelper::QPDFNameTreeObjectHelper(
863
    QPDFObjectHandle oh,
864
    QPDF& q,
865
    std::function<bool(QPDFObjectHandle const&)> value_validator,
866
    bool auto_repair) :
867
1.02k
    QPDFObjectHelper(oh),
868
1.02k
    m(std::make_shared<Members>(oh, q, value_validator, auto_repair))
869
1.02k
{
870
1.02k
}
871
872
QPDFNameTreeObjectHelper
873
QPDFNameTreeObjectHelper::newEmpty(QPDF& qpdf, bool auto_repair)
874
0
{
875
0
    return {qpdf.makeIndirectObject("<< /Names [] >>"_qpdf), qpdf, auto_repair};
876
0
}
877
878
QPDFNameTreeObjectHelper::iterator::iterator(std::shared_ptr<NNTreeIterator> const& i) :
879
3.65k
    impl(i)
880
3.65k
{
881
3.65k
}
882
883
bool
884
QPDFNameTreeObjectHelper::iterator::valid() const
885
0
{
886
0
    return impl->valid();
887
0
}
888
889
QPDFNameTreeObjectHelper::iterator&
890
QPDFNameTreeObjectHelper::iterator::operator++()
891
0
{
892
0
    ++(*impl);
893
0
    updateIValue();
894
0
    return *this;
895
0
}
896
897
QPDFNameTreeObjectHelper::iterator&
898
QPDFNameTreeObjectHelper::iterator::operator--()
899
0
{
900
0
    --(*impl);
901
0
    updateIValue();
902
0
    return *this;
903
0
}
904
905
void
906
QPDFNameTreeObjectHelper::iterator::updateIValue()
907
120
{
908
120
    if (impl->valid()) {
909
120
        auto p = *impl;
910
120
        ivalue.first = p->first.getUTF8Value();
911
120
        ivalue.second = p->second;
912
120
    } else {
913
0
        ivalue.first = "";
914
0
        ivalue.second = QPDFObjectHandle();
915
0
    }
916
120
}
917
918
QPDFNameTreeObjectHelper::iterator::reference
919
QPDFNameTreeObjectHelper::iterator::operator*()
920
0
{
921
0
    updateIValue();
922
0
    return ivalue;
923
0
}
924
925
QPDFNameTreeObjectHelper::iterator::pointer
926
QPDFNameTreeObjectHelper::iterator::operator->()
927
120
{
928
120
    updateIValue();
929
120
    return &ivalue;
930
120
}
931
932
bool
933
QPDFNameTreeObjectHelper::iterator::operator==(iterator const& other) const
934
1.82k
{
935
1.82k
    return *(impl) == *(other.impl);
936
1.82k
}
937
938
void
939
QPDFNameTreeObjectHelper::iterator::insertAfter(std::string const& key, QPDFObjectHandle value)
940
0
{
941
0
    impl->insertAfter(QPDFObjectHandle::newUnicodeString(key), value);
942
0
    updateIValue();
943
0
}
944
945
void
946
QPDFNameTreeObjectHelper::iterator::remove()
947
0
{
948
0
    impl->remove();
949
0
    updateIValue();
950
0
}
951
952
QPDFNameTreeObjectHelper::iterator
953
QPDFNameTreeObjectHelper::begin() const
954
0
{
955
0
    return {std::make_shared<NNTreeIterator>(m->impl.begin())};
956
0
}
957
958
QPDFNameTreeObjectHelper::iterator
959
QPDFNameTreeObjectHelper::end() const
960
1.82k
{
961
1.82k
    return {std::make_shared<NNTreeIterator>(m->impl.end())};
962
1.82k
}
963
964
QPDFNameTreeObjectHelper::iterator
965
QPDFNameTreeObjectHelper::last() const
966
0
{
967
0
    return {std::make_shared<NNTreeIterator>(m->impl.last())};
968
0
}
969
970
QPDFNameTreeObjectHelper::iterator
971
QPDFNameTreeObjectHelper::find(std::string const& key, bool return_prev_if_not_found)
972
1.89k
{
973
1.89k
    auto i = m->impl.find(QPDFObjectHandle::newUnicodeString(key), return_prev_if_not_found);
974
1.89k
    return {std::make_shared<NNTreeIterator>(i)};
975
1.89k
}
976
977
QPDFNameTreeObjectHelper::iterator
978
QPDFNameTreeObjectHelper::insert(std::string const& key, QPDFObjectHandle value)
979
0
{
980
0
    auto i = m->impl.insert(QPDFObjectHandle::newUnicodeString(key), value);
981
0
    return {std::make_shared<NNTreeIterator>(i)};
982
0
}
983
984
bool
985
QPDFNameTreeObjectHelper::remove(std::string const& key, QPDFObjectHandle* value)
986
0
{
987
0
    return m->impl.remove(QPDFObjectHandle::newUnicodeString(key), value);
988
0
}
989
990
bool
991
QPDFNameTreeObjectHelper::hasName(std::string const& name)
992
0
{
993
0
    auto i = find(name);
994
0
    return (i != end());
995
0
}
996
997
bool
998
QPDFNameTreeObjectHelper::findObject(std::string const& name, QPDFObjectHandle& oh)
999
1.89k
{
1000
1.89k
    auto i = find(name);
1001
1.89k
    if (i == end()) {
1002
1.70k
        return false;
1003
1.70k
    }
1004
182
    oh = i->second;
1005
182
    return true;
1006
1.89k
}
1007
1008
void
1009
QPDFNameTreeObjectHelper::setSplitThreshold(int t)
1010
0
{
1011
0
    m->impl.setSplitThreshold(t);
1012
0
}
1013
1014
std::map<std::string, QPDFObjectHandle>
1015
QPDFNameTreeObjectHelper::getAsMap() const
1016
0
{
1017
0
    std::map<std::string, QPDFObjectHandle> result;
1018
0
    result.insert(begin(), end());
1019
0
    return result;
1020
0
}
1021
1022
bool
1023
QPDFNameTreeObjectHelper::validate(bool repair)
1024
1.02k
{
1025
1.02k
    return m->impl.validate(repair);
1026
1.02k
}
1027
1028
class QPDFNumberTreeObjectHelper::Members
1029
{
1030
    typedef QPDFNumberTreeObjectHelper::numtree_number numtree_number;
1031
1032
  public:
1033
    Members(
1034
        QPDFObjectHandle& oh,
1035
        QPDF& q,
1036
        std::function<bool(QPDFObjectHandle const&)> value_validator,
1037
        bool auto_repair) :
1038
0
        impl(q, oh, ::ot_integer, value_validator, auto_repair)
1039
0
    {
1040
0
    }
1041
    Members(Members const&) = delete;
1042
0
    ~Members() = default;
1043
1044
    NNTreeImpl impl;
1045
};
1046
1047
// Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer. For this specific
1048
// class, see github issue #745.
1049
0
QPDFNumberTreeObjectHelper::~QPDFNumberTreeObjectHelper() = default;
1050
1051
QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(
1052
    QPDFObjectHandle oh, QPDF& q, bool auto_repair) :
1053
0
    QPDFNumberTreeObjectHelper(
1054
0
        oh, q, [](QPDFObjectHandle const& o) -> bool { return static_cast<bool>(o); }, auto_repair)
1055
0
{
1056
0
}
1057
1058
QPDFNumberTreeObjectHelper::QPDFNumberTreeObjectHelper(
1059
    QPDFObjectHandle oh,
1060
    QPDF& q,
1061
    std::function<bool(QPDFObjectHandle const&)> value_validator,
1062
    bool auto_repair) :
1063
0
    QPDFObjectHelper(oh),
1064
0
    m(std::make_shared<Members>(oh, q, value_validator, auto_repair))
1065
0
{
1066
0
}
1067
1068
QPDFNumberTreeObjectHelper
1069
QPDFNumberTreeObjectHelper::newEmpty(QPDF& qpdf, bool auto_repair)
1070
0
{
1071
0
    return {qpdf.makeIndirectObject("<< /Nums [] >>"_qpdf), qpdf, auto_repair};
1072
0
}
1073
1074
QPDFNumberTreeObjectHelper::iterator::iterator(std::shared_ptr<NNTreeIterator> const& i) :
1075
0
    impl(i)
1076
0
{
1077
0
}
1078
1079
bool
1080
QPDFNumberTreeObjectHelper::iterator::valid() const
1081
0
{
1082
0
    return impl->valid();
1083
0
}
1084
1085
QPDFNumberTreeObjectHelper::iterator&
1086
QPDFNumberTreeObjectHelper::iterator::operator++()
1087
0
{
1088
0
    ++(*impl);
1089
0
    updateIValue();
1090
0
    return *this;
1091
0
}
1092
1093
QPDFNumberTreeObjectHelper::iterator&
1094
QPDFNumberTreeObjectHelper::iterator::operator--()
1095
0
{
1096
0
    --(*impl);
1097
0
    updateIValue();
1098
0
    return *this;
1099
0
}
1100
1101
void
1102
QPDFNumberTreeObjectHelper::iterator::updateIValue()
1103
0
{
1104
0
    if (impl->valid()) {
1105
0
        auto p = *impl;
1106
0
        this->ivalue.first = p->first.getIntValue();
1107
0
        this->ivalue.second = p->second;
1108
0
    } else {
1109
0
        this->ivalue.first = 0;
1110
0
        this->ivalue.second = QPDFObjectHandle();
1111
0
    }
1112
0
}
1113
1114
QPDFNumberTreeObjectHelper::iterator::reference
1115
QPDFNumberTreeObjectHelper::iterator::operator*()
1116
0
{
1117
0
    updateIValue();
1118
0
    return this->ivalue;
1119
0
}
1120
1121
QPDFNumberTreeObjectHelper::iterator::pointer
1122
QPDFNumberTreeObjectHelper::iterator::operator->()
1123
0
{
1124
0
    updateIValue();
1125
0
    return &this->ivalue;
1126
0
}
1127
1128
bool
1129
QPDFNumberTreeObjectHelper::iterator::operator==(iterator const& other) const
1130
0
{
1131
0
    return *(impl) == *(other.impl);
1132
0
}
1133
1134
void
1135
QPDFNumberTreeObjectHelper::iterator::insertAfter(numtree_number key, QPDFObjectHandle value)
1136
0
{
1137
0
    impl->insertAfter(QPDFObjectHandle::newInteger(key), value);
1138
0
    updateIValue();
1139
0
}
1140
1141
void
1142
QPDFNumberTreeObjectHelper::iterator::remove()
1143
0
{
1144
0
    impl->remove();
1145
0
    updateIValue();
1146
0
}
1147
1148
QPDFNumberTreeObjectHelper::iterator
1149
QPDFNumberTreeObjectHelper::begin() const
1150
0
{
1151
0
    return {std::make_shared<NNTreeIterator>(m->impl.begin())};
1152
0
}
1153
1154
QPDFNumberTreeObjectHelper::iterator
1155
QPDFNumberTreeObjectHelper::end() const
1156
0
{
1157
0
    return {std::make_shared<NNTreeIterator>(m->impl.end())};
1158
0
}
1159
1160
QPDFNumberTreeObjectHelper::iterator
1161
QPDFNumberTreeObjectHelper::last() const
1162
0
{
1163
0
    return {std::make_shared<NNTreeIterator>(m->impl.last())};
1164
0
}
1165
1166
QPDFNumberTreeObjectHelper::iterator
1167
QPDFNumberTreeObjectHelper::find(numtree_number key, bool return_prev_if_not_found)
1168
0
{
1169
0
    auto i = m->impl.find(QPDFObjectHandle::newInteger(key), return_prev_if_not_found);
1170
0
    return {std::make_shared<NNTreeIterator>(i)};
1171
0
}
1172
1173
QPDFNumberTreeObjectHelper::iterator
1174
QPDFNumberTreeObjectHelper::insert(numtree_number key, QPDFObjectHandle value)
1175
0
{
1176
0
    auto i = m->impl.insert(QPDFObjectHandle::newInteger(key), value);
1177
0
    return {std::make_shared<NNTreeIterator>(i)};
1178
0
}
1179
1180
bool
1181
QPDFNumberTreeObjectHelper::remove(numtree_number key, QPDFObjectHandle* value)
1182
0
{
1183
0
    return m->impl.remove(QPDFObjectHandle::newInteger(key), value);
1184
0
}
1185
1186
QPDFNumberTreeObjectHelper::numtree_number
1187
QPDFNumberTreeObjectHelper::getMin()
1188
0
{
1189
0
    auto i = begin();
1190
0
    if (i == end()) {
1191
0
        return 0;
1192
0
    }
1193
0
    return i->first;
1194
0
}
1195
1196
QPDFNumberTreeObjectHelper::numtree_number
1197
QPDFNumberTreeObjectHelper::getMax()
1198
0
{
1199
0
    auto i = last();
1200
0
    if (i == end()) {
1201
0
        return 0;
1202
0
    }
1203
0
    return i->first;
1204
0
}
1205
1206
bool
1207
QPDFNumberTreeObjectHelper::hasIndex(numtree_number idx)
1208
0
{
1209
0
    auto i = find(idx);
1210
0
    return (i != this->end());
1211
0
}
1212
1213
bool
1214
QPDFNumberTreeObjectHelper::findObject(numtree_number idx, QPDFObjectHandle& oh)
1215
0
{
1216
0
    auto i = find(idx);
1217
0
    if (i == end()) {
1218
0
        return false;
1219
0
    }
1220
0
    oh = i->second;
1221
0
    return true;
1222
0
}
1223
1224
bool
1225
QPDFNumberTreeObjectHelper::findObjectAtOrBelow(
1226
    numtree_number idx, QPDFObjectHandle& oh, numtree_number& offset)
1227
0
{
1228
0
    auto i = find(idx, true);
1229
0
    if (i == end()) {
1230
0
        return false;
1231
0
    }
1232
0
    oh = i->second;
1233
0
    QIntC::range_check_subtract(idx, i->first);
1234
0
    offset = idx - i->first;
1235
0
    return true;
1236
0
}
1237
1238
void
1239
QPDFNumberTreeObjectHelper::setSplitThreshold(int t)
1240
0
{
1241
0
    m->impl.setSplitThreshold(t);
1242
0
}
1243
1244
std::map<QPDFNumberTreeObjectHelper::numtree_number, QPDFObjectHandle>
1245
QPDFNumberTreeObjectHelper::getAsMap() const
1246
0
{
1247
0
    std::map<numtree_number, QPDFObjectHandle> result;
1248
0
    result.insert(begin(), end());
1249
0
    return result;
1250
0
}
1251
1252
bool
1253
QPDFNumberTreeObjectHelper::validate(bool repair)
1254
0
{
1255
0
    return m->impl.validate(repair);
1256
0
}