2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-12-22 10:58:58 +00:00

Exercise object description in tests

This commit is contained in:
Jay Berkenbilt 2022-05-20 14:23:32 -04:00
parent 6c2fb5b8f0
commit 9b2eb01e25
8 changed files with 77 additions and 16 deletions

3
TODO
View File

@ -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

View File

@ -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<InputSource> is;

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()) {