Coverage Report

Created: 2025-08-28 06:32

/src/qpdf/libqpdf/QPDF_Array.cc
Line
Count
Source (jump to first uncovered line)
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
65.3k
{
16
65.3k
    return static_cast<size_t>(n);
17
65.3k
}
18
19
static int
20
to_i(size_t n)
21
864k
{
22
864k
    return static_cast<int>(n);
23
864k
}
24
25
inline void
26
Array::checkOwnership(QPDFObjectHandle const& item) const
27
128k
{
28
128k
    if (!item) {
29
0
        throw std::logic_error("Attempting to add an uninitialized object to a QPDF_Array.");
30
0
    }
31
128k
    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
128k
}
37
38
QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle>&& v, bool sparse)
39
26.0k
{
40
26.0k
    if (sparse) {
41
409
        sp = std::make_unique<Sparse>();
42
210k
        for (auto& item: v) {
43
210k
            if (item.raw_type_code() != ::ot_null || item.indirect()) {
44
79.3k
                sp->elements[sp->size] = std::move(item);
45
79.3k
            }
46
210k
            ++sp->size;
47
210k
        }
48
25.5k
    } else {
49
25.5k
        elements = std::move(v);
50
25.5k
    }
51
26.0k
}
52
53
QPDF_Array*
54
Array::array() const
55
149k
{
56
149k
    if (auto a = as<QPDF_Array>()) {
57
149k
        return a;
58
149k
    }
59
60
0
    throw std::runtime_error("Expected an array but found a non-array object");
61
0
    return nullptr; // unreachable
62
149k
}
63
64
Array::Array(bool empty) :
65
15.1k
    BaseHandle(empty ? QPDFObject::create<QPDF_Array>() : nullptr)
66
15.1k
{
67
15.1k
}
68
69
Array::Array(std::vector<QPDFObjectHandle> const& items) :
70
0
    BaseHandle(QPDFObject::create<QPDF_Array>(items))
71
0
{
72
0
}
73
74
Array::Array(std::vector<QPDFObjectHandle>&& items) :
75
6.41k
    BaseHandle(QPDFObject::create<QPDF_Array>(std::move(items)))
76
6.41k
{
77
6.41k
}
78
79
Array::iterator
80
Array::begin()
81
117k
{
82
117k
    if (auto a = as<QPDF_Array>()) {
83
66.5k
        if (!a->sp) {
84
66.3k
            return a->elements.begin();
85
66.3k
        }
86
160
        if (!sp_elements) {
87
160
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
88
160
        }
89
160
        return sp_elements->begin();
90
66.5k
    }
91
50.6k
    return {};
92
117k
}
93
94
Array::iterator
95
Array::end()
96
117k
{
97
117k
    if (auto a = as<QPDF_Array>()) {
98
66.5k
        if (!a->sp) {
99
66.3k
            return a->elements.end();
100
66.3k
        }
101
160
        if (!sp_elements) {
102
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
103
0
        }
104
160
        return sp_elements->end();
105
66.5k
    }
106
50.6k
    return {};
107
117k
}
108
109
Array::const_iterator
110
Array::cbegin()
111
0
{
112
0
    if (auto a = as<QPDF_Array>()) {
113
0
        if (!a->sp) {
114
0
            return a->elements.cbegin();
115
0
        }
116
0
        if (!sp_elements) {
117
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
118
0
        }
119
0
        return sp_elements->cbegin();
120
0
    }
121
0
    return {};
122
0
}
123
124
Array::const_iterator
125
Array::cend()
126
0
{
127
0
    if (auto a = as<QPDF_Array>()) {
128
0
        if (!a->sp) {
129
0
            return a->elements.cend();
130
0
        }
131
0
        if (!sp_elements) {
132
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
133
0
        }
134
0
        return sp_elements->cend();
135
0
    }
136
0
    return {};
137
0
}
138
139
Array::const_reverse_iterator
140
Array::crbegin()
141
0
{
142
0
    if (auto a = as<QPDF_Array>()) {
143
0
        if (!a->sp) {
144
0
            return a->elements.crbegin();
145
0
        }
146
0
        if (!sp_elements) {
147
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
148
0
        }
149
0
        return sp_elements->crbegin();
150
0
    }
151
0
    return {};
152
0
}
153
154
Array::const_reverse_iterator
155
Array::crend()
156
0
{
157
0
    if (auto a = as<QPDF_Array>()) {
158
0
        if (!a->sp) {
159
0
            return a->elements.crend();
160
0
        }
161
0
        if (!sp_elements) {
162
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
163
0
        }
164
0
        return sp_elements->crend();
165
0
    }
166
0
    return {};
167
0
}
168
169
QPDFObjectHandle
170
Array::null() const
171
0
{
172
0
    return null_oh;
173
0
}
174
175
size_t
176
Array::size() const
177
828k
{
178
828k
    if (auto a = as<QPDF_Array>()) {
179
727k
        return a->sp ? a->sp->size : a->elements.size();
180
727k
    }
181
100k
    return 0;
182
828k
}
183
184
QPDFObjectHandle const&
185
Array::operator[](size_t n) const
186
2.29M
{
187
2.29M
    static const QPDFObjectHandle null_obj;
188
2.29M
    auto a = as<QPDF_Array>();
189
2.29M
    if (!a) {
190
196
        return null_obj;
191
196
    }
192
2.29M
    if (a->sp) {
193
269k
        auto const& iter = a->sp->elements.find(n);
194
269k
        return iter == a->sp->elements.end() ? null_obj : iter->second;
195
269k
    }
196
2.02M
    return n >= a->elements.size() ? null_obj : a->elements[n];
197
2.29M
}
198
199
QPDFObjectHandle const&
200
Array::operator[](int n) const
201
1.78M
{
202
1.78M
    static const QPDFObjectHandle null_obj;
203
1.78M
    if (n < 0) {
204
0
        return null_obj;
205
0
    }
206
1.78M
    return (*this)[static_cast<size_t>(n)];
207
1.78M
}
208
209
QPDFObjectHandle
210
Array::get(size_t n) const
211
33
{
212
33
    if (n >= size()) {
213
0
        return {};
214
0
    }
215
33
    auto a = array();
216
33
    if (!a->sp) {
217
33
        return a->elements[n];
218
33
    }
219
0
    auto const& iter = a->sp->elements.find(n);
220
0
    return iter == a->sp->elements.end() ? null() : iter->second;
221
33
}
222
223
QPDFObjectHandle
224
Array::get(int n) const
225
33
{
226
33
    if (n < 0) {
227
0
        return {};
228
0
    }
229
33
    return get(to_s(n));
230
33
}
231
232
std::vector<QPDFObjectHandle>
233
Array::getAsVector() const
234
7.95k
{
235
7.95k
    auto a = array();
236
7.95k
    if (a->sp) {
237
167
        std::vector<QPDFObjectHandle> v;
238
167
        v.reserve(size());
239
68.9k
        for (auto const& item: a->sp->elements) {
240
68.9k
            v.resize(item.first, null_oh);
241
68.9k
            v.emplace_back(item.second);
242
68.9k
        }
243
167
        v.resize(size(), null_oh);
244
167
        return v;
245
7.78k
    } else {
246
7.78k
        return a->elements;
247
7.78k
    }
248
7.95k
}
249
250
bool
251
Array::set(size_t at, QPDFObjectHandle const& oh)
252
25.1k
{
253
25.1k
    if (at >= size()) {
254
0
        return false;
255
0
    }
256
25.1k
    auto a = array();
257
25.1k
    checkOwnership(oh);
258
25.1k
    if (a->sp) {
259
6
        a->sp->elements[at] = oh;
260
25.1k
    } else {
261
25.1k
        a->elements[at] = oh;
262
25.1k
    }
263
25.1k
    return true;
264
25.1k
}
265
266
bool
267
Array::set(int at, QPDFObjectHandle const& oh)
268
25.1k
{
269
25.1k
    if (at < 0) {
270
0
        return false;
271
0
    }
272
25.1k
    return set(to_s(at), oh);
273
25.1k
}
274
275
void
276
Array::setFromVector(std::vector<QPDFObjectHandle> const& v)
277
0
{
278
0
    auto a = array();
279
0
    a->elements.resize(0);
280
0
    a->elements.reserve(v.size());
281
0
    for (auto const& item: v) {
282
0
        checkOwnership(item);
283
0
        a->elements.emplace_back(item);
284
0
    }
285
0
}
286
287
bool
288
Array::insert(size_t at, QPDFObjectHandle const& item)
289
27.8k
{
290
27.8k
    auto a = array();
291
27.8k
    size_t sz = size();
292
27.8k
    if (at > sz) {
293
0
        return false;
294
0
    }
295
27.8k
    checkOwnership(item);
296
27.8k
    if (at == sz) {
297
        // As special case, also allow insert beyond the end
298
7.92k
        push_back(item);
299
7.92k
        return true;
300
7.92k
    }
301
19.9k
    if (!a->sp) {
302
19.9k
        a->elements.insert(a->elements.cbegin() + to_i(at), item);
303
19.9k
        return true;
304
19.9k
    }
305
0
    auto iter = a->sp->elements.crbegin();
306
0
    while (iter != a->sp->elements.crend()) {
307
0
        auto key = (iter++)->first;
308
0
        if (key >= at) {
309
0
            auto nh = a->sp->elements.extract(key);
310
0
            ++nh.key();
311
0
            a->sp->elements.insert(std::move(nh));
312
0
        } else {
313
0
            break;
314
0
        }
315
0
    }
316
0
    a->sp->elements[at] = item;
317
0
    ++a->sp->size;
318
0
    return true;
319
19.9k
}
320
321
bool
322
Array::insert(int at_i, QPDFObjectHandle const& item)
323
27.8k
{
324
27.8k
    if (at_i < 0) {
325
0
        return false;
326
0
    }
327
27.8k
    return insert(to_s(at_i), item);
328
27.8k
}
329
330
void
331
Array::push_back(QPDFObjectHandle const& item)
332
75.9k
{
333
75.9k
    auto a = array();
334
75.9k
    checkOwnership(item);
335
75.9k
    if (a->sp) {
336
0
        a->sp->elements[(a->sp->size)++] = item;
337
75.9k
    } else {
338
75.9k
        a->elements.emplace_back(item);
339
75.9k
    }
340
75.9k
}
341
342
bool
343
Array::erase(size_t at)
344
12.3k
{
345
12.3k
    auto a = array();
346
12.3k
    if (at >= size()) {
347
0
        return false;
348
0
    }
349
12.3k
    if (!a->sp) {
350
12.3k
        a->elements.erase(a->elements.cbegin() + to_i(at));
351
12.3k
        return true;
352
12.3k
    }
353
0
    auto end = a->sp->elements.end();
354
0
    if (auto iter = a->sp->elements.lower_bound(at); iter != end) {
355
0
        if (iter->first == at) {
356
0
            iter++;
357
0
            a->sp->elements.erase(at);
358
0
        }
359
360
0
        while (iter != end) {
361
0
            auto nh = a->sp->elements.extract(iter++);
362
0
            --nh.key();
363
0
            a->sp->elements.insert(std::move(nh));
364
0
        }
365
0
    }
366
0
    --(a->sp->size);
367
0
    return true;
368
12.3k
}
369
370
bool
371
Array::erase(int at_i)
372
12.3k
{
373
12.3k
    if (at_i < 0) {
374
0
        return false;
375
0
    }
376
12.3k
    return erase(to_s(at_i));
377
12.3k
}
378
379
int
380
QPDFObjectHandle::getArrayNItems() const
381
832k
{
382
832k
    auto s = size();
383
832k
    if (s > 1 || isArray()) {
384
832k
        return to_i(s);
385
832k
    }
386
363
    typeWarning("array", "treating as empty");
387
363
    return 0;
388
832k
}
389
390
QPDFObjectHandle
391
QPDFObjectHandle::getArrayItem(int n) const
392
822k
{
393
822k
    if (auto array = Array(*this)) {
394
822k
        if (auto result = array[n]) {
395
787k
            return result;
396
787k
        }
397
35.0k
        if (n >= 0 && std::cmp_less(n, array.size())) {
398
            // sparse array null
399
35.0k
            return newNull();
400
35.0k
        }
401
0
        objectWarning("returning null for out of bounds array access");
402
403
0
    } else {
404
0
        typeWarning("array", "returning null");
405
0
    }
406
0
    static auto constexpr msg = " -> null returned from invalid array access"sv;
407
0
    return QPDF_Null::create(obj, msg, "");
408
822k
}
409
410
bool
411
QPDFObjectHandle::isRectangle() const
412
40.7k
{
413
40.7k
    Array array(*this);
414
110k
    for (auto const& oh: array) {
415
110k
        if (!oh.isNumber()) {
416
1.89k
            return false;
417
1.89k
        }
418
110k
    }
419
38.8k
    return array.size() == 4;
420
40.7k
}
421
422
bool
423
QPDFObjectHandle::isMatrix() const
424
4.30k
{
425
4.30k
    Array array(*this);
426
4.30k
    for (auto const& oh: array) {
427
1.50k
        if (!oh.isNumber()) {
428
10
            return false;
429
10
        }
430
1.50k
    }
431
4.29k
    return array.size() == 6;
432
4.30k
}
433
434
QPDFObjectHandle::Rectangle
435
QPDFObjectHandle::getArrayAsRectangle() const
436
16.3k
{
437
16.3k
    Array array(*this);
438
16.3k
    if (array.size() != 4) {
439
791
        return {};
440
791
    }
441
15.5k
    std::array<double, 4> items;
442
76.6k
    for (size_t i = 0; i < 4; ++i) {
443
61.4k
        if (!array[i].getValueAsNumber(items[i])) {
444
334
            return {};
445
334
        }
446
61.4k
    }
447
15.1k
    return {
448
15.1k
        std::min(items[0], items[2]),
449
15.1k
        std::min(items[1], items[3]),
450
15.1k
        std::max(items[0], items[2]),
451
15.1k
        std::max(items[1], items[3])};
452
15.5k
}
453
454
QPDFObjectHandle::Matrix
455
QPDFObjectHandle::getArrayAsMatrix() const
456
245
{
457
245
    Array array(*this);
458
245
    if (array.size() != 6) {
459
0
        return {};
460
0
    }
461
245
    std::array<double, 6> items;
462
1.71k
    for (size_t i = 0; i < 6; ++i) {
463
1.47k
        if (!array[i].getValueAsNumber(items[i])) {
464
0
            return {};
465
0
        }
466
1.47k
    }
467
245
    return {items[0], items[1], items[2], items[3], items[4], items[5]};
468
245
}
469
470
std::vector<QPDFObjectHandle>
471
QPDFObjectHandle::getArrayAsVector() const
472
7.79k
{
473
7.79k
    if (auto array = as_array(strict)) {
474
7.79k
        return array.getAsVector();
475
7.79k
    }
476
0
    typeWarning("array", "treating as empty");
477
0
    QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
478
0
    return {};
479
7.79k
}
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
55.5k
{
527
55.5k
    if (auto array = as_array(strict)) {
528
55.5k
        array.push_back(item);
529
55.5k
    } else {
530
0
        typeWarning("array", "ignoring attempt to append item");
531
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
532
0
    }
533
55.5k
}
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
0
{
545
0
    if (auto array = as_array(strict)) {
546
0
        if (!array.erase(at)) {
547
0
            objectWarning("ignoring attempt to erase out of bounds array item");
548
0
            QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
549
0
        }
550
0
    } else {
551
0
        typeWarning("array", "ignoring attempt to erase item");
552
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
553
0
    }
554
0
}
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
1.07M
{
567
1.07M
    switch (resolved_type_code()) {
568
1.06M
    case ::ot_array:
569
1.06M
        return as<QPDF_Array>()->size();
570
0
    case ::ot_uninitialized:
571
0
    case ::ot_reserved:
572
4.64k
    case ::ot_null:
573
4.64k
    case ::ot_destroyed:
574
4.64k
    case ::ot_unresolved:
575
4.64k
    case ::ot_reference:
576
4.64k
        return 0;
577
34
    case ::ot_boolean:
578
650
    case ::ot_integer:
579
1.40k
    case ::ot_real:
580
1.44k
    case ::ot_string:
581
5.31k
    case ::ot_name:
582
5.47k
    case ::ot_dictionary:
583
5.48k
    case ::ot_stream:
584
5.48k
    case ::ot_inlineimage:
585
5.48k
    case ::ot_operator:
586
5.48k
        return 1;
587
0
    default:
588
0
        throw std::logic_error("Unexpected type code in size"); // unreachable
589
0
        return 0;                                               // unreachable
590
1.07M
    }
591
1.07M
}
592
593
QPDFObjectHandle
594
BaseHandle::operator[](size_t n) const
595
443k
{
596
443k
    if (resolved_type_code() == ::ot_array) {
597
443k
        return Array(obj)[n];
598
443k
    }
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
443k
{
608
443k
    if (n < 0) {
609
0
        return {};
610
0
    }
611
443k
    return (*this)[static_cast<size_t>(n)];
612
443k
}