aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--README-maintainer12
-rwxr-xr-xbuild-scripts/prebuild6
-rw-r--r--include/qpdf/DLL.h4
-rw-r--r--include/qpdf/QPDF.hh11
-rw-r--r--include/qpdf/QPDFJob.hh1
-rw-r--r--include/qpdf/QPDFObjectHandle.hh3
-rw-r--r--include/qpdf/QPDFOutlineDocumentHelper.hh1
-rw-r--r--include/qpdf/QPDFOutlineObjectHelper.hh1
-rw-r--r--include/qpdf/QPDFWriter.hh1
-rw-r--r--job.sums2
-rw-r--r--libqpdf/QPDF.cc87
-rw-r--r--libqpdf/QPDFParser.cc5
-rw-r--r--libqpdf/QPDFWriter.cc2
-rw-r--r--libtests/qintc.cc6
-rw-r--r--manual/cli.rst2
-rw-r--r--manual/conf.py2
-rwxr-xr-xperformance_check23
-rw-r--r--qpdf/qpdf.testcov2
-rw-r--r--qpdf/qtest/dangling-refs.test2
-rw-r--r--qpdf/qtest/qpdf/dangling-bad-xref-dangling-out.pdfbin0 -> 1145 bytes
-rw-r--r--qpdf/qtest/qpdf/dangling-bad-xref-dangling.out19
-rw-r--r--qpdf/qtest/qpdf/dangling-bad-xref.pdf110
-rw-r--r--qpdf/qtest/qpdf/dangling-refs-dangling-out.pdfbin934 -> 1057 bytes
-rw-r--r--qpdf/qtest/qpdf/dangling-refs-dangling.out1
-rw-r--r--qpdf/qtest/qpdf/minimal-dangling-out.pdfbin853 -> 853 bytes
-rw-r--r--qpdf/qtest/qpdf/minimal-dangling.out1
-rw-r--r--qpdf/test_driver.cc9
28 files changed, 218 insertions, 97 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e0a4db06..6c362767 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.16)
# the project line. When updating the version, check make_dist for all
# the places it has to be updated.
project(qpdf
- VERSION 11.2.0
+ VERSION 11.2.1
LANGUAGES C CXX)
# Enable correct rpath handling for MacOSX
diff --git a/README-maintainer b/README-maintainer
index bb1859e5..1ca7de8a 100644
--- a/README-maintainer
+++ b/README-maintainer
@@ -4,18 +4,21 @@ ROUTINE DEVELOPMENT
Default:
-cmake -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 \
+cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+ -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 \
-DCMAKE_BUILD_TYPE=RelWithDebInfo ..
Debugging:
-cmake -DMAINTAINER_MODE=1 -DBUILD_SHARED_LIBS=0 \
+cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+ -DMAINTAINER_MODE=1 -DBUILD_SHARED_LIBS=0 \
-DCMAKE_BUILD_TYPE=Debug ..
Profiling:
CFLAGS=-pg LDFLAGS=-pg \
- cmake -DMAINTAINER_MODE=1 -DBUILD_SHARED_LIBS=0 \
+ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+ -DMAINTAINER_MODE=1 -DBUILD_SHARED_LIBS=0 \
-DCMAKE_BUILD_TYPE=Debug ..
Then run `gprof gmon.out`. Note that gmon.out is not cumulative.
@@ -26,7 +29,8 @@ CFLAGS="-fsanitize=address -fsanitize=undefined" \
CXXFLAGS="-fsanitize=address -fsanitize=undefined" \
LDFLAGS="-fsanitize=address -fsanitize=undefined" \
CC=clang CXX=clang++ \
- cmake -DMAINTAINER_MODE=1 -DBUILD_SHARED_LIBS=0 \
+ cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 \
+ -DMAINTAINER_MODE=1 -DBUILD_SHARED_LIBS=0 \
-DCMAKE_BUILD_TYPE=Debug ..
Windows:
diff --git a/build-scripts/prebuild b/build-scripts/prebuild
index e6ee197c..44b59949 100755
--- a/build-scripts/prebuild
+++ b/build-scripts/prebuild
@@ -8,9 +8,9 @@ if ! ./generate_auto_job --check; then
An input or output file of generate_auto_job was modified without
rerunning ./generate_auto_job. If you are seeing this in your pull
-request, you should pass --enable-maintainer-mode to ./configure if
-possible, or if not, run "./generate_auto_job --generate" and include
-any changed files in your commit.
+request, you should pass -DMAINTAINER_MODE=1 to cmake if possible, or
+if not, run "./generate_auto_job --generate" and include any changed
+files in your commit.
******************************
diff --git a/include/qpdf/DLL.h b/include/qpdf/DLL.h
index 257486b0..ac161fe7 100644
--- a/include/qpdf/DLL.h
+++ b/include/qpdf/DLL.h
@@ -26,8 +26,8 @@
/* The first version of qpdf to include the version constants is 10.6.0. */
#define QPDF_MAJOR_VERSION 11
#define QPDF_MINOR_VERSION 2
-#define QPDF_PATCH_VERSION 0
-#define QPDF_VERSION "11.2.0"
+#define QPDF_PATCH_VERSION 1
+#define QPDF_VERSION "11.2.1"
/*
* This file defines symbols that control the which functions,
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index eb3bf2e2..5b2db6a8 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -886,7 +886,6 @@ class QPDF
qpdf->resolve(og);
}
};
- friend class Resolver;
// StreamCopier class is restricted to QPDFObjectHandle so it can
// copy stream data.
@@ -904,7 +903,6 @@ class QPDF
qpdf->copyStreamData(dest, src);
}
};
- friend class Resolver;
// The ParseGuard class allows QPDFObjectHandle to detect
// re-entrant parsing.
@@ -928,7 +926,6 @@ class QPDF
}
QPDF* qpdf;
};
- friend class ParseGuard;
// Pipe class is restricted to QPDF_Stream
class Pipe
@@ -957,7 +954,6 @@ class QPDF
will_retry);
}
};
- friend class Pipe;
// For testing only -- do not add to DLL
static bool test_json_validators();
@@ -1106,7 +1102,6 @@ class QPDF
QPDF* qpdf;
std::set<QPDFObjGen>::const_iterator iter;
};
- friend class ResolveRecorder;
class JSONReactor: public JSON::Reactor
{
@@ -1173,12 +1168,12 @@ class QPDF
std::vector<QPDFObjectHandle> object_stack;
std::set<QPDFObjGen> reserved;
};
- friend class JSONReactor;
void parse(char const* password);
void inParse(bool);
void setTrailer(QPDFObjectHandle obj);
void read_xref(qpdf_offset_t offset);
+ bool resolveXRefTable();
void reconstruct_xref(QPDFExc& e);
bool
parse_xrefFirst(std::string const& line, int& obj, int& num, int& bytes);
@@ -1208,10 +1203,10 @@ class QPDF
bool attempt_recovery,
qpdf_offset_t offset,
std::string const& description,
- QPDFObjGen const& exp_og,
+ QPDFObjGen exp_og,
QPDFObjGen& og,
bool skip_cache_if_in_xref);
- void resolve(QPDFObjGen const& og);
+ void resolve(QPDFObjGen og);
void resolveObjectsInStream(int obj_stream_number);
void stopOnError(std::string const& message);
QPDFObjectHandle reserveObjectIfNotExists(QPDFObjGen const& og);
diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh
index 2d552f64..b12c052a 100644
--- a/include/qpdf/QPDFJob.hh
+++ b/include/qpdf/QPDFJob.hh
@@ -382,7 +382,6 @@ class QPDFJob
}
QPDFJob& o;
};
- friend class Config;
// Return a top-level configuration item. See CONFIGURATION above
// for details. If an invalid configuration is created (such as
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index bf0da746..cfd19ea9 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -1504,7 +1504,6 @@ class QPDFObjectHandle
return QPDFObjectHandle(obj);
}
};
- friend class Factory;
// Accessor for raw underlying object -- only QPDF is allowed to
// call this.
@@ -1533,7 +1532,6 @@ class QPDFObjectHandle
return oh.asStream();
}
};
- friend class ObjAccessor;
// Provide access to specific classes for recursive
// disconnected().
@@ -1550,7 +1548,6 @@ class QPDFObjectHandle
o.disconnect();
}
};
- friend class Resetter;
// Convenience routine: Throws if the assumption is violated. Your
// code will be better if you call one of the isType methods and
diff --git a/include/qpdf/QPDFOutlineDocumentHelper.hh b/include/qpdf/QPDFOutlineDocumentHelper.hh
index 28328c71..bbbc2a63 100644
--- a/include/qpdf/QPDFOutlineDocumentHelper.hh
+++ b/include/qpdf/QPDFOutlineDocumentHelper.hh
@@ -76,7 +76,6 @@ class QPDFOutlineDocumentHelper: public QPDFDocumentHelper
return dh.checkSeen(og);
}
};
- friend class Accessor;
private:
bool checkSeen(QPDFObjGen const& og);
diff --git a/include/qpdf/QPDFOutlineObjectHelper.hh b/include/qpdf/QPDFOutlineObjectHelper.hh
index 6c91909c..11010a9b 100644
--- a/include/qpdf/QPDFOutlineObjectHelper.hh
+++ b/include/qpdf/QPDFOutlineObjectHelper.hh
@@ -93,7 +93,6 @@ class QPDFOutlineObjectHelper: public QPDFObjectHelper
return QPDFOutlineObjectHelper(oh, dh, depth);
}
};
- friend class Accessor;
private:
QPDF_DLL
diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh
index 040f3eba..a9d60672 100644
--- a/include/qpdf/QPDFWriter.hh
+++ b/include/qpdf/QPDFWriter.hh
@@ -550,7 +550,6 @@ class QPDFWriter
std::shared_ptr<Buffer>* bp;
std::string stack_id;
};
- friend class PipelinePopper;
unsigned int bytesNeeded(long long n);
void writeBinary(unsigned long long val, unsigned int bytes);
diff --git a/job.sums b/job.sums
index b4d697f2..48514c54 100644
--- a/job.sums
+++ b/job.sums
@@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431
libqpdf/qpdf/auto_job_json_init.hh 8eec1d4acdf3e40cea46155cbf23a60d226ae6e9493ab18265b95dca790ed66d
libqpdf/qpdf/auto_job_schema.hh a960022725ac4a59db531a3e2bc3964e40113b47348864e9229eb0f3fecfbdc0
manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
-manual/cli.rst 861e3c18c5f17886b715f36ed3ad65fb7b1ccfbe17649dbebb8bd0e228f92aa3
+manual/cli.rst 1fc340f4cbe119b45492ad05526515f2eec98ae8ae4282f73ddbbe5e97ee9f87
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 7446c6da..86846675 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -577,6 +577,8 @@ QPDF::reconstruct_xref(QPDFExc& e)
}
this->m->reconstructed_xref = true;
+ // We may find more objects, which may contain dangling references.
+ this->m->fixed_dangling_refs = false;
warn(damagedPDF("", 0, "file is damaged"));
warn(e);
@@ -1290,65 +1292,36 @@ QPDF::showXRefTable()
}
}
+// Resolve all objects in the xref table. If this triggers a xref table
+// reconstruction abort and return false. Otherwise return true.
+bool
+QPDF::resolveXRefTable()
+{
+ bool may_change = !this->m->reconstructed_xref;
+ for (auto& iter: this->m->xref_table) {
+ if (isUnresolved(iter.first)) {
+ resolve(iter.first);
+ if (may_change && this->m->reconstructed_xref) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+// Ensure all objects in the pdf file, including those in indirect
+// references, appear in the object cache.
void
QPDF::fixDanglingReferences(bool force)
{
- if (this->m->fixed_dangling_refs && (!force)) {
+ if (this->m->fixed_dangling_refs) {
return;
}
- this->m->fixed_dangling_refs = true;
-
- // Create a set of all known indirect objects including those
- // we've previously resolved and those that we have created.
- std::set<QPDFObjGen> to_process;
- for (auto const& iter: this->m->obj_cache) {
- to_process.insert(iter.first);
- }
- for (auto const& iter: this->m->xref_table) {
- to_process.insert(iter.first);
- }
-
- // For each non-scalar item to process, put it in the queue.
- std::list<QPDFObjectHandle> queue;
- queue.push_back(this->m->trailer);
- for (auto const& og: to_process) {
- auto obj = getObject(og);
- if (obj.isDictionary() || obj.isArray()) {
- queue.push_back(obj);
- } else if (obj.isStream()) {
- queue.push_back(obj.getDict());
- }
- }
-
- // Process the queue by recursively resolving all object
- // references. We don't need to do loop detection because we don't
- // traverse known indirect objects when processing the queue.
- while (!queue.empty()) {
- QPDFObjectHandle obj = queue.front();
- queue.pop_front();
- std::list<QPDFObjectHandle> to_check;
- if (obj.isDictionary()) {
- std::map<std::string, QPDFObjectHandle> members =
- obj.getDictAsMap();
- for (auto const& iter: members) {
- to_check.push_back(iter.second);
- }
- } else if (obj.isArray()) {
- auto arr = QPDFObjectHandle::ObjAccessor::asArray(obj);
- arr->addExplicitElementsToList(to_check);
- }
- for (auto sub: to_check) {
- if (sub.isIndirect()) {
- if ((sub.getOwningQPDF() == this) &&
- isUnresolved(sub.getObjGen())) {
- QTC::TC("qpdf", "QPDF detected dangling ref");
- queue.push_back(sub);
- }
- } else {
- queue.push_back(sub);
- }
- }
+ if (!resolveXRefTable()) {
+ QTC::TC("qpdf", "QPDF fix dangling triggered xref reconstruction");
+ resolveXRefTable();
}
+ this->m->fixed_dangling_refs = true;
}
size_t
@@ -1371,7 +1344,7 @@ QPDF::getAllObjects()
{
// After fixDanglingReferences is called, all objects are in the
// object cache.
- fixDanglingReferences(true);
+ fixDanglingReferences();
std::vector<QPDFObjectHandle> result;
for (auto const& iter: this->m->obj_cache) {
result.push_back(newIndirect(iter.first, iter.second.object));
@@ -1631,7 +1604,7 @@ QPDF::readObjectAtOffset(
bool try_recovery,
qpdf_offset_t offset,
std::string const& description,
- QPDFObjGen const& exp_og,
+ QPDFObjGen exp_og,
QPDFObjGen& og,
bool skip_cache_if_in_xref)
{
@@ -1814,7 +1787,7 @@ QPDF::readObjectAtOffset(
}
void
-QPDF::resolve(QPDFObjGen const& og)
+QPDF::resolve(QPDFObjGen og)
{
if (!isUnresolved(og)) {
return;
@@ -2082,6 +2055,8 @@ QPDF::reserveStream(QPDFObjGen const& og)
QPDFObjectHandle
QPDF::getObject(QPDFObjGen const& og)
{
+ // This method is called by the parser and therefore must not
+ // resolve any objects.
if (!isCached(og)) {
m->obj_cache[og] = ObjCache(QPDF_Unresolved::create(this, og), -1, -1);
}
diff --git a/libqpdf/QPDFParser.cc b/libqpdf/QPDFParser.cc
index 452e741b..eca55a71 100644
--- a/libqpdf/QPDFParser.cc
+++ b/libqpdf/QPDFParser.cc
@@ -190,6 +190,11 @@ QPDFParser::parse(bool& empty, bool content_stream)
olist.at(size - 2).getIntValueAsInt(),
olist.back().getIntValueAsInt());
if (ref_og.isIndirect()) {
+ // This action has the desirable side effect
+ // of causing dangling references (references
+ // to indirect objects that don't appear in
+ // the PDF) in any parsed object to appear in
+ // the object cache.
object = context->getObject(ref_og);
indirect_ref = true;
} else {
diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc
index b29f75b9..b07aef53 100644
--- a/libqpdf/QPDFWriter.cc
+++ b/libqpdf/QPDFWriter.cc
@@ -2266,7 +2266,7 @@ QPDFWriter::prepareFileForWrite()
// Make document extension level information direct as required by
// the spec.
- this->m->pdf.fixDanglingReferences(true);
+ this->m->pdf.fixDanglingReferences();
QPDFObjectHandle root = this->m->pdf.getRoot();
for (auto const& key: root.getKeys()) {
QPDFObjectHandle oh = root.getKey(key);
diff --git a/libtests/qintc.cc b/libtests/qintc.cc
index d5738a4b..f6c15f00 100644
--- a/libtests/qintc.cc
+++ b/libtests/qintc.cc
@@ -4,7 +4,7 @@
#include <stdint.h>
#define try_convert(exp_pass, fn, i) \
- try_convert_real(#fn "(" #i ")", exp_pass, fn, i)
+ try_convert_real(#fn "(" #i ")", exp_pass, fn, i)
template <typename From, typename To>
static void
@@ -27,7 +27,7 @@ try_convert_real(
}
#define try_range_check(exp_pass, a, b) \
- try_range_check_real(#a " + " #b, exp_pass, a, b)
+ try_range_check_real(#a " + " #b, exp_pass, a, b)
template <typename T>
static void
@@ -47,7 +47,7 @@ try_range_check_real(
}
#define try_range_check_subtract(exp_pass, a, b) \
- try_range_check_subtract_real(#a " - " #b, exp_pass, a, b)
+ try_range_check_subtract_real(#a " - " #b, exp_pass, a, b)
template <typename T>
static void
diff --git a/manual/cli.rst b/manual/cli.rst
index b50f2862..49ae90f8 100644
--- a/manual/cli.rst
+++ b/manual/cli.rst
@@ -855,7 +855,7 @@ Related Options
and gzip) unless those streams are compressed in some other way.
This analysis is made after qpdf attempts to uncompress streams and
is therefore closely related to :qpdf:ref:`--decode-level`. To
- suppress this behavior and leave streams streams uncompressed, use
+ suppress this behavior and leave streams uncompressed, use
:samp:`--compress-streams=n`. In QDF mode (see :ref:`qdf` and
:qpdf:ref:`--qdf`), the default is to leave streams uncompressed.
diff --git a/manual/conf.py b/manual/conf.py
index a1fc9298..773321cc 100644
--- a/manual/conf.py
+++ b/manual/conf.py
@@ -16,7 +16,7 @@ project = 'QPDF'
copyright = '2005-2022, Jay Berkenbilt'
author = 'Jay Berkenbilt'
# make_dist and the CI build lexically find the release version from this file.
-release = '11.2.0'
+release = '11.2.1'
version = release
extensions = [
'sphinx_rtd_theme',
diff --git a/performance_check b/performance_check
index ac04d168..85ee658b 100755
--- a/performance_check
+++ b/performance_check
@@ -12,6 +12,8 @@ my $whoami = basename($0);
$| = 1;
# [ name, [ args ] ]
+# If <IN> appears, it is replaced with the input file name. Otherwise,
+# the input file name is added to the end of the arguments.
my @tests = (
['no arguments', []],
['generate object streams', ['--object-streams=generate']],
@@ -20,6 +22,7 @@ my @tests = (
['shared resource check', ['--split-pages', '--remove-unreferenced-resources=auto']],
['linearize', ['--linearize']],
['encrypt', ['--encrypt', 'u', 'o', '256', '--']],
+ ['extract first page', ['--empty', '--pages', '<IN>', '1', '--']],
['json-output', ['--json-output']],
['json-input', ['--json-input']],
);
@@ -214,7 +217,7 @@ sub filter_args
{
my $to_check = $arg;
$to_check =~ s/=.*$//;
- if (index($help, $to_check) == -1)
+ if (($to_check =~ m/^-/) && (index($help, $to_check) == -1))
{
my $new_arg = $arg_compat{$arg};
if (! defined $new_arg)
@@ -287,15 +290,27 @@ sub run_test
my ($file, $args) = @_;
my $outfile = "out.pdf";
- foreach my $arg (@$args)
+ my $found_in = 0;
+ my @cmd = ($executable, @$report_mem);
+ for (@$args)
{
+ my $arg = $_;
if ($arg eq '--json-output')
{
$outfile = "out.json";
- last;
}
+ elsif ($arg eq '<IN>')
+ {
+ $found_in = 1;
+ $arg = $file;
+ }
+ push(@cmd, $arg);
+ }
+ if (! $found_in)
+ {
+ push(@cmd, $file);
}
- my @cmd = ($executable, @$args, @$report_mem, $file, "$workdir/$outfile");
+ push(@cmd, "$workdir/$outfile");
# Run once and discard to update caches
system("sync");
run_cmd(@cmd);
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index d9215dd1..3cef71d2 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -381,7 +381,6 @@ QPDFFormFieldObjectHelper list not found 0
QPDFFormFieldObjectHelper list found 0
QPDFFormFieldObjectHelper list first too low 0
QPDFFormFieldObjectHelper list last too high 0
-QPDF detected dangling ref 0
QPDFJob image optimize no pipeline 0
QPDFJob image optimize no shrink 0
QPDFJob image optimize too small 0
@@ -679,3 +678,4 @@ QPDF_json bad pushedinheritedpageresources 0
QPDFPageObjectHelper copied fallback 0
QPDFPageObjectHelper used fallback without copying 0
QPDF skipping cache for known unchecked object 0
+QPDF fix dangling triggered xref reconstruction 0
diff --git a/qpdf/qtest/dangling-refs.test b/qpdf/qtest/dangling-refs.test
index 44d39212..18c2cf6b 100644
--- a/qpdf/qtest/dangling-refs.test
+++ b/qpdf/qtest/dangling-refs.test
@@ -14,7 +14,7 @@ cleanup();
my $td = new TestDriver('dangling-refs');
-my @dangling = (qw(minimal dangling-refs));
+my @dangling = (qw(minimal dangling-refs dangling-bad-xref));
my $n_tests = 2 * scalar(@dangling);
foreach my $f (@dangling)
diff --git a/qpdf/qtest/qpdf/dangling-bad-xref-dangling-out.pdf b/qpdf/qtest/qpdf/dangling-bad-xref-dangling-out.pdf
new file mode 100644
index 00000000..db2f38d1
--- /dev/null
+++ b/qpdf/qtest/qpdf/dangling-bad-xref-dangling-out.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/dangling-bad-xref-dangling.out b/qpdf/qtest/qpdf/dangling-bad-xref-dangling.out
new file mode 100644
index 00000000..05221c72
--- /dev/null
+++ b/qpdf/qtest/qpdf/dangling-bad-xref-dangling.out
@@ -0,0 +1,19 @@
+WARNING: dangling-bad-xref.pdf: file is damaged
+WARNING: dangling-bad-xref.pdf (object 7 0, offset 10000): expected n n obj
+WARNING: dangling-bad-xref.pdf: Attempting to reconstruct cross-reference table
+new object: 13 0 R
+all objects
+1 0 R
+2 0 R
+3 0 R
+4 0 R
+5 0 R
+6 0 R
+7 0 R
+8 0 R
+9 0 R
+10 0 R
+11 0 R
+12 0 R
+13 0 R
+test 53 done
diff --git a/qpdf/qtest/qpdf/dangling-bad-xref.pdf b/qpdf/qtest/qpdf/dangling-bad-xref.pdf
new file mode 100644
index 00000000..797cf4fa
--- /dev/null
+++ b/qpdf/qtest/qpdf/dangling-bad-xref.pdf
@@ -0,0 +1,110 @@
+%PDF-1.3
+%¿÷¢þ
+%QDF-1.0
+
+1 0 obj
+<<
+ /Pages 2 0 R
+ /Type /Catalog
+ /Dangling 8 0 R
+ /AlsoDangling [
+ 9 0 R
+ <<
+ /yes 2 0 R
+ /no 10 0 R
+ /nope 8 0 R
+ >>
+ ]
+>>
+endobj
+
+2 0 obj
+<<
+ /Count 1
+ /Kids [
+ 3 0 R
+ ]
+ /Type /Pages
+>>
+endobj
+
+%% Page 1
+3 0 obj
+<<
+ /Contents 4 0 R
+ /MediaBox [
+ 0
+ 0
+ 612
+ 792
+ ]
+ /Parent 2 0 R
+ /Resources <<
+ /Font <<
+ /F1 6 0 R
+ >>
+ /ProcSet 7 0 R
+ >>
+ /Type /Page
+>>
+endobj
+
+%% Contents for page 1
+4 0 obj
+<<
+ /Length 5 0 R
+>>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+
+5 0 obj
+44
+endobj
+
+6 0 obj
+<<
+ /BaseFont /Helvetica
+ /Encoding /WinAnsiEncoding
+ /Name /F1
+ /Subtype /Type1
+ /Type /Font
+>>
+endobj
+
+7 0 obj
+[
+ /PDF
+ /Text
+]
+endobj
+
+11 0 obj
+[
+ 12 0 R
+]
+endobj
+
+xref
+0 8
+0000000000 65535 f
+0000000025 00000 n
+0000000195 00000 n
+0000000277 00000 n
+0000000492 00000 n
+0000000591 00000 n
+0000000610 00000 n
+0000010000 00000 n
+trailer <<
+ /Root 1 0 R
+ /Size 8
+ /ID [<7141a6cf32de469328cf0f51982b5f89><7141a6cf32de469328cf0f51982b5f89>]
+>>
+startxref
+793
+%%EOF
diff --git a/qpdf/qtest/qpdf/dangling-refs-dangling-out.pdf b/qpdf/qtest/qpdf/dangling-refs-dangling-out.pdf
index 8ca87ebe..75c5e4db 100644
--- a/qpdf/qtest/qpdf/dangling-refs-dangling-out.pdf
+++ b/qpdf/qtest/qpdf/dangling-refs-dangling-out.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/dangling-refs-dangling.out b/qpdf/qtest/qpdf/dangling-refs-dangling.out
index cf1522c4..a41cd59c 100644
--- a/qpdf/qtest/qpdf/dangling-refs-dangling.out
+++ b/qpdf/qtest/qpdf/dangling-refs-dangling.out
@@ -1,3 +1,4 @@
+new object: 11 0 R
all objects
1 0 R
2 0 R
diff --git a/qpdf/qtest/qpdf/minimal-dangling-out.pdf b/qpdf/qtest/qpdf/minimal-dangling-out.pdf
index 48dff413..1ac2c6a0 100644
--- a/qpdf/qtest/qpdf/minimal-dangling-out.pdf
+++ b/qpdf/qtest/qpdf/minimal-dangling-out.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/minimal-dangling.out b/qpdf/qtest/qpdf/minimal-dangling.out
index 6f656850..c3ad7c73 100644
--- a/qpdf/qtest/qpdf/minimal-dangling.out
+++ b/qpdf/qtest/qpdf/minimal-dangling.out
@@ -1,3 +1,4 @@
+new object: 7 0 R
all objects
1 0 R
2 0 R
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index 21fc0150..1f5363c8 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -194,7 +194,7 @@ print_rect(std::ostream& out, QPDFObjectHandle::Rectangle const& r)
}
#define assert_compare_numbers(expected, expr) \
- compare_numbers(#expr, expected, expr)
+ compare_numbers(#expr, expected, expr)
template <typename T1, typename T2>
static void
@@ -2062,8 +2062,10 @@ test_53(QPDF& pdf, char const* arg2)
{
// Test get all objects and dangling ref handling
QPDFObjectHandle root = pdf.getRoot();
- root.replaceKey(
- "/Q1", pdf.makeIndirectObject(QPDFObjectHandle::newString("potato")));
+ auto new_obj =
+ pdf.makeIndirectObject(QPDFObjectHandle::newString("potato"));
+ root.replaceKey("/Q1", new_obj);
+ std::cout << "new object: " << new_obj.unparse() << std::endl;
std::cout << "all objects" << std::endl;
for (auto& obj: pdf.getAllObjects()) {
std::cout << obj.unparse() << std::endl;
@@ -2071,6 +2073,7 @@ test_53(QPDF& pdf, char const* arg2)
QPDFWriter w(pdf, "a.pdf");
w.setStaticID(true);
+ w.setPreserveUnreferencedObjects(true);
w.write();
}