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
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
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
QPDFObjectHandle because of indirect objects. This only pertains to
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;
// 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
bool isDestroyed();
@ -1540,8 +1541,8 @@ class QPDFObjectHandle
friend class ObjAccessor;
// Provide access to specific classes for recursive
// reset().
class Resetter
// disconnected().
class DisconnectAccess
{
friend class QPDF_Dictionary;
friend class QPDF_Stream;
@ -1549,9 +1550,9 @@ class QPDFObjectHandle
private:
static void
reset(QPDFObjectHandle& o)
disconnect(QPDFObjectHandle& o)
{
o.reset();
o.disconnect();
}
};
friend class Resetter;
@ -1653,7 +1654,7 @@ class QPDFObjectHandle
bool first_level_only,
bool stop_at_streams);
void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only);
void reset();
void disconnect();
void setParsedOffset(qpdf_offset_t offset);
void parseContentStream_internal(
std::string const& description, ParserCallbacks* callbacks);

View File

@ -252,9 +252,11 @@ QPDF::~QPDF()
// resolved indirect references by replacing them with an internal
// object type representing that they have been destroyed. Note
// 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
// 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,
// but we'll explicitly clear the xref table anyway just to
@ -262,9 +264,7 @@ QPDF::~QPDF()
this->m->xref_table.clear();
auto null_obj = QPDF_Null::create();
for (auto const& iter: this->m->obj_cache) {
iter.second.object->reset();
// It would be better if reset() could call destroy(), but it
// can't -- see comments in QPDFValueProxy::reset().
iter.second.object->disconnect();
iter.second.object->destroy();
}
}

View File

@ -249,7 +249,7 @@ QPDFObjectHandle::operator!=(QPDFObjectHandle const& rhs) const
}
void
QPDFObjectHandle::reset()
QPDFObjectHandle::disconnect()
{
// Recursively remove association with any QPDF object. This
// 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
// through this method.
if (!isIndirect()) {
this->obj->reset();
this->obj->disconnect();
}
}

View File

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

View File

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

View File

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

View File

@ -22,10 +22,10 @@ QPDF_Dictionary::shallowCopy()
}
void
QPDF_Dictionary::reset()
QPDF_Dictionary::disconnect()
{
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
QPDF_Stream::reset()
QPDF_Stream::disconnect()
{
this->stream_provider = nullptr;
QPDFObjectHandle::Resetter::reset(this->stream_dict);
QPDFObjectHandle::DisconnectAccess::disconnect(this->stream_dict);
}
void

View File

@ -49,10 +49,10 @@ SparseOHArray::remove_last()
}
void
SparseOHArray::reset()
SparseOHArray::disconnect()
{
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;
}
virtual void
reset()
disconnect()
{
}

View File

@ -102,33 +102,24 @@ class QPDFValueProxy
o->value->og = og;
}
// The following two methods are for use by class QPDF only
void
setObjGen(QPDF* qpdf, QPDFObjGen const& og)
{
// Intended for use by the QPDF class
value->qpdf = qpdf;
value->og = og;
}
void
reset()
disconnect()
{
value->reset();
// It would be better if, rather than clearing value->qpdf and
// value->og, we completely replaced value with
// 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.
// Disconnect an object from its owning QPDF. This is called
// by QPDF's destructor.
value->disconnect();
value->qpdf = nullptr;
value->og = QPDFObjGen();
}
// Mark an object as destroyed. Used by QPDF's destructor for its
// indirect objects.
void destroy();
bool

View File

@ -17,7 +17,7 @@ class QPDF_Array: public QPDFValue
virtual std::shared_ptr<QPDFValueProxy> shallowCopy();
virtual std::string unparse();
virtual JSON getJSON(int json_version);
virtual void reset();
virtual void disconnect();
int getNItems() 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::string unparse();
virtual JSON getJSON(int json_version);
virtual void reset();
virtual void disconnect();
// hasKey() and getKeys() treat keys with null values as if they
// 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 JSON getJSON(int json_version);
virtual void setDescription(QPDF*, std::string const&);
virtual void reset();
virtual void disconnect();
QPDFObjectHandle getDict() const;
bool isDataModified() const;
void setFilterOnWrite(bool);

View File

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

View File

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