2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-12-22 19:08:59 +00:00

Refactor QPDF::Objects / JSONReactor interaction

Allow for the fact that when processing JSON input we cannot determine
whether references are dangling until the whole input has been processed.

We handle this by optimistically storing references in the object table.
When additional gens are encountered for the same object we store them
in the new map unconfirmed_objects. Once processing is complete we clean
up the object table and clear unconfirmed_objects.

New test cases are adapted from manual-qpdf-json.json etc.
This commit is contained in:
m-holger 2024-10-13 10:51:07 +01:00
parent 486e68f2e5
commit 405f3765c5
14 changed files with 1422 additions and 63 deletions

View File

@ -256,10 +256,10 @@ class QPDF::JSONReactor: public JSON::Reactor
struct StackFrame
{
StackFrame(state_e state) :
state(state){};
state(state) {};
StackFrame(state_e state, QPDFObjectHandle&& object) :
state(state),
object(object){};
object(object) {};
state_e state;
QPDFObjectHandle object;
};
@ -422,8 +422,7 @@ QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& val
"the value of an object may not be an indirect object reference");
return;
}
pdf.replaceObject(og, replacement);
next_obj = pdf.getObject(og);
next_obj = pdf.m->objects.replace_when_uncertain(og.getObj(), og.getGen(), replacement);
setObjectDescription(tos.object, value);
}
@ -536,7 +535,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} else if (is_obj_key(key, obj, gen)) {
this->cur_object = key;
if (setNextStateIfDictionary(key, value, st_object_top)) {
next_obj = pdf.objects().get_for_json(obj, gen);
next_obj = pdf.objects().get_when_uncertain(obj, gen);
}
} else {
QTC::TC("qpdf", "QPDF_json bad object key");
@ -597,7 +596,14 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
throw std::logic_error("stack empty in st_stream");
}
auto& tos = stack.back();
if (!tos.object.isStream()) {
if (tos.object.isDirectNull()) {
// Object is direct null if it has been replaced.
saw_dict = true;
saw_data = true;
return true;
}
throw std::logic_error("current object is not stream in st_stream");
}
auto uninitialized = QPDFObjectHandle();
@ -740,7 +746,7 @@ QPDF::JSONReactor::makeObject(JSON const& value)
int gen = 0;
std::string str;
if (is_indirect_object(str_v, obj, gen)) {
result = pdf.objects().get_for_json(obj, gen);
result = pdf.objects().get_when_uncertain(obj, gen);
} else if (is_unicode_string(str_v, str)) {
result = QPDFObjectHandle::newUnicodeString(str);
} else if (is_binary_string(str_v, str)) {
@ -798,6 +804,7 @@ QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
JSONReactor reactor(*this, is, must_be_complete);
try {
JSON::parse(*is, &reactor);
objects().clear_unconfirmeds();
} catch (std::runtime_error& e) {
throw std::runtime_error(is->getName() + ": " + e.what());
}

View File

@ -1719,7 +1719,7 @@ Objects::Entry::update(int a_gen, const std::shared_ptr<QPDFObject>& obj)
{
if (*this) {
if (gen != a_gen) {
throw std::logic_error("Internal eror in Objects::update_table");
throw std::logic_error("Internal error in Objects::update_table");
}
object->assign(obj);
} else {
@ -1729,6 +1729,18 @@ Objects::Entry::update(int a_gen, const std::shared_ptr<QPDFObject>& obj)
return object;
}
// Reset to uninitialised state, making any object null.
void
Objects::Entry::reset()
{
if (object) {
object->make_null();
}
object = nullptr;
gen = 0;
unconfirmed = false;
}
std::shared_ptr<QPDFObject>
Objects::update_entry(Entry& e, int id, int gen, const std::shared_ptr<QPDFObject>& obj)
{
@ -1752,7 +1764,14 @@ bool
Objects::unresolved(int id, int gen)
{
auto it = table.find(id);
return it == table.end() || (it->second.gen == gen && it->second.object->isUnresolved());
if (it == table.end()) {
return true;
}
if (it->second.gen == gen) {
return it->second.object->isUnresolved();
}
auto it2 = unconfirmed_objects.find({id, gen});
return it2 != unconfirmed_objects.end() && it2->second->isUnresolved();
}
// Increment last_id and return the result.
@ -1789,6 +1808,23 @@ Objects::initialize()
last_id_ = std::max(last_id_, toI(xref.size() - 1));
}
void
Objects::clear_unconfirmeds()
{
for (auto it = table.begin(), end = table.end(); it != end;) {
if (it->second && it->second.unconfirmed) {
it->second.object->make_null();
it = table.erase(it);
} else {
++it;
}
}
for (auto& item: unconfirmed_objects) {
item.second->make_null();
}
unconfirmed_objects.clear();
}
std::shared_ptr<QPDFObject>
Objects::make_indirect(std::shared_ptr<QPDFObject> const& obj)
{
@ -1820,22 +1856,85 @@ Objects::get_for_parser(int id, int gen, bool parse_pdf)
}
std::shared_ptr<QPDFObject>
Objects::get_for_json(int id, int gen)
Objects::get_when_uncertain(int id, int gen)
{
auto [it, inserted] = table.try_emplace(id);
auto& obj = it->second.object;
if (inserted) {
it->second.gen = gen;
last_id_ = std::max(last_id_, id);
obj = xref.initialized() && !xref.type(id, gen)
? QPDF_Null::create(&qpdf, QPDFObjGen(id, gen))
: QPDF_Unresolved::create(&qpdf, QPDFObjGen(id, gen));
} else {
if (it->second.gen != gen) {
return QPDF_Null::create();
auto& e = table[id];
if (!e) {
// There is no existing entry. If this (id, gen) is in the xref table, we encountered an
// unresolved object. Otherwise we optimistically assume that it is a reference to an actual
// object, but mark it as unconfirmed.
if (xref.initialized()) {
last_id_ = std::max(last_id_, id);
}
e.gen = gen;
if (!xref.type(id, gen)) {
e.unconfirmed = true;
return e.object = QPDF_Null::create(&qpdf, QPDFObjGen(id, gen));
} else {
return e.object = QPDF_Unresolved::create(&qpdf, QPDFObjGen(id, gen));
}
}
return obj;
// Existing entry
if (e.gen == gen) {
return e.object;
}
if (!e.unconfirmed && gen < e.gen) {
// Table contains a newer confirmed object. This (id, gen) is definitely a dangling
// reference.
return QPDF_Null::create();
}
// We don't know whether the table entry is valid, therefore this (id, gen) could still be
// valid despite the gen mismatch. Return a shared indirect null from unconfirmed_objects.
if (auto& j = unconfirmed_objects[{id, gen}]) {
return j;
} else {
return j = QPDF_Null::create(&qpdf, QPDFObjGen(id, gen));
}
}
QPDFObjectHandle
Objects::replace_when_uncertain(int id, int gen, QPDFObjectHandle& oh)
{
// This method is only called while loading a JSON file.
if (!id) {
// Direct null from a reference already identified as dangling.
return oh;
}
auto& e = table[id];
if (!e) {
// This should not happen since the JSONReactor should have called replace_when_uncertain
// when starting to parse the object.
throw std::logic_error("Internal error in Objects::replace_for_json");
}
if (e.gen == gen) {
e.unconfirmed = false; // Now confirmed - this is an actual object.
return update_entry(e, id, gen, oh.getObj());
}
if (e.gen < gen) {
// The current entry will definitely be replaced by oh.
e.object->make_null();
} else if (!e.unconfirmed) {
// oh has been replaced by this current entry.
oh.getObj()->make_null();
return oh;
}
if (e.unconfirmed) {
// The current entry could still become live by a later replace. Let's stash it away.
unconfirmed_objects.emplace(std::pair{id, e.gen}, e.object);
}
e.reset();
// Make sure we don't clean up the actual object.
if (auto it = unconfirmed_objects.find({id, gen}); it != unconfirmed_objects.end()) {
e.object = it->second;
unconfirmed_objects.erase(it); // Make sure we don't clean up the actual object.
}
e.gen = gen;
return update_entry(e, id, gen, oh.getObj());
}
// Replace the object with oh. In the process oh will become almost, but not quite, the indirect

View File

@ -469,11 +469,15 @@ class QPDF::Objects
void erase(int id, int gen);
void replace(int id, int gen, QPDFObjectHandle oh);
// replace for when we cannot be sure whether object references are dangling.
QPDFObjectHandle replace_when_uncertain(int id, int gen, QPDFObjectHandle& oh);
void swap(QPDFObjGen og1, QPDFObjGen og2);
std::shared_ptr<QPDFObject> make_indirect(std::shared_ptr<QPDFObject> const& obj);
void clear_unconfirmeds();
QPDFObjectHandle read(
bool attempt_recovery,
qpdf_offset_t offset,
@ -488,7 +492,9 @@ class QPDF::Objects
int last_id();
std::shared_ptr<QPDFObject> get_for_parser(int id, int gen, bool parse_pdf);
std::shared_ptr<QPDFObject> get_for_json(int id, int gen);
// get for when we cannot be sure whether object references are dangling.
std::shared_ptr<QPDFObject> get_when_uncertain(int id, int gen);
// Get a list of objects that would be permitted in an object stream.
template <typename T>
@ -524,8 +530,10 @@ class QPDF::Objects
}
std::shared_ptr<QPDFObject> update(int gen, const std::shared_ptr<QPDFObject>& obj);
void reset();
int gen{0};
bool unconfirmed{false}; // True if we are uncertain whether the entry is a dangling ref.
std::shared_ptr<QPDFObject> object;
}; // Entry
@ -553,7 +561,7 @@ class QPDF::Objects
Xref_table xref;
std::map<int, Entry> table;
std::map<std::pair<int, int>, std::shared_ptr<QPDFObject>> unconfirmed_objects;
bool initialized_{false};
int last_id_{0};
}; // Objects

View File

@ -148,7 +148,7 @@ $td->runtest("check manual JSON to JSON",
$td->NORMALIZE_NEWLINES);
$td->runtest("check manual JSON to JSON to JSON",
{$td->COMMAND => "qpdf --json-output=2 --json-input a.json -"},
{$td->FILE => "a.json", $td->EXIT_STATUS => 0},
{$td->FILE => "manual-qpdf-json-temporary-out.json", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$n_tests += 6;
@ -383,5 +383,42 @@ $td->runtest("write JSON to pipeline",
{$td->COMMAND => "test_driver 98 minimal.pdf ''"},
{$td->STRING => "test 98 done\n", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$n_tests += 8;
$td->runtest("manual JSON dangling refs to PDF",
{$td->COMMAND => "qpdf --json-input --static-id --qdf" .
" manual-qpdf-json-dangle.json a.pdf"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check manual JSON dangling refs to PDF",
{$td->FILE => "a.pdf"},
{$td->FILE => "manual-qpdf-json-dangle.pdf"});
$td->runtest("check manual JSON dangling refs to PDF to JSON",
{$td->COMMAND => "qpdf --json-output=2 a.pdf -"},
{$td->FILE => "manual-qpdf-json-pdf-dangle.json", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("manual JSON dangling refs to JSON",
{$td->COMMAND => "qpdf --json-input --json-output=2" .
" manual-qpdf-json-pdf-dangle.json a.json"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check manual JSON dangling refs to JSON",
{$td->FILE => "a.json"},
{$td->FILE => "manual-qpdf-json-dangle-out.json"},
$td->NORMALIZE_NEWLINES);
$td->runtest("check manual JSON dangling refs to JSON to JSON",
{$td->COMMAND => "qpdf --json-output=2 --json-input a.json -"},
{$td->FILE => "manual-qpdf-json-dangle-out.json", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("manual JSON replaced stream to JSON",
{$td->COMMAND => "qpdf --json-input --json-output=2" .
" manual-qpdf-json-bad-stream.json a.json"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
$td->runtest("check manual JSON dangling refs to JSON",
{$td->FILE => "a.json"},
{$td->FILE => "manual-qpdf-json-bad-stream-out.json"},
$td->NORMALIZE_NEWLINES);
cleanup();
$td->report($n_tests);

View File

@ -0,0 +1,158 @@
{
"qpdf": [
{
"jsonversion": 2,
"pdfversion": "2.0",
"pushedinheritedpageresources": false,
"calledgetallpages": false,
"maxobjectid": 100
},
{
"obj:1 2 R": {
"value": {
"/Pages": "2 1 R",
"/Type": "/Catalog"
}
},
"obj:2 1 R": {
"value": {
"/Count": 1,
"/Kids": [
"3 2 R"
],
"/Type": "/Pages"
}
},
"obj:3 2 R": {
"value": {
"/Dangle": [
null,
null,
"u:2 1 R is actual",
null,
null,
"u:3 2 R is actual",
null,
null,
"8 5 R",
"u:8 5 R is actual, encounter gens 0, 5, 2, 7",
null,
"u:1 2 R is actual",
null,
"u:5 0 % is actual"
],
"/MediaBox": [
0,
0,
612,
792
],
"/Parent": "2 1 R",
"/Resources": {
"/Font": {
"/F1": "6 0 R"
},
"/ProcSet": "5 0 R"
},
"/Type": "/Page"
}
},
"obj:4 2 R": {
"value": {
"/Type": "/Rubbish"
}
},
"obj:5 0 R": {
"value": [
"/PDF",
"/Text"
]
},
"obj:6 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
"/Name": "/F1",
"/Subtype": "/Type1",
"/Type": "/Font"
}
},
"obj:7 0 R": {
"value": {
"/hex strings": [
"u:Potato",
"b:01020300040560",
"u:AB"
],
"/names": [
"/nesting",
"/hex strings",
"/text/plain"
],
"/nesting": {
"/a": [
1,
2,
{
"/x": "u:y"
},
[
"u:z"
]
],
"/b": {
"/": "u:legal",
"/a": [
1,
2
]
}
},
"/strings": [
"u:one",
"b:24a2",
"u:",
"u:",
"u:()",
"u:(",
"u:)",
"u:a\f\b\t\r\nb",
"u:\"",
"u:\"\"",
"u:\"(\")\"",
"b:410042",
"u:a\nb",
"u:a b",
[
"u:π",
"u:π",
"u:π",
"u:π"
],
[
"u:🥔",
"u:🥔",
"u:🥔",
"u:🥔"
]
]
}
},
"obj:8 5 R": {
"value": {
"/dangling": [
null
],
"/k1": "u:hello"
}
},
"trailer": {
"value": {
"/QTest": "7 0 R",
"/Root": "1 2 R",
"/Size": 9
}
}
}
]
}

View File

@ -0,0 +1,195 @@
{
"comment": [
"File with stream 4 0 replaced by object 4 2.",
"Modified from manual-qpdf-json-dangle.json."
],
"qpdf": [
{
"jsonversion": 2,
"pdfversion": "2.0",
"maybe-future-key": {
"x": [
"Lots of times we ignore things",
"for forward-compatibility so we don't have",
"to change the version number if we add stuff",
"in the future"
]
}
},
{
"obj:3 2 R": {
"value": {
"/Contents": "4 0 R",
"/Dangle": [
"2 0 R",
"2 3 R",
"u:2 1 R is actual",
"3 3 R",
"3 1 R",
"u:3 2 R is actual",
"8 7 R",
"8 0 R",
"8 5 R",
"u:8 5 R is actual, encounter gens 0, 5, 2, 7",
"1 0 R",
"u:1 2 R is actual",
"5 5 R",
"u:5 0 % is actual"
],
"/MediaBox": [
0,
0,
612,
792
],
"/Parent": "2 1 R",
"/Resources": {
"/Font": {
"/F1": "6 0 R"
},
"/ProcSet": "5 0 R"
},
"/Type": "/Page"
},
"ignore": "this is ignored"
},
"obj:4 2 R": {
"value": {
"/Pages": "2 0 R",
"/Type": "/Rubbish"
}
},
"obj:2 1 R": {
"value": {
"/Count": 1,
"/Kids": [
"3 2 R"
],
"/Type": "/Pages"
},
"ignore": {
"potato": "salad",
"this": ["is ignored too"]
}
},
"obj:1 2 R": {
"value": {
"/Pages": "2 1 R",
"/Type": "/Catalog"
}
},
"obj:4 0 R": {
"stream": {
"what-is-this": "doesn't matter",
"dict": {},
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo="
}
},
"obj:1 1 R": {
"value": {
"/Pages": "2 0 R",
"/Type": "/AlsoRubbish"
}
},
"obj:5 0 R": {
"value": [
"/PDF",
"/Text"
]
},
"obj:6 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
"/Name": "/F1",
"/Subtype": "/Type1",
"/Type": "/Font"
}
},
"trailer": {
"even-here-we-ignore": "stuff",
"value": {
"/QTest": "7 0 R",
"/Root": "1 2 R",
"/Size": 9
}
},
"obj:7 0 R": {
"value": {
"/hex strings": [
"u:Potato",
"b:01020300040560",
"u:AB"
],
"/indirect": "8 0 R",
"/names": [
"/nesting",
"/hex strings",
"/text/plain"
],
"/nesting": {
"/a": [
1,
2,
{
"/x": "u:y"
},
[
"u:z"
]
],
"/b": {
"/": "u:legal",
"/a": [
1,
2
]
}
},
"/strings": [
"u:one",
"b:24a2",
"b:",
"u:",
"u:()",
"u:(",
"u:)",
"u:a\f\b\t\r\nb",
"u:\"",
"u:\"\"",
"u:\"(\")\"",
"b:410042",
"u:a\nb",
"u:a b",
["u:π", "u:\u03c0", "b:EFBBBFCF80", "b:feff03c0"],
["u:🥔", "u:\ud83e\udd54", "b:feffd83eDD54", "b:efbbbff09fa594"]
]
}
},
"obj:8 0 R": {
"value": {
"/k1": "u:hello wrong",
"/dangling": ["100 0 R"]
}
},
"obj:8 5 R": {
"value": {
"/k1": "u:hello",
"/dangling": ["100 0 R"]
}
},
"obj:8 2 R": {
"value": {
"/k1": "u:hello also wrong",
"/dangling": ["100 0 R"]
}
},
"obj:8 7 R": {
"value": {
"/k1": "u:hello also wrong",
"/dangling": ["100 0 R"]
}
}
}
]
}

View File

@ -0,0 +1,167 @@
{
"qpdf": [
{
"jsonversion": 2,
"pdfversion": "2.0",
"pushedinheritedpageresources": false,
"calledgetallpages": false,
"maxobjectid": 9
},
{
"obj:1 0 R": {
"value": {
"/Pages": "3 0 R",
"/Type": "/Catalog"
}
},
"obj:2 0 R": {
"value": {
"/hex strings": [
"u:Potato",
"b:01020300040560",
"u:AB"
],
"/names": [
"/nesting",
"/hex strings",
"/text/plain"
],
"/nesting": {
"/a": [
1,
2,
{
"/x": "u:y"
},
[
"u:z"
]
],
"/b": {
"/": "u:legal",
"/a": [
1,
2
]
}
},
"/strings": [
"u:one",
"b:24a2",
"u:",
"u:",
"u:()",
"u:(",
"u:)",
"u:a\f\b\t\r\nb",
"u:\"",
"u:\"\"",
"u:\"(\")\"",
"b:410042",
"u:a\nb",
"u:a b",
[
"u:π",
"u:π",
"u:π",
"u:π"
],
[
"u:🥔",
"u:🥔",
"u:🥔",
"u:🥔"
]
]
}
},
"obj:3 0 R": {
"value": {
"/Count": 1,
"/Kids": [
"4 0 R"
],
"/Type": "/Pages"
}
},
"obj:4 0 R": {
"value": {
"/Contents": "5 0 R",
"/Dangle": [
null,
null,
"u:2 1 R is actual",
null,
null,
"u:3 2 R is actual",
null,
null,
"7 0 R",
"u:8 5 R is actual, encounter gens 0, 5, 2, 7",
null,
"u:1 2 R is actual",
null,
"u:5 0 % is actual"
],
"/MediaBox": [
0,
0,
612,
792
],
"/Parent": "3 0 R",
"/Resources": {
"/Font": {
"/F1": "8 0 R"
},
"/ProcSet": "9 0 R"
},
"/Type": "/Page"
}
},
"obj:5 0 R": {
"stream": {
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
"dict": {}
}
},
"obj:6 0 R": {
"value": 44
},
"obj:7 0 R": {
"value": {
"/dangling": [
null
],
"/k1": "u:hello"
}
},
"obj:8 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
"/Name": "/F1",
"/Subtype": "/Type1",
"/Type": "/Font"
}
},
"obj:9 0 R": {
"value": [
"/PDF",
"/Text"
]
},
"trailer": {
"value": {
"/ID": [
"b:31415926535897932384626433832795",
"b:31415926535897932384626433832795"
],
"/QTest": "2 0 R",
"/Root": "1 0 R",
"/Size": 10
}
}
}
]
}

View File

@ -0,0 +1,195 @@
{
"comment": [
"File with multiple definitions for the same object id and with dangling references.",
"Modified from manual-qpdf-json-dangle.json."
],
"qpdf": [
{
"jsonversion": 2,
"pdfversion": "2.0",
"maybe-future-key": {
"x": [
"Lots of times we ignore things",
"for forward-compatibility so we don't have",
"to change the version number if we add stuff",
"in the future"
]
}
},
{
"obj:3 2 R": {
"value": {
"/Contents": "4 0 R",
"/Dangle": [
"2 0 R",
"2 3 R",
"u:2 1 R is actual",
"3 3 R",
"3 1 R",
"u:3 2 R is actual",
"8 7 R",
"8 0 R",
"8 5 R",
"u:8 5 R is actual, encounter gens 0, 5, 2, 7",
"1 0 R",
"u:1 2 R is actual",
"5 5 R",
"u:5 0 % is actual"
],
"/MediaBox": [
0,
0,
612,
792
],
"/Parent": "2 1 R",
"/Resources": {
"/Font": {
"/F1": "6 0 R"
},
"/ProcSet": "5 0 R"
},
"/Type": "/Page"
},
"ignore": "this is ignored"
},
"obj:1 0 R": {
"value": {
"/Pages": "2 0 R",
"/Type": "/Rubbish"
}
},
"obj:2 1 R": {
"value": {
"/Count": 1,
"/Kids": [
"3 2 R"
],
"/Type": "/Pages"
},
"ignore": {
"potato": "salad",
"this": ["is ignored too"]
}
},
"obj:1 2 R": {
"value": {
"/Pages": "2 1 R",
"/Type": "/Catalog"
}
},
"obj:4 0 R": {
"stream": {
"what-is-this": "doesn't matter",
"dict": {},
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo="
}
},
"obj:1 1 R": {
"value": {
"/Pages": "2 0 R",
"/Type": "/AlsoRubbish"
}
},
"obj:5 0 R": {
"value": [
"/PDF",
"/Text"
]
},
"obj:6 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
"/Name": "/F1",
"/Subtype": "/Type1",
"/Type": "/Font"
}
},
"trailer": {
"even-here-we-ignore": "stuff",
"value": {
"/QTest": "7 0 R",
"/Root": "1 2 R",
"/Size": 9
}
},
"obj:7 0 R": {
"value": {
"/hex strings": [
"u:Potato",
"b:01020300040560",
"u:AB"
],
"/indirect": "8 0 R",
"/names": [
"/nesting",
"/hex strings",
"/text/plain"
],
"/nesting": {
"/a": [
1,
2,
{
"/x": "u:y"
},
[
"u:z"
]
],
"/b": {
"/": "u:legal",
"/a": [
1,
2
]
}
},
"/strings": [
"u:one",
"b:24a2",
"b:",
"u:",
"u:()",
"u:(",
"u:)",
"u:a\f\b\t\r\nb",
"u:\"",
"u:\"\"",
"u:\"(\")\"",
"b:410042",
"u:a\nb",
"u:a b",
["u:π", "u:\u03c0", "b:EFBBBFCF80", "b:feff03c0"],
["u:🥔", "u:\ud83e\udd54", "b:feffd83eDD54", "b:efbbbff09fa594"]
]
}
},
"obj:8 0 R": {
"value": {
"/k1": "u:hello wrong",
"/dangling": ["100 0 R"]
}
},
"obj:8 5 R": {
"value": {
"/k1": "u:hello",
"/dangling": ["100 0 R"]
}
},
"obj:8 2 R": {
"value": {
"/k1": "u:hello also wrong",
"/dangling": ["100 0 R"]
}
},
"obj:8 7 R": {
"value": {
"/k1": "u:hello also wrong",
"/dangling": ["100 0 R"]
}
}
}
]
}

View File

@ -0,0 +1,193 @@
%PDF-2.0
%¿÷¢þ
%QDF-1.0
%% Original object ID: 1 2
1 0 obj
<<
/Pages 3 0 R
/Type /Catalog
>>
endobj
%% Original object ID: 7 0
2 0 obj
<<
/hex#20strings [
(Potato)
<01020300040560>
(AB)
]
/names [
/nesting
/hex#20strings
/text#2fplain
]
/nesting <<
/a [
1
2
<<
/x (y)
>>
[
(z)
]
]
/b <<
/ (legal)
/a [
1
2
]
>>
>>
/strings [
(one)
<24a2>
()
()
(\(\))
(\()
(\))
(a\f\b\t\r\nb)
(")
("")
("\("\)")
<410042>
(a\nb)
(a b)
[
<feff03c0>
<feff03c0>
<efbbbfcf80>
<feff03c0>
]
[
<feffd83edd54>
<feffd83edd54>
<feffd83edd54>
<efbbbff09fa594>
]
]
>>
endobj
%% Original object ID: 2 1
3 0 obj
<<
/Count 1
/Kids [
4 0 R
]
/Type /Pages
>>
endobj
%% Page 1
%% Original object ID: 3 2
4 0 obj
<<
/Contents 5 0 R
/Dangle [
null
null
(2 1 R is actual)
null
null
(3 2 R is actual)
null
null
7 0 R
(8 5 R is actual, encounter gens 0, 5, 2, 7)
null
(1 2 R is actual)
null
(5 0 % is actual)
]
/MediaBox [
0
0
612
792
]
/Parent 3 0 R
/Resources <<
/Font <<
/F1 8 0 R
>>
/ProcSet 9 0 R
>>
/Type /Page
>>
endobj
%% Contents for page 1
%% Original object ID: 4 0
5 0 obj
<<
/Length 6 0 R
>>
stream
BT
/F1 24 Tf
72 720 Td
(Potato) Tj
ET
endstream
endobj
6 0 obj
44
endobj
%% Original object ID: 8 5
7 0 obj
<<
/dangling [
null
]
/k1 (hello)
>>
endobj
%% Original object ID: 6 0
8 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
/Name /F1
/Subtype /Type1
/Type /Font
>>
endobj
%% Original object ID: 5 0
9 0 obj
[
/PDF
/Text
]
endobj
xref
0 10
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
0000000841 00000 n
0000000950 00000 n
0000001427 00000 n
0000001526 00000 n
0000001572 00000 n
0000001662 00000 n
0000001807 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
/Size 10
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1842
%%EOF

View File

@ -128,14 +128,11 @@
"obj:8 0 R": {
"value": {
"/dangling": [
"100 0 R"
null
],
"/k1": "u:hello"
}
},
"obj:100 0 R": {
"value": null
},
"trailer": {
"value": {
"/QTest": "7 0 R",

View File

@ -0,0 +1,167 @@
{
"qpdf": [
{
"jsonversion": 2,
"pdfversion": "2.0",
"pushedinheritedpageresources": false,
"calledgetallpages": false,
"maxobjectid": 9
},
{
"obj:1 0 R": {
"value": {
"/Pages": "3 0 R",
"/Type": "/Catalog"
}
},
"obj:2 0 R": {
"value": {
"/hex strings": [
"u:Potato",
"b:01020300040560",
"u:AB"
],
"/names": [
"/nesting",
"/hex strings",
"/text/plain"
],
"/nesting": {
"/a": [
1,
2,
{
"/x": "u:y"
},
[
"u:z"
]
],
"/b": {
"/": "u:legal",
"/a": [
1,
2
]
}
},
"/strings": [
"u:one",
"b:24a2",
"u:",
"u:",
"u:()",
"u:(",
"u:)",
"u:a\f\b\t\r\nb",
"u:\"",
"u:\"\"",
"u:\"(\")\"",
"b:410042",
"u:a\nb",
"u:a b",
[
"u:π",
"u:π",
"u:π",
"u:π"
],
[
"u:🥔",
"u:🥔",
"u:🥔",
"u:🥔"
]
]
}
},
"obj:3 0 R": {
"value": {
"/Count": 1,
"/Kids": [
"4 0 R"
],
"/Type": "/Pages"
}
},
"obj:4 0 R": {
"value": {
"/Contents": "5 0 R",
"/Dangle": [
null,
null,
"u:2 1 R is actual",
null,
null,
"u:3 2 R is actual",
null,
null,
"7 0 R",
"u:8 5 R is actual, encounter gens 0, 5, 2, 7",
null,
"u:1 2 R is actual",
null,
"u:5 0 % is actual"
],
"/MediaBox": [
0,
0,
612,
792
],
"/Parent": "3 0 R",
"/Resources": {
"/Font": {
"/F1": "8 0 R"
},
"/ProcSet": "9 0 R"
},
"/Type": "/Page"
}
},
"obj:5 0 R": {
"stream": {
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
"dict": {}
}
},
"obj:6 0 R": {
"value": 44
},
"obj:7 0 R": {
"value": {
"/dangling": [
null
],
"/k1": "u:hello"
}
},
"obj:8 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
"/Name": "/F1",
"/Subtype": "/Type1",
"/Type": "/Font"
}
},
"obj:9 0 R": {
"value": [
"/PDF",
"/Text"
]
},
"trailer": {
"value": {
"/ID": [
"b:31415926535897932384626433832795",
"b:31415926535897932384626433832795"
],
"/QTest": "2 0 R",
"/Root": "1 0 R",
"/Size": 10
}
}
}
]
}

View File

@ -5,7 +5,7 @@
"pdfversion": "2.0",
"pushedinheritedpageresources": false,
"calledgetallpages": false,
"maxobjectid": 10
"maxobjectid": 9
},
{
"obj:1 0 R": {
@ -88,14 +88,14 @@
"obj:4 0 R": {
"value": {
"/dangling": [
"6 0 R"
null
],
"/k1": "u:hello"
}
},
"obj:5 0 R": {
"value": {
"/Contents": "7 0 R",
"/Contents": "6 0 R",
"/MediaBox": [
0,
0,
@ -105,26 +105,23 @@
"/Parent": "3 0 R",
"/Resources": {
"/Font": {
"/F1": "9 0 R"
"/F1": "8 0 R"
},
"/ProcSet": "10 0 R"
"/ProcSet": "9 0 R"
},
"/Type": "/Page"
}
},
"obj:6 0 R": {
"value": null
},
"obj:7 0 R": {
"stream": {
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
"dict": {}
}
},
"obj:8 0 R": {
"obj:7 0 R": {
"value": 44
},
"obj:9 0 R": {
"obj:8 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
@ -133,7 +130,7 @@
"/Type": "/Font"
}
},
"obj:10 0 R": {
"obj:9 0 R": {
"value": [
"/PDF",
"/Text"
@ -147,7 +144,7 @@
],
"/QTest": "2 0 R",
"/Root": "1 0 R",
"/Size": 11
"/Size": 10
}
}
}

View File

@ -0,0 +1,145 @@
{
"qpdf": [
{
"jsonversion": 2,
"pdfversion": "2.0",
"pushedinheritedpageresources": false,
"calledgetallpages": false,
"maxobjectid": 8
},
{
"obj:1 0 R": {
"value": {
"/Pages": "2 0 R",
"/Type": "/Catalog"
}
},
"obj:2 0 R": {
"value": {
"/Count": 1,
"/Kids": [
"3 0 R"
],
"/Type": "/Pages"
}
},
"obj:3 0 R": {
"value": {
"/Contents": "4 0 R",
"/MediaBox": [
0,
0,
612,
792
],
"/Parent": "2 0 R",
"/Resources": {
"/Font": {
"/F1": "6 0 R"
},
"/ProcSet": "5 0 R"
},
"/Type": "/Page"
}
},
"obj:4 0 R": {
"stream": {
"data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
"dict": {}
}
},
"obj:5 0 R": {
"value": [
"/PDF",
"/Text"
]
},
"obj:6 0 R": {
"value": {
"/BaseFont": "/Helvetica",
"/Encoding": "/WinAnsiEncoding",
"/Name": "/F1",
"/Subtype": "/Type1",
"/Type": "/Font"
}
},
"obj:7 0 R": {
"value": {
"/hex strings": [
"u:Potato",
"b:01020300040560",
"u:AB"
],
"/indirect": "8 0 R",
"/names": [
"/nesting",
"/hex strings",
"/text/plain"
],
"/nesting": {
"/a": [
1,
2,
{
"/x": "u:y"
},
[
"u:z"
]
],
"/b": {
"/": "u:legal",
"/a": [
1,
2
]
}
},
"/strings": [
"u:one",
"b:24a2",
"u:",
"u:",
"u:()",
"u:(",
"u:)",
"u:a\f\b\t\r\nb",
"u:\"",
"u:\"\"",
"u:\"(\")\"",
"b:410042",
"u:a\nb",
"u:a b",
[
"u:π",
"u:π",
"u:π",
"u:π"
],
[
"u:🥔",
"u:🥔",
"u:🥔",
"u:🥔"
]
]
}
},
"obj:8 0 R": {
"value": {
"/dangling": [
null
],
"/k1": "u:hello"
}
},
"trailer": {
"value": {
"/QTest": "7 0 R",
"/Root": "1 0 R",
"/Size": 9
}
}
}
]
}

View File

@ -89,7 +89,7 @@ endobj
4 0 obj
<<
/dangling [
6 0 R
null
]
/k1 (hello)
>>
@ -99,7 +99,7 @@ endobj
%% Original object ID: 3 0
5 0 obj
<<
/Contents 7 0 R
/Contents 6 0 R
/MediaBox [
0
0
@ -109,24 +109,19 @@ endobj
/Parent 3 0 R
/Resources <<
/Font <<
/F1 9 0 R
/F1 8 0 R
>>
/ProcSet 10 0 R
/ProcSet 9 0 R
>>
/Type /Page
>>
endobj
%% Original object ID: 100 0
6 0 obj
null
endobj
%% Contents for page 1
%% Original object ID: 4 0
7 0 obj
6 0 obj
<<
/Length 8 0 R
/Length 7 0 R
>>
stream
BT
@ -137,12 +132,12 @@ ET
endstream
endobj
8 0 obj
7 0 obj
44
endobj
%% Original object ID: 6 0
9 0 obj
8 0 obj
<<
/BaseFont /Helvetica
/Encoding /WinAnsiEncoding
@ -153,7 +148,7 @@ endobj
endobj
%% Original object ID: 5 0
10 0 obj
9 0 obj
[
/PDF
/Text
@ -161,24 +156,23 @@ endobj
endobj
xref
0 11
0 10
0000000000 65535 f
0000000052 00000 n
0000000133 00000 n
0000000859 00000 n
0000000958 00000 n
0000001059 00000 n
0000001281 00000 n
0000001352 00000 n
0000001451 00000 n
0000001497 00000 n
0000001642 00000 n
0000001058 00000 n
0000001300 00000 n
0000001399 00000 n
0000001445 00000 n
0000001590 00000 n
trailer <<
/QTest 2 0 R
/Root 1 0 R
/Size 11
/Size 10
/ID [<31415926535897932384626433832795><31415926535897932384626433832795>]
>>
startxref
1678
1625
%%EOF