Commit 885674ab9202e439ee3d3f9c71d72c37b796ea4f

Authored by m-holger
Committed by GitHub
2 parents 2f232e8e 0476b7cc

Merge pull request #1502 from m-holger/assert

Refactor `assert_debug.h` to add new debug assertion aliases and upda…
README-maintainer.md
... ... @@ -200,7 +200,20 @@ Building docs from pull requests is also enabled.
200 200  
201 201 * Test code: #include <qpdf/assert_test.h> first.
202 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 218 These rules are enforced by the check-assert test. This practices
206 219 serves to
... ...
include/qpdf/ObjectHandle.hh
... ... @@ -127,6 +127,10 @@ namespace qpdf
127 127 inline void assign(qpdf_object_type_e required, BaseHandle&& other);
128 128  
129 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 134 std::invalid_argument invalid_error(std::string const& method) const;
131 135 std::runtime_error type_error(char const* expected_type) const;
132 136 QPDFExc type_error(char const* expected_type, std::string const& message) const;
... ...
include/qpdf/QPDF.hh
... ... @@ -874,6 +874,8 @@ class QPDF
874 874 std::shared_ptr<QPDFObject> const& resolve(QPDFObjGen og);
875 875 void resolveObjectsInStream(int obj_stream_number);
876 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 879 QPDFObjGen nextObjGen();
878 880 QPDFObjectHandle newIndirect(QPDFObjGen, std::shared_ptr<QPDFObject> const&);
879 881 QPDFObjectHandle makeIndirectFromQPDFObject(std::shared_ptr<QPDFObject> const& obj);
... ...
libqpdf/Pl_Base64.cc
1   -#include <qpdf/assert_debug.h>
2   -
3 1 #include <qpdf/Pl_Base64.hh>
4 2  
5 3 #include <qpdf/QIntC.hh>
6   -#include <qpdf/QUtil.hh>
7 4 #include <qpdf/Util.hh>
8 5  
9 6 #include <cstring>
... ...
libqpdf/QPDF.cc
... ... @@ -946,7 +946,7 @@ QPDF::pipeForeignStreamData(
946 946 }
947 947  
948 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 950 void
951 951 QPDF::stopOnError(std::string const& message)
952 952 {
... ...
libqpdf/QPDFObjectHandle.cc
1   -#include <qpdf/assert_debug.h>
2   -
3 1 #include <qpdf/QPDFObjectHandle_private.hh>
4 2  
5 3 #include <qpdf/JSON_writer.hh>
... ...
libqpdf/QPDFWriter.cc
1   -#include <qpdf/assert_debug.h>
2   -
3 1 #include <qpdf/qpdf-config.h> // include early for large file support
4 2  
5 3 #include <qpdf/QPDFWriter_private.hh>
... ...
libqpdf/QPDF_encryption.cc
1   -// This file implements methods from the QPDF class that involve
2   -// encryption.
3   -
4   -#include <qpdf/assert_debug.h>
  1 +// This file implements methods from the QPDF class that involve encryption.
5 2  
6 3 #include <qpdf/QPDF_private.hh>
7 4  
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -39,9 +39,9 @@ load_vector_int(
39 39 }
40 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 45 // The PDF spec says that each hint table starts at a byte boundary. Each "row" actually must
46 46 // start on a byte boundary.
47 47 bit_stream.skipToNextByte();
... ... @@ -142,13 +142,13 @@ QPDF::isLinearized()
142 142 void
143 143 QPDF::readLinearizationData()
144 144 {
  145 + util::assertion(
  146 + isLinearized(), "called readLinearizationData for file that is not linearized" //
  147 + );
  148 +
145 149 // This function throws an exception (which is trapped by checkLinearization()) for any errors
146 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 152 // /L is read and stored in linp by isLinearized()
153 153 Array H = m->lindict["/H"]; // hint table offset/length for primary and overflow hint tables
154 154 auto H_size = H.size();
... ... @@ -164,27 +164,33 @@ QPDF::readLinearizationData()
164 164 Integer P = P_oh; // first page number
165 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 185 // Store linearization parameter data
182 186  
183 187 // Various places in the code use linp.npages, which is initialized from N, to pre-allocate
184 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 195 // file_size initialized by isLinearized()
190 196 m->linp.first_page_object = O;
... ... @@ -231,9 +237,11 @@ QPDF::readLinearizationData()
231 237 readHSharedObject(BitStream(h_buf + HSi, h_size - HSi));
232 238  
233 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 245 size_t HOi = HO;
238 246 readHGeneric(BitStream(h_buf + HO, h_size - HOi), m->outline_hints);
239 247 }
... ... @@ -246,9 +254,9 @@ QPDF::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
246 254 ObjCache& oc = m->obj_cache[H];
247 255 qpdf_offset_t min_end_offset = oc.end_before_space;
248 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 261 Dictionary Hdict = H.getDict();
254 262  
... ... @@ -264,12 +272,12 @@ QPDF::readHintStream(Pipeline&amp; pl, qpdf_offset_t offset, size_t length)
264 272 QTC::TC("qpdf", "QPDF hint table length direct");
265 273 }
266 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 281 H.pipeStreamData(&pl, 0, qpdf_dl_specialized);
274 282 return Hdict;
275 283 }
... ... @@ -382,7 +390,6 @@ QPDF::checkLinearizationInternal()
382 390 // O: object number of first page
383 391 std::vector<QPDFObjectHandle> const& pages = getAllPages();
384 392 if (p.first_page_object != pages.at(0).getObjectID()) {
385   - QTC::TC("qpdf", "QPDF err /O mismatch");
386 393 linearizationWarning("first page object (/O) mismatch");
387 394 }
388 395  
... ... @@ -393,13 +400,13 @@ QPDF::checkLinearizationInternal()
393 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 406 linearizationWarning(
401 407 "page dictionary for page " + std::to_string(i) + " is compressed");
402 408 }
  409 + ++i;
403 410 }
404 411  
405 412 // T: offset of whitespace character preceding xref entry for object 0
... ... @@ -407,13 +414,12 @@ QPDF::checkLinearizationInternal()
407 414 while (true) {
408 415 char ch;
409 416 m->file->read(&ch, 1);
410   - if (!((ch == ' ') || (ch == '\r') || (ch == '\n'))) {
  417 + if (!(ch == ' ' || ch == '\r' || ch == '\n')) {
411 418 m->file->seek(-1, SEEK_CUR);
412 419 break;
413 420 }
414 421 }
415 422 if (m->file->tell() != m->first_xref_item_offset) {
416   - QTC::TC("qpdf", "QPDF err /T mismatch");
417 423 linearizationWarning(
418 424 "space before first xref item (/T) mismatch (computed = " +
419 425 std::to_string(m->first_xref_item_offset) +
... ... @@ -438,9 +444,7 @@ QPDF::checkLinearizationInternal()
438 444 // to figure out which objects are compressed and which are uncompressed.
439 445 { // local scope
440 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 448 if (entry.getType() == 2) {
445 449 object_stream_data[og.getObj()] = entry.getObjStreamNumber();
446 450 }
... ... @@ -457,23 +461,20 @@ QPDF::checkLinearizationInternal()
457 461 // are present. In that case, it would probably agree with pdlin. As of this writing, the test
458 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 467 qpdf_offset_t min_E = -1;
464 468 qpdf_offset_t max_E = -1;
465 469 for (auto const& oh: m->part6) {
466 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 473 ObjCache const& oc = m->obj_cache[og];
472 474 min_E = std::max(min_E, oc.end_before_space);
473 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 478 linearizationWarning(
478 479 "end of first page section (/E) mismatch: /E = " + std::to_string(p.first_page_end) +
479 480 "; computed = " + std::to_string(min_E) + ".." + std::to_string(max_E));
... ... @@ -490,14 +491,16 @@ QPDF::checkLinearizationInternal()
490 491 qpdf_offset_t
491 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 499 qpdf_offset_t end = 0;
497 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 504 end = std::max(end, m->obj_cache[og].end_after_space);
502 505 }
503 506 return end;
... ... @@ -506,34 +509,25 @@ QPDF::maxEnd(ObjUser const&amp; ou)
506 509 qpdf_offset_t
507 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 524 QPDFObjectHandle
529 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 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 533 QPDFObjectHandle
... ... @@ -553,14 +547,16 @@ QPDF::lengthNextN(int first_object, int n)
553 547 int length = 0;
554 548 for (int i = 0; i < n; ++i) {
555 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 558 linearizationWarning(
558 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 562 return length;
... ... @@ -629,7 +625,7 @@ QPDF::checkHPageOffset(
629 625 std::set<int> hint_shared;
630 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 629 // pdlin and Acrobat both do this even though the spec states clearly and unambiguously
634 630 // that they should not.
635 631 linearizationWarning("page 0 has shared identifier entries");
... ... @@ -637,17 +633,20 @@ QPDF::checkHPageOffset(
637 633  
638 634 for (size_t i = 0; i < toS(he.nshared_objects); ++i) {
639 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 640 hint_shared.insert(shared_idx_to_obj[idx]);
644 641 }
645 642  
646 643 for (size_t i = 0; i < toS(ce.nshared_objects); ++i) {
647 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 650 int obj = m->c_shared_object_data.entries.at(toS(idx)).object;
652 651 computed_shared.insert(obj);
653 652 }
... ... @@ -766,9 +765,9 @@ QPDF::checkHOutlines()
766 765 return;
767 766 }
768 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 771 qpdf_offset_t offset = getLinearizationOffset(og);
773 772 ObjUser ou(ObjUser::ou_root_key, "/Outlines");
774 773 int length = toI(maxEnd(ou) - offset);
... ... @@ -926,12 +925,12 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
926 925 // file must be optimized (via calling optimize()) prior to calling this function. Note that
927 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 935 // Separate objects into the categories sufficient for us to determine which part of the
937 936 // linearized file should contain the object. This categorization is useful for other purposes
... ... @@ -1108,7 +1107,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1108 1107 // Map all page objects to the containing object stream. This should be a no-op in a
1109 1108 // properly linearized file.
1110 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 1113 size_t npages = pages.size();
... ... @@ -1126,12 +1125,13 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1126 1125  
1127 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 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 1137 // Part 6: first page objects. Note: implementation note 124 states that Acrobat always treats
... ... @@ -1139,29 +1139,26 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1139 1139 // any option to set this and also disregards /OpenAction. We will do the same.
1140 1140  
1141 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 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 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 1152 // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard
1156 1153 // it except to the extent that it groups private and shared objects contiguously for the sake
1157 1154 // of hint tables.
1158 1155  
1159 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 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 1164 // Place the outline dictionary if it goes in the first page section.
... ... @@ -1182,13 +1179,12 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1182 1179 // Place this page's page object
1183 1180  
1184 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 1189 // Place all non-shared objects referenced by this page, updating the page object count for
1194 1190 // the hint table.
... ... @@ -1196,29 +1192,30 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1196 1192 m->c_page_offset_data.entries.at(i).nobjects = 1;
1197 1193  
1198 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 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 1203 ++m->c_page_offset_data.entries.at(i).nobjects;
1207 1204 }
1208 1205 }
1209 1206 }
1210 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 1214 // Part 8: other pages' shared objects
1218 1215  
1219 1216 // Order is unimportant.
1220 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 1221 // Part 9: other objects
... ... @@ -1231,13 +1228,12 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1231 1228 // Place the pages tree.
1232 1229 std::set<QPDFObjGen> pages_ogs =
1233 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 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&amp; 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 1268 // Place shared thumbnail objects
1273 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 1273 // Place outlines unless in first page
... ... @@ -1281,7 +1277,7 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1281 1277  
1282 1278 // Place all remaining objects
1283 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 1283 // Make sure we got everything exactly once.
... ... @@ -1289,12 +1285,13 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1289 1285 size_t num_placed =
1290 1286 m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size();
1291 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 1296 // Calculate shared object hint table information including references to shared objects from
1300 1297 // page offset hint data.
... ... @@ -1326,19 +1323,22 @@ QPDF::calculateLinearizationData(T const&amp; object_stream_data)
1326 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 1332 // Now compute the list of shared objects for each page after the first page.
1335 1333  
1336 1334 for (size_t i = 1; i < npages; ++i) {
1337 1335 CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i);
1338 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 1342 for (auto const& og: m->obj_user_to_objects[ou]) {
1343 1343 if ((m->object_to_obj_users[og].size() > 1) && (obj_to_index.contains(og.getObj()))) {
1344 1344 int idx = obj_to_index[og.getObj()];
... ... @@ -1372,7 +1372,7 @@ QPDF::pushOutlinesToPart(
1372 1372 if (lc_outlines.erase(outlines_og)) {
1373 1373 // Make sure outlines is in lc_outlines in case the file is damaged. in which case it may be
1374 1374 // included in an earlier part.
1375   - part.push_back(outlines);
  1375 + part.emplace_back(outlines);
1376 1376 m->c_outline_data.first_object = outlines_og.getObj();
1377 1377 m->c_outline_data.nobjects = 1;
1378 1378 }
... ... @@ -1380,7 +1380,7 @@ QPDF::pushOutlinesToPart(
1380 1380 if (!m->c_outline_data.first_object) {
1381 1381 m->c_outline_data.first_object = og.getObj();
1382 1382 }
1383   - part.push_back(getObject(og));
  1383 + part.emplace_back(getObject(og));
1384 1384 ++m->c_outline_data.nobjects;
1385 1385 }
1386 1386 }
... ... @@ -1417,15 +1417,14 @@ QPDF::outputLengthNextN(
1417 1417  
1418 1418 int first = obj[in_object].renumber;
1419 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 1422 qpdf_offset_t length = 0;
1424 1423 for (int i = first; i < last; ++i) {
1425 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 1428 length += l;
1430 1429 }
1431 1430 return toI(length);
... ... @@ -1536,9 +1535,9 @@ QPDF::calculateHSharedObject(
1536 1535 soe.emplace_back();
1537 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 1542 so.nshared_total = cso.nshared_total;
1544 1543 so.nshared_first_page = cso.nshared_first_page;
... ... @@ -1552,9 +1551,11 @@ QPDF::calculateHSharedObject(
1552 1551  
1553 1552 for (size_t i = 0; i < toS(cso.nshared_total); ++i) {
1554 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 1559 soe.at(i).delta_group_length -= min_length;
1559 1560 }
1560 1561 }
... ...
libqpdf/QPDF_optimization.cc
1 1 // See the "Optimization" section of the manual.
2 2  
3   -#include <qpdf/assert_debug.h>
4   -
5 3 #include <qpdf/QPDF_private.hh>
6 4  
7 5 #include <qpdf/QPDFExc.hh>
... ...
libqpdf/qpdf/InputSource_private.hh
... ... @@ -3,6 +3,7 @@
3 3  
4 4 #include <qpdf/Buffer.hh>
5 5 #include <qpdf/InputSource.hh>
  6 +#include <qpdf/Util.hh>
6 7  
7 8 #include <limits>
8 9 #include <sstream>
... ...
libqpdf/qpdf/Pipeline_private.hh
1 1 #ifndef PIPELINE_PRIVATE_HH
2 2 #define PIPELINE_PRIVATE_HH
3 3  
  4 +#include <qpdf/Types.h>
  5 +
4 6 #include <qpdf/Pipeline.hh>
5 7  
6 8 #include <qpdf/Pl_Flate.hh>
7   -#include <qpdf/Types.h>
  9 +#include <qpdf/Util.hh>
8 10  
9 11 namespace qpdf::pl
10 12 {
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -624,6 +624,33 @@ namespace qpdf
624 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 654 inline bool
628 655 BaseHandle::null() const
629 656 {
... ...
libqpdf/qpdf/QPDF_private.hh
... ... @@ -628,4 +628,13 @@ QPDF::page_labels()
628 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 640 #endif // QPDF_PRIVATE_HH
... ...
libqpdf/qpdf/Util.hh
1 1 #ifndef UTIL_HH
2 2 #define UTIL_HH
3 3  
  4 +#include <qpdf/assert_debug.h>
  5 +
  6 +#include <stdexcept>
4 7 #include <string>
  8 +#include <utility>
5 9  
6 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 27 inline constexpr char
13 28 hex_decode_char(char digit)
... ...
libqpdf/qpdf/assert_debug.h
... ... @@ -12,7 +12,15 @@
12 12 #else
13 13 # define QPDF_ASSERT_H
14 14  
15   -# include <assert.h>
  15 +# include <cassert>
16 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 26 #endif /* QPDF_ASSERT_H */
... ...
qpdf/qpdf.testcov
... ... @@ -10,8 +10,6 @@ QPDF object stream contains id &lt; 1 0
10 10 QPDF hint table length direct 0
11 11 QPDF P absent in lindict 1
12 12 QPDF expected n n obj 0
13   -QPDF err /T mismatch 0
14   -QPDF err /O mismatch 0
15 13 QPDF opt direct pages resource 1
16 14 QPDF opt inheritable keys 0
17 15 QPDF opt no inheritable keys 0
... ... @@ -22,7 +20,6 @@ QPDF opt key ancestors depth &gt; 1 0
22 20 QPDF opt loop detected 0
23 21 QPDF categorize pagemode present 1
24 22 QPDF categorize pagemode outlines 1
25   -QPDF warn /E mismatch 0
26 23 QPDF lin outlines in part 1
27 24 QPDF lin nshared_total > nshared_first_page 1
28 25 QPDF lin part 8 empty 1
... ...