Commit 0583a50769b1d4e0f27dafe1d13af7af76cb40cb

Authored by m-holger
1 parent ca24c235

Refactor integer handling and type conversions

Simplify integer value retrieval by introducing a templated `value` method in the `Integer` class. Replace redundant exception handling logic across multiple functions with this unified approach. Streamline type conversions and improve readability.
libqpdf/QPDF.cc
... ... @@ -636,8 +636,7 @@ int
636 636 QPDF::getExtensionLevel()
637 637 {
638 638 if (Integer ExtensionLevel = getRoot()["/Extensions"]["/ADBE"]["/ExtensionLevel"]) {
639   - int result = ExtensionLevel;
640   - return result;
  639 + return ExtensionLevel.value<int>();
641 640 }
642 641 return 0;
643 642 }
... ...
libqpdf/QPDFEFStreamObjectHelper.cc
... ... @@ -59,8 +59,7 @@ size_t
59 59 QPDFEFStreamObjectHelper::getSize()
60 60 {
61 61 if (Integer Size = getParam("/Size")) {
62   - size_t result = Size;
63   - return result;
  62 + return Size.value<size_t>();
64 63 }
65 64 return 0;
66 65 }
... ...
libqpdf/QPDFObjectHandle.cc
... ... @@ -852,13 +852,7 @@ int
852 852 QPDFObjectHandle::getIntValueAsInt() const
853 853 {
854 854 try {
855   - return Integer(*this);
856   - } catch (std::underflow_error&) {
857   - warn("requested value of integer is too small; returning INT_MIN");
858   - return INT_MIN;
859   - } catch (std::overflow_error&) {
860   - warn("requested value of integer is too big; returning INT_MAX");
861   - return INT_MAX;
  855 + return Integer(*this).value<int>();
862 856 } catch (std::invalid_argument&) {
863 857 typeWarning("integer", "returning 0");
864 858 return 0;
... ... @@ -879,10 +873,7 @@ unsigned long long
879 873 QPDFObjectHandle::getUIntValue() const
880 874 {
881 875 try {
882   - return Integer(*this);
883   - } catch (std::underflow_error&) {
884   - warn("unsigned value request for negative number; returning 0");
885   - return 0;
  876 + return Integer(*this).value<unsigned long long>();
886 877 } catch (std::invalid_argument&) {
887 878 typeWarning("integer", "returning 0");
888 879 return 0;
... ... @@ -903,13 +894,7 @@ unsigned int
903 894 QPDFObjectHandle::getUIntValueAsUInt() const
904 895 {
905 896 try {
906   - return Integer(*this);
907   - } catch (std::underflow_error&) {
908   - warn("unsigned integer value request for negative number; returning 0");
909   - return 0;
910   - } catch (std::overflow_error&) {
911   - warn("requested value of unsigned integer is too big; returning UINT_MAX");
912   - return UINT_MAX;
  897 + return Integer(*this).value<unsigned int>();
913 898 } catch (std::invalid_argument&) {
914 899 typeWarning("integer", "returning 0");
915 900 return 0;
... ...
libqpdf/QPDF_linearization.cc
... ... @@ -487,20 +487,20 @@ Lin::readLinearizationData()
487 487 );
488 488  
489 489 // file_size initialized by isLinearized()
490   - linp_.first_page_object = O;
  490 + linp_.first_page_object = O.value<int>();
491 491 linp_.first_page_end = E;
492   - linp_.npages = N;
  492 + linp_.npages = N.value<size_t>();
493 493 linp_.xref_zero_offset = T;
494   - linp_.first_page = P ? P : 0;
  494 + linp_.first_page = P ? P.value<int>() : 0;
495 495 linp_.H_offset = H_0;
496 496 linp_.H_length = H_1;
497 497  
498 498 // Read hint streams
499 499  
500 500 Pl_Buffer pb("hint buffer");
501   - auto H0 = readHintStream(pb, H_0, H_1);
  501 + auto H0 = readHintStream(pb, H_0, H_1.value<size_t>());
502 502 if (H_2) {
503   - (void)readHintStream(pb, H_2, H_3);
  503 + (void)readHintStream(pb, H_2, H_3.value<size_t>());
504 504 }
505 505  
506 506 // PDF 1.4 hint tables that we ignore:
... ... @@ -524,7 +524,7 @@ Lin::readLinearizationData()
524 524  
525 525 readHPageOffset(BitStream(h_buf, h_size));
526 526  
527   - size_t HSi = HS;
  527 + size_t HSi = HS.value<size_t>();
528 528 if (HSi < 0 || HSi >= h_size) {
529 529 throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds");
530 530 }
... ... @@ -536,7 +536,7 @@ Lin::readLinearizationData()
536 536 "/O (outline) offset is out of bounds",
537 537 "linearization dictionary" //
538 538 );
539   - size_t HOi = HO;
  539 + size_t HOi = HO.value<size_t>();
540 540 readHGeneric(BitStream(h_buf + HO, h_size - HOi), outline_hints_);
541 541 }
542 542 }
... ...
libqpdf/qpdf/QPDFObjectHandle_private.hh
... ... @@ -345,6 +345,42 @@ namespace qpdf
345 345 // std::invalid_argument exception.
346 346 int64_t value() const;
347 347  
  348 + // Return the integer value. If the object is not a valid integer, throw a
  349 + // std::invalid_argument exception. If the object is out of range for the target type,
  350 + // replicate the existing QPDFObjectHandle behavior.
  351 + template <std::integral T>
  352 + T
  353 + value() const
  354 + {
  355 + try {
  356 + return static_cast<T>(*this);
  357 + } catch (std::underflow_error&) {
  358 + if constexpr (std::is_same_v<T, int>) {
  359 + warn("requested value of integer is too small; returning INT_MIN");
  360 + } else if constexpr (std::is_same_v<T, unsigned int>) {
  361 + warn("unsigned integer value request for negative number; returning 0");
  362 + } else if constexpr (std::is_same_v<T, unsigned long long>) {
  363 + warn("unsigned value request for negative number; returning 0");
  364 + } else {
  365 + warn(
  366 + "underflow while converting integer object; returning smallest possible "
  367 + "value");
  368 + }
  369 + return std::numeric_limits<T>::min();
  370 + } catch (std::overflow_error&) {
  371 + if constexpr (std::is_same_v<T, int>) {
  372 + warn("requested value of integer is too big; returning INT_MAX");
  373 + } else if constexpr (std::is_same_v<T, unsigned int>) {
  374 + warn("requested value of unsigned integer is too big; returning UINT_MAX");
  375 + } else {
  376 + warn(
  377 + "overflow while converting integer object; returning largest possible "
  378 + "value");
  379 + }
  380 + return std::numeric_limits<T>::max();
  381 + }
  382 + }
  383 +
348 384 // Return true if object value is equal to the 'rhs' value. Return false if the object is
349 385 // not a valid Integer.
350 386 friend bool
... ... @@ -367,7 +403,7 @@ namespace qpdf
367 403 return std::cmp_greater(lhs.value(), rhs) ? std::strong_ordering::greater
368 404 : std::strong_ordering::equal;
369 405 }
370   - };
  406 + }; // class Integer
371 407  
372 408 bool
373 409 operator==(std::integral auto lhs, Integer const& rhs)
... ...
libtests/CMakeLists.txt
... ... @@ -24,6 +24,7 @@ set(TEST_PROGRAMS
24 24 md5
25 25 nntree
26 26 numrange
  27 + objects
27 28 obj_table
28 29 pdf_version
29 30 pl_function
... ...
libtests/objects.cc 0 โ†’ 100644
  1 +#include <qpdf/assert_test.h>
  2 +
  3 +// This program tests miscellaneous object handle functionality
  4 +
  5 +#include <qpdf/QPDF.hh>
  6 +
  7 +#include <qpdf/QIntC.hh>
  8 +#include <qpdf/QPDFObjectHandle_private.hh>
  9 +#include <qpdf/QUtil.hh>
  10 +
  11 +#include <climits>
  12 +#include <cstdio>
  13 +#include <cstdlib>
  14 +#include <cstring>
  15 +#include <iostream>
  16 +#include <map>
  17 +
  18 +static char const* whoami = nullptr;
  19 +
  20 +void
  21 +usage()
  22 +{
  23 + std::cerr << "Usage: " << whoami << " n filename1 [arg2]" << '\n';
  24 + exit(2);
  25 +}
  26 +
  27 +#define assert_compare_numbers(expected, expr) compare_numbers(#expr, expected, expr)
  28 +
  29 +template <typename T1, typename T2>
  30 +static void
  31 +compare_numbers(char const* description, T1 const& expected, T2 const& actual)
  32 +{
  33 + if (expected != actual) {
  34 + std::cerr << description << ": expected = " << expected << "; actual = " << actual << '\n';
  35 + }
  36 +}
  37 +
  38 +static void
  39 +test_0(QPDF& pdf, char const* arg2)
  40 +{
  41 + // Test int size checks. This test will fail if int and long long are the same size.
  42 + QPDFObjectHandle t = pdf.getTrailer();
  43 + unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX);
  44 + long long q1 = QIntC::to_longlong(q1_l);
  45 + long long q2_l = 3LL * QIntC::to_longlong(INT_MIN);
  46 + long long q2 = QIntC::to_longlong(q2_l);
  47 + unsigned int q3_i = UINT_MAX;
  48 + long long q3 = QIntC::to_longlong(q3_i);
  49 + t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1));
  50 + t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2));
  51 + t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3));
  52 + assert_compare_numbers(q1, t.getKey("/Q1").getIntValue());
  53 + assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue());
  54 + assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt());
  55 + try {
  56 + assert_compare_numbers(0u, QPDFObjectHandle::newNull().getUIntValueAsUInt());
  57 + std::cerr << "convert null to uint did not throw\n";
  58 + } catch (QPDFExc const&) {
  59 + std::cerr << "caught expected type error\n";
  60 + }
  61 + assert_compare_numbers(std::numeric_limits<int8_t>::max(), Integer(q1).value<int8_t>());
  62 + assert_compare_numbers(std::numeric_limits<int8_t>::min(), Integer(-q1).value<int8_t>());
  63 + try {
  64 + int8_t q1_8 = Integer(q1);
  65 + std::cerr << "q1_8: " << std::to_string(q1_8) << '\n';
  66 + } catch (std::overflow_error const&) {
  67 + std::cerr << "caught expected int8_t overflow error\n";
  68 + }
  69 + try {
  70 + int8_t q1_8 = Integer(-q1);
  71 + std::cerr << "q1_8: " << std::to_string(q1_8) << '\n';
  72 + } catch (std::underflow_error const&) {
  73 + std::cerr << "caught expected int8_t underflow error\n";
  74 + }
  75 + assert_compare_numbers(std::numeric_limits<uint8_t>::max(), Integer(q1).value<uint8_t>());
  76 + assert_compare_numbers(0, Integer(-q1).value<uint8_t>());
  77 + try {
  78 + uint8_t q1_u8 = Integer(q1);
  79 + std::cerr << "q1_u8: " << std::to_string(q1_u8) << '\n';
  80 + } catch (std::overflow_error const&) {
  81 + std::cerr << "caught expected uint8_t overflow error\n";
  82 + }
  83 + try {
  84 + uint8_t q1_u8 = Integer(-q1);
  85 + std::cerr << "q1_u8: " << std::to_string(q1_u8) << '\n';
  86 + } catch (std::underflow_error const&) {
  87 + std::cerr << "caught expected uint8_t underflow error\n";
  88 + }
  89 + assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt());
  90 + assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue());
  91 + assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue());
  92 + assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt());
  93 + assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt());
  94 + assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt());
  95 + assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
  96 +}
  97 +
  98 +void
  99 +runtest(int n, char const* filename1, char const* arg2)
  100 +{
  101 + // Most tests here are crafted to work on specific files. Look at
  102 + // the test suite to see how the test is invoked to find the file
  103 + // that the test is supposed to operate on.
  104 +
  105 + std::set<int> ignore_filename = {};
  106 +
  107 + QPDF pdf;
  108 + std::shared_ptr<char> file_buf;
  109 + FILE* filep = nullptr;
  110 + if (ignore_filename.contains(n)) {
  111 + // Ignore filename argument entirely
  112 + } else {
  113 + size_t size = 0;
  114 + QUtil::read_file_into_memory(filename1, file_buf, size);
  115 + pdf.processMemoryFile(filename1, file_buf.get(), size);
  116 + }
  117 +
  118 + std::map<int, void (*)(QPDF&, char const*)> test_functions = {
  119 + {0, test_0},
  120 + };
  121 +
  122 + auto fn = test_functions.find(n);
  123 + if (fn == test_functions.end()) {
  124 + throw std::runtime_error(std::string("invalid test ") + QUtil::int_to_string(n));
  125 + }
  126 + (fn->second)(pdf, arg2);
  127 +
  128 + if (filep) {
  129 + fclose(filep);
  130 + }
  131 + std::cout << "test " << n << " done" << '\n';
  132 +}
  133 +
  134 +int
  135 +main(int argc, char* argv[])
  136 +{
  137 + QUtil::setLineBuf(stdout);
  138 + if ((whoami = strrchr(argv[0], '/')) == nullptr) {
  139 + whoami = argv[0];
  140 + } else {
  141 + ++whoami;
  142 + }
  143 +
  144 + if ((argc < 3) || (argc > 4)) {
  145 + usage();
  146 + }
  147 +
  148 + try {
  149 + int n = QUtil::string_to_int(argv[1]);
  150 + char const* filename1 = argv[2];
  151 + char const* arg2 = argv[3];
  152 + runtest(n, filename1, arg2);
  153 + } catch (std::exception& e) {
  154 + std::cerr << e.what() << '\n';
  155 + exit(2);
  156 + }
  157 +
  158 + return 0;
  159 +}
... ...
libtests/qtest/objects.test 0 โ†’ 100644
  1 +#!/usr/bin/env perl
  2 +require 5.008;
  3 +use warnings;
  4 +use strict;
  5 +
  6 +
  7 +chdir("objects") or die "chdir testdir failed: $!\n";
  8 +
  9 +require TestDriver;
  10 +
  11 +
  12 +my $td = new TestDriver('objects');
  13 +
  14 +my $n_tests = 1;
  15 +
  16 +
  17 +$td->runtest("integer type checks",
  18 + {$td->COMMAND => "objects 0 minimal.pdf"},
  19 + {$td->FILE => "test0.out", $td->EXIT_STATUS => 0},
  20 + $td->NORMALIZE_NEWLINES);
  21 +
  22 +$td->report($n_tests);
... ...
libtests/qtest/objects/minimal.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +1 0 obj
  3 +<<
  4 + /Type /Catalog
  5 + /Pages 2 0 R
  6 +>>
  7 +endobj
  8 +
  9 +2 0 obj
  10 +<<
  11 + /Type /Pages
  12 + /Kids [
  13 + 3 0 R
  14 + ]
  15 + /Count 1
  16 +>>
  17 +endobj
  18 +
  19 +3 0 obj
  20 +<<
  21 + /Type /Page
  22 + /Parent 2 0 R
  23 + /MediaBox [0 0 612 792]
  24 + /Contents 4 0 R
  25 + /Resources <<
  26 + /ProcSet 5 0 R
  27 + /Font <<
  28 + /F1 6 0 R
  29 + >>
  30 + >>
  31 +>>
  32 +endobj
  33 +
  34 +4 0 obj
  35 +<<
  36 + /Length 44
  37 +>>
  38 +stream
  39 +BT
  40 + /F1 24 Tf
  41 + 72 720 Td
  42 + (Potato) Tj
  43 +ET
  44 +endstream
  45 +endobj
  46 +
  47 +5 0 obj
  48 +[
  49 + /PDF
  50 + /Text
  51 +]
  52 +endobj
  53 +
  54 +6 0 obj
  55 +<<
  56 + /Type /Font
  57 + /Subtype /Type1
  58 + /Name /F1
  59 + /BaseFont /Helvetica
  60 + /Encoding /WinAnsiEncoding
  61 +>>
  62 +endobj
  63 +
  64 +xref
  65 +0 7
  66 +0000000000 65535 f
  67 +0000000009 00000 n
  68 +0000000063 00000 n
  69 +0000000135 00000 n
  70 +0000000307 00000 n
  71 +0000000403 00000 n
  72 +0000000438 00000 n
  73 +trailer <<
  74 + /Size 7
  75 + /Root 1 0 R
  76 +>>
  77 +startxref
  78 +556
  79 +%%EOF
... ...
qpdf/qtest/qpdf/test62.out renamed to libtests/qtest/objects/test0.out
1 1 requested value of integer is too big; returning INT_MAX
  2 +caught expected type error
  3 +overflow while converting integer object; returning largest possible value
  4 +underflow while converting integer object; returning smallest possible value
  5 +caught expected int8_t overflow error
  6 +caught expected int8_t underflow error
  7 +overflow while converting integer object; returning largest possible value
  8 +underflow while converting integer object; returning smallest possible value
  9 +caught expected uint8_t overflow error
  10 +caught expected uint8_t underflow error
2 11 requested value of unsigned integer is too big; returning UINT_MAX
3 12 unsigned value request for negative number; returning 0
4 13 requested value of integer is too small; returning INT_MIN
5 14 unsigned integer value request for negative number; returning 0
6 15 requested value of integer is too big; returning INT_MAX
7   -test 62 done
  16 +test 0 done
... ...
qpdf/qtest/error-condition.test
... ... @@ -109,17 +109,12 @@ $td-&gt;runtest(&quot;C API: no recovery&quot;,
109 109 {$td->FILE => "c-no-recovery.out",
110 110 $td->EXIT_STATUS => 0},
111 111 $td->NORMALIZE_NEWLINES);
112   -
113   -$td->runtest("integer type checks",
114   - {$td->COMMAND => "test_driver 62 minimal.pdf"},
115   - {$td->FILE => "test62.out", $td->EXIT_STATUS => 0},
116   - $td->NORMALIZE_NEWLINES);
117 112 $td->runtest("getValueAs... accessor checks",
118 113 {$td->COMMAND => "test_driver 85 -"},
119 114 {$td->FILE => "test85.out", $td->EXIT_STATUS => 0},
120 115 $td->NORMALIZE_NEWLINES);
121 116  
122   -$n_tests += @badfiles + 11;
  117 +$n_tests += @badfiles + 10;
123 118  
124 119 # Recovery tests. These are mostly after-the-fact -- when recovery
125 120 # was implemented, some degree of recovery was possible on many of the
... ...
qpdf/test_driver.cc
... ... @@ -17,6 +17,7 @@
17 17 #include <qpdf/QPDFJob.hh>
18 18 #include <qpdf/QPDFNameTreeObjectHelper.hh>
19 19 #include <qpdf/QPDFNumberTreeObjectHelper.hh>
  20 +#include <qpdf/QPDFObjectHandle_private.hh>
20 21 #include <qpdf/QPDFOutlineDocumentHelper.hh>
21 22 #include <qpdf/QPDFPageDocumentHelper.hh>
22 23 #include <qpdf/QPDFPageLabelDocumentHelper.hh>
... ... @@ -189,8 +190,7 @@ static void
189 190 compare_numbers(char const* description, T1 const& expected, T2 const& actual)
190 191 {
191 192 if (expected != actual) {
192   - std::cerr << description << ": expected = " << expected << "; actual = " << actual
193   - << std::endl;
  193 + std::cerr << description << ": expected = " << expected << "; actual = " << actual << '\n';
194 194 }
195 195 }
196 196  
... ... @@ -2279,28 +2279,8 @@ test_61(QPDF&amp; pdf, char const* arg2)
2279 2279 static void
2280 2280 test_62(QPDF& pdf, char const* arg2)
2281 2281 {
2282   - // Test int size checks. This test will fail if int and long
2283   - // long are the same size.
2284   - QPDFObjectHandle t = pdf.getTrailer();
2285   - unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX);
2286   - long long q1 = QIntC::to_longlong(q1_l);
2287   - long long q2_l = 3LL * QIntC::to_longlong(INT_MIN);
2288   - long long q2 = QIntC::to_longlong(q2_l);
2289   - unsigned int q3_i = UINT_MAX;
2290   - long long q3 = QIntC::to_longlong(q3_i);
2291   - t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1));
2292   - t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2));
2293   - t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3));
2294   - assert_compare_numbers(q1, t.getKey("/Q1").getIntValue());
2295   - assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue());
2296   - assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt());
2297   - assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt());
2298   - assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue());
2299   - assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue());
2300   - assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt());
2301   - assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt());
2302   - assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt());
2303   - assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt());
  2282 + // Test int size checks. This test will fail if int and long long are the same size.
  2283 + // Moved to libtests/objects
2304 2284 }
2305 2285  
2306 2286 static void
... ...