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,6 +36,9 @@ CMAKE_DEPENDENT_OPTION(
36 WERROR "Treat compiler warnings as errors" OFF 36 WERROR "Treat compiler warnings as errors" OFF
37 "NOT MAINTAINER_MODE; NOT CI_MODE" ON) 37 "NOT MAINTAINER_MODE; NOT CI_MODE" ON)
38 CMAKE_DEPENDENT_OPTION( 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 GENERATE_AUTO_JOB "Automatically regenerate job files" OFF 42 GENERATE_AUTO_JOB "Automatically regenerate job files" OFF
40 "NOT MAINTAINER_MODE" ON) 43 "NOT MAINTAINER_MODE" ON)
41 CMAKE_DEPENDENT_OPTION( 44 CMAKE_DEPENDENT_OPTION(
README-maintainer
@@ -297,26 +297,17 @@ RELEASE PREPARATION @@ -297,26 +297,17 @@ RELEASE PREPARATION
297 testing, do performance testing. 297 testing, do performance testing.
298 298
299 * Test for performance and binary compatibility: 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 * Run pikepdf's test suite. Do this in a separate shell. 312 * Run pikepdf's test suite. Do this in a separate shell.
322 313
@@ -512,3 +503,39 @@ manual tests were done: @@ -512,3 +503,39 @@ manual tests were done:
512 We are using RelWithDebInfo for mingw and other non-Windows builds but 503 We are using RelWithDebInfo for mingw and other non-Windows builds but
513 Release for MSVC. There are linker warnings if MSVC is built with 504 Release for MSVC. There are linker warnings if MSVC is built with
514 RelWithDebInfo when using external-libs. 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,3 +579,13 @@ if(INSTALL_CMAKE_PACKAGE)
579 ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfig.cmake 579 ${CMAKE_CURRENT_BINARY_DIR}/qpdfConfig.cmake
580 DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf) 580 DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/qpdf)
581 endif() 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,6 +239,14 @@ QTEST_COLOR
239 Options for Working on qpdf 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 GENERATE_AUTO_JOB 250 GENERATE_AUTO_JOB
243 Some qpdf source files are automatically generated from 251 Some qpdf source files are automatically generated from
244 :file:`job.yml` and the CLI documentation. If you are adding new 252 :file:`job.yml` and the CLI documentation. If you are adding new
@@ -277,6 +285,8 @@ MAINTAINER_MODE @@ -277,6 +285,8 @@ MAINTAINER_MODE
277 285
278 - ``BUILD_DOC`` 286 - ``BUILD_DOC``
279 287
  288 + - ``CHECK_SIZES``
  289 +
280 - ``GENERATE_AUTO_JOB`` 290 - ``GENERATE_AUTO_JOB``
281 291
282 - ``WERROR`` 292 - ``WERROR``
qpdf/CMakeLists.txt
@@ -2,6 +2,7 @@ set(MAIN_CXX_PROGRAMS @@ -2,6 +2,7 @@ set(MAIN_CXX_PROGRAMS
2 qpdf 2 qpdf
3 fix-qdf 3 fix-qdf
4 pdf_from_scratch 4 pdf_from_scratch
  5 + sizes
5 test_driver 6 test_driver
6 test_large_file 7 test_large_file
7 test_parsedoffset 8 test_parsedoffset
@@ -26,6 +27,7 @@ foreach(PROG ${MAIN_C_PROGRAMS}) @@ -26,6 +27,7 @@ foreach(PROG ${MAIN_C_PROGRAMS})
26 set_property(TARGET ${PROG} PROPERTY LINKER_LANGUAGE CXX) 27 set_property(TARGET ${PROG} PROPERTY LINKER_LANGUAGE CXX)
27 endforeach() 28 endforeach()
28 target_include_directories(qpdf-ctest PRIVATE ${CMAKE_BINARY_DIR}/libqpdf) 29 target_include_directories(qpdf-ctest PRIVATE ${CMAKE_BINARY_DIR}/libqpdf)
  30 +target_include_directories(sizes PRIVATE ${JPEG_INCLUDE})
29 31
30 foreach(B qpdf test_unicode_filenames fix-qdf test_shell_glob) 32 foreach(B qpdf test_unicode_filenames fix-qdf test_shell_glob)
31 if(WINDOWS_WMAIN_COMPILE) 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 +}