Commit 67796b3ff0c3503a78d45646568c7fae4f545d22

Authored by m-holger
1 parent 5e7b37e1

Refactor `NNTree` implementation to simplify key validation logic

- Removed `NNTreeDetails` and moved key validation logic directly into `NNTreeImpl`.
- Replaced type determination with `qpdf_object_type_e` for improved clarity.
- Updated `QPDFNameTreeObjectHelper` and `QPDFNumberTreeObjectHelper` to align with the new `NNTreeImpl` structure.
- Simplified methods accessing tree items and validating keys.
libqpdf/NNTree.cc
... ... @@ -68,7 +68,7 @@ NNTreeIterator::updateIValue(bool allow_invalid)
68 68 ivalue.second = QPDFObjectHandle();
69 69 return;
70 70 }
71   - Array items = node.getKey(impl.details.itemsKey());
  71 + Array items = node.getKey(impl.itemsKey());
72 72 if (!std::cmp_less(item_number + 1, items.size())) {
73 73 impl.error(node, "update ivalue: items array is too short");
74 74 }
... ... @@ -91,7 +91,7 @@ NNTreeIterator::getNextKid(PathElement& pe, bool backward)
91 91 if (pe.kid_number >= 0 && std::cmp_less(pe.kid_number, kids.size())) {
92 92 auto result = kids[pe.kid_number];
93 93 if (result.isDictionary() &&
94   - (result.hasKey("/Kids") || result.hasKey(impl.details.itemsKey()))) {
  94 + (result.hasKey("/Kids") || result.hasKey(impl.itemsKey()))) {
95 95 return result;
96 96 } else {
97 97 impl.warn(
... ... @@ -121,7 +121,7 @@ NNTreeIterator::increment(bool backward)
121 121  
122 122 while (valid()) {
123 123 item_number += backward ? -2 : 2;
124   - Array items = node.getKey(impl.details.itemsKey());
  124 + Array items = node.getKey(impl.itemsKey());
125 125 if (item_number < 0 || std::cmp_greater_equal(item_number, items.size())) {
126 126 bool found = false;
127 127 setItemNumber(QPDFObjectHandle(), -1);
... ... @@ -136,10 +136,10 @@ NNTreeIterator::increment(bool backward)
136 136 }
137 137 }
138 138 if (item_number >= 0) {
139   - items = node.getKey(impl.details.itemsKey());
  139 + items = node.getKey(impl.itemsKey());
140 140 if (std::cmp_greater_equal(item_number + 1, items.size())) {
141 141 impl.warn(node, "items array doesn't have enough elements");
142   - } else if (!impl.details.keyValid(items[item_number])) {
  142 + } else if (!impl.keyValid(items[item_number])) {
143 143 impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type"));
144 144 } else if (!impl.value_valid(items[item_number + 1])) {
145 145 impl.warn(node, "item " + std::to_string(item_number + 1) + " is invalid");
... ... @@ -160,7 +160,7 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
160 160 }
161 161 Array kids = a_node.getKey("/Kids");
162 162 size_t nkids = kids.size();
163   - Array items = a_node.getKey(impl.details.itemsKey());
  163 + Array items = a_node.getKey(impl.itemsKey());
164 164 size_t nitems = items.size();
165 165  
166 166 bool changed = true;
... ... @@ -187,9 +187,8 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list&lt;PathElement&gt;::ite
187 187 if (olimits.size() == 2) {
188 188 auto ofirst = olimits[0];
189 189 auto olast = olimits[1];
190   - if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) &&
191   - impl.details.compareKeys(first, ofirst) == 0 &&
192   - impl.details.compareKeys(last, olast) == 0) {
  190 + if (impl.keyValid(ofirst) && impl.keyValid(olast) &&
  191 + impl.compareKeys(first, ofirst) == 0 && impl.compareKeys(last, olast) == 0) {
193 192 changed = false;
194 193 }
195 194 }
... ... @@ -246,7 +245,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
246 245 // Find the array we actually need to split, which is either this node's kids or items.
247 246 Array kids = to_split.getKey("/Kids");
248 247 size_t nkids = kids.size();
249   - Array items = to_split.getKey(impl.details.itemsKey());
  248 + Array items = to_split.getKey(impl.itemsKey());
250 249 size_t nitems = items.size();
251 250  
252 251 Array first_half;
... ... @@ -261,7 +260,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
261 260 first_half = items;
262 261 n = nitems;
263 262 threshold *= 2;
264   - key = impl.details.itemsKey();
  263 + key = impl.itemsKey();
265 264 } else {
266 265 throw std::logic_error("NNTreeIterator::split called on invalid node");
267 266 }
... ... @@ -295,7 +294,7 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list&lt;PathElement&gt;::iterato
295 294 Array new_kids;
296 295 new_kids.push_back(first_node);
297 296 to_split.removeKey("/Limits"); // already shouldn't be there for root
298   - to_split.removeKey(impl.details.itemsKey());
  297 + to_split.removeKey(impl.itemsKey());
299 298 to_split.replaceKey("/Kids", new_kids);
300 299 if (is_leaf) {
301 300 node = first_node;
... ... @@ -375,7 +374,7 @@ NNTreeIterator::insertAfter(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp;
375 374 return;
376 375 }
377 376  
378   - Array items = node.getKey(impl.details.itemsKey());
  377 + Array items = node.getKey(impl.itemsKey());
379 378 if (!items) {
380 379 impl.error(node, "node contains no items array");
381 380 }
... ... @@ -404,7 +403,7 @@ NNTreeIterator::remove()
404 403 if (!valid()) {
405 404 throw std::logic_error("attempt made to remove an invalid iterator");
406 405 }
407   - Array items = node.getKey(impl.details.itemsKey());
  406 + Array items = node.getKey(impl.itemsKey());
408 407 int nitems = static_cast<int>(items.size());
409 408 if (std::cmp_greater(item_number + 2, nitems)) {
410 409 impl.error(node, "found short items array while removing an item");
... ... @@ -478,7 +477,7 @@ NNTreeIterator::remove()
478 477 if (parent == path.end()) {
479 478 // We erased the very last item. Convert the root to an empty items array.
480 479 element->node.removeKey("/Kids");
481   - element->node.replaceKey(impl.details.itemsKey(), Array());
  480 + element->node.replaceKey(impl.itemsKey(), Array());
482 481 path.clear();
483 482 setItemNumber(impl.oh, -1);
484 483 return;
... ... @@ -579,7 +578,7 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
579 578 return fail(a_node, "non-dictionary node while traversing name/number tree");
580 579 }
581 580  
582   - Array items = a_node.getKey(impl.details.itemsKey());
  581 + Array items = a_node.getKey(impl.itemsKey());
583 582 int nitems = static_cast<int>(items.size());
584 583 if (nitems > 1) {
585 584 setItemNumber(a_node, first ? 0 : nitems - 2);
... ... @@ -616,22 +615,22 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty)
616 615 } else {
617 616 return fail(
618 617 a_node,
619   - "name/number tree node has neither non-empty " + impl.details.itemsKey() +
620   - " nor /Kids");
  618 + "name/number tree node has neither non-empty " + impl.itemsKey() + " nor /Kids");
621 619 }
622 620 }
623 621 return true;
624 622 }
625 623  
626 624 NNTreeImpl::NNTreeImpl(
627   - NNTreeDetails const& details,
628 625 QPDF& qpdf,
629 626 QPDFObjectHandle& oh,
  627 + qpdf_object_type_e key_type,
630 628 std::function<bool(QPDFObjectHandle const&)> value_validator,
631 629 bool auto_repair) :
632   - details(details),
633 630 qpdf(qpdf),
634 631 oh(oh),
  632 + key_type(key_type),
  633 + items_key(key_type == ::ot_string ? "/Names" : "/Nums"),
635 634 value_valid(value_validator),
636 635 auto_repair(auto_repair)
637 636 {
... ... @@ -666,16 +665,32 @@ NNTreeImpl::last()
666 665 }
667 666  
668 667 int
  668 +NNTreeImpl::compareKeys(QPDFObjectHandle a, QPDFObjectHandle b) const
  669 +{
  670 + // We don't call this without calling keyValid first
  671 + qpdf_assert_debug(keyValid(a));
  672 + qpdf_assert_debug(keyValid(b));
  673 + if (key_type == ::ot_string) {
  674 + auto as = a.getUTF8Value();
  675 + auto bs = b.getUTF8Value();
  676 + return as < bs ? -1 : (as > bs ? 1 : 0);
  677 + }
  678 + auto as = a.getIntValue();
  679 + auto bs = b.getIntValue();
  680 + return as < bs ? -1 : (as > bs ? 1 : 0);
  681 +}
  682 +
  683 +int
669 684 NNTreeImpl::withinLimits(QPDFObjectHandle const& key, QPDFObjectHandle const& node)
670 685 {
671 686 Array limits = node.getKey("/Limits");
672   - if (!(details.keyValid(limits[0]) && details.keyValid(limits[1]))) {
  687 + if (!(keyValid(limits[0]) && keyValid(limits[1]))) {
673 688 error(node, "node is missing /Limits");
674 689 }
675   - if (details.compareKeys(key, limits[0]) < 0) {
  690 + if (compareKeys(key, limits[0]) < 0) {
676 691 return -1;
677 692 }
678   - if (details.compareKeys(key, limits[1]) > 0) {
  693 + if (compareKeys(key, limits[1]) > 0) {
679 694 return 1;
680 695 }
681 696 return 0;
... ... @@ -728,10 +743,10 @@ NNTreeImpl::binarySearch(
728 743 int
729 744 NNTreeImpl::compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx)
730 745 {
731   - if (!(std::cmp_greater(items.size(), 2 * idx) && details.keyValid(items[2 * idx]))) {
  746 + if (!(std::cmp_greater(items.size(), 2 * idx) && keyValid(items[2 * idx]))) {
732 747 error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type"));
733 748 }
734   - return details.compareKeys(key, items[2 * idx]);
  749 + return compareKeys(key, items[2 * idx]);
735 750 }
736 751  
737 752 int
... ... @@ -747,15 +762,15 @@ void
747 762 NNTreeImpl::repair()
748 763 {
749 764 auto new_node = QPDFObjectHandle::newDictionary();
750   - new_node.replaceKey(details.itemsKey(), Array());
751   - NNTreeImpl repl(details, qpdf, new_node, value_valid, false);
  765 + new_node.replaceKey(itemsKey(), Array());
  766 + NNTreeImpl repl(qpdf, new_node, key_type, value_valid, false);
752 767 for (auto const& [key, value]: *this) {
753 768 if (key && value) {
754 769 repl.insert(key, value);
755 770 }
756 771 }
757 772 oh.replaceKey("/Kids", new_node.getKey("/Kids"));
758   - oh.replaceKey(details.itemsKey(), new_node.getKey(details.itemsKey()));
  773 + oh.replaceKey(itemsKey(), new_node.getKey(itemsKey()));
759 774 }
760 775  
761 776 NNTreeImpl::iterator
... ... @@ -783,13 +798,13 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
783 798 return end();
784 799 }
785 800 if (first_item.valid()) {
786   - if (!details.keyValid(first_item->first)) {
  801 + if (!keyValid(first_item->first)) {
787 802 error(oh, "encountered invalid key in find");
788 803 }
789 804 if (!value_valid(first_item->second)) {
790 805 error(oh, "encountered invalid value in find");
791 806 }
792   - if (details.compareKeys(key, first_item->first) < 0) {
  807 + if (compareKeys(key, first_item->first) < 0) {
793 808 // Before the first key
794 809 return end();
795 810 }
... ... @@ -805,14 +820,14 @@ NNTreeImpl::findInternal(QPDFObjectHandle const&amp; key, bool return_prev_if_not_fo
805 820 error(node, "loop detected in find");
806 821 }
807 822  
808   - Array items = node.getKey(details.itemsKey());
  823 + Array items = node.getKey(itemsKey());
809 824 size_t nitems = items.size();
810 825 if (nitems > 1) {
811 826 int idx = binarySearch(
812 827 key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem);
813 828 if (idx >= 0) {
814 829 result.setItemNumber(node, 2 * idx);
815   - if (!result.impl.details.keyValid(result.ivalue.first)) {
  830 + if (!result.impl.keyValid(result.ivalue.first)) {
816 831 error(node, "encountered invalid key in find");
817 832 }
818 833 if (!result.impl.value_valid(result.ivalue.second)) {
... ... @@ -843,7 +858,7 @@ NNTreeImpl::insertFirst(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; val
843 858 auto iter = begin();
844 859 Array items(nullptr);
845 860 if (iter.node.isDictionary()) {
846   - items = iter.node.getKey(details.itemsKey());
  861 + items = iter.node.getKey(itemsKey());
847 862 }
848 863 if (!items) {
849 864 error(oh, "unable to find a valid items node");
... ... @@ -868,8 +883,8 @@ NNTreeImpl::insert(QPDFObjectHandle const&amp; key, QPDFObjectHandle const&amp; value)
868 883 auto iter = find(key, true);
869 884 if (!iter.valid()) {
870 885 return insertFirst(key, value);
871   - } else if (details.compareKeys(key, iter->first) == 0) {
872   - Array items = iter.node.getKey(details.itemsKey());
  886 + } else if (compareKeys(key, iter->first) == 0) {
  887 + Array items = iter.node.getKey(itemsKey());
873 888 items.set(iter.item_number + 1, value);
874 889 iter.updateIValue();
875 890 } else {
... ... @@ -899,7 +914,7 @@ NNTreeImpl::validate(bool a_repair)
899 914 QPDFObjectHandle last_key;
900 915 try {
901 916 for (auto const& [key, value]: *this) {
902   - if (!details.keyValid(key)) {
  917 + if (!keyValid(key)) {
903 918 error(oh, "invalid key in validate");
904 919 }
905 920 if (!value_valid(value)) {
... ... @@ -907,7 +922,7 @@ NNTreeImpl::validate(bool a_repair)
907 922 }
908 923 if (first) {
909 924 first = false;
910   - } else if (last_key && details.compareKeys(last_key, key) != -1) {
  925 + } else if (last_key && compareKeys(last_key, key) != -1) {
911 926 error(oh, "keys are not sorted in validate");
912 927 }
913 928 last_key = key;
... ...
libqpdf/QPDFNameTreeObjectHelper.cc
... ... @@ -2,38 +2,6 @@
2 2  
3 3 #include <qpdf/NNTree.hh>
4 4  
5   -namespace
6   -{
7   - class NameTreeDetails: public NNTreeDetails
8   - {
9   - public:
10   - std::string const&
11   - itemsKey() const override
12   - {
13   - static std::string k("/Names");
14   - return k;
15   - }
16   - bool
17   - keyValid(QPDFObjectHandle oh) const override
18   - {
19   - return oh.isString();
20   - }
21   - int
22   - compareKeys(QPDFObjectHandle a, QPDFObjectHandle b) const override
23   - {
24   - if (!(keyValid(a) && keyValid(b))) {
25   - // We don't call this without calling keyValid first
26   - throw std::logic_error("comparing invalid keys");
27   - }
28   - auto as = a.getUTF8Value();
29   - auto bs = b.getUTF8Value();
30   - return ((as < bs) ? -1 : (as > bs) ? 1 : 0);
31   - }
32   - };
33   -} // namespace
34   -
35   -static NameTreeDetails name_tree_details;
36   -
37 5 QPDFNameTreeObjectHelper::~QPDFNameTreeObjectHelper() // NOLINT (modernize-use-equals-default)
38 6 {
39 7 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer. For this specific
... ... @@ -45,7 +13,7 @@ QPDFNameTreeObjectHelper::Members::Members(
45 13 QPDF& q,
46 14 std::function<bool(QPDFObjectHandle const&)> value_validator,
47 15 bool auto_repair) :
48   - impl(std::make_shared<NNTreeImpl>(name_tree_details, q, oh, value_validator, auto_repair))
  16 + impl(std::make_shared<NNTreeImpl>(q, oh, ::ot_string, value_validator, auto_repair))
49 17 {
50 18 }
51 19  
... ...
libqpdf/QPDFNumberTreeObjectHelper.cc
... ... @@ -3,38 +3,6 @@
3 3 #include <qpdf/NNTree.hh>
4 4 #include <qpdf/QIntC.hh>
5 5  
6   -namespace
7   -{
8   - class NumberTreeDetails: public NNTreeDetails
9   - {
10   - public:
11   - std::string const&
12   - itemsKey() const override
13   - {
14   - static std::string k("/Nums");
15   - return k;
16   - }
17   - bool
18   - keyValid(QPDFObjectHandle oh) const override
19   - {
20   - return oh.isInteger();
21   - }
22   - int
23   - compareKeys(QPDFObjectHandle a, QPDFObjectHandle b) const override
24   - {
25   - if (!(keyValid(a) && keyValid(b))) {
26   - // We don't call this without calling keyValid first
27   - throw std::logic_error("comparing invalid keys");
28   - }
29   - auto as = a.getIntValue();
30   - auto bs = b.getIntValue();
31   - return ((as < bs) ? -1 : (as > bs) ? 1 : 0);
32   - }
33   - };
34   -} // namespace
35   -
36   -static NumberTreeDetails number_tree_details;
37   -
38 6 QPDFNumberTreeObjectHelper::~QPDFNumberTreeObjectHelper() // NOLINT (modernize-use-equals-default)
39 7 {
40 8 // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer. For this specific
... ... @@ -46,7 +14,7 @@ QPDFNumberTreeObjectHelper::Members::Members(
46 14 QPDF& q,
47 15 std::function<bool(QPDFObjectHandle const&)> value_validator,
48 16 bool auto_repair) :
49   - impl(std::make_shared<NNTreeImpl>(number_tree_details, q, oh, value_validator, auto_repair))
  17 + impl(std::make_shared<NNTreeImpl>(q, oh, ::ot_integer, value_validator, auto_repair))
50 18 {
51 19 }
52 20  
... ...
libqpdf/qpdf/NNTree.hh
... ... @@ -2,20 +2,12 @@
2 2 #define NNTREE_HH
3 3  
4 4 #include <qpdf/QPDF.hh>
5   -#include <qpdf/QPDFObjectHandle.hh>
  5 +#include <qpdf/QPDFObjectHandle_private.hh>
6 6  
7 7 #include <iterator>
8 8 #include <list>
9 9 #include <memory>
10 10  
11   -class NNTreeDetails
12   -{
13   - public:
14   - virtual std::string const& itemsKey() const = 0;
15   - virtual bool keyValid(QPDFObjectHandle) const = 0;
16   - virtual int compareKeys(QPDFObjectHandle, QPDFObjectHandle) const = 0;
17   -};
18   -
19 11 class NNTreeImpl;
20 12 class NNTreeIterator
21 13 {
... ... @@ -96,9 +88,9 @@ class NNTreeImpl
96 88 typedef NNTreeIterator iterator;
97 89  
98 90 NNTreeImpl(
99   - NNTreeDetails const&,
100 91 QPDF&,
101 92 QPDFObjectHandle&,
  93 + qpdf_object_type_e key_type,
102 94 std::function<bool(QPDFObjectHandle const&)> value_validator,
103 95 bool auto_repair = true);
104 96 iterator begin();
... ... @@ -130,10 +122,24 @@ class NNTreeImpl
130 122 void warn(QPDFObjectHandle const& node, std::string const& msg);
131 123 void error(QPDFObjectHandle const& node, std::string const& msg);
132 124  
133   - NNTreeDetails const& details;
  125 + std::string const&
  126 + itemsKey() const
  127 + {
  128 + return items_key;
  129 + }
  130 + bool
  131 + keyValid(QPDFObjectHandle o) const
  132 + {
  133 + return o.resolved_type_code() == key_type;
  134 + }
  135 + int
  136 + compareKeys(QPDFObjectHandle a, QPDFObjectHandle b) const;
  137 +
134 138 QPDF& qpdf;
135 139 int split_threshold{32};
136 140 QPDFObjectHandle oh;
  141 + const qpdf_object_type_e key_type;
  142 + const std::string items_key;
137 143 const std::function<bool(QPDFObjectHandle const&)> value_valid;
138 144 bool auto_repair{true};
139 145 size_t error_count{0};
... ...