Commit db288584617600ba09f5ebbd59bf14fee4d6b6c7
Committed by
GitHub
Merge pull request #1522 from m-holger/oh_array
Add private-API subscript operators to `QPDFObjectHandle`
Showing
5 changed files
with
206 additions
and
281 deletions
include/qpdf/ObjectHandle.hh
| @@ -73,6 +73,10 @@ namespace qpdf | @@ -73,6 +73,10 @@ namespace qpdf | ||
| 73 | return size() == 0; | 73 | return size() == 0; |
| 74 | } | 74 | } |
| 75 | 75 | ||
| 76 | + QPDFObjectHandle operator[](size_t n) const; | ||
| 77 | + | ||
| 78 | + QPDFObjectHandle operator[](int n) const; | ||
| 79 | + | ||
| 76 | std::shared_ptr<QPDFObject> copy(bool shallow = false) const; | 80 | std::shared_ptr<QPDFObject> copy(bool shallow = false) const; |
| 77 | // Recursively remove association with any QPDF object. This method may only be called | 81 | // Recursively remove association with any QPDF object. This method may only be called |
| 78 | // during final destruction. | 82 | // during final destruction. |
libqpdf/NNTree.cc
| 1 | +#include <qpdf/assert_debug.h> | ||
| 2 | + | ||
| 1 | #include <qpdf/NNTree.hh> | 3 | #include <qpdf/NNTree.hh> |
| 2 | 4 | ||
| 5 | +#include <qpdf/QPDFObjectHandle_private.hh> | ||
| 3 | #include <qpdf/QPDF_private.hh> | 6 | #include <qpdf/QPDF_private.hh> |
| 4 | #include <qpdf/QTC.hh> | 7 | #include <qpdf/QTC.hh> |
| 5 | #include <qpdf/QUtil.hh> | 8 | #include <qpdf/QUtil.hh> |
| 6 | 9 | ||
| 10 | +#include <bit> | ||
| 7 | #include <exception> | 11 | #include <exception> |
| 12 | +#include <utility> | ||
| 8 | 13 | ||
| 9 | static std::string | 14 | static std::string |
| 10 | -get_description(QPDFObjectHandle& node) | 15 | +get_description(QPDFObjectHandle const& node) |
| 11 | { | 16 | { |
| 12 | std::string result("Name/Number tree node"); | 17 | std::string result("Name/Number tree node"); |
| 13 | - if (node.isIndirect()) { | 18 | + if (node.indirect()) { |
| 14 | result += " (object " + std::to_string(node.getObjectID()) + ")"; | 19 | result += " (object " + std::to_string(node.getObjectID()) + ")"; |
| 15 | } | 20 | } |
| 16 | return result; | 21 | return result; |
| 17 | } | 22 | } |
| 18 | 23 | ||
| 19 | void | 24 | void |
| 20 | -NNTreeImpl::warn(QPDFObjectHandle& node, std::string const& msg) | 25 | +NNTreeImpl::warn(QPDFObjectHandle const& node, std::string const& msg) |
| 21 | { | 26 | { |
| 22 | qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); | 27 | qpdf.warn(qpdf_e_damaged_pdf, get_description(node), 0, msg); |
| 23 | if (++error_count > 5 && qpdf.reconstructed_xref()) { | 28 | if (++error_count > 5 && qpdf.reconstructed_xref()) { |
| @@ -26,7 +31,7 @@ NNTreeImpl::warn(QPDFObjectHandle& node, std::string const& msg) | @@ -26,7 +31,7 @@ NNTreeImpl::warn(QPDFObjectHandle& node, std::string const& msg) | ||
| 26 | } | 31 | } |
| 27 | 32 | ||
| 28 | void | 33 | void |
| 29 | -NNTreeImpl::error(QPDFObjectHandle& node, std::string const& msg) | 34 | +NNTreeImpl::error(QPDFObjectHandle const& node, std::string const& msg) |
| 30 | { | 35 | { |
| 31 | throw QPDFExc(qpdf_e_damaged_pdf, qpdf.getFilename(), get_description(node), 0, msg); | 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,25 +57,21 @@ NNTreeIterator::updateIValue(bool allow_invalid) | ||
| 52 | // we must call updateIValue as well. These cases are handled, and for good measure, we also | 57 | // we must call updateIValue as well. These cases are handled, and for good measure, we also |
| 53 | // call updateIValue in operator* and operator->. | 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 | if (!allow_invalid) { | 61 | if (!allow_invalid) { |
| 68 | throw std::logic_error( | 62 | throw std::logic_error( |
| 69 | "attempt made to dereference an invalid name/number tree iterator"); | 63 | "attempt made to dereference an invalid name/number tree iterator"); |
| 70 | } | 64 | } |
| 71 | ivalue.first = QPDFObjectHandle(); | 65 | ivalue.first = QPDFObjectHandle(); |
| 72 | ivalue.second = QPDFObjectHandle(); | 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 | NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_number) : | 77 | NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_number) : |
| @@ -82,28 +83,22 @@ NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_n | @@ -82,28 +83,22 @@ NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_n | ||
| 82 | QPDFObjectHandle | 83 | QPDFObjectHandle |
| 83 | NNTreeIterator::getNextKid(PathElement& pe, bool backward) | 84 | NNTreeIterator::getNextKid(PathElement& pe, bool backward) |
| 84 | { | 85 | { |
| 85 | - QPDFObjectHandle result; | ||
| 86 | - bool found = false; | ||
| 87 | - while (!found) { | 86 | + while (true) { |
| 88 | pe.kid_number += backward ? -1 : 1; | 87 | pe.kid_number += backward ? -1 : 1; |
| 89 | auto kids = pe.node.getKey("/Kids"); | 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 | if (result.isDictionary() && | 91 | if (result.isDictionary() && |
| 93 | (result.hasKey("/Kids") || result.hasKey(impl.details.itemsKey()))) { | 92 | (result.hasKey("/Kids") || result.hasKey(impl.details.itemsKey()))) { |
| 94 | - found = true; | 93 | + return result; |
| 95 | } else { | 94 | } else { |
| 96 | - QTC::TC("qpdf", "NNTree skip invalid kid"); | ||
| 97 | impl.warn( | 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 | } else { | 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 | bool | 104 | bool |
| @@ -116,21 +111,20 @@ void | @@ -116,21 +111,20 @@ void | ||
| 116 | NNTreeIterator::increment(bool backward) | 111 | NNTreeIterator::increment(bool backward) |
| 117 | { | 112 | { |
| 118 | if (item_number < 0) { | 113 | if (item_number < 0) { |
| 119 | - QTC::TC("qpdf", "NNTree increment end()"); | ||
| 120 | deepen(impl.oh, !backward, true); | 114 | deepen(impl.oh, !backward, true); |
| 121 | return; | 115 | return; |
| 122 | } | 116 | } |
| 123 | - bool found_valid_key = false; | ||
| 124 | - while (valid() && !found_valid_key) { | 117 | + |
| 118 | + while (valid()) { | ||
| 125 | item_number += backward ? -2 : 2; | 119 | item_number += backward ? -2 : 2; |
| 126 | auto items = node.getKey(impl.details.itemsKey()); | 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 | bool found = false; | 122 | bool found = false; |
| 129 | setItemNumber(QPDFObjectHandle(), -1); | 123 | setItemNumber(QPDFObjectHandle(), -1); |
| 130 | while (!(found || path.empty())) { | 124 | while (!(found || path.empty())) { |
| 131 | auto& element = path.back(); | 125 | auto& element = path.back(); |
| 132 | auto pe_node = getNextKid(element, backward); | 126 | auto pe_node = getNextKid(element, backward); |
| 133 | - if (pe_node.isNull()) { | 127 | + if (pe_node.null()) { |
| 134 | path.pop_back(); | 128 | path.pop_back(); |
| 135 | } else { | 129 | } else { |
| 136 | found = deepen(pe_node, !backward, false); | 130 | found = deepen(pe_node, !backward, false); |
| @@ -139,14 +133,12 @@ NNTreeIterator::increment(bool backward) | @@ -139,14 +133,12 @@ NNTreeIterator::increment(bool backward) | ||
| 139 | } | 133 | } |
| 140 | if (item_number >= 0) { | 134 | if (item_number >= 0) { |
| 141 | items = node.getKey(impl.details.itemsKey()); | 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 | impl.warn(node, "items array doesn't have enough elements"); | 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 | impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type")); | 139 | impl.warn(node, ("item " + std::to_string(item_number) + " has the wrong type")); |
| 148 | } else { | 140 | } else { |
| 149 | - found_valid_key = true; | 141 | + return; |
| 150 | } | 142 | } |
| 151 | } | 143 | } |
| 152 | } | 144 | } |
| @@ -155,35 +147,31 @@ NNTreeIterator::increment(bool backward) | @@ -155,35 +147,31 @@ NNTreeIterator::increment(bool backward) | ||
| 155 | void | 147 | void |
| 156 | NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::iterator parent) | 148 | NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::iterator parent) |
| 157 | { | 149 | { |
| 158 | - bool done = false; | ||
| 159 | - while (!done) { | 150 | + while (true) { |
| 160 | if (parent == path.end()) { | 151 | if (parent == path.end()) { |
| 161 | - QTC::TC("qpdf", "NNTree remove limits from root"); | ||
| 162 | a_node.removeKey("/Limits"); | 152 | a_node.removeKey("/Limits"); |
| 163 | - done = true; | ||
| 164 | break; | 153 | break; |
| 165 | } | 154 | } |
| 166 | auto kids = a_node.getKey("/Kids"); | 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 | auto items = a_node.getKey(impl.details.itemsKey()); | 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 | bool changed = true; | 160 | bool changed = true; |
| 172 | QPDFObjectHandle first; | 161 | QPDFObjectHandle first; |
| 173 | QPDFObjectHandle last; | 162 | QPDFObjectHandle last; |
| 174 | if (nitems >= 2) { | 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 | } else if (nkids > 0) { | 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 | if (first_kid.isDictionary() && last_kid.isDictionary()) { | 169 | if (first_kid.isDictionary() && last_kid.isDictionary()) { |
| 181 | auto first_limits = first_kid.getKey("/Limits"); | 170 | auto first_limits = first_kid.getKey("/Limits"); |
| 182 | auto last_limits = last_kid.getKey("/Limits"); | 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<PathElement>::ite | @@ -192,13 +180,12 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::ite | ||
| 192 | limits.appendItem(first); | 180 | limits.appendItem(first); |
| 193 | limits.appendItem(last); | 181 | limits.appendItem(last); |
| 194 | auto olimits = a_node.getKey("/Limits"); | 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 | if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) && | 186 | if (impl.details.keyValid(ofirst) && impl.details.keyValid(olast) && |
| 199 | (impl.details.compareKeys(first, ofirst) == 0) && | 187 | (impl.details.compareKeys(first, ofirst) == 0) && |
| 200 | (impl.details.compareKeys(last, olast) == 0)) { | 188 | (impl.details.compareKeys(last, olast) == 0)) { |
| 201 | - QTC::TC("qpdf", "NNTree limits didn't change"); | ||
| 202 | changed = false; | 189 | changed = false; |
| 203 | } | 190 | } |
| 204 | } | 191 | } |
| @@ -206,12 +193,11 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::ite | @@ -206,12 +193,11 @@ NNTreeIterator::resetLimits(QPDFObjectHandle a_node, std::list<PathElement>::ite | ||
| 206 | a_node.replaceKey("/Limits", limits); | 193 | a_node.replaceKey("/Limits", limits); |
| 207 | } | 194 | } |
| 208 | } else { | 195 | } else { |
| 209 | - QTC::TC("qpdf", "NNTree unable to determine limits"); | ||
| 210 | impl.warn(a_node, "unable to determine limits"); | 196 | impl.warn(a_node, "unable to determine limits"); |
| 211 | } | 197 | } |
| 212 | 198 | ||
| 213 | if (!changed || parent == path.begin()) { | 199 | if (!changed || parent == path.begin()) { |
| 214 | - done = true; | 200 | + break; |
| 215 | } else { | 201 | } else { |
| 216 | a_node = parent->node; | 202 | a_node = parent->node; |
| 217 | --parent; | 203 | --parent; |
| @@ -255,22 +241,20 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | @@ -255,22 +241,20 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | ||
| 255 | 241 | ||
| 256 | // Find the array we actually need to split, which is either this node's kids or items. | 242 | // Find the array we actually need to split, which is either this node's kids or items. |
| 257 | auto kids = to_split.getKey("/Kids"); | 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 | auto items = to_split.getKey(impl.details.itemsKey()); | 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 | QPDFObjectHandle first_half; | 248 | QPDFObjectHandle first_half; |
| 263 | int n = 0; | 249 | int n = 0; |
| 264 | std::string key; | 250 | std::string key; |
| 265 | int threshold = 0; | 251 | int threshold = 0; |
| 266 | if (nkids > 0) { | 252 | if (nkids > 0) { |
| 267 | - QTC::TC("qpdf", "NNTree split kids"); | ||
| 268 | first_half = kids; | 253 | first_half = kids; |
| 269 | n = nkids; | 254 | n = nkids; |
| 270 | threshold = impl.split_threshold; | 255 | threshold = impl.split_threshold; |
| 271 | key = "/Kids"; | 256 | key = "/Kids"; |
| 272 | } else if (nitems > 0) { | 257 | } else if (nitems > 0) { |
| 273 | - QTC::TC("qpdf", "NNTree split items"); | ||
| 274 | first_half = items; | 258 | first_half = items; |
| 275 | n = nitems; | 259 | n = nitems; |
| 276 | threshold = 2 * impl.split_threshold; | 260 | threshold = 2 * impl.split_threshold; |
| @@ -283,8 +267,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | @@ -283,8 +267,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | ||
| 283 | return; | 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 | // CURRENT STATE: tree is in original state; iterator is valid and unchanged. | 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<PathElement>::iterato | @@ -311,10 +295,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | ||
| 311 | to_split.removeKey(impl.details.itemsKey()); | 295 | to_split.removeKey(impl.details.itemsKey()); |
| 312 | to_split.replaceKey("/Kids", new_kids); | 296 | to_split.replaceKey("/Kids", new_kids); |
| 313 | if (is_leaf) { | 297 | if (is_leaf) { |
| 314 | - QTC::TC("qpdf", "NNTree split root + leaf"); | ||
| 315 | node = first_node; | 298 | node = first_node; |
| 316 | } else { | 299 | } else { |
| 317 | - QTC::TC("qpdf", "NNTree split root + !leaf"); | ||
| 318 | auto next = path.begin(); | 300 | auto next = path.begin(); |
| 319 | next->node = first_node; | 301 | next->node = first_node; |
| 320 | } | 302 | } |
| @@ -330,8 +312,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | @@ -330,8 +312,8 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | ||
| 330 | // array. | 312 | // array. |
| 331 | QPDFObjectHandle second_half = QPDFObjectHandle::newArray(); | 313 | QPDFObjectHandle second_half = QPDFObjectHandle::newArray(); |
| 332 | int start_idx = ((n / 2) & ~1); | 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 | first_half.eraseItem(start_idx); | 317 | first_half.eraseItem(start_idx); |
| 336 | } | 318 | } |
| 337 | resetLimits(to_split, parent); | 319 | resetLimits(to_split, parent); |
| @@ -357,16 +339,13 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | @@ -357,16 +339,13 @@ NNTreeIterator::split(QPDFObjectHandle to_split, std::list<PathElement>::iterato | ||
| 357 | if (old_idx >= start_idx) { | 339 | if (old_idx >= start_idx) { |
| 358 | ++parent->kid_number; | 340 | ++parent->kid_number; |
| 359 | if (is_leaf) { | 341 | if (is_leaf) { |
| 360 | - QTC::TC("qpdf", "NNTree split second half item"); | ||
| 361 | setItemNumber(second_node, item_number - start_idx); | 342 | setItemNumber(second_node, item_number - start_idx); |
| 362 | } else { | 343 | } else { |
| 363 | - QTC::TC("qpdf", "NNTree split second half kid"); | ||
| 364 | cur_elem->node = second_node; | 344 | cur_elem->node = second_node; |
| 365 | cur_elem->kid_number -= start_idx; | 345 | cur_elem->kid_number -= start_idx; |
| 366 | } | 346 | } |
| 367 | } | 347 | } |
| 368 | if (!is_root) { | 348 | if (!is_root) { |
| 369 | - QTC::TC("qpdf", "NNTree split parent"); | ||
| 370 | auto next = parent->node; | 349 | auto next = parent->node; |
| 371 | resetLimits(next, parent); | 350 | resetLimits(next, parent); |
| 372 | --parent; | 351 | --parent; |
| @@ -385,7 +364,7 @@ NNTreeIterator::lastPathElement() | @@ -385,7 +364,7 @@ NNTreeIterator::lastPathElement() | ||
| 385 | } | 364 | } |
| 386 | 365 | ||
| 387 | void | 366 | void |
| 388 | -NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) | 367 | +NNTreeIterator::insertAfter(QPDFObjectHandle const& key, QPDFObjectHandle const& value) |
| 389 | { | 368 | { |
| 390 | if (!valid()) { | 369 | if (!valid()) { |
| 391 | QTC::TC("qpdf", "NNTree insertAfter inserts first"); | 370 | QTC::TC("qpdf", "NNTree insertAfter inserts first"); |
| @@ -395,10 +374,11 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) | @@ -395,10 +374,11 @@ NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 395 | } | 374 | } |
| 396 | 375 | ||
| 397 | auto items = node.getKey(impl.details.itemsKey()); | 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 | impl.error(node, "insert: items array is too short"); | 382 | impl.error(node, "insert: items array is too short"); |
| 403 | } | 383 | } |
| 404 | items.insertItem(item_number + 2, key); | 384 | items.insertItem(item_number + 2, key); |
| @@ -417,8 +397,8 @@ NNTreeIterator::remove() | @@ -417,8 +397,8 @@ NNTreeIterator::remove() | ||
| 417 | throw std::logic_error("attempt made to remove an invalid iterator"); | 397 | throw std::logic_error("attempt made to remove an invalid iterator"); |
| 418 | } | 398 | } |
| 419 | auto items = node.getKey(impl.details.itemsKey()); | 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 | impl.error(node, "found short items array while removing an item"); | 402 | impl.error(node, "found short items array while removing an item"); |
| 423 | } | 403 | } |
| 424 | 404 | ||
| @@ -432,20 +412,17 @@ NNTreeIterator::remove() | @@ -432,20 +412,17 @@ NNTreeIterator::remove() | ||
| 432 | if (item_number == 0 || item_number == nitems) { | 412 | if (item_number == 0 || item_number == nitems) { |
| 433 | // We removed either the first or last item of an items array that remains non-empty, so | 413 | // We removed either the first or last item of an items array that remains non-empty, so |
| 434 | // we have to adjust limits. | 414 | // we have to adjust limits. |
| 435 | - QTC::TC("qpdf", "NNTree remove reset limits"); | ||
| 436 | resetLimits(node, lastPathElement()); | 415 | resetLimits(node, lastPathElement()); |
| 437 | } | 416 | } |
| 438 | 417 | ||
| 439 | if (item_number == nitems) { | 418 | if (item_number == nitems) { |
| 440 | // We removed the last item of a non-empty items array, so advance to the successor of | 419 | // We removed the last item of a non-empty items array, so advance to the successor of |
| 441 | // the previous item. | 420 | // the previous item. |
| 442 | - QTC::TC("qpdf", "NNTree erased last item"); | ||
| 443 | item_number -= 2; | 421 | item_number -= 2; |
| 444 | increment(false); | 422 | increment(false); |
| 445 | } else if (item_number < nitems) { | 423 | } else if (item_number < nitems) { |
| 446 | // We don't have to do anything since the removed item's successor now occupies its | 424 | // We don't have to do anything since the removed item's successor now occupies its |
| 447 | // former location. | 425 | // former location. |
| 448 | - QTC::TC("qpdf", "NNTree erased non-last item"); | ||
| 449 | updateIValue(); | 426 | updateIValue(); |
| 450 | } else { | 427 | } else { |
| 451 | // We already checked to ensure this condition would not happen. | 428 | // We already checked to ensure this condition would not happen. |
| @@ -456,61 +433,51 @@ NNTreeIterator::remove() | @@ -456,61 +433,51 @@ NNTreeIterator::remove() | ||
| 456 | 433 | ||
| 457 | if (path.empty()) { | 434 | if (path.empty()) { |
| 458 | // Special case: if this is the root node, we can leave it empty. | 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 | setItemNumber(impl.oh, -1); | 436 | setItemNumber(impl.oh, -1); |
| 461 | return; | 437 | return; |
| 462 | } | 438 | } |
| 463 | 439 | ||
| 464 | - QTC::TC("qpdf", "NNTree items is empty after remove"); | ||
| 465 | - | ||
| 466 | // We removed the last item from this items array, so we need to remove this node from the | 440 | // We removed the last item from this items array, so we need to remove this node from the |
| 467 | // parent on up the tree. Then we need to position ourselves at the removed item's successor. | 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 | auto element = lastPathElement(); | 443 | auto element = lastPathElement(); |
| 471 | auto parent = element; | 444 | auto parent = element; |
| 472 | --parent; | 445 | --parent; |
| 473 | auto kids = element->node.getKey("/Kids"); | 446 | auto kids = element->node.getKey("/Kids"); |
| 474 | kids.eraseItem(element->kid_number); | 447 | kids.eraseItem(element->kid_number); |
| 475 | - auto nkids = kids.getArrayNItems(); | 448 | + auto nkids = kids.size(); |
| 476 | if (nkids > 0) { | 449 | if (nkids > 0) { |
| 477 | // The logic here is similar to the items case. | 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 | resetLimits(element->node, parent); | 452 | resetLimits(element->node, parent); |
| 481 | } | 453 | } |
| 482 | - if (element->kid_number == nkids) { | 454 | + if (std::cmp_equal(element->kid_number, nkids)) { |
| 483 | // Move to the successor of the last child of the previous kid. | 455 | // Move to the successor of the last child of the previous kid. |
| 484 | - setItemNumber(QPDFObjectHandle(), -1); | 456 | + setItemNumber({}, -1); |
| 485 | --element->kid_number; | 457 | --element->kid_number; |
| 486 | - deepen(kids.getArrayItem(element->kid_number), false, true); | 458 | + deepen(kids[element->kid_number], false, true); |
| 487 | if (valid()) { | 459 | if (valid()) { |
| 488 | increment(false); | 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 | } else { | 463 | } else { |
| 496 | // Next kid is in deleted kid's position | 464 | // Next kid is in deleted kid's position |
| 497 | - QTC::TC("qpdf", "NNTree erased non-last kid"); | ||
| 498 | deepen(kids.getArrayItem(element->kid_number), true, true); | 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 | // We erased the very last item. Convert the root to an empty items array. | 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 | element->node.removeKey("/Kids"); | 472 | element->node.removeKey("/Kids"); |
| 505 | element->node.replaceKey(impl.details.itemsKey(), QPDFObjectHandle::newArray()); | 473 | element->node.replaceKey(impl.details.itemsKey(), QPDFObjectHandle::newArray()); |
| 506 | path.clear(); | 474 | path.clear(); |
| 507 | setItemNumber(impl.oh, -1); | 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,74 +554,64 @@ NNTreeIterator::deepen(QPDFObjectHandle a_node, bool first, bool allow_empty) | ||
| 587 | // items. If we succeed, return true; otherwise return false and leave path alone. | 554 | // items. If we succeed, return true; otherwise return false and leave path alone. |
| 588 | 555 | ||
| 589 | auto opath = path; | 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 | QPDFObjGen::set seen; | 564 | QPDFObjGen::set seen; |
| 593 | for (auto const& i: path) { | 565 | for (auto const& i: path) { |
| 594 | seen.add(i.node); | 566 | seen.add(i.node); |
| 595 | } | 567 | } |
| 596 | - while (!failed) { | 568 | + while (true) { |
| 597 | if (!seen.add(a_node)) { | 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 | if (!a_node.isDictionary()) { | 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 | auto items = a_node.getKey(impl.details.itemsKey()); | 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 | setItemNumber(a_node, first ? 0 : nitems - 2); | 580 | setItemNumber(a_node, first ? 0 : nitems - 2); |
| 617 | break; | 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 | int kid_number = first ? 0 : nkids - 1; | 587 | int kid_number = first ? 0 : nkids - 1; |
| 620 | addPathElement(a_node, kid_number); | 588 | addPathElement(a_node, kid_number); |
| 621 | - auto next = kids.getArrayItem(kid_number); | 589 | + auto next = kids[kid_number]; |
| 622 | if (!next.isIndirect()) { | 590 | if (!next.isIndirect()) { |
| 623 | if (impl.auto_repair) { | 591 | if (impl.auto_repair) { |
| 624 | - QTC::TC("qpdf", "NNTree fix indirect kid"); | ||
| 625 | impl.warn( | 592 | impl.warn( |
| 626 | a_node, | 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 | next = impl.qpdf.makeIndirectObject(next); | 596 | next = impl.qpdf.makeIndirectObject(next); |
| 630 | kids.setArrayItem(kid_number, next); | 597 | kids.setArrayItem(kid_number, next); |
| 631 | } else { | 598 | } else { |
| 632 | - QTC::TC("qpdf", "NNTree warn indirect kid"); | ||
| 633 | impl.warn( | 599 | impl.warn( |
| 634 | a_node, | 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 | a_node = next; | 604 | a_node = next; |
| 640 | } else if (allow_empty && items.isArray()) { | 605 | } else if (allow_empty && items.isArray()) { |
| 641 | - QTC::TC("qpdf", "NNTree deepen found empty"); | ||
| 642 | setItemNumber(a_node, -1); | 606 | setItemNumber(a_node, -1); |
| 643 | break; | 607 | break; |
| 644 | } else { | 608 | } else { |
| 645 | - QTC::TC("qpdf", "NNTree deepen: invalid node"); | ||
| 646 | - impl.warn( | 609 | + return fail( |
| 647 | a_node, | 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 | return true; | 615 | return true; |
| 659 | } | 616 | } |
| 660 | 617 | ||
| @@ -696,103 +653,81 @@ NNTreeImpl::last() | @@ -696,103 +653,81 @@ NNTreeImpl::last() | ||
| 696 | } | 653 | } |
| 697 | 654 | ||
| 698 | int | 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 | auto limits = node.getKey("/Limits"); | 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 | error(node, "node is missing /Limits"); | 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 | int | 671 | int |
| 718 | NNTreeImpl::binarySearch( | 672 | NNTreeImpl::binarySearch( |
| 719 | QPDFObjectHandle key, | 673 | QPDFObjectHandle key, |
| 720 | QPDFObjectHandle items, | 674 | QPDFObjectHandle items, |
| 721 | - int num_items, | 675 | + size_t num_items, |
| 722 | bool return_prev_if_not_found, | 676 | bool return_prev_if_not_found, |
| 723 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)) | 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 | int idx = step; | 683 | int idx = step; |
| 733 | int found_idx = -1; | 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 | status = (this->*compare)(key, items, idx); | 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 | found_idx = idx; | 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 | int | 715 | int |
| 777 | NNTreeImpl::compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx) | 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 | error(oh, ("item at index " + std::to_string(2 * idx) + " is not the right type")); | 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 | int | 724 | int |
| 788 | NNTreeImpl::compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& kids, int idx) | 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 | error(oh, "invalid kid at index " + std::to_string(idx)); | 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 | void | 733 | void |
| @@ -826,28 +761,19 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -826,28 +761,19 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 826 | } | 761 | } |
| 827 | 762 | ||
| 828 | NNTreeImpl::iterator | 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 | auto first_item = begin(); | 766 | auto first_item = begin(); |
| 832 | auto last_item = end(); | 767 | auto last_item = end(); |
| 833 | if (first_item == end()) { | 768 | if (first_item == end()) { |
| 834 | - // Empty | ||
| 835 | return end(); | 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 | details.compareKeys(key, first_item->first) < 0) { | 772 | details.compareKeys(key, first_item->first) < 0) { |
| 839 | // Before the first key | 773 | // Before the first key |
| 840 | return end(); | 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 | QPDFObjGen::set seen; | 778 | QPDFObjGen::set seen; |
| 853 | auto node = oh; | 779 | auto node = oh; |
| @@ -855,48 +781,44 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -855,48 +781,44 @@ NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 855 | 781 | ||
| 856 | while (true) { | 782 | while (true) { |
| 857 | if (!seen.add(node)) { | 783 | if (!seen.add(node)) { |
| 858 | - QTC::TC("qpdf", "NNTree loop in find"); | ||
| 859 | error(node, "loop detected in find"); | 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 | auto items = node.getKey(details.itemsKey()); | 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 | int idx = binarySearch( | 790 | int idx = binarySearch( |
| 868 | key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem); | 791 | key, items, nitems / 2, return_prev_if_not_found, &NNTreeImpl::compareKeyItem); |
| 869 | if (idx >= 0) { | 792 | if (idx >= 0) { |
| 870 | result.setItemNumber(node, 2 * idx); | 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 | int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); | 801 | int idx = binarySearch(key, kids, nkids, true, &NNTreeImpl::compareKeyKid); |
| 875 | if (idx == -1) { | 802 | if (idx == -1) { |
| 876 | - QTC::TC("qpdf", "NNTree -1 in binary search"); | ||
| 877 | error(node, "unexpected -1 from binary search of kids; limits may by wrong"); | 803 | error(node, "unexpected -1 from binary search of kids; limits may by wrong"); |
| 878 | } | 804 | } |
| 879 | result.addPathElement(node, idx); | 805 | result.addPathElement(node, idx); |
| 880 | - node = kids.getArrayItem(idx); | 806 | + node = kids[idx]; |
| 881 | } else { | 807 | } else { |
| 882 | - QTC::TC("qpdf", "NNTree bad node during find"); | ||
| 883 | error(node, "bad node during find"); | 808 | error(node, "bad node during find"); |
| 884 | } | 809 | } |
| 885 | } | 810 | } |
| 886 | - | ||
| 887 | - return result; | ||
| 888 | } | 811 | } |
| 889 | 812 | ||
| 890 | NNTreeImpl::iterator | 813 | NNTreeImpl::iterator |
| 891 | -NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) | 814 | +NNTreeImpl::insertFirst(QPDFObjectHandle const& key, QPDFObjectHandle const& value) |
| 892 | { | 815 | { |
| 893 | auto iter = begin(); | 816 | auto iter = begin(); |
| 894 | QPDFObjectHandle items; | 817 | QPDFObjectHandle items; |
| 895 | if (iter.node.isDictionary()) { | 818 | if (iter.node.isDictionary()) { |
| 896 | items = iter.node.getKey(details.itemsKey()); | 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 | error(oh, "unable to find a valid items node"); | 822 | error(oh, "unable to find a valid items node"); |
| 901 | } | 823 | } |
| 902 | items.insertItem(0, key); | 824 | items.insertItem(0, key); |
| @@ -908,30 +830,26 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) | @@ -908,30 +830,26 @@ NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 908 | } | 830 | } |
| 909 | 831 | ||
| 910 | NNTreeImpl::iterator | 832 | NNTreeImpl::iterator |
| 911 | -NNTreeImpl::insert(QPDFObjectHandle key, QPDFObjectHandle value) | 833 | +NNTreeImpl::insert(QPDFObjectHandle const& key, QPDFObjectHandle const& value) |
| 912 | { | 834 | { |
| 913 | auto iter = find(key, true); | 835 | auto iter = find(key, true); |
| 914 | if (!iter.valid()) { | 836 | if (!iter.valid()) { |
| 915 | - QTC::TC("qpdf", "NNTree insert inserts first"); | ||
| 916 | return insertFirst(key, value); | 837 | return insertFirst(key, value); |
| 917 | } else if (details.compareKeys(key, iter->first) == 0) { | 838 | } else if (details.compareKeys(key, iter->first) == 0) { |
| 918 | - QTC::TC("qpdf", "NNTree insert replaces"); | ||
| 919 | auto items = iter.node.getKey(details.itemsKey()); | 839 | auto items = iter.node.getKey(details.itemsKey()); |
| 920 | items.setArrayItem(iter.item_number + 1, value); | 840 | items.setArrayItem(iter.item_number + 1, value); |
| 921 | iter.updateIValue(); | 841 | iter.updateIValue(); |
| 922 | } else { | 842 | } else { |
| 923 | - QTC::TC("qpdf", "NNTree insert inserts after"); | ||
| 924 | iter.insertAfter(key, value); | 843 | iter.insertAfter(key, value); |
| 925 | } | 844 | } |
| 926 | return iter; | 845 | return iter; |
| 927 | } | 846 | } |
| 928 | 847 | ||
| 929 | bool | 848 | bool |
| 930 | -NNTreeImpl::remove(QPDFObjectHandle key, QPDFObjectHandle* value) | 849 | +NNTreeImpl::remove(QPDFObjectHandle const& key, QPDFObjectHandle* value) |
| 931 | { | 850 | { |
| 932 | auto iter = find(key, false); | 851 | auto iter = find(key, false); |
| 933 | if (!iter.valid()) { | 852 | if (!iter.valid()) { |
| 934 | - QTC::TC("qpdf", "NNTree remove not found"); | ||
| 935 | return false; | 853 | return false; |
| 936 | } | 854 | } |
| 937 | if (value) { | 855 | if (value) { |
libqpdf/QPDF_Array.cc
| @@ -528,3 +528,47 @@ BaseHandle::size() const | @@ -528,3 +528,47 @@ BaseHandle::size() const | ||
| 528 | return 0; // unreachable | 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,7 +56,7 @@ class NNTreeIterator | ||
| 56 | return !operator==(other); | 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 | void remove(); | 60 | void remove(); |
| 61 | 61 | ||
| 62 | private: | 62 | private: |
| @@ -100,9 +100,9 @@ class NNTreeImpl | @@ -100,9 +100,9 @@ class NNTreeImpl | ||
| 100 | iterator end(); | 100 | iterator end(); |
| 101 | iterator last(); | 101 | iterator last(); |
| 102 | iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false); | 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 | // Change the split threshold for easier testing. There's no real reason to expose this to | 107 | // Change the split threshold for easier testing. There's no real reason to expose this to |
| 108 | // downstream tree helpers, but it has to be public so we can call it from the test suite. | 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,18 +110,18 @@ class NNTreeImpl | ||
| 110 | 110 | ||
| 111 | private: | 111 | private: |
| 112 | void repair(); | 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 | int binarySearch( | 115 | int binarySearch( |
| 116 | QPDFObjectHandle key, | 116 | QPDFObjectHandle key, |
| 117 | QPDFObjectHandle items, | 117 | QPDFObjectHandle items, |
| 118 | - int num_items, | 118 | + size_t num_items, |
| 119 | bool return_prev_if_not_found, | 119 | bool return_prev_if_not_found, |
| 120 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)); | 120 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, QPDFObjectHandle& arr, int item)); |
| 121 | int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); | 121 | int compareKeyItem(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); |
| 122 | int compareKeyKid(QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); | 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 | NNTreeDetails const& details; | 126 | NNTreeDetails const& details; |
| 127 | QPDF& qpdf; | 127 | QPDF& qpdf; |
qpdf/qpdf.testcov
| @@ -519,50 +519,9 @@ qpdf-c called qpdf_oh_unparse_resolved 0 | @@ -519,50 +519,9 @@ qpdf-c called qpdf_oh_unparse_resolved 0 | ||
| 519 | qpdf-c called qpdf_oh_unparse_binary 0 | 519 | qpdf-c called qpdf_oh_unparse_binary 0 |
| 520 | QPDFWriter getFilterOnWrite false 0 | 520 | QPDFWriter getFilterOnWrite false 0 |
| 521 | QPDFPageObjectHelper::forEachXObject 3 | 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 | NNTree repair 0 | 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 | NNTree insertAfter inserts first 0 | 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 | QPDFPageObjectHelper unresolved names 0 | 525 | QPDFPageObjectHelper unresolved names 0 |
| 567 | QPDFPageObjectHelper resolving unresolved 0 | 526 | QPDFPageObjectHelper resolving unresolved 0 |
| 568 | QPDFFileSpecObjectHelper empty compat_name 0 | 527 | QPDFFileSpecObjectHelper empty compat_name 0 |