From 9b2eb01e2505e301ce95d31f5387fea0de35eff0 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Fri, 20 May 2022 14:23:32 -0400 Subject: Exercise object description in tests --- TODO | 3 --- include/qpdf/QPDF.hh | 5 ++++- libqpdf/QPDF_json.cc | 34 ++++++++++++++++++++++--------- qpdf/qtest/qpdf-json.test | 10 +++++++++ qpdf/qtest/qpdf/qjson-object-not-dict.out | 2 +- qpdf/qtest/qpdf/test-89.out | 5 +++++ qpdf/qtest/qpdf/test-90.out | 5 +++++ qpdf/test_driver.cc | 29 +++++++++++++++++++++++++- 8 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 qpdf/qtest/qpdf/test-89.out create mode 100644 qpdf/qtest/qpdf/test-90.out diff --git a/TODO b/TODO index eed33e6d..353be485 100644 --- a/TODO +++ b/TODO @@ -58,9 +58,6 @@ Some of this documentation has drifted from the actual implementation. Make sure pages tree repair generates warnings. -* Have a test case if possible that exercises the object description - which means we need some kind of semantic error that gets caught - after creation. * Document that /Length is ignored in stream dictionary replacements Try to never flatten pages tree. Make sure we do something reasonable diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 7d7af225..b4f03599 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -1041,12 +1041,15 @@ class QPDF void containerStart(); void nestedState(std::string const& key, JSON const& value, state_e); + void setObjectDescription(QPDFObjectHandle& oh, JSON const& value); QPDFObjectHandle makeObject(JSON const& value); void error(size_t offset, std::string const& message); QPDFObjectHandle reserveObject(std::string const& obj, std::string const& gen); void replaceObject( - QPDFObjectHandle to_replace, QPDFObjectHandle replacement); + QPDFObjectHandle to_replace, + QPDFObjectHandle replacement, + JSON const& value); QPDF& pdf; std::shared_ptr is; diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index af12459a..9ae5b288 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -249,11 +249,15 @@ QPDF::JSONReactor::reserveObject(std::string const& obj, std::string const& gen) void QPDF::JSONReactor::replaceObject( - QPDFObjectHandle to_replace, QPDFObjectHandle replacement) + QPDFObjectHandle to_replace, + QPDFObjectHandle replacement, + JSON const& value) { auto og = to_replace.getObjGen(); this->reserved.erase(og); this->pdf.replaceObject(og, replacement); + auto oh = pdf.getObjectByObjGen(og); + setObjectDescription(oh, value); } void @@ -326,9 +330,10 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) nestedState(key, value, st_trailer); this->cur_object = "trailer"; } else if (std::regex_match(key, m, OBJ_KEY_RE)) { - object_stack.push_back(reserveObject(m[1].str(), m[2].str())); - nestedState(key, value, st_object_top); this->cur_object = key; + auto oh = reserveObject(m[1].str(), m[2].str()); + object_stack.push_back(oh); + nestedState(key, value, st_object_top); } else { QTC::TC("qpdf", "QPDF_json bad object key"); error( @@ -348,7 +353,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) this->saw_value = true; next_state = st_object; replacement = makeObject(value); - replaceObject(tos, replacement); + replaceObject(tos, replacement, value); } else if (key == "stream") { this->saw_stream = true; nestedState(key, value, st_stream); @@ -359,7 +364,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) this->this_stream_needs_data = true; replacement = pdf.reserveStream(tos.getObjectID(), tos.getGeneration()); - replaceObject(tos, replacement); + replaceObject(tos, replacement, value); } } else { // Ignore unknown keys for forward compatibility @@ -376,6 +381,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) // The trailer must be a dictionary, so we can use nestedState. nestedState("trailer.value", value, st_object); this->pdf.m->trailer = makeObject(value); + setObjectDescription(this->pdf.m->trailer, value); } else if (key == "stream") { // Don't need to set saw_stream here since there's already // an error. @@ -471,6 +477,17 @@ QPDF::JSONReactor::arrayItem(JSON const& value) return true; } +void +QPDF::JSONReactor::setObjectDescription(QPDFObjectHandle& oh, JSON const& value) +{ + std::string description = this->is->getName(); + if (!this->cur_object.empty()) { + description += ", " + this->cur_object; + } + description += " at offset " + QUtil::uint_to_string(value.getStart()); + oh.setObjectDescription(&this->pdf, description); +} + QPDFObjectHandle QPDF::JSONReactor::makeObject(JSON const& value) { @@ -515,12 +532,9 @@ QPDF::JSONReactor::makeObject(JSON const& value) "JSONReactor::makeObject didn't initialize the object"); } - std::string description = this->is->getName(); - if (!this->cur_object.empty()) { - description += " " + this->cur_object + ","; + if (!result.hasObjectDescription()) { + setObjectDescription(result, value); } - description += " offset " + QUtil::uint_to_string(value.getStart()); - result.setObjectDescription(&this->pdf, description); return result; } diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test index 22b714c5..c627da89 100644 --- a/qpdf/qtest/qpdf-json.test +++ b/qpdf/qtest/qpdf-json.test @@ -202,6 +202,16 @@ foreach my $f (@update_files) { {$td->FILE => "$f-updated.pdf"}); } +# Exercise object description +$n_tests += 2; +$td->runtest("json-input object description", + {$td->COMMAND => "test_driver 89 manual-qpdf-json.json"}, + {$td->FILE => "test-89.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("update-from-json object description", + {$td->COMMAND => "test_driver 90 good13.pdf various-updates.json"}, + {$td->FILE => "test-90.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); cleanup(); $td->report($n_tests); diff --git a/qpdf/qtest/qpdf/qjson-object-not-dict.out b/qpdf/qtest/qpdf/qjson-object-not-dict.out index cfb44457..1268c83d 100644 --- a/qpdf/qtest/qpdf/qjson-object-not-dict.out +++ b/qpdf/qtest/qpdf/qjson-object-not-dict.out @@ -1,2 +1,2 @@ -WARNING: qjson-object-not-dict.json (offset 100): "obj:1 0 R" must be a dictionary +WARNING: qjson-object-not-dict.json (obj:1 0 R, offset 100): "obj:1 0 R" must be a dictionary qpdf: qjson-object-not-dict.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/test-89.out b/qpdf/qtest/qpdf/test-89.out new file mode 100644 index 00000000..97d71e1b --- /dev/null +++ b/qpdf/qtest/qpdf/test-89.out @@ -0,0 +1,5 @@ +WARNING: manual-qpdf-json.json, trailer at offset 1761: operation for array attempted on object of type dictionary: ignoring attempt to append item +WARNING: manual-qpdf-json.json, obj:1 0 R at offset 1079: operation for array attempted on object of type dictionary: ignoring attempt to append item +WARNING: manual-qpdf-json.json, obj:5 0 R at offset 1404: operation for dictionary attempted on object of type array: ignoring key replacement request +WARNING: manual-qpdf-json.json, obj:5 0 R at offset 1416: operation for dictionary attempted on object of type name: ignoring key replacement request +test 89 done diff --git a/qpdf/qtest/qpdf/test-90.out b/qpdf/qtest/qpdf/test-90.out new file mode 100644 index 00000000..2c535a09 --- /dev/null +++ b/qpdf/qtest/qpdf/test-90.out @@ -0,0 +1,5 @@ +WARNING: various-updates.json, trailer at offset 580: operation for array attempted on object of type dictionary: ignoring attempt to append item +WARNING: various-updates.json, obj:7 0 R at offset 171: operation for array attempted on object of type dictionary: ignoring attempt to append item +WARNING: various-updates.json, obj:7 0 R at offset 283: operation for integer attempted on object of type array: returning 0 +WARNING: good13.pdf, object 1 0 at offset 19: operation for array attempted on object of type dictionary: ignoring attempt to append item +test 90 done diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index f355aa54..8f09785a 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -3172,6 +3172,31 @@ test_88(QPDF& pdf, char const* arg2) assert(arr2.eraseItemAndGet(50).isNull()); } +static void +test_89(QPDF& pdf, char const* arg2) +{ + // Generate object warning with json-input. Crafted to work with + // manual-qpdf-json.json. + auto null = QPDFObjectHandle::newNull(); + pdf.getTrailer().appendItem(null); + pdf.getRoot().appendItem(null); + pdf.getObjectByID(5, 0).replaceKey("/X", null); + pdf.getObjectByID(5, 0).getArrayItem(0).replaceKey("/X", null); +} + +static void +test_90(QPDF& pdf, char const* arg2) +{ + // Generate object warning with update-from-json. Crafted to work + // with good13.pdf and various-updates.json. JSON file is arg2. + pdf.updateFromJSON(arg2); + pdf.getTrailer().appendItem(QPDFObjectHandle::newNull()); + pdf.getTrailer().getKey("/QTest").appendItem(QPDFObjectHandle::newNull()); + pdf.getTrailer().getKey("/QTest").getKey("/strings").getIntValue(); + // not from json + pdf.getRoot().appendItem(QPDFObjectHandle::newNull()); +} + void runtest(int n, char const* filename1, char const* arg2) { @@ -3235,6 +3260,8 @@ runtest(int n, char const* filename1, char const* arg2) (std::string(filename1) + ".pdf").c_str(), p, size); } else if (ignore_filename.count(n)) { // Ignore filename argument entirely + } else if (n == 89) { + pdf.createFromJSON(filename1); } else if (n % 2 == 0) { if (n % 4 == 0) { QTC::TC("qpdf", "exercise processFile(name)"); @@ -3274,7 +3301,7 @@ runtest(int n, char const* filename1, char const* arg2) {76, test_76}, {77, test_77}, {78, test_78}, {79, test_79}, {80, test_80}, {81, test_81}, {82, test_82}, {83, test_83}, {84, test_84}, {85, test_85}, {86, test_86}, {87, test_87}, - {88, test_88}}; + {88, test_88}, {89, test_89}, {90, test_90}}; auto fn = test_functions.find(n); if (fn == test_functions.end()) { -- cgit v1.2.3-70-g09d2