From bddb168dbf8b2a90af74f4492dec3ff1697181a7 Mon Sep 17 00:00:00 2001 From: m-holger Date: Sun, 17 Mar 2024 12:53:30 +0000 Subject: [PATCH] In FUTURE, treat uninitialized object handles as null --- include/qpdf/QPDFObjectHelper.hh | 3 + libqpdf/NNTree.cc | 15 +++-- libqpdf/QPDF.cc | 37 ++++++++++-- libqpdf/QPDFAcroFormDocumentHelper.cc | 4 ++ libqpdf/QPDFFormFieldObjectHelper.cc | 21 ++++--- libqpdf/QPDFObjGen.cc | 12 +++- libqpdf/QPDFObjectHandle.cc | 82 +++++++++++++++++++++----- libqpdf/QPDFOutlineDocumentHelper.cc | 2 + libqpdf/QPDFOutlineObjectHelper.cc | 5 ++ libqpdf/QPDFParser.cc | 6 +- libqpdf/QPDF_Array.cc | 59 ++++++++++++------ libqpdf/QPDF_Null.cc | 3 + libtests/sparse_array.cc | 3 + qpdf/qtest/c-api-object-handle.test | 20 +++++-- qpdf/qtest/invalid-objects.test | 23 ++++++-- qpdf/qtest/qpdf/c-oh-errors-future.out | 56 ++++++++++++++++++ qpdf/qtest/qpdf/test73_future.out | 3 + qpdf/qtest/qpdf_test_helpers.pm | 11 ++++ qpdf/test_driver.cc | 17 +++++- 19 files changed, 312 insertions(+), 70 deletions(-) create mode 100644 qpdf/qtest/qpdf/c-oh-errors-future.out create mode 100644 qpdf/qtest/qpdf/test73_future.out diff --git a/include/qpdf/QPDFObjectHelper.hh b/include/qpdf/QPDFObjectHelper.hh index dff9386f..0b7a2e83 100644 --- a/include/qpdf/QPDFObjectHelper.hh +++ b/include/qpdf/QPDFObjectHelper.hh @@ -55,6 +55,9 @@ class QPDF_DLL_CLASS QPDFObjectHelper } protected: +#ifdef QPDF_FUTURE + QPDFObjectHelper() = default; +#endif QPDFObjectHandle oh; }; diff --git a/libqpdf/NNTree.cc b/libqpdf/NNTree.cc index fa054794..477af2e6 100644 --- a/libqpdf/NNTree.cc +++ b/libqpdf/NNTree.cc @@ -79,16 +79,14 @@ NNTreeIterator::PathElement::PathElement(QPDFObjectHandle const& node, int kid_n QPDFObjectHandle NNTreeIterator::getNextKid(PathElement& pe, bool backward) { - QPDFObjectHandle result; - bool found = false; - while (!found) { + while (true) { pe.kid_number += backward ? -1 : 1; auto kids = pe.node.getKey("/Kids"); if ((pe.kid_number >= 0) && (pe.kid_number < kids.getArrayNItems())) { - result = kids.getArrayItem(pe.kid_number); + auto result = kids.getArrayItem(pe.kid_number); if (result.isDictionary() && (result.hasKey("/Kids") || result.hasKey(impl.details.itemsKey()))) { - found = true; + return result; } else { QTC::TC("qpdf", "NNTree skip invalid kid"); warn( @@ -97,11 +95,12 @@ NNTreeIterator::getNextKid(PathElement& pe, bool backward) ("skipping over invalid kid at index " + std::to_string(pe.kid_number))); } } else { - result = QPDFObjectHandle::newNull(); - found = true; +#ifndef QPDF_FUTURE + return QPDFObjectHandle::newNull(); +#endif + return {}; } } - return result; } bool diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index fdd75359..e64bce0e 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -228,9 +228,15 @@ 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) { + if (iter.second.object) { + iter.second.object->disconnect(); +#ifndef QPDF_FUTURE + if (iter.second.object->getTypeCode() != ::ot_null) { + iter.second.object->destroy(); + } +#else iter.second.object->destroy(); +#endif } } } @@ -1571,7 +1577,10 @@ QPDF::readObjectAtOffset( if (offset == 0) { QTC::TC("qpdf", "QPDF bogus 0 offset", 0); warn(damagedPDF(0, "object has offset 0")); +#ifndef QPDF_FUTURE return QPDFObjectHandle::newNull(); +#endif + return {}; } m->file->seek(offset, SEEK_SET); @@ -1629,7 +1638,10 @@ QPDF::readObjectAtOffset( ("object " + exp_og.unparse(' ') + " not found in file after regenerating cross reference " "table"))); +#ifndef QPDF_FUTURE return QPDFObjectHandle::newNull(); +#endif + return {}; } } else { throw; @@ -1835,7 +1847,9 @@ QPDF::resolveObjectsInStream(int obj_stream_number) QPDFObjectHandle QPDF::newIndirect(QPDFObjGen const& og, std::shared_ptr const& obj) { - obj->setDefaultDescription(this, og); + if (obj) { + obj->setDefaultDescription(this, og); + } return {obj}; } @@ -1890,9 +1904,11 @@ QPDF::makeIndirectFromQPDFObject(std::shared_ptr const& obj) QPDFObjectHandle QPDF::makeIndirectObject(QPDFObjectHandle oh) { +#ifndef QPDF_FUTURE if (!oh.isInitialized()) { throw std::logic_error("attempted to make an uninitialized QPDFObjectHandle indirect"); } +#endif return makeIndirectFromQPDFObject(oh.getObj()); } @@ -1985,11 +2001,19 @@ QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh) void QPDF::replaceObject(QPDFObjGen const& og, QPDFObjectHandle oh) { +#ifndef QPDF_FUTURE if (oh.isIndirect() || !oh.isInitialized()) { +#else + if (oh.isIndirect()) { +#endif QTC::TC("qpdf", "QPDF replaceObject called with indirect object"); throw std::logic_error("QPDF::replaceObject called with indirect object handle"); } - updateCache(og, oh.getObj(), -1, -1); + auto obj = oh.getObj(); + if (!obj) { + obj = QPDF_Null::create(); + } + updateCache(og, obj, -1, -1); } void @@ -2092,7 +2116,10 @@ QPDF::copyForeignObject(QPDFObjectHandle foreign) if (!obj_copier.object_map.count(og)) { warn(damagedPDF("unexpected reference to /Pages object while copying foreign object; " "replacing with null")); +#ifndef QPDF_FUTURE return QPDFObjectHandle::newNull(); +#endif + return {}; } return obj_copier.object_map[foreign.getObjGen()]; } @@ -2166,7 +2193,9 @@ QPDF::replaceForeignIndirectObjects(QPDFObjectHandle foreign, ObjCopier& obj_cop // This case would occur if this is a reference to a Pages object that we didn't // traverse into. QTC::TC("qpdf", "QPDF replace foreign indirect with null"); +#ifndef QPDF_FUTURE result = QPDFObjectHandle::newNull(); +#endif } else { result = mapping->second; } diff --git a/libqpdf/QPDFAcroFormDocumentHelper.cc b/libqpdf/QPDFAcroFormDocumentHelper.cc index fb096e5e..0d1c7b42 100644 --- a/libqpdf/QPDFAcroFormDocumentHelper.cc +++ b/libqpdf/QPDFAcroFormDocumentHelper.cc @@ -55,7 +55,11 @@ QPDFAcroFormDocumentHelper::addFormField(QPDFFormFieldObjectHelper ff) } fields.appendItem(ff.getObjectHandle()); QPDFObjGen::set visited; +#ifndef QPDF_FUTURE traverseField(ff.getObjectHandle(), QPDFObjectHandle::newNull(), 0, visited); +#else + traverseField(ff.getObjectHandle(), {}, 0, visited); +#endif } void diff --git a/libqpdf/QPDFFormFieldObjectHelper.cc b/libqpdf/QPDFFormFieldObjectHelper.cc index 40627c3d..dcd7f3e5 100644 --- a/libqpdf/QPDFFormFieldObjectHelper.cc +++ b/libqpdf/QPDFFormFieldObjectHelper.cc @@ -15,7 +15,9 @@ QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper(QPDFObjectHandle oh) : } QPDFFormFieldObjectHelper::QPDFFormFieldObjectHelper() : +#ifndef QPDF_FUTURE QPDFObjectHelper(QPDFObjectHandle::newNull()), +#endif m(new Members()) { } @@ -51,15 +53,13 @@ QPDFFormFieldObjectHelper::getFieldFromAcroForm(std::string const& name) { QPDFObjectHandle result = QPDFObjectHandle::newNull(); // Fields are supposed to be indirect, so this should work. - QPDF* q = this->oh.getOwningQPDF(); - if (!q) { - return result; + if (QPDF* q = this->oh.getOwningQPDF()) { + return q->getRoot().getKey("/AcroForm").getKeyIfDict(name); } - auto acroform = q->getRoot().getKey("/AcroForm"); - if (!acroform.isDictionary()) { - return result; - } - return acroform.getKey(name); +#ifndef QPDF_FUTURE + return QPDFObjectHandle::newNull(); +#endif + return {}; } QPDFObjectHandle @@ -67,9 +67,12 @@ QPDFFormFieldObjectHelper::getInheritableFieldValue(std::string const& name) { QPDFObjectHandle node = this->oh; if (!node.isDictionary()) { +#ifndef QPDF_FUTURE return QPDFObjectHandle::newNull(); +#endif + return {}; } - QPDFObjectHandle result(node.getKey(name)); + auto result(node.getKey(name)); if (result.isNull()) { QPDFObjGen::set seen; while (seen.add(node) && node.hasKey("/Parent")) { diff --git a/libqpdf/QPDFObjGen.cc b/libqpdf/QPDFObjGen.cc index f3b19f6c..757e815d 100644 --- a/libqpdf/QPDFObjGen.cc +++ b/libqpdf/QPDFObjGen.cc @@ -27,9 +27,11 @@ QPDFObjGen::set::add(QPDFObjectHandle const& oh) if (auto* ptr = oh.getObjectPtr()) { return add(ptr->getObjGen()); } else { +#ifndef QPDF_FUTURE throw std::logic_error( "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); - return false; +#endif + return true; } } @@ -39,9 +41,11 @@ QPDFObjGen::set::add(QPDFObjectHelper const& helper) if (auto* ptr = helper.getObjectHandle().getObjectPtr()) { return add(ptr->getObjGen()); } else { +#ifndef QPDF_FUTURE throw std::logic_error( "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); - return false; +#endif + return true; } } @@ -51,8 +55,10 @@ QPDFObjGen::set::erase(QPDFObjectHandle const& oh) if (auto* ptr = oh.getObjectPtr()) { erase(ptr->getObjGen()); } else { +#ifndef QPDF_FUTURE throw std::logic_error( "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); +#endif } } @@ -62,7 +68,9 @@ QPDFObjGen::set::erase(QPDFObjectHelper const& helper) if (auto* ptr = helper.getObjectHandle().getObjectPtr()) { erase(ptr->getObjGen()); } else { +#ifndef QPDF_FUTURE throw std::logic_error( "attempt to retrieve QPDFObjGen from uninitialized QPDFObjectHandle"); +#endif } } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 0c6ce030..fa61003b 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -240,13 +240,21 @@ QPDFObjectHandle::disconnect() qpdf_object_type_e QPDFObjectHandle::getTypeCode() { +#ifndef QPDF_FUTURE return dereference() ? this->obj->getTypeCode() : ::ot_uninitialized; +#else + return dereference() ? this->obj->getTypeCode() : ::ot_null; +#endif } char const* QPDFObjectHandle::getTypeName() { +#ifndef QPDF_FUTURE return dereference() ? this->obj->getTypeName() : "uninitialized"; +#else + return dereference() ? this->obj->getTypeName() : "null"; +#endif } QPDF_Array* @@ -346,13 +354,21 @@ QPDFObjectHandle::isDirectNull() const { // Don't call dereference() -- this is a const method, and we know // objid == 0, so there's nothing to resolve. - return (isInitialized() && (getObjectID() == 0) && (obj->getTypeCode() == ::ot_null)); +#ifdef QPDF_FUTURE + return !obj || (getObjectID() == 0 && obj->getTypeCode() == ::ot_null); +#else + return isInitialized() && getObjectID() == 0 && obj->getTypeCode() == ::ot_null; +#endif } bool QPDFObjectHandle::isNull() { - return dereference() && (obj->getTypeCode() == ::ot_null); +#ifdef QPDF_FUTURE + return !obj || (dereference() && obj->getTypeCode() == ::ot_null); +#else + return dereference() && obj->getTypeCode() == ::ot_null; +#endif } bool @@ -992,21 +1008,22 @@ QPDFObjectHandle::getKey(std::string const& key) QPDFObjectHandle QPDFObjectHandle::getKeyIfDict(std::string const& key) { +#ifndef QPDF_FUTURE return isNull() ? newNull() : getKey(key); +#endif + return isNull() ? QPDFObjectHandle() : getKey(key); } std::set QPDFObjectHandle::getKeys() { - std::set result; - auto dict = asDictionary(); - if (dict) { - result = dict->getKeys(); + if (auto dict = asDictionary()) { + return dict->getKeys(); } else { typeWarning("dictionary", "treating as empty"); QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); } - return result; + return {}; } std::map @@ -1015,12 +1032,12 @@ QPDFObjectHandle::getDictAsMap() std::map result; auto dict = asDictionary(); if (dict) { - result = dict->getAsMap(); + return dict->getAsMap(); } else { typeWarning("dictionary", "treating as empty"); QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); } - return result; + return {}; } // Array and Name accessors @@ -1240,13 +1257,15 @@ QPDFObjectHandle::removeKey(std::string const& key) QPDFObjectHandle QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) { - auto result = QPDFObjectHandle::newNull(); - auto dict = asDictionary(); - if (dict) { - result = dict->getKey(key); + if (auto dict = asDictionary()) { + auto result = dict->getKey(key); + removeKey(key); + return result; } - removeKey(key); - return result; +#ifndef QPDF_FUTURE + return QPDFObjectHandle::newNull(); +#endif + return {}; } void @@ -1588,7 +1607,11 @@ std::string QPDFObjectHandle::unparseResolved() { if (!dereference()) { +#ifndef QPDF_FUTURE throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); +#else + return "null"; +#endif } return obj->unparse(); } @@ -1617,7 +1640,11 @@ QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) if ((!dereference_indirect) && isIndirect()) { return JSON::makeString(unparse()); } else if (!dereference()) { +#ifndef QPDF_FUTURE throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); +#else + return JSON::makeNull(); +#endif } else { Pl_Buffer p{"json"}; JSON::Writer jw{&p, 0}; @@ -1633,7 +1660,11 @@ QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_ if (!dereference_indirect && isIndirect()) { p << "\"" << getObjGen().unparse(' ') << " R\""; } else if (!dereference()) { +#ifndef QPDF_FUTURE throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); +#else + p << "null"; +#endif } else { obj->writeJSON(json_version, p); } @@ -2074,7 +2105,11 @@ QPDFObjectHandle QPDFObjectHandle::shallowCopy() { if (!dereference()) { +#ifndef QPDF_FUTURE throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); +#else + return {}; +#endif } return {obj->copy()}; } @@ -2083,7 +2118,11 @@ QPDFObjectHandle QPDFObjectHandle::unsafeShallowCopy() { if (!dereference()) { +#ifndef QPDF_FUTURE throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); +#else + return {}; +#endif } return {obj->copy(true)}; } @@ -2092,6 +2131,9 @@ void QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) { assertInitialized(); + if (!obj) { + return; + } auto cur_og = getObjGen(); if (!visited.add(cur_og)) { @@ -2161,9 +2203,11 @@ QPDFObjectHandle::makeDirect(bool allow_streams) void QPDFObjectHandle::assertInitialized() const { +#ifndef QPDF_FUTURE if (!isInitialized()) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } +#endif } void @@ -2173,10 +2217,16 @@ QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& warn std::string description; // Type checks above guarantee that the object has been dereferenced. Nevertheless, dereference // throws exceptions in the test suite +#ifdef QPDF_FUTURE + if (dereference()) { + obj->getDescription(context, description); + } +#else if (!dereference()) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } - this->obj->getDescription(context, description); + obj->getDescription(context, description); +#endif // Null context handled by warn warn( context, diff --git a/libqpdf/QPDFOutlineDocumentHelper.cc b/libqpdf/QPDFOutlineDocumentHelper.cc index 45ed696a..9c2ace59 100644 --- a/libqpdf/QPDFOutlineDocumentHelper.cc +++ b/libqpdf/QPDFOutlineDocumentHelper.cc @@ -90,8 +90,10 @@ QPDFOutlineDocumentHelper::resolveNamedDest(QPDFObjectHandle name) } } } +#ifndef QPDF_FUTURE if (!result.isInitialized()) { result = QPDFObjectHandle::newNull(); } +#endif return result; } diff --git a/libqpdf/QPDFOutlineObjectHelper.cc b/libqpdf/QPDFOutlineObjectHelper.cc index 3b5db47a..23e54d38 100644 --- a/libqpdf/QPDFOutlineObjectHelper.cc +++ b/libqpdf/QPDFOutlineObjectHelper.cc @@ -58,9 +58,11 @@ QPDFOutlineObjectHelper::getDest() QTC::TC("qpdf", "QPDFOutlineObjectHelper action dest"); dest = A.getKey("/D"); } +#ifndef QPDF_FUTURE if (!dest.isInitialized()) { dest = QPDFObjectHandle::newNull(); } +#endif if (dest.isName() || dest.isString()) { QTC::TC("qpdf", "QPDFOutlineObjectHelper named dest"); @@ -77,7 +79,10 @@ QPDFOutlineObjectHelper::getDestPage() if ((dest.isArray()) && (dest.getArrayNItems() > 0)) { return dest.getArrayItem(0); } +#ifndef QPDF_FUTURE return QPDFObjectHandle::newNull(); +#endif + return {}; } int diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc index 56448364..6cde7a12 100644 --- a/libqpdf/QPDFParser.cc +++ b/libqpdf/QPDFParser.cc @@ -394,7 +394,11 @@ QPDFParser::add(std::shared_ptr&& obj) void QPDFParser::addNull() { +#ifndef QPDF_FUTURE const static ObjectPtr null_obj = QPDF_Null::create(); +#else + const static ObjectPtr null_obj; +#endif if (frame->state != st_dictionary_value) { // If state is st_dictionary_key then there is a missing key. Push onto olist for @@ -448,7 +452,7 @@ QPDFParser::fixMissingKeys() { std::set names; for (auto& obj: frame->olist) { - if (obj->getTypeCode() == ::ot_name) { + if (obj && obj->getTypeCode() == ::ot_name) { names.insert(obj->getStringValue()); } } diff --git a/libqpdf/QPDF_Array.cc b/libqpdf/QPDF_Array.cc index fd7893f3..2f23fd08 100644 --- a/libqpdf/QPDF_Array.cc +++ b/libqpdf/QPDF_Array.cc @@ -5,8 +5,11 @@ #include #include +#ifndef QPDF_FUTURE static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull(); - +#else +static const QPDFObjectHandle null_oh; +#endif inline void QPDF_Array::checkOwnership(QPDFObjectHandle const& item) const { @@ -21,7 +24,9 @@ QPDF_Array::checkOwnership(QPDFObjectHandle const& item) const } } } else { +#ifndef QPDF_FUTURE throw std::logic_error("Attempting to add an uninitialized object to a QPDF_Array."); +#endif } } @@ -48,7 +53,7 @@ QPDF_Array::QPDF_Array(std::vector>&& v, bool sparse if (sparse) { sp = std::make_unique(); for (auto&& item: v) { - if (item->getTypeCode() != ::ot_null || item->getObjGen().isIndirect()) { + if (item && (item->getTypeCode() != ::ot_null || item->getObjGen().isIndirect())) { sp->elements[sp->size] = std::move(item); } ++sp->size; @@ -84,7 +89,7 @@ QPDF_Array::copy(bool shallow) for (auto const& element: sp->elements) { auto const& obj = element.second; result->sp->elements[element.first] = - obj->getObjGen().isIndirect() ? obj : obj->copy(); + !obj || obj->getObjGen().isIndirect() ? obj : obj->copy(); } return do_create(result); } else { @@ -106,13 +111,13 @@ QPDF_Array::disconnect() if (sp) { for (auto& item: sp->elements) { auto& obj = item.second; - if (!obj->getObjGen().isIndirect()) { + if (obj && !obj->getObjGen().isIndirect()) { obj->disconnect(); } } } else { for (auto& obj: elements) { - if (!obj->getObjGen().isIndirect()) { + if (obj && !obj->getObjGen().isIndirect()) { obj->disconnect(); } } @@ -130,9 +135,13 @@ QPDF_Array::unparse() for (int j = next; j < key; ++j) { result += "null "; } - item.second->resolve(); - auto og = item.second->getObjGen(); - result += og.isIndirect() ? og.unparse(' ') + " R " : item.second->unparse() + " "; + if (item.second) { + item.second->resolve(); + auto og = item.second->getObjGen(); + result += og.isIndirect() ? og.unparse(' ') + " R " : item.second->unparse() + " "; + } else { + result += "null "; + } next = ++key; } for (int j = next; j < sp->size; ++j) { @@ -140,9 +149,13 @@ QPDF_Array::unparse() } } else { for (auto const& item: elements) { - item->resolve(); - auto og = item->getObjGen(); - result += og.isIndirect() ? og.unparse(' ') + " R " : item->unparse() + " "; + if (item) { + item->resolve(); + auto og = item->getObjGen(); + result += og.isIndirect() ? og.unparse(' ') + " R " : item->unparse() + " "; + } else { + result += "null "; + } } } result += "]"; @@ -161,11 +174,15 @@ QPDF_Array::writeJSON(int json_version, JSON::Writer& p) p.writeNext() << "null"; } p.writeNext(); - auto og = item.second->getObjGen(); - if (og.isIndirect()) { - p << "\"" << og.unparse(' ') << " R\""; + if (item.second) { + auto og = item.second->getObjGen(); + if (og.isIndirect()) { + p << "\"" << og.unparse(' ') << " R\""; + } else { + item.second->writeJSON(json_version, p); + } } else { - item.second->writeJSON(json_version, p); + p << "null"; } next = ++key; } @@ -175,11 +192,15 @@ QPDF_Array::writeJSON(int json_version, JSON::Writer& p) } else { for (auto const& item: elements) { p.writeNext(); - auto og = item->getObjGen(); - if (og.isIndirect()) { - p << "\"" << og.unparse(' ') << " R\""; + if (item) { + auto og = item->getObjGen(); + if (og.isIndirect()) { + p << "\"" << og.unparse(' ') << " R\""; + } else { + item->writeJSON(json_version, p); + } } else { - item->writeJSON(json_version, p); + p << "null"; } } } diff --git a/libqpdf/QPDF_Null.cc b/libqpdf/QPDF_Null.cc index fa4b6cab..34b27c30 100644 --- a/libqpdf/QPDF_Null.cc +++ b/libqpdf/QPDF_Null.cc @@ -35,7 +35,10 @@ QPDF_Null::create( std::shared_ptr QPDF_Null::copy(bool shallow) { +#ifndef QPDF_FUTURE return create(); +#endif + return nullptr; } std::string diff --git a/libtests/sparse_array.cc b/libtests/sparse_array.cc index fb004919..d740a95a 100644 --- a/libtests/sparse_array.cc +++ b/libtests/sparse_array.cc @@ -102,11 +102,14 @@ main() assert(c->unparse() == "[ null null null null null 5 0 R null [ 0 1 42 3 ] null null ]"); assert(d->unparse() == "[ null null null null null 5 0 R null [ 0 1 2 3 ] null null ]"); +#ifndef QPDF_FUTURE + try { b.setAt(3, {}); std::cout << "inserted uninitialized object\n"; } catch (std::logic_error&) { } +#endif QPDF pdf2; pdf2.emptyPDF(); try { diff --git a/qpdf/qtest/c-api-object-handle.test b/qpdf/qtest/c-api-object-handle.test index 40511df1..b88a82a7 100644 --- a/qpdf/qtest/c-api-object-handle.test +++ b/qpdf/qtest/c-api-object-handle.test @@ -12,6 +12,8 @@ require TestDriver; cleanup(); +my $future = check_future(); + my $td = new TestDriver('c-api-object-handle'); my $n_tests = 13; @@ -54,10 +56,20 @@ $td->runtest("C wrap and clone objects", {$td->COMMAND => "qpdf-ctest 28 minimal.pdf '' ''"}, {$td->STRING => "C test 28 done\n", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); -$td->runtest("C object handle errors", - {$td->COMMAND => "qpdf-ctest 29 minimal.pdf '' ''"}, - {$td->FILE => "c-oh-errors.out", $td->EXIT_STATUS => 0}, - $td->NORMALIZE_NEWLINES); +if ($future) +{ + $td->runtest("C object handle errors", + {$td->COMMAND => "qpdf-ctest 29 minimal.pdf '' ''"}, + {$td->FILE => "c-oh-errors-future.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +} +else +{ + $td->runtest("C object handle errors", + {$td->COMMAND => "qpdf-ctest 29 minimal.pdf '' ''"}, + {$td->FILE => "c-oh-errors.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +} $td->runtest("C unhandled error warning", {$td->COMMAND => "qpdf-ctest 30 bad1.pdf '' ''"}, {$td->FILE => "c-unhandled-error.out", $td->EXIT_STATUS => 0}, diff --git a/qpdf/qtest/invalid-objects.test b/qpdf/qtest/invalid-objects.test index 1ece3810..3a43d2e6 100644 --- a/qpdf/qtest/invalid-objects.test +++ b/qpdf/qtest/invalid-objects.test @@ -12,15 +12,28 @@ require TestDriver; cleanup(); +my $future = check_future(); + my $td = new TestDriver('invalid-objects'); my $n_tests = 4; -$td->runtest("closed input source", - {$td->COMMAND => "test_driver 73 minimal.pdf"}, - {$td->FILE => "test73.out", - $td->EXIT_STATUS => 2}, - $td->NORMALIZE_NEWLINES); +if ($future) +{ + $td->runtest("closed input source", + {$td->COMMAND => "test_driver 73 minimal.pdf"}, + {$td->FILE => "test73_future.out", + $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); +} +else +{ + $td->runtest("closed input source", + {$td->COMMAND => "test_driver 73 minimal.pdf"}, + {$td->FILE => "test73.out", + $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); +} $td->runtest("empty object", {$td->COMMAND => "qpdf -show-object=7,0 empty-object.pdf"}, diff --git a/qpdf/qtest/qpdf/c-oh-errors-future.out b/qpdf/qtest/qpdf/c-oh-errors-future.out new file mode 100644 index 00000000..e8ef88d1 --- /dev/null +++ b/qpdf/qtest/qpdf/c-oh-errors-future.out @@ -0,0 +1,56 @@ +get root: operation for dictionary attempted on object of type null: returning null for attempted key retrieval + code: 7 + file: + pos: 0 + text: operation for dictionary attempted on object of type null: returning null for attempted key retrieval +bad parse: parsed object (offset 1): unknown token while reading object; treating as string + code: 5 + file: parsed object + pos: 1 + text: unknown token while reading object; treating as string +type mismatch (int operation on string): operation for integer attempted on object of type string: returning 0 + code: 7 + file: + pos: 0 + text: operation for integer attempted on object of type string: returning 0 +type mismatch (string operation on int): operation for string attempted on object of type integer: returning empty string + code: 7 + file: + pos: 0 + text: operation for string attempted on object of type integer: returning empty string +array type mismatch - n_items: operation for array attempted on object of type integer: treating as empty + code: 7 + file: + pos: 0 + text: operation for array attempted on object of type integer: treating as empty +array type mismatch - item: operation for array attempted on object of type integer: returning null + code: 7 + file: + pos: 0 + text: operation for array attempted on object of type integer: returning null +append to non-array: operation for array attempted on object of type integer: ignoring attempt to append item + code: 7 + file: + pos: 0 + text: operation for array attempted on object of type integer: ignoring attempt to append item +array bounds: returning null for out of bounds array access + code: 7 + file: + pos: 0 + text: returning null for out of bounds array access +dictionary iter type mismatch: operation for dictionary attempted on object of type integer: treating as empty + code: 7 + file: + pos: 0 + text: operation for dictionary attempted on object of type integer: treating as empty +dictionary type mismatch: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval + code: 7 + file: + pos: 0 + text: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval +dictionary type mismatch: operation for dictionary attempted on object of type integer: returning false for a key containment request + code: 7 + file: + pos: 0 + text: operation for dictionary attempted on object of type integer: returning false for a key containment request +C test 29 done diff --git a/qpdf/qtest/qpdf/test73_future.out b/qpdf/qtest/qpdf/test73_future.out new file mode 100644 index 00000000..9a8982f7 --- /dev/null +++ b/qpdf/qtest/qpdf/test73_future.out @@ -0,0 +1,3 @@ +getRoot: operation for dictionary attempted on object of type null: returning null for attempted key retrieval +WARNING: closed input source: object 1/0: error reading object: QPDF operation attempted on a QPDF object with no input source. QPDF operations are invalid before processFile (or another process method) or after closeInputSource +closed input source: unable to find /Root dictionary diff --git a/qpdf/qtest/qpdf_test_helpers.pm b/qpdf/qtest/qpdf_test_helpers.pm index f1e4a93c..0b8efba1 100644 --- a/qpdf/qtest/qpdf_test_helpers.pm +++ b/qpdf/qtest/qpdf_test_helpers.pm @@ -158,4 +158,15 @@ sub cleanup system("rm -rf *split-out* ???-kfo.pdf *.tmpout \@file.pdf auto-*"); } +sub check_future +{ + my $future = 0; + chomp($_ = `qpdf --version`); + if (m/future/) + { + $future = 1; + } + $future; +} + 1; diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 0f15f506..d0a15334 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -214,8 +214,10 @@ test_0_1(QPDF& pdf, char const* arg2) } QTC::TC("qpdf", "main QTest indirect", qtest.isIndirect() ? 1 : 0); - std::cout << "/QTest is " << (qtest.isIndirect() ? "in" : "") << "direct and has type " - << qtest.getTypeName() << " (" << qtest.getTypeCode() << ")" << std::endl; + auto tn = qtest.getTypeName(); + auto tc = qtest.getTypeCode(); + std::cout << "/QTest is " << (qtest.isIndirect() ? "in" : "") << "direct and has type " << tn + << " (" << tc << ")" << std::endl; if (qtest.isNull()) { QTC::TC("qpdf", "main QTest null"); @@ -1545,7 +1547,13 @@ test_42(QPDF& pdf, char const* arg2) assert(!uninitialized.isInitialized()); assert(!uninitialized.isInteger()); assert(!uninitialized.isDictionary()); +#ifdef QPDF_FUTURE + assert(uninitialized.isNull()); + assert(uninitialized.isScalar()); +#else + assert(!uninitialized.isNull()); assert(!uninitialized.isScalar()); +#endif } static void @@ -3477,8 +3485,13 @@ runtest(int n, char const* filename1, char const* arg2) assert(password == "1234567890123456789012(45678"); QPDFObjectHandle uninitialized; +#ifndef QPDF_FUTURE assert(uninitialized.getTypeCode() == ::ot_uninitialized); assert(strcmp(uninitialized.getTypeName(), "uninitialized") == 0); +#else + assert(uninitialized.getTypeCode() == ::ot_null); + assert(strcmp(uninitialized.getTypeName(), "null") == 0); +#endif // ABI: until QPDF 12, spot check deprecated constants assert(QPDFObject::ot_dictionary == ::ot_dictionary);