Coverage Report

Created: 2025-07-11 07:03

/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
using namespace std::literals;
6
using namespace qpdf;
7
8
static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull();
9
10
inline void
11
Array::checkOwnership(QPDFObjectHandle const& item) const
12
3.73M
{
13
3.73M
    if (!item) {
14
0
        throw std::logic_error("Attempting to add an uninitialized object to a QPDF_Array.");
15
0
    }
16
3.73M
    if (qpdf() && item.qpdf() && qpdf() != item.qpdf()) {
17
0
        throw std::logic_error(
18
0
            "Attempting to add an object from a different QPDF. Use "
19
0
            "QPDF::copyForeignObject to add objects from another file.");
20
0
    }
21
3.73M
}
22
23
QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle>&& v, bool sparse)
24
31.3k
{
25
31.3k
    if (sparse) {
26
4.08k
        sp = std::make_unique<Sparse>();
27
5.16M
        for (auto& item: v) {
28
5.16M
            if (item.raw_type_code() != ::ot_null || item.indirect()) {
29
4.22M
                sp->elements[sp->size] = std::move(item);
30
4.22M
            }
31
5.16M
            ++sp->size;
32
5.16M
        }
33
27.2k
    } else {
34
27.2k
        elements = std::move(v);
35
27.2k
    }
36
31.3k
}
37
38
QPDF_Array*
39
Array::array() const
40
32.5M
{
41
32.5M
    if (auto a = as<QPDF_Array>()) {
42
32.5M
        return a;
43
32.5M
    }
44
45
0
    throw std::runtime_error("Expected an array but found a non-array object");
46
0
    return nullptr; // unreachable
47
32.5M
}
48
49
Array::iterator
50
Array::begin()
51
3.94M
{
52
3.94M
    if (auto a = as<QPDF_Array>()) {
53
3.91M
        if (!a->sp) {
54
3.90M
            return a->elements.begin();
55
3.90M
        }
56
4.47k
        if (!sp_elements) {
57
4.47k
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
58
4.47k
        }
59
4.47k
        return sp_elements->begin();
60
3.91M
    }
61
35.1k
    return {};
62
3.94M
}
63
64
Array::iterator
65
Array::end()
66
3.94M
{
67
3.94M
    if (auto a = as<QPDF_Array>()) {
68
3.91M
        if (!a->sp) {
69
3.90M
            return a->elements.end();
70
3.90M
        }
71
4.47k
        if (!sp_elements) {
72
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
73
0
        }
74
4.47k
        return sp_elements->end();
75
3.91M
    }
76
35.1k
    return {};
77
3.94M
}
78
79
Array::const_iterator
80
Array::cbegin()
81
0
{
82
0
    if (auto a = as<QPDF_Array>()) {
83
0
        if (!a->sp) {
84
0
            return a->elements.cbegin();
85
0
        }
86
0
        if (!sp_elements) {
87
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
88
0
        }
89
0
        return sp_elements->cbegin();
90
0
    }
91
0
    return {};
92
0
}
93
94
Array::const_iterator
95
Array::cend()
96
0
{
97
0
    if (auto a = as<QPDF_Array>()) {
98
0
        if (!a->sp) {
99
0
            return a->elements.cend();
100
0
        }
101
0
        if (!sp_elements) {
102
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
103
0
        }
104
0
        return sp_elements->cend();
105
0
    }
106
0
    return {};
107
0
}
108
109
Array::const_reverse_iterator
110
Array::crbegin()
111
6.33M
{
112
6.33M
    if (auto a = as<QPDF_Array>()) {
113
252k
        if (!a->sp) {
114
252k
            return a->elements.crbegin();
115
252k
        }
116
307
        if (!sp_elements) {
117
307
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
118
307
        }
119
307
        return sp_elements->crbegin();
120
252k
    }
121
6.08M
    return {};
122
6.33M
}
123
124
Array::const_reverse_iterator
125
Array::crend()
126
6.33M
{
127
6.33M
    if (auto a = as<QPDF_Array>()) {
128
252k
        if (!a->sp) {
129
252k
            return a->elements.crend();
130
252k
        }
131
307
        if (!sp_elements) {
132
0
            sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector());
133
0
        }
134
307
        return sp_elements->crend();
135
252k
    }
136
6.08M
    return {};
137
6.33M
}
138
139
QPDFObjectHandle
140
Array::null() const
141
453k
{
142
453k
    return null_oh;
143
453k
}
144
145
int
146
Array::size() const
147
17.9M
{
148
17.9M
    auto a = array();
149
17.9M
    return a->sp ? a->sp->size : int(a->elements.size());
150
17.9M
}
151
152
std::pair<bool, QPDFObjectHandle>
153
Array::at(int n) const
154
10.5M
{
155
10.5M
    auto a = array();
156
10.5M
    if (n < 0 || n >= size()) {
157
3.69k
        return {false, {}};
158
3.69k
    }
159
10.5M
    if (!a->sp) {
160
10.0M
        return {true, a->elements[size_t(n)]};
161
10.0M
    }
162
579k
    auto const& iter = a->sp->elements.find(n);
163
579k
    return {true, iter == a->sp->elements.end() ? null() : iter->second};
164
10.5M
}
165
166
std::vector<QPDFObjectHandle>
167
Array::getAsVector() const
168
153k
{
169
153k
    auto a = array();
170
153k
    if (a->sp) {
171
5.30k
        std::vector<QPDFObjectHandle> v;
172
5.30k
        v.reserve(size_t(size()));
173
7.37M
        for (auto const& item: a->sp->elements) {
174
7.37M
            v.resize(size_t(item.first), null_oh);
175
7.37M
            v.emplace_back(item.second);
176
7.37M
        }
177
5.30k
        v.resize(size_t(size()), null_oh);
178
5.30k
        return v;
179
147k
    } else {
180
147k
        return a->elements;
181
147k
    }
182
153k
}
183
184
bool
185
Array::setAt(int at, QPDFObjectHandle const& oh)
186
44.7k
{
187
44.7k
    if (at < 0 || at >= size()) {
188
0
        return false;
189
0
    }
190
44.7k
    auto a = array();
191
44.7k
    checkOwnership(oh);
192
44.7k
    if (a->sp) {
193
23
        a->sp->elements[at] = oh;
194
44.6k
    } else {
195
44.6k
        a->elements[size_t(at)] = oh;
196
44.6k
    }
197
44.7k
    return true;
198
44.7k
}
199
200
void
201
Array::setFromVector(std::vector<QPDFObjectHandle> const& v)
202
0
{
203
0
    auto a = array();
204
0
    a->elements.resize(0);
205
0
    a->elements.reserve(v.size());
206
0
    for (auto const& item: v) {
207
0
        checkOwnership(item);
208
0
        a->elements.emplace_back(item);
209
0
    }
210
0
}
211
212
bool
213
Array::insert(int at, QPDFObjectHandle const& item)
214
119k
{
215
119k
    auto a = array();
216
119k
    int sz = size();
217
119k
    if (at < 0 || at > sz) {
218
        // As special case, also allow insert beyond the end
219
0
        return false;
220
119k
    } else if (at == sz) {
221
33.8k
        push_back(item);
222
85.9k
    } else {
223
85.9k
        checkOwnership(item);
224
85.9k
        if (a->sp) {
225
0
            auto iter = a->sp->elements.crbegin();
226
0
            while (iter != a->sp->elements.crend()) {
227
0
                auto key = (iter++)->first;
228
0
                if (key >= at) {
229
0
                    auto nh = a->sp->elements.extract(key);
230
0
                    ++nh.key();
231
0
                    a->sp->elements.insert(std::move(nh));
232
0
                } else {
233
0
                    break;
234
0
                }
235
0
            }
236
0
            a->sp->elements[at] = item.getObj();
237
0
            ++a->sp->size;
238
85.9k
        } else {
239
85.9k
            a->elements.insert(a->elements.cbegin() + at, item.getObj());
240
85.9k
        }
241
85.9k
    }
242
119k
    return true;
243
119k
}
244
245
void
246
Array::push_back(QPDFObjectHandle const& item)
247
3.60M
{
248
3.60M
    auto a = array();
249
3.60M
    checkOwnership(item);
250
3.60M
    if (a->sp) {
251
651
        a->sp->elements[(a->sp->size)++] = item;
252
3.60M
    } else {
253
3.60M
        a->elements.emplace_back(item);
254
3.60M
    }
255
3.60M
}
256
257
bool
258
Array::erase(int at)
259
56.3k
{
260
56.3k
    auto a = array();
261
56.3k
    if (at < 0 || at >= size()) {
262
41
        return false;
263
41
    }
264
56.2k
    if (a->sp) {
265
330
        auto end = a->sp->elements.end();
266
330
        if (auto iter = a->sp->elements.lower_bound(at); iter != end) {
267
328
            if (iter->first == at) {
268
323
                iter++;
269
323
                a->sp->elements.erase(at);
270
323
            }
271
272
116k
            while (iter != end) {
273
115k
                auto nh = a->sp->elements.extract(iter++);
274
115k
                --nh.key();
275
115k
                a->sp->elements.insert(std::move(nh));
276
115k
            }
277
328
        }
278
330
        --(a->sp->size);
279
55.9k
    } else {
280
55.9k
        a->elements.erase(a->elements.cbegin() + at);
281
55.9k
    }
282
56.2k
    return true;
283
56.3k
}
284
285
int
286
QPDFObjectHandle::getArrayNItems() const
287
6.67M
{
288
6.67M
    if (auto array = as_array(strict)) {
289
6.66M
        return array.size();
290
6.66M
    }
291
9.36k
    typeWarning("array", "treating as empty");
292
9.36k
    QTC::TC("qpdf", "QPDFObjectHandle array treating as empty");
293
9.36k
    return 0;
294
6.67M
}
295
296
QPDFObjectHandle
297
QPDFObjectHandle::getArrayItem(int n) const
298
8.67M
{
299
8.67M
    if (auto array = as_array(strict)) {
300
8.67M
        if (auto const [success, oh] = array.at(n); success) {
301
8.67M
            return oh;
302
8.67M
        } else {
303
37
            objectWarning("returning null for out of bounds array access");
304
37
            QTC::TC("qpdf", "QPDFObjectHandle array bounds");
305
37
        }
306
8.67M
    } else {
307
214
        typeWarning("array", "returning null");
308
214
        QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
309
214
    }
310
251
    static auto constexpr msg = " -> null returned from invalid array access"sv;
311
251
    return QPDF_Null::create(obj, msg, "");
312
8.67M
}
313
314
bool
315
QPDFObjectHandle::isRectangle() const
316
508k
{
317
508k
    if (auto array = as_array(strict)) {
318
1.53M
        for (int i = 0; i < 4; ++i) {
319
1.23M
            if (auto item = array.at(i).second; !item.isNumber()) {
320
16.0k
                return false;
321
16.0k
            }
322
1.23M
        }
323
298k
        return array.size() == 4;
324
314k
    }
325
194k
    return false;
326
508k
}
327
328
bool
329
QPDFObjectHandle::isMatrix() const
330
37.9k
{
331
37.9k
    if (auto array = as_array(strict)) {
332
7.28k
        for (int i = 0; i < 6; ++i) {
333
6.57k
            if (auto item = array.at(i).second; !item.isNumber()) {
334
519
                return false;
335
519
            }
336
6.57k
        }
337
716
        return array.size() == 6;
338
1.23k
    }
339
36.7k
    return false;
340
37.9k
}
341
342
QPDFObjectHandle::Rectangle
343
QPDFObjectHandle::getArrayAsRectangle() const
344
161k
{
345
161k
    if (auto array = as_array(strict)) {
346
159k
        if (array.size() != 4) {
347
1.27k
            return {};
348
1.27k
        }
349
158k
        double items[4];
350
788k
        for (int i = 0; i < 4; ++i) {
351
631k
            if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) {
352
637
                return {};
353
637
            }
354
631k
        }
355
157k
        return {
356
157k
            std::min(items[0], items[2]),
357
157k
            std::min(items[1], items[3]),
358
157k
            std::max(items[0], items[2]),
359
157k
            std::max(items[1], items[3])};
360
158k
    }
361
2.49k
    return {};
362
161k
}
363
364
QPDFObjectHandle::Matrix
365
QPDFObjectHandle::getArrayAsMatrix() const
366
716
{
367
716
    if (auto array = as_array(strict)) {
368
716
        if (array.size() != 6) {
369
0
            return {};
370
0
        }
371
716
        double items[6];
372
5.01k
        for (int i = 0; i < 6; ++i) {
373
4.29k
            if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) {
374
0
                return {};
375
0
            }
376
4.29k
        }
377
716
        return {items[0], items[1], items[2], items[3], items[4], items[5]};
378
716
    }
379
0
    return {};
380
716
}
381
382
std::vector<QPDFObjectHandle>
383
QPDFObjectHandle::getArrayAsVector() const
384
148k
{
385
148k
    if (auto array = as_array(strict)) {
386
148k
        return array.getAsVector();
387
148k
    }
388
0
    typeWarning("array", "treating as empty");
389
0
    QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
390
0
    return {};
391
148k
}
392
393
void
394
QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
395
44.7k
{
396
44.7k
    if (auto array = as_array(strict)) {
397
44.7k
        if (!array.setAt(n, item)) {
398
0
            objectWarning("ignoring attempt to set out of bounds array item");
399
0
            QTC::TC("qpdf", "QPDFObjectHandle set array bounds");
400
0
        }
401
44.7k
    } else {
402
0
        typeWarning("array", "ignoring attempt to set item");
403
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item");
404
0
    }
405
44.7k
}
406
void
407
QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
408
0
{
409
0
    if (auto array = as_array(strict)) {
410
0
        array.setFromVector(items);
411
0
    } else {
412
0
        typeWarning("array", "ignoring attempt to replace items");
413
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items");
414
0
    }
415
0
}
416
417
void
418
QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
419
119k
{
420
119k
    if (auto array = as_array(strict)) {
421
119k
        if (!array.insert(at, item)) {
422
0
            objectWarning("ignoring attempt to insert out of bounds array item");
423
0
            QTC::TC("qpdf", "QPDFObjectHandle insert array bounds");
424
0
        }
425
119k
    } else {
426
0
        typeWarning("array", "ignoring attempt to insert item");
427
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
428
0
    }
429
119k
}
430
431
QPDFObjectHandle
432
QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item)
433
0
{
434
0
    insertItem(at, item);
435
0
    return item;
436
0
}
437
438
void
439
QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
440
3.56M
{
441
3.56M
    if (auto array = as_array(strict)) {
442
3.56M
        array.push_back(item);
443
3.56M
    } else {
444
0
        typeWarning("array", "ignoring attempt to append item");
445
0
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
446
0
    }
447
3.56M
}
448
449
QPDFObjectHandle
450
QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item)
451
0
{
452
0
    appendItem(item);
453
0
    return item;
454
0
}
455
456
void
457
QPDFObjectHandle::eraseItem(int at)
458
57.5k
{
459
57.5k
    if (auto array = as_array(strict)) {
460
56.3k
        if (!array.erase(at)) {
461
41
            objectWarning("ignoring attempt to erase out of bounds array item");
462
41
            QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
463
41
        }
464
56.3k
    } else {
465
1.26k
        typeWarning("array", "ignoring attempt to erase item");
466
1.26k
        QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
467
1.26k
    }
468
57.5k
}
469
470
QPDFObjectHandle
471
QPDFObjectHandle::eraseItemAndGetOld(int at)
472
0
{
473
0
    auto array = as_array(strict);
474
0
    auto result = (array && at < array.size() && at >= 0) ? array.at(at).second : newNull();
475
0
    eraseItem(at);
476
0
    return result;
477
0
}