From f49c5588ec5d4242a2e96714e3ebe0b7cf041e95 Mon Sep 17 00:00:00 2001 From: m-holger Date: Tue, 2 Sep 2025 14:23:11 +0100 Subject: [PATCH] Refactor: introduce private-API `Integer` class and update integer handling in `QPDFObjectHandle`. --- include/qpdf/ObjectHandle.hh | 3 ++- libqpdf/QPDFObjectHandle.cc | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------ libqpdf/qpdf/QPDFObjectHandle_private.hh | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ libqpdf/qpdf/QPDFObject_private.hh | 2 ++ qpdf/qpdf.testcov | 1 - 5 files changed, 158 insertions(+), 38 deletions(-) diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh index 094a1cf..14951d1 100644 --- a/include/qpdf/ObjectHandle.hh +++ b/include/qpdf/ObjectHandle.hh @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -42,6 +41,7 @@ namespace qpdf class Array; class BaseDictionary; class Dictionary; + class Integer; class Stream; enum typed : std::uint8_t { strict = 0, any_flag = 1, optional = 2, any = 3, error = 4 }; @@ -127,6 +127,7 @@ namespace qpdf inline void assign(qpdf_object_type_e required, BaseHandle&& other); std::string description() const; + std::invalid_argument invalid_error(std::string const& method) const; std::runtime_error type_error(char const* expected_type) const; QPDFExc type_error(char const* expected_type, std::string const& message) const; char const* type_name() const; diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index af62923..3dd04da 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -806,16 +806,36 @@ QPDFObjectHandle::getValueAsBool(bool& value) const return false; } -// Integer accessors +// Integer methods + +Integer::Integer(long long value) : + BaseHandle(QPDFObject::create(value)) +{ +} + +QPDFObjectHandle +QPDFObjectHandle::newInteger(long long value) +{ + return {QPDFObject::create(value)}; +} + +int64_t +Integer::value() const +{ + auto* i = as(); + if (!i) { + throw invalid_error("Integer"); + } + return i->val; +} long long QPDFObjectHandle::getIntValue() const { - if (auto integer = as()) { - return integer->val; + if (auto const integer = Integer(*this)) { + return integer; } else { typeWarning("integer", "returning 0"); - QTC::TC("qpdf", "QPDFObjectHandle integer returning 0"); return 0; } } @@ -823,8 +843,8 @@ QPDFObjectHandle::getIntValue() const bool QPDFObjectHandle::getValueAsInt(long long& value) const { - if (auto integer = as()) { - value = integer->val; + if (auto const integer = Integer(*this)) { + value = integer; return true; } return false; @@ -833,16 +853,18 @@ QPDFObjectHandle::getValueAsInt(long long& value) const int QPDFObjectHandle::getIntValueAsInt() const { - long long v = getIntValue(); - if (v < INT_MIN) { + try { + return Integer(*this); + } catch (std::underflow_error&) { warn("requested value of integer is too small; returning INT_MIN"); return INT_MIN; - } - if (v > INT_MAX) { + } catch (std::overflow_error&) { warn("requested value of integer is too big; returning INT_MAX"); return INT_MAX; + } catch (std::invalid_argument&) { + typeWarning("integer", "returning 0"); + return 0; } - return static_cast(v); } bool @@ -858,12 +880,14 @@ QPDFObjectHandle::getValueAsInt(int& value) const unsigned long long QPDFObjectHandle::getUIntValue() const { - long long v = getIntValue(); - if (v < 0) { + try { + return Integer(*this); + } catch (std::underflow_error&) { warn("unsigned value request for negative number; returning 0"); return 0; - } else { - return static_cast(v); + } catch (std::invalid_argument&) { + typeWarning("integer", "returning 0"); + return 0; } } @@ -880,16 +904,18 @@ QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const unsigned int QPDFObjectHandle::getUIntValueAsUInt() const { - long long v = getIntValue(); - if (v < 0) { + try { + return Integer(*this); + } catch (std::underflow_error&) { warn("unsigned integer value request for negative number; returning 0"); return 0; - } - if (v > UINT_MAX) { + } catch (std::overflow_error&) { warn("requested value of unsigned integer is too big; returning UINT_MAX"); return UINT_MAX; + } catch (std::invalid_argument&) { + typeWarning("integer", "returning 0"); + return 0; } - return static_cast(v); } bool @@ -1329,7 +1355,7 @@ QPDFObjectHandle::rotatePage(int angle, bool relative) } new_angle = (new_angle + 360) % 360; // Make this explicit even with new_angle == 0 since /Rotate can be inherited. - replaceKey("/Rotate", QPDFObjectHandle::newInteger(new_angle)); + replaceKey("/Rotate", Integer(new_angle)); } void @@ -1595,12 +1621,11 @@ QPDFObjectHandle::parseContentStream_data( QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image"); warn( context, - QPDFExc( - qpdf_e_damaged_pdf, - description, - "stream data", - input.tell(), - "EOF found while reading inline image")); + {qpdf_e_damaged_pdf, + description, + "stream data", + input.tell(), + "EOF found while reading inline image"}); } else { QTC::TC("qpdf", "QPDFObjectHandle inline image token"); if (callbacks) { @@ -1658,12 +1683,6 @@ QPDFObjectHandle::newNull() } QPDFObjectHandle -QPDFObjectHandle::newInteger(long long value) -{ - return {QPDFObject::create(value)}; -} - -QPDFObjectHandle QPDFObjectHandle::newReal(std::string const& value) { return {QPDFObject::create(value)}; @@ -1932,6 +1951,11 @@ QPDFObjectHandle::assertInitialized() const } } +std::invalid_argument +BaseHandle::invalid_error(std::string const& method) const +{ + return std::invalid_argument(method + " operation attempted on invalid object"); +} std::runtime_error BaseHandle::type_error(char const* expected_type) const { @@ -1976,9 +2000,7 @@ void QPDFObjectHandle::assertType(char const* type_name, bool istype) const { if (!istype) { - throw std::runtime_error( - std::string("operation for ") + type_name + " attempted on object of type " + - QPDFObjectHandle(*this).getTypeName()); + throw type_error(type_name); } } diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index cd71327..c213f1b 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -7,6 +7,11 @@ #include #include +#include +#include + +using namespace std::literals; + namespace qpdf { class Array final: public BaseHandle @@ -285,6 +290,97 @@ namespace qpdf ~Dictionary() = default; }; + class Integer final: public BaseHandle + { + public: + Integer() = default; + Integer(Integer const&) = default; + Integer(Integer&&) = default; + Integer& operator=(Integer const&) = default; + Integer& operator=(Integer&&) = default; + ~Integer() = default; + + explicit Integer(long long value); + + explicit Integer(std::integral auto value) : + Integer(static_cast(value)) + { + if constexpr ( + std::numeric_limits::max() > + std::numeric_limits::max()) { + if (value > std::numeric_limits::max()) { + throw std::overflow_error("overflow constructing Integer"); + } + } + } + + Integer(QPDFObjectHandle const& oh) : + BaseHandle(oh.type_code() == ::ot_integer ? oh : QPDFObjectHandle()) + { + } + + Integer(QPDFObjectHandle&& oh) : + BaseHandle(oh.type_code() == ::ot_integer ? std::move(oh) : QPDFObjectHandle()) + { + } + + // Return the integer value. If the object is not a valid integer, throw a + // std::invalid_argument exception. If the object is out of range for the target type, + // throw a std::overflow_error or std::underflow_error exception. + template + operator T() const + { + auto v = value(); + + if (std::cmp_greater(v, std::numeric_limits::max())) { + throw std::overflow_error("Integer conversion overflow"); + } + if (std::cmp_less(v, std::numeric_limits::min())) { + throw std::underflow_error("Integer conversion underflow"); + } + return static_cast(v); + } + + // Return the integer value. If the object is not a valid integer, throw a + // std::invalid_argument exception. + int64_t value() const; + + // Return true if object value is equal to the 'rhs' value. Return false if the object is + // not a valid Integer. + friend bool + operator==(Integer const& lhs, std::integral auto rhs) + { + return lhs && std::cmp_equal(lhs.value(), rhs); + } + + // Compare the object value to the 'rhs' value. Throw a std::invalid_argument exception if + // the object is not a valid Integer. + friend std::strong_ordering + operator<=>(Integer const& lhs, std::integral auto rhs) + { + if (!lhs) { + throw lhs.invalid_error("Integer"); + } + if (std::cmp_less(lhs.value(), rhs)) { + return std::strong_ordering::less; + } + return std::cmp_greater(lhs.value(), rhs) ? std::strong_ordering::greater + : std::strong_ordering::equal; + } + }; + + bool + operator==(std::integral auto lhs, Integer const& rhs) + { + return rhs == lhs; + } + + std::strong_ordering + operator<=>(std::integral auto lhs, Integer const& rhs) + { + return rhs <=> lhs; + } + class Name final: public BaseHandle { public: diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 0637069..9dd4b92 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -27,6 +27,7 @@ namespace qpdf class Array; class BaseDictionary; class Dictionary; + class Integer; class Stream; } // namespace qpdf @@ -123,6 +124,7 @@ class QPDF_Integer final { friend class QPDFObject; friend class qpdf::BaseHandle; + friend class qpdf::Integer; friend class QPDFObjectHandle; QPDF_Integer(long long val) : diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 2fee084..a0ddbd0 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -277,7 +277,6 @@ QPDFParser bad token in parseRemainder 0 QPDFParser eof in parse 0 QPDFParser eof in parseRemainder 0 QPDFObjectHandle boolean returning false 0 -QPDFObjectHandle integer returning 0 0 QPDFObjectHandle real returning 0.0 0 QPDFObjectHandle name returning dummy name 0 QPDFObjectHandle string returning empty string 0 -- libgit2 0.21.4