Coverage Report

Created: 2025-10-12 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDF_Array.cc
Line
Count
Source
1
#include <qpdf/QPDFObjectHandle_private.hh>
2
3
#include <qpdf/QTC.hh>
4
5
#include <array>
6
#include <utility>
7
8
using namespace std::literals;
9
using namespace qpdf;
10
11
static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull();
12
13
static size_t
14
to_s(int n)
15
64.8k
{
16
64.8k
    return static_cast<size_t>(n);
17
64.8k
}
18
19
static int
20
to_i(size_t n)
21
2.35M
{
22
2.35M
    return static_cast<int>(n);
23
2.35M
}
24
25
inline void
26
Array::checkOwnership(QPDFObjectHandle const& item) const
27
2.36M
{
28
2.36M
    if (!item) {
29
0
        throw std::logic_error("Attempting to add an uninitialized object to a QPDF_Array.");
30
0
    }
31
2.36M
    if (qpdf() && item.qpdf() && qpdf() != item.qpdf()) {
32
0
        throw std::logic_error(
33
0
            "Attempting to add an object from a different QPDF. Use "
34
0
            "QPDF::copyForeignObject to add objects from another file.");
35
0
    }
36
2.36M
}
37
38
QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle>&& v, bool sparse)
39
58.8k
{
40
58.8k
    if (sparse) {
41
4.72k
        sp = std::make_unique<Sparse>();
42
5.90M
        for (auto& item: v) {
43
5.90M
            if (item.raw_type_code() != ::ot_null || item.indirect()) {
44
5.13M
                sp->elements[sp->size] = std::move(item);
45
5.13M
            }
46
5.90M
            ++sp->size;
47
5.90M
        }
48
54.0k
    } else {
49
54.0k
        elements = std::move(v);
50
54.0k
    }
51
58.8k
}
52
53
QPDF_Array*
54
Array::array() const
55
2.54M
{
56
2.54M
    if (auto a = as<QPDF_Array>()) {
57
2.54M
        return a;
58
2.54M
    }
59
60
0
    throw std::runtime_error("Expected an array but found a non-array object");
61
0
    return nullptr; // unreachable
62
2.54M
}
63
64
Array::Array(std::vector<QPDFObjectHandle> const& items) :
65
0
    BaseHandle(QPDFObject::create<QPDF_Array>(items))
66
0
{
67
0
}
68
69
Array::Array(std::vector<QPDFObjectHandle>&& items) :
70
16.3k
    BaseHandle(QPDFObject::create<QPDF_Array>(std::move(items)))
71
16.3k
{
72
16.3k
}
73
74
Array
75
Array::empty()
76
2.67k
{
77
2.67k
    return Array(std::vector<QPDFObjectHandle>());
78
2.67k
}
79
80
Array::iterator
81
Array::begin()
82
3.84M
{
83
3.84M
    if (auto a = as<QPDF_Array>()) {
84
3.58M
        if (!a->sp) {
85
3.57M
            return a->elements.begin();
86
3.57M
        }
87
5.86k
        if (!sp_elements) {
88
5.86k
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
89
5.86k
        }
90
5.86k
        return sp_elements->begin();
91
3.58M
    }
92
262k
    return {};
93
3.84M
}
94
95
Array::iterator
96
Array::end()
97
3.84M
{
98
3.84M
    if (auto a = as<QPDF_Array>()) {
99
3.58M
        if (!a->sp) {
100
3.57M
            return a->elements.end();
101
3.57M
        }
102
5.86k
        if (!sp_elements) {
103
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
104
0
        }
105
5.86k
        return sp_elements->end();
106
3.58M
    }
107
262k
    return {};
108
3.84M
}
109
110
Array::const_iterator
111
Array::cbegin()
112
0
{
113
0
    if (auto a = as<QPDF_Array>()) {
114
0
        if (!a->sp) {
115
0
            return a->elements.cbegin();
116
0
        }
117
0
        if (!sp_elements) {
118
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
119
0
        }
120
0
        return sp_elements->cbegin();
121
0
    }
122
0
    return {};
123
0
}
124
125
Array::const_iterator
126
Array::cend()
127
0
{
128
0
    if (auto a = as<QPDF_Array>()) {
129
0
        if (!a->sp) {
130
0
            return a->elements.cend();
131
0
        }
132
0
        if (!sp_elements) {
133
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
134
0
        }
135
0
        return sp_elements->cend();
136
0
    }
137
0
    return {};
138
0
}
139
140
Array::const_reverse_iterator
141
Array::crbegin()
142
8.46M
{
143
8.46M
    if (auto a = as<QPDF_Array>()) {
144
215k
        if (!a->sp) {
145
214k
            return a->elements.crbegin();
146
214k
        }
147
391
        if (!sp_elements) {
148
391
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
149
391
        }
150
391
        return sp_elements->crbegin();
151
215k
    }
152
8.24M
    return {};
153
8.46M
}
154
155
Array::const_reverse_iterator
156
Array::crend()
157
8.46M
{
158
8.46M
    if (auto a = as<QPDF_Array>()) {
159
215k
        if (!a->sp) {
160
214k
            return a->elements.crend();
161
214k
        }
162
391
        if (!sp_elements) {
163
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
164
0
        }
165
391
        return sp_elements->crend();
166
215k
    }
167
8.24M
    return {};
168
8.46M
}
169
170
QPDFObjectHandle
171
Array::null() const
172
0
{
173
0
    return null_oh;
174
0
}
175
176
size_t
177
Array::size() const
178
1.06M
{
179
1.06M
    if (auto a = as<QPDF_Array>()) {
180
811k
        return a->sp ? a->sp->size : a->elements.size();
181
811k
    }
182
255k
    return 0;
183
1.06M
}
184
185
QPDFObjectHandle const&
186
Array::operator[](size_t n) const
187
3.54M
{
188
3.54M
    static const QPDFObjectHandle null_obj;
189
3.54M
    auto a = as<QPDF_Array>();
190
3.54M
    if (!a) {
191
3.63k
        return null_obj;
192
3.63k
    }
193
3.54M
    if (a->sp) {
194
268k
        auto const& iter = a->sp->elements.find(n);
195
268k
        return iter == a->sp->elements.end() ? null_obj : iter->second;
196
268k
    }
197
3.27M
    return n >= a->elements.size() ? null_obj : a->elements[n];
198
3.54M
}
199
200
QPDFObjectHandle const&
201
Array::operator[](int n) const
202
3.39M
{
203
3.39M
    static const QPDFObjectHandle null_obj;
204
3.39M
    if (n < 0) {
205
6.46k
        return null_obj;
206
6.46k
    }
207
3.38M
    return (*this)[static_cast<size_t>(n)];
208
3.39M
}
209
210
QPDFObjectHandle
211
Array::get(size_t n) const
212
4.23k
{
213
4.23k
    if (n >= size()) {
214
0
        return {};
215
0
    }
216
4.23k
    auto a = array();
217
4.23k
    if (!a->sp) {
218
4.23k
        return a->elements[n];
219
4.23k
    }
220
0
    auto const& iter = a->sp->elements.find(n);
221
0
    return iter == a->sp->elements.end() ? null() : iter->second;
222
4.23k
}
223
224
QPDFObjectHandle
225
Array::get(int n) const
226
4.23k
{
227
4.23k
    if (n < 0) {
228
0
        return {};
229
0
    }
230
4.23k
    return get(to_s(n));
231
4.23k
}
232
233
std::vector<QPDFObjectHandle>
234
Array::getAsVector() const
235
154k
{
236
154k
    auto a = array();
237
154k
    if (a->sp) {
238
6.87k
        std::vector<QPDFObjectHandle> v;
239
6.87k
        v.reserve(size());
240
15.2M
        for (auto const& item: a->sp->elements) {
241
15.2M
            v.resize(item.first, null_oh);
242
15.2M
            v.emplace_back(item.second);
243
15.2M
        }
244
6.87k
        v.resize(size(), null_oh);
245
6.87k
        return v;
246
147k
    } else {
247
147k
        return a->elements;
248
147k
    }
249
154k
}
250
251
bool
252
Array::set(size_t at, QPDFObjectHandle const& oh)
253
7
{
254
7
    if (at >= size()) {
255
0
        return false;
256
0
    }
257
7
    auto a = array();
258
7
    checkOwnership(oh);
259
7
    if (a->sp) {
260
0
        a->sp->elements[at] = oh;
261
7
    } else {
262
7
        a->elements[at] = oh;
263
7
    }
264
7
    return true;
265
7
}
266
267
bool
268
Array::set(int at, QPDFObjectHandle const& oh)
269
7
{
270
7
    if (at < 0) {
271
0
        return false;
272
0
    }
273
7
    return set(to_s(at), oh);
274
7
}
275
276
void
277
Array::setFromVector(std::vector<QPDFObjectHandle> const& v)
278
0
{
279
0
    auto a = array();
280
0
    a->elements.resize(0);
281
0
    a->elements.reserve(v.size());
282
0
    for (auto const& item: v) {
283
0
        checkOwnership(item);
284
0
        a->elements.emplace_back(item);
285
0
    }
286
0
}
287
288
bool
289
Array::insert(size_t at, QPDFObjectHandle const& item)
290
39.4k
{
291
39.4k
    auto a = array();
292
39.4k
    size_t sz = size();
293
39.4k
    if (at > sz) {
294
0
        return false;
295
0
    }
296
39.4k
    checkOwnership(item);
297
39.4k
    if (at == sz) {
298
        // As special case, also allow insert beyond the end
299
39.4k
        push_back(item);
300
39.4k
        return true;
301
39.4k
    }
302
0
    if (!a->sp) {
303
0
        a->elements.insert(a->elements.cbegin() + to_i(at), item);
304
0
        return true;
305
0
    }
306
0
    auto iter = a->sp->elements.crbegin();
307
0
    while (iter != a->sp->elements.crend()) {
308
0
        auto key = (iter++)->first;
309
0
        if (key >= at) {
310
0
            auto nh = a->sp->elements.extract(key);
311
0
            ++nh.key();
312
0
            a->sp->elements.insert(std::move(nh));
313
0
        } else {
314
0
            break;
315
0
        }
316
0
    }
317
0
    a->sp->elements[at] = item;
318
0
    ++a->sp->size;
319
0
    return true;
320
0
}
321
322
bool
323
Array::insert(int at_i, QPDFObjectHandle const& item)
324
39.4k
{
325
39.4k
    if (at_i < 0) {
326
0
        return false;
327
0
    }
328
39.4k
    return insert(to_s(at_i), item);
329
39.4k
}
330
331
void
332
Array::push_back(QPDFObjectHandle const& item)
333
2.32M
{
334
2.32M
    auto a = array();
335
2.32M
    checkOwnership(item);
336
2.32M
    if (a->sp) {
337
265
        a->sp->elements[(a->sp->size)++] = item;
338
2.32M
    } else {
339
2.32M
        a->elements.emplace_back(item);
340
2.32M
    }
341
2.32M
}
342
343
bool
344
Array::erase(size_t at)
345
21.1k
{
346
21.1k
    auto a = array();
347
21.1k
    if (at >= size()) {
348
83
        return false;
349
83
    }
350
21.0k
    if (!a->sp) {
351
20.6k
        a->elements.erase(a->elements.cbegin() + to_i(at));
352
20.6k
        return true;
353
20.6k
    }
354
452
    auto end = a->sp->elements.end();
355
452
    if (auto iter = a->sp->elements.lower_bound(at); iter != end) {
356
441
        if (iter->first == at) {
357
411
            iter++;
358
411
            a->sp->elements.erase(at);
359
411
        }
360
361
116k
        while (iter != end) {
362
115k
            auto nh = a->sp->elements.extract(iter++);
363
115k
            --nh.key();
364
115k
            a->sp->elements.insert(std::move(nh));
365
115k
        }
366
441
    }
367
452
    --(a->sp->size);
368
452
    return true;
369
21.0k
}
370
371
bool
372
Array::erase(int at_i)
373
21.1k
{
374
21.1k
    if (at_i < 0) {
375
0
        return false;
376
0
    }
377
21.1k
    return erase(to_s(at_i));
378
21.1k
}
379
380
int
381
QPDFObjectHandle::getArrayNItems() const
382
2.35M
{
383
2.35M
    auto s = size();
384
2.35M
    if (s > 1 || isArray()) {
385
2.33M
        return to_i(s);
386
2.33M
    }
387
14.1k
    typeWarning("array", "treating as empty");
388
14.1k
    return 0;
389
2.35M
}
390
391
QPDFObjectHandle
392
QPDFObjectHandle::getArrayItem(int n) const
393
2.38M
{
394
2.38M
    if (auto array = Array(*this)) {
395
2.38M
        if (auto result = array[n]) {
396
2.18M
            return result;
397
2.18M
        }
398
204k
        if (n >= 0 && std::cmp_less(n, array.size())) {
399
            // sparse array null
400
204k
            return newNull();
401
204k
        }
402
77
        objectWarning("returning null for out of bounds array access");
403
269
    } else {
404
269
        typeWarning("array", "returning null");
405
269
    }
406
346
    static auto constexpr msg = " -> null returned from invalid array access"sv;
407
346
    return QPDF_Null::create(obj, msg, "");
408
2.38M
}
409
410
bool
411
QPDFObjectHandle::isRectangle() const
412
308k
{
413
308k
    Array array(*this);
414
497k
    for (auto const& oh: array) {
415
497k
        if (!oh.isNumber()) {
416
7.42k
            return false;
417
7.42k
        }
418
497k
    }
419
301k
    return array.size() == 4;
420
308k
}
421
422
bool
423
QPDFObjectHandle::isMatrix() const
424
11.1k
{
425
11.1k
    Array array(*this);
426
11.1k
    for (auto const& oh: array) {
427
3.53k
        if (!oh.isNumber()) {
428
38
            return false;
429
38
        }
430
3.53k
    }
431
11.1k
    return array.size() == 6;
432
11.1k
}
433
434
QPDFObjectHandle::Rectangle
435
QPDFObjectHandle::getArrayAsRectangle() const
436
38.0k
{
437
38.0k
    Array array(*this);
438
38.0k
    if (array.size() != 4) {
439
2.14k
        return {};
440
2.14k
    }
441
35.8k
    std::array<double, 4> items;
442
178k
    for (size_t i = 0; i < 4; ++i) {
443
142k
        if (!array[i].getValueAsNumber(items[i])) {
444
437
            return {};
445
437
        }
446
142k
    }
447
35.4k
    return {
448
35.4k
        std::min(items[0], items[2]),
449
35.4k
        std::min(items[1], items[3]),
450
35.4k
        std::max(items[0], items[2]),
451
35.4k
        std::max(items[1], items[3])};
452
35.8k
}
453
454
QPDFObjectHandle::Matrix
455
QPDFObjectHandle::getArrayAsMatrix() const
456
554
{
457
554
    Array array(*this);
458
554
    if (array.size() != 6) {
459
0
        return {};
460
0
    }
461
554
    std::array<double, 6> items;
462
3.87k
    for (size_t i = 0; i < 6; ++i) {
463
3.32k
        if (!array[i].getValueAsNumber(items[i])) {
464
0
            return {};
465
0
        }
466
3.32k
    }
467
554
    return {items[0], items[1], items[2], items[3], items[4], items[5]};
468
554
}
469
470
std::vector<QPDFObjectHandle>
471
QPDFObjectHandle::getArrayAsVector() const
472
148k
{
473
148k
    if (auto array = as_array(strict)) {
474
148k
        return array.getAsVector();
475
148k
    }
476
0
    typeWarning("array", "treating as empty");
477
0
    QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
478
0
    return {};
479
148k
}
480
481
void
482
QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
483
0
{
484
0
    if (auto array = as_array(strict)) {
485
0
        if (!array.set(n, item)) {
486
0
            objectWarning("ignoring attempt to set out of bounds array item");
487
0
        }
488
0
    } else {
489
0
        typeWarning("array", "ignoring attempt to set item");
490
0
    }
491
0
}
492
void
493
QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
494
0
{
495
0
    if (auto array = as_array(strict)) {
496
0
        array.setFromVector(items);
497
0
    } else {
498
0
        typeWarning("array", "ignoring attempt to replace items");
499
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items");
500
0
    }
501
0
}
502
503
void
504
QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
505
0
{
506
0
    if (auto array = as_array(strict)) {
507
0
        if (!array.insert(at, item)) {
508
0
            objectWarning("ignoring attempt to insert out of bounds array item");
509
0
            QTC::TC("qpdf", "QPDFObjectHandle insert array bounds");
510
0
        }
511
0
    } else {
512
0
        typeWarning("array", "ignoring attempt to insert item");
513
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
514
0
    }
515
0
}
516
517
QPDFObjectHandle
518
QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item)
519
0
{
520
0
    insertItem(at, item);
521
0
    return item;
522
0
}
523
524
void
525
QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
526
2.27M
{
527
2.27M
    if (auto array = as_array(strict)) {
528
2.27M
        array.push_back(item);
529
2.27M
    } else {
530
0
        typeWarning("array", "ignoring attempt to append item");
531
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
532
0
    }
533
2.27M
}
534
535
QPDFObjectHandle
536
QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item)
537
0
{
538
0
    appendItem(item);
539
0
    return item;
540
0
}
541
542
void
543
QPDFObjectHandle::eraseItem(int at)
544
4.32k
{
545
4.32k
    if (auto array = as_array(strict)) {
546
2.67k
        if (!array.erase(at)) {
547
83
            objectWarning("ignoring attempt to erase out of bounds array item");
548
83
            QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
549
83
        }
550
2.67k
    } else {
551
1.65k
        typeWarning("array", "ignoring attempt to erase item");
552
1.65k
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
553
1.65k
    }
554
4.32k
}
555
556
QPDFObjectHandle
557
QPDFObjectHandle::eraseItemAndGetOld(int at)
558
0
{
559
0
    auto result = Array(*this)[at];
560
0
    eraseItem(at);
561
0
    return result ? result : newNull();
562
0
}
563
564
size_t
565
BaseHandle::size() const
566
2.74M
{
567
2.74M
    switch (resolved_type_code()) {
568
2.44M
    case ::ot_array:
569
2.44M
        return as<QPDF_Array>()->size();
570
0
    case ::ot_uninitialized:
571
0
    case ::ot_reserved:
572
276k
    case ::ot_null:
573
276k
    case ::ot_destroyed:
574
276k
    case ::ot_unresolved:
575
276k
    case ::ot_reference:
576
276k
        return 0;
577
517
    case ::ot_boolean:
578
4.39k
    case ::ot_integer:
579
5.71k
    case ::ot_real:
580
6.26k
    case ::ot_string:
581
20.6k
    case ::ot_name:
582
30.1k
    case ::ot_dictionary:
583
30.5k
    case ::ot_stream:
584
30.5k
    case ::ot_inlineimage:
585
30.5k
    case ::ot_operator:
586
30.5k
        return 1;
587
0
    default:
588
0
        throw std::logic_error("Unexpected type code in size"); // unreachable
589
0
        return 0;                                               // unreachable
590
2.74M
    }
591
2.74M
}
592
593
QPDFObjectHandle
594
BaseHandle::operator[](size_t n) const
595
5.08k
{
596
5.08k
    if (resolved_type_code() == ::ot_array) {
597
5.08k
        return Array(obj)[n];
598
5.08k
    }
599
0
    if (n < size()) {
600
0
        return *this;
601
0
    }
602
0
    return {};
603
0
}
604
605
QPDFObjectHandle
606
BaseHandle::operator[](int n) const
607
5.08k
{
608
5.08k
    if (n < 0) {
609
0
        return {};
610
0
    }
611
5.08k
    return (*this)[static_cast<size_t>(n)];
612
5.08k
}