diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh index 6655322..78bd9f8 100644 --- a/include/qpdf/ObjectHandle.hh +++ b/include/qpdf/ObjectHandle.hh @@ -47,6 +47,8 @@ namespace qpdf // QPDFObjGen and bool. class BaseHandle { + friend class QPDF; + public: explicit inline operator bool() const; inline operator QPDFObjectHandle() const; @@ -54,12 +56,18 @@ namespace qpdf // The rest of the header file is for qpdf internal use only. + std::shared_ptr copy(bool shallow = false) const; + // Recursively remove association with any QPDF object. This method may only be called + // during final destruction. + void disconnect(bool only_direct = true); inline QPDFObjGen id_gen() const; inline bool indirect() const; inline bool null() const; inline QPDF* qpdf() const; inline qpdf_object_type_e raw_type_code() const; inline qpdf_object_type_e type_code() const; + std::string unparse() const; + void write_json(int json_version, JSON::Writer& p) const; protected: BaseHandle() = default; diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index fd4b19d..e01fd46 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -1242,19 +1242,6 @@ class QPDFObjectHandle final: public qpdf::BaseHandle QPDF_DLL void warnIfPossible(std::string const& warning) const; - // Provide access to specific classes for recursive disconnected(). - class DisconnectAccess - { - friend class QPDFObject; - - private: - static void - disconnect(QPDFObjectHandle o) - { - o.disconnect(); - } - }; - // Convenience routine: Throws if the assumption is violated. Your code will be better if you // call one of the isType methods and handle the case of the type being wrong, but these can be // convenient if you have already verified the type. @@ -1354,7 +1341,6 @@ class QPDFObjectHandle final: public qpdf::BaseHandle void objectWarning(std::string const& warning) const; void assertType(char const* type_name, bool istype) const; void makeDirect(QPDFObjGen::set& visited, bool stop_at_streams); - void disconnect(); void setParsedOffset(qpdf_offset_t offset); void parseContentStream_internal(std::string const& description, ParserCallbacks* callbacks); static void parseContentStream_data( diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index 7cbc656..8b7f1fd 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -212,6 +212,25 @@ QPDF::QPDF() : m->unique_id = unique_id.fetch_add(1ULL); } +// Provide access to disconnect(). Disconnect will in due course be merged into the current ObjCache +// (future Objects::Entry) to centralize all QPDF access to QPDFObject. +class Disconnect: BaseHandle +{ + public: + Disconnect(std::shared_ptr const& obj) : + BaseHandle(obj) + { + } + void + disconnect() + { + BaseHandle::disconnect(false); + if (raw_type_code() != ::ot_null) { + obj->value = QPDF_Destroyed(); + } + } +}; + QPDF::~QPDF() { // If two objects are mutually referential (through each object having an array or dictionary @@ -228,10 +247,7 @@ QPDF::~QPDF() // the xref table anyway just to prevent any possibility of resolve() succeeding. m->xref_table.clear(); for (auto const& iter: m->obj_cache) { - iter.second.object->disconnect(); - if (iter.second.object->getTypeCode() != ::ot_null) { - iter.second.object->destroy(); - } + Disconnect(iter.second.object).disconnect(); } } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 13f04fd..4c001cd 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -289,29 +289,29 @@ Name::normalize(std::string const& name) } std::shared_ptr -QPDFObject::copy(bool shallow) +BaseHandle::copy(bool shallow) const { - switch (getResolvedTypeCode()) { + switch (type_code()) { case ::ot_uninitialized: throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object"); return {}; // does not return case ::ot_reserved: - return create(); + return QPDFObject::create(); case ::ot_null: - return create(); + return QPDFObject::create(); case ::ot_boolean: - return create(std::get(value).val); + return QPDFObject::create(std::get(obj->value).val); case ::ot_integer: - return create(std::get(value).val); + return QPDFObject::create(std::get(obj->value).val); case ::ot_real: - return create(std::get(value).val); + return QPDFObject::create(std::get(obj->value).val); case ::ot_string: - return create(std::get(value).val); + return QPDFObject::create(std::get(obj->value).val); case ::ot_name: - return create(std::get(value).name); + return QPDFObject::create(std::get(obj->value).name); case ::ot_array: { - auto const& a = std::get(value); + auto const& a = std::get(obj->value); if (shallow) { return QPDFObject::create(a); } else { @@ -321,7 +321,7 @@ QPDFObject::copy(bool shallow) result.sp = std::make_unique(); result.sp->size = a.sp->size; for (auto const& [idx, oh]: a.sp->elements) { - result.sp->elements[idx] = oh.indirect() ? oh : oh.getObj()->copy(); + result.sp->elements[idx] = oh.indirect() ? oh : oh.copy(); } return QPDFObject::create(std::move(result)); } else { @@ -329,8 +329,7 @@ QPDFObject::copy(bool shallow) result.reserve(a.elements.size()); for (auto const& element: a.elements) { result.emplace_back( - element ? (element.indirect() ? element : element.getObj()->copy()) - : element); + element ? (element.indirect() ? element : element.copy()) : element); } return QPDFObject::create(std::move(result), false); } @@ -338,13 +337,13 @@ QPDFObject::copy(bool shallow) } case ::ot_dictionary: { - auto const& d = std::get(value); + auto const& d = std::get(obj->value); if (shallow) { return QPDFObject::create(d.items); } else { std::map new_items; for (auto const& [key, val]: d.items) { - new_items[key] = val.indirect() ? val : val.getObj()->copy(); + new_items[key] = val.indirect() ? val : val.copy(); } return QPDFObject::create(new_items); } @@ -354,9 +353,9 @@ QPDFObject::copy(bool shallow) throw std::runtime_error("stream objects cannot be cloned"); return {}; // does not return case ::ot_operator: - return create(std::get(value).val); + return QPDFObject::create(std::get(obj->value).val); case ::ot_inlineimage: - return create(std::get(value).val); + return QPDFObject::create(std::get(obj->value).val); case ::ot_unresolved: throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object"); return {}; // does not return @@ -364,15 +363,15 @@ QPDFObject::copy(bool shallow) throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF"); return {}; // does not return case ::ot_reference: - return qpdf->getObject(og).getObj(); + return obj->qpdf->getObject(obj->og).getObj(); } return {}; // unreachable } std::string -QPDFObject::unparse() +BaseHandle::unparse() const { - switch (getResolvedTypeCode()) { + switch (type_code()) { case ::ot_uninitialized: throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object"); return ""; // does not return @@ -382,18 +381,18 @@ QPDFObject::unparse() case ::ot_null: return "null"; case ::ot_boolean: - return std::get(value).val ? "true" : "false"; + return std::get(obj->value).val ? "true" : "false"; case ::ot_integer: - return std::to_string(std::get(value).val); + return std::to_string(std::get(obj->value).val); case ::ot_real: - return std::get(value).val; + return std::get(obj->value).val; case ::ot_string: - return std::get(value).unparse(false); + return std::get(obj->value).unparse(false); case ::ot_name: - return Name::normalize(std::get(value).name); + return Name::normalize(std::get(obj->value).name); case ::ot_array: { - auto const& a = std::get(value); + auto const& a = std::get(obj->value); std::string result = "[ "; if (a.sp) { int next = 0; @@ -402,9 +401,7 @@ QPDFObject::unparse() for (int j = next; j < key; ++j) { result += "null "; } - auto item_og = item.second.id_gen(); - result += item_og.isIndirect() ? item_og.unparse(' ') + " R " - : item.second.getObj()->unparse() + " "; + result += item.second.unparse() + " "; next = ++key; } for (int j = next; j < a.sp->size; ++j) { @@ -412,9 +409,7 @@ QPDFObject::unparse() } } else { for (auto const& item: a.elements) { - auto item_og = item.id_gen(); - result += item_og.isIndirect() ? item_og.unparse(' ') + " R " - : item.getObj()->unparse() + " "; + result += item.unparse() + " "; } } result += "]"; @@ -422,7 +417,7 @@ QPDFObject::unparse() } case ::ot_dictionary: { - auto const& items = std::get(value).items; + auto const& items = std::get(obj->value).items; std::string result = "<< "; for (auto& iter: items) { if (!iter.second.null()) { @@ -433,11 +428,11 @@ QPDFObject::unparse() return result; } case ::ot_stream: - return og.unparse(' ') + " R"; + return obj->og.unparse(' ') + " R"; case ::ot_operator: - return std::get(value).val; + return std::get(obj->value).val; case ::ot_inlineimage: - return std::get(value).val; + return std::get(obj->value).val; case ::ot_unresolved: throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object"); return ""; // does not return @@ -445,15 +440,15 @@ QPDFObject::unparse() throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF"); return ""; // does not return case ::ot_reference: - return og.unparse(' ') + " R"; + return obj->og.unparse(' ') + " R"; } return {}; // unreachable } void -QPDFObject::write_json(int json_version, JSON::Writer& p) +BaseHandle::write_json(int json_version, JSON::Writer& p) const { - switch (getResolvedTypeCode()) { + switch (type_code()) { case ::ot_uninitialized: throw std::logic_error( "QPDFObjectHandle: attempting to get JSON from a uninitialized object"); @@ -464,14 +459,14 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) p << "null"; break; case ::ot_boolean: - p << std::get(value).val; + p << std::get(obj->value).val; break; case ::ot_integer: - p << std::to_string(std::get(value).val); + p << std::to_string(std::get(obj->value).val); break; case ::ot_real: { - auto const& val = std::get(value).val; + auto const& val = std::get(obj->value).val; if (val.length() == 0) { // Can't really happen... p << "0"; @@ -488,11 +483,11 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) } break; case ::ot_string: - std::get(value).writeJSON(json_version, p); + std::get(obj->value).writeJSON(json_version, p); break; case ::ot_name: { - auto const& n = std::get(value); + auto const& n = std::get(obj->value); // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When // updating this method make sure QPDF_Dictionary is also update. if (json_version == 1) { @@ -512,7 +507,7 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) break; case ::ot_array: { - auto const& a = std::get(value); + auto const& a = std::get(obj->value); p.writeStart('['); if (a.sp) { int next = 0; @@ -526,7 +521,7 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) if (item_og.isIndirect()) { p << "\"" << item_og.unparse(' ') << " R\""; } else { - item.second.getObj()->write_json(json_version, p); + item.second.write_json(json_version, p); } next = ++key; } @@ -540,7 +535,7 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) if (item_og.isIndirect()) { p << "\"" << item_og.unparse(' ') << " R\""; } else { - item.getObj()->write_json(json_version, p); + item.write_json(json_version, p); } } } @@ -549,7 +544,7 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) break; case ::ot_dictionary: { - auto const& d = std::get(value); + auto const& d = std::get(obj->value); p.writeStart('{'); for (auto& iter: d.items) { if (!iter.second.null()) { @@ -574,10 +569,10 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) } break; case ::ot_stream: - std::get(value).m->stream_dict.writeJSON(json_version, p); + std::get(obj->value).m->stream_dict.writeJSON(json_version, p); break; case ::ot_reference: - p << "\"" << getObjGen().unparse(' ') << " R\""; + p << "\"" << obj->og.unparse(' ') << " R\""; break; default: throw std::logic_error("attempted to write an unsuitable object as JSON"); @@ -585,48 +580,49 @@ QPDFObject::write_json(int json_version, JSON::Writer& p) } void -QPDFObject::disconnect() +BaseHandle::disconnect(bool only_direct) { - // Disconnect an object from its owning QPDF. This is called by QPDF's destructor. + // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here. + if (only_direct && indirect()) { + return; + } - switch (getTypeCode()) { + switch (raw_type_code()) { case ::ot_array: { - auto& a = std::get(value); + auto& a = std::get(obj->value); if (a.sp) { for (auto& item: a.sp->elements) { - auto& obj = item.second; - if (!obj.indirect()) { - obj.getObj()->disconnect(); - } + item.second.disconnect(); } } else { - for (auto& obj: a.elements) { - if (!obj.indirect()) { - obj.getObj()->disconnect(); - } + for (auto& oh: a.elements) { + oh.disconnect(); } } } break; case ::ot_dictionary: - for (auto& iter: std::get(value).items) { - QPDFObjectHandle::DisconnectAccess::disconnect(iter.second); + for (auto& iter: std::get(obj->value).items) { + iter.second.disconnect(); } break; case ::ot_stream: { - auto& s = std::get(value); + auto& s = std::get(obj->value); s.m->stream_provider = nullptr; - QPDFObjectHandle::DisconnectAccess::disconnect(s.m->stream_dict); + s.m->stream_dict.disconnect(); } break; + case ::ot_uninitialized: + return; default: break; } - qpdf = nullptr; - og = QPDFObjGen(); + obj->qpdf = nullptr; + obj->og = QPDFObjGen(); } + std::string QPDFObject::getStringValue() const { @@ -654,16 +650,6 @@ QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const { return this->obj == rhs.obj; } -void -QPDFObjectHandle::disconnect() -{ - // Recursively remove association with any QPDF object. This method may only be called during - // final destruction. QPDF::~QPDF() calls it for indirect objects using the object pointer - // itself, so we don't do that here. Other objects call it through this method. - if (obj && !isIndirect()) { - this->obj->disconnect(); - } -} qpdf_object_type_e QPDFObjectHandle::getTypeCode() const @@ -1439,7 +1425,7 @@ QPDFObjectHandle::unparseResolved() const if (!obj) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } - return obj->unparse(); + return BaseHandle::unparse(); } std::string @@ -1476,7 +1462,7 @@ QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_ } else if (!obj) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } else { - obj->write_json(json_version, p); + write_json(json_version, p); } } @@ -1902,7 +1888,7 @@ QPDFObjectHandle::shallowCopy() if (!obj) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } - return {obj->copy()}; + return {copy()}; } QPDFObjectHandle @@ -1911,7 +1897,7 @@ QPDFObjectHandle::unsafeShallowCopy() if (!obj) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } - return {obj->copy(true)}; + return {copy(true)}; } void @@ -1926,7 +1912,7 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) } if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) { - this->obj = obj->copy(true); + this->obj = copy(true); } else if (auto a = as_array(strict)) { std::vector items; for (auto const& item: a) { diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index 61ce62a..b940251 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -358,7 +358,7 @@ namespace qpdf inline bool BaseHandle::null() const { - return !obj || obj->getResolvedTypeCode() == ::ot_null; + return !obj || type_code() == ::ot_null; } inline QPDF* @@ -383,7 +383,7 @@ namespace qpdf return QPDF::Resolver::resolved(obj->qpdf, obj->og)->getTypeCode(); } if (raw_type_code() == ::ot_reference) { - return std::get(obj->value).obj->getResolvedTypeCode(); + return std::get(obj->value).obj->getTypeCode(); } return raw_type_code(); } diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 25db481..db2da1c 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -18,6 +18,7 @@ #include #include +class Disconnect; class QPDFObject; class QPDFObjectHandle; @@ -50,7 +51,9 @@ class QPDF_Array final private: friend class QPDFObject; + friend class qpdf::BaseHandle; friend class qpdf::Array; + QPDF_Array(std::vector const& items) : elements(items) { @@ -75,6 +78,7 @@ class QPDF_Array final class QPDF_Bool final { friend class QPDFObject; + friend class qpdf::BaseHandle; friend class QPDFObjectHandle; explicit QPDF_Bool(bool val) : @@ -92,6 +96,7 @@ class QPDF_Dictionary final { friend class QPDFObject; friend class qpdf::BaseDictionary; + friend class qpdf::BaseHandle; QPDF_Dictionary(std::map const& items) : items(items) @@ -105,6 +110,7 @@ class QPDF_Dictionary final class QPDF_InlineImage final { friend class QPDFObject; + friend class qpdf::BaseHandle; explicit QPDF_InlineImage(std::string val) : val(std::move(val)) @@ -116,6 +122,7 @@ class QPDF_InlineImage final class QPDF_Integer final { friend class QPDFObject; + friend class qpdf::BaseHandle; friend class QPDFObjectHandle; QPDF_Integer(long long val) : @@ -128,6 +135,7 @@ class QPDF_Integer final class QPDF_Name final { friend class QPDFObject; + friend class qpdf::BaseHandle; explicit QPDF_Name(std::string name) : name(std::move(name)) @@ -139,6 +147,7 @@ class QPDF_Name final class QPDF_Null final { friend class QPDFObject; + friend class qpdf::BaseHandle; public: static inline std::shared_ptr create( @@ -150,6 +159,7 @@ class QPDF_Null final class QPDF_Operator final { friend class QPDFObject; + friend class qpdf::BaseHandle; QPDF_Operator(std::string val) : val(std::move(val)) @@ -162,6 +172,7 @@ class QPDF_Operator final class QPDF_Real final { friend class QPDFObject; + friend class qpdf::BaseHandle; QPDF_Real(std::string val) : val(std::move(val)) @@ -199,6 +210,7 @@ class QPDF_Stream final friend class QPDF_Stream; friend class QPDFObject; friend class qpdf::Stream; + friend class qpdf::BaseHandle; public: Members(QPDFObjectHandle stream_dict, size_t length) : @@ -217,6 +229,7 @@ class QPDF_Stream final }; friend class QPDFObject; + friend class qpdf::BaseHandle; friend class qpdf::Stream; QPDF_Stream(QPDFObjectHandle stream_dict, size_t length) : @@ -235,6 +248,7 @@ class QPDF_Stream final class QPDF_String final { friend class QPDFObject; + friend class qpdf::BaseHandle; friend class QPDFWriter; public: @@ -284,10 +298,6 @@ class QPDFObject qpdf, og, std::forward(T(std::forward(args)...))); } - std::shared_ptr copy(bool shallow = false); - std::string unparse(); - void write_json(int json_version, JSON::Writer& p); - void disconnect(); std::string getStringValue() const; // Return a unique type code for the resolved object @@ -298,7 +308,7 @@ class QPDFObject return QPDF::Resolver::resolved(qpdf, og)->getTypeCode(); } if (getTypeCode() == ::ot_reference) { - return std::get(value).obj->getResolvedTypeCode(); + return std::get(value).obj->getTypeCode(); } return getTypeCode(); } @@ -344,23 +354,12 @@ class QPDFObject qpdf = a_qpdf; og = a_og; } - // Mark an object as destroyed. Used by QPDF's destructor for its indirect objects. - void - destroy() - { - value = QPDF_Destroyed(); - } bool isUnresolved() const { return getTypeCode() == ::ot_unresolved; } - const QPDFObject* - resolved_object() const - { - return isUnresolved() ? QPDF::Resolver::resolved(qpdf, og).get() : this; - } struct JSON_Descr { @@ -457,6 +456,7 @@ class QPDFObject private: friend class QPDF_Stream; friend class qpdf::BaseHandle; + friend class Disconnect; typedef std::variant< std::monostate, diff --git a/libtests/sparse_array.cc b/libtests/sparse_array.cc index a930ee7..7bbdc23 100644 --- a/libtests/sparse_array.cc +++ b/libtests/sparse_array.cc @@ -95,12 +95,14 @@ main() assert(b.at(3).second.isNull()); assert(b.at(8).second.isNull()); assert(b.at(5).second.isIndirect()); - assert(obj->unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); - auto c = obj->copy(true); - auto d = obj->copy(false); + assert( + QPDFObjectHandle(obj).unparse() == + "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); + auto c = QPDFObjectHandle(obj).unsafeShallowCopy(); + auto d = QPDFObjectHandle(obj).shallowCopy(); b.at(7).second.setArrayItem(2, "42"_qpdf); - assert(c->unparse() == "[ null null null null null 3 0 R null [ 0 1 42 3 ] null null ]"); - assert(d->unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); + assert(c.unparse() == "[ null null null null null 3 0 R null [ 0 1 42 3 ] null null ]"); + assert(d.unparse() == "[ null null null null null 3 0 R null [ 0 1 2 3 ] null null ]"); try { b.setAt(3, {});