Commit db288584617600ba09f5ebbd59bf14fee4d6b6c7

Authored by m-holger
Committed by GitHub
2 parents f5083840 1674b67c

Merge pull request #1522 from m-holger/oh_array

Add private-API subscript operators to `QPDFObjectHandle`
include/qpdf/ObjectHandle.hh
... ... @@ -73,6 +73,10 @@ namespace qpdf
73 73 return size() == 0;
74 74 }
75 75  
  76 + QPDFObjectHandle operator[](size_t n) const;
  77 +
  78 + QPDFObjectHandle operator[](int n) const;
  79 +
76 80 std::shared_ptr<QPDFObject> copy(bool shallow = false) const;
77 81 // Recursively remove association with any QPDF object. This method may only be called
78 82 // during final destruction.
... ...
libqpdf/NNTree.cc
  1 +#include <qpdf/assert_debug.h>
  2 +
1 3 #include <qpdf/NNTree.hh>
2 4  
  5 +#include <qpdf/QPDFObjectHandle_private.hh>
3 6 #include <qpdf/QPDF_private.hh>
4 7 #include <qpdf/QTC.hh>
5 8 #include <qpdf/QUtil.hh>
6 9  
  10 +#include <bit>
7 11 #include <exception>
  12 +#include <utility>
8 13  
9 14 static std::string
10   -get_description(QPDFObjectHandle& node)
  15 +get_description(QPDFObjectHandle const& node)
11 16 {
12 17 std::string result("Name/Number tree node");
13   - if (node.isIndirect()) {
  18 + if (node.indirect()) {
14 19 result += " (object " + std::to_string(node.getObjectID()) + ")";
15 20 }
16 21 return result;
17 22 }
18 23  
19 24 void
20   -NNTreeImpl::warn(QPDFObjectHandle& node, std::string const& msg)
  25 +NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg)
21 26 {
22 27 qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg);
23 28 if (++error_count > 5 && qpdf.reconstructed_xref()) {
... ... @@ -26,7 +31,7 @@ NNTreeImpl::warn(QPDFObjectHandle&amp; node, std::string const&amp; msg)
26 31 }
27 32  
28 33 void
29   -NNTreeImpl::error(QPDFObjectHandle& node, std::string const& msg)
  34 +NNTreeImpl::error(QPDFObjectHandle const& node, std::string const& msg)
30 35 {
31 36 throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg);
32 37 }
... ... @@ -52,25 +57,21 @@ NNTreeIterator::updateIValue(bool allow_invalid)
52 57 // we must call updateIValue as well. These cases are handled, and for good measure, we also
53 58 // call updateIValue in operator* and operator->.
54 59  
55   - bool okay = false;
56   - if (item_number >= 0 && node.isDictionary()) {
57   - auto items = node.getKey(impl.details.itemsKey());
58   - if (item_number + 1 < items.getArrayNItems()) {
59   - okay = true;
60   - ivalue.first = items.getArrayItem(item_number);
61   - ivalue.second = items.getArrayItem(1 + item_number);
62   - } else {
63   - impl.error(node, "update ivalue: items array is too short");
64   - }
65   - }
66   - if (!okay) {
  60 + if (item_number < 0 || !node.isDictionary()) {
67 61 if (!allow_invalid) {
68 62 throw std::logic_error(
69 63 "attempt made to dereference an invalid name/number tree iterator");
70 64 }
71 65 ivalue.first = QPDFObjectHandle();
72 66 ivalue.second = QPDFObjectHandle();
  67 + return;
  68 + }
  69 + auto items = node.getKey(impl.details.itemsKey());
  70 + if (!std::cmp_less(item_number + 1, items.size())) {
  71 + impl.error(node, "update ivalue: items array is too short");
73 72 }
  73 + ivalue.first = items[item_number];
  74 + ivalue.second = items[1 + item_number];
74 75 }
75 76  
76 77 NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_number) :
... ... @@ -82,28 +83,22 @@ NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const&amp; node, int kid_n
82 83 QPDFObjectHandle
83 84 NNTreeIterator::getNextKid(PathElement& pe, bool backward)
84 85 {
85   - QPDFObjectHandle result;
86   - bool found = false;
87   - while (!found) {
  86 + while (true) {
88 87 pe.kid_number += backward ? -1 : 1;
89 88 auto kids = pe.node.getKey("/Kids");
90   - if ((pe.kid_number >= 0) && (pe.kid_number < kids.getArrayNItems())) {
91   - result = kids.getArrayItem(pe.kid_number);
  89 + if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) {
  90 + auto result = kids[pe.kid_number];
92 91 if (result.isDictionary() &&
93 92 (result.hasKey("/Kids") || result.hasKey(impl.details.itemsKey()))) {
94   - found = true;
  93 + return result;
95 94 } else {
96   - QTC::TC("qpdf", "NNTree skip invalid kid");
97 95 impl.warn(
98   - pe.node,
99   - ("skipping over invalid kid at index " + std::to_string(pe.kid_number)));
  96 + pe.node, "skipping over invalid kid at index " + std::to_string(pe.kid_number));
100 97 }
101 98 } else {
102   - result = QPDFObjectHandle::newNull();
103   - found = true;
  99 + return QPDFObjectHandle::newNull();
104 100 }
105 101 }
106   - return result;
107 102 }
108 103  
109 104 bool
... ... @@ -116,21 +111,20 @@ void
116 111 NNTreeIterator::increment(bool backward)
117 112 {
118 113 if (item_number < 0) {
119   - QTC::TC("qpdf", "NNTree increment end()");
120 114 deepen(impl.oh, !backward, true);
121 115 return;
122 116 }
123   - bool found_valid_key = false;
124   - while (valid() && !found_valid_key) {
  117 +
  118 + while (valid()) {
125 119 item_number += backward ? -2 : 2;
126 120 auto items = node.getKey(impl.details.itemsKey());
127   - if (item_number < 0 || item_number >= items.getArrayNItems()) {
  121 + if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
128 122 bool found = false;
129 123 setItemNumber(QPDFObjectHandle(), -1);
130 124 while (!(found || path.empty())) {
131 125 auto& element = path.back();
132 126 auto pe_node = getNextKid(element, backward);
133   - if (pe_node.isNull()) {
  127 + if (pe_node.null()) {
134 128 path.pop_back();
135 129 } else {
136 130 found = deepen(pe_node, !backward, false);
... ... @@ -139,14 +133,12 @@ NNTreeIterator::increment(bool backward)
139 133 }
140 134 if (item_number >= 0) {
141 135 items = node.getKey(impl.details.itemsKey());
142   - if (item_number + 1 >= items.getArrayNItems()) {
143   - QTC::TC("qpdf", "NNTree skip item at end of short items");
  136 + if (std::cmp_greater_equal(item_number + 1, items.size())) {
144 137 impl.warn(node, "items array doesn't have enough elements");
145   - } else if (!impl.details.keyValid(items.getArrayItem(item_number))) {
146   - QTC::TC("qpdf", "NNTree skip invalid key");
  138 + } else if (!impl.details.keyValid(items[item_number])) {
147 139 impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type"));
148 140 } else {
149   - found_valid_key = true;
  141 + return;
150 142 }
151 143 }
152 144 }
... ... @@ -155,35 +147,31 @@ NNTreeIterator::increment(bool backward)
155 147 void
156 148 NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::iterator parent)
157 149 {
158   - bool done = false;
159   - while (!done) {
  150 + while (true) {
160 151 if (parent == path.end()) {
161   - QTC::TC("qpdf", "NNTree remove limits from root");
162 152 a_node.removeKey("/Limits");
163   - done = true;
164 153 break;
165 154 }
166 155 auto kids = a_node.getKey("/Kids");
167   - int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
  156 + size_t nkids = kids.isArray() ? kids.size() : 0;
168 157 auto items = a_node.getKey(impl.details.itemsKey());
169   - int nitems = items.isArray() ? items.getArrayNItems() : 0;
  158 + size_t nitems = items.size();
170 159  
171 160 bool changed = true;
172 161 QPDFObjectHandle first;
173 162 QPDFObjectHandle last;
174 163 if (nitems >= 2) {
175   - first = items.getArrayItem(0);
176   - last = items.getArrayItem((nitems - 1) & ~1);
  164 + first = items[0];
  165 + last = items[(nitems - 1u) & ~1u];
177 166 } else if (nkids > 0) {
178   - auto first_kid = kids.getArrayItem(0);
179   - auto last_kid = kids.getArrayItem(nkids - 1);
  167 + auto first_kid = kids[0];
  168 + auto last_kid = kids[nkids - 1u];
180 169 if (first_kid.isDictionary() && last_kid.isDictionary()) {
181 170 auto first_limits = first_kid.getKey("/Limits");
182 171 auto last_limits = last_kid.getKey("/Limits");
183   - if (first_limits.isArray() && (first_limits.getArrayNItems() >= 2) &&
184   - last_limits.isArray() && (last_limits.getArrayNItems() >= 2)) {
185   - first = first_limits.getArrayItem(0);
186   - last = last_limits.getArrayItem(1);
  172 + if (first_limits.size() >= 2 && last_limits.size() >= 2) {
  173 + first = first_limits[0];
  174 + last = last_limits[1];
187 175 }
188 176 }
189 177 }
... ... @@ -192,13 +180,12 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
192 180 limits.appendItem(first);
193 181 limits.appendItem(last);
194 182 auto olimits = a_node.getKey("/Limits");
195   - if (olimits.isArray() && (olimits.getArrayNItems() == 2)) {
196   - auto ofirst = olimits.getArrayItem(0);
197   - auto olast = olimits.getArrayItem(1);
  183 + if (olimits.size() == 2) {
  184 + auto ofirst = olimits[0];
  185 + auto olast = olimits[1];
198 186 if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) &&
199 187 (impl.details.compareKeys(first, ofirst) == 0) &&
200 188 (impl.details.compareKeys(last, olast) == 0)) {
201   - QTC::TC("qpdf", "NNTree limits didn't change");
202 189 changed = false;
203 190 }
204 191 }
... ... @@ -206,12 +193,11 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
206 193 a_node.replaceKey("/Limits", limits);
207 194 }
208 195 } else {
209   - QTC::TC("qpdf", "NNTree unable to determine limits");
210 196 impl.warn(a_node, "unable to determine limits");
211 197 }
212 198  
213 199 if (!changed || parent == path.begin()) {
214   - done = true;
  200 + break;
215 201 } else {
216 202 a_node = parent->node;
217 203 --parent;
... ... @@ -255,22 +241,20 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
255 241  
256 242 // Find the array we actually need to split, which is either this node's kids or items.
257 243 auto kids = to_split.getKey("/Kids");
258   - int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
  244 + int nkids = kids.isArray() ? static_cast<int>(kids.size()) : 0;
259 245 auto items = to_split.getKey(impl.details.itemsKey());
260   - int nitems = items.isArray() ? items.getArrayNItems() : 0;
  246 + int nitems = items.isArray() ? static_cast<int>(items.size()) : 0;
261 247  
262 248 QPDFObjectHandle first_half;
263 249 int n = 0;
264 250 std::string key;
265 251 int threshold = 0;
266 252 if (nkids > 0) {
267   - QTC::TC("qpdf", "NNTree split kids");
268 253 first_half = kids;
269 254 n = nkids;
270 255 threshold = impl.split_threshold;
271 256 key = "/Kids";
272 257 } else if (nitems > 0) {
273   - QTC::TC("qpdf", "NNTree split items");
274 258 first_half = items;
275 259 n = nitems;
276 260 threshold = 2 * impl.split_threshold;
... ... @@ -283,8 +267,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
283 267 return;
284 268 }
285 269  
286   - bool is_root = (parent == path.end());
287   - bool is_leaf = (nitems > 0);
  270 + bool is_root = parent == path.end();
  271 + bool is_leaf = nitems > 0;
288 272  
289 273 // CURRENT STATE: tree is in original state; iterator is valid and unchanged.
290 274  
... ... @@ -311,10 +295,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
311 295 to_split.removeKey(impl.details.itemsKey());
312 296 to_split.replaceKey("/Kids", new_kids);
313 297 if (is_leaf) {
314   - QTC::TC("qpdf", "NNTree split root + leaf");
315 298 node = first_node;
316 299 } else {
317   - QTC::TC("qpdf", "NNTree split root + !leaf");
318 300 auto next = path.begin();
319 301 next->node = first_node;
320 302 }
... ... @@ -330,8 +312,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
330 312 // array.
331 313 QPDFObjectHandle second_half = QPDFObjectHandle::newArray();
332 314 int start_idx = ((n / 2) & ~1);
333   - while (first_half.getArrayNItems() > start_idx) {
334   - second_half.appendItem(first_half.getArrayItem(start_idx));
  315 + while (std::cmp_greater(first_half.size(), start_idx)) {
  316 + second_half.appendItem(first_half[start_idx]);
335 317 first_half.eraseItem(start_idx);
336 318 }
337 319 resetLimits(to_split, parent);
... ... @@ -357,16 +339,13 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
357 339 if (old_idx >= start_idx) {
358 340 ++parent->kid_number;
359 341 if (is_leaf) {
360   - QTC::TC("qpdf", "NNTree split second half item");
361 342 setItemNumber(second_node, item_number - start_idx);
362 343 } else {
363   - QTC::TC("qpdf", "NNTree split second half kid");
364 344 cur_elem->node = second_node;
365 345 cur_elem->kid_number -= start_idx;
366 346 }
367 347 }
368 348 if (!is_root) {
369   - QTC::TC("qpdf", "NNTree split parent");
370 349 auto next = parent->node;
371 350 resetLimits(next, parent);
372 351 --parent;
... ... @@ -385,7 +364,7 @@ NNTreeIterator::lastPathElement()
385 364 }
386 365  
387 366 void
388   -NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value)
  367 +NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
389 368 {
390 369 if (!valid()) {
391 370 QTC::TC("qpdf", "NNTree insertAfter inserts first");
... ... @@ -395,10 +374,11 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value)
395 374 }
396 375  
397 376 auto items = node.getKey(impl.details.itemsKey());
398   - if (!items.isArray()) {
399   - impl.error(node, "node contains no items array");
400   - }
401   - if (items.getArrayNItems() < item_number + 2) {
  377 +
  378 + if (std::cmp_less(items.size(), item_number + 2)) {
  379 + if (!items.isArray()) {
  380 + impl.error(node, "node contains no items array");
  381 + }
402 382 impl.error(node, "insert: items array is too short");
403 383 }
404 384 items.insertItem(item_number + 2, key);
... ... @@ -417,8 +397,8 @@ NNTreeIterator::remove()
417 397 throw std::logic_error("attempt made to remove an invalid iterator");
418 398 }
419 399 auto items = node.getKey(impl.details.itemsKey());
420   - int nitems = items.getArrayNItems();
421   - if (item_number + 2 > nitems) {
  400 + int nitems = static_cast<int>(items.size());
  401 + if (std::cmp_greater(item_number + 2, nitems)) {
422 402 impl.error(node, "found short items array while removing an item");
423 403 }
424 404  
... ... @@ -432,20 +412,17 @@ NNTreeIterator::remove()
432 412 if (item_number == 0 || item_number == nitems) {
433 413 // We removed either the first or last item of an items array that remains non-empty, so
434 414 // we have to adjust limits.
435   - QTC::TC("qpdf", "NNTree remove reset limits");
436 415 resetLimits(node, lastPathElement());
437 416 }
438 417  
439 418 if (item_number == nitems) {
440 419 // We removed the last item of a non-empty items array, so advance to the successor of
441 420 // the previous item.
442   - QTC::TC("qpdf", "NNTree erased last item");
443 421 item_number -= 2;
444 422 increment(false);
445 423 } else if (item_number < nitems) {
446 424 // We don't have to do anything since the removed item's successor now occupies its
447 425 // former location.
448   - QTC::TC("qpdf", "NNTree erased non-last item");
449 426 updateIValue();
450 427 } else {
451 428 // We already checked to ensure this condition would not happen.
... ... @@ -456,61 +433,51 @@ NNTreeIterator::remove()
456 433  
457 434 if (path.empty()) {
458 435 // Special case: if this is the root node, we can leave it empty.
459   - QTC::TC("qpdf", "NNTree erased all items on leaf/root");
460 436 setItemNumber(impl.oh, -1);
461 437 return;
462 438 }
463 439  
464   - QTC::TC("qpdf", "NNTree items is empty after remove");
465   -
466 440 // We removed the last item from this items array, so we need to remove this node from the
467 441 // parent on up the tree. Then we need to position ourselves at the removed item's successor.
468   - bool done = false;
469   - while (!done) {
  442 + while (true) {
470 443 auto element = lastPathElement();
471 444 auto parent = element;
472 445 --parent;
473 446 auto kids = element->node.getKey("/Kids");
474 447 kids.eraseItem(element->kid_number);
475   - auto nkids = kids.getArrayNItems();
  448 + auto nkids = kids.size();
476 449 if (nkids > 0) {
477 450 // The logic here is similar to the items case.
478   - if ((element->kid_number == 0) || (element->kid_number == nkids)) {
479   - QTC::TC("qpdf", "NNTree erased first or last kid");
  451 + if (element->kid_number == 0 || std::cmp_equal(element->kid_number, nkids)) {
480 452 resetLimits(element->node, parent);
481 453 }
482   - if (element->kid_number == nkids) {
  454 + if (std::cmp_equal(element->kid_number, nkids)) {
483 455 // Move to the successor of the last child of the previous kid.
484   - setItemNumber(QPDFObjectHandle(), -1);
  456 + setItemNumber({}, -1);
485 457 --element->kid_number;
486   - deepen(kids.getArrayItem(element->kid_number), false, true);
  458 + deepen(kids[element->kid_number], false, true);
487 459 if (valid()) {
488 460 increment(false);
489   - if (!valid()) {
490   - QTC::TC("qpdf", "NNTree erased last item in tree");
491   - } else {
492   - QTC::TC("qpdf", "NNTree erased last kid");
493   - }
  461 + QTC::TC("qpdf", "NNTree erased last kid/item in tree", valid() ? 0 : 1);
494 462 }
495 463 } else {
496 464 // Next kid is in deleted kid's position
497   - QTC::TC("qpdf", "NNTree erased non-last kid");
498 465 deepen(kids.getArrayItem(element->kid_number), true, true);
499 466 }
500   - done = true;
501   - } else if (parent == path.end()) {
  467 + return;
  468 + }
  469 +
  470 + if (parent == path.end()) {
502 471 // We erased the very last item. Convert the root to an empty items array.
503   - QTC::TC("qpdf", "NNTree non-flat tree is empty after remove");
504 472 element->node.removeKey("/Kids");
505 473 element->node.replaceKey(impl.details.itemsKey(), QPDFObjectHandle::newArray());
506 474 path.clear();
507 475 setItemNumber(impl.oh, -1);
508   - done = true;
509   - } else {
510   - // Walk up the tree and continue
511   - QTC::TC("qpdf", "NNTree remove walking up tree");
512   - path.pop_back();
  476 + return;
513 477 }
  478 +
  479 + // Walk up the tree and continue
  480 + path.pop_back();
514 481 }
515 482 }
516 483  
... ... @@ -587,74 +554,64 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
587 554 // items. If we succeed, return true; otherwise return false and leave path alone.
588 555  
589 556 auto opath = path;
590   - bool failed = false;
  557 +
  558 + auto fail = [this, &opath](QPDFObjectHandle const& failed_node, std::string const& msg) {
  559 + impl.warn(failed_node, msg);
  560 + path = opath;
  561 + return false;
  562 + };
591 563  
592 564 QPDFObjGen::set seen;
593 565 for (auto const& i: path) {
594 566 seen.add(i.node);
595 567 }
596   - while (!failed) {
  568 + while (true) {
597 569 if (!seen.add(a_node)) {
598   - QTC::TC("qpdf", "NNTree deepen: loop");
599   - impl.warn(a_node, "loop detected while traversing name/number tree");
600   - failed = true;
601   - break;
  570 + return fail(a_node, "loop detected while traversing name/number tree");
602 571 }
603 572  
604 573 if (!a_node.isDictionary()) {
605   - QTC::TC("qpdf", "NNTree node is not a dictionary");
606   - impl.warn(a_node, "non-dictionary node while traversing name/number tree");
607   - failed = true;
608   - break;
  574 + return fail(a_node, "non-dictionary node while traversing name/number tree");
609 575 }
610 576  
611   - auto kids = a_node.getKey("/Kids");
612   - int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
613 577 auto items = a_node.getKey(impl.details.itemsKey());
614   - int nitems = items.isArray() ? items.getArrayNItems() : 0;
615   - if (nitems > 0) {
  578 + int nitems = static_cast<int>(items.size());
  579 + if (nitems > 1) {
616 580 setItemNumber(a_node, first ? 0 : nitems - 2);
617 581 break;
618   - } else if (nkids > 0) {
  582 + }
  583 +
  584 + auto kids = a_node.getKey("/Kids");
  585 + int nkids = kids.isArray() ? static_cast<int>(kids.size()) : 0;
  586 + if (nkids > 0) {
619 587 int kid_number = first ? 0 : nkids - 1;
620 588 addPathElement(a_node, kid_number);
621   - auto next = kids.getArrayItem(kid_number);
  589 + auto next = kids[kid_number];
622 590 if (!next.isIndirect()) {
623 591 if (impl.auto_repair) {
624   - QTC::TC("qpdf", "NNTree fix indirect kid");
625 592 impl.warn(
626 593 a_node,
627   - ("converting kid number " + std::to_string(kid_number) +
628   - " to an indirect object"));
  594 + "converting kid number " + std::to_string(kid_number) +
  595 + " to an indirect object");
629 596 next = impl.qpdf.makeIndirectObject(next);
630 597 kids.setArrayItem(kid_number, next);
631 598 } else {
632   - QTC::TC("qpdf", "NNTree warn indirect kid");
633 599 impl.warn(
634 600 a_node,
635   - ("kid number " + std::to_string(kid_number) +
636   - " is not an indirect object"));
  601 + "kid number " + std::to_string(kid_number) + " is not an indirect object");
637 602 }
638 603 }
639 604 a_node = next;
640 605 } else if (allow_empty && items.isArray()) {
641   - QTC::TC("qpdf", "NNTree deepen found empty");
642 606 setItemNumber(a_node, -1);
643 607 break;
644 608 } else {
645   - QTC::TC("qpdf", "NNTree deepen: invalid node");
646   - impl.warn(
  609 + return fail(
647 610 a_node,
648   - ("name/number tree node has neither non-empty " + impl.details.itemsKey() +
649   - " nor /Kids"));
650   - failed = true;
651   - break;
  611 + "name/number tree node has neither non-empty " + impl.details.itemsKey() +
  612 + " nor /Kids");
652 613 }
653 614 }
654   - if (failed) {
655   - path = opath;
656   - return false;
657   - }
658 615 return true;
659 616 }
660 617  
... ... @@ -696,103 +653,81 @@ NNTreeImpl::last()
696 653 }
697 654  
698 655 int
699   -NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node)
  656 +NNTreeImpl::withinLimits(QPDFObjectHandle const& key, QPDFObjectHandle const& node)
700 657 {
701   - int result = 0;
702 658 auto limits = node.getKey("/Limits");
703   - if (limits.isArray() && (limits.getArrayNItems() >= 2) &&
704   - details.keyValid(limits.getArrayItem(0)) && details.keyValid(limits.getArrayItem(1))) {
705   - if (details.compareKeys(key, limits.getArrayItem(0)) < 0) {
706   - result = -1;
707   - } else if (details.compareKeys(key, limits.getArrayItem(1)) > 0) {
708   - result = 1;
709   - }
710   - } else {
711   - QTC::TC("qpdf", "NNTree missing limits");
  659 + if (!(limits.size() >= 2 && details.keyValid(limits[0]) && details.keyValid(limits[1]))) {
712 660 error(node, "node is missing /Limits");
713 661 }
714   - return result;
  662 + if (details.compareKeys(key, limits[0]) < 0) {
  663 + return -1;
  664 + }
  665 + if (details.compareKeys(key, limits[1]) > 0) {
  666 + return 1;
  667 + }
  668 + return 0;
715 669 }
716 670  
717 671 int
718 672 NNTreeImpl::binarySearch(
719 673 QPDFObjectHandle key,
720 674 QPDFObjectHandle items,
721   - int num_items,
  675 + size_t num_items,
722 676 bool return_prev_if_not_found,
723 677 int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item))
724 678 {
725   - int max_idx = 1;
726   - while (max_idx < num_items) {
727   - max_idx <<= 1;
728   - }
  679 + size_t max_idx = std::bit_ceil(num_items);
729 680  
730   - int step = max_idx / 2;
731   - int checks = max_idx;
  681 + int step = static_cast<int>(max_idx / 2);
  682 + size_t checks = max_idx;
732 683 int idx = step;
733 684 int found_idx = -1;
734   - bool found = false;
735   - bool found_leq = false;
736   - int status = 0;
737 685  
738   - while ((!found) && (checks > 0)) {
739   - if (idx < num_items) {
  686 + while (checks > 0) {
  687 + int status = -1;
  688 + if (std::cmp_less(idx, num_items)) {
740 689 status = (this->*compare)(key, items, idx);
741   - if (status >= 0) {
742   - found_leq = true;
  690 + if (status == 0) {
  691 + return idx;
  692 + }
  693 + if (status > 0) {
743 694 found_idx = idx;
744 695 }
745   - } else {
746   - // consider item to be below anything after the top
747   - status = -1;
748 696 }
  697 + checks >>= 1;
  698 + if (checks > 0) {
  699 + step >>= 1;
  700 + if (step == 0) {
  701 + step = 1;
  702 + }
749 703  
750   - if (status == 0) {
751   - found = true;
752   - } else {
753   - checks >>= 1;
754   - if (checks > 0) {
755   - step >>= 1;
756   - if (step == 0) {
757   - step = 1;
758   - }
759   -
760   - if (status < 0) {
761   - idx -= step;
762   - } else {
763   - idx += step;
764   - }
  704 + if (status < 0) {
  705 + idx -= step;
  706 + } else {
  707 + idx += step;
765 708 }
766 709 }
767 710 }
768 711  
769   - if (found || (found_leq && return_prev_if_not_found)) {
770   - return found_idx;
771   - } else {
772   - return -1;
773   - }
  712 + return return_prev_if_not_found ? found_idx : -1;
774 713 }
775 714  
776 715 int
777 716 NNTreeImpl::compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx)
778 717 {
779   - if (!((items.isArray() && (items.getArrayNItems() > (2 * idx)) &&
780   - details.keyValid(items.getArrayItem(2 * idx))))) {
781   - QTC::TC("qpdf", "NNTree item is wrong type");
  718 + if (!(std::cmp_greater(items.size(), 2 * idx) && details.keyValid(items[2 * idx]))) {
782 719 error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
783 720 }
784   - return details.compareKeys(key, items.getArrayItem(2 * idx));
  721 + return details.compareKeys(key, items[2 * idx]);
785 722 }
786 723  
787 724 int
788 725 NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx)
789 726 {
790   - if (!(kids.isArray() && (idx < kids.getArrayNItems()) &&
791   - kids.getArrayItem(idx).isDictionary())) {
792   - QTC::TC("qpdf", "NNTree kid is invalid");
  727 + if (!(std::cmp_less(idx, kids.size()) && kids[idx].isDictionary())) {
793 728 error(oh, "invalid kid at index " + std::to_string(idx));
794 729 }
795   - return withinLimits(key, kids.getArrayItem(idx));
  730 + return withinLimits(key, kids[idx]);
796 731 }
797 732  
798 733 void
... ... @@ -826,28 +761,19 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found)
826 761 }
827 762  
828 763 NNTreeImpl::iterator
829   -NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
  764 +NNTreeImpl::findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found)
830 765 {
831 766 auto first_item = begin();
832 767 auto last_item = end();
833 768 if (first_item == end()) {
834   - // Empty
835 769 return end();
836   - } else if (
837   - first_item.valid() && details.keyValid(first_item->first) &&
  770 + }
  771 + if (first_item.valid() && details.keyValid(first_item->first) &&
838 772 details.compareKeys(key, first_item->first) < 0) {
839 773 // Before the first key
840 774 return end();
841   - } else if (
842   - last_item.valid() && details.keyValid(last_item->first) &&
843   - details.compareKeys(key, last_item->first) > 0) {
844   - // After the last key
845   - if (return_prev_if_not_found) {
846   - return last_item;
847   - } else {
848   - return end();
849   - }
850 775 }
  776 + qpdf_assert_debug(!last_item.valid());
851 777  
852 778 QPDFObjGen::set seen;
853 779 auto node = oh;
... ... @@ -855,48 +781,44 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found)
855 781  
856 782 while (true) {
857 783 if (!seen.add(node)) {
858   - QTC::TC("qpdf", "NNTree loop in find");
859 784 error(node, "loop detected in find");
860 785 }
861 786  
862   - auto kids = node.getKey("/Kids");
863   - int nkids = kids.isArray() ? kids.getArrayNItems() : 0;
864 787 auto items = node.getKey(details.itemsKey());
865   - int nitems = items.isArray() ? items.getArrayNItems() : 0;
866   - if (nitems > 0) {
  788 + size_t nitems = items.size();
  789 + if (nitems > 1) {
867 790 int idx = binarySearch(
868 791 key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem);
869 792 if (idx >= 0) {
870 793 result.setItemNumber(node, 2 * idx);
871 794 }
872   - break;
873   - } else if (nkids > 0) {
  795 + return result;
  796 + }
  797 +
  798 + auto kids = node.getKey("/Kids");
  799 + size_t nkids = kids.isArray() ? kids.size() : 0;
  800 + if (nkids > 0) {
874 801 int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid);
875 802 if (idx == -1) {
876   - QTC::TC("qpdf", "NNTree -1 in binary search");
877 803 error(node, "unexpected -1 from binary search of kids; limits may by wrong");
878 804 }
879 805 result.addPathElement(node, idx);
880   - node = kids.getArrayItem(idx);
  806 + node = kids[idx];
881 807 } else {
882   - QTC::TC("qpdf", "NNTree bad node during find");
883 808 error(node, "bad node during find");
884 809 }
885 810 }
886   -
887   - return result;
888 811 }
889 812  
890 813 NNTreeImpl::iterator
891   -NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value)
  814 +NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
892 815 {
893 816 auto iter = begin();
894 817 QPDFObjectHandle items;
895 818 if (iter.node.isDictionary()) {
896 819 items = iter.node.getKey(details.itemsKey());
897 820 }
898   - if (!(items.isArray())) {
899   - QTC::TC("qpdf", "NNTree no valid items node in insertFirst");
  821 + if (!items.isArray()) {
900 822 error(oh, "unable to find a valid items node");
901 823 }
902 824 items.insertItem(0, key);
... ... @@ -908,30 +830,26 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value)
908 830 }
909 831  
910 832 NNTreeImpl::iterator
911   -NNTreeImpl::insert(QPDFObjectHandle key, QPDFObjectHandle value)
  833 +NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value)
912 834 {
913 835 auto iter = find(key, true);
914 836 if (!iter.valid()) {
915   - QTC::TC("qpdf", "NNTree insert inserts first");
916 837 return insertFirst(key, value);
917 838 } else if (details.compareKeys(key, iter->first) == 0) {
918   - QTC::TC("qpdf", "NNTree insert replaces");
919 839 auto items = iter.node.getKey(details.itemsKey());
920 840 items.setArrayItem(iter.item_number + 1, value);
921 841 iter.updateIValue();
922 842 } else {
923   - QTC::TC("qpdf", "NNTree insert inserts after");
924 843 iter.insertAfter(key, value);
925 844 }
926 845 return iter;
927 846 }
928 847  
929 848 bool
930   -NNTreeImpl::remove(QPDFObjectHandle key, QPDFObjectHandle* value)
  849 +NNTreeImpl::remove(QPDFObjectHandle const& key, QPDFObjectHandle* value)
931 850 {
932 851 auto iter = find(key, false);
933 852 if (!iter.valid()) {
934   - QTC::TC("qpdf", "NNTree remove not found");
935 853 return false;
936 854 }
937 855 if (value) {
... ...
libqpdf/QPDF_Array.cc
... ... @@ -528,3 +528,47 @@ BaseHandle::size() const
528 528 return 0; // unreachable
529 529 }
530 530 }
  531 +
  532 +QPDFObjectHandle
  533 +BaseHandle::operator[](size_t n) const
  534 +{
  535 + switch (resolved_type_code()) {
  536 + case ::ot_array:
  537 + {
  538 + auto a = as<QPDF_Array>();
  539 + if (n >= a->size()) {
  540 + return {};
  541 + }
  542 + return Array(obj).at(static_cast<int>(n)).second;
  543 + }
  544 + case ::ot_uninitialized:
  545 + case ::ot_reserved:
  546 + case ::ot_null:
  547 + case ::ot_destroyed:
  548 + case ::ot_unresolved:
  549 + case ::ot_reference:
  550 + return {};
  551 + case ::ot_boolean:
  552 + case ::ot_integer:
  553 + case ::ot_real:
  554 + case ::ot_string:
  555 + case ::ot_name:
  556 + case ::ot_dictionary:
  557 + case ::ot_stream:
  558 + case ::ot_inlineimage:
  559 + case ::ot_operator:
  560 + return {obj};
  561 + default:
  562 + throw std::logic_error("Unexpected type code in size"); // unreachable
  563 + return {}; // unreachable
  564 + }
  565 +}
  566 +
  567 +QPDFObjectHandle
  568 +BaseHandle::operator[](int n) const
  569 +{
  570 + if (n < 0) {
  571 + return {};
  572 + }
  573 + return (*this)[static_cast<size_t>(n)];
  574 +}
... ...
libqpdf/qpdf/NNTree.hh
... ... @@ -56,7 +56,7 @@ class NNTreeIterator
56 56 return !operator==(other);
57 57 }
58 58  
59   - void insertAfter(QPDFObjectHandle key, QPDFObjectHandle value);
  59 + void insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
60 60 void remove();
61 61  
62 62 private:
... ... @@ -100,9 +100,9 @@ class NNTreeImpl
100 100 iterator end();
101 101 iterator last();
102 102 iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false);
103   - iterator insertFirst(QPDFObjectHandle key, QPDFObjectHandle value);
104   - iterator insert(QPDFObjectHandle key, QPDFObjectHandle value);
105   - bool remove(QPDFObjectHandle key, QPDFObjectHandle* value = nullptr);
  103 + iterator insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
  104 + iterator insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value);
  105 + bool remove(QPDFObjectHandle const& key, QPDFObjectHandle* value = nullptr);
106 106  
107 107 // Change the split threshold for easier testing. There's no real reason to expose this to
108 108 // downstream tree helpers, but it has to be public so we can call it from the test suite.
... ... @@ -110,18 +110,18 @@ class NNTreeImpl
110 110  
111 111 private:
112 112 void repair();
113   - iterator findInternal(QPDFObjectHandle key, bool return_prev_if_not_found = false);
114   - int withinLimits(QPDFObjectHandle key, QPDFObjectHandle node);
  113 + iterator findInternal(QPDFObjectHandle const& key, bool return_prev_if_not_found = false);
  114 + int withinLimits(QPDFObjectHandle const& key, QPDFObjectHandle const& node);
115 115 int binarySearch(
116 116 QPDFObjectHandle key,
117 117 QPDFObjectHandle items,
118   - int num_items,
  118 + size_t num_items,
119 119 bool return_prev_if_not_found,
120 120 int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item));
121 121 int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx);
122 122 int compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx);
123   - void warn(QPDFObjectHandle& node, std::string const& msg);
124   - void error(QPDFObjectHandle& node, std::string const& msg);
  123 + void warn(QPDFObjectHandle const& node, std::string const& msg);
  124 + void error(QPDFObjectHandle const& node, std::string const& msg);
125 125  
126 126 NNTreeDetails const& details;
127 127 QPDF& qpdf;
... ...
qpdf/qpdf.testcov
... ... @@ -519,50 +519,9 @@ qpdf-c called qpdf_oh_unparse_resolved 0
519 519 qpdf-c called qpdf_oh_unparse_binary 0
520 520 QPDFWriter getFilterOnWrite false 0
521 521 QPDFPageObjectHelper::forEachXObject 3
522   -NNTree deepen: invalid node 0
523   -NNTree deepen: loop 0
524   -NNTree skip invalid kid 0
525   -NNTree skip item at end of short items 0
526   -NNTree skip invalid key 0
527   -NNTree no valid items node in insertFirst 0
528   -NNTree deepen found empty 0
529   -NNTree insert inserts first 0
530   -NNTree insert replaces 0
531   -NNTree insert inserts after 0
532   -NNTree unable to determine limits 0
533   -NNTree warn indirect kid 0
534   -NNTree fix indirect kid 0
535 522 NNTree repair 0
536   -NNTree split root + leaf 0
537   -NNTree split root + !leaf 0
538   -NNTree split kids 0
539   -NNTree split items 0
540   -NNTree split second half item 0
541   -NNTree split parent 0
542   -NNTree split second half kid 0
543   -NNTree missing limits 0
544   -NNTree item is wrong type 0
545   -NNTree kid is invalid 0
546   -NNTree loop in find 0
547   -NNTree -1 in binary search 0
548   -NNTree bad node during find 0
549   -NNTree node is not a dictionary 0
550   -NNTree limits didn't change 0
551   -NNTree increment end() 0
552 523 NNTree insertAfter inserts first 0
553   -NNTree remove not found 0
554   -NNTree remove reset limits 0
555   -NNTree erased last item 0
556   -NNTree erased non-last item 0
557   -NNTree items is empty after remove 0
558   -NNTree erased all items on leaf/root 0
559   -NNTree erased first or last kid 0
560   -NNTree erased last kid 0
561   -NNTree erased non-last kid 0
562   -NNTree non-flat tree is empty after remove 0
563   -NNTree remove walking up tree 0
564   -NNTree erased last item in tree 0
565   -NNTree remove limits from root 0
  524 +NNTree erased last kid/item in tree 1
566 525 QPDFPageObjectHelper unresolved names 0
567 526 QPDFPageObjectHelper resolving unresolved 0
568 527 QPDFFileSpecObjectHelper empty compat_name 0
... ...