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,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
1 -#include <qpdf/assert_debug.h>  
2 -  
3 #include <qpdf/Pl_Base64.hh> 1 #include <qpdf/Pl_Base64.hh>
4 2
5 #include <qpdf/QIntC.hh> 3 #include <qpdf/QIntC.hh>
6 -#include <qpdf/QUtil.hh>  
7 #include <qpdf/Util.hh> 4 #include <qpdf/Util.hh>
8 5
9 #include <cstring> 6 #include <cstring>
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
1 -#include <qpdf/assert_debug.h>  
2 -  
3 #include <qpdf/QPDFObjectHandle_private.hh> 1 #include <qpdf/QPDFObjectHandle_private.hh>
4 2
5 #include <qpdf/JSON_writer.hh> 3 #include <qpdf/JSON_writer.hh>
libqpdf/QPDFWriter.cc
1 -#include <qpdf/assert_debug.h>  
2 -  
3 #include <qpdf/qpdf-config.h> // include early for large file support 1 #include <qpdf/qpdf-config.h> // include early for large file support
4 2
5 #include <qpdf/QPDFWriter_private.hh> 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 #include <qpdf/QPDF_private.hh> 3 #include <qpdf/QPDF_private.hh>
7 4
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&amp; pl, qpdf_offset_t offset, size_t length) @@ -246,9 +254,9 @@ QPDF::readHintStream(Pipeline&amp; 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&amp; pl, qpdf_offset_t offset, size_t length) @@ -264,12 +272,12 @@ QPDF::readHintStream(Pipeline&amp; 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&amp; ou) @@ -506,34 +509,25 @@ QPDF::maxEnd(ObjUser const&amp; 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&amp; object_stream_data) @@ -926,12 +925,12 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1108,7 +1107,7 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1126,12 +1125,13 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1139,29 +1139,26 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1182,13 +1179,12 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1196,29 +1192,30 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1231,13 +1228,12 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -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 // 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&amp; object_stream_data) @@ -1281,7 +1277,7 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1289,12 +1285,13 @@ QPDF::calculateLinearizationData(T const&amp; 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&amp; object_stream_data) @@ -1326,19 +1323,22 @@ QPDF::calculateLinearizationData(T const&amp; 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
1 // See the "Optimization" section of the manual. 1 // See the "Optimization" section of the manual.
2 2
3 -#include <qpdf/assert_debug.h>  
4 -  
5 #include <qpdf/QPDF_private.hh> 3 #include <qpdf/QPDF_private.hh>
6 4
7 #include <qpdf/QPDFExc.hh> 5 #include <qpdf/QPDFExc.hh>
libqpdf/qpdf/InputSource_private.hh
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 3
4 #include <qpdf/Buffer.hh> 4 #include <qpdf/Buffer.hh>
5 #include <qpdf/InputSource.hh> 5 #include <qpdf/InputSource.hh>
  6 +#include <qpdf/Util.hh>
6 7
7 #include <limits> 8 #include <limits>
8 #include <sstream> 9 #include <sstream>
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 &lt; 1 0 @@ -10,8 +10,6 @@ QPDF object stream contains id &lt; 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 &gt; 1 0 @@ -22,7 +20,6 @@ QPDF opt key ancestors depth &gt; 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