Change reset to disconnect and clarify comments

I decided that it's actually fine to copy a direct object to another
QPDF. Even if we eventually prevent a QPDFObject from having multiple
parents, this could happen if an object is moved.
This commit is contained in:
Jay Berkenbilt 2022-09-08 11:06:15 -04:00
parent dba61da1bf
commit c7a4967d10
17 changed files with 43 additions and 63 deletions

11
TODO
View File

@ -785,7 +785,7 @@ Rejected Ideas
and too much toil for library users to be worth the small benefit of and too much toil for library users to be worth the small benefit of
not having to call resetObjGen in QPDF's destructor. not having to call resetObjGen in QPDF's destructor.
* Fix Multiple Direct Object Owner Issue * Fix Multiple Direct Object Parent Issue
These are some ideas I had before m-holger's changes to split These are some ideas I had before m-holger's changes to split
QPDFValue from QPDFObject. These notes were written prior to the QPDFValue from QPDFObject. These notes were written prior to the
@ -811,12 +811,3 @@ Rejected Ideas
Note that arrays and dictionaries still need to contain Note that arrays and dictionaries still need to contain
QPDFObjectHandle because of indirect objects. This only pertains to QPDFObjectHandle because of indirect objects. This only pertains to
direct objects, which are always "resolved" in QPDFObjectHandle. direct objects, which are always "resolved" in QPDFObjectHandle.
If this is addressed, read comments in the following places:
* QPDFWriter.cc::enqueueObject near the call to getOwningQPDF
* QPDFValueProxy::reset and QPDFValueProxy::destroy
* QPDF::~QPDF()
* test 92 in test_driver.cc
* QPDFObjectHandle.hh near isDestroyed
All these references were from the release of qpdf 11 (in case they
have moved by such time as this might be resurrected).

View File

@ -392,7 +392,8 @@ class QPDFObjectHandle
inline bool isIndirect() const; inline bool isIndirect() const;
// This returns true for indirect objects from a QPDF that has // This returns true for indirect objects from a QPDF that has
// been destroyed. // been destroyed. Trying unparse such an object will throw a
// logic_error.
QPDF_DLL QPDF_DLL
bool isDestroyed(); bool isDestroyed();
@ -1540,8 +1541,8 @@ class QPDFObjectHandle
friend class ObjAccessor; friend class ObjAccessor;
// Provide access to specific classes for recursive // Provide access to specific classes for recursive
// reset(). // disconnected().
class Resetter class DisconnectAccess
{ {
friend class QPDF_Dictionary; friend class QPDF_Dictionary;
friend class QPDF_Stream; friend class QPDF_Stream;
@ -1549,9 +1550,9 @@ class QPDFObjectHandle
private: private:
static void static void
reset(QPDFObjectHandle& o) disconnect(QPDFObjectHandle& o)
{ {
o.reset(); o.disconnect();
} }
}; };
friend class Resetter; friend class Resetter;
@ -1653,7 +1654,7 @@ class QPDFObjectHandle
bool first_level_only, bool first_level_only,
bool stop_at_streams); bool stop_at_streams);
void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only); void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only);
void reset(); void disconnect();
void setParsedOffset(qpdf_offset_t offset); void setParsedOffset(qpdf_offset_t offset);
void parseContentStream_internal( void parseContentStream_internal(
std::string const& description, ParserCallbacks* callbacks); std::string const& description, ParserCallbacks* callbacks);

View File

@ -252,9 +252,11 @@ QPDF::~QPDF()
// resolved indirect references by replacing them with an internal // resolved indirect references by replacing them with an internal
// object type representing that they have been destroyed. Note // object type representing that they have been destroyed. Note
// that we can't break references like this at any time when the // that we can't break references like this at any time when the
// QPDF object is active. The call to reset also causes all // QPDF object is active. The call to reset also causes all direct
// QPDFObjectHandle objects that are reachable from this object to // QPDFObjectHandle objects that are reachable from this object to
// release their association with this QPDF. // release their association with this QPDF. Direct objects are
// not destroyed since they can be moved to other QPDF objects
// safely.
// At this point, obviously no one is still using the QPDF object, // At this point, obviously no one is still using the QPDF object,
// but we'll explicitly clear the xref table anyway just to // but we'll explicitly clear the xref table anyway just to
@ -262,9 +264,7 @@ QPDF::~QPDF()
this->m->xref_table.clear(); this->m->xref_table.clear();
auto null_obj = QPDF_Null::create(); auto null_obj = QPDF_Null::create();
for (auto const& iter: this->m->obj_cache) { for (auto const& iter: this->m->obj_cache) {
iter.second.object->reset(); iter.second.object->disconnect();
// It would be better if reset() could call destroy(), but it
// can't -- see comments in QPDFValueProxy::reset().
iter.second.object->destroy(); iter.second.object->destroy();
} }
} }

View File

@ -249,7 +249,7 @@ QPDFObjectHandle::operator!=(QPDFObjectHandle const& rhs) const
} }
void void
QPDFObjectHandle::reset() QPDFObjectHandle::disconnect()
{ {
// Recursively remove association with any QPDF object. This // Recursively remove association with any QPDF object. This
// method may only be called during final destruction. // method may only be called during final destruction.
@ -257,7 +257,7 @@ QPDFObjectHandle::reset()
// pointer itself, so we don't do that here. Other objects call it // pointer itself, so we don't do that here. Other objects call it
// through this method. // through this method.
if (!isIndirect()) { if (!isIndirect()) {
this->obj->reset(); this->obj->disconnect();
} }
} }

View File

@ -13,6 +13,5 @@ QPDFValueProxy::doResolve()
void void
QPDFValueProxy::destroy() QPDFValueProxy::destroy()
{ {
// See comments in reset() for why this isn't part of reset.
value = QPDF_Destroyed::getInstance(); value = QPDF_Destroyed::getInstance();
} }

View File

@ -1198,14 +1198,12 @@ void
QPDFWriter::enqueueObject(QPDFObjectHandle object) QPDFWriter::enqueueObject(QPDFObjectHandle object)
{ {
if (object.isIndirect()) { if (object.isIndirect()) {
// This owner check should really be done for all objects, not // This owner check can only be done for indirect objects. It
// just indirect objects. As of the time of the release of // is possible for a direct object to have an owning QPDF that
// qpdf 11, it is known that there are cases of direct objects // is from another file if a direct QPDFObjectHandle from one
// from other files getting copied into multiple QPDF objects. // file was insert into another file without copying. Doing
// This definitely happens in the page splitting code. If we // that is safe even if the original QPDF gets destroyed,
// were to implement strong checks to prevent objects from // which just disconnects the QPDFObjectHandle from its owner.
// having multiple owners, once that was complete phased in,
// this check could be moved outside the if statement.
if (object.getOwningQPDF() != &(this->m->pdf)) { if (object.getOwningQPDF() != &(this->m->pdf)) {
QTC::TC("qpdf", "QPDFWriter foreign object"); QTC::TC("qpdf", "QPDFWriter foreign object");
throw std::logic_error( throw std::logic_error(

View File

@ -35,9 +35,9 @@ QPDF_Array::shallowCopy()
} }
void void
QPDF_Array::reset() QPDF_Array::disconnect()
{ {
elements.reset(); elements.disconnect();
} }
std::string std::string

View File

@ -22,10 +22,10 @@ QPDF_Dictionary::shallowCopy()
} }
void void
QPDF_Dictionary::reset() QPDF_Dictionary::disconnect()
{ {
for (auto& iter: this->items) { for (auto& iter: this->items) {
QPDFObjectHandle::Resetter::reset(iter.second); QPDFObjectHandle::DisconnectAccess::disconnect(iter.second);
} }
} }

View File

@ -168,10 +168,10 @@ QPDF_Stream::getFilterOnWrite() const
} }
void void
QPDF_Stream::reset() QPDF_Stream::disconnect()
{ {
this->stream_provider = nullptr; this->stream_provider = nullptr;
QPDFObjectHandle::Resetter::reset(this->stream_dict); QPDFObjectHandle::DisconnectAccess::disconnect(this->stream_dict);
} }
void void

View File

@ -49,10 +49,10 @@ SparseOHArray::remove_last()
} }
void void
SparseOHArray::reset() SparseOHArray::disconnect()
{ {
for (auto& iter: this->elements) { for (auto& iter: this->elements) {
QPDFObjectHandle::Resetter::reset(iter.second); QPDFObjectHandle::DisconnectAccess::disconnect(iter.second);
} }
} }

View File

@ -64,7 +64,7 @@ class QPDFValue
return og; return og;
} }
virtual void virtual void
reset() disconnect()
{ {
} }

View File

@ -102,33 +102,24 @@ class QPDFValueProxy
o->value->og = og; o->value->og = og;
} }
// The following two methods are for use by class QPDF only
void void
setObjGen(QPDF* qpdf, QPDFObjGen const& og) setObjGen(QPDF* qpdf, QPDFObjGen const& og)
{ {
// Intended for use by the QPDF class
value->qpdf = qpdf; value->qpdf = qpdf;
value->og = og; value->og = og;
} }
void void
reset() disconnect()
{ {
value->reset(); // Disconnect an object from its owning QPDF. This is called
// It would be better if, rather than clearing value->qpdf and // by QPDF's destructor.
// value->og, we completely replaced value with value->disconnect();
// QPDF_Destroyed. However, at the time of the release of qpdf
// 11, this causes test failures and would likely break a lot
// of code since it possible for a direct object that
// recursively contains no indirect objects to be copied into
// multiple QPDF objects. For that reason, we have to break
// the association with the owning QPDF but not otherwise
// mutate the object. For indirect objects, QPDF::~QPDF
// replaces indirect objects with QPDF_Destroyed, which clears
// circular references. If this code were able to do that,
// that code would not have to.
value->qpdf = nullptr; value->qpdf = nullptr;
value->og = QPDFObjGen(); value->og = QPDFObjGen();
} }
// Mark an object as destroyed. Used by QPDF's destructor for its
// indirect objects.
void destroy(); void destroy();
bool bool

View File

@ -17,7 +17,7 @@ class QPDF_Array: public QPDFValue
virtual std::shared_ptr<QPDFValueProxy> shallowCopy(); virtual std::shared_ptr<QPDFValueProxy> shallowCopy();
virtual std::string unparse(); virtual std::string unparse();
virtual JSON getJSON(int json_version); virtual JSON getJSON(int json_version);
virtual void reset(); virtual void disconnect();
int getNItems() const; int getNItems() const;
QPDFObjectHandle getItem(int n) const; QPDFObjectHandle getItem(int n) const;

View File

@ -17,7 +17,7 @@ class QPDF_Dictionary: public QPDFValue
virtual std::shared_ptr<QPDFValueProxy> shallowCopy(); virtual std::shared_ptr<QPDFValueProxy> shallowCopy();
virtual std::string unparse(); virtual std::string unparse();
virtual JSON getJSON(int json_version); virtual JSON getJSON(int json_version);
virtual void reset(); virtual void disconnect();
// hasKey() and getKeys() treat keys with null values as if they // hasKey() and getKeys() treat keys with null values as if they
// aren't there. getKey() returns null for the value of a // aren't there. getKey() returns null for the value of a

View File

@ -27,7 +27,7 @@ class QPDF_Stream: public QPDFValue
virtual std::string unparse(); virtual std::string unparse();
virtual JSON getJSON(int json_version); virtual JSON getJSON(int json_version);
virtual void setDescription(QPDF*, std::string const&); virtual void setDescription(QPDF*, std::string const&);
virtual void reset(); virtual void disconnect();
QPDFObjectHandle getDict() const; QPDFObjectHandle getDict() const;
bool isDataModified() const; bool isDataModified() const;
void setFilterOnWrite(bool); void setFilterOnWrite(bool);

View File

@ -15,7 +15,7 @@ class SparseOHArray
void setAt(size_t idx, QPDFObjectHandle oh); void setAt(size_t idx, QPDFObjectHandle oh);
void erase(size_t idx); void erase(size_t idx);
void insert(size_t idx, QPDFObjectHandle oh); void insert(size_t idx, QPDFObjectHandle oh);
void reset(); void disconnect();
typedef std::unordered_map<size_t, QPDFObjectHandle>::const_iterator typedef std::unordered_map<size_t, QPDFObjectHandle>::const_iterator
const_iterator; const_iterator;

View File

@ -3316,8 +3316,8 @@ test_92(QPDF& pdf, char const* arg2)
check(contents); check(contents);
check(contents_dict); check(contents_dict);
// Objects that were originally indirect should be destroyed. // Objects that were originally indirect should be destroyed.
// Otherwise, they should have retained their old values. See // Otherwise, they should have retained their old values but just
// comments in QPDFValueProxy::reset for why this is the case. // lost their connection to the owning QPDF.
assert(root.isDestroyed()); assert(root.isDestroyed());
assert(page1.isDestroyed()); assert(page1.isDestroyed());
assert(contents.isDestroyed()); assert(contents.isDestroyed());