Commit c7a4967d10fb9688f235baa8e57a1fb578f5387d
1 parent
dba61da1
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.
Showing
17 changed files
with
44 additions
and
64 deletions
TODO
| @@ -785,7 +785,7 @@ Rejected Ideas | @@ -785,7 +785,7 @@ Rejected Ideas | ||
| 785 | and too much toil for library users to be worth the small benefit of | 785 | and too much toil for library users to be worth the small benefit of |
| 786 | not having to call resetObjGen in QPDF's destructor. | 786 | not having to call resetObjGen in QPDF's destructor. |
| 787 | 787 | ||
| 788 | -* Fix Multiple Direct Object Owner Issue | 788 | +* Fix Multiple Direct Object Parent Issue |
| 789 | 789 | ||
| 790 | These are some ideas I had before m-holger's changes to split | 790 | These are some ideas I had before m-holger's changes to split |
| 791 | QPDFValue from QPDFObject. These notes were written prior to the | 791 | QPDFValue from QPDFObject. These notes were written prior to the |
| @@ -811,12 +811,3 @@ Rejected Ideas | @@ -811,12 +811,3 @@ Rejected Ideas | ||
| 811 | Note that arrays and dictionaries still need to contain | 811 | Note that arrays and dictionaries still need to contain |
| 812 | QPDFObjectHandle because of indirect objects. This only pertains to | 812 | QPDFObjectHandle because of indirect objects. This only pertains to |
| 813 | direct objects, which are always "resolved" in QPDFObjectHandle. | 813 | direct objects, which are always "resolved" in QPDFObjectHandle. |
| 814 | - | ||
| 815 | - If this is addressed, read comments in the following places: | ||
| 816 | - * QPDFWriter.cc::enqueueObject near the call to getOwningQPDF | ||
| 817 | - * QPDFValueProxy::reset and QPDFValueProxy::destroy | ||
| 818 | - * QPDF::~QPDF() | ||
| 819 | - * test 92 in test_driver.cc | ||
| 820 | - * QPDFObjectHandle.hh near isDestroyed | ||
| 821 | - All these references were from the release of qpdf 11 (in case they | ||
| 822 | - have moved by such time as this might be resurrected). |
include/qpdf/QPDFObjectHandle.hh
| @@ -392,7 +392,8 @@ class QPDFObjectHandle | @@ -392,7 +392,8 @@ class QPDFObjectHandle | ||
| 392 | inline bool isIndirect() const; | 392 | inline bool isIndirect() const; |
| 393 | 393 | ||
| 394 | // This returns true for indirect objects from a QPDF that has | 394 | // This returns true for indirect objects from a QPDF that has |
| 395 | - // been destroyed. | 395 | + // been destroyed. Trying unparse such an object will throw a |
| 396 | + // logic_error. | ||
| 396 | QPDF_DLL | 397 | QPDF_DLL |
| 397 | bool isDestroyed(); | 398 | bool isDestroyed(); |
| 398 | 399 | ||
| @@ -1540,8 +1541,8 @@ class QPDFObjectHandle | @@ -1540,8 +1541,8 @@ class QPDFObjectHandle | ||
| 1540 | friend class ObjAccessor; | 1541 | friend class ObjAccessor; |
| 1541 | 1542 | ||
| 1542 | // Provide access to specific classes for recursive | 1543 | // Provide access to specific classes for recursive |
| 1543 | - // reset(). | ||
| 1544 | - class Resetter | 1544 | + // disconnected(). |
| 1545 | + class DisconnectAccess | ||
| 1545 | { | 1546 | { |
| 1546 | friend class QPDF_Dictionary; | 1547 | friend class QPDF_Dictionary; |
| 1547 | friend class QPDF_Stream; | 1548 | friend class QPDF_Stream; |
| @@ -1549,9 +1550,9 @@ class QPDFObjectHandle | @@ -1549,9 +1550,9 @@ class QPDFObjectHandle | ||
| 1549 | 1550 | ||
| 1550 | private: | 1551 | private: |
| 1551 | static void | 1552 | static void |
| 1552 | - reset(QPDFObjectHandle& o) | 1553 | + disconnect(QPDFObjectHandle& o) |
| 1553 | { | 1554 | { |
| 1554 | - o.reset(); | 1555 | + o.disconnect(); |
| 1555 | } | 1556 | } |
| 1556 | }; | 1557 | }; |
| 1557 | friend class Resetter; | 1558 | friend class Resetter; |
| @@ -1653,7 +1654,7 @@ class QPDFObjectHandle | @@ -1653,7 +1654,7 @@ class QPDFObjectHandle | ||
| 1653 | bool first_level_only, | 1654 | bool first_level_only, |
| 1654 | bool stop_at_streams); | 1655 | bool stop_at_streams); |
| 1655 | void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only); | 1656 | void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only); |
| 1656 | - void reset(); | 1657 | + void disconnect(); |
| 1657 | void setParsedOffset(qpdf_offset_t offset); | 1658 | void setParsedOffset(qpdf_offset_t offset); |
| 1658 | void parseContentStream_internal( | 1659 | void parseContentStream_internal( |
| 1659 | std::string const& description, ParserCallbacks* callbacks); | 1660 | std::string const& description, ParserCallbacks* callbacks); |
libqpdf/QPDF.cc
| @@ -252,9 +252,11 @@ QPDF::~QPDF() | @@ -252,9 +252,11 @@ QPDF::~QPDF() | ||
| 252 | // resolved indirect references by replacing them with an internal | 252 | // resolved indirect references by replacing them with an internal |
| 253 | // object type representing that they have been destroyed. Note | 253 | // object type representing that they have been destroyed. Note |
| 254 | // that we can't break references like this at any time when the | 254 | // that we can't break references like this at any time when the |
| 255 | - // QPDF object is active. The call to reset also causes all | 255 | + // QPDF object is active. The call to reset also causes all direct |
| 256 | // QPDFObjectHandle objects that are reachable from this object to | 256 | // QPDFObjectHandle objects that are reachable from this object to |
| 257 | - // release their association with this QPDF. | 257 | + // release their association with this QPDF. Direct objects are |
| 258 | + // not destroyed since they can be moved to other QPDF objects | ||
| 259 | + // safely. | ||
| 258 | 260 | ||
| 259 | // At this point, obviously no one is still using the QPDF object, | 261 | // At this point, obviously no one is still using the QPDF object, |
| 260 | // but we'll explicitly clear the xref table anyway just to | 262 | // but we'll explicitly clear the xref table anyway just to |
| @@ -262,9 +264,7 @@ QPDF::~QPDF() | @@ -262,9 +264,7 @@ QPDF::~QPDF() | ||
| 262 | this->m->xref_table.clear(); | 264 | this->m->xref_table.clear(); |
| 263 | auto null_obj = QPDF_Null::create(); | 265 | auto null_obj = QPDF_Null::create(); |
| 264 | for (auto const& iter: this->m->obj_cache) { | 266 | for (auto const& iter: this->m->obj_cache) { |
| 265 | - iter.second.object->reset(); | ||
| 266 | - // It would be better if reset() could call destroy(), but it | ||
| 267 | - // can't -- see comments in QPDFValueProxy::reset(). | 267 | + iter.second.object->disconnect(); |
| 268 | iter.second.object->destroy(); | 268 | iter.second.object->destroy(); |
| 269 | } | 269 | } |
| 270 | } | 270 | } |
libqpdf/QPDFObjectHandle.cc
| @@ -249,7 +249,7 @@ QPDFObjectHandle::operator!=(QPDFObjectHandle const& rhs) const | @@ -249,7 +249,7 @@ QPDFObjectHandle::operator!=(QPDFObjectHandle const& rhs) const | ||
| 249 | } | 249 | } |
| 250 | 250 | ||
| 251 | void | 251 | void |
| 252 | -QPDFObjectHandle::reset() | 252 | +QPDFObjectHandle::disconnect() |
| 253 | { | 253 | { |
| 254 | // Recursively remove association with any QPDF object. This | 254 | // Recursively remove association with any QPDF object. This |
| 255 | // method may only be called during final destruction. | 255 | // method may only be called during final destruction. |
| @@ -257,7 +257,7 @@ QPDFObjectHandle::reset() | @@ -257,7 +257,7 @@ QPDFObjectHandle::reset() | ||
| 257 | // pointer itself, so we don't do that here. Other objects call it | 257 | // pointer itself, so we don't do that here. Other objects call it |
| 258 | // through this method. | 258 | // through this method. |
| 259 | if (!isIndirect()) { | 259 | if (!isIndirect()) { |
| 260 | - this->obj->reset(); | 260 | + this->obj->disconnect(); |
| 261 | } | 261 | } |
| 262 | } | 262 | } |
| 263 | 263 |
libqpdf/QPDFValueProxy.cc
| @@ -13,6 +13,5 @@ QPDFValueProxy::doResolve() | @@ -13,6 +13,5 @@ QPDFValueProxy::doResolve() | ||
| 13 | void | 13 | void |
| 14 | QPDFValueProxy::destroy() | 14 | QPDFValueProxy::destroy() |
| 15 | { | 15 | { |
| 16 | - // See comments in reset() for why this isn't part of reset. | ||
| 17 | value = QPDF_Destroyed::getInstance(); | 16 | value = QPDF_Destroyed::getInstance(); |
| 18 | } | 17 | } |
libqpdf/QPDFWriter.cc
| @@ -1198,14 +1198,12 @@ void | @@ -1198,14 +1198,12 @@ void | ||
| 1198 | QPDFWriter::enqueueObject(QPDFObjectHandle object) | 1198 | QPDFWriter::enqueueObject(QPDFObjectHandle object) |
| 1199 | { | 1199 | { |
| 1200 | if (object.isIndirect()) { | 1200 | if (object.isIndirect()) { |
| 1201 | - // This owner check should really be done for all objects, not | ||
| 1202 | - // just indirect objects. As of the time of the release of | ||
| 1203 | - // qpdf 11, it is known that there are cases of direct objects | ||
| 1204 | - // from other files getting copied into multiple QPDF objects. | ||
| 1205 | - // This definitely happens in the page splitting code. If we | ||
| 1206 | - // were to implement strong checks to prevent objects from | ||
| 1207 | - // having multiple owners, once that was complete phased in, | ||
| 1208 | - // this check could be moved outside the if statement. | 1201 | + // This owner check can only be done for indirect objects. It |
| 1202 | + // is possible for a direct object to have an owning QPDF that | ||
| 1203 | + // is from another file if a direct QPDFObjectHandle from one | ||
| 1204 | + // file was insert into another file without copying. Doing | ||
| 1205 | + // that is safe even if the original QPDF gets destroyed, | ||
| 1206 | + // which just disconnects the QPDFObjectHandle from its owner. | ||
| 1209 | if (object.getOwningQPDF() != &(this->m->pdf)) { | 1207 | if (object.getOwningQPDF() != &(this->m->pdf)) { |
| 1210 | QTC::TC("qpdf", "QPDFWriter foreign object"); | 1208 | QTC::TC("qpdf", "QPDFWriter foreign object"); |
| 1211 | throw std::logic_error( | 1209 | throw std::logic_error( |
libqpdf/QPDF_Array.cc
libqpdf/QPDF_Dictionary.cc
| @@ -22,10 +22,10 @@ QPDF_Dictionary::shallowCopy() | @@ -22,10 +22,10 @@ QPDF_Dictionary::shallowCopy() | ||
| 22 | } | 22 | } |
| 23 | 23 | ||
| 24 | void | 24 | void |
| 25 | -QPDF_Dictionary::reset() | 25 | +QPDF_Dictionary::disconnect() |
| 26 | { | 26 | { |
| 27 | for (auto& iter: this->items) { | 27 | for (auto& iter: this->items) { |
| 28 | - QPDFObjectHandle::Resetter::reset(iter.second); | 28 | + QPDFObjectHandle::DisconnectAccess::disconnect(iter.second); |
| 29 | } | 29 | } |
| 30 | } | 30 | } |
| 31 | 31 |
libqpdf/QPDF_Stream.cc
| @@ -168,10 +168,10 @@ QPDF_Stream::getFilterOnWrite() const | @@ -168,10 +168,10 @@ QPDF_Stream::getFilterOnWrite() const | ||
| 168 | } | 168 | } |
| 169 | 169 | ||
| 170 | void | 170 | void |
| 171 | -QPDF_Stream::reset() | 171 | +QPDF_Stream::disconnect() |
| 172 | { | 172 | { |
| 173 | this->stream_provider = nullptr; | 173 | this->stream_provider = nullptr; |
| 174 | - QPDFObjectHandle::Resetter::reset(this->stream_dict); | 174 | + QPDFObjectHandle::DisconnectAccess::disconnect(this->stream_dict); |
| 175 | } | 175 | } |
| 176 | 176 | ||
| 177 | void | 177 | void |
libqpdf/SparseOHArray.cc
| @@ -49,10 +49,10 @@ SparseOHArray::remove_last() | @@ -49,10 +49,10 @@ SparseOHArray::remove_last() | ||
| 49 | } | 49 | } |
| 50 | 50 | ||
| 51 | void | 51 | void |
| 52 | -SparseOHArray::reset() | 52 | +SparseOHArray::disconnect() |
| 53 | { | 53 | { |
| 54 | for (auto& iter: this->elements) { | 54 | for (auto& iter: this->elements) { |
| 55 | - QPDFObjectHandle::Resetter::reset(iter.second); | 55 | + QPDFObjectHandle::DisconnectAccess::disconnect(iter.second); |
| 56 | } | 56 | } |
| 57 | } | 57 | } |
| 58 | 58 |
libqpdf/qpdf/QPDFValue.hh
libqpdf/qpdf/QPDFValueProxy.hh
| @@ -102,33 +102,24 @@ class QPDFValueProxy | @@ -102,33 +102,24 @@ class QPDFValueProxy | ||
| 102 | o->value->og = og; | 102 | o->value->og = og; |
| 103 | } | 103 | } |
| 104 | 104 | ||
| 105 | - // The following two methods are for use by class QPDF only | ||
| 106 | void | 105 | void |
| 107 | setObjGen(QPDF* qpdf, QPDFObjGen const& og) | 106 | setObjGen(QPDF* qpdf, QPDFObjGen const& og) |
| 108 | { | 107 | { |
| 108 | + // Intended for use by the QPDF class | ||
| 109 | value->qpdf = qpdf; | 109 | value->qpdf = qpdf; |
| 110 | value->og = og; | 110 | value->og = og; |
| 111 | } | 111 | } |
| 112 | void | 112 | void |
| 113 | - reset() | ||
| 114 | - { | ||
| 115 | - value->reset(); | ||
| 116 | - // It would be better if, rather than clearing value->qpdf and | ||
| 117 | - // value->og, we completely replaced value with | ||
| 118 | - // QPDF_Destroyed. However, at the time of the release of qpdf | ||
| 119 | - // 11, this causes test failures and would likely break a lot | ||
| 120 | - // of code since it possible for a direct object that | ||
| 121 | - // recursively contains no indirect objects to be copied into | ||
| 122 | - // multiple QPDF objects. For that reason, we have to break | ||
| 123 | - // the association with the owning QPDF but not otherwise | ||
| 124 | - // mutate the object. For indirect objects, QPDF::~QPDF | ||
| 125 | - // replaces indirect objects with QPDF_Destroyed, which clears | ||
| 126 | - // circular references. If this code were able to do that, | ||
| 127 | - // that code would not have to. | 113 | + disconnect() |
| 114 | + { | ||
| 115 | + // Disconnect an object from its owning QPDF. This is called | ||
| 116 | + // by QPDF's destructor. | ||
| 117 | + value->disconnect(); | ||
| 128 | value->qpdf = nullptr; | 118 | value->qpdf = nullptr; |
| 129 | value->og = QPDFObjGen(); | 119 | value->og = QPDFObjGen(); |
| 130 | } | 120 | } |
| 131 | - | 121 | + // Mark an object as destroyed. Used by QPDF's destructor for its |
| 122 | + // indirect objects. | ||
| 132 | void destroy(); | 123 | void destroy(); |
| 133 | 124 | ||
| 134 | bool | 125 | bool |
libqpdf/qpdf/QPDF_Array.hh
| @@ -17,7 +17,7 @@ class QPDF_Array: public QPDFValue | @@ -17,7 +17,7 @@ class QPDF_Array: public QPDFValue | ||
| 17 | virtual std::shared_ptr<QPDFValueProxy> shallowCopy(); | 17 | virtual std::shared_ptr<QPDFValueProxy> shallowCopy(); |
| 18 | virtual std::string unparse(); | 18 | virtual std::string unparse(); |
| 19 | virtual JSON getJSON(int json_version); | 19 | virtual JSON getJSON(int json_version); |
| 20 | - virtual void reset(); | 20 | + virtual void disconnect(); |
| 21 | 21 | ||
| 22 | int getNItems() const; | 22 | int getNItems() const; |
| 23 | QPDFObjectHandle getItem(int n) const; | 23 | QPDFObjectHandle getItem(int n) const; |
libqpdf/qpdf/QPDF_Dictionary.hh
| @@ -17,7 +17,7 @@ class QPDF_Dictionary: public QPDFValue | @@ -17,7 +17,7 @@ class QPDF_Dictionary: public QPDFValue | ||
| 17 | virtual std::shared_ptr<QPDFValueProxy> shallowCopy(); | 17 | virtual std::shared_ptr<QPDFValueProxy> shallowCopy(); |
| 18 | virtual std::string unparse(); | 18 | virtual std::string unparse(); |
| 19 | virtual JSON getJSON(int json_version); | 19 | virtual JSON getJSON(int json_version); |
| 20 | - virtual void reset(); | 20 | + virtual void disconnect(); |
| 21 | 21 | ||
| 22 | // hasKey() and getKeys() treat keys with null values as if they | 22 | // hasKey() and getKeys() treat keys with null values as if they |
| 23 | // aren't there. getKey() returns null for the value of a | 23 | // aren't there. getKey() returns null for the value of a |
libqpdf/qpdf/QPDF_Stream.hh
| @@ -27,7 +27,7 @@ class QPDF_Stream: public QPDFValue | @@ -27,7 +27,7 @@ class QPDF_Stream: public QPDFValue | ||
| 27 | virtual std::string unparse(); | 27 | virtual std::string unparse(); |
| 28 | virtual JSON getJSON(int json_version); | 28 | virtual JSON getJSON(int json_version); |
| 29 | virtual void setDescription(QPDF*, std::string const&); | 29 | virtual void setDescription(QPDF*, std::string const&); |
| 30 | - virtual void reset(); | 30 | + virtual void disconnect(); |
| 31 | QPDFObjectHandle getDict() const; | 31 | QPDFObjectHandle getDict() const; |
| 32 | bool isDataModified() const; | 32 | bool isDataModified() const; |
| 33 | void setFilterOnWrite(bool); | 33 | void setFilterOnWrite(bool); |
libqpdf/qpdf/SparseOHArray.hh
| @@ -15,7 +15,7 @@ class SparseOHArray | @@ -15,7 +15,7 @@ class SparseOHArray | ||
| 15 | void setAt(size_t idx, QPDFObjectHandle oh); | 15 | void setAt(size_t idx, QPDFObjectHandle oh); |
| 16 | void erase(size_t idx); | 16 | void erase(size_t idx); |
| 17 | void insert(size_t idx, QPDFObjectHandle oh); | 17 | void insert(size_t idx, QPDFObjectHandle oh); |
| 18 | - void reset(); | 18 | + void disconnect(); |
| 19 | 19 | ||
| 20 | typedef std::unordered_map<size_t, QPDFObjectHandle>::const_iterator | 20 | typedef std::unordered_map<size_t, QPDFObjectHandle>::const_iterator |
| 21 | const_iterator; | 21 | const_iterator; |
qpdf/test_driver.cc
| @@ -3316,8 +3316,8 @@ test_92(QPDF& pdf, char const* arg2) | @@ -3316,8 +3316,8 @@ test_92(QPDF& pdf, char const* arg2) | ||
| 3316 | check(contents); | 3316 | check(contents); |
| 3317 | check(contents_dict); | 3317 | check(contents_dict); |
| 3318 | // Objects that were originally indirect should be destroyed. | 3318 | // Objects that were originally indirect should be destroyed. |
| 3319 | - // Otherwise, they should have retained their old values. See | ||
| 3320 | - // comments in QPDFValueProxy::reset for why this is the case. | 3319 | + // Otherwise, they should have retained their old values but just |
| 3320 | + // lost their connection to the owning QPDF. | ||
| 3321 | assert(root.isDestroyed()); | 3321 | assert(root.isDestroyed()); |
| 3322 | assert(page1.isDestroyed()); | 3322 | assert(page1.isDestroyed()); |
| 3323 | assert(contents.isDestroyed()); | 3323 | assert(contents.isDestroyed()); |