Coverage Report

Created: 2026-01-09 06:28

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