mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Create a special "destroyed" type rather than using null
When a QPDF is destroyed, changing indirect objects to direct nulls makes them effectively disappear silently when they sneak into other places. Instead, we should treat this as an error. Adding a destroyed object type makes this possible.
This commit is contained in:
parent
264e25f391
commit
dba61da1bf
@ -1,3 +1,8 @@
|
|||||||
|
2022-09-08 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Add QPDFObjectHandle::isDestroyed() to test whether an indirect
|
||||||
|
object was from a QPDF that has been destroyed.
|
||||||
|
|
||||||
2022-09-07 Jay Berkenbilt <ejb@ql.org>
|
2022-09-07 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
* Add QPDFObjectHandle::getQPDF(), which returns a reference, as
|
* Add QPDFObjectHandle::getQPDF(), which returns a reference, as
|
||||||
|
14
TODO
14
TODO
@ -812,9 +812,11 @@ Rejected Ideas
|
|||||||
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 QPDFWriter.cc::enqueueObject
|
If this is addressed, read comments in the following places:
|
||||||
near the call to getOwningQPDF, comments in QPDFValueProxy::reset,
|
* QPDFWriter.cc::enqueueObject near the call to getOwningQPDF
|
||||||
and comments in QPDF::~QPDF() near the line that assigns to null.
|
* QPDFValueProxy::reset and QPDFValueProxy::destroy
|
||||||
This will also affect test 92 in test_driver.cc. All these
|
* QPDF::~QPDF()
|
||||||
references were from the release of qpdf 11 (in case they have moved
|
* test 92 in test_driver.cc
|
||||||
by such time as this might be resurrected).
|
* 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).
|
||||||
|
@ -123,6 +123,7 @@ enum qpdf_object_type_e {
|
|||||||
ot_inlineimage,
|
ot_inlineimage,
|
||||||
/* Object types internal to qpdf */
|
/* Object types internal to qpdf */
|
||||||
ot_unresolved,
|
ot_unresolved,
|
||||||
|
ot_destroyed,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Write Parameters. See QPDFWriter.hh for details. */
|
/* Write Parameters. See QPDFWriter.hh for details. */
|
||||||
|
@ -391,6 +391,11 @@ class QPDFObjectHandle
|
|||||||
QPDF_DLL
|
QPDF_DLL
|
||||||
inline bool isIndirect() const;
|
inline bool isIndirect() const;
|
||||||
|
|
||||||
|
// This returns true for indirect objects from a QPDF that has
|
||||||
|
// been destroyed.
|
||||||
|
QPDF_DLL
|
||||||
|
bool isDestroyed();
|
||||||
|
|
||||||
// True for everything except array, dictionary, stream, word, and
|
// True for everything except array, dictionary, stream, word, and
|
||||||
// inline image.
|
// inline image.
|
||||||
QPDF_DLL
|
QPDF_DLL
|
||||||
|
@ -90,6 +90,7 @@ set(libqpdf_SOURCES
|
|||||||
QPDFXRefEntry.cc
|
QPDFXRefEntry.cc
|
||||||
QPDF_Array.cc
|
QPDF_Array.cc
|
||||||
QPDF_Bool.cc
|
QPDF_Bool.cc
|
||||||
|
QPDF_Destroyed.cc
|
||||||
QPDF_Dictionary.cc
|
QPDF_Dictionary.cc
|
||||||
QPDF_InlineImage.cc
|
QPDF_InlineImage.cc
|
||||||
QPDF_Integer.cc
|
QPDF_Integer.cc
|
||||||
|
@ -249,22 +249,23 @@ QPDF::~QPDF()
|
|||||||
// std::shared_ptr objects will prevent the objects from being
|
// std::shared_ptr objects will prevent the objects from being
|
||||||
// deleted. Walk through all objects in the object cache, which is
|
// deleted. Walk through all objects in the object cache, which is
|
||||||
// those objects that we read from the file, and break all
|
// those objects that we read from the file, and break all
|
||||||
// resolved indirect references by replacing them with direct null
|
// resolved indirect references by replacing them with an internal
|
||||||
// objects. At this point, obviously no one is still using the
|
// object type representing that they have been destroyed. Note
|
||||||
// QPDF object, but we'll explicitly clear the xref table anyway
|
|
||||||
// just to prevent any possibility of resolve() succeeding. 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. This also causes all QPDFObjectHandle
|
// QPDF object is active. The call to reset also causes all
|
||||||
// objects that are reachable from this object to become nulls and
|
// QPDFObjectHandle objects that are reachable from this object to
|
||||||
// release their association with this QPDF.
|
// release their association with this QPDF.
|
||||||
|
|
||||||
|
// At this point, obviously no one is still using the QPDF object,
|
||||||
|
// but we'll explicitly clear the xref table anyway just to
|
||||||
|
// prevent any possibility of resolve() succeeding.
|
||||||
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->reset();
|
||||||
// If the issue discussed in QPDFValueProxy::reset were
|
// It would be better if reset() could call destroy(), but it
|
||||||
// resolved, then this assignment to null_obj could be
|
// can't -- see comments in QPDFValueProxy::reset().
|
||||||
// removed.
|
iter.second.object->destroy();
|
||||||
iter.second.object->assign(null_obj);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,8 +252,10 @@ void
|
|||||||
QPDFObjectHandle::reset()
|
QPDFObjectHandle::reset()
|
||||||
{
|
{
|
||||||
// Recursively remove association with any QPDF object. This
|
// Recursively remove association with any QPDF object. This
|
||||||
// method may only be called during final destruction. See
|
// method may only be called during final destruction.
|
||||||
// comments in QPDF::~QPDF().
|
// 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 (!isIndirect()) {
|
if (!isIndirect()) {
|
||||||
this->obj->reset();
|
this->obj->reset();
|
||||||
}
|
}
|
||||||
@ -351,6 +353,12 @@ QPDFObjectHandle::asString()
|
|||||||
return dereference() ? obj->as<QPDF_String>() : nullptr;
|
return dereference() ? obj->as<QPDF_String>() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
QPDFObjectHandle::isDestroyed()
|
||||||
|
{
|
||||||
|
return dereference() && (obj->getTypeCode() == ::ot_destroyed);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
QPDFObjectHandle::isBool()
|
QPDFObjectHandle::isBool()
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <qpdf/QPDFValueProxy.hh>
|
#include <qpdf/QPDFValueProxy.hh>
|
||||||
|
|
||||||
#include <qpdf/QPDF.hh>
|
#include <qpdf/QPDF.hh>
|
||||||
|
#include <qpdf/QPDF_Destroyed.hh>
|
||||||
|
|
||||||
void
|
void
|
||||||
QPDFValueProxy::doResolve()
|
QPDFValueProxy::doResolve()
|
||||||
@ -8,3 +9,10 @@ QPDFValueProxy::doResolve()
|
|||||||
auto og = value->og;
|
auto og = value->og;
|
||||||
QPDF::Resolver::resolve(value->qpdf, og);
|
QPDF::Resolver::resolve(value->qpdf, og);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
QPDFValueProxy::destroy()
|
||||||
|
{
|
||||||
|
// See comments in reset() for why this isn't part of reset.
|
||||||
|
value = QPDF_Destroyed::getInstance();
|
||||||
|
}
|
||||||
|
39
libqpdf/QPDF_Destroyed.cc
Normal file
39
libqpdf/QPDF_Destroyed.cc
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#include <qpdf/QPDF_Destroyed.hh>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
QPDF_Destroyed::QPDF_Destroyed() :
|
||||||
|
QPDFValue(::ot_destroyed, "destroyed")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<QPDFValue>
|
||||||
|
QPDF_Destroyed::getInstance()
|
||||||
|
{
|
||||||
|
static std::shared_ptr<QPDFValue> instance(new QPDF_Destroyed());
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<QPDFValueProxy>
|
||||||
|
QPDF_Destroyed::shallowCopy()
|
||||||
|
{
|
||||||
|
throw std::logic_error(
|
||||||
|
"attempted to shallow copy QPDFObjectHandle from destroyed QPDF");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string
|
||||||
|
QPDF_Destroyed::unparse()
|
||||||
|
{
|
||||||
|
throw std::logic_error(
|
||||||
|
"attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON
|
||||||
|
QPDF_Destroyed::getJSON(int json_version)
|
||||||
|
{
|
||||||
|
throw std::logic_error(
|
||||||
|
"attempted to get JSON from a QPDFObjectHandle from a destroyed QPDF");
|
||||||
|
return JSON::makeNull();
|
||||||
|
}
|
@ -31,6 +31,6 @@ JSON
|
|||||||
QPDF_Reserved::getJSON(int json_version)
|
QPDF_Reserved::getJSON(int json_version)
|
||||||
{
|
{
|
||||||
throw std::logic_error(
|
throw std::logic_error(
|
||||||
"QPDFObjectHandle: attempting to unparse a reserved object");
|
"QPDFObjectHandle: attempting to get JSON from a reserved object");
|
||||||
return JSON::makeNull();
|
return JSON::makeNull();
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ std::shared_ptr<QPDFValueProxy>
|
|||||||
QPDF_Unresolved::shallowCopy()
|
QPDF_Unresolved::shallowCopy()
|
||||||
{
|
{
|
||||||
throw std::logic_error(
|
throw std::logic_error(
|
||||||
"attempted to shallow copy unresolved QPDFObjectHandle");
|
"attempted to shallow copy an unresolved QPDFObjectHandle");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,5 +32,7 @@ QPDF_Unresolved::unparse()
|
|||||||
JSON
|
JSON
|
||||||
QPDF_Unresolved::getJSON(int json_version)
|
QPDF_Unresolved::getJSON(int json_version)
|
||||||
{
|
{
|
||||||
|
throw std::logic_error(
|
||||||
|
"attempted to get JSON from an unresolved QPDFObjectHandle");
|
||||||
return JSON::makeNull();
|
return JSON::makeNull();
|
||||||
}
|
}
|
||||||
|
@ -114,21 +114,23 @@ class QPDFValueProxy
|
|||||||
{
|
{
|
||||||
value->reset();
|
value->reset();
|
||||||
// It would be better if, rather than clearing value->qpdf and
|
// It would be better if, rather than clearing value->qpdf and
|
||||||
// value->og, we completely replaced value with a null object.
|
// value->og, we completely replaced value with
|
||||||
// However, at the time of the release of qpdf 11, this causes
|
// QPDF_Destroyed. However, at the time of the release of qpdf
|
||||||
// test failures and would likely break a lot of code since it
|
// 11, this causes test failures and would likely break a lot
|
||||||
// possible for a direct object that recursively contains no
|
// of code since it possible for a direct object that
|
||||||
// indirect objects to be copied into multiple QPDF objects.
|
// recursively contains no indirect objects to be copied into
|
||||||
// For that reason, we have to break the association with the
|
// multiple QPDF objects. For that reason, we have to break
|
||||||
// owning QPDF but not otherwise mutate the object. For
|
// the association with the owning QPDF but not otherwise
|
||||||
// indirect objects, QPDF::~QPDF replaces the object with
|
// mutate the object. For indirect objects, QPDF::~QPDF
|
||||||
// null, which clears circular references. If this code were
|
// replaces indirect objects with QPDF_Destroyed, which clears
|
||||||
// able to do the null replacement, that code would not have
|
// circular references. If this code were able to do that,
|
||||||
// to.
|
// that code would not have to.
|
||||||
value->qpdf = nullptr;
|
value->qpdf = nullptr;
|
||||||
value->og = QPDFObjGen();
|
value->og = QPDFObjGen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void destroy();
|
||||||
|
|
||||||
bool
|
bool
|
||||||
isUnresolved() const
|
isUnresolved() const
|
||||||
{
|
{
|
||||||
|
19
libqpdf/qpdf/QPDF_Destroyed.hh
Normal file
19
libqpdf/qpdf/QPDF_Destroyed.hh
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef QPDF_DESTROYED_HH
|
||||||
|
#define QPDF_DESTROYED_HH
|
||||||
|
|
||||||
|
#include <qpdf/QPDFValue.hh>
|
||||||
|
|
||||||
|
class QPDF_Destroyed: public QPDFValue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~QPDF_Destroyed() = default;
|
||||||
|
virtual std::shared_ptr<QPDFValueProxy> shallowCopy();
|
||||||
|
virtual std::string unparse();
|
||||||
|
virtual JSON getJSON(int json_version);
|
||||||
|
static std::shared_ptr<QPDFValue> getInstance();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPDF_Destroyed();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QPDF_DESTROYED_HH
|
@ -202,11 +202,13 @@ For a detailed list of changes, please see the file
|
|||||||
``replaceKeyAndGetOld``, a ``null`` object if the object was not
|
``replaceKeyAndGetOld``, a ``null`` object if the object was not
|
||||||
previously there.
|
previously there.
|
||||||
|
|
||||||
- The method ``QPDFObjectHandle::getOwningQPDF`` now returns a
|
- It is now possible to detect when a ``QPDFObjectHandle`` is an
|
||||||
null pointer (rather than an invalid pointer) if the owning
|
indirect object that belongs to a ``QPDF`` that has been
|
||||||
``QPDF`` object has been destroyed. This situation should
|
destroyed. Any attempt to unparse this type of
|
||||||
generally not happen for correct code, but at least the
|
``QPDFObjectHandle`` will throw a logic error. You can detect
|
||||||
situation is detectible now.
|
this by calling the new ``QPDFObjectHandle::isDestroyed``
|
||||||
|
method. Also the ``QPDFObjectHandle::getOwningQPDF`` method now
|
||||||
|
returns a null pointer rather than an invalid pointer.
|
||||||
|
|
||||||
- The method ``QPDFObjectHandle::getQPDF`` returns a ``QPDF&``
|
- The method ``QPDFObjectHandle::getQPDF`` returns a ``QPDF&``
|
||||||
(rather than a ``QPDF*``) and is an alternative to
|
(rather than a ``QPDF*``) and is an alternative to
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
logic error: QPDFObjectHandle from different QPDF found while writing. Use QPDF::copyForeignObject to add objects from another file.
|
logic error: QPDFObjectHandle from different QPDF found while writing. Use QPDF::copyForeignObject to add objects from another file.
|
||||||
|
logic error: attempted to unparse a QPDFObjectHandle from a destroyed QPDF
|
||||||
logic error: Attempting to add an object from a different QPDF. Use QPDF::copyForeignObject to add objects from another file.
|
logic error: Attempting to add an object from a different QPDF. Use QPDF::copyForeignObject to add objects from another file.
|
||||||
test 29 done
|
test 29 done
|
||||||
|
@ -1135,8 +1135,8 @@ test_29(QPDF& pdf, char const* arg2)
|
|||||||
{
|
{
|
||||||
// Detect mixed objects in QPDFWriter
|
// Detect mixed objects in QPDFWriter
|
||||||
assert(arg2 != 0);
|
assert(arg2 != 0);
|
||||||
QPDF other;
|
auto other = QPDF::create();
|
||||||
other.processFile(arg2);
|
other->processFile(arg2);
|
||||||
// We need to create a QPDF with mixed ownership to exercise
|
// We need to create a QPDF with mixed ownership to exercise
|
||||||
// QPDFWriter's ownership check. To do this, we have to sneak the
|
// QPDFWriter's ownership check. To do this, we have to sneak the
|
||||||
// foreign object inside an ownerless direct object to avoid
|
// foreign object inside an ownerless direct object to avoid
|
||||||
@ -1146,10 +1146,25 @@ test_29(QPDF& pdf, char const* arg2)
|
|||||||
// explicitly change the ownership to the wrong value.
|
// explicitly change the ownership to the wrong value.
|
||||||
auto dict = QPDFObjectHandle::newDictionary();
|
auto dict = QPDFObjectHandle::newDictionary();
|
||||||
dict.replaceKey("/QTest", pdf.getTrailer().getKey("/QTest"));
|
dict.replaceKey("/QTest", pdf.getTrailer().getKey("/QTest"));
|
||||||
other.getTrailer().replaceKey("/QTest", dict);
|
other->getTrailer().replaceKey("/QTest", dict);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
QPDFWriter w(other, "a.pdf");
|
QPDFWriter w(*other, "a.pdf");
|
||||||
|
w.write();
|
||||||
|
std::cout << "oops -- didn't throw" << std::endl;
|
||||||
|
} catch (std::logic_error const& e) {
|
||||||
|
std::cout << "logic error: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure deleting the other source doesn't prevent detection.
|
||||||
|
auto other2 = QPDF::create();
|
||||||
|
other2->emptyPDF();
|
||||||
|
dict = QPDFObjectHandle::newDictionary();
|
||||||
|
dict.replaceKey("/QTest", other2->getRoot());
|
||||||
|
other->getTrailer().replaceKey("/QTest", dict);
|
||||||
|
other2 = nullptr;
|
||||||
|
try {
|
||||||
|
QPDFWriter w(*other, "a.pdf");
|
||||||
w.write();
|
w.write();
|
||||||
std::cout << "oops -- didn't throw" << std::endl;
|
std::cout << "oops -- didn't throw" << std::endl;
|
||||||
} catch (std::logic_error const& e) {
|
} catch (std::logic_error const& e) {
|
||||||
@ -1158,7 +1173,7 @@ test_29(QPDF& pdf, char const* arg2)
|
|||||||
|
|
||||||
// Detect adding a foreign object
|
// Detect adding a foreign object
|
||||||
auto root1 = pdf.getRoot();
|
auto root1 = pdf.getRoot();
|
||||||
auto root2 = other.getRoot();
|
auto root2 = other->getRoot();
|
||||||
try {
|
try {
|
||||||
root1.replaceKey("/Oops", root2);
|
root1.replaceKey("/Oops", root2);
|
||||||
} catch (std::logic_error const& e) {
|
} catch (std::logic_error const& e) {
|
||||||
@ -3300,14 +3315,20 @@ test_92(QPDF& pdf, char const* arg2)
|
|||||||
check(resources);
|
check(resources);
|
||||||
check(contents);
|
check(contents);
|
||||||
check(contents_dict);
|
check(contents_dict);
|
||||||
// Objects that were originally indirect should be null.
|
// 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. See
|
||||||
// comments in QPDFValueProxy::reset for why this is the case.
|
// comments in QPDFValueProxy::reset for why this is the case.
|
||||||
assert(root.isNull());
|
assert(root.isDestroyed());
|
||||||
assert(page1.isNull());
|
assert(page1.isDestroyed());
|
||||||
assert(contents.isNull());
|
assert(contents.isDestroyed());
|
||||||
assert(!resources.isNull());
|
assert(resources.isDictionary());
|
||||||
assert(!contents_dict.isNull());
|
assert(contents_dict.isDictionary());
|
||||||
|
try {
|
||||||
|
root.unparse();
|
||||||
|
assert(false);
|
||||||
|
} catch (std::logic_error&) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
Loading…
Reference in New Issue
Block a user