Commit 885674ab9202e439ee3d3f9c71d72c37b796ea4f
Committed by
GitHub
Merge pull request #1502 from m-holger/assert
Refactor `assert_debug.h` to add new debug assertion aliases and upda…
Showing
17 changed files
with
268 additions
and
201 deletions
README-maintainer.md
| @@ -200,7 +200,20 @@ Building docs from pull requests is also enabled. | @@ -200,7 +200,20 @@ Building docs from pull requests is also enabled. | ||
| 200 | 200 | ||
| 201 | * Test code: #include <qpdf/assert_test.h> first. | 201 | * Test code: #include <qpdf/assert_test.h> first. |
| 202 | * Debug code: #include <qpdf/assert_debug.h> first and use | 202 | * Debug code: #include <qpdf/assert_debug.h> first and use |
| 203 | - qpdf_assert_debug instead of assert. | 203 | + qpdf_assert_debug instead of assert. Note that <qpdf/Util.hh> |
| 204 | + includes assert_debug.h. Include this instead if 'At most one | ||
| 205 | + qpdf/assert header ...' errors are encounted, especially when | ||
| 206 | + using assert in private header files. | ||
| 207 | + * Use 'qpdf_expect', 'qpdf_static_expect', 'qpdf_ensures' and | ||
| 208 | + 'qpdf_ionvariant' to document pre/post-conditions and ivariants. | ||
| 209 | + This requires inclusion of 'assert_debug.h' or 'Util.hh'. Remember | ||
| 210 | + that these (except for 'qpdf_static_expect') are only checked in | ||
| 211 | + debug builds. | ||
| 212 | + * Use 'util::assertion' when checks should also be carried out in | ||
| 213 | + release code in preference to throwing logic_errors directly | ||
| 214 | + unless it is practical and desirable to test violations during | ||
| 215 | + CI testing. This avoids obscuring genuine gaps in coverage with | ||
| 216 | + noise generated by unreachable sanity checks. | ||
| 204 | 217 | ||
| 205 | These rules are enforced by the check-assert test. This practices | 218 | These rules are enforced by the check-assert test. This practices |
| 206 | serves to | 219 | serves to |
include/qpdf/ObjectHandle.hh
| @@ -127,6 +127,10 @@ namespace qpdf | @@ -127,6 +127,10 @@ namespace qpdf | ||
| 127 | inline void assign(qpdf_object_type_e required, BaseHandle&& other); | 127 | inline void assign(qpdf_object_type_e required, BaseHandle&& other); |
| 128 | 128 | ||
| 129 | std::string description() const; | 129 | std::string description() const; |
| 130 | + | ||
| 131 | + void no_ci_warn_if(bool condition, std::string const& warning) const; | ||
| 132 | + void no_ci_stop_if(bool condition, std::string const& warning) const; | ||
| 133 | + void no_ci_stop_damaged_if(bool condition, std::string const& warning) const; | ||
| 130 | std::invalid_argument invalid_error(std::string const& method) const; | 134 | std::invalid_argument invalid_error(std::string const& method) const; |
| 131 | std::runtime_error type_error(char const* expected_type) const; | 135 | std::runtime_error type_error(char const* expected_type) const; |
| 132 | QPDFExc type_error(char const* expected_type, std::string const& message) const; | 136 | QPDFExc type_error(char const* expected_type, std::string const& message) const; |
include/qpdf/QPDF.hh
| @@ -874,6 +874,8 @@ class QPDF | @@ -874,6 +874,8 @@ class QPDF | ||
| 874 | std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og); | 874 | std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og); |
| 875 | void resolveObjectsInStream(int obj_stream_number); | 875 | void resolveObjectsInStream(int obj_stream_number); |
| 876 | void stopOnError(std::string const& message); | 876 | void stopOnError(std::string const& message); |
| 877 | + inline void | ||
| 878 | + no_ci_stop_if(bool condition, std::string const& message, std::string const& context = {}); | ||
| 877 | QPDFObjGen nextObjGen(); | 879 | QPDFObjGen nextObjGen(); |
| 878 | QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&); | 880 | QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&); |
| 879 | QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj); | 881 | QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj); |
libqpdf/Pl_Base64.cc
libqpdf/QPDF.cc
| @@ -946,7 +946,7 @@ QPDF::pipeForeignStreamData( | @@ -946,7 +946,7 @@ QPDF::pipeForeignStreamData( | ||
| 946 | } | 946 | } |
| 947 | 947 | ||
| 948 | // Throw a generic exception when we lack context for something more specific. New code should not | 948 | // Throw a generic exception when we lack context for something more specific. New code should not |
| 949 | -// use this. This method exists to improve somewhat from calling assert in very old code. | 949 | +// use this. |
| 950 | void | 950 | void |
| 951 | QPDF::stopOnError(std::string const& message) | 951 | QPDF::stopOnError(std::string const& message) |
| 952 | { | 952 | { |
libqpdf/QPDFObjectHandle.cc
libqpdf/QPDFWriter.cc
libqpdf/QPDF_encryption.cc
libqpdf/QPDF_linearization.cc
| @@ -39,9 +39,9 @@ load_vector_int( | @@ -39,9 +39,9 @@ load_vector_int( | ||
| 39 | } | 39 | } |
| 40 | vec.at(i).*field = bit_stream.getBitsInt(QIntC::to_size(bits_wanted)); | 40 | vec.at(i).*field = bit_stream.getBitsInt(QIntC::to_size(bits_wanted)); |
| 41 | } | 41 | } |
| 42 | - if (QIntC::to_int(vec.size()) != nitems) { | ||
| 43 | - throw std::logic_error("vector has wrong size in load_vector_int"); | ||
| 44 | - } | 42 | + util::assertion( |
| 43 | + std::cmp_equal(vec.size(), nitems), "vector has wrong size in load_vector_int" // | ||
| 44 | + ); | ||
| 45 | // The PDF spec says that each hint table starts at a byte boundary. Each "row" actually must | 45 | // The PDF spec says that each hint table starts at a byte boundary. Each "row" actually must |
| 46 | // start on a byte boundary. | 46 | // start on a byte boundary. |
| 47 | bit_stream.skipToNextByte(); | 47 | bit_stream.skipToNextByte(); |
| @@ -142,13 +142,13 @@ QPDF::isLinearized() | @@ -142,13 +142,13 @@ QPDF::isLinearized() | ||
| 142 | void | 142 | void |
| 143 | QPDF::readLinearizationData() | 143 | QPDF::readLinearizationData() |
| 144 | { | 144 | { |
| 145 | + util::assertion( | ||
| 146 | + isLinearized(), "called readLinearizationData for file that is not linearized" // | ||
| 147 | + ); | ||
| 148 | + | ||
| 145 | // This function throws an exception (which is trapped by checkLinearization()) for any errors | 149 | // This function throws an exception (which is trapped by checkLinearization()) for any errors |
| 146 | // that prevent loading. | 150 | // that prevent loading. |
| 147 | 151 | ||
| 148 | - if (!isLinearized()) { | ||
| 149 | - throw std::logic_error("called readLinearizationData for file that is not linearized"); | ||
| 150 | - } | ||
| 151 | - | ||
| 152 | // /L is read and stored in linp by isLinearized() | 152 | // /L is read and stored in linp by isLinearized() |
| 153 | Array H = m->lindict["/H"]; // hint table offset/length for primary and overflow hint tables | 153 | Array H = m->lindict["/H"]; // hint table offset/length for primary and overflow hint tables |
| 154 | auto H_size = H.size(); | 154 | auto H_size = H.size(); |
| @@ -164,27 +164,33 @@ QPDF::readLinearizationData() | @@ -164,27 +164,33 @@ QPDF::readLinearizationData() | ||
| 164 | Integer P = P_oh; // first page number | 164 | Integer P = P_oh; // first page number |
| 165 | QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1); | 165 | QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1); |
| 166 | 166 | ||
| 167 | - if (!(H && O && E && N && T && (P || P_oh.null()))) { | ||
| 168 | - throw damagedPDF( | ||
| 169 | - "linearization dictionary", | ||
| 170 | - "some keys in linearization dictionary are of the wrong type"); | ||
| 171 | - } | 167 | + no_ci_stop_if( |
| 168 | + !(H && O && E && N && T && (P || P_oh.null())), | ||
| 169 | + "some keys in linearization dictionary are of the wrong type", | ||
| 170 | + "linearization dictionary" // | ||
| 171 | + ); | ||
| 172 | 172 | ||
| 173 | - if (!(H_size == 2 || H_size == 4)) { | ||
| 174 | - throw damagedPDF("linearization dictionary", "H has the wrong number of items"); | ||
| 175 | - } | 173 | + no_ci_stop_if( |
| 174 | + !(H_size == 2 || H_size == 4), | ||
| 175 | + "H has the wrong number of items", | ||
| 176 | + "linearization dictionary" // | ||
| 177 | + ); | ||
| 176 | 178 | ||
| 177 | - if (!(H_0 && H_1 && (H_size == 2 || (H_2 && H_3)))) { | ||
| 178 | - throw damagedPDF("linearization dictionary", "some H items are of the wrong type"); | ||
| 179 | - } | 179 | + no_ci_stop_if( |
| 180 | + !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))), | ||
| 181 | + "some H items are of the wrong type", | ||
| 182 | + "linearization dictionary" // | ||
| 183 | + ); | ||
| 180 | 184 | ||
| 181 | // Store linearization parameter data | 185 | // Store linearization parameter data |
| 182 | 186 | ||
| 183 | // Various places in the code use linp.npages, which is initialized from N, to pre-allocate | 187 | // Various places in the code use linp.npages, which is initialized from N, to pre-allocate |
| 184 | // memory, so make sure it's accurate and bail right now if it's not. | 188 | // memory, so make sure it's accurate and bail right now if it's not. |
| 185 | - if (N != getAllPages().size()) { | ||
| 186 | - throw damagedPDF("linearization hint table", "/N does not match number of pages"); | ||
| 187 | - } | 189 | + no_ci_stop_if( |
| 190 | + N != getAllPages().size(), | ||
| 191 | + "/N does not match number of pages", | ||
| 192 | + "linearization dictionary" // | ||
| 193 | + ); | ||
| 188 | 194 | ||
| 189 | // file_size initialized by isLinearized() | 195 | // file_size initialized by isLinearized() |
| 190 | m->linp.first_page_object = O; | 196 | m->linp.first_page_object = O; |
| @@ -231,9 +237,11 @@ QPDF::readLinearizationData() | @@ -231,9 +237,11 @@ QPDF::readLinearizationData() | ||
| 231 | readHSharedObject(BitStream(h_buf + HSi, h_size - HSi)); | 237 | readHSharedObject(BitStream(h_buf + HSi, h_size - HSi)); |
| 232 | 238 | ||
| 233 | if (HO) { | 239 | if (HO) { |
| 234 | - if (HO < 0 || HO >= h_size) { | ||
| 235 | - throw damagedPDF("linearization hint table", "/O (outline) offset is out of bounds"); | ||
| 236 | - } | 240 | + no_ci_stop_if( |
| 241 | + HO < 0 || HO >= h_size, | ||
| 242 | + "/O (outline) offset is out of bounds", | ||
| 243 | + "linearization dictionary" // | ||
| 244 | + ); | ||
| 237 | size_t HOi = HO; | 245 | size_t HOi = HO; |
| 238 | readHGeneric(BitStream(h_buf + HO, h_size - HOi), m->outline_hints); | 246 | readHGeneric(BitStream(h_buf + HO, h_size - HOi), m->outline_hints); |
| 239 | } | 247 | } |
| @@ -246,9 +254,9 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) | @@ -246,9 +254,9 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) | ||
| 246 | ObjCache& oc = m->obj_cache[H]; | 254 | ObjCache& oc = m->obj_cache[H]; |
| 247 | qpdf_offset_t min_end_offset = oc.end_before_space; | 255 | qpdf_offset_t min_end_offset = oc.end_before_space; |
| 248 | qpdf_offset_t max_end_offset = oc.end_after_space; | 256 | qpdf_offset_t max_end_offset = oc.end_after_space; |
| 249 | - if (!H.isStream()) { | ||
| 250 | - throw damagedPDF("linearization dictionary", "hint table is not a stream"); | ||
| 251 | - } | 257 | + no_ci_stop_if( |
| 258 | + !H.isStream(), "hint table is not a stream", "linearization dictionary" // | ||
| 259 | + ); | ||
| 252 | 260 | ||
| 253 | Dictionary Hdict = H.getDict(); | 261 | Dictionary Hdict = H.getDict(); |
| 254 | 262 | ||
| @@ -264,12 +272,12 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) | @@ -264,12 +272,12 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) | ||
| 264 | QTC::TC("qpdf", "QPDF hint table length direct"); | 272 | QTC::TC("qpdf", "QPDF hint table length direct"); |
| 265 | } | 273 | } |
| 266 | qpdf_offset_t computed_end = offset + toO(length); | 274 | qpdf_offset_t computed_end = offset + toO(length); |
| 267 | - if ((computed_end < min_end_offset) || (computed_end > max_end_offset)) { | ||
| 268 | - linearizationWarning( | ||
| 269 | - "expected = " + std::to_string(computed_end) + | ||
| 270 | - "; actual = " + std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset)); | ||
| 271 | - throw damagedPDF("linearization dictionary", "hint table length mismatch"); | ||
| 272 | - } | 275 | + no_ci_stop_if( |
| 276 | + computed_end < min_end_offset || computed_end > max_end_offset, | ||
| 277 | + "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " + | ||
| 278 | + std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")", | ||
| 279 | + "linearization dictionary" // | ||
| 280 | + ); | ||
| 273 | H.pipeStreamData(&pl, 0, qpdf_dl_specialized); | 281 | H.pipeStreamData(&pl, 0, qpdf_dl_specialized); |
| 274 | return Hdict; | 282 | return Hdict; |
| 275 | } | 283 | } |
| @@ -382,7 +390,6 @@ QPDF::checkLinearizationInternal() | @@ -382,7 +390,6 @@ QPDF::checkLinearizationInternal() | ||
| 382 | // O: object number of first page | 390 | // O: object number of first page |
| 383 | std::vector<QPDFObjectHandle> const& pages = getAllPages(); | 391 | std::vector<QPDFObjectHandle> const& pages = getAllPages(); |
| 384 | if (p.first_page_object != pages.at(0).getObjectID()) { | 392 | if (p.first_page_object != pages.at(0).getObjectID()) { |
| 385 | - QTC::TC("qpdf", "QPDF err /O mismatch"); | ||
| 386 | linearizationWarning("first page object (/O) mismatch"); | 393 | linearizationWarning("first page object (/O) mismatch"); |
| 387 | } | 394 | } |
| 388 | 395 | ||
| @@ -393,13 +400,13 @@ QPDF::checkLinearizationInternal() | @@ -393,13 +400,13 @@ QPDF::checkLinearizationInternal() | ||
| 393 | linearizationWarning("page count (/N) mismatch"); | 400 | linearizationWarning("page count (/N) mismatch"); |
| 394 | } | 401 | } |
| 395 | 402 | ||
| 396 | - for (size_t i = 0; i < npages; ++i) { | ||
| 397 | - QPDFObjectHandle const& page = pages.at(i); | ||
| 398 | - QPDFObjGen og(page.getObjGen()); | ||
| 399 | - if (m->xref_table[og].getType() == 2) { | 403 | + int i = 0; |
| 404 | + for (auto const& page: pages) { | ||
| 405 | + if (m->xref_table[page].getType() == 2) { | ||
| 400 | linearizationWarning( | 406 | linearizationWarning( |
| 401 | "page dictionary for page " + std::to_string(i) + " is compressed"); | 407 | "page dictionary for page " + std::to_string(i) + " is compressed"); |
| 402 | } | 408 | } |
| 409 | + ++i; | ||
| 403 | } | 410 | } |
| 404 | 411 | ||
| 405 | // T: offset of whitespace character preceding xref entry for object 0 | 412 | // T: offset of whitespace character preceding xref entry for object 0 |
| @@ -407,13 +414,12 @@ QPDF::checkLinearizationInternal() | @@ -407,13 +414,12 @@ QPDF::checkLinearizationInternal() | ||
| 407 | while (true) { | 414 | while (true) { |
| 408 | char ch; | 415 | char ch; |
| 409 | m->file->read(&ch, 1); | 416 | m->file->read(&ch, 1); |
| 410 | - if (!((ch == ' ') || (ch == '\r') || (ch == '\n'))) { | 417 | + if (!(ch == ' ' || ch == '\r' || ch == '\n')) { |
| 411 | m->file->seek(-1, SEEK_CUR); | 418 | m->file->seek(-1, SEEK_CUR); |
| 412 | break; | 419 | break; |
| 413 | } | 420 | } |
| 414 | } | 421 | } |
| 415 | if (m->file->tell() != m->first_xref_item_offset) { | 422 | if (m->file->tell() != m->first_xref_item_offset) { |
| 416 | - QTC::TC("qpdf", "QPDF err /T mismatch"); | ||
| 417 | linearizationWarning( | 423 | linearizationWarning( |
| 418 | "space before first xref item (/T) mismatch (computed = " + | 424 | "space before first xref item (/T) mismatch (computed = " + |
| 419 | std::to_string(m->first_xref_item_offset) + | 425 | std::to_string(m->first_xref_item_offset) + |
| @@ -438,9 +444,7 @@ QPDF::checkLinearizationInternal() | @@ -438,9 +444,7 @@ QPDF::checkLinearizationInternal() | ||
| 438 | // to figure out which objects are compressed and which are uncompressed. | 444 | // to figure out which objects are compressed and which are uncompressed. |
| 439 | { // local scope | 445 | { // local scope |
| 440 | std::map<int, int> object_stream_data; | 446 | std::map<int, int> object_stream_data; |
| 441 | - for (auto const& iter: m->xref_table) { | ||
| 442 | - QPDFObjGen const& og = iter.first; | ||
| 443 | - QPDFXRefEntry const& entry = iter.second; | 447 | + for (auto const& [og, entry]: m->xref_table) { |
| 444 | if (entry.getType() == 2) { | 448 | if (entry.getType() == 2) { |
| 445 | object_stream_data[og.getObj()] = entry.getObjStreamNumber(); | 449 | object_stream_data[og.getObj()] = entry.getObjStreamNumber(); |
| 446 | } | 450 | } |
| @@ -457,23 +461,20 @@ QPDF::checkLinearizationInternal() | @@ -457,23 +461,20 @@ QPDF::checkLinearizationInternal() | ||
| 457 | // are present. In that case, it would probably agree with pdlin. As of this writing, the test | 461 | // are present. In that case, it would probably agree with pdlin. As of this writing, the test |
| 458 | // suite doesn't contain any files with threads. | 462 | // suite doesn't contain any files with threads. |
| 459 | 463 | ||
| 460 | - if (m->part6.empty()) { | ||
| 461 | - stopOnError("linearization part 6 unexpectedly empty"); | ||
| 462 | - } | 464 | + no_ci_stop_if( |
| 465 | + m->part6.empty(), "linearization part 6 unexpectedly empty" // | ||
| 466 | + ); | ||
| 463 | qpdf_offset_t min_E = -1; | 467 | qpdf_offset_t min_E = -1; |
| 464 | qpdf_offset_t max_E = -1; | 468 | qpdf_offset_t max_E = -1; |
| 465 | for (auto const& oh: m->part6) { | 469 | for (auto const& oh: m->part6) { |
| 466 | QPDFObjGen og(oh.getObjGen()); | 470 | QPDFObjGen og(oh.getObjGen()); |
| 467 | - if (!m->obj_cache.contains(og)) { | ||
| 468 | - // All objects have to have been dereferenced to be classified. | ||
| 469 | - throw std::logic_error("linearization part6 object not in cache"); | ||
| 470 | - } | 471 | + // All objects have to have been dereferenced to be classified. |
| 472 | + util::assertion(m->obj_cache.contains(og), "linearization part6 object not in cache"); | ||
| 471 | ObjCache const& oc = m->obj_cache[og]; | 473 | ObjCache const& oc = m->obj_cache[og]; |
| 472 | min_E = std::max(min_E, oc.end_before_space); | 474 | min_E = std::max(min_E, oc.end_before_space); |
| 473 | max_E = std::max(max_E, oc.end_after_space); | 475 | max_E = std::max(max_E, oc.end_after_space); |
| 474 | } | 476 | } |
| 475 | - if ((p.first_page_end < min_E) || (p.first_page_end > max_E)) { | ||
| 476 | - QTC::TC("qpdf", "QPDF warn /E mismatch"); | 477 | + if (p.first_page_end < min_E || p.first_page_end > max_E) { |
| 477 | linearizationWarning( | 478 | linearizationWarning( |
| 478 | "end of first page section (/E) mismatch: /E = " + std::to_string(p.first_page_end) + | 479 | "end of first page section (/E) mismatch: /E = " + std::to_string(p.first_page_end) + |
| 479 | "; computed = " + std::to_string(min_E) + ".." + std::to_string(max_E)); | 480 | "; computed = " + std::to_string(min_E) + ".." + std::to_string(max_E)); |
| @@ -490,14 +491,16 @@ QPDF::checkLinearizationInternal() | @@ -490,14 +491,16 @@ QPDF::checkLinearizationInternal() | ||
| 490 | qpdf_offset_t | 491 | qpdf_offset_t |
| 491 | QPDF::maxEnd(ObjUser const& ou) | 492 | QPDF::maxEnd(ObjUser const& ou) |
| 492 | { | 493 | { |
| 493 | - if (!m->obj_user_to_objects.contains(ou)) { | ||
| 494 | - stopOnError("no entry in object user table for requested object user"); | ||
| 495 | - } | 494 | + no_ci_stop_if( |
| 495 | + !m->obj_user_to_objects.contains(ou), | ||
| 496 | + "no entry in object user table for requested object user" // | ||
| 497 | + ); | ||
| 498 | + | ||
| 496 | qpdf_offset_t end = 0; | 499 | qpdf_offset_t end = 0; |
| 497 | for (auto const& og: m->obj_user_to_objects[ou]) { | 500 | for (auto const& og: m->obj_user_to_objects[ou]) { |
| 498 | - if (!m->obj_cache.contains(og)) { | ||
| 499 | - stopOnError("unknown object referenced in object user table"); | ||
| 500 | - } | 501 | + no_ci_stop_if( |
| 502 | + !m->obj_cache.contains(og), "unknown object referenced in object user table" // | ||
| 503 | + ); | ||
| 501 | end = std::max(end, m->obj_cache[og].end_after_space); | 504 | end = std::max(end, m->obj_cache[og].end_after_space); |
| 502 | } | 505 | } |
| 503 | return end; | 506 | return end; |
| @@ -506,34 +509,25 @@ QPDF::maxEnd(ObjUser const& ou) | @@ -506,34 +509,25 @@ QPDF::maxEnd(ObjUser const& ou) | ||
| 506 | qpdf_offset_t | 509 | qpdf_offset_t |
| 507 | QPDF::getLinearizationOffset(QPDFObjGen og) | 510 | QPDF::getLinearizationOffset(QPDFObjGen og) |
| 508 | { | 511 | { |
| 509 | - QPDFXRefEntry entry = m->xref_table[og]; | ||
| 510 | - qpdf_offset_t result = 0; | ||
| 511 | - switch (entry.getType()) { | ||
| 512 | - case 1: | ||
| 513 | - result = entry.getOffset(); | ||
| 514 | - break; | ||
| 515 | - | ||
| 516 | - case 2: | ||
| 517 | - // For compressed objects, return the offset of the object stream that contains them. | ||
| 518 | - result = getLinearizationOffset(QPDFObjGen(entry.getObjStreamNumber(), 0)); | ||
| 519 | - break; | ||
| 520 | - | ||
| 521 | - default: | ||
| 522 | - stopOnError("getLinearizationOffset called for xref entry not of type 1 or 2"); | ||
| 523 | - break; | ||
| 524 | - } | ||
| 525 | - return result; | 512 | + QPDFXRefEntry const& entry = m->xref_table[og]; |
| 513 | + auto typ = entry.getType(); | ||
| 514 | + if (typ == 1) { | ||
| 515 | + return entry.getOffset(); | ||
| 516 | + } | ||
| 517 | + no_ci_stop_if( | ||
| 518 | + typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" // | ||
| 519 | + ); | ||
| 520 | + // For compressed objects, return the offset of the object stream that contains them. | ||
| 521 | + return getLinearizationOffset({entry.getObjStreamNumber(), 0}); | ||
| 526 | } | 522 | } |
| 527 | 523 | ||
| 528 | QPDFObjectHandle | 524 | QPDFObjectHandle |
| 529 | QPDF::getUncompressedObject(QPDFObjectHandle& obj, std::map<int, int> const& object_stream_data) | 525 | QPDF::getUncompressedObject(QPDFObjectHandle& obj, std::map<int, int> const& object_stream_data) |
| 530 | { | 526 | { |
| 531 | - if (obj.null() || (!object_stream_data.contains(obj.getObjectID()))) { | 527 | + if (obj.null() || !object_stream_data.contains(obj.getObjectID())) { |
| 532 | return obj; | 528 | return obj; |
| 533 | - } else { | ||
| 534 | - int repl = (*(object_stream_data.find(obj.getObjectID()))).second; | ||
| 535 | - return getObject(repl, 0); | ||
| 536 | } | 529 | } |
| 530 | + return getObject((*(object_stream_data.find(obj.getObjectID()))).second, 0); | ||
| 537 | } | 531 | } |
| 538 | 532 | ||
| 539 | QPDFObjectHandle | 533 | QPDFObjectHandle |
| @@ -553,14 +547,16 @@ QPDF::lengthNextN(int first_object, int n) | @@ -553,14 +547,16 @@ QPDF::lengthNextN(int first_object, int n) | ||
| 553 | int length = 0; | 547 | int length = 0; |
| 554 | for (int i = 0; i < n; ++i) { | 548 | for (int i = 0; i < n; ++i) { |
| 555 | QPDFObjGen og(first_object + i, 0); | 549 | QPDFObjGen og(first_object + i, 0); |
| 556 | - if (!m->xref_table.contains(og)) { | 550 | + if (m->xref_table.contains(og)) { |
| 551 | + no_ci_stop_if( | ||
| 552 | + !m->obj_cache.contains(og), | ||
| 553 | + "found unknown object while calculating length for linearization data" // | ||
| 554 | + ); | ||
| 555 | + | ||
| 556 | + length += toI(m->obj_cache[og].end_after_space - getLinearizationOffset(og)); | ||
| 557 | + } else { | ||
| 557 | linearizationWarning( | 558 | linearizationWarning( |
| 558 | "no xref table entry for " + std::to_string(first_object + i) + " 0"); | 559 | "no xref table entry for " + std::to_string(first_object + i) + " 0"); |
| 559 | - } else { | ||
| 560 | - if (!m->obj_cache.contains(og)) { | ||
| 561 | - stopOnError("found unknown object while calculating length for linearization data"); | ||
| 562 | - } | ||
| 563 | - length += toI(m->obj_cache[og].end_after_space - getLinearizationOffset(og)); | ||
| 564 | } | 560 | } |
| 565 | } | 561 | } |
| 566 | return length; | 562 | return length; |
| @@ -629,7 +625,7 @@ QPDF::checkHPageOffset( | @@ -629,7 +625,7 @@ QPDF::checkHPageOffset( | ||
| 629 | std::set<int> hint_shared; | 625 | std::set<int> hint_shared; |
| 630 | std::set<int> computed_shared; | 626 | std::set<int> computed_shared; |
| 631 | 627 | ||
| 632 | - if ((pageno == 0) && (he.nshared_objects > 0)) { | 628 | + if (pageno == 0 && he.nshared_objects > 0) { |
| 633 | // pdlin and Acrobat both do this even though the spec states clearly and unambiguously | 629 | // pdlin and Acrobat both do this even though the spec states clearly and unambiguously |
| 634 | // that they should not. | 630 | // that they should not. |
| 635 | linearizationWarning("page 0 has shared identifier entries"); | 631 | linearizationWarning("page 0 has shared identifier entries"); |
| @@ -637,17 +633,20 @@ QPDF::checkHPageOffset( | @@ -637,17 +633,20 @@ QPDF::checkHPageOffset( | ||
| 637 | 633 | ||
| 638 | for (size_t i = 0; i < toS(he.nshared_objects); ++i) { | 634 | for (size_t i = 0; i < toS(he.nshared_objects); ++i) { |
| 639 | int idx = he.shared_identifiers.at(i); | 635 | int idx = he.shared_identifiers.at(i); |
| 640 | - if (!shared_idx_to_obj.contains(idx)) { | ||
| 641 | - stopOnError("unable to get object for item in shared objects hint table"); | ||
| 642 | - } | 636 | + no_ci_stop_if( |
| 637 | + !shared_idx_to_obj.contains(idx), | ||
| 638 | + "unable to get object for item in shared objects hint table"); | ||
| 639 | + | ||
| 643 | hint_shared.insert(shared_idx_to_obj[idx]); | 640 | hint_shared.insert(shared_idx_to_obj[idx]); |
| 644 | } | 641 | } |
| 645 | 642 | ||
| 646 | for (size_t i = 0; i < toS(ce.nshared_objects); ++i) { | 643 | for (size_t i = 0; i < toS(ce.nshared_objects); ++i) { |
| 647 | int idx = ce.shared_identifiers.at(i); | 644 | int idx = ce.shared_identifiers.at(i); |
| 648 | - if (idx >= m->c_shared_object_data.nshared_total) { | ||
| 649 | - stopOnError("index out of bounds for shared object hint table"); | ||
| 650 | - } | 645 | + no_ci_stop_if( |
| 646 | + idx >= m->c_shared_object_data.nshared_total, | ||
| 647 | + "index out of bounds for shared object hint table" // | ||
| 648 | + ); | ||
| 649 | + | ||
| 651 | int obj = m->c_shared_object_data.entries.at(toS(idx)).object; | 650 | int obj = m->c_shared_object_data.entries.at(toS(idx)).object; |
| 652 | computed_shared.insert(obj); | 651 | computed_shared.insert(obj); |
| 653 | } | 652 | } |
| @@ -766,9 +765,9 @@ QPDF::checkHOutlines() | @@ -766,9 +765,9 @@ QPDF::checkHOutlines() | ||
| 766 | return; | 765 | return; |
| 767 | } | 766 | } |
| 768 | QPDFObjGen og(outlines.getObjGen()); | 767 | QPDFObjGen og(outlines.getObjGen()); |
| 769 | - if (!m->xref_table.contains(og)) { | ||
| 770 | - stopOnError("unknown object in outlines hint table"); | ||
| 771 | - } | 768 | + no_ci_stop_if( |
| 769 | + !m->xref_table.contains(og), "unknown object in outlines hint table" // | ||
| 770 | + ); | ||
| 772 | qpdf_offset_t offset = getLinearizationOffset(og); | 771 | qpdf_offset_t offset = getLinearizationOffset(og); |
| 773 | ObjUser ou(ObjUser::ou_root_key, "/Outlines"); | 772 | ObjUser ou(ObjUser::ou_root_key, "/Outlines"); |
| 774 | int length = toI(maxEnd(ou) - offset); | 773 | int length = toI(maxEnd(ou) - offset); |
| @@ -926,12 +925,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -926,12 +925,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 926 | // file must be optimized (via calling optimize()) prior to calling this function. Note that | 925 | // file must be optimized (via calling optimize()) prior to calling this function. Note that |
| 927 | // actual offsets and lengths are not computed here, but anything related to object ordering is. | 926 | // actual offsets and lengths are not computed here, but anything related to object ordering is. |
| 928 | 927 | ||
| 929 | - if (m->object_to_obj_users.empty()) { | ||
| 930 | - // Note that we can't call optimize here because we don't know whether it should be called | ||
| 931 | - // with or without allow changes. | ||
| 932 | - throw std::logic_error( | ||
| 933 | - "INTERNAL ERROR: QPDF::calculateLinearizationData called before optimize()"); | ||
| 934 | - } | 928 | + util::assertion( |
| 929 | + !m->object_to_obj_users.empty(), | ||
| 930 | + "INTERNAL ERROR: QPDF::calculateLinearizationData called before optimize()" // | ||
| 931 | + ); | ||
| 932 | + // Note that we can't call optimize here because we don't know whether it should be called | ||
| 933 | + // with or without allow changes. | ||
| 935 | 934 | ||
| 936 | // Separate objects into the categories sufficient for us to determine which part of the | 935 | // Separate objects into the categories sufficient for us to determine which part of the |
| 937 | // linearized file should contain the object. This categorization is useful for other purposes | 936 | // linearized file should contain the object. This categorization is useful for other purposes |
| @@ -1108,7 +1107,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1108,7 +1107,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1108 | // Map all page objects to the containing object stream. This should be a no-op in a | 1107 | // Map all page objects to the containing object stream. This should be a no-op in a |
| 1109 | // properly linearized file. | 1108 | // properly linearized file. |
| 1110 | for (auto oh: getAllPages()) { | 1109 | for (auto oh: getAllPages()) { |
| 1111 | - pages.push_back(getUncompressedObject(oh, object_stream_data)); | 1110 | + pages.emplace_back(getUncompressedObject(oh, object_stream_data)); |
| 1112 | } | 1111 | } |
| 1113 | } | 1112 | } |
| 1114 | size_t npages = pages.size(); | 1113 | size_t npages = pages.size(); |
| @@ -1126,12 +1125,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1126,12 +1125,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1126 | 1125 | ||
| 1127 | // Part 4: open document objects. We don't care about the order. | 1126 | // Part 4: open document objects. We don't care about the order. |
| 1128 | 1127 | ||
| 1129 | - if (lc_root.size() != 1) { | ||
| 1130 | - stopOnError("found other than one root while calculating linearization data"); | ||
| 1131 | - } | ||
| 1132 | - m->part4.push_back(getObject(*(lc_root.begin()))); | 1128 | + no_ci_stop_if( |
| 1129 | + lc_root.size() != 1, "found other than one root while calculating linearization data" // | ||
| 1130 | + ); | ||
| 1131 | + | ||
| 1132 | + m->part4.emplace_back(getObject(*(lc_root.begin()))); | ||
| 1133 | for (auto const& og: lc_open_document) { | 1133 | for (auto const& og: lc_open_document) { |
| 1134 | - m->part4.push_back(getObject(og)); | 1134 | + m->part4.emplace_back(getObject(og)); |
| 1135 | } | 1135 | } |
| 1136 | 1136 | ||
| 1137 | // Part 6: first page objects. Note: implementation note 124 states that Acrobat always treats | 1137 | // Part 6: first page objects. Note: implementation note 124 states that Acrobat always treats |
| @@ -1139,29 +1139,26 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1139,29 +1139,26 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1139 | // any option to set this and also disregards /OpenAction. We will do the same. | 1139 | // any option to set this and also disregards /OpenAction. We will do the same. |
| 1140 | 1140 | ||
| 1141 | // First, place the actual first page object itself. | 1141 | // First, place the actual first page object itself. |
| 1142 | - if (pages.empty()) { | ||
| 1143 | - stopOnError("no pages found while calculating linearization data"); | ||
| 1144 | - } | 1142 | + no_ci_stop_if( |
| 1143 | + pages.empty(), "no pages found while calculating linearization data" // | ||
| 1144 | + ); | ||
| 1145 | QPDFObjGen first_page_og(pages.at(0).getObjGen()); | 1145 | QPDFObjGen first_page_og(pages.at(0).getObjGen()); |
| 1146 | - if (!lc_first_page_private.contains(first_page_og)) { | ||
| 1147 | - stopOnError( | ||
| 1148 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: first page " | ||
| 1149 | - "object not in lc_first_page_private"); | ||
| 1150 | - } | ||
| 1151 | - lc_first_page_private.erase(first_page_og); | 1146 | + no_ci_stop_if( |
| 1147 | + !lc_first_page_private.erase(first_page_og), "unable to linearize first page" // | ||
| 1148 | + ); | ||
| 1152 | m->c_linp.first_page_object = pages.at(0).getObjectID(); | 1149 | m->c_linp.first_page_object = pages.at(0).getObjectID(); |
| 1153 | - m->part6.push_back(pages.at(0)); | 1150 | + m->part6.emplace_back(pages.at(0)); |
| 1154 | 1151 | ||
| 1155 | // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard | 1152 | // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard |
| 1156 | // it except to the extent that it groups private and shared objects contiguously for the sake | 1153 | // it except to the extent that it groups private and shared objects contiguously for the sake |
| 1157 | // of hint tables. | 1154 | // of hint tables. |
| 1158 | 1155 | ||
| 1159 | for (auto const& og: lc_first_page_private) { | 1156 | for (auto const& og: lc_first_page_private) { |
| 1160 | - m->part6.push_back(getObject(og)); | 1157 | + m->part6.emplace_back(getObject(og)); |
| 1161 | } | 1158 | } |
| 1162 | 1159 | ||
| 1163 | for (auto const& og: lc_first_page_shared) { | 1160 | for (auto const& og: lc_first_page_shared) { |
| 1164 | - m->part6.push_back(getObject(og)); | 1161 | + m->part6.emplace_back(getObject(og)); |
| 1165 | } | 1162 | } |
| 1166 | 1163 | ||
| 1167 | // Place the outline dictionary if it goes in the first page section. | 1164 | // Place the outline dictionary if it goes in the first page section. |
| @@ -1182,13 +1179,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1182,13 +1179,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1182 | // Place this page's page object | 1179 | // Place this page's page object |
| 1183 | 1180 | ||
| 1184 | QPDFObjGen page_og(pages.at(i).getObjGen()); | 1181 | QPDFObjGen page_og(pages.at(i).getObjGen()); |
| 1185 | - if (!lc_other_page_private.contains(page_og)) { | ||
| 1186 | - stopOnError( | ||
| 1187 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: page object for page " + | ||
| 1188 | - std::to_string(i) + " not in lc_other_page_private"); | ||
| 1189 | - } | ||
| 1190 | - lc_other_page_private.erase(page_og); | ||
| 1191 | - m->part7.push_back(pages.at(i)); | 1182 | + no_ci_stop_if( |
| 1183 | + !lc_other_page_private.erase(page_og), | ||
| 1184 | + "unable to linearize page " + std::to_string(i) // | ||
| 1185 | + ); | ||
| 1186 | + | ||
| 1187 | + m->part7.emplace_back(pages.at(i)); | ||
| 1192 | 1188 | ||
| 1193 | // Place all non-shared objects referenced by this page, updating the page object count for | 1189 | // Place all non-shared objects referenced by this page, updating the page object count for |
| 1194 | // the hint table. | 1190 | // the hint table. |
| @@ -1196,29 +1192,30 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1196,29 +1192,30 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1196 | m->c_page_offset_data.entries.at(i).nobjects = 1; | 1192 | m->c_page_offset_data.entries.at(i).nobjects = 1; |
| 1197 | 1193 | ||
| 1198 | ObjUser ou(ObjUser::ou_page, i); | 1194 | ObjUser ou(ObjUser::ou_page, i); |
| 1199 | - if (!m->obj_user_to_objects.contains(ou)) { | ||
| 1200 | - stopOnError("found unreferenced page while calculating linearization data"); | ||
| 1201 | - } | 1195 | + no_ci_stop_if( |
| 1196 | + !m->obj_user_to_objects.contains(ou), | ||
| 1197 | + "found unreferenced page while calculating linearization data" // | ||
| 1198 | + ); | ||
| 1199 | + | ||
| 1202 | for (auto const& og: m->obj_user_to_objects[ou]) { | 1200 | for (auto const& og: m->obj_user_to_objects[ou]) { |
| 1203 | - if (lc_other_page_private.contains(og)) { | ||
| 1204 | - lc_other_page_private.erase(og); | ||
| 1205 | - m->part7.push_back(getObject(og)); | 1201 | + if (lc_other_page_private.erase(og)) { |
| 1202 | + m->part7.emplace_back(getObject(og)); | ||
| 1206 | ++m->c_page_offset_data.entries.at(i).nobjects; | 1203 | ++m->c_page_offset_data.entries.at(i).nobjects; |
| 1207 | } | 1204 | } |
| 1208 | } | 1205 | } |
| 1209 | } | 1206 | } |
| 1210 | // That should have covered all part7 objects. | 1207 | // That should have covered all part7 objects. |
| 1211 | - if (!lc_other_page_private.empty()) { | ||
| 1212 | - stopOnError( | ||
| 1213 | - "INTERNAL ERROR: QPDF::calculateLinearizationData:" | ||
| 1214 | - " lc_other_page_private is not empty after generation of part7"); | ||
| 1215 | - } | 1208 | + util::assertion( |
| 1209 | + lc_other_page_private.empty(), | ||
| 1210 | + "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_other_page_private is not empty " | ||
| 1211 | + "after generation of part7" // | ||
| 1212 | + ); | ||
| 1216 | 1213 | ||
| 1217 | // Part 8: other pages' shared objects | 1214 | // Part 8: other pages' shared objects |
| 1218 | 1215 | ||
| 1219 | // Order is unimportant. | 1216 | // Order is unimportant. |
| 1220 | for (auto const& og: lc_other_page_shared) { | 1217 | for (auto const& og: lc_other_page_shared) { |
| 1221 | - m->part8.push_back(getObject(og)); | 1218 | + m->part8.emplace_back(getObject(og)); |
| 1222 | } | 1219 | } |
| 1223 | 1220 | ||
| 1224 | // Part 9: other objects | 1221 | // Part 9: other objects |
| @@ -1231,13 +1228,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1231,13 +1228,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1231 | // Place the pages tree. | 1228 | // Place the pages tree. |
| 1232 | std::set<QPDFObjGen> pages_ogs = | 1229 | std::set<QPDFObjGen> pages_ogs = |
| 1233 | m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")]; | 1230 | m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")]; |
| 1234 | - if (pages_ogs.empty()) { | ||
| 1235 | - stopOnError("found empty pages tree while calculating linearization data"); | ||
| 1236 | - } | 1231 | + no_ci_stop_if( |
| 1232 | + pages_ogs.empty(), "found empty pages tree while calculating linearization data" // | ||
| 1233 | + ); | ||
| 1237 | for (auto const& og: pages_ogs) { | 1234 | for (auto const& og: pages_ogs) { |
| 1238 | - if (lc_other.contains(og)) { | ||
| 1239 | - lc_other.erase(og); | ||
| 1240 | - m->part9.push_back(getObject(og)); | 1235 | + if (lc_other.erase(og)) { |
| 1236 | + m->part9.emplace_back(getObject(og)); | ||
| 1241 | } | 1237 | } |
| 1242 | } | 1238 | } |
| 1243 | 1239 | ||
| @@ -1263,15 +1259,15 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1263,15 +1259,15 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1263 | } | 1259 | } |
| 1264 | } | 1260 | } |
| 1265 | } | 1261 | } |
| 1266 | - if (!lc_thumbnail_private.empty()) { | ||
| 1267 | - stopOnError( | ||
| 1268 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_thumbnail_private not " | ||
| 1269 | - "empty after placing thumbnails"); | ||
| 1270 | - } | 1262 | + util::assertion( |
| 1263 | + lc_thumbnail_private.empty(), | ||
| 1264 | + "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_thumbnail_private not " | ||
| 1265 | + "empty after placing thumbnails" // | ||
| 1266 | + ); | ||
| 1271 | 1267 | ||
| 1272 | // Place shared thumbnail objects | 1268 | // Place shared thumbnail objects |
| 1273 | for (auto const& og: lc_thumbnail_shared) { | 1269 | for (auto const& og: lc_thumbnail_shared) { |
| 1274 | - m->part9.push_back(getObject(og)); | 1270 | + m->part9.emplace_back(getObject(og)); |
| 1275 | } | 1271 | } |
| 1276 | 1272 | ||
| 1277 | // Place outlines unless in first page | 1273 | // Place outlines unless in first page |
| @@ -1281,7 +1277,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1281,7 +1277,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1281 | 1277 | ||
| 1282 | // Place all remaining objects | 1278 | // Place all remaining objects |
| 1283 | for (auto const& og: lc_other) { | 1279 | for (auto const& og: lc_other) { |
| 1284 | - m->part9.push_back(getObject(og)); | 1280 | + m->part9.emplace_back(getObject(og)); |
| 1285 | } | 1281 | } |
| 1286 | 1282 | ||
| 1287 | // Make sure we got everything exactly once. | 1283 | // Make sure we got everything exactly once. |
| @@ -1289,12 +1285,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1289,12 +1285,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1289 | size_t num_placed = | 1285 | size_t num_placed = |
| 1290 | m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size(); | 1286 | m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size(); |
| 1291 | size_t num_wanted = m->object_to_obj_users.size(); | 1287 | size_t num_wanted = m->object_to_obj_users.size(); |
| 1292 | - if (num_placed != num_wanted) { | ||
| 1293 | - stopOnError( | ||
| 1294 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: wrong " | ||
| 1295 | - "number of objects placed (num_placed = " + | ||
| 1296 | - std::to_string(num_placed) + "; number of objects: " + std::to_string(num_wanted)); | ||
| 1297 | - } | 1288 | + no_ci_stop_if( |
| 1289 | + // This can happen with damaged files, e.g. if the root is part of the the pages tree. | ||
| 1290 | + num_placed != num_wanted, | ||
| 1291 | + "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " + | ||
| 1292 | + std::to_string(num_placed) + "; number of objects: " + std::to_string(num_wanted) + | ||
| 1293 | + "\nIf the file did not generate any other warnings please report this as a bug." // | ||
| 1294 | + ); | ||
| 1298 | 1295 | ||
| 1299 | // Calculate shared object hint table information including references to shared objects from | 1296 | // Calculate shared object hint table information including references to shared objects from |
| 1300 | // page offset hint data. | 1297 | // page offset hint data. |
| @@ -1326,19 +1323,22 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | @@ -1326,19 +1323,22 @@ QPDF::calculateLinearizationData(T const& object_stream_data) | ||
| 1326 | shared.emplace_back(obj); | 1323 | shared.emplace_back(obj); |
| 1327 | } | 1324 | } |
| 1328 | } | 1325 | } |
| 1329 | - if (static_cast<size_t>(m->c_shared_object_data.nshared_total) != | ||
| 1330 | - m->c_shared_object_data.entries.size()) { | ||
| 1331 | - stopOnError("shared object hint table has wrong number of entries"); | ||
| 1332 | - } | 1326 | + no_ci_stop_if( |
| 1327 | + std::cmp_not_equal( | ||
| 1328 | + m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()), | ||
| 1329 | + "shared object hint table has wrong number of entries" // | ||
| 1330 | + ); | ||
| 1333 | 1331 | ||
| 1334 | // Now compute the list of shared objects for each page after the first page. | 1332 | // Now compute the list of shared objects for each page after the first page. |
| 1335 | 1333 | ||
| 1336 | for (size_t i = 1; i < npages; ++i) { | 1334 | for (size_t i = 1; i < npages; ++i) { |
| 1337 | CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i); | 1335 | CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i); |
| 1338 | ObjUser ou(ObjUser::ou_page, i); | 1336 | ObjUser ou(ObjUser::ou_page, i); |
| 1339 | - if (!m->obj_user_to_objects.contains(ou)) { | ||
| 1340 | - stopOnError("found unreferenced page while calculating linearization data"); | ||
| 1341 | - } | 1337 | + no_ci_stop_if( |
| 1338 | + !m->obj_user_to_objects.contains(ou), | ||
| 1339 | + "found unreferenced page while calculating linearization data" // | ||
| 1340 | + ); | ||
| 1341 | + | ||
| 1342 | for (auto const& og: m->obj_user_to_objects[ou]) { | 1342 | for (auto const& og: m->obj_user_to_objects[ou]) { |
| 1343 | if ((m->object_to_obj_users[og].size() > 1) && (obj_to_index.contains(og.getObj()))) { | 1343 | if ((m->object_to_obj_users[og].size() > 1) && (obj_to_index.contains(og.getObj()))) { |
| 1344 | int idx = obj_to_index[og.getObj()]; | 1344 | int idx = obj_to_index[og.getObj()]; |
| @@ -1372,7 +1372,7 @@ QPDF::pushOutlinesToPart( | @@ -1372,7 +1372,7 @@ QPDF::pushOutlinesToPart( | ||
| 1372 | if (lc_outlines.erase(outlines_og)) { | 1372 | if (lc_outlines.erase(outlines_og)) { |
| 1373 | // Make sure outlines is in lc_outlines in case the file is damaged. in which case it may be | 1373 | // Make sure outlines is in lc_outlines in case the file is damaged. in which case it may be |
| 1374 | // included in an earlier part. | 1374 | // included in an earlier part. |
| 1375 | - part.push_back(outlines); | 1375 | + part.emplace_back(outlines); |
| 1376 | m->c_outline_data.first_object = outlines_og.getObj(); | 1376 | m->c_outline_data.first_object = outlines_og.getObj(); |
| 1377 | m->c_outline_data.nobjects = 1; | 1377 | m->c_outline_data.nobjects = 1; |
| 1378 | } | 1378 | } |
| @@ -1380,7 +1380,7 @@ QPDF::pushOutlinesToPart( | @@ -1380,7 +1380,7 @@ QPDF::pushOutlinesToPart( | ||
| 1380 | if (!m->c_outline_data.first_object) { | 1380 | if (!m->c_outline_data.first_object) { |
| 1381 | m->c_outline_data.first_object = og.getObj(); | 1381 | m->c_outline_data.first_object = og.getObj(); |
| 1382 | } | 1382 | } |
| 1383 | - part.push_back(getObject(og)); | 1383 | + part.emplace_back(getObject(og)); |
| 1384 | ++m->c_outline_data.nobjects; | 1384 | ++m->c_outline_data.nobjects; |
| 1385 | } | 1385 | } |
| 1386 | } | 1386 | } |
| @@ -1417,15 +1417,14 @@ QPDF::outputLengthNextN( | @@ -1417,15 +1417,14 @@ QPDF::outputLengthNextN( | ||
| 1417 | 1417 | ||
| 1418 | int first = obj[in_object].renumber; | 1418 | int first = obj[in_object].renumber; |
| 1419 | int last = first + n; | 1419 | int last = first + n; |
| 1420 | - if (first <= 0) { | ||
| 1421 | - stopOnError("found object that is not renumbered while writing linearization data"); | ||
| 1422 | - } | 1420 | + no_ci_stop_if( |
| 1421 | + first <= 0, "found object that is not renumbered while writing linearization data"); | ||
| 1423 | qpdf_offset_t length = 0; | 1422 | qpdf_offset_t length = 0; |
| 1424 | for (int i = first; i < last; ++i) { | 1423 | for (int i = first; i < last; ++i) { |
| 1425 | auto l = new_obj[i].length; | 1424 | auto l = new_obj[i].length; |
| 1426 | - if (l == 0) { | ||
| 1427 | - stopOnError("found item with unknown length while writing linearization data"); | ||
| 1428 | - } | 1425 | + no_ci_stop_if( |
| 1426 | + l == 0, "found item with unknown length while writing linearization data" // | ||
| 1427 | + ); | ||
| 1429 | length += l; | 1428 | length += l; |
| 1430 | } | 1429 | } |
| 1431 | return toI(length); | 1430 | return toI(length); |
| @@ -1536,9 +1535,9 @@ QPDF::calculateHSharedObject( | @@ -1536,9 +1535,9 @@ QPDF::calculateHSharedObject( | ||
| 1536 | soe.emplace_back(); | 1535 | soe.emplace_back(); |
| 1537 | soe.at(i).delta_group_length = length; | 1536 | soe.at(i).delta_group_length = length; |
| 1538 | } | 1537 | } |
| 1539 | - if (soe.size() != toS(cso.nshared_total)) { | ||
| 1540 | - stopOnError("soe has wrong size after initialization"); | ||
| 1541 | - } | 1538 | + no_ci_stop_if( |
| 1539 | + soe.size() != toS(cso.nshared_total), "soe has wrong size after initialization" // | ||
| 1540 | + ); | ||
| 1542 | 1541 | ||
| 1543 | so.nshared_total = cso.nshared_total; | 1542 | so.nshared_total = cso.nshared_total; |
| 1544 | so.nshared_first_page = cso.nshared_first_page; | 1543 | so.nshared_first_page = cso.nshared_first_page; |
| @@ -1552,9 +1551,11 @@ QPDF::calculateHSharedObject( | @@ -1552,9 +1551,11 @@ QPDF::calculateHSharedObject( | ||
| 1552 | 1551 | ||
| 1553 | for (size_t i = 0; i < toS(cso.nshared_total); ++i) { | 1552 | for (size_t i = 0; i < toS(cso.nshared_total); ++i) { |
| 1554 | // Adjust deltas | 1553 | // Adjust deltas |
| 1555 | - if (soe.at(i).delta_group_length < min_length) { | ||
| 1556 | - stopOnError("found too small group length while writing linearization data"); | ||
| 1557 | - } | 1554 | + no_ci_stop_if( |
| 1555 | + soe.at(i).delta_group_length < min_length, | ||
| 1556 | + "found too small group length while writing linearization data" // | ||
| 1557 | + ); | ||
| 1558 | + | ||
| 1558 | soe.at(i).delta_group_length -= min_length; | 1559 | soe.at(i).delta_group_length -= min_length; |
| 1559 | } | 1560 | } |
| 1560 | } | 1561 | } |
libqpdf/QPDF_optimization.cc
libqpdf/qpdf/InputSource_private.hh
libqpdf/qpdf/Pipeline_private.hh
| 1 | #ifndef PIPELINE_PRIVATE_HH | 1 | #ifndef PIPELINE_PRIVATE_HH |
| 2 | #define PIPELINE_PRIVATE_HH | 2 | #define PIPELINE_PRIVATE_HH |
| 3 | 3 | ||
| 4 | +#include <qpdf/Types.h> | ||
| 5 | + | ||
| 4 | #include <qpdf/Pipeline.hh> | 6 | #include <qpdf/Pipeline.hh> |
| 5 | 7 | ||
| 6 | #include <qpdf/Pl_Flate.hh> | 8 | #include <qpdf/Pl_Flate.hh> |
| 7 | -#include <qpdf/Types.h> | 9 | +#include <qpdf/Util.hh> |
| 8 | 10 | ||
| 9 | namespace qpdf::pl | 11 | namespace qpdf::pl |
| 10 | { | 12 | { |
libqpdf/qpdf/QPDFObjectHandle_private.hh
| @@ -624,6 +624,33 @@ namespace qpdf | @@ -624,6 +624,33 @@ namespace qpdf | ||
| 624 | return obj ? obj->og.isIndirect() : false; | 624 | return obj ? obj->og.isIndirect() : false; |
| 625 | } | 625 | } |
| 626 | 626 | ||
| 627 | + inline void | ||
| 628 | + BaseHandle::no_ci_stop_if(bool condition, std::string const& message) const | ||
| 629 | + { | ||
| 630 | + if (condition) { | ||
| 631 | + if (qpdf()) { | ||
| 632 | + throw QPDFExc(qpdf_e_damaged_pdf, "", description(), 0, message); | ||
| 633 | + } | ||
| 634 | + throw std::runtime_error(message); | ||
| 635 | + } | ||
| 636 | + } | ||
| 637 | + | ||
| 638 | + inline void | ||
| 639 | + BaseHandle::no_ci_stop_damaged_if(bool condition, std::string const& message) const | ||
| 640 | + { | ||
| 641 | + if (condition) { | ||
| 642 | + throw std::runtime_error(message); | ||
| 643 | + } | ||
| 644 | + } | ||
| 645 | + | ||
| 646 | + inline void | ||
| 647 | + BaseHandle::no_ci_warn_if(bool condition, std::string const& warning) const | ||
| 648 | + { | ||
| 649 | + if (condition) { | ||
| 650 | + warn(warning); | ||
| 651 | + } | ||
| 652 | + } | ||
| 653 | + | ||
| 627 | inline bool | 654 | inline bool |
| 628 | BaseHandle::null() const | 655 | BaseHandle::null() const |
| 629 | { | 656 | { |
libqpdf/qpdf/QPDF_private.hh
| @@ -628,4 +628,13 @@ QPDF::page_labels() | @@ -628,4 +628,13 @@ QPDF::page_labels() | ||
| 628 | return *m->page_labels; | 628 | return *m->page_labels; |
| 629 | } | 629 | } |
| 630 | 630 | ||
| 631 | +// Throw a generic exception for unusual error conditions that do not be covered during CI testing. | ||
| 632 | +inline void | ||
| 633 | +QPDF::no_ci_stop_if(bool condition, std::string const& message, std::string const& context) | ||
| 634 | +{ | ||
| 635 | + if (condition) { | ||
| 636 | + throw damagedPDF(context, message); | ||
| 637 | + } | ||
| 638 | +} | ||
| 639 | + | ||
| 631 | #endif // QPDF_PRIVATE_HH | 640 | #endif // QPDF_PRIVATE_HH |
libqpdf/qpdf/Util.hh
| 1 | #ifndef UTIL_HH | 1 | #ifndef UTIL_HH |
| 2 | #define UTIL_HH | 2 | #define UTIL_HH |
| 3 | 3 | ||
| 4 | +#include <qpdf/assert_debug.h> | ||
| 5 | + | ||
| 6 | +#include <stdexcept> | ||
| 4 | #include <string> | 7 | #include <string> |
| 8 | +#include <utility> | ||
| 5 | 9 | ||
| 6 | namespace qpdf::util | 10 | namespace qpdf::util |
| 7 | { | 11 | { |
| 8 | - // This is a collection of useful utility functions for qpdf internal use. They include inline | ||
| 9 | - // functions, some of which are exposed as regular functions in QUtil. Implementations are in | ||
| 10 | - // QUtil.cc. | 12 | + // qpdf::util is a collection of useful utility functions for qpdf internal use. It includes |
| 13 | + // inline functions, some of which are exposed as regular functions in QUtil. Implementations | ||
| 14 | + // are in QUtil.cc. | ||
| 15 | + | ||
| 16 | + // Throw a logic_error if 'cond' does not hold. | ||
| 17 | + // | ||
| 18 | + // DO NOT USE unless it is impractical or unnecessary to cover violations during CI Testing. | ||
| 19 | + inline void | ||
| 20 | + assertion(bool cond, std::string const msg) | ||
| 21 | + { | ||
| 22 | + if (!cond) { | ||
| 23 | + throw std::logic_error(msg); | ||
| 24 | + } | ||
| 25 | + } | ||
| 11 | 26 | ||
| 12 | inline constexpr char | 27 | inline constexpr char |
| 13 | hex_decode_char(char digit) | 28 | hex_decode_char(char digit) |
libqpdf/qpdf/assert_debug.h
| @@ -12,7 +12,15 @@ | @@ -12,7 +12,15 @@ | ||
| 12 | #else | 12 | #else |
| 13 | # define QPDF_ASSERT_H | 13 | # define QPDF_ASSERT_H |
| 14 | 14 | ||
| 15 | -# include <assert.h> | 15 | +# include <cassert> |
| 16 | # define qpdf_assert_debug assert | 16 | # define qpdf_assert_debug assert |
| 17 | +// Alias for assert. Pre-condition is only enforced in debug builds. | ||
| 18 | +# define qpdf_expect assert | ||
| 19 | +// Alias for assert. Post-condition is only enforced in debug builds. | ||
| 20 | +# define qpdf_ensures assert | ||
| 21 | +// Alias for assert. Invariant is only enforced in debug builds. | ||
| 22 | +# define qpdf_invariant assert | ||
| 23 | +// Alias for static_assert. | ||
| 24 | +# define qpdf_static_expect static_assert | ||
| 17 | 25 | ||
| 18 | #endif /* QPDF_ASSERT_H */ | 26 | #endif /* QPDF_ASSERT_H */ |
qpdf/qpdf.testcov
| @@ -10,8 +10,6 @@ QPDF object stream contains id < 1 0 | @@ -10,8 +10,6 @@ QPDF object stream contains id < 1 0 | ||
| 10 | QPDF hint table length direct 0 | 10 | QPDF hint table length direct 0 |
| 11 | QPDF P absent in lindict 1 | 11 | QPDF P absent in lindict 1 |
| 12 | QPDF expected n n obj 0 | 12 | QPDF expected n n obj 0 |
| 13 | -QPDF err /T mismatch 0 | ||
| 14 | -QPDF err /O mismatch 0 | ||
| 15 | QPDF opt direct pages resource 1 | 13 | QPDF opt direct pages resource 1 |
| 16 | QPDF opt inheritable keys 0 | 14 | QPDF opt inheritable keys 0 |
| 17 | QPDF opt no inheritable keys 0 | 15 | QPDF opt no inheritable keys 0 |
| @@ -22,7 +20,6 @@ QPDF opt key ancestors depth > 1 0 | @@ -22,7 +20,6 @@ QPDF opt key ancestors depth > 1 0 | ||
| 22 | QPDF opt loop detected 0 | 20 | QPDF opt loop detected 0 |
| 23 | QPDF categorize pagemode present 1 | 21 | QPDF categorize pagemode present 1 |
| 24 | QPDF categorize pagemode outlines 1 | 22 | QPDF categorize pagemode outlines 1 |
| 25 | -QPDF warn /E mismatch 0 | ||
| 26 | QPDF lin outlines in part 1 | 23 | QPDF lin outlines in part 1 |
| 27 | QPDF lin nshared_total > nshared_first_page 1 | 24 | QPDF lin nshared_total > nshared_first_page 1 |
| 28 | QPDF lin part 8 empty 1 | 25 | QPDF lin part 8 empty 1 |