Commit acdf5b2e7a9b3074125bc95bfcf7e6abdc9704b4

Authored by Jay Berkenbilt
Committed by Jay Berkenbilt
1 parent 4c0addfe

Update process for ABI testing

CMakeLists.txt
... ... @@ -36,6 +36,9 @@ CMAKE_DEPENDENT_OPTION(
36 36 WERROR "Treat compiler warnings as errors" OFF
37 37 "NOT MAINTAINER_MODE; NOT CI_MODE" ON)
38 38 CMAKE_DEPENDENT_OPTION(
  39 + CHECK_SIZES "Compare sizes.cc with classes in public API" OFF
  40 + "NOT MAINTAINER_MODE" ON)
  41 +CMAKE_DEPENDENT_OPTION(
39 42 GENERATE_AUTO_JOB "Automatically regenerate job files" OFF
40 43 "NOT MAINTAINER_MODE" ON)
41 44 CMAKE_DEPENDENT_OPTION(
... ...
README-maintainer
... ... @@ -297,26 +297,17 @@ RELEASE PREPARATION
297 297 testing, do performance testing.
298 298  
299 299 * Test for performance and binary compatibility:
300   - * Check out the last release
301   - * cmake -S . -B build \
302   - -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 -DBUILD_DOC=0 \
303   - -DCMAKE_BUILD_TYPE=RelWithDebInfo
304   - * cmake --build build -j$(nproc)
305   - * Check out the current version
306   - * ./performance_check | tee -a /tmp/perf
307   - * cmake -S . -B build \
308   - -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 -DBUILD_DOC=0 \
309   - -DCMAKE_BUILD_TYPE=RelWithDebInfo
310   - * cmake --build build -j$(nproc) --target libqpdf
311   - * Checkout the last release
312   - * (cd build; ctest --verbose)
313   - * (some failures are normal -- looking for binary compatibility)
314   - * Check out the current version
315   - * cmake -S . -B build \
316   - -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 -DBUILD_DOC=0 \
317   - -DCMAKE_BUILD_TYPE=RelWithDebInfo
318   - * cmake --build build -j$(nproc)
319   - * ./performance_check | tee -a /tmp/perf
  300 +
  301 + ./abi-perf-test release-<old> @
  302 +
  303 + Prefix with SKIP_PERF=1 to skip performance test.
  304 + Prefix with SKIP_TESTS=1 to skip test suite run.
  305 +
  306 + See "ABI checks" for details about the process.
  307 + End state:
  308 + * /tmp/check-abi/old contains old sizes and library
  309 + * /tmp/check-abi/new contains new sizes and library
  310 + * run check_abi manually to compare
320 311  
321 312 * Run pikepdf's test suite. Do this in a separate shell.
322 313  
... ... @@ -512,3 +503,39 @@ manual tests were done:
512 503 We are using RelWithDebInfo for mingw and other non-Windows builds but
513 504 Release for MSVC. There are linker warnings if MSVC is built with
514 505 RelWithDebInfo when using external-libs.
  506 +
  507 +
  508 +ABI checks
  509 +
  510 +Until the conversion of the build to cmake, we relied on running the
  511 +test suite with old executables and a new library. When QPDFJob was
  512 +introduced, this method got much less reliable since a lot of public
  513 +API doesn't cross the shared library boundary. Also, when switching to
  514 +cmake, we wanted a stronger check that the library had the expected
  515 +ABI.
  516 +
  517 +Our ABI check now consists of three parts:
  518 +
  519 +* The same check as before: run the test suite with old executables
  520 + and a new library
  521 +
  522 +* Do a literal comparison of the symbols in the old and new shared
  523 + libraries -- this is a strong test of ABI change
  524 +
  525 +* Do a check to ensure that object sizes didn't change -- even with no
  526 + changes to the API of exported functions, size changes break API
  527 +
  528 +The combination of these checks is pretty strong, though there are
  529 +still things that could potentially break ABI, such as
  530 +
  531 +* Changes to the types of public or protected data members without
  532 + changing the size
  533 +
  534 +* Changes to the meanings of parameters with changing the signature
  535 +
  536 +Not breaking ABI/API still requires care.
  537 +
  538 +The check_abi script is responsible for performing many of these
  539 +steps. See comments in check_abi for additional notes. Running
  540 +"check_abi check-sizes" is run by ctest on Linux when CHECK_SIZES is
  541 +on.
... ...
abi-perf-test 0 → 100755
  1 +#!/usr/bin/env bash
  2 +set -eo pipefail
  3 +cd $(dirname $0)
  4 +whoami=$(basename $0)
  5 +
  6 +if [[ $(git status -s | egrep -v abi-perf-test | wc -l) != 0 ]]; then
  7 + echo 1>&2 "${whoami}: git is not clean. (abi-perf-test changes ignored)"
  8 + git status -s
  9 + exit 2
  10 +fi
  11 +
  12 +old_rev=${1-bad}
  13 +new_rev=${2-bad}
  14 +
  15 +if [ "$new_rev" = "bad" ]; then
  16 + echo 1>&2 "Usage: $whoami old-rev new-rev"
  17 + exit 2
  18 +fi
  19 +
  20 +old_rev_hash=$(git rev-parse $old_rev)
  21 +new_rev_hash=$(git rev-parse $new_rev)
  22 +
  23 +cat <<EOF
  24 +
  25 +Checking ABI:
  26 +* old revision: $old_rev = $old_rev_hash
  27 +* new revision: $new_rev = $new_rev_hash
  28 +
  29 +EOF
  30 +
  31 +work=/tmp/check-abi
  32 +if [ -d $work ]; then
  33 + if [ ! -f $work/.abi ]; then
  34 + echo 1>&2 "$work exists and is not ours"
  35 + exit 2
  36 + else
  37 + rm -rf $work
  38 + fi
  39 +fi
  40 +mkdir -p $work/{old,new}
  41 +touch $work/.abi
  42 +
  43 +source=$PWD
  44 +repo=file://$source/.git
  45 +cd $work
  46 +git clone $repo qpdf
  47 +cd qpdf
  48 +
  49 +git tag abi-old $old_rev_hash
  50 +git tag abi-new $new_rev_hash
  51 +
  52 +echo "** building old version **"
  53 +
  54 +git checkout abi-old
  55 +cmake -S . -B build \
  56 + -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 -DBUILD_DOC=0 \
  57 + -DCMAKE_BUILD_TYPE=RelWithDebInfo
  58 +cmake --build build -j$(nproc)
  59 +
  60 +echo "** saving old library and size information **"
  61 +
  62 +$source/check_abi check-sizes --lib build/libqpdf/libqpdf.so
  63 +./build/qpdf/sizes >| $work/old/sizes
  64 +cp build/libqpdf/libqpdf.so.*.* $work/old
  65 +
  66 +if [ "$SKIP_PERF" != "1" ]; then
  67 + echo "** writing performance details for old revision to $work/perf **"
  68 + $source/performance_check --dir $source/../performance-test-files | \
  69 + tee -a $work/perf
  70 +fi
  71 +
  72 +echo "** building new version's library and sizes **"
  73 +
  74 +git checkout abi-new
  75 +cmake -S . -B build \
  76 + -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 -DBUILD_DOC=0 \
  77 + -DCMAKE_BUILD_TYPE=RelWithDebInfo
  78 +cmake --build build -j$(nproc) --target sizes
  79 +
  80 +echo "** saving new library and size information **"
  81 +
  82 +$source/check_abi check-sizes --lib build/libqpdf/libqpdf.so
  83 +./build/qpdf/sizes >| $work/new/sizes
  84 +cp build/libqpdf/libqpdf.so.*.* $work/new
  85 +
  86 +echo "** running ABI comparison **"
  87 +
  88 +$source/check_abi compare --old-lib $work/old/libqpdf.so.*.* \
  89 + --new-lib build/libqpdf/libqpdf.so \
  90 + --old-sizes $work/old/sizes --new-sizes $work/new/sizes
  91 +
  92 +if [ "$SKIP_TESTS" != "1" ]; then
  93 + # Switch back to the previous release and run tests. There may be
  94 + # some failures based on functionality change. We are looking for
  95 + # ABI breakage.
  96 + git checkout abi-old
  97 + set +e
  98 + (cd build; ctest --verbose)
  99 + if [ $? != 0 ]; then
  100 + cat <<EOF
  101 +
  102 +**********************************************************************
  103 +There were some test failures; inspect to determine whether they are
  104 +ABI-related.
  105 +**********************************************************************
  106 +
  107 +EOF
  108 + fi
  109 + set -e
  110 +fi
  111 +
  112 +git checkout abi-new
  113 +
  114 +if [ "$SKIP_PERF" != "1" ]; then
  115 + echo "** writing performance details for new revision to $work/perf **"
  116 +
  117 + cmake -S . -B build \
  118 + -DMAINTAINER_MODE=1 -DBUILD_STATIC_LIBS=0 -DBUILD_DOC=0 \
  119 + -DCMAKE_BUILD_TYPE=RelWithDebInfo
  120 + cmake --build build -j$(nproc)
  121 + $source/performance_check --dir $source/../performance-test-files | \
  122 + tee -a $work/perf
  123 +fi
... ...
check_abi 0 → 100755
  1 +#!/usr/bin/env python3
  2 +import os
  3 +import sys
  4 +import argparse
  5 +import subprocess
  6 +import re
  7 +
  8 +whoami = os.path.basename(sys.argv[0])
  9 +whereami = os.path.dirname(os.path.realpath(__file__))
  10 +
  11 +
  12 +def warn(*args, **kwargs):
  13 + print(*args, file=sys.stderr, **kwargs)
  14 +
  15 +
  16 +class Main:
  17 + def main(self, args=sys.argv[1:], prog=whoami):
  18 + options = self.parse_args(args, prog)
  19 + if options.action == 'dump':
  20 + self.dump(options)
  21 + elif options.action == 'check-sizes':
  22 + self.check_sizes(options)
  23 + elif options.action == 'compare':
  24 + self.compare(options)
  25 + else:
  26 + exit(f'{whoami}: unknown action')
  27 +
  28 + def parse_args(self, args, prog):
  29 + parser = argparse.ArgumentParser(
  30 + prog=prog,
  31 + # formatter_class=argparse.RawDescriptionHelpFormatter,
  32 + description='Check ABI for changes',
  33 + )
  34 +
  35 + subparsers = parser.add_subparsers(
  36 + dest='action',
  37 + help='specify subcommand; run action with --help for details',
  38 + required=True)
  39 + lib_arg = ('--lib', {'help': 'library file', 'required': True})
  40 +
  41 + p_dump = subparsers.add_parser(
  42 + 'dump',
  43 + help='dump qpdf symbols in a library')
  44 + p_dump.add_argument(lib_arg[0], **lib_arg[1])
  45 +
  46 + p_check_sizes = subparsers.add_parser(
  47 + 'check-sizes',
  48 + help='check consistency between library and sizes.cc')
  49 + p_check_sizes.add_argument(lib_arg[0], **lib_arg[1])
  50 +
  51 + p_compare = subparsers.add_parser(
  52 + 'compare',
  53 + help='compare libraries and sizes')
  54 + p_compare.add_argument('--new-lib',
  55 + help='new library file',
  56 + required=True)
  57 + p_compare.add_argument('--old-lib',
  58 + help='old library file',
  59 + required=True)
  60 + p_compare.add_argument('--old-sizes',
  61 + help='output of old sizes',
  62 + required=True)
  63 + p_compare.add_argument('--new-sizes',
  64 + help='output of new sizes',
  65 + required=True)
  66 + return parser.parse_args(args)
  67 +
  68 + def get_versions(self, path):
  69 + p = os.path.basename(os.path.realpath(path))
  70 + m = re.match(r'^libqpdf.so.(\d+).(\d+).(\d+)$', p)
  71 + if not m:
  72 + exit(f'{whoami}: {path} does end with libqpdf.so.x.y.z')
  73 + major = int(m.group(1))
  74 + minor = int(m.group(2))
  75 + patch = int(m.group(3))
  76 + return (major, minor, patch)
  77 +
  78 + def get_symbols(self, path):
  79 + symbols = set()
  80 + p = subprocess.run(
  81 + ['nm', '-D', '--demangle', '--with-symbol-versions', path],
  82 + stdout=subprocess.PIPE)
  83 + if p.returncode:
  84 + exit(f'{whoami}: failed to get symbols from {path}')
  85 + for line in p.stdout.decode().split('\n'):
  86 + # The LIBQPDF_\d+ comes from the version tag in
  87 + # libqpdf.map.in.
  88 + m = re.match(r'^[0-9a-f]+ (.) (.+)@@LIBQPDF_\d+\s*$', line)
  89 + if not m:
  90 + continue
  91 + symbols.add(m.group(2))
  92 + return symbols
  93 +
  94 + def dump(self, options):
  95 + # This is just for manual use to investigate surprises.
  96 + for i in sorted(self.get_symbols(options.lib)):
  97 + print(i)
  98 +
  99 + def check_sizes(self, options):
  100 + # Make sure that every class with methods in the public API
  101 + # appears in sizes.cc either explicitly ignored or in a
  102 + # print_size call. This enables us to reliably test whether
  103 + # any object changed size by following the ABI checking
  104 + # procedures outlined in README-maintainer.
  105 +
  106 + # To keep things up to date, whenever we add or remove
  107 + # objects, we have to update sizes.cc. The check-sizes option
  108 + # can be run at any time on an up-to-date build.
  109 +
  110 + lib = self.get_symbols(options.lib)
  111 + classes = set()
  112 + for i in sorted(lib):
  113 + # Find a symbol that looks like a class method.
  114 + m = re.match(r'(((?:^\S*?::)?(?:[^:\s]+))::([^:\s]+))\(', i)
  115 + if m:
  116 + full = m.group(1)
  117 + clas = m.group(2)
  118 + method = m.group(3)
  119 + if full.startswith('std::') or method.startswith('~'):
  120 + # Sometimes std:: template instantiations make it
  121 + # into the library. Ignore those. Also ignore
  122 + # classes whose only exported method is a
  123 + # destructor.
  124 + continue
  125 + # Otherwise, if the class exports a method, we
  126 + # potentially care about changes to its size, so add
  127 + # it.
  128 + classes.add(clas)
  129 + in_sizes = set()
  130 + # Read the sizes.cc to make sure everything's there.
  131 + with open(os.path.join(whereami, 'qpdf/sizes.cc'), 'r') as f:
  132 + for line in f.readlines():
  133 + m = re.search(r'^\s*(?:ignore_class|print_size)\((.*?)\)',
  134 + line)
  135 + if m:
  136 + in_sizes.add(m.group(1))
  137 + sizes_only = in_sizes - classes
  138 + classes_only = classes - in_sizes
  139 + if sizes_only or classes_only:
  140 + if sizes_only:
  141 + print("classes in sizes.cc but not in the library:")
  142 + for i in sorted(sizes_only):
  143 + print(' ', i)
  144 + if classes_only:
  145 + print("classes in the library but not in sizes.cc:")
  146 + for i in sorted(classes_only):
  147 + print(' ', i)
  148 + exit(f'{whoami}: mismatch between library and sizes.cc')
  149 + else:
  150 + print(f'{whoami}: sizes.cc is consistent with the library')
  151 +
  152 + def read_sizes(self, filename):
  153 + sizes = {}
  154 + with open(filename, 'r') as f:
  155 + for line in f.readlines():
  156 + line = line.strip()
  157 + m = re.match(r'^(.*) (\d+)$', line)
  158 + if not m:
  159 + exit(f'{filename}: bad sizes line: {line}')
  160 + sizes[m.group(1)] = m.group(2)
  161 + return sizes
  162 +
  163 + def compare(self, options):
  164 + old_version = self.get_versions(options.old_lib)
  165 + new_version = self.get_versions(options.new_lib)
  166 + old = self.get_symbols(options.old_lib)
  167 + new = self.get_symbols(options.new_lib)
  168 + if old_version > new_version:
  169 + exit(f'{whoami}: old version is newer than new version')
  170 + allow_abi_change = new_version[0] > old_version[0]
  171 + allow_added = allow_abi_change or (new_version[1] > old_version[1])
  172 + removed = sorted(old - new)
  173 + added = sorted(new - old)
  174 + if removed:
  175 + print('INTERFACES REMOVED:')
  176 + for i in removed:
  177 + print(' ', i)
  178 + else:
  179 + print('No interfaces were removed')
  180 + if added:
  181 + print('INTERFACES ADDED')
  182 + for i in added:
  183 + print(' ', i)
  184 + else:
  185 + print('No interfaces were added')
  186 +
  187 + if removed and not allow_abi_change:
  188 + exit(f'{whoami}: **ERROR**: major version must be bumped')
  189 + elif added and not allow_added:
  190 + exit(f'{whoami}: **ERROR**: minor version must be bumped')
  191 + else:
  192 + print(f'{whoami}: ABI check passed.')
  193 +
  194 + old_sizes = self.read_sizes(options.old_sizes)
  195 + new_sizes = self.read_sizes(options.new_sizes)
  196 + size_errors = False
  197 + for k, v in old_sizes.items():
  198 + if k in new_sizes and v != new_sizes[k]:
  199 + size_errors = True
  200 + print(f'{k} changed size from {v} to {new_sizes[k]}')
  201 + if size_errors:
  202 + if not allow_abi_change:
  203 + exit(f'{whoami}:'
  204 + 'size changes detected; this is an ABI change.')
  205 + else:
  206 + print(f'{whoami}: no size changes detected')
  207 +
  208 +
  209 +if __name__ == '__main__':
  210 + try:
  211 + Main().main()
  212 + except KeyboardInterrupt:
  213 + exit(130)
... ...
libqpdf/CMakeLists.txt
... ... @@ -579,3 +579,13 @@ if(INSTALL_CMAKE_PACKAGE)
579 579 ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfig.cmake
580 580 DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf)
581 581 endif()
  582 +
  583 +if(CHECK_SIZES AND BUILD_SHARED_LIBS AND (CMAKE_SYSTEM_NAME STREQUAL "Linux"))
  584 + # We can only do this check on a system with ELF shared libraries.
  585 + # Since this is a maintainer-only option, testing for Linux is a
  586 + # close enough approximation.
  587 + add_test(
  588 + NAME check-sizes
  589 + COMMAND ${qpdf_SOURCE_DIR}/check_abi check-sizes
  590 + --lib $<TARGET_FILE:libqpdf>)
  591 +endif()
... ...
manual/installation.rst
... ... @@ -239,6 +239,14 @@ QTEST_COLOR
239 239 Options for Working on qpdf
240 240 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
241 241  
  242 +CHECK_SIZES
  243 + The source file :file:`qpdf/sizes.cc` is used to display the sizes
  244 + of all objects in the public API. Consistency of its output between
  245 + releases is used as part of the check against accidental breakage of
  246 + the binary interface (ABI). Turning this on causes a test to be run
  247 + that ensures an exact match between classes in ``sizes.cc`` and
  248 + classes in the library's public API. This option requires Python 3.
  249 +
242 250 GENERATE_AUTO_JOB
243 251 Some qpdf source files are automatically generated from
244 252 :file:`job.yml` and the CLI documentation. If you are adding new
... ... @@ -277,6 +285,8 @@ MAINTAINER_MODE
277 285  
278 286 - ``BUILD_DOC``
279 287  
  288 + - ``CHECK_SIZES``
  289 +
280 290 - ``GENERATE_AUTO_JOB``
281 291  
282 292 - ``WERROR``
... ...
qpdf/CMakeLists.txt
... ... @@ -2,6 +2,7 @@ set(MAIN_CXX_PROGRAMS
2 2 qpdf
3 3 fix-qdf
4 4 pdf_from_scratch
  5 + sizes
5 6 test_driver
6 7 test_large_file
7 8 test_parsedoffset
... ... @@ -26,6 +27,7 @@ foreach(PROG ${MAIN_C_PROGRAMS})
26 27 set_property(TARGET ${PROG} PROPERTY LINKER_LANGUAGE CXX)
27 28 endforeach()
28 29 target_include_directories(qpdf-ctest PRIVATE ${CMAKE_BINARY_DIR}/libqpdf)
  30 +target_include_directories(sizes PRIVATE ${JPEG_INCLUDE})
29 31  
30 32 foreach(B qpdf test_unicode_filenames fix-qdf test_shell_glob)
31 33 if(WINDOWS_WMAIN_COMPILE)
... ...
qpdf/sizes.cc 0 → 100644
  1 +// See "ABI checks" in README-maintainer and comments in check_abi.
  2 +
  3 +#include <iostream>
  4 +
  5 +#include <qpdf/Buffer.hh>
  6 +#include <qpdf/BufferInputSource.hh>
  7 +#include <qpdf/ClosedFileInputSource.hh>
  8 +#include <qpdf/FileInputSource.hh>
  9 +#include <qpdf/InputSource.hh>
  10 +#include <qpdf/JSON.hh>
  11 +#include <qpdf/PDFVersion.hh>
  12 +#include <qpdf/Pipeline.hh>
  13 +#include <qpdf/Pl_Buffer.hh>
  14 +#include <qpdf/Pl_Concatenate.hh>
  15 +#include <qpdf/Pl_Count.hh>
  16 +#include <qpdf/Pl_DCT.hh>
  17 +#include <qpdf/Pl_Discard.hh>
  18 +#include <qpdf/Pl_Flate.hh>
  19 +#include <qpdf/Pl_QPDFTokenizer.hh>
  20 +#include <qpdf/Pl_RunLength.hh>
  21 +#include <qpdf/Pl_StdioFile.hh>
  22 +#include <qpdf/QPDF.hh>
  23 +#include <qpdf/QPDFAcroFormDocumentHelper.hh>
  24 +#include <qpdf/QPDFAnnotationObjectHelper.hh>
  25 +#include <qpdf/QPDFCryptoProvider.hh>
  26 +#include <qpdf/QPDFEFStreamObjectHelper.hh>
  27 +#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
  28 +#include <qpdf/QPDFExc.hh>
  29 +#include <qpdf/QPDFFileSpecObjectHelper.hh>
  30 +#include <qpdf/QPDFFormFieldObjectHelper.hh>
  31 +#include <qpdf/QPDFJob.hh>
  32 +#include <qpdf/QPDFMatrix.hh>
  33 +#include <qpdf/QPDFNameTreeObjectHelper.hh>
  34 +#include <qpdf/QPDFNumberTreeObjectHelper.hh>
  35 +#include <qpdf/QPDFObjGen.hh>
  36 +#include <qpdf/QPDFObject.hh>
  37 +#include <qpdf/QPDFObjectHandle.hh>
  38 +#include <qpdf/QPDFOutlineDocumentHelper.hh>
  39 +#include <qpdf/QPDFOutlineObjectHelper.hh>
  40 +#include <qpdf/QPDFPageDocumentHelper.hh>
  41 +#include <qpdf/QPDFPageLabelDocumentHelper.hh>
  42 +#include <qpdf/QPDFPageObjectHelper.hh>
  43 +#include <qpdf/QPDFStreamFilter.hh>
  44 +#include <qpdf/QPDFSystemError.hh>
  45 +#include <qpdf/QPDFTokenizer.hh>
  46 +#include <qpdf/QPDFUsage.hh>
  47 +#include <qpdf/QPDFWriter.hh>
  48 +#include <qpdf/QPDFXRefEntry.hh>
  49 +
  50 +#define ignore_class(cls)
  51 +#define print_size(cls) \
  52 + std::cout << #cls << " " << sizeof(cls) << std::endl
  53 +
  54 +// These classes are not really public.
  55 +// ------
  56 +ignore_class(BitStream);
  57 +ignore_class(BitWriter);
  58 +ignore_class(CryptoRandomDataProvider);
  59 +ignore_class(InsecureRandomDataProvider);
  60 +ignore_class(JSONHandler);
  61 +ignore_class(MD5);
  62 +ignore_class(Pl_AES_PDF);
  63 +ignore_class(Pl_ASCII85Decoder);
  64 +ignore_class(Pl_ASCIIHexDecoder);
  65 +ignore_class(Pl_LZWDecoder);
  66 +ignore_class(Pl_MD5);
  67 +ignore_class(Pl_PNGFilter);
  68 +ignore_class(Pl_RC4);
  69 +ignore_class(Pl_SHA2);
  70 +ignore_class(Pl_TIFFPredictor);
  71 +ignore_class(QPDFArgParser);
  72 +ignore_class(RC4);
  73 +ignore_class(SecureRandomDataProvider);
  74 +ignore_class(SparseOHArray);
  75 +
  76 +// This is public because of QPDF_DLL_CLASS on InputSource
  77 +// -------
  78 +ignore_class(InputSource::Members);
  79 +
  80 +// These are not classes
  81 +// -------
  82 +ignore_class(QUtil);
  83 +ignore_class(QTC);
  84 +
  85 +int main()
  86 +{
  87 + // Print the size of every class in the public API. This file is
  88 + // read by the check_abi script at the top of the repository as
  89 + // part of the binary compatibility checks performed before each
  90 + // release.
  91 + print_size(Buffer);
  92 + print_size(BufferInputSource);
  93 + print_size(ClosedFileInputSource);
  94 + print_size(FileInputSource);
  95 + print_size(InputSource);
  96 + print_size(JSON);
  97 + print_size(PDFVersion);
  98 + print_size(Pipeline);
  99 + print_size(Pl_Buffer);
  100 + print_size(Pl_Concatenate);
  101 + print_size(Pl_Count);
  102 + print_size(Pl_DCT);
  103 + print_size(Pl_Discard);
  104 + print_size(Pl_Flate);
  105 + print_size(Pl_QPDFTokenizer);
  106 + print_size(Pl_RunLength);
  107 + print_size(Pl_StdioFile);
  108 + print_size(QPDF);
  109 + print_size(QPDFAcroFormDocumentHelper);
  110 + print_size(QPDFAnnotationObjectHelper);
  111 + print_size(QPDFCryptoProvider);
  112 + print_size(QPDFEFStreamObjectHelper);
  113 + print_size(QPDFEmbeddedFileDocumentHelper);
  114 + print_size(QPDFExc);
  115 + print_size(QPDFFileSpecObjectHelper);
  116 + print_size(QPDFFormFieldObjectHelper);
  117 + print_size(QPDFJob);
  118 + print_size(QPDFJob::AttConfig);
  119 + print_size(QPDFJob::Config);
  120 + print_size(QPDFJob::CopyAttConfig);
  121 + print_size(QPDFJob::EncConfig);
  122 + print_size(QPDFJob::PagesConfig);
  123 + print_size(QPDFJob::UOConfig);
  124 + print_size(QPDFMatrix);
  125 + print_size(QPDFNameTreeObjectHelper);
  126 + print_size(QPDFNameTreeObjectHelper::iterator);
  127 + print_size(QPDFNumberTreeObjectHelper);
  128 + print_size(QPDFNumberTreeObjectHelper::iterator);
  129 + print_size(QPDFObjGen);
  130 + print_size(QPDFObject);
  131 + print_size(QPDFObjectHandle);
  132 + print_size(QPDFObjectHandle::ParserCallbacks);
  133 + print_size(QPDFObjectHandle::QPDFArrayItems);
  134 + print_size(QPDFObjectHandle::QPDFArrayItems::iterator);
  135 + print_size(QPDFObjectHandle::QPDFDictItems);
  136 + print_size(QPDFObjectHandle::QPDFDictItems::iterator);
  137 + print_size(QPDFObjectHandle::StreamDataProvider);
  138 + print_size(QPDFObjectHandle::TokenFilter);
  139 + print_size(QPDFOutlineDocumentHelper);
  140 + print_size(QPDFOutlineObjectHelper);
  141 + print_size(QPDFPageDocumentHelper);
  142 + print_size(QPDFPageLabelDocumentHelper);
  143 + print_size(QPDFPageObjectHelper);
  144 + print_size(QPDFStreamFilter);
  145 + print_size(QPDFSystemError);
  146 + print_size(QPDFTokenizer);
  147 + print_size(QPDFTokenizer::Token);
  148 + print_size(QPDFUsage);
  149 + print_size(QPDFWriter);
  150 + print_size(QPDFXRefEntry);
  151 + return 0;
  152 +}
... ...