diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 00000000..2da11161 --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,207 @@ +# Based on php-ast's appveyor config. +# See https://github.com/nikic/php-ast/blob/master/.appveyor.yml +# This tests against PHP 7.0+ +# Author: Tyson Andre + +version: '{branch}.{build}' + +branches: + only: + - master + +clone_folder: c:\projects\igbinary + +install: + ps: | + if (-not (Test-Path c:\build-cache)) { + mkdir c:\build-cache + } + $bname = 'php-sdk-' + $env:BIN_SDK_VER + '.zip' + if (-not (Test-Path c:\build-cache\$bname)) { + Invoke-WebRequest "https://github.com/OSTC/php-sdk-binary-tools/archive/$bname" -OutFile "c:\build-cache\$bname" + } + $dname0 = 'php-sdk-binary-tools-php-sdk-' + $env:BIN_SDK_VER + $dname1 = 'php-sdk-' + $env:BIN_SDK_VER + if (-not (Test-Path c:\build-cache\$dname1)) { + 7z x c:\build-cache\$bname -oc:\build-cache + move c:\build-cache\$dname0 c:\build-cache\$dname1 + } + +cache: + c:\build-cache -> .appveyor.yml + +environment: + BIN_SDK_VER: 2.2.0 + matrix: + - ARCH: x86 + VC: vs16 + PHP_VER: 8.3.0 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x64 + VC: vs16 + PHP_VER: 8.3.0 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x86 + VC: vs16 + PHP_VER: 8.2.13 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x64 + VC: vs16 + PHP_VER: 8.2.13 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x86 + VC: vs16 + PHP_VER: 8.1.26 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x64 + VC: vs16 + PHP_VER: 8.1.26 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x86 + VC: vs16 + PHP_VER: 8.0.30 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x64 + VC: vs16 + PHP_VER: 8.0.30 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + - ARCH: x64 + VC: vc14 + PHP_VER: 7.0.33 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - ARCH: x64 + VC: vc14 + PHP_VER: 7.0.33 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - ARCH: x86 + VC: vc14 + PHP_VER: 7.0.33 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - ARCH: x86 + VC: vc14 + PHP_VER: 7.0.33 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - ARCH: x64 + VC: vc14 + PHP_VER: 7.1.33 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - ARCH: x86 + VC: vc14 + PHP_VER: 7.1.33 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + - ARCH: x64 + VC: vc15 + PHP_VER: 7.2.34 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - ARCH: x86 + VC: vc15 + PHP_VER: 7.2.34 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - ARCH: x86 + VC: vc15 + PHP_VER: 7.3.32 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - ARCH: x64 + VC: vc15 + PHP_VER: 7.3.32 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - ARCH: x64 + VC: vc15 + PHP_VER: 7.4.30 + TS: 1 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + - ARCH: x86 + VC: vc15 + PHP_VER: 7.4.30 + TS: 0 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + +build_script: + ps: | + # Install the php extension development tools + $ts_part = '' + if ('0' -eq $env:TS) { $ts_part = '-nts' } + $bname = 'php-devel-pack-' + $env:PHP_VER + $ts_part + '-Win32-' + $env:VC.toUpper() + '-' + $env:ARCH + '.zip' + $urls = @( + "https://windows.php.net/downloads/releases/archives/$bname", + "https://windows.php.net/downloads/releases/$bname", + "https://windows.php.net/downloads/qa/$bname", + "https://windows.php.net/downloads/qa/archives/$bname" + ) + foreach ($url in $urls) { + Invoke-WebRequest $url -OutFile "c:\build-cache\$bname" + if (Test-Path c:\build-cache\$bname) { + break; + } + } + $dname0 = 'php-' + $env:PHP_VER + '-devel-' + $env:VC.toUpper() + '-' + $env:ARCH + $dname1 = 'php-' + $env:PHP_VER + $ts_part + '-devel-' + $env:VC.toUpper() + '-' + $env:ARCH + if (-not (Test-Path c:\build-cache\$dname1)) { + 7z x c:\build-cache\$bname -oc:\build-cache + move c:\build-cache\$dname0 c:\build-cache\$dname1 + } + $ts_part = '' + if ('0' -eq $env:TS) { $ts_part = '-nts' } + + # Install the php binary + $bname = 'php-' + $env:PHP_VER + $ts_part + '-Win32-' + $env:VC.toUpper() + '-' + $env:ARCH + '.zip' + $urls = @( + "https://windows.php.net/downloads/releases/archives/$bname", + "https://windows.php.net/downloads/releases/$bname", + "https://windows.php.net/downloads/qa/$bname", + "https://windows.php.net/downloads/qa/archives/$bname" + ) + foreach ($url in $urls) { + Invoke-WebRequest $url -OutFile "c:\build-cache\$bname" + if (Test-Path c:\build-cache\$bname) { + break; + } + } + $dname = 'php-' + $env:PHP_VER + $ts_part + '-' + $env:VC.toUpper() + '-' + $env:ARCH + if (-not (Test-Path c:\build-cache\$dname)) { + 7z x c:\build-cache\$bname -oc:\build-cache\$dname + } + cd c:\projects\igbinary + $env:PATH = 'c:\build-cache\' + $dname1 + ';' + $env:PATH + $env:PATH = 'c:\build-cache\' + $dname + ';' + $env:PATH + #echo "@echo off" | Out-File -Encoding "ASCII" task.bat + #echo "" | Out-File -Encoding "ASCII" -Append task.bat + echo "" | Out-File -Encoding "ASCII" task.bat + echo "call phpize 2>&1" | Out-File -Encoding "ASCII" -Append task.bat + # Overwrite the version of run-tests.php generated by phpize + # with a version supporting -j + echo "copy ci\run-tests-parallel.php run-tests.php" | Out-File -Encoding "ASCII" -Append task.bat + + $cmd = 'call configure --enable-igbinary --with-prefix=c:\build-cache\' + $dname + ' 2>&1' + echo $cmd | Out-File -Encoding "ASCII" -Append task.bat + echo "set REPORT_EXIT_STATUS=1" | Out-File -Encoding "ASCII" -Append task.bat + echo "set NO_INTERACTION=1" | Out-File -Encoding "ASCII" -Append task.bat + # Configure this to use two worker processes (same as appveyor core count?) to run tests + echo "set TEST_PHP_ARGS=--show-diff -j2" | Out-File -Encoding "ASCII" -Append task.bat + $cmd = 'set TEST_PHP_EXECUTABLE=c:\build-cache\' + $dname + '\php.exe' + echo $cmd | Out-File -Encoding "ASCII" -Append task.bat + echo "nmake /nologo 2>&1" | Out-File -Encoding "ASCII" -Append task.bat + echo "nmake test /nologo 2>&1" | Out-File -Encoding "ASCII" -Append task.bat + echo "exit %errorlevel%" | Out-File -Encoding "ASCII" -Append task.bat + $here = (Get-Item -Path "." -Verbose).FullName + $runner = 'c:\build-cache\php-sdk-' + $env:BIN_SDK_VER + '\phpsdk' + '-' + $env:VC + '-' + $env:ARCH + '.bat' + $task = $here + '\task.bat' + & $runner -t $task diff --git a/.azure/job.yml b/.azure/job.yml new file mode 100644 index 00000000..a2efe7ef --- /dev/null +++ b/.azure/job.yml @@ -0,0 +1,51 @@ +parameters: + configurationName: '' + phpVersion: '' + +# A reference for testing PECL extensions using azure can be seen at https://github.com/microsoft/msphpsql/blob/master/azure-pipelines.yml +# (that extension also runs unit tests on Windows/Macs) +jobs: + - job: ${{ parameters.configurationName }} + # NOTE: This currently does not use containers. Doing so may be useful for testing on php zts/32-bit. + # Containers need to provide sudo apt-get in order to work, the php:x-cli images don't. + # Containers are slower to start up than the default vm images + pool: + vmImage: ${{ parameters.vmImage }} + steps: + # E.g. ondrej/php sometimes updates the latest version + - script: | + sudo apt-get install -y valgrind + displayName: 'Install valgrind' + - script: | + VER=${{ parameters.phpVersion }} + # Refresh the cache for the PPA repository, it can be out of date if it's updated recently + if [[ ! -f /usr/bin/phpize$VER ]]; then + sudo add-apt-repository -u ppa:ondrej/php + fi + # Silently try to install the php version if it's available. + sudo apt-get install -y php$VER-dev + sudo update-alternatives --set php /usr/bin/php$VER + # Fail the build early if the php version isn't installed on this image + sudo update-alternatives --set phpize /usr/bin/phpize$VER || exit 1 + sudo update-alternatives --set pecl /usr/bin/pecl$VER + sudo update-alternatives --set phar /usr/bin/phar$VER + sudo update-alternatives --set phpdbg /usr/bin/phpdbg$VER + sudo update-alternatives --set php-cgi /usr/bin/php-cgi$VER + sudo update-alternatives --set phar.phar /usr/bin/phar.phar$VER + sudo update-alternatives --set php-config /usr/bin/php-config$VER + php -version + displayName: Use PHP version ${{ parameters.phpVersion }} + - script: | + phpize + # Replace run-tests.php with a patched version (from php 7.4's) that supports parallel builds. + cp ci/run-tests-parallel.php run-tests.php + ./configure --enable-igbinary + make + displayName: 'Build igbinary' + - script: | + REPORT_EXIT_STATUS=1 NO_INTERACTION=1 TEST_PHP_ARGS="--show-diff -j$(nproc)" make test + # For most travis builds, re-run `make test` with valgrind. + if [ "x$SKIP_VALGRIND" = "x" ]; then export TEST_PHP_ARGS="-m --show-diff -j2"; REPORT_EXIT_STATUS=1 NO_INTERACTION=1 make test; fi + # Print a summary of any memory leaks + shopt -s nullglob; head -n 999 tests/*.mem < /dev/null + displayName: 'Test igbinary' diff --git a/.clang-format.example b/.clang-format.example new file mode 100644 index 00000000..6d951734 --- /dev/null +++ b/.clang-format.example @@ -0,0 +1,12 @@ +# Note: This has a lot of false positives with the macros used for developing PHP modules, +# and can't be used automatically. +BasedOnStyle: LLVM +UseTab: Always +IndentWidth: 8 +TabWidth: 8 +ColumnLimit: 0 +SpacesBeforeTrailingComments: 2 + +# I tried ForEachMacros, +# but clang-format 3.8.0 would insert a space before the '(', making the C macro not work. +# ForEachMacros: ['ZEND_HASH_FOREACH_KEY_PTR', 'ZEND_HASH_FOREACH_KEY_VAL_IND', 'ZEND_HASH_FOREACH_PTR', 'ZEND_HASH_FOREACH_STR_KEY', 'ZEND_HASH_FOREACH_STR_KEY_PTR', 'ZEND_HASH_FOREACH_STR_KEY_VAL', 'ZEND_HASH_FOREACH_VAL_IND'] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..50c3a44a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,59 @@ +.git +**/*.swp +**/*.lo +**/*.la +**/*.tgz +**/*.dep +**/.deps +**/.libs +**/Dockerfile* +**/Makefile +**/Makefile.fragments +**/Makefile.global +**/Makefile.objects +**/acinclude.m4 +**/aclocal.m4 +**/autom4te.cache +**/build +**/config.cache +**/config.guess +**/config.h +**/config.h.in +**/config.log +**/config.nice +**/config.status +**/config.sub +**/configure +**/configure.in +**/configure.ac +**/conftest +**/conftest.c +**/include +**/install-sh +**/libtool +**/ltmain.sh +**/missing +**/mkinstalldirs +**/modules +**/scan_makefile_in.awk +**/*.dsw +**/*.plg +**/*.opt +**/*.ncb +**/Release +**/Release_inline +**/Debug +**/Release_TS +**/Release_TSDbg +**/Release_TS_inline +**/Debug_TS +**/run-tests.php +**/cscope.out +tests/*.out +tests/*.php +tests/*.mem +tests/*.diff +tests/*.log +tests/*.exp +tests/*.sh +**/tmp-php.ini diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..c2af055f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ +; 4 space indentation (no size specified) +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +indent_size = 4 +[*.{phpt,php}] +indent_style = space +[*.xml] +indent_size = 1 +indent_style = space +[*.yml] +indent_size = 2 +indent_style = space +[.travis.yml] +indent_size = 8 +indent_style = space +[*.w32] +charset = utf-8 +end_of_line = crlf +indent_style = space +indent_size = 2 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..04f2cfb8 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,92 @@ +# Runs igbinary's tests and verifies that the package can be built. + +name: CI + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ master ] + pull_request: + branches: [ master ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-environment-variables-in-a-matrix + strategy: + fail-fast: false + matrix: + include: + # NOTE: If this is not quoted, the yaml parser will convert 7.0 to the number 7, + # and the docker image `php:7` is the latest minor version of php 7.x (7.4). + - PHP_VERSION: '7.0' + PHP_VERSION_FULL: 7.0.33 + DOCKER_ARCHITECTURE: i386 + - PHP_VERSION: '7.1' + PHP_VERSION_FULL: 7.1.33 + - PHP_VERSION: '7.2' + PHP_VERSION_FULL: 7.2.34 + - PHP_VERSION: '7.3' + PHP_VERSION_FULL: 7.3.33 + - PHP_VERSION: '7.4' + PHP_VERSION_FULL: 7.4.33 + - PHP_VERSION: '8.0' + PHP_VERSION_FULL: 8.0.30 + - PHP_VERSION: '8.0' + PHP_VERSION_FULL: 8.0.30 + DOCKER_ARCHITECTURE: i386 + - PHP_VERSION: '8.1' + PHP_VERSION_FULL: 8.1.34 + - PHP_VERSION: '8.2' + PHP_VERSION_FULL: 8.2.30 + - PHP_VERSION: '8.2' + PHP_VERSION_FULL: 8.2.30 + DOCKER_ARCHITECTURE: i386 + - PHP_VERSION: '8.3' + PHP_VERSION_FULL: 8.3.30 + - PHP_VERSION: '8.4' + PHP_VERSION_FULL: 8.4.20 + - PHP_VERSION: '8.5' + PHP_VERSION_FULL: 8.5.5 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v6 + + # Runs a single command using the runners shell + - name: Build and test in docker + run: bash ci/test_dockerized.sh ${{ matrix.PHP_VERSION }} ${{ matrix.PHP_VERSION_FULL }} ${{ matrix.DOCKER_ARCHITECTURE }} + + - name: Build and test in docker again with valgrind + run: bash ci/test_dockerized_valgrind.sh ${{ matrix.PHP_VERSION }} ${{ matrix.PHP_VERSION_FULL }} ${{ matrix.DOCKER_ARCHITECTURE }} + + # Build and test on Windows + windows: + strategy: + matrix: + php-version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"] + arch: [x64, x86] + ts: [nts, ts] + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v6 + # The builder handles building, testing and artifact packaging/upload automatically + - name: Build and test the extension + uses: php/php-windows-builder/extension@v1 + with: + php-version: ${{ matrix.php-version }} + arch: ${{ matrix.arch }} + ts: ${{ matrix.ts }} + args: --enable-igbinary --enable-debug-pack + run-tests: true + test-runner-args: "--show-diff tests" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..3c6566e7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,45 @@ +# This workflow is triggered if a new release or prerelease is published on GitHub (no drafts). +# It builds and publishes zip-files with prebuilt Windows DLLs as release artifacts. These +# artifacts are required by PIE (PHP Installer for Extensions). See https://github.com/php/pie +# and https://github.com/php/php-windows-builder for more information. +name: Release + +on: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release + release: + types: [published] + +jobs: + # Build and test on Windows + windows: + strategy: + matrix: + php-version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5"] + arch: [x64, x86] + ts: [nts, ts] + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v6 + # The builder handles building, testing and artifact packaging/upload automatically + - name: Build and test the extension + uses: php/php-windows-builder/extension@v1 + with: + php-version: ${{ matrix.php-version }} + arch: ${{ matrix.arch }} + ts: ${{ matrix.ts }} + args: --enable-igbinary --enable-debug-pack + run-tests: true + test-runner-args: "--show-diff tests" + + # Upload zip-files with prebuilt Windows DLLs to the release (required for PIE) + release_windows_pie: + if: ${{ github.event_name == 'release' }} + needs: windows + runs-on: ubuntu-latest + steps: + - name: Upload artifacts to the release + uses: php/php-windows-builder/release@v1 + with: + release: ${{ github.event.release.tag_name }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 29feb748..ac8d3e63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.swp *.lo *.la +*.tgz +*.dep .deps .libs Makefile @@ -21,6 +23,7 @@ config.status config.sub configure configure.in +configure.ac conftest conftest.c include diff --git a/.travis.yml b/.travis.yml index f8695972..dbef302b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,27 +1,114 @@ language: php -php: - - 5.6 - - 5.5 - - 5.4 - - 5.3 - - 5.2 - -env: - - CC=clang CFLAGS="" - - CC=clang CFLAGS="-g -O0" - - CC=gcc-4.6 CFLAGS="" - - CC=gcc-4.6 CFLAGS="-g -O0 -fstack-protector -fstack-protector-all" - - CC=gcc-4.6 CFLAGS="-g" VALGRIND=1 + +# Available php versions can be seen at https://github.com/php-build/php-build/tree/master/share/php-build/definitions +# NOTE: Older OS versions have older valgrind versions and report false positives for zend_string_equals in php 7.3+ +# due to the use of inline assembly in php-src. +matrix: + include: + - php: nightly + env: CC=gcc CFLAGS="" SKIP_VALGRIND=1 + dist: focal + - php: 8.1.0RC6 + env: CC=gcc CFLAGS="" SKIP_VALGRIND=1 + dist: focal + - php: 8.1.0RC6 + env: CC=gcc CFLAGS="-g -O0 -fstack-protector -fstack-protector-all" SKIP_VALGRIND=1 + dist: focal + - php: 8.0 + env: CC=gcc CFLAGS="" SKIP_VALGRIND=1 + dist: focal + - php: 8.0 + env: CC=gcc CFLAGS="-g -O0 -fstack-protector -fstack-protector-all" SKIP_VALGRIND=1 + dist: focal + # valgrind reports "WARNING: unhandled x86-linux syscall: 403" + - php: 8.0 + env: CC=gcc CXX=g++ USE_32BIT=1 PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-zts --enable-debug --enable-cgi --enable-session --enable-json' SKIP_VALGRIND=1 + dist: focal + + - php: 7.4 + env: CC=gcc CFLAGS="-g -O0 -fstack-protector -fstack-protector-all" SKIP_VALGRIND=1 + dist: focal + - php: 7.4 + env: CC=gcc CXX=g++ USE_32BIT=1 PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json' SKIP_VALGRIND=1 + dist: focal + + - php: 7.4 + env: CC=gcc CXX=g++ PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json' + dist: focal + + - php: 7.3 + env: CC=gcc CXX=g++ USE_32BIT=1 PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json' + dist: xenial + + - php: 7.2 + env: CC=clang CFLAGS="-O3" SKIP_VALGRIND=1 + dist: xenial + - php: 7.2 + env: CC=clang CFLAGS="" + dist: xenial + - php: 7.2 + env: CC=gcc CFLAGS="" + dist: xenial + - php: 7.2 + env: CC=gcc CFLAGS="-g -O0 -fstack-protector -fstack-protector-all" SKIP_VALGRIND=1 + dist: xenial + - php: 7.2 + env: CC=gcc CXX=g++ USE_32BIT=1 PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json' + dist: xenial + + - php: 7.1 + env: CC=clang CFLAGS="-g -O0" + dist: xenial + - php: 7.1 + env: CC=gcc CFLAGS="" + dist: xenial + - php: 7.1 + env: CC=gcc CXX=g++ USE_32BIT=1 PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json' + dist: xenial + + - php: 7.0 + env: CC=clang CFLAGS="-O3" SKIP_VALGRIND=1 + dist: xenial + - php: 7.0 + env: CC=gcc CFLAGS="" + dist: xenial + - php: 7.0 + env: CC=gcc CFLAGS="-g -O0 -fstack-protector -fstack-protector-all" SKIP_VALGRIND=1 + dist: xenial + - php: 7.0 + env: CC=gcc CXX=g++ USE_32BIT=1 PHP_CUSTOM=maintainer-zts PHP_CONFIGURE_ARGS='--disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json' + dist: xenial + +cache: + directories: + - $HOME/travis_cache install: - - if [ "$VALGRIND" -eq 1 ]; then sudo apt-get install valgrind; export TEST_PHP_ARGS="-m"; fi - - sudo apt-get install $CC + - sudo apt-get update -qq + - sudo apt-get install -qq $CC + # For 32-bit installations: Install multilib so it can be compiled, as well libc6-dbg:i386 so that valgrind will work. + - if [ "x$USE_32BIT" != "x" ]; then sudo apt-get install -y $CXX g++-multilib libc6-dev-i386 libc6-dbg:i386; export CC="$PWD/ci/gcc-32.sh"; export CXX="$PWD/ci/g++-32.sh"; $CC --version; if [ ! -d /usr/include/asm ]; then ln -nsf /usr/include/asm-generic /usr/include/asm; fi; fi + - ls /usr/include -la; find /usr/include -iname errno.h + # If making a 32-bit build: Automatically determine and download the latest patch of each minor version we support. + - if [ "x$SKIP_VALGRIND" = "x" ]; then sudo apt-get install -qq valgrind; valgrind --version; fi + - if [ "x$PHP_CUSTOM" != "x" ]; then export PHP_CUSTOM_VERSION=$(./ci/get_global_php_version.sh); echo "Version is $PHP_CUSTOM_VERSION"; ./ci/install_php_custom.sh || exit 1; export PATH="$(./ci/generate_php_install_dir.sh)/bin:$PATH"; export PHPRC=$PWD/ci/; else ./ci/wipe_travis_cache.sh; fi - $CC --version +before_script: + - $CC --version && ci/print_php_int_max.php + script: - phpize + # Replace run-tests.php with a patched version (from php 7.4's) that supports parallel builds. + - cp ci/run-tests-parallel.php run-tests.php - ./configure --enable-igbinary - # Fix not failing makes in php 5.2 and 5.3 - - perl -i -pe 's/\-\@if/\@if/' Makefile - make - - REPORT_EXIT_STATUS=1 NO_INTERACTION=1 make test + - REPORT_EXIT_STATUS=1 NO_INTERACTION=1 TEST_PHP_ARGS="--show-diff -j2" make test + # For most travis builds, re-run `make test` with valgrind. + - if [ "x$SKIP_VALGRIND" = "x" ]; then export TEST_PHP_ARGS="-m --show-diff -j2"; REPORT_EXIT_STATUS=1 NO_INTERACTION=1 make test; fi + # Print a summary of any memory leaks + - shopt -s nullglob; head -n 999 tests/*.mem < /dev/null + +branches: + only: + - master diff --git a/CODE_COVERAGE.md b/CODE_COVERAGE.md new file mode 100644 index 00000000..eec087e6 --- /dev/null +++ b/CODE_COVERAGE.md @@ -0,0 +1,9 @@ +Generating Code Coverage +======================== + +1. Clone php-src (e.g. for the PHP-7.4 branch) +2. Copy the igbinary directory into php-src/ext/igbinary +3. Run `cd /path/to/php-src; ./buildconf --force; ./configure --disable-all --enable-maintainer-zts --enable-debug --enable-cgi --enable-session --enable-json --enable-igbinary --enable-gcov; make -j8` +4. Run the tests: `make test TESTS=ext/igbinary/tests` +5. Generate coverage from the tests: `make lcov TESTS=ext/igbinary/tests` +6. View the code coverage in `lcov_html/index.html` for ext/igbinary in your browser. diff --git a/CREDITS b/CREDITS index 19e9118f..8b6df6d2 100644 --- a/CREDITS +++ b/CREDITS @@ -8,6 +8,7 @@ Contributors * Kari Lavikka (docs) * Pierre Joye (Windows support) * Mikko Koppanen (PECL and RPM spec files) +* Tyson Andre (fixes, tests, docs, php 7.x support) Other * Original hash functions - Bob Jenkins diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index 89e0a475..00000000 --- a/ChangeLog +++ /dev/null @@ -1,10 +0,0 @@ -2008-06-29 Oleg Grenrus - * config.m4: fixed CFLAGS - * igbinary.c: do not init object & string hashes if serializing scalars. - unserialize_callback_func support. - igbinary_serialize and igbinary_unserialize for the external use. - * intbinary.h: header for external use - * tests/022.phpt: unserialize_callback_func test - * tests/023.phpt: resource serializes to null test - * tests/002.phpt tests/003.phpt tests/004.phpt tests/005.phpt tests/006.phpt: == -> === - * tests/015b.phpt: pointed out http://bugs.net/bug.php?id=45406 diff --git a/EXPERIMENTAL b/EXPERIMENTAL deleted file mode 100644 index e69de29b..00000000 diff --git a/NEWS b/NEWS index 14df4169..f140825e 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,300 @@ -1.2.2 xxxx-xx-xx +3.2.17RC1 2025-11-26 +======= +* Support PHP 8.5 + +3.2.15 2023-12-02 +======= +* Properly fix crash in igbinary_unserialize_object_enum_case with opcache protected memory and non-constant value. (#380) + +3.2.14 2023-02-06 +======= +* Fix build error in PHP 8.3-dev +* Fix test expectation errors in php 8.3-dev due to change to php's TypeErrors. + +3.2.13 2023-02-02 +======= + +* Speed up unserialization of typed properties by reducing hash table collisions when looking up property reference info. + +3.2.12 2022-11-07 +======= + +* Fix symbol error seen in php 8.2.0 loading zend_class_unserialize_deny, due to failing to load a header defining a macro. + +3.2.11 2022-11-06 +======= +* Fix a bug that could prevent objects/arrays with reference cycles from being properly garbage collected. +* Fix bugs in unserializing PHP references to values found in php 7.4 typed properties (#363) + +3.2.10 2022-11-06 +======= + +* Add a macro that callers can use to check if igbinary will accept the header for data being unserialized. +* Fix bug preventing the unserialization of data containing representations of strings larger than 4GB. + +3.2.9 2022-10-17 +======= + +* Fix invalid release artifact name in job to build dlls for https://github.com/igbinary/igbinary + +3.2.8 2022-10-17 +======= + +* Fix invalid release artifact name in job to build dlls for https://github.com/igbinary/igbinary + +3.2.8 2022-10-16 +======= + +* Reduce excessive inlining to reduce shared library size. +* Miscellaneous optimizations. +* Set up CI job to build dlls on https://github.com/igbinary/igbinary - at the moment, the infrastructure used by the Windows for php team has been broken for months. + +3.2.7 2022-01-12 +======= + +* Update test expectations for php 8.2.0-dev. Add `#[AllowDynamicProperties]` Attribute to some tests to avoid notices. +* In php 8.1+, make igbinary_unserialize check to see if an equivalent interned string already exists when unserializing object property names, array keys, and class names + and use that instead of creating a brand new string. + (This deliberately doesn't create a new interned string if one doesn't already exist.) + (Before this change, igbinary would deduplicate strings when serializing, but would not check if strings were interned by PHP itself when unserializing) +* Avoid debug build assertion failure for `HT_ASSERT_RC1` the same way as PHP's unserialize - this is a case where ostensibly there are no other references to the array being unserialized. + +3.2.6 2021-08-11 +======= + +* Fix igbinary version found in Reflection. + +3.2.5 2021-08-07 +======= + +* Fix change in behavior introduced in 3.2.2RC1 when unserializing arrays - the internal array pointer (for `next()`, `key()`, etc) pointed past the end of the array in php 7.0-7.2. + +3.2.4 2021-07-24 +======= + +* Forbid serializing classes that deny serialization/unserialization (anonymous classes, CURLFile, etc.) even when subclasses implement '__serialize' and '__unserialize' + +3.2.3 2021-06-09 +======= + +* Fix build for php 8.1 after changes to enum internals. +* Update tests to suppress deprecations in php 8.1 and support run-tests.php changes in php 8.1 +* Don't emit a notice when unserialize_callback_func causes igbinary_unserialize to throw https://bugs.php.net/bug.php?id=81118 + +3.2.2 2021-04-18 +======= + +* Eliminate impossible/redundant checks. +* Add a new type code for serialization and unserialization of PHP strings that are larger than 4GB. +* Add additional checks for overflow when serializing extremely large data structures. + (e.g. serializing more than 2**32 strings or 2**32 objects/references/arrays) +* Support serializing and unserializing php 8.1 enums (can only be unserialized in php 8.1+) + +3.2.2RC1 2021-01-11 +======= + +* Update php version check to allow igbinary to be statically built in PHP 8.0+ +* Fix bug in out of memory error handling in __sleep, slightly speed up serializing with __sleep. +* Continue serializing remaining properties if a missing property name is returned from __sleep. +* Speed up serializing by optimizing for the case where there is no memory manager override. + When there is a memory manager override, only use that for allocating the string to return. + (benchmarks/serialize-scalar-int.b.php showed a speedup from 0.22 to 0.18 seconds for repeated serialization of a single scalar, + and from 0.186 to 0.180 seconds for benchmarks/serialize-stringarray.b.php for an array of strings) +* Speed up unserializing arrays in php 7.2-8.0 by adding optimized code for finding the hash bucket of + a string/integer key of an array, or creating a placeholder if it does not already exist. + +3.2.1 2020-12-27 +======= +* Fix crash when unserializing if __serialize was defined but __unserialize was undefined in php 8.0+ (due to typo). + +3.2.0 2020-12-26 +======= + +* Use PHP's shared empty array instance when unserializing empty arrays in php 7.3+. + (helps slightly with memory usage when repeatedly unserializing, + when removing elements from arrays before unserializing them, + or when serializing values including an empty array that was unserialized) +* Emit a deprecation notice when serializing resources. + PHP itself is converting many resources to objects that throw an Error on serialization attempts. + Continue to represent resources as null in the serialized data. +* Fix memory management bug when unserializing invalid data (duplicate properties in objects (e.g. from `__sleep`) or duplicate fields in arrays (impossible for valid data)). +* Speed up calls to `__serialize`/`__unserialize` in php 8.0+. +* Fix error messages for unserialize_callback_func: make messages properly refer to the autoload function. +* Optimize unserializing alternative names for private/protected properties that were previously public. + +3.1.6 2020-10-08 +======= + +* Fix build failure with older C standard (e.g. building on CentOS 6). +* Otherwise, identical to 3.1.6RC1. + +3.1.6RC1 2020-10-07 +======= + +* Fix igbinary_serialize incorrectly deduplicating arrays/objects/references when they were garbage collected/freed during serialization. + +3.1.5 2020-09-02 +======= + +* Update unit test expectation to match behavior in php 8 due to changes in php's handling of cyclic references in arrays. +* Support API changes in php 8.0.0beta3. + +3.1.4 2020-07-05 +======= + +* Fix unserialization of PHP references to internal/user-defined classes using PHP 7.4's `__unserialize` (e.g. `ArrayObject`) + +3.1.3 2020-07-04 +======= + +* Properly serialize reference groups of size 1 (these can be created by array_walk_recursive and other functions). + Note that this does not fix the general case where values not being serialized are in the same reference group as a value being serialized. +* PHP 8.0 compatibility fixes. + +3.1.2 2020-01-16 +======= + +* Speed up object, array, reference, and string serialization. +* Speed up unserializing integers between 0 and 65535 (as values and array keys). +* Speed up unserializing objects with declared properties. + +3.1.1 2020-01-16 +======= + +* Fix bug causing incorrect serialization for 1 in 2**32 strings on 64-bit php installations when string hashes collide. + (https://github.com/igbinary/igbinary/issues/260) + +3.1.1a1 2020-01-11 +======= + +* Throw when an uninitialized php 7.4 typed property is included in the result of __sleep(), + instead of emitting a notice and attempting to represent the unset/uninitialized value as null (#258). + See https://bugs.php.net/bug.php?id=79002 + + Uninitialized properties without types (from __sleep) continue to cause igbinary to emit notices and are represented as null. + +3.1.0 2019-12-27 +======= + +* Same as 3.1.0b4. + +3.1.0b4 2019-12-20 +======= + +* Don't call __destruct for objects where deferred __unserialize calls were not started (e.g. due to Serializable::unserialize throwing). + +3.1.0b3 2019-12-10 +======= + +* Skip over object properties that are uninitialized or unset when serializing, instead of serializing them as null. + This is done to avoid Errors when unserializing their values for php 7.4 typed properties. + +3.1.0b2 2019-12-09 +======= + +* Fix crashes related to unserializing instances of classes with php 7.4 typed properties. + +3.1.0b1 2019-12-08 +======= + +* Support php 7.4's __serialize/__unserialize the same way serialize()/unserialize() does. + This deliberately only supports __serialize/__unserialize in php 7.4, to making switching to/from serialize()/unserialize() as straightforward as possible. + +3.0.1 2019-03-20 +======= + +* Fix version check when statically building igbinary inside of the php-src folder. + +3.0.0 2019-02-17 +======= + +* Identical to 3.0.0a2 + +3.0.0a2 2019-02-13 +======= + +* Don't use empty string for serializing empty $_SESSION array, it breaks some save handlers. (Issue #231) + Continue treating the empty string as the empty $_SESSION array when unserializing. + +3.0.0a1 2019-02-08 +======= + +* This release line drops support for PHP5. +* Drop support for APC (APC was only available for PHP5 - It is the predecessor of APCu) +* Emit a warning and return null if igbinary_unserialize() is passed more data to unserialize than expected. +* Fix compilation against PHP 7.4-dev. Igbinary does NOT yet properly serialize/unserialize all classes with PHP 7.4-dev's typed properties. +* The serialization format is exactly the same as igbinary 2.x + +2.0.8 2018-10-20 +======= + +* Be more aggressive about deduplication when generating serialization of arrays in php 7.0+. +* Define HAVE_IGBINARY on Unix/Linux. (previously defined only on Windows) +* Update formatting/wording of documentation. + +2.0.7 2018-06-27 +======= + +* Fix compiler warnings about format strings, for errors that should not occur during normal igbinary usage. + +2.0.6 2018-05-12 +======= + +* Same as 2.0.6RC1 + +2.0.6RC1 2018-04-01 +======= + +* Fix a bug in Windows debug builds. +* Emit more specific warnings when __sleep() returns a declared property that was unset. +* Fix harmless compiler warnings during builds. +* Fix a build error on PHP7.3-dev. + +2.0.5 2017-11-04 +======== + +* Same as 2.0.5RC1 (no bugs were reported in that release candidate) + +2.0.5RC1 2017-10-15 +======== +* Improve performance when unserializing objects/arrays and serializing objects/arrays/strings in php 5/7. +* Update unserialization of integer object keys for php 7.2: Make those keys accessible when unserializing. +* Properly pick up presence of gcc for default compiler flags (`cc --version` doesn't contain gcc). + Add -O2 to default gcc compiler flags. +* Use empty string for serializing empty $_SESSION array, similar to "session.serialize_handler=php". + Older igbinary releases already unserialize the empty string to the empty array. + +2.0.4 2017-04-14 +======== +* Fixes bug #129: Should not call __wakeup() on data which was created by Serializable::unserialize() + +2.0.3 2017-03-31 +======== +* Fixes bug #126: Fatal error: "igbinary_serialize_zval: zval has unknown type 0" (IS_UNDEF) + Make this a warning instead of a fatal error (and serialize as null instead), since IS_UNDEF is a known type. + Later releases will fix the root cause of the warning, and consistently omit array/object/other entries for IS_UNDEF. + +2.0.2 2017-02-27 +======== +* Compatible with PHP 5.2 - 7.1 +* Fixes crash in Memcached->setMulti (in php 7.0+) when the first level of array elements have references as values. + Other extensions using igbinary shouldn't be affected. + +2.0.1 2016-11-19 +======== +* Compatible with PHP 5.2 - 7.0 +* Fixes bug in session decoder not calling __wakeup() in php 7.0+ +* (Enhancement) Reuses identical strings when unserializing objects and arrays in php 7.0+ + +2.0.0 2016-10-30 ======== -* TBD +* Compatible with PHP 5.2 - 7.0 (Adds PHP 7 support) +* Serialization format is unchanged +* Performance improvements for serialization and unserialization in PHP 5.2 - 7.0 +* (PHP 7) Don't call __destruct if __wakeup threw an exception (or __wakeup wasn't called yet) +* Ports integration with other extensions to PHP 7.0 (session serialization, Memcached, Redis, APCu, etc.) +* Fixes Windows PECL builds for PHP 5.6+ +* Reword warnings for invalid header bytes of serialized data (in igbinary_unserialize). 1.2.1 2014-08-29 ======== diff --git a/README.md b/README.md index 7d7a3a35..97e77a7d 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,59 @@ igbinary ======== -[![Build Status](https://travis-ci.org/igbinary/igbinary.svg?branch=master)](https://travis-ci.org/igbinary/igbinary) - -Igbinary is a drop in replacement for the standard php serializer. Instead of -time and space consuming textual representation, igbinary stores php data -structures in compact binary form. Savings are significant when using -memcached or similar memory based storages for serialized data. About 50% -reduction in storage requirement can be expected. Specific number depends on -your data. - -Unserialization performance is at least on par with the standard PHP serializer. -Serialization performance depends on the "compact_strings" option which enables -duplicate string tracking. String are inserted to a hash table which adds some -overhead. In usual scenarios this does not have much significance since usage -pattern is "serialize rarely, unserialize often". With "compact_strings" -option igbinary is usually a bit slower than the standard serializer. Without -it, a bit faster. +[![Build Status](https://github.com/igbinary/igbinary/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/igbinary/igbinary/actions/workflows/main.yml?query=branch%3Amaster) +[![Build Status (Windows)](https://ci.appveyor.com/api/projects/status/suhkkumj1yh9dgan?svg=true)](https://ci.appveyor.com/project/TysonAndre/igbinary-bemsx) + +Igbinary is a drop in replacement for the standard php serializer. +Instead of the time and space consuming textual representation used by PHP's `serialize`, +igbinary stores php data structures in a compact binary form. +Memory savings are significant when using memcached, APCu, or similar memory based storages for serialized data. +The typical reduction in storage requirements are around 50%. +The exact percentage depends on your data. + +Unserialization performance is [at least on par with the standard PHP serializer, and is much faster for repetitive data](benchmark/comparisons.php). +Serialization performance depends on the `igbinary.compact_strings` option which enables +duplicate string tracking. +String are inserted to a hash table, which adds some overhead when serializing. +In usual scenarios this does not have much of an impact, +because the typical usage pattern is "serialize rarely, unserialize often". +With the `compact_strings` option enabled, +igbinary is usually a bit slower than the standard serializer. +Without it, igbinary is a bit faster. Features -------- -- Supports same data types as the standard PHP serializer: null, bool, int, - float, string, array and objects. +- Support for the same data types as the standard PHP serializer: null, bool, int, + float, string, array and object. - `__autoload` & `unserialize_callback_func` - `__sleep` & `__wakeup` +- `__serialize` & `__unserialize` (only used in php 7.4+) - Serializable -interface -- Data portability between platforms (32/64bit, endianess) +- Data portability between platforms (32/64bit, endianness) - Tested on Linux amd64, Linux ARM, Mac OSX x86, HP-UX PA-RISC and NetBSD sparc64 -- Hooks up to APC opcode cache as a serialization handler (APC 3.1.7+) -- Compatible with PHP 5.2 – 5.6 +- Hooks up to the APCu in-memory key-value store as a serialization handler. +- Compatible with 7.0 – 8.0 (The older igbinary [2.x releases](https://github.com/igbinary/igbinary/tree/v2) support 5.2 – 5.6, 7.0 – 7.3) Implementation details ---------------------- -Storing complex PHP data structures like arrays of associative arrays -with the standard PHP serializer is not very space efficient. The main -reasons in order of significance are (at least in our applications): +Storing complex PHP data structures such as arrays of associative arrays +with the standard PHP serializer is not very space efficient. +The main reasons of this inefficiency are listed below, in order of significance (at least in our applications): -1. Array keys are repeated redundantly. +1. Array keys, property names, and class names are repeated redundantly. 2. Numerical values are plain text. 3. Human readability adds some overhead. -Igbinary uses two specific strategies to minimize the size of the serialized +Igbinary uses two strategies to minimize the size of the serialized output. -1. Repetitive strings are stored only once. Collections of objects benefit - significantly from this. See "compact_strings" option. +1. Repeated strings are stored only once (this also includes class and property names). + Collections of objects benefit significantly from this. + See the `igbinary.compact_strings` option. -2. Numerical values are stored in the smallest primitive data type - available: +2. Integer values are stored in the smallest primitive data type available: *123* = `int8_t`, *1234* = `int16_t`, *123456* = `int32_t` @@ -62,69 +66,86 @@ How to use Add the following lines to your php.ini: - ; Load igbinary extension - extension=igbinary.so +```ini +; Load igbinary extension +extension=igbinary.so - ; Use igbinary as session serializer - session.serialize_handler=igbinary +; Use igbinary as session serializer +session.serialize_handler=igbinary - ; Enable or disable compacting of duplicate strings - ; The default is On. - igbinary.compact_strings=On +; Enable or disable compacting of duplicate strings +; The default is On. +igbinary.compact_strings=On - ; Use igbinary as serializer in APC cache (3.1.7 or later) - ;apc.serializer=igbinary +; If uncommented, use igbinary as the serializer of APCu +; (APCu 5.1.10 or newer is strongly recommended) +;apc.serializer=igbinary +``` -.. and in your php code replace serialize and unserialize function calls -with `igbinary_serialize` and `igbinary_unserialize`. +Then, in your php code, replace `serialize` and `unserialize` function calls +with [`igbinary_serialize` and `igbinary_unserialize`](./igbinary.php). Installing ---------- -Note: -Sometimes phpize must be substituted with phpize5. In such cases the following -option must be given to configure script: "--with-php-config=.../php-config5" +### Linux + +If PHP was installed through your package manager, +the package manager may also contain prebuilt packages for `igbinary` +(with a package name similar to php-igbinary). + +- The packages from some package managers and OS versions may be out of date and have known bugs. The latest release of igbinary is [![The Latest Stable Version](https://img.shields.io/github/v/release/igbinary/igbinary.svg)](https://github.com/igbinary/igbinary/releases) + +Igbinary may also be installed with the command `pecl install igbinary` (You will need to enable igbinary in php.ini) + +Alternately, you may wish to [build from source](#building-from-source) + +### MacOS + +`pecl install igbinary` is the recommended installation method (You will need to enable igbinary in php.ini) + +Alternately, you may wish to [build from source](#building-from-source). + +### Installing on Windows + +Prebuilt DLLs can be [downloaded from PECL](https://pecl.php.net/package/igbinary). + +If you are a contributor to/packager of igbinary, or need to build from source, see [WINDOWS.md](./WINDOWS.md) + +### Building from source 1. `phpize` -2. `./configure: +2. `./configure` + - With GCC: `./configure CFLAGS="-O2 -g" --enable-igbinary` - With ICC (Intel C Compiler) `./configure CFLAGS=" -no-prec-div -O3 -xO -unroll2 -g" CC=icc --enable-igbinary` - With clang: `./configure CC=clang CFLAGS="-O0 -g" --enable-igbinary` 3. `make` 4. `make test` 5. `make install` -6. igbinary.so is installed to the default extension directory - -### To run APCu test - -``` -# go to modules directory -cd modules - -# ... and create symlink to apcu extension -# it will be loaded during test suite -/opt/lib/php/extensions/no-debug-non-zts-20121212/apcu.so -``` - -Similar approach should work for APC. +6. `igbinary.so` is installed to the default extension directory Bugs & Contributions -------------------- Mailing list for bug reports and other development discussion can be found -at http://groups.google.com/group/igbinary +at http://groups.google.com/group/igbinary (no longer used) -Fill bug reports at +File bug reports at https://github.com/igbinary/igbinary/issues -The preferred ways for contributions are pull requests and email patches -(in git format). Feel free to fork at http://github.com/igbinary/igbinary +The preferred way to contribute is with pull requests. +Feel free to fork this at http://github.com/igbinary/igbinary + +See [TESTING.md](./TESTING.md) for advice for testing patches. + +See [TECH\_NOTES.md](./TECH_NOTES.md) for information about how igbinary is implemented Utilizing in other extensions ----------------------------- Igbinary can be called from other extensions fairly easily. Igbinary installs -its header file to _ext/igbinary/igbinary.h_. There are just two straighforward +its header file to _ext/igbinary/igbinary.h_. There are just two straightforward functions: `igbinary_serialize` and `igbinary_unserialize`. Look at _igbinary.h_ for prototypes and usage. @@ -137,6 +158,4 @@ Trivia Where does the name "igbinary" come from? There was once a similar project called fbinary but it has disappeared from the Internet a long time ago. Its architecture wasn't particularly clean either. IG is an abbreviation for a -finnish social networking site IRC-Galleria (http://irc-galleria.net/) - - +Finnish social networking site IRC-Galleria (http://irc-galleria.net/) diff --git a/RELEASE_CHECKLIST b/RELEASE_CHECKLIST new file mode 100644 index 00000000..9833da47 --- /dev/null +++ b/RELEASE_CHECKLIST @@ -0,0 +1,11 @@ +* Update version in src/php7/igbinary.h to remove dev suffix. +* Update version, release date in package.xml and NEWS. +* Run `pecl package package.xml`. +* Run `pecl install igbinary-XXX.tgz`. +* Commit. +* Create a release. +* Bump version in igbinary.h and add dev suffix. +* Copy release in package.xml into changelog. +* Clear current changelog, bump versions and add dev suffix. +* Commit. +* Upload package on pecl.php.net. diff --git a/TECH_NOTES.md b/TECH_NOTES.md new file mode 100644 index 00000000..cf532344 --- /dev/null +++ b/TECH_NOTES.md @@ -0,0 +1,155 @@ +Overview +-------- + +The implementation of `igbinary` is largely based on php's own serializer. +Refer to https://github.com/php/php-src/blob/master/ext/standard/var_unserializer.re +and https://github.com/php/php-src/blob/master/ext/standard/var.c + +Format +------ + +Refer to the implementation for details of how this works - this section is an overview and is not comprehensive. +Values are serialized with a byte representing the type of the value, +followed by 0 or more bytes with the serialization of the value. + +Values can also be references to reuse earlier values +- `igbinary_type_string_id8` refers to a string value that was already serialized, where the identifier of that string takes up 8 bits(1 byte) +- `igbinary_type_objref8` refers to the *array of properties in* an object value that was already serialized +- `igbinary_type_object_id8` refers to a object that was already serialized. +- `igbinary_type_ref8` refers to something of any type that's part of a PHP reference group. + `igbinary_type_ref` creates a reference group the first time it's used, and `igbinary_type_ref8` adds a value that's a reference to that reference group. + +The header returned by `igbinary_serialize()` in the latest igbinary releases +always starts with `\x00\x00\x00\x02` (version 2) (long ago, this started with `\x00\x00\x00\x01`) + +```c +// From src/php7/igbinary.c +enum igbinary_type { + /* 00 */ igbinary_type_null, /**< Null. */ + + /* 01 */ igbinary_type_ref8, /**< Array reference. */ + /* 02 */ igbinary_type_ref16, /**< Array reference. */ + /* 03 */ igbinary_type_ref32, /**< Array reference. */ + + /* 04 */ igbinary_type_bool_false, /**< Boolean true. */ + /* 05 */ igbinary_type_bool_true, /**< Boolean false. */ + + /* 06 */ igbinary_type_long8p, /**< Long 8bit positive. */ + /* 07 */ igbinary_type_long8n, /**< Long 8bit negative. */ + /* 08 */ igbinary_type_long16p, /**< Long 16bit positive. */ + /* 09 */ igbinary_type_long16n, /**< Long 16bit negative. */ + /* 0a */ igbinary_type_long32p, /**< Long 32bit positive. */ + /* 0b */ igbinary_type_long32n, /**< Long 32bit negative. */ + + /* 0c */ igbinary_type_double, /**< Double. */ + + /* 0d */ igbinary_type_string_empty, /**< Empty string. */ + + /* 0e */ igbinary_type_string_id8, /**< String id. */ + /* 0f */ igbinary_type_string_id16, /**< String id. */ + /* 10 */ igbinary_type_string_id32, /**< String id. */ + + /* 11 */ igbinary_type_string8, /**< String. */ + /* 12 */ igbinary_type_string16, /**< String. */ + /* 13 */ igbinary_type_string32, /**< String. */ + + /* 14 */ igbinary_type_array8, /**< Array. */ + /* 15 */ igbinary_type_array16, /**< Array. */ + /* 16 */ igbinary_type_array32, /**< Array. */ + + /* 17 */ igbinary_type_object8, /**< Object. */ + /* 18 */ igbinary_type_object16, /**< Object. */ + /* 19 */ igbinary_type_object32, /**< Object. */ + + /* 1a */ igbinary_type_object_id8, /**< Object string id. */ + /* 1b */ igbinary_type_object_id16, /**< Object string id. */ + /* 1c */ igbinary_type_object_id32, /**< Object string id. */ + + /* 1d */ igbinary_type_object_ser8, /**< Object serialized data. (when Serializable::serialize() is used) */ + /* 1e */ igbinary_type_object_ser16, /**< Object serialized data. */ + /* 1f */ igbinary_type_object_ser32, /**< Object serialized data. */ + + /* 20 */ igbinary_type_long64p, /**< Long 64bit positive. */ + /* 21 */ igbinary_type_long64n, /**< Long 64bit negative. */ + + /* 22 */ igbinary_type_objref8, /**< Object reference. */ + /* 23 */ igbinary_type_objref16, /**< Object reference. */ + /* 24 */ igbinary_type_objref32, /**< Object reference. */ + + /* 25 */ igbinary_type_ref, /**< Simple reference */ +}; +``` + +For example, `bin2hex(igbinary_serialize(['first', true]))` is `000000021402060011056669727374060105` + +``` +00000002 -- 4 byte header indicating this is igbinary serialized data, version 2 +14 02 -- An array(igbinary_type_array8) of size 2 (8-bit length) + 06 00 -- The first array key - an igbinary_type_long8p (positive unsigned 8-bit integer) with value `0` (index 0) + 11 05 6669727374 -- The array value - igbinary_type_string8 with an 8-bit length of 5 and the string value `first` + 06 01 -- The second array key - the value `0` + 05 -- igbinary_type_bool_true representing the value `true` +``` + +A limitation of the current serialization format and unserializers is that there can only be one reference group to the same value. (`igbinary_type_ref`) + +For example, `$b = $a; igbinary_serialize([&$a, &$a, &$b, &$b])` would be serialized the same way as `[&$a, &$a, &$a, &$a]`. + +Serializing +----------- + +See README.md for details about the format, performance, and ini options. + +### Edge cases + +The following types of PHP methods are invoked when serializing values. +`igbinary_serialize()` must ensure that the pointers it keeps to the values being serialized +(and temporary values returned by invoked methods) aren't modified during serialization. + +1. `Serializable::serialize()` may have side effects if the serialized + object has +2. `__sleep` is called immediately, which may have side effects +3. `__serialize` may have immediate side effects. +4. The serializers of internal classes. + +Edge cases are dealt with by adding a reference to all referenceable values +so that they can't be freed prematurely. + +Unserializing +------------- + +### Edge cases + +`igbinary_unserialize()` must ensure that the pointers it keeps to the php values it creates don't become invalid +during unserialization. + +The following types of magic methods can be called during unserialization: + +1. `Serializable::unserialize(string $serialized)` may have global side effects, + but is assumed to be unable to read or modify other values that are being unserialized + (excluding values created during other calls to `Serializable::unserialize()`, but igbinary is only concerned with the resulting object instance) + because it must be called while unserializing everything else. +2. `__unserialize()` is expected to be safe, because everything else has already been unserialized before it gets called. +3. `__wakeup()` is expected to be safe, because everything else has already been unserialized before it gets called. +4. `__destruct()` is called, but only if&after everything was successfully unserialized + (e.g. if `__wakeup()` throws, `__destruct()` will not be called for any directly created objects) + +Sessions +-------- + +The standard implementation of `igbinary_serialize` and `igbinary_unserialize` are used to serialize/unserialize session data. + +Refer to https://github.com/php/php-src/blob/master/ext/session/session.c for reference implementations of `PS_SERIALIZER_ENCODE_FUNC` and `PS_SERIALIZER_DECODE_FUNC` and how they change in PHP minor versions. + +APCu +---- + +`apc_register_serializer` is called if igbinary was called with APCu support to make APCu aware that the igbinary serializer is available to serialize data to store in memory. + +See the parts of the code referring to `HAVE_APCU_SUPPORT` + +Redis, Memcached, etc. +---------------------- + +These data stores often use binary flags (or any other unambiguous indicator) to indicate that the igbinary serializer was used when saving data to the database, +and read those flags to determine which unserializer (e.g. `json_decode()`, `unserialize()`, `igbinary_unserialize()`, msgpack) to use. diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 00000000..e35c6d0f --- /dev/null +++ b/TESTING.md @@ -0,0 +1,37 @@ +# Testing + +## Running tests + +```sh +phpize +./configure +make clean +# To run tests with normal options +make test +# To run a specific test with Valgrind to debug crashes or memory leaks, add -m +make test TESTS="-m tests/igbinary_0mytest.phpt" +``` + +### Running APCu test cases + +This is only necessary if you've touched the igbinary memory management code or the code for interacting with APCu, +or are debugging an issue involving both APCu with the igbinary serializer. + +Note that APCu 5.1.10 or newer is strongly recommended, +since there were [known bugs in APCu](https://github.com/krakjoe/apcu/issues/260) prior to that. + +Instructions + +```sh +# go to modules directory +cd modules + +# ... and create symlink to apcu extension +# it will be loaded during test suite +# replace this example path with the actual path to apcu.so for the php binary +ln -nsf /opt/lib/php/extensions/no-debug-non-zts-20121212/apcu.so +``` + +### Code Coverage + +See [`CODE_COVERAGE.md`](./CODE_COVERAGE.md) diff --git a/WINDOWS.md b/WINDOWS.md new file mode 100644 index 00000000..2600131b --- /dev/null +++ b/WINDOWS.md @@ -0,0 +1,150 @@ +Compiling igbinary from source on Windows +========================================= + +This guide is added for packagers or developers of the igbinary project. + +Downloading [prebuilt DLLs from PECL](https://pecl.php.net/package/igbinary) is easier for end users. + +Setting up a Windows VM +----------------------- + +If you don't have Windows installed: + +1. Download a VM from https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/ (E.g. Windows 7 IE11) + Prefer Windows 7. The trial period can be extended. +2. Install the VM. Keep the disk image around, the VM trial period expires in 30 days. +3. (Optional) Configure the VM to use more than one core (but not all available cores) to speed up installation/upgrading/compiling. +4. Download https://support.microsoft.com/en-us/kb/3102810#bookmark-prerequisite (x86), disable internet, install that patch, re-enable internet) +5. Run Windows Updates, which will take a long time. +6. Read the instructions on the desktop background (for extending trial period). +7. (Windows 7)Search for "Command Prompt", right click on it and click "Run as administrator", and execute `slmgr /ato` to extend the trial period to 90 days. + +If the license expires/is about to expire, it is possible to "re-arm" (extend) the license several times with `slmgr` + +Installing Visual Studio +------------------------ + +1. Download the version of Visual Studio needed to build the desired version of PHP + (mentioned on https://wiki.php.net/internals/windows/stepbystepbuild#building_pecl_extensions for PHP 7.0-7.1), + and https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2 for PHP 7.2+ + + At the time of writing, that was [Visual Studio Community Edition 2015](https://visualstudio.microsoft.com/vs/older-downloads/) for PHP7.0 - 7.2, + and [Visual Studio Community Edition 2017](https://www.visualstudio.com/products/visual-studio-community-vs) for PHP 7.2+ +2. Install Visual Studio 2015. (Use custom installation, make sure that all C++ features are enabled) + +Downloading PHP +--------------- + +See https://wiki.php.net/internals/windows/stepbystepbuild + +Prefer downloading stable releases of php? + +(This hasn't been tested yet on the suggested VM) + + +Building igbinary +----------------- + +See https://wiki.php.net/internals/windows/stepbystepbuild#building\_pecl\_extensions + +### Install APCu (optional) + +Note: The Windows build hasn't worked for a while in 5.6, so there may be compilation errors or test failures. + +TODO: Make sure that `config.w32` works without APCu installed. + +> 1. Open the extension's page on PECL ([APCu](https://pecl.php.net/package/APCu)) +> 2. Download the extension source either by: +> +> - downloading a source archive +> - fetching the source from the extension's repository (link can be found under *Browse Source*) +> 3. Create a directory named pecl on the same level as your PHP source directory, e.g. C:\php-sdk\phpdev\vc11\x86\pecl +> 4. Extract or clone the extension source code to the pecl directory +> +> - if cloning, clone to a subdirectory, e.g. C:\php-sdk\phpdev\vc11\x86\pecl\apcu +> - source code archive should already contain a subdirectory named e.g. apcu-4.0.7 +> 5. Open a command prompt, run the `setvars` script, and enter your PHP source directory +> 6. Rebuild the configure script by running: +> +> ``` +> buildconf +> ``` +> +> 7. Executing `configure --help` should now contain an option to enable APCu +> +> ``` +> --enable-apcu Whether to enable APCu support +> ``` +> +> 8. Configure and build: +> +> ``` +> configure --disable-all --enable-cli --enable-apcu +> ``` +> +> ``` +> nmake +> ``` +> +> 9. Test the binary with a php -m command, to make sure APCu is loaded + +### Install igbinary + +Similar to the above instructions. + +1. (This isn't released yet, skip this step and download from github) + Open the extension's page on PECL ( APCu) +2. Download the extension source either by: + + - downloading a source archive + - fetching the source from the extension's repository (link can be found under *Browse Source*) + https://github.com/igbinary/igbinary/ +3. If it doesn't already exist, then create a directory named pecl on the same level as your PHP source directory, e.g. C:\php-sdk\phpdev\vc11\x86\pecl +4. Extract or clone the extension source code to the pecl directory + + - if cloning, clone to a subdirectory, e.g. C:\php-sdk\phpdev\vc11\x86\pecl\igbinary + - source code archive should already contain a subdirectory with a name like igbinary-2.0.8. That should be renamed to igbinary. +5. Open a command prompt, run the setvars script, and enter your PHP source directory (not the extension directory) +6. Rebuild the configure script by running: + + ``` + buildconf + ``` + +7. Executing `configure --help` should now contain options. + (`config.m4` currently has no options. May allow disabling apcu support in the future, etc.) + + ``` + --enable-apcu Whether to enable APCu support + --enable-igbinary Whether to enable igbinary support + ``` + +8. Configure and build: + One may want to set CFLAGS (or equivalent) to -O2 so it will be faster? + + If you want to test sessions and apcu as well, an extremely minimal configure script would be: + + ``` + configure --disable-all --enable-cli --enable-cgi --enable-apcu --enable-igbinary --enable-json + ``` + + (If testing a full installation, add `--enable-igbinary` to the config flags you were already using) + + Then, build/rebuild php. + + ``` + nmake + ``` + +9. Invoke the `php -m` command to confirm that igbinary is loaded. (and any other extensions such as APCu) +10. Run the unit tests. Most should pass, some should be skipped (Or be expected failures(XFAIL)), none should fail. + + ``` + # In the php source directory, execute the following commands: + nmake test TESTS=C:\php-sdk\phpdev\path\to\igbinary\tests + # Optional test of related extensions: + # nmake test TESTS=C:\php-sdk\phpdev\path\to\apcu\tests + ``` + + In recent releases of php7, this can be probably done by running `nmake test` + within the igbinary folder immediately after configuration for the desired php7 version. diff --git a/apc_serializer.h b/apc_serializer.h deleted file mode 100644 index bc29ca1a..00000000 --- a/apc_serializer.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | APC | - +----------------------------------------------------------------------+ - | Copyright (c) 2006-2011 The PHP Group | - +----------------------------------------------------------------------+ - | This source file is subject to version 3.01 of the PHP license, | - | that is bundled with this package in the file LICENSE, and is | - | available through the world-wide-web at the following url: | - | http://www.php.net/license/3_01.txt. | - | If you did not receive a copy of the PHP license and are unable to | - | obtain it through the world-wide-web, please send a note to | - | license@php.net so we can mail you a copy immediately. | - +----------------------------------------------------------------------+ - | Authors: Gopal Vijayaraghavan | - +----------------------------------------------------------------------+ - - */ - -/* $Id: $ */ - -#ifndef APC_SERIALIZER_H -#define APC_SERIALIZER_H - -/* this is a shipped .h file, do not include any other header in this file */ -#define APC_SERIALIZER_NAME(module) module##_apc_serializer -#define APC_UNSERIALIZER_NAME(module) module##_apc_unserializer - -#define APC_SERIALIZER_ARGS unsigned char **buf, size_t *buf_len, const zval *value, void *config TSRMLS_DC -#define APC_UNSERIALIZER_ARGS zval **value, unsigned char *buf, size_t buf_len, void *config TSRMLS_DC - -typedef int (*apc_serialize_t)(APC_SERIALIZER_ARGS); -typedef int (*apc_unserialize_t)(APC_UNSERIALIZER_ARGS); - -typedef int (*apc_register_serializer_t)(const char* name, - apc_serialize_t serialize, - apc_unserialize_t unserialize, - void *config TSRMLS_DC); - -/* - * ABI version for constant hooks. Increment this any time you make any changes - * to any function in this file. - */ -#define APC_SERIALIZER_ABI "0" -#define APC_SERIALIZER_CONSTANT "\000apc_register_serializer-" APC_SERIALIZER_ABI - -#if !defined(APC_UNUSED) -# if defined(__GNUC__) -# define APC_UNUSED __attribute__((unused)) -# else -# define APC_UNUSED -# endif -#endif - -static APC_UNUSED int apc_register_serializer(const char* name, - apc_serialize_t serialize, - apc_unserialize_t unserialize, - void *config TSRMLS_DC) -{ - zval *apc_magic_constant = NULL; - (void)config; - - ALLOC_INIT_ZVAL(apc_magic_constant); - - if (zend_get_constant(APC_SERIALIZER_CONSTANT, sizeof(APC_SERIALIZER_CONSTANT)-1, apc_magic_constant TSRMLS_CC)) { - if(apc_magic_constant) { - apc_register_serializer_t register_func = (apc_register_serializer_t)(Z_LVAL_P(apc_magic_constant)); - if(register_func) { - zval_dtor(apc_magic_constant); - return register_func(name, serialize, unserialize, NULL TSRMLS_CC); - } - } - } - - zval_dtor(apc_magic_constant); - - return 0; -} - -#endif - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - * vim>600: expandtab sw=4 ts=4 sts=4 fdm=marker - * vim<600: expandtab sw=4 ts=4 sts=4 - */ diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..03da1df8 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,39 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: +- master + +jobs: + - template: .azure/job.yml + parameters: + configurationName: PHP_70_NTS + phpVersion: 7.0 + vmImage: 'ubuntu-16.04' + - template: .azure/job.yml + parameters: + configurationName: PHP_71_NTS + phpVersion: 7.1 + vmImage: 'ubuntu-16.04' + - template: .azure/job.yml + parameters: + configurationName: PHP_72_NTS + phpVersion: 7.2 + vmImage: 'ubuntu-16.04' + - template: .azure/job.yml + parameters: + configurationName: PHP_73_NTS + phpVersion: 7.3 + vmImage: 'ubuntu-18.04' + - template: .azure/job.yml + parameters: + configurationName: PHP_74_NTS + phpVersion: 7.4 + vmImage: 'ubuntu-20.04' +# - template: .azure/job.yml +# parameters: +# configurationName: PHP_80_NTS +# phpVersion: 8.0 +# vmImage: 'ubuntu-20.04' diff --git a/benchmark/bench.php b/benchmark/bench.php index 7cf10f81..fde71dd0 100644 --- a/benchmark/bench.php +++ b/benchmark/bench.php @@ -1,5 +1,7 @@ headerWritten = true; @@ -69,13 +71,13 @@ public function write() { $this->writeHeader(); } - printf("%s\t%.6f\t%d\t%.8f\t%.6f\n", + printf("%s\t%.6f\t%d\t%.8f\t%.6f\t%dKB\n", $this->name, $this->startTime, $this->iterations, $this->stopTime - $this->startTime, - $this->stopUsage - $this->startUsage); + $this->stopUsage - $this->startUsage, + memory_get_usage() / 1024 + ); } } - - diff --git a/benchmark/comparisons.php b/benchmark/comparisons.php new file mode 100644 index 00000000..0d03b806 --- /dev/null +++ b/benchmark/comparisons.php @@ -0,0 +1,255 @@ +longPropertyName = $longPropertyName; + $this->bar = $bar; + $this->i = $i; + } +} + +class Example { + public $data; + /** @var int */ + public $iterations; + public function __construct($data, int $iterations) { + $this->data = $data; + $this->iterations = $iterations; + } +} + +class Result { + /** @var float */ + public $serialize_time; + /** @var float */ + public $unserialize_time; + /** @var int the length of the serialized data */ + public $serialized_bytes; + public function __construct(float $serialize_time, float $unserialize_time, int $serialized_bytes) { + $this->serialize_time = $serialize_time; + $this->unserialize_time = $unserialize_time; + $this->serialized_bytes = $serialized_bytes; + } +} + +class Benchmark { + const NANOSECONDS_PER_SECOND = 1000000000; + public static function run(Closure $serialize, Closure $unserialize, Example $example): Result { + $iterations = (int)$example->iterations; + $data = $example->data; + $t1 = hrtime(true); + for ($i = 0; $i < $iterations; $i++) { + $ser = $serialize($data); + } + $t2 = hrtime(true); + for ($i = 0; $i < $iterations; $i++) { + $result = $unserialize($ser); + } + $t3 = hrtime(true); + return new Result(($t2 - $t1) / self::NANOSECONDS_PER_SECOND, ($t3 - $t2) / self::NANOSECONDS_PER_SECOND, strlen($ser)); + } + + public static function createArrayArray() { + srand(13333); + $data = []; + for ($i = 0; $i < 1000; $i++) { + $part = []; + for ($j = 0; $j < rand() % 20; $j++) { + $part[] = rand() % 300; + } + $data[] = $part; + } + return $data; + } + + public static function createObjectArray(): array { + $array = []; + for ($i = 0; $i < 1000; $i++) { + $o = new MyObject($i, "test$i", null); + + $array[] = $o; + } + return $array; + } + + public static function createObject(): MyObject { + return new MyObject(10, 1234, true); + } + + public static function createScalarArray(): array { + $array = []; + for ($i = 0; $i < 1000; $i++) { + switch ($i % 4) { + case 0: + $array[] = "da string " . $i; + break; + case 1: + $array[] = 1.31 * $i; + break; + case 2: + $array[] = rand(0, PHP_INT_MAX); + break; + case 3: + $array[] = (bool)($i & 1); + break; + } + } + return $array; + } + + public static function createStdClassArray(): array { + $array = []; + for ($i = 0; $i < 1000; $i++) { + $array = []; + for ($i = 0; $i < 1000; $i++) { + $o = new MyObject(10, $i, $i % 2 != 0); + $array[] = $o; + } + } + return $array; + } + + public static function createReusedArrayArray(): array { + $array = []; + for ($i = 0; $i < 1000; $i++) { + $array = []; + for ($i = 0; $i < 1000; $i++) { + $o = ['longPropertyName' => 10, 'i' => $i, 'isOdd' => $i % 2 != 0]; + $array[] = $o; + } + } + return $array; + } + + /** + * @return array + */ + public static function createExamples(): array { + return [ + 'list_of_lists' => new Example(self::createArrayArray(), 2000), + 'scalar_int' => new Example(1, 3000000), + 'scalar_array' => new Example(self::createScalarArray(), 10000), + 'object' => new Example(self::createObject(), 300000), + 'stdClass' => new Example((object)['longPropertyName' => 10, 'bar' => 'test', 'i' => null], 300000), + 'distinct_stringtostring_array' => new Example(unserialize(file_get_contents(__DIR__ . '/l10n-en.ser')), 600), + 'array_of_reusedkey_stdClass' => new Example(self::createStdClassArray(), 1000), + 'array_of_reusedkey_object' => new Example(self::createObjectArray(), 1000), + 'array_of_reusedkey_array' => new Example(self::createReusedArrayArray(), 1000), + ]; + } + + public static function createSerializersToBenchmark(): array + { + $igbinary_serialize = from_callable('igbinary_serialize'); + $igbinary_unserialize = from_callable('igbinary_unserialize'); + $serialize = from_callable('serialize'); + $unserialize = from_callable('unserialize'); + $igbinary = ['serialize' => $igbinary_serialize, 'unserialize' => $igbinary_unserialize]; + $default = ['serialize' => $serialize, 'unserialize' => $unserialize]; + $serializers = [ + 'igbinary' => $igbinary, + 'default' => $default, + ]; + if (function_exists('json_encode') && getenv('BENCHMARK_JSON')) { + // NOTE that json does not preserve class types or distinguish between associative arrays and objects + $json = ['serialize' => from_callable('json_encode'), 'unserialize' => from_callable('json_decode')]; + $serializers['json'] = $json; + } + if (function_exists('simdjson_decode') && getenv('BENCHMARK_SIMDJSON')) { + // NOTE that json does not preserve class types or distinguish between associative arrays and objects + $json = ['serialize' => from_callable('json_encode'), 'unserialize' => from_callable('simdjson_decode')]; + $serializers['simdjson'] = $json; + } + if (function_exists('msgpack_pack')) { + // NOTE that json does not preserve class types or distinguish between associative arrays and objects + $msgpack = ['serialize' => from_callable('msgpack_pack'), 'unserialize' => from_callable('msgpack_unpack')]; + $serializers['msgpack'] = $msgpack; + } + return $serializers; + } + + public static function main() { + error_reporting(E_ALL); + ini_set('display_errors', 'stderr'); + $serializers = self::createSerializersToBenchmark(); + + $opcache_enabled = function_exists('opcache_get_status') ? opcache_get_status()['opcache_enabled'] : false; + printf("Running with PHP %s\n", PHP_VERSION); + printf("igbinary version=%s\n", phpversion('igbinary')); + printf("opcache is %s\n", $opcache_enabled ? 'enabled' : 'disabled'); + printf("igbinary.compact_strings=%s\n", ini_get('igbinary.compact_strings')); + printf("Smaller times are better (the iterations completed faster)\n"); + echo "\n"; + $first_line = sprintf("%30s | ", ""); + foreach ($serializers as $name => $results) { + $first_line .= sprintf(" | %10s time(seconds) ", $name); + } + echo "$first_line\n"; + printf("%30s | iterations", "Benchmark"); + foreach ($serializers as $_) { + printf(" | serialize unserialize ser_bytes unser_speedup", $name); + } + echo "\n"; + echo str_repeat('-', strlen($first_line)) . "\n"; + $examples = self::createExamples(); + foreach ($examples as $exampleName => $example) { + $results = []; + foreach ($serializers as $name => $callbacks) { + gc_collect_cycles(); + $results[$name] = self::run($callbacks['serialize'], $callbacks['unserialize'], $example); + } + printf("%30s | %10d", $exampleName, $example->iterations); + foreach ($results as $name => $result) { + $unser_speedup = $results['default']->unserialize_time / $result->unserialize_time; + printf(" | %9.5f %11.5f %9d %12.3fx", $result->serialize_time, $result->unserialize_time, $result->serialized_bytes, $unser_speedup); + } + echo "\n"; + } + } +} + +Benchmark::main(); diff --git a/benchmark/serialize-arrayarray.b.php b/benchmark/serialize-arrayarray.b.php new file mode 100644 index 00000000..44e5d0f0 --- /dev/null +++ b/benchmark/serialize-arrayarray.b.php @@ -0,0 +1,31 @@ +start(); + for ($j = 0; $j < 2000; $j++) { + $ser = igbinary_serialize($array); + $unser = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/serialize-objectarray.b.php b/benchmark/serialize-objectarray.b.php index 21cec8d2..ec6ccf65 100644 --- a/benchmark/serialize-objectarray.b.php +++ b/benchmark/serialize-objectarray.b.php @@ -4,27 +4,29 @@ require_once 'bench.php'; -$b = new Bench('serialize-object-array'); - class Obj { private $foo = 10; public $bar = "test"; public $i; } -$array = array(); -for ($i = 0; $i < 1000; $i++) { - $o = new Obj(); - $o->i = $i; +call_user_func(function () { + $b = new Bench('serialize-object-array'); - $array[] = $o; -} + $array = array(); + for ($i = 0; $i < 1000; $i++) { + $o = new Obj(); + $o->i = $i; -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 2000; $j++) { - $ser = igbinary_serialize($array); - } - $b->stop($j); - $b->write(); -} + $array[] = $o; + } + + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 2000; $j++) { + $ser = igbinary_serialize($array); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/serialize-scalar-int.b.php b/benchmark/serialize-scalar-int.b.php index a3e33764..150d00ed 100644 --- a/benchmark/serialize-scalar-int.b.php +++ b/benchmark/serialize-scalar-int.b.php @@ -4,15 +4,17 @@ require_once 'bench.php'; -$b = new Bench('serialize-scalar-int'); +call_user_func(function () { + $b = new Bench('serialize-scalar-int'); -$var = 1; + $var = 1; -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 3500000; $j++) { - $ser = igbinary_serialize($var); - } - $b->stop($j); - $b->write(); -} + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 3500000; $j++) { + $ser = igbinary_serialize($var); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/serialize-scalararray.b.php b/benchmark/serialize-scalararray.b.php index 4c847fd7..dc2c0f95 100644 --- a/benchmark/serialize-scalararray.b.php +++ b/benchmark/serialize-scalararray.b.php @@ -4,31 +4,33 @@ require_once 'bench.php'; -$b = new Bench('serialize-scalar-array'); +call_user_func(function () { + $b = new Bench('serialize-scalar-array'); -$array = array(); -for ($i = 0; $i < 1000; $i++) { - switch ($i % 4) { - case 0: - $array[] = "da string " . $i; - break; - case 1: - $array[] = 1.31 * $i; - break; - case 2: - $array[] = rand(0, PHP_INT_MAX); - break; - case 3: - $array[] = (bool)($i & 1); - break; - } -} + $array = array(); + for ($i = 0; $i < 1000; $i++) { + switch ($i % 4) { + case 0: + $array[] = "da string " . $i; + break; + case 1: + $array[] = 1.31 * $i; + break; + case 2: + $array[] = rand(0, PHP_INT_MAX); + break; + case 3: + $array[] = (bool)($i & 1); + break; + } + } -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 12000; $j++) { - $ser = igbinary_serialize($array); - } - $b->stop($j); - $b->write(); -} + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 12000; $j++) { + $ser = igbinary_serialize($array); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/serialize-stringarray.b.php b/benchmark/serialize-stringarray.b.php index 48622652..012e60bd 100644 --- a/benchmark/serialize-stringarray.b.php +++ b/benchmark/serialize-stringarray.b.php @@ -4,15 +4,17 @@ require_once 'bench.php'; -$b = new Bench('serialize-string-array'); +call_user_func(function () { + $b = new Bench('serialize-string-array'); -$array = unserialize(file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'l10n-en.ser')); + $array = unserialize(file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'l10n-en.ser')); -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 360; $j++) { - $ser = igbinary_serialize($array); - } - $b->stop($j); - $b->write(); -} + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 360; $j++) { + $ser = igbinary_serialize($array); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-arrayarray.php b/benchmark/unserialize-arrayarray.php new file mode 100644 index 00000000..9e318ea0 --- /dev/null +++ b/benchmark/unserialize-arrayarray.php @@ -0,0 +1,30 @@ +start(); + for ($j = 0; $j < 500; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + unset($array); + } +}); diff --git a/benchmark/unserialize-emptyarray.php b/benchmark/unserialize-emptyarray.php new file mode 100644 index 00000000..52371003 --- /dev/null +++ b/benchmark/unserialize-emptyarray.php @@ -0,0 +1,23 @@ +start(); + $results = []; + for ($j = 0; $j < 500000; $j++) { + $results[] = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-intarray.b.php b/benchmark/unserialize-intarray.b.php new file mode 100644 index 00000000..d29a29d2 --- /dev/null +++ b/benchmark/unserialize-intarray.b.php @@ -0,0 +1,26 @@ +start(); + for ($j = 0; $j < 50000; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-largearrayarray.php b/benchmark/unserialize-largearrayarray.php new file mode 100644 index 00000000..27979e97 --- /dev/null +++ b/benchmark/unserialize-largearrayarray.php @@ -0,0 +1,30 @@ +start(); + for ($j = 0; $j < 500; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + unset($array); + } +}); diff --git a/benchmark/unserialize-object.b.php b/benchmark/unserialize-object.b.php new file mode 100644 index 00000000..e981c9cb --- /dev/null +++ b/benchmark/unserialize-object.b.php @@ -0,0 +1,28 @@ +i = 2; + $ser = igbinary_serialize($o); + + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 300000; $j++) { + $o2 = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-objectarray.b.php b/benchmark/unserialize-objectarray.b.php index f9bc98e8..1d43b136 100644 --- a/benchmark/unserialize-objectarray.b.php +++ b/benchmark/unserialize-objectarray.b.php @@ -4,28 +4,30 @@ require_once 'bench.php'; -$b = new Bench('unserialize-object-array'); - class Obj { private $foo = 10; public $bar = "test"; public $i; } -$array = array(); -for ($i = 0; $i < 1000; $i++) { - $o = new Obj(); - $o->i = $i; +call_user_func(function () { + $b = new Bench('unserialize-object-array'); - $array[] = $o; -} -$ser = igbinary_serialize($array); + $array = array(); + for ($i = 0; $i < 1000; $i++) { + $o = new Obj(); + $o->i = $i; -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 540; $j++) { - $array = igbinary_unserialize($ser); - } - $b->stop($j); - $b->write(); -} + $array[] = $o; + } + $ser = igbinary_serialize($array); + + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 540; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-objectarray.totalmem.php b/benchmark/unserialize-objectarray.totalmem.php new file mode 100644 index 00000000..f0b9b227 --- /dev/null +++ b/benchmark/unserialize-objectarray.totalmem.php @@ -0,0 +1,34 @@ +i = $i; + + $array[] = $o; + } + $ser = igbinary_serialize($array); + + for ($i = 0; $i < 40; $i++) { + $b->start(); + $arrays = []; + for ($j = 0; $j < 200; $j++) { + $arrays[] = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-scalar-int.b.php b/benchmark/unserialize-scalar-int.b.php index b780da98..f287a085 100644 --- a/benchmark/unserialize-scalar-int.b.php +++ b/benchmark/unserialize-scalar-int.b.php @@ -4,15 +4,17 @@ require_once 'bench.php'; -$b = new Bench('serialize-scalar-int'); +call_user_func(function () { + $b = new Bench('serialize-scalar-int'); -$ser = igbinary_serialize(1); + $ser = igbinary_serialize(1); -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 3600000; $j++) { - $var = igbinary_unserialize($ser); - } - $b->stop($j); - $b->write(); -} + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 3600000; $j++) { + $var = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-scalararray.b.php b/benchmark/unserialize-scalararray.b.php index 997bfb95..3840ff72 100644 --- a/benchmark/unserialize-scalararray.b.php +++ b/benchmark/unserialize-scalararray.b.php @@ -4,32 +4,34 @@ require_once 'bench.php'; -$b = new Bench('unserialize-scalar-array'); +call_user_func(function () { + $b = new Bench('unserialize-scalar-array'); -$array = array(); -for ($i = 0; $i < 1000; $i++) { - switch ($i % 4) { - case 0: - $array[] = "da string " . $i; - break; - case 1: - $array[] = 1.31 * $i; - break; - case 2: - $array[] = rand(0, PHP_INT_MAX); - break; - case 3: - $array[] = (bool)($i & 1); - break; - } -} -$ser = igbinary_serialize($array); + $array = array(); + for ($i = 0; $i < 1000; $i++) { + switch ($i % 4) { + case 0: + $array[] = "da string " . $i; + break; + case 1: + $array[] = 1.31 * $i; + break; + case 2: + $array[] = rand(0, PHP_INT_MAX); + break; + case 3: + $array[] = (bool)($i & 1); + break; + } + } + $ser = igbinary_serialize($array); -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 5800; $j++) { - $array = igbinary_unserialize($ser); - } - $b->stop($j); - $b->write(); -} + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 5800; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-stdclass.b.php b/benchmark/unserialize-stdclass.b.php new file mode 100644 index 00000000..56944558 --- /dev/null +++ b/benchmark/unserialize-stdclass.b.php @@ -0,0 +1,28 @@ +foo = 10; + $o->i = $i; + $o->isOdd = $i % 2 != 0; + $array[] = $o; + } + $ser = igbinary_serialize($array); + + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 2000; $j++) { + $data = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-stringarray.b.php b/benchmark/unserialize-stringarray.b.php index a8d26d2a..319cd2d6 100644 --- a/benchmark/unserialize-stringarray.b.php +++ b/benchmark/unserialize-stringarray.b.php @@ -4,15 +4,17 @@ require_once 'bench.php'; -$b = new Bench('serialize-string-array'); +call_user_func(function () { + $b = new Bench('unserialize-string-array'); -$ser = igbinary_serialize(unserialize(file_get_contents(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'l10n-en.ser'))); + $ser = igbinary_serialize(unserialize(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'l10n-en.ser'))); -for ($i = 0; $i < 40; $i++) { - $b->start(); - for ($j = 0; $j < 500; $j++) { - $array = igbinary_unserialize($ser); - } - $b->stop($j); - $b->write(); -} + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 500; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/benchmark/unserialize-structured-array.b.php b/benchmark/unserialize-structured-array.b.php new file mode 100644 index 00000000..eb57bc9f --- /dev/null +++ b/benchmark/unserialize-structured-array.b.php @@ -0,0 +1,24 @@ + "da string $i", 'flag' => $i % 2]; + } + $ser = igbinary_serialize($array); + + for ($i = 0; $i < 40; $i++) { + $b->start(); + for ($j = 0; $j < 5800; $j++) { + $array = igbinary_unserialize($ser); + } + $b->stop($j); + $b->write(); + } +}); diff --git a/ci/Dockerfile b/ci/Dockerfile new file mode 100644 index 00000000..c076e355 --- /dev/null +++ b/ci/Dockerfile @@ -0,0 +1,17 @@ +ARG PHP_IMAGE +ARG PHP_VERSION +FROM $PHP_IMAGE:$PHP_VERSION +WORKDIR /code +# Used for running tests in Docker +# RUN apt-get update && apt-get install -y valgrind && apt-get clean +# NOTE: In order to avoid valgrind false positives, this would need to compile php from source and configure php --with-valgrind (php-src's zend_string_equals uses inline assembly that causes false positives) +# - ci/install_php_custom.sh and ci/generate_php_install_dir.sh may be a useful reference for that. +ADD *.sh *.c *.h *.php *.md config.m4 config.w32 package.xml COPYING CREDITS NEWS igbinary.spec igbinary.php.ini ./ +ADD src ./src + +# Assume compilation will be the time consuming step. +# Add tests after compiling so that it's faster to update tests and re-run them locally. +RUN phpize && ./configure && make -j2 && make install +RUN docker-php-ext-enable igbinary +ADD tests ./tests +ADD ci ./ci diff --git a/ci/Dockerfile.valgrind b/ci/Dockerfile.valgrind new file mode 100644 index 00000000..84508464 --- /dev/null +++ b/ci/Dockerfile.valgrind @@ -0,0 +1,37 @@ +ARG PHP_IMAGE +ARG PHP_VERSION +FROM $PHP_IMAGE:$PHP_VERSION +WORKDIR /code + +ADD ci/adjust-debian-apt-sources.sh ci/ +RUN ci/adjust-debian-apt-sources.sh +RUN set -eux; . /etc/os-release; apt-get update && apt-get install -y $( [ "x${ID:-linux}" = "xdebian" ] && [ "x${VERSION_ID:-0}" = "x9" ] && echo "--allow-unauthenticated" ) valgrind && apt-get clean +RUN set -eux; . /etc/os-release; apt-get install -y $( [ "x${ID:-linux}" = "xdebian" ] && [ "x${VERSION_ID:-0}" = "x9" ] && echo "--allow-unauthenticated" ) libcurl4-openssl-dev + +ADD ci/install_php_custom.sh ci/generate_php_install_dir.sh ci/ +ARG PHP_VERSION_FULL +ARG PHP_CONFIGURE_ARGS="--disable-all --enable-zts --enable-debug --enable-cgi --enable-session --enable-json --with-curl" +ARG CFLAGS="-DZEND_RC_DEBUG=1" +ENV PHP_CONFIGURE_ARGS=$PHP_CONFIGURE_ARGS PHP_CUSTOM_VERSION=$PHP_VERSION_FULL CFLAGS=$CFLAGS +RUN ci/install_php_custom.sh + +# Assume compilation will be the time consuming step. +# Add tests after compiling so that it's faster to update tests and re-run them locally. +# (The ability to use custom install directories is useful for running tests locally outside docker) +# TODO: Reorder +# +# NOTE: php 8.1 has a patch that may avoid the need to recompile php with --enable-valgrind, +# but the PHP_CONFIGURE_ARGS overrides such as --enable-debug may still be useful +RUN ln -nsf $(ci/generate_php_install_dir.sh) /php-valgrind-install +ENV PATH=/php-valgrind-install/bin:$PATH + +# Used for running tests in Docker +# RUN set -eux; . /etc/os-release; apt-get update && apt-get install -y $( [ "x${ID:-linux}" = "xdebian" ] && [ "x${VERSION_ID:-0}" = "x9" ] && echo "--allow-unauthenticated" ) valgrind && apt-get clean +# NOTE: In order to avoid valgrind false positives, this would need to compile php from source and configure php --with-valgrind (php-src's zend_string_equals uses inline assembly that causes false positives) +# - ci/install_php_custom.sh and ci/generate_php_install_dir.sh may be a useful reference for that. +ADD *.sh *.c *.h *.php *.md config.m4 config.w32 package.xml COPYING CREDITS NEWS igbinary.spec igbinary.php.ini ./ +ADD src ./src +RUN export CFLAGS; phpize && ./configure $PHP_CONFIGURE_ARGS && make clean && make -j2 +# RUN docker-php-ext-enable igbinary +ADD tests ./tests +ADD ci ./ci diff --git a/ci/adjust-debian-apt-sources.sh b/ci/adjust-debian-apt-sources.sh new file mode 100755 index 00000000..cc30e515 --- /dev/null +++ b/ci/adjust-debian-apt-sources.sh @@ -0,0 +1,23 @@ +#!/bin/bash -xeu + +# Adjust Debian APT sources to use a different mirror if needed. +# This is useful in CI environments where the default mirror may be slow or unreliable. + +. /etc/os-release + +if [ "x${ID:-linux}" = "xdebian" ] && [ "x${VERSION_ID:-0}" = "x10" ]; then + echo "Adjusting APT sources for Debian Buster" + sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list + sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list +fi + +if [ "x${ID:-linux}" = "xdebian" ] && [ "x${VERSION_ID:-0}" = "x9" ]; then + echo "Adjusting APT sources for Debian Stretch" + sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list + sed -i 's|http://security.debian.org/debian-security|http://archive.debian.org/debian-security|g' /etc/apt/sources.list + echo 'Acquire::Check-Valid-Until "false";' >> /etc/apt/apt.conf.d/99no-check-valid-until + echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99no-check-valid-until + echo "Disabled stretch-updates and stretch-security in sources.list to prevent 404 errors" + sed -i 's|^deb .*stretch-updates|# &|g' /etc/apt/sources.list + sed -i 's|^deb .*stretch/updates|# &|g' /etc/apt/sources.list +fi diff --git a/ci/g++-32.sh b/ci/g++-32.sh new file mode 100755 index 00000000..85c27717 --- /dev/null +++ b/ci/g++-32.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec g++ -m32 "$@" diff --git a/ci/gcc-32.sh b/ci/gcc-32.sh new file mode 100755 index 00000000..0275fb5d --- /dev/null +++ b/ci/gcc-32.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec gcc -m32 "$@" diff --git a/ci/generate_php_install_dir.sh b/ci/generate_php_install_dir.sh new file mode 100755 index 00000000..bff92e5e --- /dev/null +++ b/ci/generate_php_install_dir.sh @@ -0,0 +1,13 @@ +#!/bin/bash -xeu +# Print a folder name to stdout based on the desired integer width(32 bit or 64 bit) and PHP_CONFIGURE_ARGS. +# This could instead be done by the `source` builtin, but wouldn't work if a different shell (e.g. zsh) was calling bash. +PHP_INSTALL_DIR="$HOME/travis_cache/php-$PHP_CUSTOM_VERSION" +if [ "${USE_32BIT:-0}" = "1" ]; then + PHP_INSTALL_DIR="$PHP_INSTALL_DIR-32bit" +else + PHP_INSTALL_DIR="$PHP_INSTALL_DIR-64bit" +fi +# Generate a unique 4-character suffix based on $PHP_CONFIGURE_ARGS. +HASH=$(echo -n "$PHP_CONFIGURE_ARGS" | sha1sum | cut -c -4) +PHP_INSTALL_DIR="$PHP_INSTALL_DIR-$HASH" +echo -n "$PHP_INSTALL_DIR" diff --git a/ci/get_global_php_version.sh b/ci/get_global_php_version.sh new file mode 100755 index 00000000..1c78c5e5 --- /dev/null +++ b/ci/get_global_php_version.sh @@ -0,0 +1,2 @@ +#!/bin/sh +php -r "if (preg_match('/-dev|RC[0-9]+/', PHP_VERSION)) { printf('%d.%d.%d', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, max(PHP_RELEASE_VERSION-1,0)); } else { echo PHP_VERSION; }" diff --git a/ci/install_php_custom.sh b/ci/install_php_custom.sh new file mode 100755 index 00000000..c76ff509 --- /dev/null +++ b/ci/install_php_custom.sh @@ -0,0 +1,58 @@ +#!/bin/bash -xeu + +# Required env variables: +# PHP_CONFIGURE_ARGS +# PHP_CUSTOM_VERSION (E.g. 7.4.20) +# PHP_INSTALL_DIR (path to installation directory) + +# optional env vars: +# CFLAGS +# CC +# PATH +echo "Attempting to install PHP from source, PHP version '$PHP_CUSTOM_VERSION'/ configure args '$PHP_CONFIGURE_ARGS'" +if [ "x$PHP_CUSTOM_VERSION" = "x" -o "x$PHP_CONFIGURE_ARGS" = "x" ] ; then + echo "Missing php version or configuration arguments"; + exit 1; +fi +PHP_FOLDER="php-$PHP_CUSTOM_VERSION" +PHP_INSTALL_DIR="$(./ci/generate_php_install_dir.sh)" +echo "Downloading $PHP_INSTALL_DIR\n" +if [ -x $PHP_INSTALL_DIR/bin/php ] ; then + echo "PHP $PHP_CUSTOM_VERSION already installed and in cache at $PHP_INSTALL_DIR"; + exit 0 +fi +PHP_CUSTOM_NORMAL_VERSION=${PHP_CUSTOM_VERSION//RC[0-9]/} +PHP_CUSTOM_NORMAL_VERSION=${PHP_CUSTOM_NORMAL_VERSION//alpha[0-9]/} +PHP_CUSTOM_NORMAL_VERSION=${PHP_CUSTOM_NORMAL_VERSION//beta[0-9]/} + +# Remove cache if it somehow exists +if [ "x${TRAVIS:-0}" != "x" ]; then + rm -rf $HOME/travis_cache/ +fi + +# Otherwise, put a minimal installation inside of the cache. +if [ "$PHP_CUSTOM_NORMAL_VERSION" == "8.5.0" ] ; then + PHP_CUSTOM_VERSION=8.5.0RC1 + PHP_FOLDER="php-$PHP_CUSTOM_VERSION" + + PHP_TAR_FILE="$PHP_FOLDER.tar.bz2" + PHP_TAR_URL=https://downloads.php.net/~daniels/$PHP_TAR_FILE +else + PHP_FOLDER="php-$PHP_CUSTOM_VERSION" + + PHP_TAR_FILE="$PHP_FOLDER.tar.bz2" + PHP_TAR_URL=https://www.php.net/distributions/$PHP_TAR_FILE +fi +if [ ! -f $PHP_TAR_FILE ]; then + curl --location --verbose --http1.1 $PHP_TAR_URL -o $PHP_TAR_FILE +fi + +tar xjf $PHP_TAR_FILE + +pushd $PHP_FOLDER +./configure $PHP_CONFIGURE_ARGS --prefix="$PHP_INSTALL_DIR" +make -j5 +make install +popd + +echo "PHP $PHP_CUSTOM_VERSION successfully installed and in cache at $HOME/travis_cache/$PHP_FOLDER"; diff --git a/ci/php.ini b/ci/php.ini new file mode 100644 index 00000000..e52bbb27 --- /dev/null +++ b/ci/php.ini @@ -0,0 +1 @@ +; Intentionally left blank. diff --git a/ci/print_php_int_max.php b/ci/print_php_int_max.php new file mode 100755 index 00000000..8e739236 --- /dev/null +++ b/ci/print_php_int_max.php @@ -0,0 +1,5 @@ +#!/usr/bin/env php + | + | Preston L. Bannister | + | Marcus Boerger | + | Derick Rethans | + | Sander Roobol | + | Andrea Faulds | + | (based on version by: Stig Bakken ) | + | (based on the PHP 3 test framework by Rasmus Lerdorf) | + +----------------------------------------------------------------------+ + */ + +/* $Id: af9f309bc6dac7ccd827ddf739fb71e6aa1945a6 $ */ + +/* Let there be no top-level code beyond this point: + * Only functions and classes, thanks! + * + * Minimum required PHP version: 7.1.0 + */ + +function show_usage() +{ + echo << Run up to simultaneous testing processes in parallel for + quicker testing on systems with multiple logical processors. + Note that this is experimental feature. + + -l Read the testfiles to be executed from . After the test + has finished all failed tests are written to the same . + If the list is empty and no further test is specified then + all tests are executed (same as: -r -w ). + + -r Read the testfiles to be executed from . + + -w Write a list of all failed tests to . + + -a Same as -w but append rather then truncating . + + -W Write a list of all tests and their result status to . + + -c Look for php.ini in directory or use as ini. + + -n Pass -n option to the php binary (Do not use a php.ini). + + -d foo=bar Pass -d option to the php binary (Define INI entry foo + with value 'bar'). + + -g Comma separated list of groups to show during test run + (possible values: PASS, FAIL, XFAIL, XLEAK, SKIP, BORK, WARN, LEAK, REDIRECT). + + -m Test for memory leaks with Valgrind (equivalent to -M memcheck). + + -M Test for errors with Valgrind tool. + + -p Specify PHP executable to run. + + -P Use PHP_BINARY as PHP executable to run (default). + + -q Quiet, no user interaction (same as environment NO_INTERACTION). + + -s Write output to . + + -x Sets 'SKIP_SLOW_TESTS' environmental variable. + + --offline Sets 'SKIP_ONLINE_TESTS' environmental variable. + + --verbose + -v Verbose mode. + + --help + -h This Help. + + --temp-source --temp-target [--temp-urlbase ] + Write temporary files to by replacing from the + filenames to generate with . In general you want to make + the path to your source files and some patch in + your web page hierarchy with pointing to . + + --keep-[all|php|skip|clean] + Do not delete 'all' files, 'php' test file, 'skip' or 'clean' + file. + + --set-timeout [n] + Set timeout for individual tests, where [n] is the number of + seconds. The default value is 60 seconds, or 300 seconds when + testing for memory leaks. + + --context [n] + Sets the number of lines of surrounding context to print for diffs. + The default value is 3. + + --show-[all|php|skip|clean|exp|diff|out|mem] + Show 'all' files, 'php' test file, 'skip' or 'clean' file. You + can also use this to show the output 'out', the expected result + 'exp', the difference between them 'diff' or the valgrind log + 'mem'. The result types get written independent of the log format, + however 'diff' only exists when a test fails. + + --show-slow [n] + Show all tests that took longer than [n] milliseconds to run. + + --no-clean Do not execute clean section if any. + + --color + --no-color Do/Don't colorize the result type in the test result. + + +HELP; +} + +/** + * One function to rule them all, one function to find them, one function to + * bring them all and in the darkness bind them. + * This is the entry point and exit point überfunction. It contains all the + * code that was previously found at the top level. It could and should be + * refactored to be smaller and more manageable. + */ +function main() +{ + /* This list was derived in a naïve mechanical fashion. If a member + * looks like it doesn't belong, it probably doesn't; cull at will. + */ + global $DETAILED, $PHP_FAILED_TESTS, $SHOW_ONLY_GROUPS, $argc, $argv, $cfg, + $cfgfiles, $cfgtypes, $conf_passed, $end_time, $environment, + $exts_skipped, $exts_tested, $exts_to_test, $failed_tests_file, + $ignored_by_ext, $ini_overwrites, $is_switch, $colorize, + $just_save_results, $log_format, $matches, $no_clean, $no_file_cache, + $optionals, $output_file, $pass_option_n, $pass_options, + $pattern_match, $php, $php_cgi, $phpdbg, $preload, $redir_tests, + $repeat, $result_tests_file, $slow_min_ms, $start_time, $switch, + $temp_source, $temp_target, $temp_urlbase, $test_cnt, $test_dirs, + $test_files, $test_idx, $test_list, $test_results, $testfile, + $user_tests, $valgrind, $sum_results, $shuffle, $file_cache; + // Parallel testing + global $workers, $workerID; + global $context_line_count; + + define('IS_WINDOWS', substr(PHP_OS, 0, 3) == "WIN"); + + $workerID = 0; + if (getenv("TEST_PHP_WORKER")) { + $workerID = intval(getenv("TEST_PHP_WORKER")); + run_worker(); + return; + } + + define('INIT_DIR', getcwd()); + + // Change into the PHP source directory. + if (getenv('TEST_PHP_SRCDIR')) { + @chdir(getenv('TEST_PHP_SRCDIR')); + } + + define('TEST_PHP_SRCDIR', getcwd()); + + check_proc_open_function_exists(); + + // If timezone is not set, use UTC. + if (ini_get('date.timezone') == '') { + date_default_timezone_set('UTC'); + } + + // Delete some security related environment variables + putenv('SSH_CLIENT=deleted'); + putenv('SSH_AUTH_SOCK=deleted'); + putenv('SSH_TTY=deleted'); + putenv('SSH_CONNECTION=deleted'); + + set_time_limit(0); + + ini_set('pcre.backtrack_limit', PHP_INT_MAX); + + init_output_buffers(); + + error_reporting(E_ALL); + + $environment = $_ENV ?? []; + + // Some configurations like php.ini-development set variables_order="GPCS" + // not "EGPCS", in which case $_ENV is NOT populated. Detect if the $_ENV + // was empty and handle it by explicitly populating through getenv(). + if (empty($environment)) { + $environment = getenv(); + } + + if (empty($environment['TEMP'])) { + $environment['TEMP'] = sys_get_temp_dir(); + + if (empty($environment['TEMP'])) { + // For example, OpCache on Windows will fail in this case because + // child processes (for tests) will not get a TEMP variable, so + // GetTempPath() will fallback to c:\windows, while GetTempPath() + // will return %TEMP% for parent (likely a different path). The + // parent will initialize the OpCache in that path, and child will + // fail to reattach to the OpCache because it will be using the + // wrong path. + die("TEMP environment is NOT set"); + } else { + if (count($environment) == 1) { + // Not having other environment variables, only having TEMP, is + // probably ok, but strange and may make a difference in the + // test pass rate, so warn the user. + echo "WARNING: Only 1 environment variable will be available to tests(TEMP environment variable)" . PHP_EOL; + } + } + } + + if (IS_WINDOWS && empty($environment["SystemRoot"])) { + $environment["SystemRoot"] = getenv("SystemRoot"); + } + + $php = null; + $php_cgi = null; + $phpdbg = null; + + if (getenv('TEST_PHP_EXECUTABLE')) { + $php = getenv('TEST_PHP_EXECUTABLE'); + + if ($php == 'auto') { + $php = TEST_PHP_SRCDIR . '/sapi/cli/php'; + putenv("TEST_PHP_EXECUTABLE=$php"); + + if (!getenv('TEST_PHP_CGI_EXECUTABLE')) { + $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; + + if (file_exists($php_cgi)) { + putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); + } else { + $php_cgi = null; + } + } + } + $environment['TEST_PHP_EXECUTABLE'] = $php; + } + + if (getenv('TEST_PHP_CGI_EXECUTABLE')) { + $php_cgi = getenv('TEST_PHP_CGI_EXECUTABLE'); + + if ($php_cgi == 'auto') { + $php_cgi = TEST_PHP_SRCDIR . '/sapi/cgi/php-cgi'; + putenv("TEST_PHP_CGI_EXECUTABLE=$php_cgi"); + } + + $environment['TEST_PHP_CGI_EXECUTABLE'] = $php_cgi; + } + + if (!getenv('TEST_PHPDBG_EXECUTABLE')) { + if (IS_WINDOWS && file_exists(dirname($php) . "/phpdbg.exe")) { + $phpdbg = realpath(dirname($php) . "/phpdbg.exe"); + } elseif (file_exists(dirname($php) . "/../../sapi/phpdbg/phpdbg")) { + $phpdbg = realpath(dirname($php) . "/../../sapi/phpdbg/phpdbg"); + } elseif (file_exists("./sapi/phpdbg/phpdbg")) { + $phpdbg = realpath("./sapi/phpdbg/phpdbg"); + } elseif (file_exists(dirname($php) . "/phpdbg")) { + $phpdbg = realpath(dirname($php) . "/phpdbg"); + } else { + $phpdbg = null; + } + if ($phpdbg) { + putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); + } + } + + if (getenv('TEST_PHPDBG_EXECUTABLE')) { + $phpdbg = getenv('TEST_PHPDBG_EXECUTABLE'); + + if ($phpdbg == 'auto') { + $phpdbg = TEST_PHP_SRCDIR . '/sapi/phpdbg/phpdbg'; + putenv("TEST_PHPDBG_EXECUTABLE=$phpdbg"); + } + + $environment['TEST_PHPDBG_EXECUTABLE'] = $phpdbg; + } + + if (getenv('TEST_PHP_LOG_FORMAT')) { + $log_format = strtoupper(getenv('TEST_PHP_LOG_FORMAT')); + } else { + $log_format = 'LEODS'; + } + + // Check whether a detailed log is wanted. + if (getenv('TEST_PHP_DETAILED')) { + $DETAILED = getenv('TEST_PHP_DETAILED'); + } else { + $DETAILED = 0; + } + + junit_init(); + + if (getenv('SHOW_ONLY_GROUPS')) { + $SHOW_ONLY_GROUPS = explode(",", getenv('SHOW_ONLY_GROUPS')); + } else { + $SHOW_ONLY_GROUPS = []; + } + + // Check whether user test dirs are requested. + if (getenv('TEST_PHP_USER')) { + $user_tests = explode(',', getenv('TEST_PHP_USER')); + } else { + $user_tests = []; + } + + $exts_to_test = []; + $ini_overwrites = [ + 'output_handler=', + 'open_basedir=', + 'disable_functions=', + 'output_buffering=Off', + 'error_reporting=' . E_ALL, + 'display_errors=1', + 'display_startup_errors=1', + 'log_errors=0', + 'html_errors=0', + 'track_errors=0', + 'report_memleaks=1', + 'report_zend_debug=0', + 'docref_root=', + 'docref_ext=.html', + 'error_prepend_string=', + 'error_append_string=', + 'auto_prepend_file=', + 'auto_append_file=', + 'ignore_repeated_errors=0', + 'precision=14', + 'serialize_precision=-1', + 'memory_limit=128M', + 'log_errors_max_len=0', + 'opcache.fast_shutdown=0', + 'opcache.file_update_protection=0', + 'opcache.revalidate_freq=0', + 'opcache.jit_hot_loop=1', + 'opcache.jit_hot_func=1', + 'opcache.jit_hot_return=1', + 'opcache.jit_hot_side_exit=1', + 'zend.assertions=1', + 'zend.exception_ignore_args=0', + 'zend.exception_string_param_max_len=15', + 'short_open_tag=0', + ]; + + $no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; + + define('PHP_QA_EMAIL', 'qa-reports@lists.php.net'); + define('QA_SUBMISSION_PAGE', 'http://qa.php.net/buildtest-process.php'); + define('QA_REPORTS_PAGE', 'http://qa.php.net/reports'); + define('TRAVIS_CI', (bool) getenv('TRAVIS')); + + // Determine the tests to be run. + + $test_files = []; + $redir_tests = []; + $test_results = []; + $PHP_FAILED_TESTS = [ + 'BORKED' => [], + 'FAILED' => [], + 'WARNED' => [], + 'LEAKED' => [], + 'XFAILED' => [], + 'XLEAKED' => [], + 'SLOW' => [] + ]; + + // If parameters given assume they represent selected tests to run. + $result_tests_file = false; + $failed_tests_file = false; + $pass_option_n = false; + $pass_options = ''; + + $output_file = INIT_DIR . '/php_test_results_' . date('Ymd_Hi') . '.txt'; + + $just_save_results = false; + $valgrind = null; + $temp_source = null; + $temp_target = null; + $temp_urlbase = null; + $conf_passed = null; + $no_clean = false; + $colorize = true; + if (function_exists('sapi_windows_vt100_support') && !sapi_windows_vt100_support(STDOUT, true)) { + $colorize = false; + } + if (array_key_exists('NO_COLOR', $_ENV)) { + $colorize = false; + } + $selected_tests = false; + $slow_min_ms = INF; + $preload = false; + $file_cache = null; + $shuffle = false; + $workers = null; + $context_line_count = 3; + + $cfgtypes = ['show', 'keep']; + $cfgfiles = ['skip', 'php', 'clean', 'out', 'diff', 'exp', 'mem']; + $cfg = []; + + foreach ($cfgtypes as $type) { + $cfg[$type] = []; + + foreach ($cfgfiles as $file) { + $cfg[$type][$file] = false; + } + } + + if (!isset($argc, $argv) || !$argc) { + $argv = [__FILE__]; + $argc = 1; + } + + if (getenv('TEST_PHP_ARGS')) { + $argv = array_merge($argv, explode(' ', getenv('TEST_PHP_ARGS'))); + $argc = count($argv); + } + + for ($i = 1; $i < $argc; $i++) { + $is_switch = false; + $switch = substr($argv[$i], 1, 1); + $repeat = substr($argv[$i], 0, 1) == '-'; + + while ($repeat) { + if (!$is_switch) { + $switch = substr($argv[$i], 1, 1); + } + + $is_switch = true; + + if ($repeat) { + foreach ($cfgtypes as $type) { + if (strpos($switch, '--' . $type) === 0) { + foreach ($cfgfiles as $file) { + if ($switch == '--' . $type . '-' . $file) { + $cfg[$type][$file] = true; + $is_switch = false; + break; + } + } + } + } + } + + if (!$is_switch) { + $is_switch = true; + break; + } + + $repeat = false; + + switch ($switch) { + case 'j': + $workers = substr($argv[$i], 2); + if (!preg_match('/^\d+$/', $workers) || $workers == 0) { + error("'$workers' is not a valid number of workers, try e.g. -j16 for 16 workers"); + } + $workers = intval($workers, 10); + // Don't use parallel testing infrastructure if there is only one worker. + if ($workers === 1) { + $workers = null; + } + break; + case 'r': + case 'l': + $test_list = file($argv[++$i]); + if ($test_list) { + foreach ($test_list as $test) { + $matches = []; + if (preg_match('/^#.*\[(.*)\]\:\s+(.*)$/', $test, $matches)) { + $redir_tests[] = [$matches[1], $matches[2]]; + } else { + if (strlen($test)) { + $test_files[] = trim($test); + } + } + } + } + if ($switch != 'l') { + break; + } + $i--; + // no break + case 'w': + $failed_tests_file = fopen($argv[++$i], 'w+t'); + break; + case 'a': + $failed_tests_file = fopen($argv[++$i], 'a+t'); + break; + case 'W': + $result_tests_file = fopen($argv[++$i], 'w+t'); + break; + case 'c': + $conf_passed = $argv[++$i]; + break; + case 'd': + $ini_overwrites[] = $argv[++$i]; + break; + case 'g': + $SHOW_ONLY_GROUPS = explode(",", $argv[++$i]); + break; + //case 'h' + case '--keep-all': + foreach ($cfgfiles as $file) { + $cfg['keep'][$file] = true; + } + break; + //case 'l' + case 'm': + $valgrind = new RuntestsValgrind($environment); + break; + case 'M': + $valgrind = new RuntestsValgrind($environment, $argv[++$i]); + break; + case 'n': + if (!$pass_option_n) { + $pass_options .= ' -n'; + } + $pass_option_n = true; + break; + case 'e': + $pass_options .= ' -e'; + break; + case '--preload': + $preload = true; + break; + case '--file-cache-prime': + $file_cache = 'prime'; + break; + case '--file-cache-use': + $file_cache = 'use'; + break; + case '--no-clean': + $no_clean = true; + break; + case '--color': + $colorize = true; + break; + case '--no-color': + $colorize = false; + break; + case 'p': + $php = $argv[++$i]; + putenv("TEST_PHP_EXECUTABLE=$php"); + $environment['TEST_PHP_EXECUTABLE'] = $php; + break; + case 'P': + $php = PHP_BINARY; + putenv("TEST_PHP_EXECUTABLE=$php"); + $environment['TEST_PHP_EXECUTABLE'] = $php; + break; + case 'q': + putenv('NO_INTERACTION=1'); + $environment['NO_INTERACTION'] = 1; + break; + //case 'r' + case 's': + $output_file = $argv[++$i]; + $just_save_results = true; + break; + case '--set-timeout': + $environment['TEST_TIMEOUT'] = $argv[++$i]; + break; + case '--context': + $context_line_count = $argv[++$i] ?? ''; + if (!preg_match('/^\d+$/', $context_line_count)) { + error("'$context_line_count' is not a valid number of lines of context, try e.g. --context 3 for 3 lines"); + } + $context_line_count = intval($context_line_count, 10); + break; + case '--show-all': + foreach ($cfgfiles as $file) { + $cfg['show'][$file] = true; + } + break; + case '--show-slow': + $slow_min_ms = $argv[++$i]; + break; + case '--temp-source': + $temp_source = $argv[++$i]; + break; + case '--temp-target': + $temp_target = $argv[++$i]; + if ($temp_urlbase) { + $temp_urlbase = $temp_target; + } + break; + case '--temp-urlbase': + $temp_urlbase = $argv[++$i]; + break; + case 'v': + case '--verbose': + $DETAILED = true; + break; + case 'x': + $environment['SKIP_SLOW_TESTS'] = 1; + break; + case '--offline': + $environment['SKIP_ONLINE_TESTS'] = 1; + break; + case '--shuffle': + $shuffle = true; + break; + case '--asan': + $environment['USE_ZEND_ALLOC'] = 0; + $environment['USE_TRACKED_ALLOC'] = 1; + $environment['SKIP_ASAN'] = 1; + $environment['SKIP_PERF_SENSITIVE'] = 1; + + $lsanSuppressions = __DIR__ . '/azure/lsan-suppressions.txt'; + if (file_exists($lsanSuppressions)) { + $environment['LSAN_OPTIONS'] = 'suppressions=' . $lsanSuppressions + . ':print_suppressions=0'; + } + break; + //case 'w' + case '-': + // repeat check with full switch + $switch = $argv[$i]; + if ($switch != '-') { + $repeat = true; + } + break; + case '--version': + echo '$Id: af9f309bc6dac7ccd827ddf739fb71e6aa1945a6 $' . "\n"; + exit(1); + + default: + echo "Illegal switch '$switch' specified!\n"; + // no break + case 'h': + case '-help': + case '--help': + show_usage(); + exit(1); + } + } + + if (!$is_switch) { + $selected_tests = true; + $testfile = realpath($argv[$i]); + + if (!$testfile && strpos($argv[$i], '*') !== false && function_exists('glob')) { + if (substr($argv[$i], -5) == '.phpt') { + $pattern_match = glob($argv[$i]); + } else { + if (preg_match("/\*$/", $argv[$i])) { + $pattern_match = glob($argv[$i] . '.phpt'); + } else { + die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); + } + } + + if (is_array($pattern_match)) { + $test_files = array_merge($test_files, $pattern_match); + } + } else { + if (is_dir($testfile)) { + find_files($testfile); + } else { + if (substr($testfile, -5) == '.phpt') { + $test_files[] = $testfile; + } else { + die('Cannot find test file "' . $argv[$i] . '".' . PHP_EOL); + } + } + } + } + } + + if ($selected_tests && count($test_files) === 0) { + echo "No tests found.\n"; + return; + } + + // Default to PHP_BINARY as executable + if (!isset($environment['TEST_PHP_EXECUTABLE'])) { + $php = PHP_BINARY; + putenv("TEST_PHP_EXECUTABLE=$php"); + $environment['TEST_PHP_EXECUTABLE'] = $php; + } + + if ($conf_passed !== null) { + if (IS_WINDOWS) { + $pass_options .= " -c " . escapeshellarg($conf_passed); + } else { + $pass_options .= " -c '" . realpath($conf_passed) . "'"; + } + } + + $test_files = array_unique($test_files); + $test_files = array_merge($test_files, $redir_tests); + + // Run selected tests. + $test_cnt = count($test_files); + + verify_config(); + write_information(); + + if ($test_cnt) { + putenv('NO_INTERACTION=1'); + usort($test_files, "test_sort"); + $start_time = time(); + + echo "Running selected tests.\n"; + + $test_idx = 0; + run_all_tests($test_files, $environment); + $end_time = time(); + + if ($failed_tests_file) { + fclose($failed_tests_file); + } + + if ($result_tests_file) { + fclose($result_tests_file); + } + + if (0 == count($test_results)) { + echo "No tests were run.\n"; + return; + } + + compute_summary(); + echo "====================================================================="; + echo get_summary(false); + + if ($output_file != '' && $just_save_results) { + save_or_mail_results(); + } + } else { + // Compile a list of all test files (*.phpt). + $test_files = []; + $exts_tested = count($exts_to_test); + $exts_skipped = 0; + $ignored_by_ext = 0; + sort($exts_to_test); + $test_dirs = []; + $optionals = ['Zend', 'tests', 'ext', 'sapi']; + + foreach ($optionals as $dir) { + if (is_dir($dir)) { + $test_dirs[] = $dir; + } + } + + // Convert extension names to lowercase + foreach ($exts_to_test as $key => $val) { + $exts_to_test[$key] = strtolower($val); + } + + foreach ($test_dirs as $dir) { + find_files(TEST_PHP_SRCDIR . "/{$dir}", $dir == 'ext'); + } + + foreach ($user_tests as $dir) { + find_files($dir, $dir == 'ext'); + } + + $test_files = array_unique($test_files); + usort($test_files, "test_sort"); + + $start_time = time(); + show_start($start_time); + + $test_cnt = count($test_files); + $test_idx = 0; + run_all_tests($test_files, $environment); + $end_time = time(); + + if ($failed_tests_file) { + fclose($failed_tests_file); + } + + if ($result_tests_file) { + fclose($result_tests_file); + } + + // Summarize results + + if (0 == count($test_results)) { + echo "No tests were run.\n"; + return; + } + + compute_summary(); + + show_end($end_time); + show_summary(); + + save_or_mail_results(); + } + + junit_save_xml(); + if (getenv('REPORT_EXIT_STATUS') !== '0' && getenv('REPORT_EXIT_STATUS') !== 'no' && + ($sum_results['FAILED'] || $sum_results['LEAKED'])) { + exit(1); + } +} + +if (!function_exists("hrtime")) { + /** + * @return array|float|int + */ + function hrtime(bool $as_num = false) + { + $t = microtime(true); + + if ($as_num) { + return $t * 1000000000; + } + + $s = floor($t); + return [0 => $s, 1 => ($t - $s) * 1000000000]; + } +} + +function verify_config() +{ + global $php; + + if (empty($php) || !file_exists($php)) { + error('environment variable TEST_PHP_EXECUTABLE must be set to specify PHP executable!'); + } + + if (!is_executable($php)) { + error("invalid PHP executable specified by TEST_PHP_EXECUTABLE = $php"); + } +} + +function write_information() +{ + global $php, $php_cgi, $phpdbg, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $valgrind, $no_file_cache; + + // Get info from php + $info_file = __DIR__ . '/run-test-info.php'; + @unlink($info_file); + $php_info = ''; + save_text($info_file, $php_info); + $info_params = []; + settings2array($ini_overwrites, $info_params); + $info_params = settings2params($info_params); + $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`; + define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`); + + if ($php_cgi && $php != $php_cgi) { + $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`; + $php_info_sep = "\n---------------------------------------------------------------------"; + $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; + } else { + $php_cgi_info = ''; + } + + if ($phpdbg) { + $phpdbg_info = `$phpdbg $pass_options $info_params $no_file_cache -qrr "$info_file"`; + $php_info_sep = "\n---------------------------------------------------------------------"; + $phpdbg_info = "$php_info_sep\nPHP : $phpdbg $phpdbg_info$php_info_sep"; + } else { + $phpdbg_info = ''; + } + + if (function_exists('opcache_invalidate')) { + opcache_invalidate($info_file, true); + } + @unlink($info_file); + + // load list of enabled extensions + save_text($info_file, + ''); + $exts_to_test = explode(',', `$php $pass_options $info_params $no_file_cache "$info_file"`); + // check for extensions that need special handling and regenerate + $info_params_ex = [ + 'session' => ['session.auto_start=0'], + 'tidy' => ['tidy.clean_output=0'], + 'zlib' => ['zlib.output_compression=Off'], + 'xdebug' => ['xdebug.default_enable=0','xdebug.mode=off'], + 'mbstring' => ['mbstring.func_overload=0'], + ]; + + foreach ($info_params_ex as $ext => $ini_overwrites_ex) { + if (in_array($ext, $exts_to_test)) { + $ini_overwrites = array_merge($ini_overwrites, $ini_overwrites_ex); + } + } + + if (function_exists('opcache_invalidate')) { + opcache_invalidate($info_file, true); + } + @unlink($info_file); + + // Write test context information. + echo " +===================================================================== +PHP : $php $php_info $php_cgi_info $phpdbg_info +CWD : " . TEST_PHP_SRCDIR . " +Extra dirs : "; + foreach ($user_tests as $test_dir) { + echo "{$test_dir}\n "; + } + echo " +VALGRIND : " . ($valgrind ? $valgrind->getHeader() : 'Not used') . " +===================================================================== +"; +} + +function save_or_mail_results() +{ + global $sum_results, $just_save_results, $failed_test_summary, + $PHP_FAILED_TESTS, $php, $output_file; + + /* We got failed Tests, offer the user to send an e-mail to QA team, unless NO_INTERACTION is set */ + if (!getenv('NO_INTERACTION') && !TRAVIS_CI) { + $fp = fopen("php://stdin", "r+"); + if ($sum_results['FAILED'] || $sum_results['BORKED'] || $sum_results['WARNED'] || $sum_results['LEAKED']) { + echo "\nYou may have found a problem in PHP."; + } + echo "\nThis report can be automatically sent to the PHP QA team at\n"; + echo QA_REPORTS_PAGE . " and http://news.php.net/php.qa.reports\n"; + echo "This gives us a better understanding of PHP's behavior.\n"; + echo "If you don't want to send the report immediately you can choose\n"; + echo "option \"s\" to save it. You can then email it to " . PHP_QA_EMAIL . " later.\n"; + echo "Do you want to send this report now? [Yns]: "; + flush(); + + $user_input = fgets($fp, 10); + $just_save_results = (strtolower($user_input[0]) == 's'); + } + + if ($just_save_results || !getenv('NO_INTERACTION') || TRAVIS_CI) { + if ($just_save_results || TRAVIS_CI || strlen(trim($user_input)) == 0 || strtolower($user_input[0]) == 'y') { + /* + * Collect information about the host system for our report + * Fetch phpinfo() output so that we can see the PHP environment + * Make an archive of all the failed tests + * Send an email + */ + if ($just_save_results) { + $user_input = 's'; + } + + /* Ask the user to provide an email address, so that QA team can contact the user */ + if (TRAVIS_CI) { + $user_email = 'travis at php dot net'; + } elseif (!strncasecmp($user_input, 'y', 1) || strlen(trim($user_input)) == 0) { + echo "\nPlease enter your email address.\n(Your address will be mangled so that it will not go out on any\nmailinglist in plain text): "; + flush(); + $user_email = trim(fgets($fp, 1024)); + $user_email = str_replace("@", " at ", str_replace(".", " dot ", $user_email)); + } + + $failed_tests_data = ''; + $sep = "\n" . str_repeat('=', 80) . "\n"; + $failed_tests_data .= $failed_test_summary . "\n"; + $failed_tests_data .= get_summary(true) . "\n"; + + if ($sum_results['FAILED']) { + foreach ($PHP_FAILED_TESTS['FAILED'] as $test_info) { + $failed_tests_data .= $sep . $test_info['name'] . $test_info['info']; + $failed_tests_data .= $sep . file_get_contents(realpath($test_info['output'])); + $failed_tests_data .= $sep . file_get_contents(realpath($test_info['diff'])); + $failed_tests_data .= $sep . "\n\n"; + } + $status = "failed"; + } else { + $status = "success"; + } + + $failed_tests_data .= "\n" . $sep . 'BUILD ENVIRONMENT' . $sep; + $failed_tests_data .= "OS:\n" . PHP_OS . " - " . php_uname() . "\n\n"; + $ldd = $autoconf = $sys_libtool = $libtool = $compiler = 'N/A'; + + if (!IS_WINDOWS) { + /* If PHP_AUTOCONF is set, use it; otherwise, use 'autoconf'. */ + if (getenv('PHP_AUTOCONF')) { + $autoconf = shell_exec(getenv('PHP_AUTOCONF') . ' --version'); + } else { + $autoconf = shell_exec('autoconf --version'); + } + + /* Always use the generated libtool - Mac OSX uses 'glibtool' */ + $libtool = shell_exec(INIT_DIR . '/libtool --version'); + + /* Use shtool to find out if there is glibtool present (MacOSX) */ + $sys_libtool_path = shell_exec(__DIR__ . '/build/shtool path glibtool libtool'); + + if ($sys_libtool_path) { + $sys_libtool = shell_exec(str_replace("\n", "", $sys_libtool_path) . ' --version'); + } + + /* Try the most common flags for 'version' */ + $flags = ['-v', '-V', '--version']; + $cc_status = 0; + + foreach ($flags as $flag) { + system(getenv('CC') . " $flag >/dev/null 2>&1", $cc_status); + if ($cc_status == 0) { + $compiler = shell_exec(getenv('CC') . " $flag 2>&1"); + break; + } + } + + $ldd = shell_exec("ldd $php 2>/dev/null"); + } + + $failed_tests_data .= "Autoconf:\n$autoconf\n"; + $failed_tests_data .= "Bundled Libtool:\n$libtool\n"; + $failed_tests_data .= "System Libtool:\n$sys_libtool\n"; + $failed_tests_data .= "Compiler:\n$compiler\n"; + $failed_tests_data .= "Bison:\n" . shell_exec('bison --version 2>/dev/null') . "\n"; + $failed_tests_data .= "Libraries:\n$ldd\n"; + $failed_tests_data .= "\n"; + + if (isset($user_email)) { + $failed_tests_data .= "User's E-mail: " . $user_email . "\n\n"; + } + + $failed_tests_data .= $sep . "PHPINFO" . $sep; + $failed_tests_data .= shell_exec($php . ' -ddisplay_errors=stderr -dhtml_errors=0 -i 2> /dev/null'); + + if (($just_save_results || !mail_qa_team($failed_tests_data, $status)) && !TRAVIS_CI) { + file_put_contents($output_file, $failed_tests_data); + + if (!$just_save_results) { + echo "\nThe test script was unable to automatically send the report to PHP's QA Team\n"; + } + + echo "Please send " . $output_file . " to " . PHP_QA_EMAIL . " manually, thank you.\n"; + } elseif (!getenv('NO_INTERACTION') && !TRAVIS_CI) { + fwrite($fp, "\nThank you for helping to make PHP better.\n"); + fclose($fp); + } + } + } +} + +function find_files(string $dir, bool $is_ext_dir = false, bool $ignore = false) +{ + global $test_files, $exts_to_test, $ignored_by_ext, $exts_skipped; + + $o = opendir($dir) or error("cannot open directory: $dir"); + + while (($name = readdir($o)) !== false) { + if (is_dir("{$dir}/{$name}") && !in_array($name, ['.', '..', '.svn'])) { + $skip_ext = ($is_ext_dir && !in_array(strtolower($name), $exts_to_test)); + if ($skip_ext) { + $exts_skipped++; + } + find_files("{$dir}/{$name}", false, $ignore || $skip_ext); + } + + // Cleanup any left-over tmp files from last run. + if (substr($name, -4) == '.tmp') { + @unlink("$dir/$name"); + continue; + } + + // Otherwise we're only interested in *.phpt files. + if (substr($name, -5) == '.phpt') { + if ($ignore) { + $ignored_by_ext++; + } else { + $testfile = realpath("{$dir}/{$name}"); + $test_files[] = $testfile; + } + } + } + + closedir($o); +} + +/** + * @param array|string $name + */ +function test_name($name): string +{ + if (is_array($name)) { + return $name[0] . ':' . $name[1]; + } else { + return $name; + } +} +/** + * @param array|string $a + * @param array|string $b + */ +function test_sort($a, $b): int +{ + $a = test_name($a); + $b = test_name($b); + + $ta = strpos($a, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($a, + TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0; + $tb = strpos($b, TEST_PHP_SRCDIR . "/tests") === 0 ? 1 + (strpos($b, + TEST_PHP_SRCDIR . "/tests/run-test") === 0 ? 1 : 0) : 0; + + if ($ta == $tb) { + return strcmp($a, $b); + } else { + return $tb - $ta; + } +} + +// +// Send Email to QA Team +// + +function mail_qa_team(string $data, bool $status = false): bool +{ + $url_bits = parse_url(QA_SUBMISSION_PAGE); + + if ($proxy = getenv('http_proxy')) { + $proxy = parse_url($proxy); + $path = $url_bits['host'] . $url_bits['path']; + $host = $proxy['host']; + if (empty($proxy['port'])) { + $proxy['port'] = 80; + } + $port = $proxy['port']; + } else { + $path = $url_bits['path']; + $host = $url_bits['host']; + $port = empty($url_bits['port']) ? 80 : $port = $url_bits['port']; + } + + $data = "php_test_data=" . urlencode(base64_encode(str_replace("\00", '[0x0]', $data))); + $data_length = strlen($data); + + $fs = fsockopen($host, $port, $errno, $errstr, 10); + + if (!$fs) { + return false; + } + + $php_version = urlencode(TESTED_PHP_VERSION); + + echo "\nPosting to " . QA_SUBMISSION_PAGE . "\n"; + fwrite($fs, "POST " . $path . "?status=$status&version=$php_version HTTP/1.1\r\n"); + fwrite($fs, "Host: " . $host . "\r\n"); + fwrite($fs, "User-Agent: QA Browser 0.1\r\n"); + fwrite($fs, "Content-Type: application/x-www-form-urlencoded\r\n"); + fwrite($fs, "Content-Length: " . $data_length . "\r\n\r\n"); + fwrite($fs, $data); + fwrite($fs, "\r\n\r\n"); + fclose($fs); + + return true; +} + +// +// Write the given text to a temporary file, and return the filename. +// + +function save_text(string $filename, string $text, string $filename_copy = null) +{ + global $DETAILED; + + if ($filename_copy && $filename_copy != $filename) { + if (file_put_contents($filename_copy, $text) === false) { + error("Cannot open file '" . $filename_copy . "' (save_text)"); + } + } + + if (file_put_contents($filename, $text) === false) { + error("Cannot open file '" . $filename . "' (save_text)"); + } + + if (1 < $DETAILED) { + echo " +FILE $filename {{{ +$text +}}} +"; + } +} + +// +// Write an error in a format recognizable to Emacs or MSVC. +// + +function error_report(string $testname, string $logname, string $tested) +{ + $testname = realpath($testname); + $logname = realpath($logname); + + switch (strtoupper(getenv('TEST_PHP_ERROR_STYLE'))) { + case 'MSVC': + echo $testname . "(1) : $tested\n"; + echo $logname . "(1) : $tested\n"; + break; + case 'EMACS': + echo $testname . ":1: $tested\n"; + echo $logname . ":1: $tested\n"; + break; + } +} + +/** + * @return false|string + */ +function system_with_timeout( + string $commandline, + array $env = null, + string $stdin = null, + bool $captureStdIn = true, + bool $captureStdOut = true, + bool $captureStdErr = true +) { + global $valgrind; + + $data = ''; + + $bin_env = []; + foreach ((array) $env as $key => $value) { + $bin_env[$key] = $value; + } + + $descriptorspec = []; + if ($captureStdIn) { + $descriptorspec[0] = ['pipe', 'r']; + } + if ($captureStdOut) { + $descriptorspec[1] = ['pipe', 'w']; + } + if ($captureStdErr) { + $descriptorspec[2] = ['pipe', 'w']; + } + $proc = proc_open($commandline, $descriptorspec, $pipes, TEST_PHP_SRCDIR, $bin_env, ['suppress_errors' => true]); + + if (!$proc) { + return false; + } + + if ($captureStdIn) { + if (!is_null($stdin)) { + fwrite($pipes[0], $stdin); + } + fclose($pipes[0]); + unset($pipes[0]); + } + + $timeout = $valgrind ? 300 : ($env['TEST_TIMEOUT'] ?? 60); + + while (true) { + /* hide errors from interrupted syscalls */ + $r = $pipes; + $w = null; + $e = null; + + $n = @stream_select($r, $w, $e, $timeout); + + if ($n === false) { + break; + } elseif ($n === 0) { + /* timed out */ + $data .= "\n ** ERROR: process timed out **\n"; + proc_terminate($proc, 9); + return $data; + } elseif ($n > 0) { + if ($captureStdOut) { + $line = fread($pipes[1], 8192); + } elseif ($captureStdErr) { + $line = fread($pipes[2], 8192); + } else { + $line = ''; + } + if (strlen($line) == 0) { + /* EOF */ + break; + } + $data .= $line; + } + } + + $stat = proc_get_status($proc); + + if ($stat['signaled']) { + $data .= "\nTermsig=" . $stat['stopsig'] . "\n"; + } + if ($stat["exitcode"] > 128 && $stat["exitcode"] < 160) { + $data .= "\nTermsig=" . ($stat["exitcode"] - 128) . "\n"; + } + + proc_close($proc); + return $data; +} + +/** + * @param string|array|null $redir_tested + */ +function run_all_tests(array $test_files, array $env, $redir_tested = null) +{ + global $test_results, $failed_tests_file, $result_tests_file, $php, $test_idx, $file_cache; + // Parallel testing + global $PHP_FAILED_TESTS, $workers, $workerID, $workerSock; + + if ($file_cache !== null) { + /* Automatically skip opcache tests in --file-cache mode, + * because opcache generally doesn't expect those to run under file cache */ + $test_files = array_filter($test_files, function($test) { + return !is_string($test) || false === strpos($test, 'ext/opcache'); + }); + } + + /* Ignore -jN if there is only one file to analyze. */ + if ($workers !== null && count($test_files) > 1 && !$workerID) { + run_all_tests_parallel($test_files, $env, $redir_tested); + return; + } + + foreach ($test_files as $name) { + if (is_array($name)) { + $index = "# $name[1]: $name[0]"; + + if ($redir_tested) { + $name = $name[0]; + } + } elseif ($redir_tested) { + $index = "# $redir_tested: $name"; + } else { + $index = $name; + } + $test_idx++; + + if ($workerID) { + $PHP_FAILED_TESTS = ['BORKED' => [], 'FAILED' => [], 'WARNED' => [], 'LEAKED' => [], 'XFAILED' => [], 'XLEAKED' => [], 'SLOW' => []]; + ob_start(); + } + + $result = run_test($php, $name, $env); + if ($workerID) { + $resultText = ob_get_clean(); + } + + if (!is_array($name) && $result != 'REDIR') { + if ($workerID) { + send_message($workerSock, [ + "type" => "test_result", + "name" => $name, + "index" => $index, + "result" => $result, + "text" => $resultText, + "PHP_FAILED_TESTS" => $PHP_FAILED_TESTS + ]); + continue; + } + + $test_results[$index] = $result; + if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { + fwrite($failed_tests_file, "$index\n"); + } + if ($result_tests_file) { + fwrite($result_tests_file, "$result\t$index\n"); + } + } + } +} + +/** The heart of parallel testing. + * @param string|array|null $redir_tested + */ +function run_all_tests_parallel(array $test_files, array $env, $redir_tested) +{ + global $workers, $test_idx, $test_cnt, $test_results, $failed_tests_file, $result_tests_file, $PHP_FAILED_TESTS, $shuffle, $SHOW_ONLY_GROUPS, $valgrind; + + // The PHP binary running run-tests.php, and run-tests.php itself + // This PHP executable is *not* necessarily the same as the tested version + $thisPHP = PHP_BINARY; + $thisScript = __FILE__; + + $workerProcs = []; + $workerSocks = []; + + echo "=====================================================================\n"; + echo "========= WELCOME TO THE FUTURE: run-tests PARALLEL EDITION =========\n"; + echo "=====================================================================\n"; + + // Each test may specify a list of conflict keys. While a test that conflicts with + // key K is running, no other test that conflicts with K may run. Conflict keys are + // specified either in the --CONFLICTS-- section, or CONFLICTS file inside a directory. + $dirConflictsWith = []; + $fileConflictsWith = []; + $sequentialTests = []; + foreach ($test_files as $i => $file) { + $contents = file_get_contents($file); + if (preg_match('/^--CONFLICTS--(.+?)^--/ms', $contents, $matches)) { + $conflicts = parse_conflicts($matches[1]); + } else { + // Cache per-directory conflicts in a separate map, so we compute these only once. + $dir = dirname($file); + if (!isset($dirConflictsWith[$dir])) { + $dirConflicts = []; + if (file_exists($dir . '/CONFLICTS')) { + $contents = file_get_contents($dir . '/CONFLICTS'); + $dirConflicts = parse_conflicts($contents); + } + $dirConflictsWith[$dir] = $dirConflicts; + } + $conflicts = $dirConflictsWith[$dir]; + } + + // For tests conflicting with "all", no other tests may run in parallel. We'll run these + // tests separately at the end, when only one worker is left. + if (in_array('all', $conflicts, true)) { + $sequentialTests[] = $file; + unset($test_files[$i]); + } + + $fileConflictsWith[$file] = $conflicts; + } + + // Some tests assume that they are executed in a certain order. We will be popping from + // $test_files, so reverse its order here. This makes sure that order is preserved at least + // for tests with a common conflict key. + $test_files = array_reverse($test_files); + + // To discover parallelization issues it is useful to randomize the test order. + if ($shuffle) { + shuffle($test_files); + } + + // Don't start more workers than test files. + $workers = max(1, min($workers, count($test_files))); + + echo "Spawning $workers workers... "; + + // We use sockets rather than STDIN/STDOUT for comms because on Windows, + // those can't be non-blocking for some reason. + $listenSock = stream_socket_server("tcp://127.0.0.1:0") or error("Couldn't create socket on localhost."); + $sockName = stream_socket_get_name($listenSock, false); + // PHP is terrible and returns IPv6 addresses not enclosed by [] + $portPos = strrpos($sockName, ":"); + $sockHost = substr($sockName, 0, $portPos); + if (false !== strpos($sockHost, ":")) { + $sockHost = "[$sockHost]"; + } + $sockPort = substr($sockName, $portPos + 1); + $sockUri = "tcp://$sockHost:$sockPort"; + $totalFileCount = count($test_files); + + $startTime = microtime(true); + for ($i = 1; $i <= $workers; $i++) { + $proc = proc_open( + $thisPHP . ' ' . escapeshellarg($thisScript), + [], // Inherit our stdin, stdout and stderr + $pipes, + null, + $_ENV + [ + "TEST_PHP_WORKER" => $i, + "TEST_PHP_URI" => $sockUri, + ], + [ + "suppress_errors" => true, + 'create_new_console' => true, + ] + ); + if ($proc === false) { + kill_children($workerProcs); + error("Failed to spawn worker $i"); + } + $workerProcs[$i] = $proc; + } + + for ($i = 1; $i <= $workers; $i++) { + $workerSock = stream_socket_accept($listenSock, 5); + if ($workerSock === false) { + kill_children($workerProcs); + error("Failed to accept connection from worker."); + } + + $greeting = base64_encode(serialize([ + "type" => "hello", + "GLOBALS" => $GLOBALS, + "constants" => [ + "INIT_DIR" => INIT_DIR, + "TEST_PHP_SRCDIR" => TEST_PHP_SRCDIR, + "PHP_QA_EMAIL" => PHP_QA_EMAIL, + "QA_SUBMISSION_PAGE" => QA_SUBMISSION_PAGE, + "QA_REPORTS_PAGE" => QA_REPORTS_PAGE, + "TRAVIS_CI" => TRAVIS_CI + ] + ])) . "\n"; + + stream_set_timeout($workerSock, 5); + if (fwrite($workerSock, $greeting) === false) { + kill_children($workerProcs); + error("Failed to send greeting to worker."); + } + + $rawReply = fgets($workerSock); + if ($rawReply === false) { + kill_children($workerProcs); + error("Failed to read greeting reply from worker."); + } + + $reply = unserialize(base64_decode($rawReply)); + if (!$reply || $reply["type"] !== "hello_reply") { + kill_children($workerProcs); + error("Greeting reply from worker unexpected or could not be decoded: '$rawReply'"); + } + + stream_set_timeout($workerSock, 0); + stream_set_blocking($workerSock, false); + + $workerID = $reply["workerID"]; + $workerSocks[$workerID] = $workerSock; + } + printf("Done in %.2fs\n", microtime(true) - $startTime); + echo "=====================================================================\n"; + echo "\n"; + + $rawMessageBuffers = []; + $testsInProgress = 0; + + // Map from conflict key to worker ID. + $activeConflicts = []; + // Tests waiting due to conflicts. Map from conflict key to array. + $waitingTests = []; + +escape: + while ($test_files || $sequentialTests || $testsInProgress > 0) { + $toRead = array_values($workerSocks); + $toWrite = null; + $toExcept = null; + if (stream_select($toRead, $toWrite, $toExcept, 10)) { + foreach ($toRead as $workerSock) { + $i = array_search($workerSock, $workerSocks); + if ($i === false) { + kill_children($workerProcs); + error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN."); + } + while (false !== ($rawMessage = fgets($workerSock))) { + // work around fgets truncating things + if (($rawMessageBuffers[$i] ?? '') !== '') { + $rawMessage = $rawMessageBuffers[$i] . $rawMessage; + $rawMessageBuffers[$i] = ''; + } + if (substr($rawMessage, -1) !== "\n") { + $rawMessageBuffers[$i] = $rawMessage; + continue; + } + + $message = unserialize(base64_decode($rawMessage)); + if (!$message) { + kill_children($workerProcs); + $stuff = fread($workerSock, 65536); + error("Could not decode message from worker $i: '$rawMessage$stuff'"); + } + + switch ($message["type"]) { + case "tests_finished": + $testsInProgress--; + foreach ($activeConflicts as $key => $workerId) { + if ($workerId === $i) { + unset($activeConflicts[$key]); + if (isset($waitingTests[$key])) { + while ($test = array_pop($waitingTests[$key])) { + $test_files[] = $test; + } + unset($waitingTests[$key]); + } + } + } + if (junit_enabled()) { + junit_merge_results($message["junit"]); + } + // no break + case "ready": + // Schedule sequential tests only once we are down to one worker. + if (count($workerProcs) === 1 && $sequentialTests) { + $test_files = array_merge($test_files, $sequentialTests); + $sequentialTests = []; + } + // Batch multiple tests to reduce communication overhead. + // - When valgrind is used, communication overhead is relatively small, + // so just use a batch size of 1. + // - If this is running a small enough number of tests, + // reduce the batch size to give batches to more workers. + $files = []; + $maxBatchSize = $valgrind ? 1 : ($shuffle ? 4 : 32); + $averageFilesPerWorker = max(1, (int) ceil($totalFileCount / count($workerProcs))); + $batchSize = min($maxBatchSize, $averageFilesPerWorker); + while (count($files) <= $batchSize && $file = array_pop($test_files)) { + foreach ($fileConflictsWith[$file] as $conflictKey) { + if (isset($activeConflicts[$conflictKey])) { + $waitingTests[$conflictKey][] = $file; + continue 2; + } + } + $files[] = $file; + } + if ($files) { + foreach ($files as $file) { + foreach ($fileConflictsWith[$file] as $conflictKey) { + $activeConflicts[$conflictKey] = $i; + } + } + $testsInProgress++; + send_message($workerSocks[$i], [ + "type" => "run_tests", + "test_files" => $files, + "env" => $env, + "redir_tested" => $redir_tested + ]); + } else { + proc_terminate($workerProcs[$i]); + unset($workerProcs[$i]); + unset($workerSocks[$i]); + goto escape; + } + break; + case "test_result": + list($name, $index, $result, $resultText) = [$message["name"], $message["index"], $message["result"], $message["text"]]; + foreach ($message["PHP_FAILED_TESTS"] as $category => $tests) { + $PHP_FAILED_TESTS[$category] = array_merge($PHP_FAILED_TESTS[$category], $tests); + } + $test_idx++; + + if (!$SHOW_ONLY_GROUPS) { + clear_show_test(); + } + + echo $resultText; + + if (!$SHOW_ONLY_GROUPS) { + show_test($test_idx, count($workerProcs) . "/$workers concurrent test workers running"); + } + + if (!is_array($name) && $result != 'REDIR') { + $test_results[$index] = $result; + + if ($failed_tests_file && ($result == 'XFAILED' || $result == 'XLEAKED' || $result == 'FAILED' || $result == 'WARNED' || $result == 'LEAKED')) { + fwrite($failed_tests_file, "$index\n"); + } + if ($result_tests_file) { + fwrite($result_tests_file, "$result\t$index\n"); + } + } + break; + case "error": + kill_children($workerProcs); + error("Worker $i reported error: $message[msg]"); + break; + case "php_error": + kill_children($workerProcs); + $error_consts = [ + 'E_ERROR', + 'E_WARNING', + 'E_PARSE', + 'E_NOTICE', + 'E_CORE_ERROR', + 'E_CORE_WARNING', + 'E_COMPILE_ERROR', + 'E_COMPILE_WARNING', + 'E_USER_ERROR', + 'E_USER_WARNING', + 'E_USER_NOTICE', + 'E_STRICT', // TODO Cleanup when removed from Zend Engine. + 'E_RECOVERABLE_ERROR', + 'E_USER_DEPRECATED' + ]; + $error_consts = array_combine(array_map('constant', $error_consts), $error_consts); + error("Worker $i reported unexpected {$error_consts[$message['errno']]}: $message[errstr] in $message[errfile] on line $message[errline]"); + // no break + default: + kill_children($workerProcs); + error("Unrecognised message type '$message[type]' from worker $i"); + } + } + } + } + } + + if (!$SHOW_ONLY_GROUPS) { + clear_show_test(); + } + + kill_children($workerProcs); + + if ($testsInProgress < 0) { + error("$testsInProgress test batches “in progress”, which is less than zero. THIS SHOULD NOT HAPPEN."); + } +} + +function send_message($stream, array $message) +{ + $blocking = stream_get_meta_data($stream)["blocked"]; + stream_set_blocking($stream, true); + fwrite($stream, base64_encode(serialize($message)) . "\n"); + stream_set_blocking($stream, $blocking); +} + +function kill_children(array $children) +{ + foreach ($children as $child) { + if ($child) { + proc_terminate($child); + } + } +} + +function run_worker() +{ + global $workerID, $workerSock; + + $sockUri = getenv("TEST_PHP_URI"); + + $workerSock = stream_socket_client($sockUri, $_, $_, 5) or error("Couldn't connect to $sockUri"); + + $greeting = fgets($workerSock); + $greeting = unserialize(base64_decode($greeting)) or die("Could not decode greeting\n"); + if ($greeting["type"] !== "hello") { + error("Unexpected greeting of type $greeting[type]"); + } + + set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) use ($workerSock): bool { + if (error_reporting() & $errno) { + send_message($workerSock, compact('errno', 'errstr', 'errfile', 'errline') + [ + 'type' => 'php_error' + ]); + } + + return true; + }); + + foreach ($greeting["GLOBALS"] as $var => $value) { + if ($var !== "workerID" && $var !== "workerSock" && $var !== "GLOBALS") { + $GLOBALS[$var] = $value; + } + } + foreach ($greeting["constants"] as $const => $value) { + define($const, $value); + } + + send_message($workerSock, [ + "type" => "hello_reply", + "workerID" => $workerID + ]); + + send_message($workerSock, [ + "type" => "ready" + ]); + + while (($command = fgets($workerSock))) { + $command = unserialize(base64_decode($command)); + + switch ($command["type"]) { + case "run_tests": + run_all_tests($command["test_files"], $command["env"], $command["redir_tested"]); + send_message($workerSock, [ + "type" => "tests_finished", + "junit" => junit_enabled() ? $GLOBALS['JUNIT'] : null, + ]); + junit_init(); + break; + default: + send_message($workerSock, [ + "type" => "error", + "msg" => "Unrecognised message type: $command[type]" + ]); + break 2; + } + } +} + +// +// Show file or result block +// +function show_file_block(string $file, string $block, string $section = null) +{ + global $cfg; + global $colorize; + + if ($cfg['show'][$file]) { + if (is_null($section)) { + $section = strtoupper($file); + } + if ($section === 'DIFF' && $colorize) { + // '-' is Light Red for removal, '+' is Light Green for addition + $block = preg_replace('/^[0-9]+\-\s.*$/m', "\e[1;31m\\0\e[0m", $block); + $block = preg_replace('/^[0-9]+\+\s.*$/m', "\e[1;32m\\0\e[0m", $block); + } + + echo "\n========" . $section . "========\n"; + echo rtrim($block); + echo "\n========DONE========\n"; + } +} + +// +// Run an individual test case. +// +/** + * @param string|array $file + */ +function run_test(string $php, $file, array $env): string +{ + global $log_format, $ini_overwrites, $PHP_FAILED_TESTS; + global $pass_options, $DETAILED, $IN_REDIRECT, $test_cnt, $test_idx; + global $valgrind, $temp_source, $temp_target, $cfg, $environment; + global $no_clean; + global $SHOW_ONLY_GROUPS; + global $no_file_cache; + global $slow_min_ms; + global $preload, $file_cache; + // Parallel testing + global $workerID; + $temp_filenames = null; + $org_file = $file; + + if (isset($env['TEST_PHP_CGI_EXECUTABLE'])) { + $php_cgi = $env['TEST_PHP_CGI_EXECUTABLE']; + } + + if (isset($env['TEST_PHPDBG_EXECUTABLE'])) { + $phpdbg = $env['TEST_PHPDBG_EXECUTABLE']; + } + + if (is_array($file)) { + $file = $file[0]; + } + + if ($DETAILED) { + echo " +================= +TEST $file +"; + } + + // Load the sections of the test file. + $section_text = ['TEST' => '']; + + $fp = fopen($file, "rb") or error("Cannot open test file: $file"); + + $bork_info = null; + + if (!feof($fp)) { + $line = fgets($fp); + + if ($line === false) { + $bork_info = "cannot read test"; + } + } else { + $bork_info = "empty test [$file]"; + } + if ($bork_info === null && strncmp('--TEST--', $line, 8)) { + $bork_info = "tests must start with --TEST-- [$file]"; + } + + $section = 'TEST'; + $secfile = false; + $secdone = false; + + while (!feof($fp)) { + $line = fgets($fp); + + if ($line === false) { + break; + } + + // Match the beginning of a section. + if (preg_match('/^--([_A-Z]+)--/', $line, $r)) { + $section = (string) $r[1]; + + if (isset($section_text[$section]) && $section_text[$section]) { + $bork_info = "duplicated $section section"; + } + + // check for unknown sections + if (!in_array($section, [ + 'EXPECT', 'EXPECTF', 'EXPECTREGEX', 'EXPECTREGEX_EXTERNAL', 'EXPECT_EXTERNAL', 'EXPECTF_EXTERNAL', 'EXPECTHEADERS', + 'POST', 'POST_RAW', 'GZIP_POST', 'DEFLATE_POST', 'PUT', 'GET', 'COOKIE', 'ARGS', + 'FILE', 'FILEEOF', 'FILE_EXTERNAL', 'REDIRECTTEST', + 'CAPTURE_STDIO', 'STDIN', 'CGI', 'PHPDBG', + 'INI', 'ENV', 'EXTENSIONS', + 'SKIPIF', 'XFAIL', 'XLEAK', 'CLEAN', + 'CREDITS', 'DESCRIPTION', 'CONFLICTS', 'WHITESPACE_SENSITIVE', + ])) { + $bork_info = 'Unknown section "' . $section . '"'; + } + + $section_text[$section] = ''; + $secfile = $section == 'FILE' || $section == 'FILEEOF' || $section == 'FILE_EXTERNAL'; + $secdone = false; + continue; + } + + // Add to the section text. + if (!$secdone) { + $section_text[$section] .= $line; + } + + // End of actual test? + if ($secfile && preg_match('/^===DONE===\s*$/', $line)) { + $secdone = true; + } + } + + // the redirect section allows a set of tests to be reused outside of + // a given test dir + if ($bork_info === null) { + if (isset($section_text['REDIRECTTEST'])) { + if ($IN_REDIRECT) { + $bork_info = "Can't redirect a test from within a redirected test"; + } + } else { + if (!isset($section_text['PHPDBG']) && isset($section_text['FILE']) + isset($section_text['FILEEOF']) + isset($section_text['FILE_EXTERNAL']) != 1) { + $bork_info = "missing section --FILE--"; + } + + if (isset($section_text['FILEEOF'])) { + $section_text['FILE'] = preg_replace("/[\r\n]+$/", '', $section_text['FILEEOF']); + unset($section_text['FILEEOF']); + } + + foreach (['FILE', 'EXPECT', 'EXPECTF', 'EXPECTREGEX'] as $prefix) { + $key = $prefix . '_EXTERNAL'; + + if (isset($section_text[$key])) { + // don't allow tests to retrieve files from anywhere but this subdirectory + $section_text[$key] = dirname($file) . '/' . trim(str_replace('..', '', $section_text[$key])); + + if (file_exists($section_text[$key])) { + $section_text[$prefix] = file_get_contents($section_text[$key]); + unset($section_text[$key]); + } else { + $bork_info = "could not load --" . $key . "-- " . dirname($file) . '/' . trim($section_text[$key]); + } + } + } + + if ((isset($section_text['EXPECT']) + isset($section_text['EXPECTF']) + isset($section_text['EXPECTREGEX'])) != 1) { + $bork_info = "missing section --EXPECT--, --EXPECTF-- or --EXPECTREGEX--"; + } + } + } + fclose($fp); + + $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); + $tested_file = $shortname; + + if ($bork_info !== null) { + show_result("BORK", $bork_info, $tested_file); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$bork_info [$file]", + ]; + + junit_mark_test_as('BORK', $shortname, $tested_file, 0, $bork_info); + return 'BORKED'; + } + + if (isset($section_text['CAPTURE_STDIO'])) { + $captureStdIn = stripos($section_text['CAPTURE_STDIO'], 'STDIN') !== false; + $captureStdOut = stripos($section_text['CAPTURE_STDIO'], 'STDOUT') !== false; + $captureStdErr = stripos($section_text['CAPTURE_STDIO'], 'STDERR') !== false; + } else { + $captureStdIn = true; + $captureStdOut = true; + $captureStdErr = true; + } + if ($captureStdOut && $captureStdErr) { + $cmdRedirect = ' 2>&1'; + } else { + $cmdRedirect = ''; + } + + $tested = trim($section_text['TEST']); + + /* For GET/POST/PUT tests, check if cgi sapi is available and if it is, use it. */ + if (array_key_exists('CGI', $section_text) || !empty($section_text['GET']) || !empty($section_text['POST']) || !empty($section_text['GZIP_POST']) || !empty($section_text['DEFLATE_POST']) || !empty($section_text['POST_RAW']) || !empty($section_text['PUT']) || !empty($section_text['COOKIE']) || !empty($section_text['EXPECTHEADERS'])) { + if (isset($php_cgi)) { + $php = $php_cgi . ' -C '; + } elseif (IS_WINDOWS && file_exists(dirname($php) . "/php-cgi.exe")) { + $php = realpath(dirname($php) . "/php-cgi.exe") . ' -C '; + } else { + if (file_exists(dirname($php) . "/../../sapi/cgi/php-cgi")) { + $php = realpath(dirname($php) . "/../../sapi/cgi/php-cgi") . ' -C '; + } elseif (file_exists("./sapi/cgi/php-cgi")) { + $php = realpath("./sapi/cgi/php-cgi") . ' -C '; + } elseif (file_exists(dirname($php) . "/php-cgi")) { + $php = realpath(dirname($php) . "/php-cgi") . ' -C '; + } else { + show_result('SKIP', $tested, $tested_file, "reason: CGI not available"); + + junit_init_suite(junit_get_suitename_for($shortname)); + junit_mark_test_as('SKIP', $shortname, $tested, 0, 'CGI not available'); + return 'SKIPPED'; + } + } + $uses_cgi = true; + } + + /* For phpdbg tests, check if phpdbg sapi is available and if it is, use it. */ + $extra_options = ''; + if (array_key_exists('PHPDBG', $section_text)) { + if (!isset($section_text['STDIN'])) { + $section_text['STDIN'] = $section_text['PHPDBG'] . "\n"; + } + + if (isset($phpdbg)) { + $php = $phpdbg . ' -qIb'; + + // Additional phpdbg command line options for sections that need to + // be run straight away. For example, EXTENSIONS, SKIPIF, CLEAN. + $extra_options = '-rr'; + } else { + show_result('SKIP', $tested, $tested_file, "reason: phpdbg not available"); + + junit_init_suite(junit_get_suitename_for($shortname)); + junit_mark_test_as('SKIP', $shortname, $tested, 0, 'phpdbg not available'); + return 'SKIPPED'; + } + } + + if (!$SHOW_ONLY_GROUPS && !$workerID) { + show_test($test_idx, $shortname); + } + + if (is_array($IN_REDIRECT)) { + $temp_dir = $test_dir = $IN_REDIRECT['dir']; + } else { + $temp_dir = $test_dir = realpath(dirname($file)); + } + + if ($temp_source && $temp_target) { + $temp_dir = str_replace($temp_source, $temp_target, $temp_dir); + } + + $main_file_name = basename($file, 'phpt'); + + $diff_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'diff'; + $log_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'log'; + $exp_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'exp'; + $output_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'out'; + $memcheck_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'mem'; + $sh_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'sh'; + $temp_file = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; + $test_file = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'php'; + $temp_skipif = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; + $test_skipif = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'skip.php'; + $temp_clean = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; + $test_clean = $test_dir . DIRECTORY_SEPARATOR . $main_file_name . 'clean.php'; + $preload_filename = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'preload.php'; + $tmp_post = $temp_dir . DIRECTORY_SEPARATOR . $main_file_name . 'post'; + $tmp_relative_file = str_replace(__DIR__ . DIRECTORY_SEPARATOR, '', $test_file) . 't'; + + if ($temp_source && $temp_target) { + $temp_skipif .= 's'; + $temp_file .= 's'; + $temp_clean .= 's'; + $copy_file = $temp_dir . DIRECTORY_SEPARATOR . basename(is_array($file) ? $file[1] : $file) . '.phps'; + + if (!is_dir(dirname($copy_file))) { + mkdir(dirname($copy_file), 0777, true) or error("Cannot create output directory - " . dirname($copy_file)); + } + + if (isset($section_text['FILE'])) { + save_text($copy_file, $section_text['FILE']); + } + + $temp_filenames = [ + 'file' => $copy_file, + 'diff' => $diff_filename, + 'log' => $log_filename, + 'exp' => $exp_filename, + 'out' => $output_filename, + 'mem' => $memcheck_filename, + 'sh' => $sh_filename, + 'php' => $temp_file, + 'skip' => $temp_skipif, + 'clean' => $temp_clean + ]; + } + + if (is_array($IN_REDIRECT)) { + $tested = $IN_REDIRECT['prefix'] . ' ' . trim($section_text['TEST']); + $tested_file = $tmp_relative_file; + $shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $tested_file); + } + + // unlink old test results + @unlink($diff_filename); + @unlink($log_filename); + @unlink($exp_filename); + @unlink($output_filename); + @unlink($memcheck_filename); + @unlink($sh_filename); + @unlink($temp_file); + @unlink($test_file); + @unlink($temp_skipif); + @unlink($test_skipif); + @unlink($tmp_post); + @unlink($temp_clean); + @unlink($test_clean); + @unlink($preload_filename); + + // Reset environment from any previous test. + $env['REDIRECT_STATUS'] = ''; + $env['QUERY_STRING'] = ''; + $env['PATH_TRANSLATED'] = ''; + $env['SCRIPT_FILENAME'] = ''; + $env['REQUEST_METHOD'] = ''; + $env['CONTENT_TYPE'] = ''; + $env['CONTENT_LENGTH'] = ''; + $env['TZ'] = ''; + + if (!empty($section_text['ENV'])) { + foreach (explode("\n", trim($section_text['ENV'])) as $e) { + $e = explode('=', trim($e), 2); + + if (!empty($e[0]) && isset($e[1])) { + $env[$e[0]] = $e[1]; + } + } + } + + // Default ini settings + $ini_settings = $workerID ? ['opcache.cache_id' => "worker$workerID"] : []; + + // Additional required extensions + if (array_key_exists('EXTENSIONS', $section_text)) { + $ext_params = []; + settings2array($ini_overwrites, $ext_params); + $ext_params = settings2params($ext_params); + $ext_dir = `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo ini_get('extension_dir');"`; + $extensions = preg_split("/[\n\r]+/", trim($section_text['EXTENSIONS'])); + $loaded = explode(",", `$php $pass_options $extra_options $ext_params $no_file_cache -d display_errors=0 -r "echo implode(',', get_loaded_extensions());"`); + $ext_prefix = IS_WINDOWS ? "php_" : ""; + foreach ($extensions as $req_ext) { + if (!in_array($req_ext, $loaded)) { + if ($req_ext == 'opcache') { + $ini_settings['zend_extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; + } else { + $ini_settings['extension'][] = $ext_dir . DIRECTORY_SEPARATOR . $ext_prefix . $req_ext . '.' . PHP_SHLIB_SUFFIX; + } + } + } + } + + // additional ini overwrites + //$ini_overwrites[] = 'setting=value'; + settings2array($ini_overwrites, $ini_settings); + + $orig_ini_settings = settings2params($ini_settings); + + if ($file_cache !== null) { + $ini_settings['opcache.file_cache'] = '/tmp'; + // Make sure warnings still show up on the second run. + $ini_settings['opcache.record_warnings'] = '1'; + // File cache is currently incompatible with JIT. + $ini_settings['opcache.jit'] = '0'; + if ($file_cache === 'use') { + // Disable timestamp validation in order to fetch from file cache, + // even though all the files are re-created. + $ini_settings['opcache.validate_timestamps'] = '0'; + } + } + + // Any special ini settings + // these may overwrite the test defaults... + if (array_key_exists('INI', $section_text)) { + $section_text['INI'] = str_replace('{PWD}', dirname($file), $section_text['INI']); + $section_text['INI'] = str_replace('{TMP}', sys_get_temp_dir(), $section_text['INI']); + $replacement = IS_WINDOWS ? '"' . PHP_BINARY . ' -r \"while ($in = fgets(STDIN)) echo $in;\" > $1"' : 'tee $1 >/dev/null'; + $section_text['INI'] = preg_replace('/{MAIL:(\S+)}/', $replacement, $section_text['INI']); + settings2array(preg_split("/[\n\r]+/", $section_text['INI']), $ini_settings); + } + + $ini_settings = settings2params($ini_settings); + + $env['TEST_PHP_EXTRA_ARGS'] = $pass_options . ' ' . $ini_settings; + + // Check if test should be skipped. + $info = ''; + $warn = false; + + if (array_key_exists('SKIPIF', $section_text)) { + if (trim($section_text['SKIPIF'])) { + show_file_block('skip', $section_text['SKIPIF']); + save_text($test_skipif, $section_text['SKIPIF'], $temp_skipif); + $extra = !IS_WINDOWS ? + "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; + + if ($valgrind) { + $env['USE_ZEND_ALLOC'] = '0'; + $env['ZEND_DONT_UNLOAD_MODULES'] = 1; + } + + junit_start_timer($shortname); + + $output = system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache -d display_errors=1 -d display_startup_errors=0 \"$test_skipif\"", $env); + $output = trim($output); + + junit_finish_timer($shortname); + + if (!$cfg['keep']['skip']) { + @unlink($test_skipif); + } + + if (!strncasecmp('skip', $output, 4)) { + if (preg_match('/^skip\s*(.+)/i', $output, $m)) { + show_result('SKIP', $tested, $tested_file, "reason: $m[1]", $temp_filenames); + } else { + show_result('SKIP', $tested, $tested_file, '', $temp_filenames); + } + + if (!$cfg['keep']['skip']) { + @unlink($test_skipif); + } + + $message = !empty($m[1]) ? $m[1] : ''; + junit_mark_test_as('SKIP', $shortname, $tested, null, $message); + return 'SKIPPED'; + } + + if (!strncasecmp('info', $output, 4) && preg_match('/^info\s*(.+)/i', $output, $m)) { + $info = " (info: $m[1])"; + } elseif (!strncasecmp('warn', $output, 4) && preg_match('/^warn\s+(.+)/i', $output, $m)) { + $warn = true; /* only if there is a reason */ + $info = " (warn: $m[1])"; + } elseif (!strncasecmp('xfail', $output, 5)) { + // Pretend we have an XFAIL section + $section_text['XFAIL'] = ltrim(substr($output, 5)); + } elseif ($output !== '') { + show_result("BORK", $output, $tested_file, 'reason: invalid output from SKIPIF', $temp_filenames); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$output [$file]", + ]; + + junit_mark_test_as('BORK', $shortname, $tested, null, $output); + return 'BORKED'; + } + } + } + + if (!extension_loaded("zlib") + && (array_key_exists("GZIP_POST", $section_text) + || array_key_exists("DEFLATE_POST", $section_text))) { + $message = "ext/zlib required"; + show_result('SKIP', $tested, $tested_file, "reason: $message", $temp_filenames); + junit_mark_test_as('SKIP', $shortname, $tested, null, $message); + return 'SKIPPED'; + } + + if (isset($section_text['REDIRECTTEST'])) { + $test_files = []; + + $IN_REDIRECT = eval($section_text['REDIRECTTEST']); + $IN_REDIRECT['via'] = "via [$shortname]\n\t"; + $IN_REDIRECT['dir'] = realpath(dirname($file)); + $IN_REDIRECT['prefix'] = trim($section_text['TEST']); + + if (!empty($IN_REDIRECT['TESTS'])) { + if (is_array($org_file)) { + $test_files[] = $org_file[1]; + } else { + $GLOBALS['test_files'] = $test_files; + find_files($IN_REDIRECT['TESTS']); + + foreach ($GLOBALS['test_files'] as $f) { + $test_files[] = [$f, $file]; + } + } + $test_cnt += count($test_files) - 1; + $test_idx--; + + show_redirect_start($IN_REDIRECT['TESTS'], $tested, $tested_file); + + // set up environment + $redirenv = array_merge($environment, $IN_REDIRECT['ENV']); + $redirenv['REDIR_TEST_DIR'] = realpath($IN_REDIRECT['TESTS']) . DIRECTORY_SEPARATOR; + + usort($test_files, "test_sort"); + run_all_tests($test_files, $redirenv, $tested); + + show_redirect_ends($IN_REDIRECT['TESTS'], $tested, $tested_file); + + // a redirected test never fails + $IN_REDIRECT = false; + + junit_mark_test_as('PASS', $shortname, $tested); + return 'REDIR'; + } else { + $bork_info = "Redirect info must contain exactly one TEST string to be used as redirect directory."; + show_result("BORK", $bork_info, '', '', $temp_filenames); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$bork_info [$file]", + ]; + } + } + + if (is_array($org_file) || isset($section_text['REDIRECTTEST'])) { + if (is_array($org_file)) { + $file = $org_file[0]; + } + + $bork_info = "Redirected test did not contain redirection info"; + show_result("BORK", $bork_info, '', '', $temp_filenames); + $PHP_FAILED_TESTS['BORKED'][] = [ + 'name' => $file, + 'test_name' => '', + 'output' => '', + 'diff' => '', + 'info' => "$bork_info [$file]", + ]; + + junit_mark_test_as('BORK', $shortname, $tested, null, $bork_info); + + return 'BORKED'; + } + + // We've satisfied the preconditions - run the test! + if (isset($section_text['FILE'])) { + show_file_block('php', $section_text['FILE'], 'TEST'); + save_text($test_file, $section_text['FILE'], $temp_file); + } else { + $test_file = $temp_file = ""; + } + + if (array_key_exists('GET', $section_text)) { + $query_string = trim($section_text['GET']); + } else { + $query_string = ''; + } + + $env['REDIRECT_STATUS'] = '1'; + if (empty($env['QUERY_STRING'])) { + $env['QUERY_STRING'] = $query_string; + } + if (empty($env['PATH_TRANSLATED'])) { + $env['PATH_TRANSLATED'] = $test_file; + } + if (empty($env['SCRIPT_FILENAME'])) { + $env['SCRIPT_FILENAME'] = $test_file; + } + + if (array_key_exists('COOKIE', $section_text)) { + $env['HTTP_COOKIE'] = trim($section_text['COOKIE']); + } else { + $env['HTTP_COOKIE'] = ''; + } + + $args = isset($section_text['ARGS']) ? ' -- ' . $section_text['ARGS'] : ''; + + if ($preload && !empty($test_file)) { + save_text($preload_filename, "wrapCommand($cmd, $memcheck_filename, strpos($test_file, "pcre") !== false); + } + + if ($DETAILED) { + echo " +CONTENT_LENGTH = " . $env['CONTENT_LENGTH'] . " +CONTENT_TYPE = " . $env['CONTENT_TYPE'] . " +PATH_TRANSLATED = " . $env['PATH_TRANSLATED'] . " +QUERY_STRING = " . $env['QUERY_STRING'] . " +REDIRECT_STATUS = " . $env['REDIRECT_STATUS'] . " +REQUEST_METHOD = " . $env['REQUEST_METHOD'] . " +SCRIPT_FILENAME = " . $env['SCRIPT_FILENAME'] . " +HTTP_COOKIE = " . $env['HTTP_COOKIE'] . " +COMMAND $cmd +"; + } + + junit_start_timer($shortname); + $hrtime = hrtime(); + $startTime = $hrtime[0] * 1000000000 + $hrtime[1]; + + $out = system_with_timeout($cmd, $env, $section_text['STDIN'] ?? null, $captureStdIn, $captureStdOut, $captureStdErr); + + junit_finish_timer($shortname); + $hrtime = hrtime(); + $time = $hrtime[0] * 1000000000 + $hrtime[1] - $startTime; + if ($time >= $slow_min_ms * 1000000) { + $PHP_FAILED_TESTS['SLOW'][] = [ + 'name' => $file, + 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", + 'output' => '', + 'diff' => '', + 'info' => $time / 1000000000, + ]; + } + + if (array_key_exists('CLEAN', $section_text) && (!$no_clean || $cfg['keep']['clean'])) { + if (trim($section_text['CLEAN'])) { + show_file_block('clean', $section_text['CLEAN']); + save_text($test_clean, trim($section_text['CLEAN']), $temp_clean); + + if (!$no_clean) { + $extra = !IS_WINDOWS ? + "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;" : ""; + system_with_timeout("$extra $php $pass_options $extra_options -q $orig_ini_settings $no_file_cache \"$test_clean\"", $env); + } + + if (!$cfg['keep']['clean']) { + @unlink($test_clean); + } + } + } + + @unlink($preload_filename); + + $leaked = false; + $passed = false; + + if ($valgrind) { // leak check + $leaked = filesize($memcheck_filename) > 0; + + if (!$leaked) { + @unlink($memcheck_filename); + } + } + + // Does the output match what is expected? + $output = preg_replace("/\r\n/", "\n", trim($out)); + + /* when using CGI, strip the headers from the output */ + $headers = []; + + if (!empty($uses_cgi) && preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $out, $match)) { + $output = trim($match[2]); + $rh = preg_split("/[\n\r]+/", $match[1]); + + foreach ($rh as $line) { + if (strpos($line, ':') !== false) { + $line = explode(':', $line, 2); + $headers[trim($line[0])] = trim($line[1]); + } + } + } + + $failed_headers = false; + + if (isset($section_text['EXPECTHEADERS'])) { + $want = []; + $wanted_headers = []; + $lines = preg_split("/[\n\r]+/", $section_text['EXPECTHEADERS']); + + foreach ($lines as $line) { + if (strpos($line, ':') !== false) { + $line = explode(':', $line, 2); + $want[trim($line[0])] = trim($line[1]); + $wanted_headers[] = trim($line[0]) . ': ' . trim($line[1]); + } + } + + $output_headers = []; + + foreach ($want as $k => $v) { + if (isset($headers[$k])) { + $output_headers[] = $k . ': ' . $headers[$k]; + } + + if (!isset($headers[$k]) || $headers[$k] != $v) { + $failed_headers = true; + } + } + + ksort($wanted_headers); + $wanted_headers = implode("\n", $wanted_headers); + ksort($output_headers); + $output_headers = implode("\n", $output_headers); + } + + show_file_block('out', $output); + + if ($preload) { + $output = trim(preg_replace("/\n?Warning: Can't preload [^\n]*\n?/", "", $output)); + } + + if (isset($section_text['EXPECTF']) || isset($section_text['EXPECTREGEX'])) { + if (isset($section_text['EXPECTF'])) { + $wanted = trim($section_text['EXPECTF']); + } else { + $wanted = trim($section_text['EXPECTREGEX']); + } + + show_file_block('exp', $wanted); + $wanted_re = preg_replace('/\r\n/', "\n", $wanted); + + if (isset($section_text['EXPECTF'])) { + // do preg_quote, but miss out any %r delimited sections + $temp = ""; + $r = "%r"; + $startOffset = 0; + $length = strlen($wanted_re); + while ($startOffset < $length) { + $start = strpos($wanted_re, $r, $startOffset); + if ($start !== false) { + // we have found a start tag + $end = strpos($wanted_re, $r, $start + 2); + if ($end === false) { + // unbalanced tag, ignore it. + $end = $start = $length; + } + } else { + // no more %r sections + $start = $end = $length; + } + // quote a non re portion of the string + $temp .= preg_quote(substr($wanted_re, $startOffset, $start - $startOffset), '/'); + // add the re unquoted. + if ($end > $start) { + $temp .= '(' . substr($wanted_re, $start + 2, $end - $start - 2) . ')'; + } + $startOffset = $end + 2; + } + $wanted_re = $temp; + + // Stick to basics + $wanted_re = str_replace('%e', '\\' . DIRECTORY_SEPARATOR, $wanted_re); + $wanted_re = str_replace('%s', '[^\r\n]+', $wanted_re); + $wanted_re = str_replace('%S', '[^\r\n]*', $wanted_re); + $wanted_re = str_replace('%a', '.+', $wanted_re); + $wanted_re = str_replace('%A', '.*', $wanted_re); + $wanted_re = str_replace('%w', '\s*', $wanted_re); + $wanted_re = str_replace('%i', '[+-]?\d+', $wanted_re); + $wanted_re = str_replace('%d', '\d+', $wanted_re); + $wanted_re = str_replace('%x', '[0-9a-fA-F]+', $wanted_re); + $wanted_re = str_replace('%f', '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', $wanted_re); + $wanted_re = str_replace('%c', '.', $wanted_re); + // %f allows two points "-.0.0" but that is the best *simple* expression + } + + if (preg_match("/^$wanted_re\$/s", $output)) { + $passed = true; + if (!$cfg['keep']['php']) { + @unlink($test_file); + } + @unlink($tmp_post); + + if (!$leaked && !$failed_headers) { + if (isset($section_text['XFAIL'])) { + $warn = true; + $info = " (warn: XFAIL section but test passes)"; + } elseif (isset($section_text['XLEAK'])) { + $warn = true; + $info = " (warn: XLEAK section but test passes)"; + } else { + show_result("PASS", $tested, $tested_file, '', $temp_filenames); + junit_mark_test_as('PASS', $shortname, $tested); + return 'PASSED'; + } + } + } + } else { + $wanted = trim($section_text['EXPECT']); + $wanted = preg_replace('/\r\n/', "\n", $wanted); + show_file_block('exp', $wanted); + + // compare and leave on success + if (!strcmp($output, $wanted)) { + $passed = true; + + if (!$cfg['keep']['php']) { + @unlink($test_file); + } + @unlink($tmp_post); + + if (!$leaked && !$failed_headers) { + if (isset($section_text['XFAIL'])) { + $warn = true; + $info = " (warn: XFAIL section but test passes)"; + } elseif (isset($section_text['XLEAK'])) { + $warn = true; + $info = " (warn: XLEAK section but test passes)"; + } else { + show_result("PASS", $tested, $tested_file, '', $temp_filenames); + junit_mark_test_as('PASS', $shortname, $tested); + return 'PASSED'; + } + } + } + + $wanted_re = null; + } + + // Test failed so we need to report details. + if ($failed_headers) { + $passed = false; + $wanted = $wanted_headers . "\n--HEADERS--\n" . $wanted; + $output = $output_headers . "\n--HEADERS--\n" . $output; + + if (isset($wanted_re)) { + $wanted_re = preg_quote($wanted_headers . "\n--HEADERS--\n", '/') . $wanted_re; + } + } + + if ($leaked) { + $restype[] = isset($section_text['XLEAK']) ? + 'XLEAK' : 'LEAK'; + } + + if ($warn) { + $restype[] = 'WARN'; + } + + if (!$passed) { + if (isset($section_text['XFAIL'])) { + $restype[] = 'XFAIL'; + $info = ' XFAIL REASON: ' . rtrim($section_text['XFAIL']); + } elseif (isset($section_text['XLEAK'])) { + $restype[] = 'XLEAK'; + $info = ' XLEAK REASON: ' . rtrim($section_text['XLEAK']); + } else { + $restype[] = 'FAIL'; + } + } + + if (!$passed) { + // write .exp + if (strpos($log_format, 'E') !== false && file_put_contents($exp_filename, $wanted) === false) { + error("Cannot create expected test output - $exp_filename"); + } + + // write .out + if (strpos($log_format, 'O') !== false && file_put_contents($output_filename, $output) === false) { + error("Cannot create test output - $output_filename"); + } + + // write .diff + $diff = generate_diff($wanted, $wanted_re, $output); + if (is_array($IN_REDIRECT)) { + $orig_shortname = str_replace(TEST_PHP_SRCDIR . '/', '', $file); + $diff = "# original source file: $orig_shortname\n" . $diff; + } + show_file_block('diff', $diff); + if (strpos($log_format, 'D') !== false && file_put_contents($diff_filename, $diff) === false) { + error("Cannot create test diff - $diff_filename"); + } + + // write .sh + $sh_script = << $file, + 'test_name' => (is_array($IN_REDIRECT) ? $IN_REDIRECT['via'] : '') . $tested . " [$tested_file]", + 'output' => $output_filename, + 'diff' => $diff_filename, + 'info' => $info, + ]; + } + + $diff = empty($diff) ? '' : preg_replace('/\e/', '', $diff); + + junit_mark_test_as($restype, $shortname, $tested, null, $info, $diff); + + return $restype[0] . 'ED'; +} + +/** + * @return bool|int + */ +function comp_line(string $l1, string $l2, bool $is_reg) +{ + if ($is_reg) { + return preg_match('/^' . $l1 . '$/s', $l2); + } else { + return !strcmp($l1, $l2); + } +} + +function count_array_diff( + array $ar1, + array $ar2, + bool $is_reg, + array $w, + int $idx1, + int $idx2, + int $cnt1, + int $cnt2, + int $steps +): int { + $equal = 0; + + while ($idx1 < $cnt1 && $idx2 < $cnt2 && comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { + $idx1++; + $idx2++; + $equal++; + $steps--; + } + if (--$steps > 0) { + $eq1 = 0; + $st = $steps / 2; + + for ($ofs1 = $idx1 + 1; $ofs1 < $cnt1 && $st-- > 0; $ofs1++) { + $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $ofs1, $idx2, $cnt1, $cnt2, $st); + + if ($eq > $eq1) { + $eq1 = $eq; + } + } + + $eq2 = 0; + $st = $steps; + + for ($ofs2 = $idx2 + 1; $ofs2 < $cnt2 && $st-- > 0; $ofs2++) { + $eq = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $ofs2, $cnt1, $cnt2, $st); + if ($eq > $eq2) { + $eq2 = $eq; + } + } + + if ($eq1 > $eq2) { + $equal += $eq1; + } elseif ($eq2 > 0) { + $equal += $eq2; + } + } + + return $equal; +} + +function generate_array_diff(array $ar1, array $ar2, bool $is_reg, array $w): array +{ + global $context_line_count; + $idx1 = 0; + $cnt1 = @count($ar1); + $idx2 = 0; + $cnt2 = @count($ar2); + $diff = []; + $old1 = []; + $old2 = []; + $number_len = max(3, strlen((string)max($cnt1 + 1, $cnt2 + 1))); + $line_number_spec = '%0' . $number_len . 'd'; + + /** Mapping from $idx2 to $idx1, including indexes of idx2 that are identical to idx1 as well as entries that don't have matches */ + $mapping = []; + + while ($idx1 < $cnt1 && $idx2 < $cnt2) { + $mapping[$idx2] = $idx1; + if (comp_line($ar1[$idx1], $ar2[$idx2], $is_reg)) { + $idx1++; + $idx2++; + continue; + } else { + $c1 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1 + 1, $idx2, $cnt1, $cnt2, 10); + $c2 = @count_array_diff($ar1, $ar2, $is_reg, $w, $idx1, $idx2 + 1, $cnt1, $cnt2, 10); + + if ($c1 > $c2) { + $old1[$idx1] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++]; + } elseif ($c2 > 0) { + $old2[$idx2] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++]; + } else { + $old1[$idx1] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++]; + $old2[$idx2] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++]; + } + $last_printed_context_line = $idx1; + } + } + $mapping[$idx2] = $idx1; + + reset($old1); + $k1 = key($old1); + $l1 = -2; + reset($old2); + $k2 = key($old2); + $l2 = -2; + $old_k1 = -1; + $add_context_lines = function (int $new_k1) use (&$old_k1, &$diff, $w, $context_line_count, $number_len) { + if ($old_k1 >= $new_k1 || !$context_line_count) { + return; + } + $end = $new_k1 - 1; + $range_end = min($end, $old_k1 + $context_line_count); + if ($old_k1 >= 0) { + while ($old_k1 < $range_end) { + $diff[] = str_repeat(' ', $number_len + 2) . $w[$old_k1++]; + } + } + if ($end - $context_line_count > $old_k1) { + $old_k1 = $end - $context_line_count; + if ($old_k1 > 0) { + // Add a '--' to mark sections where the common areas were truncated + $diff[] = '--'; + } + } + $old_k1 = max($old_k1, 0); + while ($old_k1 < $end) { + $diff[] = str_repeat(' ', $number_len + 2) . $w[$old_k1++]; + } + $old_k1 = $new_k1; + }; + + while ($k1 !== null || $k2 !== null) { + if ($k1 == $l1 + 1 || $k2 === null) { + $add_context_lines($k1); + $l1 = $k1; + $diff[] = current($old1); + $old_k1 = $k1; + $k1 = next($old1) ? key($old1) : null; + } elseif ($k2 == $l2 + 1 || $k1 === null) { + $add_context_lines($mapping[$k2]); + $l2 = $k2; + $diff[] = current($old2); + $k2 = next($old2) ? key($old2) : null; + } elseif ($k1 < $mapping[$k2]) { + $add_context_lines($k1); + $l1 = $k1; + $diff[] = current($old1); + $k1 = next($old1) ? key($old1) : null; + } else { + $add_context_lines($mapping[$k2]); + $l2 = $k2; + $diff[] = current($old2); + $k2 = next($old2) ? key($old2) : null; + } + } + + while ($idx1 < $cnt1) { + $add_context_lines($idx1 + 1); + $diff[] = sprintf("{$line_number_spec}- ", $idx1 + 1) . $w[$idx1++]; + } + + while ($idx2 < $cnt2) { + if (isset($mapping[$idx2])) { + $add_context_lines($mapping[$idx2] + 1); + } + $diff[] = sprintf("{$line_number_spec}+ ", $idx2 + 1) . $ar2[$idx2++]; + } + $add_context_lines(min($old_k1 + $context_line_count + 1, $cnt1 + 1)); + if ($context_line_count && $old_k1 < $cnt1 + 1) { + // Add a '--' to mark sections where the common areas were truncated + $diff[] = '--'; + } + + return $diff; +} + +function generate_diff(string $wanted, string $wanted_re = null, string $output = ''): string +{ + $w = explode("\n", $wanted); + $o = explode("\n", $output); + $r = is_null($wanted_re) ? $w : explode("\n", $wanted_re); + $diff = generate_array_diff($r, $o, !is_null($wanted_re), $w); + + return implode(PHP_EOL, $diff); +} + +function error(string $message) +{ + echo "ERROR: {$message}\n"; + exit(1); +} + +function settings2array(array $settings, &$ini_settings) +{ + foreach ($settings as $setting) { + if (strpos($setting, '=') !== false) { + $setting = explode("=", $setting, 2); + $name = trim($setting[0]); + $value = trim($setting[1]); + + if ($name == 'extension' || $name == 'zend_extension') { + if (!isset($ini_settings[$name])) { + $ini_settings[$name] = []; + } + + $ini_settings[$name][] = $value; + } else { + $ini_settings[$name] = $value; + } + } + } +} + +function settings2params(array $ini_settings): string +{ + $settings = ''; + + foreach ($ini_settings as $name => $value) { + if (is_array($value)) { + foreach ($value as $val) { + $val = addslashes($val); + $settings .= " -d \"$name=$val\""; + } + } else { + if (IS_WINDOWS && !empty($value) && $value[0] == '"') { + $len = strlen($value); + + if ($value[$len - 1] == '"') { + $value[0] = "'"; + $value[$len - 1] = "'"; + } + } else { + $value = addslashes($value); + } + + $settings .= " -d \"$name=$value\""; + } + } + + return $settings; +} + +function compute_summary() +{ + global $n_total, $test_results, $ignored_by_ext, $sum_results, $percent_results; + + $n_total = count($test_results); + $n_total += $ignored_by_ext; + $sum_results = [ + 'PASSED' => 0, + 'WARNED' => 0, + 'SKIPPED' => 0, + 'FAILED' => 0, + 'BORKED' => 0, + 'LEAKED' => 0, + 'XFAILED' => 0, + 'XLEAKED' => 0 + ]; + + foreach ($test_results as $v) { + $sum_results[$v]++; + } + + $sum_results['SKIPPED'] += $ignored_by_ext; + $percent_results = []; + + foreach ($sum_results as $v => $n) { + $percent_results[$v] = (100.0 * $n) / $n_total; + } +} + +function get_summary(bool $show_ext_summary): string +{ + global $exts_skipped, $exts_tested, $n_total, $sum_results, $percent_results, $end_time, $start_time, $failed_test_summary, $PHP_FAILED_TESTS, $valgrind; + + $x_total = $n_total - $sum_results['SKIPPED'] - $sum_results['BORKED']; + + if ($x_total) { + $x_warned = (100.0 * $sum_results['WARNED']) / $x_total; + $x_failed = (100.0 * $sum_results['FAILED']) / $x_total; + $x_xfailed = (100.0 * $sum_results['XFAILED']) / $x_total; + $x_xleaked = (100.0 * $sum_results['XLEAKED']) / $x_total; + $x_leaked = (100.0 * $sum_results['LEAKED']) / $x_total; + $x_passed = (100.0 * $sum_results['PASSED']) / $x_total; + } else { + $x_warned = $x_failed = $x_passed = $x_leaked = $x_xfailed = $x_xleaked = 0; + } + + $summary = ''; + + if ($show_ext_summary) { + $summary .= ' +===================================================================== +TEST RESULT SUMMARY +--------------------------------------------------------------------- +Exts skipped : ' . sprintf('%4d', $exts_skipped) . ' +Exts tested : ' . sprintf('%4d', $exts_tested) . ' +--------------------------------------------------------------------- +'; + } + + $summary .= ' +Number of tests : ' . sprintf('%4d', $n_total) . ' ' . sprintf('%8d', $x_total); + + if ($sum_results['BORKED']) { + $summary .= ' +Tests borked : ' . sprintf('%4d (%5.1f%%)', $sum_results['BORKED'], $percent_results['BORKED']) . ' --------'; + } + + $summary .= ' +Tests skipped : ' . sprintf('%4d (%5.1f%%)', $sum_results['SKIPPED'], $percent_results['SKIPPED']) . ' -------- +Tests warned : ' . sprintf('%4d (%5.1f%%)', $sum_results['WARNED'], $percent_results['WARNED']) . ' ' . sprintf('(%5.1f%%)', $x_warned) . ' +Tests failed : ' . sprintf('%4d (%5.1f%%)', $sum_results['FAILED'], $percent_results['FAILED']) . ' ' . sprintf('(%5.1f%%)', $x_failed); + + if ($sum_results['XFAILED']) { + $summary .= ' +Expected fail : ' . sprintf('%4d (%5.1f%%)', $sum_results['XFAILED'], $percent_results['XFAILED']) . ' ' . sprintf('(%5.1f%%)', $x_xfailed); + } + + if ($valgrind) { + $summary .= ' +Tests leaked : ' . sprintf('%4d (%5.1f%%)', $sum_results['LEAKED'], $percent_results['LEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_leaked); + if ($sum_results['XLEAKED']) { + $summary .= ' +Expected leak : ' . sprintf('%4d (%5.1f%%)', $sum_results['XLEAKED'], $percent_results['XLEAKED']) . ' ' . sprintf('(%5.1f%%)', $x_xleaked); + } + } + + $summary .= ' +Tests passed : ' . sprintf('%4d (%5.1f%%)', $sum_results['PASSED'], $percent_results['PASSED']) . ' ' . sprintf('(%5.1f%%)', $x_passed) . ' +--------------------------------------------------------------------- +Time taken : ' . sprintf('%4d seconds', $end_time - $start_time) . ' +===================================================================== +'; + $failed_test_summary = ''; + + if (count($PHP_FAILED_TESTS['SLOW'])) { + usort($PHP_FAILED_TESTS['SLOW'], function (array $a, array $b): int { + return $a['info'] < $b['info'] ? 1 : -1; + }); + + $failed_test_summary .= ' +===================================================================== +SLOW TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['SLOW'] as $failed_test_data) { + $failed_test_summary .= sprintf('(%.3f s) ', $failed_test_data['info']) . $failed_test_data['test_name'] . "\n"; + } + $failed_test_summary .= "=====================================================================\n"; + } + + if (count($PHP_FAILED_TESTS['XFAILED'])) { + $failed_test_summary .= ' +===================================================================== +EXPECTED FAILED TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['XFAILED'] as $failed_test_data) { + $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; + } + $failed_test_summary .= "=====================================================================\n"; + } + + if (count($PHP_FAILED_TESTS['BORKED'])) { + $failed_test_summary .= ' +===================================================================== +BORKED TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['BORKED'] as $failed_test_data) { + $failed_test_summary .= $failed_test_data['info'] . "\n"; + } + + $failed_test_summary .= "=====================================================================\n"; + } + + if (count($PHP_FAILED_TESTS['FAILED'])) { + $failed_test_summary .= ' +===================================================================== +FAILED TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['FAILED'] as $failed_test_data) { + $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; + } + $failed_test_summary .= "=====================================================================\n"; + } + if (count($PHP_FAILED_TESTS['WARNED'])) { + $failed_test_summary .= ' +===================================================================== +WARNED TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['WARNED'] as $failed_test_data) { + $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; + } + + $failed_test_summary .= "=====================================================================\n"; + } + + if (count($PHP_FAILED_TESTS['LEAKED'])) { + $failed_test_summary .= ' +===================================================================== +LEAKED TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['LEAKED'] as $failed_test_data) { + $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; + } + + $failed_test_summary .= "=====================================================================\n"; + } + + if (count($PHP_FAILED_TESTS['XLEAKED'])) { + $failed_test_summary .= ' +===================================================================== +EXPECTED LEAK TEST SUMMARY +--------------------------------------------------------------------- +'; + foreach ($PHP_FAILED_TESTS['XLEAKED'] as $failed_test_data) { + $failed_test_summary .= $failed_test_data['test_name'] . $failed_test_data['info'] . "\n"; + } + + $failed_test_summary .= "=====================================================================\n"; + } + + if ($failed_test_summary && !getenv('NO_PHPTEST_SUMMARY')) { + $summary .= $failed_test_summary; + } + + return $summary; +} + +function show_start($start_time) +{ + echo "TIME START " . date('Y-m-d H:i:s', $start_time) . "\n=====================================================================\n"; +} + +function show_end($end_time) +{ + echo "=====================================================================\nTIME END " . date('Y-m-d H:i:s', $end_time) . "\n"; +} + +function show_summary() +{ + echo get_summary(true); +} + +function show_redirect_start(string $tests, string $tested, string $tested_file) +{ + global $SHOW_ONLY_GROUPS; + + if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { + echo "REDIRECT $tests ($tested [$tested_file]) begin\n"; + } else { + clear_show_test(); + } +} + +function show_redirect_ends(string $tests, string $tested, string $tested_file) +{ + global $SHOW_ONLY_GROUPS; + + if (!$SHOW_ONLY_GROUPS || in_array('REDIRECT', $SHOW_ONLY_GROUPS)) { + echo "REDIRECT $tests ($tested [$tested_file]) done\n"; + } else { + clear_show_test(); + } +} + +function show_test(int $test_idx, string $shortname) +{ + global $test_cnt; + global $line_length; + + $str = "TEST $test_idx/$test_cnt [$shortname]\r"; + $line_length = strlen($str); + echo $str; + flush(); +} + +function clear_show_test() +{ + global $line_length; + // Parallel testing + global $workerID; + + if (!$workerID) { + // Write over the last line to avoid random trailing chars on next echo + echo str_repeat(" ", $line_length), "\r"; + } +} + +function parse_conflicts(string $text): array +{ + // Strip comments + $text = preg_replace('/#.*/', '', $text); + return array_map('trim', explode("\n", trim($text))); +} + +function show_result( + string $result, + string $tested, + string $tested_file, + string $extra = '', + array $temp_filenames = null +) { + global $SHOW_ONLY_GROUPS, $colorize; + + if (!$SHOW_ONLY_GROUPS || in_array($result, $SHOW_ONLY_GROUPS)) { + if ($colorize) { + /* Use ANSI escape codes for coloring test result */ + switch ( $result ) { + case 'PASS': // Light Green + $color = "\e[1;32m{$result}\e[0m"; break; + case 'FAIL': + case 'BORK': + case 'LEAK': + // Light Red + $color = "\e[1;31m{$result}\e[0m"; break; + default: // Yellow + $color = "\e[1;33m{$result}\e[0m"; break; + } + + echo "$color $tested [$tested_file] $extra\n"; + } else { + echo "$result $tested [$tested_file] $extra\n"; + } + } elseif (!$SHOW_ONLY_GROUPS) { + clear_show_test(); + } + +} + +function junit_init() +{ + // Check whether a junit log is wanted. + global $workerID; + $JUNIT = getenv('TEST_PHP_JUNIT'); + if (empty($JUNIT)) { + $GLOBALS['JUNIT'] = false; + return; + } + if ($workerID) { + $fp = null; + } elseif (!$fp = fopen($JUNIT, 'w')) { + error("Failed to open $JUNIT for writing."); + } + $GLOBALS['JUNIT'] = [ + 'fp' => $fp, + 'name' => 'PHP', + 'test_total' => 0, + 'test_pass' => 0, + 'test_fail' => 0, + 'test_error' => 0, + 'test_skip' => 0, + 'test_warn' => 0, + 'execution_time' => 0, + 'suites' => [], + 'files' => [] + ]; +} + +function junit_save_xml() +{ + global $JUNIT; + if (!junit_enabled()) { + return; + } + + $xml = '<' . '?' . 'xml version="1.0" encoding="UTF-8"' . '?' . '>' . PHP_EOL; + $xml .= sprintf( + '' . PHP_EOL, + $JUNIT['name'], + $JUNIT['test_total'], + $JUNIT['test_fail'], + $JUNIT['test_error'], + $JUNIT['test_skip'], + $JUNIT['execution_time'] + ); + $xml .= junit_get_suite_xml(); + $xml .= ''; + fwrite($JUNIT['fp'], $xml); +} + +function junit_get_suite_xml(string $suite_name = ''): string +{ + global $JUNIT; + + $result = ""; + + foreach ($JUNIT['suites'] as $suite_name => $suite) { + $result .= sprintf( + '' . PHP_EOL, + $suite['name'], + $suite['test_total'], + $suite['test_fail'], + $suite['test_error'], + $suite['test_skip'], + $suite['execution_time'] + ); + + if (!empty($suite_name)) { + foreach ($suite['files'] as $file) { + $result .= $JUNIT['files'][$file]['xml']; + } + } + + $result .= '' . PHP_EOL; + } + + return $result; +} + +function junit_enabled(): bool +{ + global $JUNIT; + return !empty($JUNIT); +} + +/** + * @param array|string $type + */ +function junit_mark_test_as( + $type, + string $file_name, + string $test_name, + int $time = null, + string $message = '', + string $details = '' +) { + global $JUNIT; + if (!junit_enabled()) { + return; + } + + $suite = junit_get_suitename_for($file_name); + + junit_suite_record($suite, 'test_total'); + + $time = $time ?? junit_get_timer($file_name); + junit_suite_record($suite, 'execution_time', $time); + + $escaped_details = htmlspecialchars($details, ENT_QUOTES, 'UTF-8'); + $escaped_details = preg_replace_callback('/[\0-\x08\x0B\x0C\x0E-\x1F]/', function (array $c): string { + return sprintf('[[0x%02x]]', ord($c[0])); + }, $escaped_details); + $escaped_message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8'); + + $escaped_test_name = htmlspecialchars($file_name . ' (' . $test_name . ')', ENT_QUOTES); + $JUNIT['files'][$file_name]['xml'] = "\n"; + + if (is_array($type)) { + $output_type = $type[0] . 'ED'; + $temp = array_intersect(['XFAIL', 'XLEAK', 'FAIL', 'WARN'], $type); + $type = reset($temp); + } else { + $output_type = $type . 'ED'; + } + + if ('PASS' == $type || 'XFAIL' == $type || 'XLEAK' == $type) { + junit_suite_record($suite, 'test_pass'); + } elseif ('BORK' == $type) { + junit_suite_record($suite, 'test_error'); + $JUNIT['files'][$file_name]['xml'] .= "\n"; + } elseif ('SKIP' == $type) { + junit_suite_record($suite, 'test_skip'); + $JUNIT['files'][$file_name]['xml'] .= "$escaped_message\n"; + } elseif ('WARN' == $type) { + junit_suite_record($suite, 'test_warn'); + $JUNIT['files'][$file_name]['xml'] .= "$escaped_message\n"; + } elseif ('FAIL' == $type) { + junit_suite_record($suite, 'test_fail'); + $JUNIT['files'][$file_name]['xml'] .= "$escaped_details\n"; + } else { + junit_suite_record($suite, 'test_error'); + $JUNIT['files'][$file_name]['xml'] .= "$escaped_details\n"; + } + + $JUNIT['files'][$file_name]['xml'] .= "\n"; +} + +function junit_suite_record(string $suite, string $param, int $value = 1) +{ + global $JUNIT; + + $JUNIT[$param] += $value; + $JUNIT['suites'][$suite][$param] += $value; +} + +function junit_get_timer(string $file_name): int +{ + global $JUNIT; + if (!junit_enabled()) { + return 0; + } + + if (isset($JUNIT['files'][$file_name]['total'])) { + return number_format($JUNIT['files'][$file_name]['total'], 4); + } + + return 0; +} + +function junit_start_timer(string $file_name) +{ + global $JUNIT; + if (!junit_enabled()) { + return; + } + + if (!isset($JUNIT['files'][$file_name]['start'])) { + $JUNIT['files'][$file_name]['start'] = microtime(true); + + $suite = junit_get_suitename_for($file_name); + junit_init_suite($suite); + $JUNIT['suites'][$suite]['files'][$file_name] = $file_name; + } +} + +function junit_get_suitename_for(string $file_name): string +{ + return junit_path_to_classname(dirname($file_name)); +} + +function junit_path_to_classname(string $file_name): string +{ + global $JUNIT; + + if (!junit_enabled()) { + return ''; + } + + $ret = $JUNIT['name']; + $_tmp = []; + + // lookup whether we're in the PHP source checkout + $max = 5; + if (is_file($file_name)) { + $dir = dirname(realpath($file_name)); + } else { + $dir = realpath($file_name); + } + do { + array_unshift($_tmp, basename($dir)); + $chk = $dir . DIRECTORY_SEPARATOR . "main" . DIRECTORY_SEPARATOR . "php_version.h"; + $dir = dirname($dir); + } while (!file_exists($chk) && --$max > 0); + if (file_exists($chk)) { + if ($max) { + array_shift($_tmp); + } + foreach ($_tmp as $p) { + $ret .= "." . preg_replace(",[^a-z0-9]+,i", ".", $p); + } + return $ret; + } + + return $JUNIT['name'] . '.' . str_replace([DIRECTORY_SEPARATOR, '-'], '.', $file_name); +} + +function junit_init_suite(string $suite_name) +{ + global $JUNIT; + if (!junit_enabled()) { + return; + } + + if (!empty($JUNIT['suites'][$suite_name])) { + return; + } + + $JUNIT['suites'][$suite_name] = [ + 'name' => $suite_name, + 'test_total' => 0, + 'test_pass' => 0, + 'test_fail' => 0, + 'test_error' => 0, + 'test_skip' => 0, + 'test_warn' => 0, + 'files' => [], + 'execution_time' => 0, + ]; +} + +function junit_finish_timer(string $file_name) +{ + global $JUNIT; + if (!junit_enabled()) { + return; + } + + if (!isset($JUNIT['files'][$file_name]['start'])) { + error("Timer for $file_name was not started!"); + } + + if (!isset($JUNIT['files'][$file_name]['total'])) { + $JUNIT['files'][$file_name]['total'] = 0; + } + + $start = $JUNIT['files'][$file_name]['start']; + $JUNIT['files'][$file_name]['total'] += microtime(true) - $start; + unset($JUNIT['files'][$file_name]['start']); +} + +function junit_merge_results(array $junit) +{ + global $JUNIT; + $JUNIT['test_total'] += $junit['test_total']; + $JUNIT['test_pass'] += $junit['test_pass']; + $JUNIT['test_fail'] += $junit['test_fail']; + $JUNIT['test_error'] += $junit['test_error']; + $JUNIT['test_skip'] += $junit['test_skip']; + $JUNIT['test_warn'] += $junit['test_warn']; + $JUNIT['execution_time'] += $junit['execution_time']; + $JUNIT['files'] += $junit['files']; + foreach ($junit['suites'] as $name => $suite) { + if (!isset($JUNIT['suites'][$name])) { + $JUNIT['suites'][$name] = $suite; + continue; + } + + $SUITE =& $JUNIT['suites'][$name]; + $SUITE['test_total'] += $suite['test_total']; + $SUITE['test_pass'] += $suite['test_pass']; + $SUITE['test_fail'] += $suite['test_fail']; + $SUITE['test_error'] += $suite['test_error']; + $SUITE['test_skip'] += $suite['test_skip']; + $SUITE['test_warn'] += $suite['test_warn']; + $SUITE['execution_time'] += $suite['execution_time']; + $SUITE['files'] += $suite['files']; + } +} + +class RuntestsValgrind +{ + protected $version = ''; + protected $header = ''; + protected $version_3_3_0 = false; + protected $version_3_8_0 = false; + protected $tool = null; + + public function getVersion(): string + { + return $this->version; + } + + public function getHeader(): string + { + return $this->header; + } + + public function __construct(array $environment, string $tool = 'memcheck') + { + $this->tool = $tool; + $header = system_with_timeout("valgrind --tool={$this->tool} --version", $environment); + if (!$header) { + error("Valgrind returned no version info for {$this->tool}, cannot proceed.\n". + "Please check if Valgrind is installed and the tool is named correctly."); + } + $count = 0; + $version = preg_replace("/valgrind-(\d+)\.(\d+)\.(\d+)([.\w_-]+)?(\s+)/", '$1.$2.$3', $header, 1, $count); + if ($count != 1) { + error("Valgrind returned invalid version info (\"{$header}\") for {$this->tool}, cannot proceed."); + } + $this->version = $version; + $this->header = sprintf( + "%s (%s)", trim($header), $this->tool); + $this->version_3_3_0 = version_compare($version, '3.3.0', '>='); + $this->version_3_8_0 = version_compare($version, '3.8.0', '>='); + } + + public function wrapCommand(string $cmd, string $memcheck_filename, bool $check_all): string + { + $vcmd = "valgrind -q --tool={$this->tool} --trace-children=yes"; + if ($check_all) { + $vcmd .= ' --smc-check=all'; + } + + /* --vex-iropt-register-updates=allregs-at-mem-access is necessary for phpdbg watchpoint tests */ + if ($this->version_3_8_0) { + /* valgrind 3.3.0+ doesn't have --log-file-exactly option */ + return "$vcmd --vex-iropt-register-updates=allregs-at-mem-access --log-file=$memcheck_filename $cmd"; + } elseif ($this->version_3_3_0) { + return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file=$memcheck_filename $cmd"; + } else { + return "$vcmd --vex-iropt-precise-memory-exns=yes --log-file-exactly=$memcheck_filename $cmd"; + } + } +} + +function init_output_buffers() +{ + // Delete as much output buffers as possible. + while (@ob_end_clean()) { + } + + if (ob_get_level()) { + echo "Not all buffers were deleted.\n"; + } +} + +function check_proc_open_function_exists() +{ + if (!function_exists('proc_open')) { + echo << 3 ]]; then + echo "Usage: $0 PHP_VERSION PHP_VERSION_FULL [i386]" 1>&2 + echo "e.g. $0 8.0 8.0.10 i386" 1>&2 + echo "The PHP_VERSION is the version of the php docker image to use" 1>&2 + exit 1 +fi +# -x Exit immediately if any command fails +# -e Echo all commands being executed. +# -u fail for undefined variables +set -xeu +PHP_VERSION=$1 +PHP_VERSION_FULL=${2:-$PHP_VERSION} +ARCHITECTURE=${3:-} + +# Determine if we have a pre-release version, and if so, use the full version +# instead of the short version. Docker hub only has short tags for stable +# releases. +case "$PHP_VERSION_FULL" in + *RC[0-9]*) + PHP_VERSION="${PHP_VERSION_FULL}" + ;; + *alpha[0-9]*) + PHP_VERSION="${PHP_VERSION_FULL}" + ;; + *beta[0-9]*) + PHP_VERSION="${PHP_VERSION_FULL}" + ;; + *) + # Use only the major.minor version for stable releases. + # As provided in PHP_VERSION + ;; +esac + +if [[ "$ARCHITECTURE" == i386 ]]; then + PHP_IMAGE="$ARCHITECTURE/php" + DOCKER_IMAGE="igbinary-test-runner:$ARCHITECTURE-$PHP_VERSION" +else + PHP_IMAGE="php" + DOCKER_IMAGE="igbinary-test-runner:$PHP_VERSION" +fi + +docker build --build-arg="PHP_VERSION=$PHP_VERSION" --build-arg="PHP_IMAGE=$PHP_IMAGE" --tag="$DOCKER_IMAGE" -f ci/Dockerfile . +docker run --rm $DOCKER_IMAGE ci/test_inner.sh diff --git a/ci/test_dockerized_valgrind.sh b/ci/test_dockerized_valgrind.sh new file mode 100755 index 00000000..42dab3b5 --- /dev/null +++ b/ci/test_dockerized_valgrind.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# Runs this PECL's unit tests with valgrind and a customizable php build +# TODO: Support overridable PHP_CONFIGURE_ARGS +# +# We reuse the php base image because +# 1. It has any necessary dependencies installed for php 7.0-8.0 +# 2. It is already downloaded +# +# We need to install valgrind then rebuild php from source with the configure option '--with-valgrind' to avoid valgrind false positives +# because php-src has inline assembly that causes false positives in valgrind when that option isn't used. +if [[ $# < 2 || $# > 3 ]]; then + echo "Usage: $0 PHP_VERSION PHP_VERSION_FULL [i386]" 1>&2 + echo "e.g. $0 8.0 8.0.3" 1>&2 + echo "The PHP_VERSION is the version of the php docker image to use" 1>&2 + echo "The PHP_VERSION_FULL is the version of the php release to download using ci/install_php_custom.sh" 1>&2 + exit 1 +fi +# -x Exit immediately if any command fails +# -e Echo all commands being executed. +# -u fail for undefined variables +set -xeu +PHP_VERSION=$1 +PHP_VERSION_FULL=$2 +ARCHITECTURE=${3:-} + +case "$PHP_VERSION_FULL" in + *RC[0-9]*) + PHP_VERSION="${PHP_VERSION_FULL}" + ;; + *alpha[0-9]*) + PHP_VERSION="${PHP_VERSION_FULL}" + ;; + *beta[0-9]*) + PHP_VERSION="${PHP_VERSION_FULL}" + ;; + *) + # Use only the major.minor version for stable releases. + # As provided in PHP_VERSION + ;; +esac + +# NOTE: php 7.3-8.0 (but not 8.1) will fail in valgrind without "--with-valgrind" because php-src uses custom assembly for its implementation of zend_string_equals +# In order to fix those false positives, a different set of images would be needed where (1) valgrind was installed before compiling php, and (2) php was compiled with support for valgrind (--with-valgrind) to avoid false positives +# docker run --rm $DOCKER_IMAGE ci/test_inner_valgrind.sh +CFLAGS="-DZEND_RC_DEBUG=1" +PHP_CONFIGURE_ARGS="--disable-all --enable-zts --enable-debug --enable-cgi --enable-session --enable-json" +if [[ "$ARCHITECTURE" == i386 ]]; then + PHP_IMAGE="$ARCHITECTURE/php" + DOCKER_IMAGE_VALGRIND="igbinary-valgrind-test-runner:$ARCHITECTURE-$PHP_VERSION_FULL" + # php infers the x86_64 platform and php fails to build the corresponding assembly. + # igbinary tests don't even involve Fibers + PHP_CONFIGURE_ARGS="$PHP_CONFIGURE_ARGS --disable-fiber-asm" + CFLAGS="$CFLAGS -m32" +else + PHP_IMAGE="php" + DOCKER_IMAGE_VALGRIND="igbinary-valgrind-test-runner:$ARCHITECTURE-$PHP_VERSION" +fi + +DOCKER_IMAGE_VALGRIND=igbinary-valgrind-test-runner:$PHP_VERSION_FULL +docker build \ + --build-arg="CFLAGS=$CFLAGS" \ + --build-arg="PHP_CONFIGURE_ARGS=$PHP_CONFIGURE_ARGS" \ + --build-arg="PHP_IMAGE=$PHP_IMAGE" \ + --build-arg="PHP_VERSION=$PHP_VERSION" \ + --build-arg="PHP_VERSION_FULL=$PHP_VERSION_FULL" \ + --tag="$DOCKER_IMAGE_VALGRIND" \ + -f ci/Dockerfile.valgrind . +docker run --rm $DOCKER_IMAGE_VALGRIND ci/test_inner_valgrind.sh diff --git a/ci/test_inner.sh b/ci/test_inner.sh new file mode 100755 index 00000000..2033398a --- /dev/null +++ b/ci/test_inner.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# -x Exit immediately if any command fails +# -e Echo all commands being executed. +# -u fail for undefined variables +set -xeu +echo "Run tests in docker" +php --version +php --ini +cp ci/run-tests-parallel.php run-tests.php +REPORT_EXIT_STATUS=1 make test TESTS="-j$(nproc) -P -q --show-diff" +echo "Test that package.xml is valid" +pecl package diff --git a/ci/test_inner_valgrind.sh b/ci/test_inner_valgrind.sh new file mode 100755 index 00000000..0e7381cb --- /dev/null +++ b/ci/test_inner_valgrind.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# -x Exit immediately if any command fails +# -e Echo all commands being executed. +# -u fail for undefined variables +set -xeu +echo "Run tests in docker" +php --version +php --ini +cp ci/run-tests-parallel.php run-tests.php +REPORT_EXIT_STATUS=1 make test TESTS="-j$(nproc) -P -q --show-diff -m --show-mem" +echo "Test that package.xml is valid" +pecl package diff --git a/ci/wipe_travis_cache.sh b/ci/wipe_travis_cache.sh new file mode 100755 index 00000000..ca3b4c82 --- /dev/null +++ b/ci/wipe_travis_cache.sh @@ -0,0 +1,14 @@ +#!/bin/bash -xeu +# Ensure that ~/travis_cache is a folder with 0 files in it. +# This avoids accidentally caching left over files in s3. +CACHE_DIR="$HOME/travis_cache" +if [ -d "$CACHE_DIR" ]; then + if [ find "$CACHE_DIR" -mindepth 1 -print -quit | grep -q . ]; then + echo "$CACHE_DIR was not empty, clearing" + rm -rf $CACHE_DIR; + mkdir "$CACHE_DIR" + fi +else + echo "$CACHE_DIR did not exist, creating an empty directory to cache" + mkdir "$CACHE_DIR" +fi diff --git a/composer.json b/composer.json new file mode 100644 index 00000000..110cba0f --- /dev/null +++ b/composer.json @@ -0,0 +1,31 @@ +{ + "name": "igbinary/igbinary", + "type": "php-ext", + "homepage": "https://github.com/igbinary/igbinary", + "php-ext": { + "extension-name": "igbinary" + }, + "license": "BSD-3-Clause", + "description": "Igbinary is a drop in replacement for the standard php serializer. Instead of the time and space consuming textual representation used by PHP's serialize(), igbinary stores php data structures in a compact binary form. Savings are significant when using memcached or similar memory based storages for serialized data.", + "authors": [ + { + "name": "Oleg Grenrus", + "email": "oleg.grenrus@iki.fi" + }, + { + "name": "Pierre Joye", + "email": "pierre@php.net" + }, + { + "name": "Teddy Grenman", + "email": "teddy.pecl@luuseri.com" + }, + { + "name": "Tyson Andre", + "email": "tandre@php.net" + } + ], + "require": { + "php": ">= 7.0.0" + } +} diff --git a/config.m4 b/config.m4 index 2cc15d62..eeca2e6d 100644 --- a/config.m4 +++ b/config.m4 @@ -20,19 +20,31 @@ if test "$PHP_IGBINARY" != "no"; then AC_CHECK_HEADERS([stddef.h],, AC_MSG_ERROR([stddef.h not exists])) AC_CHECK_HEADERS([stdint.h],, AC_MSG_ERROR([stdint.h not exists])) - AC_MSG_CHECKING([for APC/APCU includes]) - if test -f "$phpincludedir/ext/apcu/apc_serializer.h"; then - apc_inc_path="$phpincludedir" - AC_MSG_RESULT([APCU in $apc_inc_path]) - AC_DEFINE(HAVE_APCU_SUPPORT,1,[Whether to enable apcu support]) - elif test -f "$phpincludedir/ext/apc/apc_serializer.h"; then - apc_inc_path="$phpincludedir" - AC_MSG_RESULT([APC in $apc_inc_path]) - AC_DEFINE(HAVE_APC_SUPPORT,1,[Whether to enable apc support]) - elif test -f "${srcdir}/apc_serializer.h"; then - AC_MSG_RESULT([apc_serializer.h bundled]) - AC_DEFINE(HAVE_APC_SUPPORT,1,[Whether to enable apc support]) - AC_DEFINE(USE_BUNDLED_APC,1,[Whether to use bundled apc includes]) + AC_MSG_CHECKING(PHP version) + + PHP_IGBINARY_SRC_FILES="src/php7/igbinary.c src/php7/hash_si.c src/php7/hash_si_ptr.c" + if test -n "$phpincludedir" -a -d "$phpincludedir"; then + IGBINARY_PHPINCLUDEDIR=$phpincludedir + else + IGBINARY_PHPINCLUDEDIR=$abs_srcdir + fi + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #include <$IGBINARY_PHPINCLUDEDIR/main/php_version.h> + ]], [[ +#if PHP_MAJOR_VERSION < 7 +#error PHP < 7 +#endif + ]])],[ + AC_MSG_RESULT([PHP 7 or newer]) + ],[ + AC_MSG_ERROR([PHP 5 is not supported by igbinary 3. Use igbinary 2 instead for PHP5 support.]) + ]) + + AC_MSG_CHECKING([for APCu includes]) + if test -f "$IGBINARY_PHPINCLUDEDIR/ext/apcu/apc_serializer.h"; then + apc_inc_path="$IGBINARY_PHPINCLUDEDIR" + AC_MSG_RESULT([APCu in $apc_inc_path]) + AC_DEFINE(HAVE_APCU_SUPPORT,1,[Whether to enable APCu support]) else AC_MSG_RESULT([not found]) fi @@ -41,28 +53,30 @@ if test "$PHP_IGBINARY" != "no"; then dnl GCC AC_MSG_CHECKING(compiler type) - if test ! -z "`$CC --version | grep -i GCC`"; then - AC_MSG_RESULT(gcc) + if test ! -z "`$CC --version | grep -i CLANG`"; then + AC_MSG_RESULT(clang) if test -z "`echo $CFLAGS | grep -- -O0`"; then - PHP_IGBINARY_CFLAGS="-Wall -Wpointer-arith -Wmissing-prototypes -Wstrict-prototypes -Wcast-align -Wshadow -Wwrite-strings -Wswitch -Winline -finline-limit=10000 --param large-function-growth=10000 --param inline-unit-growth=10000" + PHP_IGBINARY_CFLAGS="$CFLAGS -Wall -O2" fi - elif test ! -z "`$CC --version | grep -i ICC`"; then - AC_MSG_RESULT(icc) - if test -z "`echo $CFLAGS | grep -- -O0`"; then - PHP_IGBINARY_CFLAGS="-no-prec-div -O3 -x0 -unroll2" + elif test "$GCC" = yes; then + AC_MSG_RESULT(gcc) + if test -z "`echo $CFLAGS | grep -- '-O[0123]'`"; then + PHP_IGBINARY_CFLAGS="$CFLAGS -O2 -Wall -Wpointer-arith -Wcast-align -Wwrite-strings -Wswitch" fi - elif test ! -z "`$CC --version | grep -i CLANG`"; then - AC_MSG_RESULT(clang) + elif test "$ICC" = yes; then + AC_MSG_RESULT(icc) if test -z "`echo $CFLAGS | grep -- -O0`"; then - PHP_IGBINARY_CFLAGS="-Wall -O2" + PHP_IGBINARY_CFLAGS="$CFLAGS -no-prec-div -O3 -x0 -unroll2" fi else AC_MSG_RESULT(other) fi PHP_ADD_MAKEFILE_FRAGMENT(Makefile.bench) - PHP_INSTALL_HEADERS([ext/igbinary], [igbinary.h]) - PHP_NEW_EXTENSION(igbinary, igbinary.c hash_si.c, $ext_shared,, $PHP_IGBINARY_CFLAGS) + PHP_INSTALL_HEADERS([ext/igbinary], [igbinary.h src/php7/igbinary.h php_igbinary.h src/php7/php_igbinary.h]) + PHP_NEW_EXTENSION(igbinary, $PHP_IGBINARY_SRC_FILES, $ext_shared,, $PHP_IGBINARY_CFLAGS) PHP_ADD_EXTENSION_DEP(igbinary, session, true) + AC_DEFINE(HAVE_IGBINARY, 1, [Have igbinary support]) + PHP_ADD_BUILD_DIR($abs_builddir/src/php7, 1) PHP_SUBST(IGBINARY_SHARED_LIBADD) fi diff --git a/config.w32 b/config.w32 index eb9a9c56..14e23c25 100644 --- a/config.w32 +++ b/config.w32 @@ -1,14 +1,40 @@ -// $Id$ // vim:ft=javascript ARG_ENABLE("igbinary", "whether to enable igbinary support", "no"); if (PHP_IGBINARY == "yes") { - res = CHECK_HEADER_ADD_INCLUDE("apc_serializer.h", "CFLAGS_IGBINARY", "..\\pecl\\apc;ext\\apc"); - if (res) { - AC_DEFINE('HAVE_APC_SUPPORT', 1, 'Whether to enable apc support') + var dll = get_define('PHPDLL'); + var is_php5 = null != dll.match(/^php5/); + // php 8.x also uses src/php7 + var is_php7_or_newer = !is_php5 && null != dll.match(/^php[78]/) + + if (CHECK_HEADER_ADD_INCLUDE("apc_serializer.h", "CFLAGS_IGBINARY", "..\\pecl\\apcu;ext\\apcu")) { + AC_DEFINE('HAVE_APCU_SUPPORT', 1, "Whether to enable apcu support"); + if (!CHECK_HEADER_ADD_INCLUDE("apc_serializer.h", "CFLAGS_IGBINARY", "ext\\apcu")) { + // Workaround to allow configuring and making apcu and igbinary at the same time. + // If they aren't available in ext/apcu, expect them in ../pecl/apcu + AC_DEFINE('HAVE_APCU_HEADERS_IN_PECL', 1, "Whether or not apcu headers exist only in pecl folder") + } + } + var old_conf_dir = configure_module_dirname; + var php_igbinary_src_files; + var subdir; + + /* Copied from solr config.w32 */ + /* XXX tricky job here, override configure_module_dirname, define the basic extension, + then set it back*/ + if (is_php5) { + ERROR("PHP 5 is not supported by igbinary 3 - Use igbinary 2.x"); + } else if (is_php7_or_newer) { + subdir = "src\\php7"; + php_igbinary_src_files = "igbinary.c hash_si.c hash_si_ptr.c" + } else { + ERROR("Cannot match any known PHP version with '" + dll + "'"); } - EXTENSION("igbinary", "igbinary.c hash_si.c"); + configure_module_dirname = configure_module_dirname + "\\" + subdir; + EXTENSION("igbinary", php_igbinary_src_files); + configure_module_dirname = old_conf_dir; AC_DEFINE('HAVE_IGBINARY', 1, 'Have igbinary support', false); - ADD_EXTENSION_DEP('igbinary', 'session'); + ADD_EXTENSION_DEP('igbinary', 'session', true); + PHP_INSTALL_HEADERS('ext/igbinary', 'igbinary.h php_igbinary.h ' + subdir + '\\igbinary.h ' + subdir + '\\php_igbinary.h'); } diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..da55f8f2 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,9 @@ +ARG PHP_VERSION +FROM php:$PHP_VERSION +WORKDIR /code +ENV NO_INTERACTION=1 +RUN mkdir -p src/php7/ +ADD src/php7/*.c src/php7/*.h ./src/php7/ +ADD *.c *.h config.m4 ./ +RUN phpize && ./configure && make -j$(nproc) +ADD tests ./tests diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..4358460e --- /dev/null +++ b/docker/README.md @@ -0,0 +1,6 @@ +Dockerized test scripts +======================= + +This uses https://docs.docker.com/desktop/multi-arch/ + +Currently, this is only used to test locally. See https://github.com/docker/buildx#building-multi-platform-images for installing binfmt with `docker run --privileged`. diff --git a/docker/test_S390X.sh b/docker/test_S390X.sh new file mode 100755 index 00000000..106decf0 --- /dev/null +++ b/docker/test_S390X.sh @@ -0,0 +1,4 @@ +#!/bin/bash +IMAGE_NAME=pecl-igbinary-s390x-emulated-8.0.13 +docker build --platform S390X --tag=$IMAGE_NAME --build-arg=PHP_VERSION=8.0.13 -f docker/Dockerfile . || exit 1 +exec docker run --rm --platform S390X $IMAGE_NAME /bin/bash -c 'export CFLAGS=-O1; make test TESTS="--show-diff -j$(nproc)"' diff --git a/docker/test_arm64_emulated.sh b/docker/test_arm64_emulated.sh new file mode 100755 index 00000000..e512bf96 --- /dev/null +++ b/docker/test_arm64_emulated.sh @@ -0,0 +1,4 @@ +#!/bin/bash +IMAGE_NAME=pecl-igbinary-arm64-emulated-8.1.0 +docker build --platform arm64 --tag=$IMAGE_NAME --build-arg=PHP_VERSION=8.1.0 -f docker/Dockerfile . || exit 1 +exec docker run $IMAGE_NAME make test TESTS="--show-diff -j$(nproc)" diff --git a/hash_si.c b/hash_si.c deleted file mode 100644 index 07a4c21f..00000000 --- a/hash_si.c +++ /dev/null @@ -1,268 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | See COPYING file for further copyright information | - +----------------------------------------------------------------------+ - | Author: Oleg Grenrus | - | See CREDITS for contributors | - +----------------------------------------------------------------------+ -*/ - -#ifdef PHP_WIN32 -# include "ig_win32.h" -#endif - -#include -#include -#include - -#include - -#include "hash.h" -#include "zend.h" - -/* {{{ nextpow2 */ -/** Next power of 2. - * @param n Integer. - * @return next to n power of 2 . - */ -inline static uint32_t nextpow2(uint32_t n) { - uint32_t m = 1; - while (m < n) { - m = m << 1; - } - - return m; -} -/* }}} */ -/* {{{ hash_si_init */ -int hash_si_init(struct hash_si *h, size_t size) { - size = nextpow2(size); - - h->size = size; - h->used = 0; - h->data = (struct hash_si_pair *) malloc(sizeof(struct hash_si_pair) * size); - if (h->data == NULL) { - return 1; - } - - memset(h->data, 0, sizeof(struct hash_si_pair) * size); - - return 0; -} -/* }}} */ -/* {{{ hash_si_deinit */ -void hash_si_deinit(struct hash_si *h) { - size_t i; - - for (i = 0; i < h->size; i++) { - if (h->data[i].key != NULL) { - free(h->data[i].key); - } - } - - free(h->data); - - h->size = 0; - h->used = 0; -} -/* }}} */ -/* {{{ _hash_si_find */ -/** Returns index of key, or where it should be. - * @param h Pointer to hash_si struct. - * @param key Pointer to key. - * @param key_len Key length. - * @return index. - */ -inline static size_t _hash_si_find(struct hash_si *h, const char *key, size_t key_len) { - uint32_t hv; - size_t size; - - assert(h != NULL); - - size = h->size; - hv = zend_inline_hash_func(key, key_len) & (h->size-1); - - while (size > 0 && - h->data[hv].key != NULL && - (h->data[hv].key_len != key_len || memcmp(h->data[hv].key, key, key_len) != 0)) { - /* linear prob */ - hv = (hv + 1) & (h->size-1); - size--; - } - - return hv; -} -/* }}} */ -/* {{{ hash_si_remove */ -int hash_si_remove(struct hash_si *h, const char *key, size_t key_len, uint32_t *value) { - uint32_t hv; - uint32_t j, k; - - assert(h != NULL); - - hv = _hash_si_find(h, key, key_len); - - /* dont exists */ - if (h->data[hv].key == NULL) { - return 1; - } - - h->used--; - - free(h->data[hv].key); - - if (value != NULL) - *value = h->data[hv].value; - - j = (hv + 1) & (h->size-1); - while (h->data[j].key != NULL) { - k = zend_inline_hash_func(h->data[j].key, strlen(h->data[j].key)) & (h->size-1); - if ((j > hv && (k <= hv || k > j)) || (j < hv && (k <= hv && k > j))) { - h->data[hv].key = h->data[j].key; - h->data[hv].key_len = h->data[j].key_len; - h->data[hv].value = h->data[j].value; - - hv = j; - } - j = (j + 1) & (h->size-1); - } - h->data[hv].key = NULL; - - - return 0; -/* - * loop - * j := (j+1) modulo num_slots - * if slot[j] is unoccupied - * exit loop - * k := hash(slot[j].key) modulo num_slots - * if (j > i and (k <= i or k > j)) or - * (j < i and (k <= i and k > j)) (note 2) - * slot[i] := slot[j] - * i := j - * mark slot[i] as unoccupied - * - * For all records in a cluster, there must be no vacant slots between their natural - * hash position and their current position (else lookups will terminate before finding - * the record). At this point in the pseudocode, i is a vacant slot that might be - * invalidating this property for subsequent records in the cluster. j is such a - * subsequent record. k is the raw hash where the record at j would naturally land in - * the hash table if there were no collisions. This test is asking if the record at j - * is invalidly positioned with respect to the required properties of a cluster now - * that i is vacant. - * - * Another technique for removal is simply to mark the slot as deleted. However - * this eventually requires rebuilding the table simply to remove deleted records. - * The methods above provide O(1) updating and removal of existing records, with - * occasional rebuilding if the high water mark of the table size grows. - */ -} -/* }}} */ -/* {{{ hash_si_rehash */ -/** Rehash/resize hash_si. - * @param h Pointer to hash_si struct. - */ -inline static void hash_si_rehash(struct hash_si *h) { - uint32_t hv; - size_t i; - struct hash_si newh; - - assert(h != NULL); - - hash_si_init(&newh, h->size * 2); - - for (i = 0; i < h->size; i++) { - if (h->data[i].key != NULL) { - hv = _hash_si_find(&newh, h->data[i].key, h->data[i].key_len); - newh.data[hv].key = h->data[i].key; - newh.data[hv].key_len = h->data[i].key_len; - newh.data[hv].value = h->data[i].value; - } - } - - free(h->data); - h->data = newh.data; - h->size *= 2; -} -/* }}} */ -/* {{{ hash_si_insert */ -int hash_si_insert(struct hash_si *h, const char *key, size_t key_len, uint32_t value) { - uint32_t hv; - - if (h->size / 4 * 3 < h->used + 1) { - hash_si_rehash(h); - } - - hv = _hash_si_find(h, key, key_len); - - if (h->data[hv].key == NULL) { - h->data[hv].key = (char *) malloc(key_len + 1); - if (h->data[hv].key == NULL) { - return 1; - } - memcpy(h->data[hv].key, key, key_len); - h->data[hv].key[key_len] = '\0'; - h->data[hv].key_len = key_len; - - h->used++; - } else { - return 2; - } - - h->data[hv].value = value; - - return 0; -} -/* }}} */ -/* {{{ hash_si_find */ -int hash_si_find(struct hash_si *h, const char *key, size_t key_len, uint32_t *value) { - uint32_t hv; - - assert(h != NULL); - - hv = _hash_si_find(h, key, key_len); - - if (h->data[hv].key == NULL) { - return 1; - } else { - *value = h->data[hv].value; - return 0; - } -} -/* }}} */ -/* {{{ hash_si_traverse */ -void hash_si_traverse(struct hash_si *h, int (*traverse_function) (const char *key, size_t key_len, uint32_t value)) { - size_t i; - - assert(h != NULL && traverse_function != NULL); - - for (i = 0; i < h->size; i++) { - if (h->data[i].key != NULL && traverse_function(h->data[i].key, h->data[i].key_len, h->data[i].value) != 1) { - return; - } - } -} -/* }}} */ -/* {{{ hash_si_size */ -size_t hash_si_size(struct hash_si *h) { - assert(h != NULL); - - return h->used; -} -/* }}} */ -/* {{{ hash_si_capacity */ -size_t hash_si_capacity(struct hash_si *h) { - assert(h != NULL); - - return h->size; -} -/* }}} */ - -/* - * Local variables: - * tab-width: 2 - * c-basic-offset: 4 - * End: - * vim600: noet sw=4 ts=4 fdm=marker - * vim<600: noet sw=4 ts=4 - */ diff --git a/ig_win32.h b/ig_win32.h deleted file mode 100644 index 2c92e462..00000000 --- a/ig_win32.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef _IG_WIN32_H -#define _IG_WIN32_H - -#if PHP_WIN32 -# include "win32/php_stdint.h" -# ifndef inline -# define inline __inline -# endif - -# ifndef __cplusplus -# if !0 -typedef enum { false = 0, true = 1 } _Bool; -# endif -# else -typedef bool _Bool; -# endif -# define bool _Bool - -# define false 0 -# define true 1 -# define __bool_true_false_are_defined 1 -#endif - -#endif diff --git a/igbinary.c b/igbinary.c deleted file mode 100644 index e3e98113..00000000 --- a/igbinary.c +++ /dev/null @@ -1,2416 +0,0 @@ -/* - +----------------------------------------------------------------------+ - | See COPYING file for further copyright information | - +----------------------------------------------------------------------+ - | Author: Oleg Grenrus | - | See CREDITS for contributors | - +----------------------------------------------------------------------+ -*/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef PHP_WIN32 -# include "ig_win32.h" -#endif - -#include "php.h" -#include "php_ini.h" -#include "zend_dynamic_array.h" -#include "zend_alloc.h" -#include "ext/standard/info.h" -#include "ext/standard/php_var.h" - -#if HAVE_PHP_SESSION -# include "ext/session/php_session.h" -#endif /* HAVE_PHP_SESSION */ - -#include "ext/standard/php_incomplete_class.h" - -#if defined(HAVE_APCU_SUPPORT) -# include "ext/apcu/apc_serializer.h" -#elif defined(HAVE_APC_SUPPORT) -# if USE_BUNDLED_APC -# include "apc_serializer.h" -# else -# include "ext/apc/apc_serializer.h" -# endif -#endif /* HAVE_APCU_SUPPORT || HAVE_APC_SUPPORT */ - -#include "php_igbinary.h" - -#include "igbinary.h" - -#include - -#ifndef PHP_WIN32 -# include -# include -# include -#endif - - -#include -#include "hash.h" - -#if HAVE_PHP_SESSION -/** Session serializer function prototypes. */ -PS_SERIALIZER_FUNCS(igbinary); -#endif /* HAVE_PHP_SESSION */ - -#if defined(HAVE_APC_SUPPORT) || defined(HAVE_APCU_SUPPORT) -/** Apc serializer function prototypes */ -static int APC_SERIALIZER_NAME(igbinary) (APC_SERIALIZER_ARGS); -static int APC_UNSERIALIZER_NAME(igbinary) (APC_UNSERIALIZER_ARGS); -#endif - -/* {{{ Types */ -enum igbinary_type { - /* 00 */ igbinary_type_null, /**< Null. */ - - /* 01 */ igbinary_type_ref8, /**< Array reference. */ - /* 02 */ igbinary_type_ref16, /**< Array reference. */ - /* 03 */ igbinary_type_ref32, /**< Array reference. */ - - /* 04 */ igbinary_type_bool_false, /**< Boolean true. */ - /* 05 */ igbinary_type_bool_true, /**< Boolean false. */ - - /* 06 */ igbinary_type_long8p, /**< Long 8bit positive. */ - /* 07 */ igbinary_type_long8n, /**< Long 8bit negative. */ - /* 08 */ igbinary_type_long16p, /**< Long 16bit positive. */ - /* 09 */ igbinary_type_long16n, /**< Long 16bit negative. */ - /* 0a */ igbinary_type_long32p, /**< Long 32bit positive. */ - /* 0b */ igbinary_type_long32n, /**< Long 32bit negative. */ - - /* 0c */ igbinary_type_double, /**< Double. */ - - /* 0d */ igbinary_type_string_empty, /**< Empty string. */ - - /* 0e */ igbinary_type_string_id8, /**< String id. */ - /* 0f */ igbinary_type_string_id16, /**< String id. */ - /* 10 */ igbinary_type_string_id32, /**< String id. */ - - /* 11 */ igbinary_type_string8, /**< String. */ - /* 12 */ igbinary_type_string16, /**< String. */ - /* 13 */ igbinary_type_string32, /**< String. */ - - /* 14 */ igbinary_type_array8, /**< Array. */ - /* 15 */ igbinary_type_array16, /**< Array. */ - /* 16 */ igbinary_type_array32, /**< Array. */ - - /* 17 */ igbinary_type_object8, /**< Object. */ - /* 18 */ igbinary_type_object16, /**< Object. */ - /* 19 */ igbinary_type_object32, /**< Object. */ - - /* 1a */ igbinary_type_object_id8, /**< Object string id. */ - /* 1b */ igbinary_type_object_id16, /**< Object string id. */ - /* 1c */ igbinary_type_object_id32, /**< Object string id. */ - - /* 1d */ igbinary_type_object_ser8, /**< Object serialized data. */ - /* 1e */ igbinary_type_object_ser16, /**< Object serialized data. */ - /* 1f */ igbinary_type_object_ser32, /**< Object serialized data. */ - - /* 20 */ igbinary_type_long64p, /**< Long 64bit positive. */ - /* 21 */ igbinary_type_long64n, /**< Long 64bit negative. */ - - /* 22 */ igbinary_type_objref8, /**< Object reference. */ - /* 23 */ igbinary_type_objref16, /**< Object reference. */ - /* 24 */ igbinary_type_objref32, /**< Object reference. */ - - /* 25 */ igbinary_type_ref, /**< Simple reference */ -}; - -/** Serializer data. - * @author Oleg Grenrus - */ -struct igbinary_serialize_data { - uint8_t *buffer; /**< Buffer. */ - size_t buffer_size; /**< Buffer size. */ - size_t buffer_capacity; /**< Buffer capacity. */ - bool scalar; /**< Serializing scalar. */ - bool compact_strings; /**< Check for duplicate strings. */ - struct hash_si strings; /**< Hash of already serialized strings. */ - struct hash_si objects; /**< Hash of already serialized objects. */ - int string_count; /**< Serialized string count, used for back referencing */ - int error; /**< Error number. Not used. */ - struct igbinary_memory_manager mm; /**< Memory management functions. */ -}; - -/** String/len pair for the igbinary_unserializer_data. - * @author Oleg Grenrus - * @see igbinary_unserialize_data. - */ -struct igbinary_unserialize_string_pair { - char *data; /**< Data. */ - size_t len; /**< Data length. */ -}; - -/** Unserializer data. - * @author Oleg Grenrus - */ -struct igbinary_unserialize_data { - uint8_t *buffer; /**< Buffer. */ - size_t buffer_size; /**< Buffer size. */ - size_t buffer_offset; /**< Current read offset. */ - - struct igbinary_unserialize_string_pair *strings; /**< Unserialized strings. */ - size_t strings_count; /**< Unserialized string count. */ - size_t strings_capacity; /**< Unserialized string array capacity. */ - - void **references; /**< Unserialized Arrays/Objects. */ - size_t references_count; /**< Unserialized array/objects count. */ - size_t references_capacity; /**< Unserialized array/object array capacity. */ - - int error; /**< Error number. Not used. */ - smart_str string0_buf; /**< Temporary buffer for strings */ -}; -/* }}} */ -/* {{{ Memory allocator wrapper prototypes */ -static inline void *igbinary_mm_wrapper_malloc(size_t size, void *context); -static inline void *igbinary_mm_wrapper_realloc(void *ptr, size_t size, void *context); -static inline void igbinary_mm_wrapper_free(void *ptr, void *context); -/* }}} */ -/* {{{ Serializing functions prototypes */ -inline static int igbinary_serialize_data_init(struct igbinary_serialize_data *igsd, bool scalar, struct igbinary_memory_manager *memory_manager TSRMLS_DC); -inline static void igbinary_serialize_data_deinit(struct igbinary_serialize_data *igsd, int free_buffer TSRMLS_DC); - -inline static int igbinary_serialize_header(struct igbinary_serialize_data *igsd TSRMLS_DC); - -inline static int igbinary_serialize8(struct igbinary_serialize_data *igsd, uint8_t i TSRMLS_DC); -inline static int igbinary_serialize16(struct igbinary_serialize_data *igsd, uint16_t i TSRMLS_DC); -inline static int igbinary_serialize32(struct igbinary_serialize_data *igsd, uint32_t i TSRMLS_DC); -inline static int igbinary_serialize64(struct igbinary_serialize_data *igsd, uint64_t i TSRMLS_DC); - -inline static int igbinary_serialize_null(struct igbinary_serialize_data *igsd TSRMLS_DC); -inline static int igbinary_serialize_bool(struct igbinary_serialize_data *igsd, int b TSRMLS_DC); -inline static int igbinary_serialize_long(struct igbinary_serialize_data *igsd, long l TSRMLS_DC); -inline static int igbinary_serialize_double(struct igbinary_serialize_data *igsd, double d TSRMLS_DC); -inline static int igbinary_serialize_string(struct igbinary_serialize_data *igsd, char *s, size_t len TSRMLS_DC); -inline static int igbinary_serialize_chararray(struct igbinary_serialize_data *igsd, const char *s, size_t len TSRMLS_DC); - -inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, zval *z, bool object, bool incomplete_class TSRMLS_DC); -inline static int igbinary_serialize_array_ref(struct igbinary_serialize_data *igsd, zval *z, bool object TSRMLS_DC); -inline static int igbinary_serialize_array_sleep(struct igbinary_serialize_data *igsd, zval *z, HashTable *ht, zend_class_entry *ce, bool incomplete_class TSRMLS_DC); -inline static int igbinary_serialize_object_name(struct igbinary_serialize_data *igsd, const char *name, size_t name_len TSRMLS_DC); -inline static int igbinary_serialize_object(struct igbinary_serialize_data *igsd, zval *z TSRMLS_DC); - -static int igbinary_serialize_zval(struct igbinary_serialize_data *igsd, zval *z TSRMLS_DC); -/* }}} */ -/* {{{ Unserializing functions prototypes */ -inline static int igbinary_unserialize_data_init(struct igbinary_unserialize_data *igsd TSRMLS_DC); -inline static void igbinary_unserialize_data_deinit(struct igbinary_unserialize_data *igsd TSRMLS_DC); - -inline static int igbinary_unserialize_header(struct igbinary_unserialize_data *igsd TSRMLS_DC); - -inline static uint8_t igbinary_unserialize8(struct igbinary_unserialize_data *igsd TSRMLS_DC); -inline static uint16_t igbinary_unserialize16(struct igbinary_unserialize_data *igsd TSRMLS_DC); -inline static uint32_t igbinary_unserialize32(struct igbinary_unserialize_data *igsd TSRMLS_DC); -inline static uint64_t igbinary_unserialize64(struct igbinary_unserialize_data *igsd TSRMLS_DC); - -inline static int igbinary_unserialize_long(struct igbinary_unserialize_data *igsd, enum igbinary_type t, long *ret TSRMLS_DC); -inline static int igbinary_unserialize_double(struct igbinary_unserialize_data *igsd, enum igbinary_type t, double *ret TSRMLS_DC); -inline static int igbinary_unserialize_string(struct igbinary_unserialize_data *igsd, enum igbinary_type t, char **s, size_t *len TSRMLS_DC); -inline static int igbinary_unserialize_chararray(struct igbinary_unserialize_data *igsd, enum igbinary_type t, char **s, size_t *len TSRMLS_DC); - -inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z, int object TSRMLS_DC); -inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z TSRMLS_DC); -inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z, zend_class_entry *ce TSRMLS_DC); -inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z TSRMLS_DC); - -static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval **z TSRMLS_DC); -/* }}} */ -/* {{{ arginfo */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_igbinary_serialize, 0, 0, 1) - ZEND_ARG_INFO(0, value) -ZEND_END_ARG_INFO() - -ZEND_BEGIN_ARG_INFO_EX(arginfo_igbinary_unserialize, 0, 0, 1) - ZEND_ARG_INFO(0, str) -ZEND_END_ARG_INFO() -/* }}} */ -/* {{{ igbinary_functions[] */ -/** Exported php functions. */ -zend_function_entry igbinary_functions[] = { - PHP_FE(igbinary_serialize, arginfo_igbinary_serialize) - PHP_FE(igbinary_unserialize, arginfo_igbinary_unserialize) - {NULL, NULL, NULL} -}; -/* }}} */ - -/* {{{ igbinary dependencies */ -#if ZEND_MODULE_API_NO >= 20050922 -static const zend_module_dep igbinary_module_deps[] = { - ZEND_MOD_REQUIRED("standard") -#ifdef HAVE_PHP_SESSION - ZEND_MOD_REQUIRED("session") -#endif -#if defined(HAVE_APCU_SUPPORT) - ZEND_MOD_OPTIONAL("apcu") -#elif defined(HAVE_APC_SUPPORT) - ZEND_MOD_OPTIONAL("apc") -#endif - {NULL, NULL, NULL} -}; -#endif -/* }}} */ - -/* {{{ igbinary_module_entry */ -zend_module_entry igbinary_module_entry = { -#if ZEND_MODULE_API_NO >= 20050922 - STANDARD_MODULE_HEADER_EX, NULL, - igbinary_module_deps, -#elif ZEND_MODULE_API_NO >= 20010901 - STANDARD_MODULE_HEADER, -#endif - "igbinary", - igbinary_functions, - PHP_MINIT(igbinary), - PHP_MSHUTDOWN(igbinary), - NULL, - NULL, - PHP_MINFO(igbinary), -#if ZEND_MODULE_API_NO >= 20010901 - PHP_IGBINARY_VERSION, /* Replace with version number for your extension */ -#endif - STANDARD_MODULE_PROPERTIES -}; -/* }}} */ - -ZEND_DECLARE_MODULE_GLOBALS(igbinary) - -/* {{{ ZEND_GET_MODULE */ -#ifdef COMPILE_DL_IGBINARY -ZEND_GET_MODULE(igbinary) -#endif -/* }}} */ - -/* {{{ INI entries */ -PHP_INI_BEGIN() - STD_PHP_INI_BOOLEAN("igbinary.compact_strings", "1", PHP_INI_ALL, OnUpdateBool, compact_strings, zend_igbinary_globals, igbinary_globals) -PHP_INI_END() -/* }}} */ - -/* {{{ php_igbinary_init_globals */ -static void php_igbinary_init_globals(zend_igbinary_globals *igbinary_globals) { - igbinary_globals->compact_strings = 1; -} -/* }}} */ - -/* {{{ PHP_MINIT_FUNCTION */ -PHP_MINIT_FUNCTION(igbinary) { - (void) type; - (void) module_number; - ZEND_INIT_MODULE_GLOBALS(igbinary, php_igbinary_init_globals, NULL); - -#if HAVE_PHP_SESSION - php_session_register_serializer("igbinary", - PS_SERIALIZER_ENCODE_NAME(igbinary), - PS_SERIALIZER_DECODE_NAME(igbinary)); -#endif - -#if defined(HAVE_APC_SUPPORT) || defined(HAVE_APCU_SUPPORT) - apc_register_serializer("igbinary", - APC_SERIALIZER_NAME(igbinary), - APC_UNSERIALIZER_NAME(igbinary), - NULL TSRMLS_CC); -#endif - - REGISTER_INI_ENTRIES(); - - return SUCCESS; -} -/* }}} */ -/* {{{ PHP_MSHUTDOWN_FUNCTION */ -PHP_MSHUTDOWN_FUNCTION(igbinary) { - (void) type; - (void) module_number; - -#ifdef ZTS - ts_free_id(igbinary_globals_id); -#endif - - /* - * unregister serializer? - */ - UNREGISTER_INI_ENTRIES(); - - return SUCCESS; -} -/* }}} */ -/* {{{ PHP_MINFO_FUNCTION */ -PHP_MINFO_FUNCTION(igbinary) { - (void) zend_module; - php_info_print_table_start(); - php_info_print_table_row(2, "igbinary support", "enabled"); - php_info_print_table_row(2, "igbinary version", PHP_IGBINARY_VERSION); -#if defined(HAVE_APCU_SUPPORT) - php_info_print_table_row(2, "igbinary APCU serializer ABI", APC_SERIALIZER_ABI); -#elif defined(HAVE_APC_SUPPORT) - php_info_print_table_row(2, "igbinary APC serializer ABI", APC_SERIALIZER_ABI); -#else - php_info_print_table_row(2, "igbinary APC serializer ABI", "no"); -#endif -#if HAVE_PHP_SESSION - php_info_print_table_row(2, "igbinary session support", "yes"); -#else - php_info_print_table_row(2, "igbinary session support", "no"); -#endif - php_info_print_table_end(); - - DISPLAY_INI_ENTRIES(); -} -/* }}} */ -/* {{{ Memory allocator wrappers */ -static inline void *igbinary_mm_wrapper_malloc(size_t size, void *context) -{ - return emalloc(size); -} - -static inline void *igbinary_mm_wrapper_realloc(void *ptr, size_t size, void *context) -{ - return erealloc(ptr, size); -} - -static inline void igbinary_mm_wrapper_free(void *ptr, void *context) -{ - return efree(ptr); -} -/* }}} */ -/* {{{ int igbinary_serialize(uint8_t**, size_t*, zval*) */ -IGBINARY_API int igbinary_serialize(uint8_t **ret, size_t *ret_len, zval *z TSRMLS_DC) { - return igbinary_serialize_ex(ret, ret_len, z, NULL TSRMLS_CC); -} -/* }}} */ -/* {{{ int igbinary_serialize_ex(uint8_t**, size_t*, zval*, igbinary_memory_manager*) */ -IGBINARY_API int igbinary_serialize_ex(uint8_t **ret, size_t *ret_len, zval *z, struct igbinary_memory_manager *memory_manager TSRMLS_DC) { - struct igbinary_serialize_data igsd; - uint8_t *tmpbuf; - - if (igbinary_serialize_data_init(&igsd, Z_TYPE_P(z) != IS_OBJECT && Z_TYPE_P(z) != IS_ARRAY, memory_manager TSRMLS_CC)) { - zend_error(E_WARNING, "igbinary_serialize: cannot init igsd"); - return 1; - } - - if (igbinary_serialize_header(&igsd TSRMLS_CC) != 0) { - zend_error(E_WARNING, "igbinary_serialize: cannot write header"); - igbinary_serialize_data_deinit(&igsd, 1 TSRMLS_CC); - return 1; - } - - if (igbinary_serialize_zval(&igsd, z TSRMLS_CC) != 0) { - igbinary_serialize_data_deinit(&igsd, 1 TSRMLS_CC); - return 1; - } - - /* Explicit nul termination */ - if (igbinary_serialize8(&igsd, 0 TSRMLS_CC) != 0) { - igbinary_serialize_data_deinit(&igsd, 1 TSRMLS_CC); - return 1; - } - - /* shrink buffer to the real length, ignore errors */ - tmpbuf = (uint8_t *) igsd.mm.realloc(igsd.buffer, igsd.buffer_size, igsd.mm.context); - if (tmpbuf != NULL) { - igsd.buffer = tmpbuf; - } - - /* Set return values */ - *ret_len = igsd.buffer_size - 1; - *ret = igsd.buffer; - - igbinary_serialize_data_deinit(&igsd, 0 TSRMLS_CC); - - return 0; -} -/* }}} */ -/* {{{ int igbinary_unserialize(const uint8_t *, size_t, zval **) */ -IGBINARY_API int igbinary_unserialize(const uint8_t *buf, size_t buf_len, zval **z TSRMLS_DC) { - struct igbinary_unserialize_data igsd; - - igbinary_unserialize_data_init(&igsd TSRMLS_CC); - - igsd.buffer = (uint8_t *) buf; - igsd.buffer_size = buf_len; - - if (igbinary_unserialize_header(&igsd TSRMLS_CC)) { - igbinary_unserialize_data_deinit(&igsd TSRMLS_CC); - return 1; - } - - if (igbinary_unserialize_zval(&igsd, z TSRMLS_CC)) { - igbinary_unserialize_data_deinit(&igsd TSRMLS_CC); - return 1; - } - - igbinary_unserialize_data_deinit(&igsd TSRMLS_CC); - - return 0; -} -/* }}} */ -/* {{{ proto string igbinary_unserialize(mixed value) */ -PHP_FUNCTION(igbinary_unserialize) { - char *string; - int string_len; - - (void) return_value_ptr; - (void) this_ptr; - (void) return_value_used; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &string, &string_len) == FAILURE) { - RETURN_NULL(); - } - - if (string_len <= 0) { - RETURN_FALSE; - } - - if (igbinary_unserialize((uint8_t *) string, string_len, &return_value TSRMLS_CC) != 0) { - RETURN_NULL(); - } -} -/* }}} */ -/* {{{ proto mixed igbinary_serialize(string value) */ -PHP_FUNCTION(igbinary_serialize) { - zval *z; - uint8_t *string; - size_t string_len; - - (void) return_value_ptr; - (void) this_ptr; - (void) return_value_used; - - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z) == FAILURE) { - RETURN_NULL(); - } - - if (igbinary_serialize(&string, &string_len, z TSRMLS_CC) != 0) { - RETURN_NULL(); - } - - RETVAL_STRINGL((char *)string, string_len, 0); -} -/* }}} */ -#ifdef HAVE_PHP_SESSION -/* {{{ Serializer encode function */ -PS_SERIALIZER_ENCODE_FUNC(igbinary) -{ - struct igbinary_serialize_data igsd; - uint8_t *tmpbuf; - - if (igbinary_serialize_data_init(&igsd, false, NULL TSRMLS_CC)) { - zend_error(E_WARNING, "igbinary_serialize: cannot init igsd"); - return FAILURE; - } - - if (igbinary_serialize_header(&igsd TSRMLS_CC) != 0) { - zend_error(E_WARNING, "igbinary_serialize: cannot write header"); - igbinary_serialize_data_deinit(&igsd, 1 TSRMLS_CC); - return FAILURE; - } - - if (igbinary_serialize_array(&igsd, PS(http_session_vars), false, false TSRMLS_CC) != 0) { - igbinary_serialize_data_deinit(&igsd, 1 TSRMLS_CC); - return FAILURE; - } - - if (igbinary_serialize8(&igsd, 0 TSRMLS_CC) != 0) { - igbinary_serialize_data_deinit(&igsd, 1 TSRMLS_CC); - return FAILURE; - } - - /* shrink buffer to the real length, ignore errors */ - tmpbuf = (uint8_t *)igsd.mm.realloc(igsd.buffer, igsd.buffer_size, igsd.mm.context); - if (tmpbuf != NULL) { - igsd.buffer = tmpbuf; - } - - *newstr = (char *)igsd.buffer; - if (newlen) { - *newlen = igsd.buffer_size - 1; - } - - igbinary_serialize_data_deinit(&igsd, 0 TSRMLS_CC); - - return SUCCESS; -} -/* }}} */ -/* {{{ Serializer decode function */ -PS_SERIALIZER_DECODE_FUNC(igbinary) { - HashPosition tmp_hash_pos; - HashTable *tmp_hash; - char *key_str; - ulong key_long; - int tmp_int; - uint key_len; - zval *z; - zval **d; - - struct igbinary_unserialize_data igsd; - - if (!val || vallen==0) - return SUCCESS; - - if (igbinary_unserialize_data_init(&igsd TSRMLS_CC) != 0) { - return FAILURE; - } - - igsd.buffer = (uint8_t *)val; - igsd.buffer_size = vallen; - - if (igbinary_unserialize_header(&igsd TSRMLS_CC)) { - igbinary_unserialize_data_deinit(&igsd TSRMLS_CC); - return FAILURE; - } - - ALLOC_INIT_ZVAL(z); - if (igbinary_unserialize_zval(&igsd, &z TSRMLS_CC)) { - igbinary_unserialize_data_deinit(&igsd TSRMLS_CC); - zval_dtor(z); - FREE_ZVAL(z); - return FAILURE; - } - - igbinary_unserialize_data_deinit(&igsd TSRMLS_CC); - - tmp_hash = HASH_OF(z); - - zend_hash_internal_pointer_reset_ex(tmp_hash, &tmp_hash_pos); - while (zend_hash_get_current_data_ex(tmp_hash, (void *) &d, &tmp_hash_pos) == SUCCESS) { - tmp_int = zend_hash_get_current_key_ex(tmp_hash, &key_str, &key_len, &key_long, 0, &tmp_hash_pos); - - switch (tmp_int) { - case HASH_KEY_IS_LONG: - /* ??? */ - break; - case HASH_KEY_IS_STRING: - php_set_session_var(key_str, key_len-1, *d, NULL TSRMLS_CC); - php_add_session_var(key_str, key_len-1 TSRMLS_CC); - break; - } - zend_hash_move_forward_ex(tmp_hash, &tmp_hash_pos); - } - zval_dtor(z); - FREE_ZVAL(z); - - return SUCCESS; -} -/* }}} */ -#endif /* HAVE_PHP_SESSION */ - -#if defined(HAVE_APC_SUPPORT) || defined(HAVE_APCU_SUPPORT) -/* {{{ apc_serialize function */ -static int APC_SERIALIZER_NAME(igbinary) ( APC_SERIALIZER_ARGS ) { - (void)config; - - if (igbinary_serialize(buf, buf_len, (zval *)value TSRMLS_CC) == 0) { - /* flipped semantics */ - return 1; - } - return 0; -} -/* }}} */ -/* {{{ apc_unserialize function */ -static int APC_UNSERIALIZER_NAME(igbinary) ( APC_UNSERIALIZER_ARGS ) { - (void)config; - - if (igbinary_unserialize(buf, buf_len, value TSRMLS_CC) == 0) { - /* flipped semantics */ - return 1; - } - zval_dtor(*value); - (*value)->type = IS_NULL; - return 0; -} -/* }}} */ -#endif - -/* {{{ igbinary_serialize_data_init */ -/** Inits igbinary_serialize_data. */ -inline static int igbinary_serialize_data_init(struct igbinary_serialize_data *igsd, bool scalar, struct igbinary_memory_manager *memory_manager TSRMLS_DC) { - int r = 0; - - if (memory_manager == NULL) { - igsd->mm.alloc = igbinary_mm_wrapper_malloc; - igsd->mm.realloc = igbinary_mm_wrapper_realloc; - igsd->mm.free = igbinary_mm_wrapper_free; - igsd->mm.context = NULL; - } else { - igsd->mm = *memory_manager; - } - - igsd->buffer = NULL; - igsd->buffer_size = 0; - igsd->buffer_capacity = 32; - igsd->string_count = 0; - igsd->error = 0; - - igsd->buffer = (uint8_t *) igsd->mm.alloc(igsd->buffer_capacity, igsd->mm.context); - if (igsd->buffer == NULL) { - return 1; - } - - igsd->scalar = scalar; - if (!igsd->scalar) { - hash_si_init(&igsd->strings, 16); - hash_si_init(&igsd->objects, 16); - } - - igsd->compact_strings = (bool)IGBINARY_G(compact_strings); - - return r; -} -/* }}} */ -/* {{{ igbinary_serialize_data_deinit */ -/** Deinits igbinary_serialize_data. */ -inline static void igbinary_serialize_data_deinit(struct igbinary_serialize_data *igsd, int free_buffer TSRMLS_DC) { - if (free_buffer && igsd->buffer) { - igsd->mm.free(igsd->buffer, igsd->mm.context); - } - - if (!igsd->scalar) { - hash_si_deinit(&igsd->strings); - hash_si_deinit(&igsd->objects); - } -} -/* }}} */ -/* {{{ igbinary_serialize_header */ -/** Serializes header. */ -inline static int igbinary_serialize_header(struct igbinary_serialize_data *igsd TSRMLS_DC) { - return igbinary_serialize32(igsd, IGBINARY_FORMAT_VERSION TSRMLS_CC); /* version */ -} -/* }}} */ -/* {{{ igbinary_serialize_resize */ -/** Expands igbinary_serialize_data. */ -inline static int igbinary_serialize_resize(struct igbinary_serialize_data *igsd, size_t size TSRMLS_DC) { - if (igsd->buffer_size + size < igsd->buffer_capacity) { - return 0; - } - - while (igsd->buffer_size + size >= igsd->buffer_capacity) { - igsd->buffer_capacity *= 2; - } - - igsd->buffer = (uint8_t *) igsd->mm.realloc(igsd->buffer, igsd->buffer_capacity, igsd->mm.context); - if (igsd->buffer == NULL) - return 1; - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize8 */ -/** Serialize 8bit value. */ -inline static int igbinary_serialize8(struct igbinary_serialize_data *igsd, uint8_t i TSRMLS_DC) { - if (igbinary_serialize_resize(igsd, 1 TSRMLS_CC)) { - return 1; - } - - igsd->buffer[igsd->buffer_size++] = i; - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize16 */ -/** Serialize 16bit value. */ -inline static int igbinary_serialize16(struct igbinary_serialize_data *igsd, uint16_t i TSRMLS_DC) { - if (igbinary_serialize_resize(igsd, 2 TSRMLS_CC)) { - return 1; - } - - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 8 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i & 0xff); - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize32 */ -/** Serialize 32bit value. */ -inline static int igbinary_serialize32(struct igbinary_serialize_data *igsd, uint32_t i TSRMLS_DC) { - if (igbinary_serialize_resize(igsd, 4 TSRMLS_CC)) { - return 1; - } - - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 24 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 16 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 8 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i & 0xff); - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize64 */ -/** Serialize 64bit value. */ -inline static int igbinary_serialize64(struct igbinary_serialize_data *igsd, uint64_t i TSRMLS_DC) { - if (igbinary_serialize_resize(igsd, 8 TSRMLS_CC)) { - return 1; - } - - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 56 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 48 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 40 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 32 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 24 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 16 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i >> 8 & 0xff); - igsd->buffer[igsd->buffer_size++] = (uint8_t) (i & 0xff); - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize_null */ -/** Serializes null. */ -inline static int igbinary_serialize_null(struct igbinary_serialize_data *igsd TSRMLS_DC) { - return igbinary_serialize8(igsd, igbinary_type_null TSRMLS_CC); -} -/* }}} */ -/* {{{ igbinary_serialize_bool */ -/** Serializes bool. */ -inline static int igbinary_serialize_bool(struct igbinary_serialize_data *igsd, int b TSRMLS_DC) { - return igbinary_serialize8(igsd, (uint8_t) (b ? igbinary_type_bool_true : igbinary_type_bool_false) TSRMLS_CC); -} -/* }}} */ -/* {{{ igbinary_serialize_long */ -/** Serializes long. */ -inline static int igbinary_serialize_long(struct igbinary_serialize_data *igsd, long l TSRMLS_DC) { - long k = l >= 0 ? l : -l; - bool p = l >= 0 ? true : false; - - /* -LONG_MIN is 0 otherwise. */ - if (l == LONG_MIN) { -#if SIZEOF_LONG == 8 - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_long64n TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize64(igsd, (uint64_t) 0x8000000000000000 TSRMLS_CC) != 0) { - return 1; - } -#elif SIZEOF_LONG == 4 - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_long32n TSRMLS_CC) != 0) { - return 1; - } - if (igbinary_serialize32(igsd, (uint32_t) 0x80000000 TSRMLS_CC) != 0) { - return 1; - } -#else -#error "Strange sizeof(long)." -#endif - return 0; - } - - if (k <= 0xff) { - if (igbinary_serialize8(igsd, (uint8_t) (p ? igbinary_type_long8p : igbinary_type_long8n) TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, (uint8_t) k TSRMLS_CC) != 0) { - return 1; - } - } else if (k <= 0xffff) { - if (igbinary_serialize8(igsd, (uint8_t) (p ? igbinary_type_long16p : igbinary_type_long16n) TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, (uint16_t) k TSRMLS_CC) != 0) { - return 1; - } -#if SIZEOF_LONG == 8 - } else if (k <= 0xffffffff) { - if (igbinary_serialize8(igsd, (uint8_t) (p ? igbinary_type_long32p : igbinary_type_long32n) TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, (uint32_t) k TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, (uint8_t) (p ? igbinary_type_long64p : igbinary_type_long64n) TSRMLS_CC) != 0) { - return 1; - } - if (igbinary_serialize64(igsd, (uint64_t) k TSRMLS_CC) != 0) { - return 1; - } - } -#elif SIZEOF_LONG == 4 - } else { - if (igbinary_serialize8(igsd, (uint8_t) (p ? igbinary_type_long32p : igbinary_type_long32n) TSRMLS_CC) != 0) { - return 1; - } - if (igbinary_serialize32(igsd, (uint32_t) k TSRMLS_CC) != 0) { - return 1; - } - } -#else -#error "Strange sizeof(long)." -#endif - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize_double */ -/** Serializes double. */ -inline static int igbinary_serialize_double(struct igbinary_serialize_data *igsd, double d TSRMLS_DC) { - union { - double d; - uint64_t u; - } u; - - if (igbinary_serialize8(igsd, igbinary_type_double TSRMLS_CC) != 0) { - return 1; - } - - u.d = d; - - return igbinary_serialize64(igsd, u.u TSRMLS_CC); -} -/* }}} */ -/* {{{ igbinary_serialize_string */ -/** Serializes string. - * Serializes each string once, after first time uses pointers. - */ -inline static int igbinary_serialize_string(struct igbinary_serialize_data *igsd, char *s, size_t len TSRMLS_DC) { - uint32_t t; - uint32_t *i = &t; - - if (len == 0) { - if (igbinary_serialize8(igsd, igbinary_type_string_empty TSRMLS_CC) != 0) { - return 1; - } - - return 0; - } - - if (igsd->scalar || !igsd->compact_strings || hash_si_find(&igsd->strings, s, len, i) == 1) { - if (!igsd->scalar && igsd->compact_strings) { - hash_si_insert(&igsd->strings, s, len, igsd->string_count); - } - - igsd->string_count += 1; - - if (igbinary_serialize_chararray(igsd, s, len TSRMLS_CC) != 0) { - return 1; - } - } else { - if (*i <= 0xff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_string_id8 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, (uint8_t) *i TSRMLS_CC) != 0) { - return 1; - } - } else if (*i <= 0xffff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_string_id16 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, (uint16_t) *i TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_string_id32 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, (uint32_t) *i TSRMLS_CC) != 0) { - return 1; - } - } - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize_chararray */ -/** Serializes string data. */ -inline static int igbinary_serialize_chararray(struct igbinary_serialize_data *igsd, const char *s, size_t len TSRMLS_DC) { - if (len <= 0xff) { - if (igbinary_serialize8(igsd, igbinary_type_string8 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, len TSRMLS_CC) != 0) { - return 1; - } - } else if (len <= 0xffff) { - if (igbinary_serialize8(igsd, igbinary_type_string16 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, len TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, igbinary_type_string32 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, len TSRMLS_CC) != 0) { - return 1; - } - } - - if (igbinary_serialize_resize(igsd, len TSRMLS_CC)) { - return 1; - } - - memcpy(igsd->buffer+igsd->buffer_size, s, len); - igsd->buffer_size += len; - - return 0; -} -/* }}} */ -/* {{{ igbinay_serialize_array */ -/** Serializes array or objects inner properties. */ -inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, zval *z, bool object, bool incomplete_class TSRMLS_DC) { - HashTable *h; - HashPosition pos; - size_t n; - zval **d; - - char *key; - uint key_len; - int key_type; - ulong key_index; - - /* hash */ - h = object ? Z_OBJPROP_P(z) : HASH_OF(z); - - /* hash size */ - n = h ? zend_hash_num_elements(h) : 0; - - /* incomplete class magic member */ - if (n > 0 && incomplete_class) { - --n; - } - - if (!object && igbinary_serialize_array_ref(igsd, z, object TSRMLS_CC) == 0) { - return 0; - } - - if (n <= 0xff) { - if (igbinary_serialize8(igsd, igbinary_type_array8 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, n TSRMLS_CC) != 0) { - return 1; - } - } else if (n <= 0xffff) { - if (igbinary_serialize8(igsd, igbinary_type_array16 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, n TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, igbinary_type_array32 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, n TSRMLS_CC) != 0) { - return 1; - } - } - - if (n == 0) { - return 0; - } - - /* serialize properties. */ - zend_hash_internal_pointer_reset_ex(h, &pos); - for (;; zend_hash_move_forward_ex(h, &pos)) { - key_type = zend_hash_get_current_key_ex(h, &key, &key_len, &key_index, 0, &pos); - - /* last */ - if (key_type == HASH_KEY_NON_EXISTANT) { - break; - } - - /* skip magic member in incomplete classes */ - if (incomplete_class && strcmp(key, MAGIC_MEMBER) == 0) { - continue; - } - - switch (key_type) { - case HASH_KEY_IS_LONG: - if (igbinary_serialize_long(igsd, key_index TSRMLS_CC) != 0) { - return 1; - } - break; - - case HASH_KEY_IS_STRING: - if (igbinary_serialize_string(igsd, key, key_len-1 TSRMLS_CC) != 0) { - return 1; - } - break; - - default: - zend_error(E_ERROR, "igbinary_serialize_array: key is not string nor array"); - /* not reached */ - return 1; - } - - /* we should still add element even if it's not OK, - * since we already wrote the length of the array before */ - if (zend_hash_get_current_data_ex(h, (void *) &d, &pos) != SUCCESS || d == NULL) { - if (igbinary_serialize_null(igsd TSRMLS_CC)) { - return 1; - } - } else { - if (igbinary_serialize_zval(igsd, *d TSRMLS_CC)) { - return 1; - } - } - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize_array_ref */ -/** Serializes array reference. */ -inline static int igbinary_serialize_array_ref(struct igbinary_serialize_data *igsd, zval *z, bool object TSRMLS_DC) { - uint32_t t = 0; - uint32_t *i = &t; - union { - zval *z; - struct { - zend_class_entry *ce; - zend_object_handle handle; - } obj; - } key = { 0 }; - - if (object && Z_TYPE_P(z) == IS_OBJECT && Z_OBJ_HT_P(z)->get_class_entry) { - key.obj.ce = Z_OBJCE_P(z); - key.obj.handle = Z_OBJ_HANDLE_P(z); - } else { - key.z = z; - } - - if (hash_si_find(&igsd->objects, (char *)&key, sizeof(key), i) == 1) { - t = hash_si_size(&igsd->objects); - hash_si_insert(&igsd->objects, (char *)&key, sizeof(key), t); - return 1; - } else { - enum igbinary_type type; - if (*i <= 0xff) { - type = object ? igbinary_type_objref8 : igbinary_type_ref8; - if (igbinary_serialize8(igsd, (uint8_t) type TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, (uint8_t) *i TSRMLS_CC) != 0) { - return 1; - } - } else if (*i <= 0xffff) { - type = object ? igbinary_type_objref16 : igbinary_type_ref16; - if (igbinary_serialize8(igsd, (uint8_t) type TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, (uint16_t) *i TSRMLS_CC) != 0) { - return 1; - } - } else { - type = object ? igbinary_type_objref32 : igbinary_type_ref32; - if (igbinary_serialize8(igsd, (uint8_t) type TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, (uint32_t) *i TSRMLS_CC) != 0) { - return 1; - } - } - - return 0; - } - - return 1; -} -/* }}} */ -/* {{{ igbinary_serialize_array_sleep */ -/** Serializes object's properties array with __sleep -function. */ -inline static int igbinary_serialize_array_sleep(struct igbinary_serialize_data *igsd, zval *z, HashTable *h, zend_class_entry *ce, bool incomplete_class TSRMLS_DC) { - HashPosition pos; - size_t n = zend_hash_num_elements(h); - zval **d; - zval **v; - - char *key; - uint key_len; - int key_type; - ulong key_index; - - /* Decrease array size by one, because of magic member (with class name) */ - if (n > 0 && incomplete_class) { - --n; - } - - /* Serialize array id. */ - if (n <= 0xff) { - if (igbinary_serialize8(igsd, igbinary_type_array8 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, n TSRMLS_CC) != 0) { - return 1; - } - } else if (n <= 0xffff) { - if (igbinary_serialize8(igsd, igbinary_type_array16 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, n TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, igbinary_type_array32 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, n TSRMLS_CC) != 0) { - return 1; - } - } - - if (n == 0) { - return 0; - } - - zend_hash_internal_pointer_reset_ex(h, &pos); - - for (;; zend_hash_move_forward_ex(h, &pos)) { - key_type = zend_hash_get_current_key_ex(h, &key, &key_len, &key_index, 0, &pos); - - /* last */ - if (key_type == HASH_KEY_NON_EXISTANT) { - break; - } - - /* skip magic member in incomplete classes */ - if (incomplete_class && strcmp(key, MAGIC_MEMBER) == 0) { - continue; - } - - if (zend_hash_get_current_data_ex(h, (void *) &d, &pos) != SUCCESS || d == NULL || Z_TYPE_PP(d) != IS_STRING) { - php_error_docref(NULL TSRMLS_CC, E_NOTICE, "__sleep should return an array only " - "containing the names of instance-variables to " - "serialize"); - - /* we should still add element even if it's not OK, - * since we already wrote the length of the array before - * serialize null as key-value pair */ - if (igbinary_serialize_null(igsd TSRMLS_CC) != 0) { - return 1; - } - } else { - - if (zend_hash_find(Z_OBJPROP_P(z), Z_STRVAL_PP(d), Z_STRLEN_PP(d) + 1, (void *) &v) == SUCCESS) { - if (igbinary_serialize_string(igsd, Z_STRVAL_PP(d), Z_STRLEN_PP(d) TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize_zval(igsd, *v TSRMLS_CC) != 0) { - return 1; - } - } else if (ce) { - char *prot_name = NULL; - char *priv_name = NULL; - int prop_name_length; - - do { - /* try private */ - zend_mangle_property_name(&priv_name, &prop_name_length, ce->name, ce->name_length, - Z_STRVAL_PP(d), Z_STRLEN_PP(d), ce->type & ZEND_INTERNAL_CLASS); - if (zend_hash_find(Z_OBJPROP_P(z), priv_name, prop_name_length+1, (void *) &v) == SUCCESS) { - if (igbinary_serialize_string(igsd, priv_name, prop_name_length TSRMLS_CC) != 0) { - efree(priv_name); - return 1; - } - - efree(priv_name); - if (igbinary_serialize_zval(igsd, *v TSRMLS_CC) != 0) { - return 1; - } - break; - } - efree(priv_name); - - /* try protected */ - zend_mangle_property_name(&prot_name, &prop_name_length, "*", 1, - Z_STRVAL_PP(d), Z_STRLEN_PP(d), ce->type & ZEND_INTERNAL_CLASS); - if (zend_hash_find(Z_OBJPROP_P(z), prot_name, prop_name_length+1, (void *) &v) == SUCCESS) { - if (igbinary_serialize_string(igsd, prot_name, prop_name_length TSRMLS_CC) != 0) { - efree(prot_name); - return 1; - } - - efree(prot_name); - if (igbinary_serialize_zval(igsd, *v TSRMLS_CC) != 0) { - return 1; - } - break; - } - efree(prot_name); - - /* no win */ - php_error_docref(NULL TSRMLS_CC, E_NOTICE, "\"%s\" returned as member variable from __sleep() but does not exist", Z_STRVAL_PP(d)); - if (igbinary_serialize_string(igsd, Z_STRVAL_PP(d), Z_STRLEN_PP(d) TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize_null(igsd TSRMLS_CC) != 0) { - return 1; - } - } while (0); - - } else { - // if all else fails, just serialize the value in anyway. - if (igbinary_serialize_string(igsd, Z_STRVAL_PP(d), Z_STRLEN_PP(d) TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize_zval(igsd, *v TSRMLS_CC) != 0) { - return 1; - } - } - } - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize_object_name */ -/** Serialize object name. */ -inline static int igbinary_serialize_object_name(struct igbinary_serialize_data *igsd, const char *class_name, size_t name_len TSRMLS_DC) { - uint32_t t; - uint32_t *i = &t; - - if (hash_si_find(&igsd->strings, class_name, name_len, i) == 1) { - hash_si_insert(&igsd->strings, class_name, name_len, igsd->string_count); - igsd->string_count += 1; - - if (name_len <= 0xff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object8 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, (uint8_t) name_len TSRMLS_CC) != 0) { - return 1; - } - } else if (name_len <= 0xffff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object16 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, (uint16_t) name_len TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object32 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, (uint32_t) name_len TSRMLS_CC) != 0) { - return 1; - } - } - - if (igbinary_serialize_resize(igsd, name_len TSRMLS_CC)) { - return 1; - } - - memcpy(igsd->buffer+igsd->buffer_size, class_name, name_len); - igsd->buffer_size += name_len; - } else { - /* already serialized string */ - if (*i <= 0xff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object_id8 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize8(igsd, (uint8_t) *i TSRMLS_CC) != 0) { - return 1; - } - } else if (*i <= 0xffff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object_id16 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize16(igsd, (uint16_t) *i TSRMLS_CC) != 0) { - return 1; - } - } else { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object_id32 TSRMLS_CC) != 0) { - return 1; - } - - if (igbinary_serialize32(igsd, (uint32_t) *i TSRMLS_CC) != 0) { - return 1; - } - } - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_serialize_object */ -/** Serialize object. - * @see ext/standard/var.c - * */ -inline static int igbinary_serialize_object(struct igbinary_serialize_data *igsd, zval *z TSRMLS_DC) { - zend_class_entry *ce; - - zval f; - zval *h = NULL; - - int r = 0; - - unsigned char *serialized_data = NULL; - zend_uint serialized_len; - - PHP_CLASS_ATTRIBUTES; - - if (igbinary_serialize_array_ref(igsd, z, true TSRMLS_CC) == 0) { - return r; - } - - ce = Z_OBJCE_P(z); - - /* custom serializer */ - if (ce && ce->serialize != NULL) { - if (ce->serialize(z, &serialized_data, &serialized_len, (zend_serialize_data *)NULL TSRMLS_CC) == SUCCESS && !EG(exception)) { - if (igbinary_serialize_object_name(igsd, ce->name, ce->name_length TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - - - if (serialized_len <= 0xff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object_ser8 TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - - if (igbinary_serialize8(igsd, (uint8_t) serialized_len TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - } else if (serialized_len <= 0xffff) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object_ser16 TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - - if (igbinary_serialize16(igsd, (uint16_t) serialized_len TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - } else { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_object_ser32 TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - - if (igbinary_serialize32(igsd, (uint32_t) serialized_len TSRMLS_CC) != 0) { - if (serialized_data) { - efree(serialized_data); - } - return 1; - } - } - - if (igbinary_serialize_resize(igsd, serialized_len TSRMLS_CC)) { - if (serialized_data) { - efree(serialized_data); - } - - return 1; - } - - memcpy(igsd->buffer+igsd->buffer_size, serialized_data, serialized_len); - igsd->buffer_size += serialized_len; - } else if (EG(exception)) { - /* exception, return failure */ - r = 1; - } else { - /* Serialization callback failed, assume null output */ - r = igbinary_serialize_null(igsd TSRMLS_CC); - } - - if (serialized_data) { - efree(serialized_data); - } - - return r; - } - - /* serialize class name */ - PHP_SET_CLASS_ATTRIBUTES(z); - if (igbinary_serialize_object_name(igsd, class_name, name_len TSRMLS_CC) != 0) { - PHP_CLEANUP_CLASS_ATTRIBUTES(); - return 1; - } - PHP_CLEANUP_CLASS_ATTRIBUTES(); - - if (ce && ce != PHP_IC_ENTRY && zend_hash_exists(&ce->function_table, "__sleep", sizeof("__sleep"))) { - /* function name string */ - INIT_PZVAL(&f); - ZVAL_STRINGL(&f, "__sleep", sizeof("__sleep") - 1, 0); - - /* calling z->__sleep */ - r = call_user_function_ex(CG(function_table), &z, &f, &h, 0, 0, 1, NULL TSRMLS_CC); - - if (r == SUCCESS && !EG(exception)) { - r = 0; - - if (h) { - if (Z_TYPE_P(h) == IS_ARRAY) { - r = igbinary_serialize_array_sleep(igsd, z, HASH_OF(h), ce, incomplete_class TSRMLS_CC); - } else { - php_error_docref(NULL TSRMLS_CC, E_NOTICE, "__sleep should return an array only " - "containing the names of instance-variables to " - "serialize"); - - /* empty array */ - r = igbinary_serialize8(igsd, igbinary_type_array8 TSRMLS_CC); - if (r == 0) { - r = igbinary_serialize8(igsd, 0 TSRMLS_CC); - } - } - } - } else { - r = 1; - } - - /* cleanup */ - if (h) { - zval_ptr_dtor(&h); - } - - return r; - } else { - return igbinary_serialize_array(igsd, z, true, incomplete_class TSRMLS_CC); - } -} -/* }}} */ -/* {{{ igbinary_serialize_zval */ -/** Serialize zval. */ -static int igbinary_serialize_zval(struct igbinary_serialize_data *igsd, zval *z TSRMLS_DC) { - if (Z_ISREF_P(z)) { - if (igbinary_serialize8(igsd, (uint8_t) igbinary_type_ref TSRMLS_CC) != 0) { - return 1; - } - - /* Complex types serialize a reference, scalars do not... */ - /* FIXME: Absolutely wrong level to check this. */ - switch (Z_TYPE_P(z)) { - case IS_RESOURCE: - case IS_STRING: - case IS_LONG: - case IS_NULL: - case IS_BOOL: - case IS_DOUBLE: - /* Serialize a reference if zval already added */ - if (igbinary_serialize_array_ref(igsd, z, false TSRMLS_CC) == 0) { - return 0; - } - /* otherwise fall through */ - } - } - switch (Z_TYPE_P(z)) { - case IS_RESOURCE: - return igbinary_serialize_null(igsd TSRMLS_CC); - case IS_OBJECT: - return igbinary_serialize_object(igsd, z TSRMLS_CC); - case IS_ARRAY: - return igbinary_serialize_array(igsd, z, false, false TSRMLS_CC); - case IS_STRING: - return igbinary_serialize_string(igsd, Z_STRVAL_P(z), Z_STRLEN_P(z) TSRMLS_CC); - case IS_LONG: - return igbinary_serialize_long(igsd, Z_LVAL_P(z) TSRMLS_CC); - case IS_NULL: - return igbinary_serialize_null(igsd TSRMLS_CC); - case IS_BOOL: - return igbinary_serialize_bool(igsd, Z_LVAL_P(z) ? 1 : 0 TSRMLS_CC); - case IS_DOUBLE: - return igbinary_serialize_double(igsd, Z_DVAL_P(z) TSRMLS_CC); - default: - zend_error(E_ERROR, "igbinary_serialize_zval: zval has unknown type %d", (int)Z_TYPE_P(z)); - /* not reached */ - return 1; - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_data_init */ -/** Inits igbinary_unserialize_data_init. */ -inline static int igbinary_unserialize_data_init(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - smart_str empty_str = { 0 }; - - igsd->buffer = NULL; - igsd->buffer_size = 0; - igsd->buffer_offset = 0; - - igsd->strings = NULL; - igsd->strings_count = 0; - igsd->strings_capacity = 4; - igsd->string0_buf = empty_str; - - igsd->error = 0; - igsd->references = NULL; - igsd->references_count = 0; - igsd->references_capacity = 4; - - igsd->references = (void **) emalloc(sizeof(void *) * igsd->references_capacity); - if (igsd->references == NULL) { - return 1; - } - - igsd->strings = (struct igbinary_unserialize_string_pair *) emalloc(sizeof(struct igbinary_unserialize_string_pair) * igsd->strings_capacity); - if (igsd->strings == NULL) { - efree(igsd->references); - return 1; - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_data_deinit */ -/** Deinits igbinary_unserialize_data_init. */ -inline static void igbinary_unserialize_data_deinit(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - if (igsd->strings) { - efree(igsd->strings); - } - - if (igsd->references) { - efree(igsd->references); - } - - smart_str_free(&igsd->string0_buf); - - return; -} -/* }}} */ -/* {{{ igbinary_unserialize_header */ -/** Unserialize header. Check for version. */ -inline static int igbinary_unserialize_header(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - uint32_t version; - - if (igsd->buffer_offset + 4 >= igsd->buffer_size) { - return 1; - } - - version = igbinary_unserialize32(igsd TSRMLS_CC); - - /* Support older version 1 and the current format 2 */ - if (version == IGBINARY_FORMAT_VERSION || version == 0x00000001) { - return 0; - } else { - zend_error(E_WARNING, "igbinary_unserialize_header: unsupported version: %u, should be %u or %u", (unsigned int) version, 0x00000001, (unsigned int) IGBINARY_FORMAT_VERSION); - return 1; - } -} -/* }}} */ -/* {{{ igbinary_unserialize8 */ -/** Unserialize 8bit value. */ -inline static uint8_t igbinary_unserialize8(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - uint8_t ret = 0; - ret = igsd->buffer[igsd->buffer_offset++]; - return ret; -} -/* }}} */ -/* {{{ igbinary_unserialize16 */ -/** Unserialize 16bit value. */ -inline static uint16_t igbinary_unserialize16(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - uint16_t ret = 0; - ret |= ((uint16_t) igsd->buffer[igsd->buffer_offset++] << 8); - ret |= ((uint16_t) igsd->buffer[igsd->buffer_offset++] << 0); - return ret; -} -/* }}} */ -/* {{{ igbinary_unserialize32 */ -/** Unserialize 32bit value. */ -inline static uint32_t igbinary_unserialize32(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - uint32_t ret = 0; - ret |= ((uint32_t) igsd->buffer[igsd->buffer_offset++] << 24); - ret |= ((uint32_t) igsd->buffer[igsd->buffer_offset++] << 16); - ret |= ((uint32_t) igsd->buffer[igsd->buffer_offset++] << 8); - ret |= ((uint32_t) igsd->buffer[igsd->buffer_offset++] << 0); - return ret; -} -/* }}} */ -/* {{{ igbinary_unserialize64 */ -/** Unserialize 64bit value. */ -inline static uint64_t igbinary_unserialize64(struct igbinary_unserialize_data *igsd TSRMLS_DC) { - uint64_t ret = 0; - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 56); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 48); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 40); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 32); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 24); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 16); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 8); - ret |= ((uint64_t) igsd->buffer[igsd->buffer_offset++] << 0); - return ret; -} -/* }}} */ -/* {{{ igbinary_unserialize_long */ -/** Unserializes long */ -inline static int igbinary_unserialize_long(struct igbinary_unserialize_data *igsd, enum igbinary_type t, long *ret TSRMLS_DC) { - uint32_t tmp32; -#if SIZEOF_LONG == 8 - uint64_t tmp64; -#endif - - if (t == igbinary_type_long8p || t == igbinary_type_long8n) { - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); - return 1; - } - - *ret = (long) (t == igbinary_type_long8n ? -1 : 1) * igbinary_unserialize8(igsd TSRMLS_CC); - } else if (t == igbinary_type_long16p || t == igbinary_type_long16n) { - if (igsd->buffer_offset + 2 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); - return 1; - } - - *ret = (long) (t == igbinary_type_long16n ? -1 : 1) * igbinary_unserialize16(igsd TSRMLS_CC); - } else if (t == igbinary_type_long32p || t == igbinary_type_long32n) { - if (igsd->buffer_offset + 4 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); - return 1; - } - - /* check for boundaries */ - tmp32 = igbinary_unserialize32(igsd TSRMLS_CC); -#if SIZEOF_LONG == 4 - if (tmp32 > 0x80000000 || (tmp32 == 0x80000000 && t == igbinary_type_long32p)) { - zend_error(E_WARNING, "igbinary_unserialize_long: 64bit long on 32bit platform?"); - tmp32 = 0; /* t == igbinary_type_long32p ? LONG_MAX : LONG_MIN; */ - } -#endif - *ret = (long) (t == igbinary_type_long32n ? -1 : 1) * tmp32; - } else if (t == igbinary_type_long64p || t == igbinary_type_long64n) { -#if SIZEOF_LONG == 8 - if (igsd->buffer_offset + 8 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); - return 1; - } - - /* check for boundaries */ - tmp64 = igbinary_unserialize64(igsd TSRMLS_CC); - if (tmp64 > 0x8000000000000000 || (tmp64 == 0x8000000000000000 && t == igbinary_type_long64p)) { - zend_error(E_WARNING, "igbinary_unserialize_long: too big 64bit long."); - tmp64 = 0; /* t == igbinary_type_long64p ? LONG_MAX : LONG_MIN */; - } - - *ret = (long) (t == igbinary_type_long64n ? -1 : 1) * tmp64; -#elif SIZEOF_LONG == 4 - /* can't put 64bit long into 32bit one, placeholder zero */ - *ret = 0; - igbinary_unserialize64(igsd TSRMLS_CC); - zend_error(E_WARNING, "igbinary_unserialize_long: 64bit long on 32bit platform"); -#else -#error "Strange sizeof(long)." -#endif - } else { - *ret = 0; - zend_error(E_WARNING, "igbinary_unserialize_long: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_double */ -/** Unserializes double. */ -inline static int igbinary_unserialize_double(struct igbinary_unserialize_data *igsd, enum igbinary_type t, double *ret TSRMLS_DC) { - union { - double d; - uint64_t u; - } u; - - (void) t; - - if (igsd->buffer_offset + 8 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_double: end-of-data"); - return 1; - } - - - u.u = igbinary_unserialize64(igsd TSRMLS_CC); - - *ret = u.d; - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_string */ -/** Unserializes string. Unserializes both actual string or by string id. */ -inline static int igbinary_unserialize_string(struct igbinary_unserialize_data *igsd, enum igbinary_type t, char **s, size_t *len TSRMLS_DC) { - size_t i; - if (t == igbinary_type_string_id8 || t == igbinary_type_object_id8) { - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_string: end-of-data"); - return 1; - } - i = igbinary_unserialize8(igsd TSRMLS_CC); - } else if (t == igbinary_type_string_id16 || t == igbinary_type_object_id16) { - if (igsd->buffer_offset + 2 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_string: end-of-data"); - return 1; - } - i = igbinary_unserialize16(igsd TSRMLS_CC); - } else if (t == igbinary_type_string_id32 || t == igbinary_type_object_id32) { - if (igsd->buffer_offset + 4 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_string: end-of-data"); - return 1; - } - i = igbinary_unserialize32(igsd TSRMLS_CC); - } else { - zend_error(E_WARNING, "igbinary_unserialize_string: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - if (i >= igsd->strings_count) { - zend_error(E_WARNING, "igbinary_unserialize_string: string index is out-of-bounds"); - return 1; - } - - *s = igsd->strings[i].data; - *len = igsd->strings[i].len; - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_chararray */ -/** Unserializes chararray of string. */ -inline static int igbinary_unserialize_chararray(struct igbinary_unserialize_data *igsd, enum igbinary_type t, char **s, size_t *len TSRMLS_DC) { - size_t l; - - if (t == igbinary_type_string8 || t == igbinary_type_object8) { - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); - return 1; - } - l = igbinary_unserialize8(igsd TSRMLS_CC); - if (igsd->buffer_offset + l > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); - return 1; - } - } else if (t == igbinary_type_string16 || t == igbinary_type_object16) { - if (igsd->buffer_offset + 2 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); - return 1; - } - l = igbinary_unserialize16(igsd TSRMLS_CC); - if (igsd->buffer_offset + l > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); - return 1; - } - } else if (t == igbinary_type_string32 || t == igbinary_type_object32) { - if (igsd->buffer_offset + 4 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); - return 1; - } - l = igbinary_unserialize32(igsd TSRMLS_CC); - if (igsd->buffer_offset + l > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); - return 1; - } - } else { - zend_error(E_WARNING, "igbinary_unserialize_chararray: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - if (igsd->strings_count + 1 > igsd->strings_capacity) { - while (igsd->strings_count + 1 > igsd->strings_capacity) { - igsd->strings_capacity *= 2; - } - - igsd->strings = (struct igbinary_unserialize_string_pair *) erealloc(igsd->strings, sizeof(struct igbinary_unserialize_string_pair) * igsd->strings_capacity); - if (igsd->strings == NULL) { - return 1; - } - } - - igsd->strings[igsd->strings_count].data = (char *) (igsd->buffer + igsd->buffer_offset); - igsd->strings[igsd->strings_count].len = l; - - igsd->buffer_offset += l; - - if (igsd->strings[igsd->strings_count].data == NULL) { - return 1; - } - - *len = igsd->strings[igsd->strings_count].len; - *s = igsd->strings[igsd->strings_count].data; - - igsd->strings_count += 1; - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_array */ -/** Unserializes array. */ -inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z, int object TSRMLS_DC) { - size_t n; - size_t i; - - zval *v = NULL; - /* zval *old_v; */ - - char *key; - size_t key_len = 0; - long key_index = 0; - - enum igbinary_type key_type; - - HashTable *h; - - if (t == igbinary_type_array8) { - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); - return 1; - } - n = igbinary_unserialize8(igsd TSRMLS_CC); - } else if (t == igbinary_type_array16) { - if (igsd->buffer_offset + 2 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); - return 1; - } - n = igbinary_unserialize16(igsd TSRMLS_CC); - } else if (t == igbinary_type_array32) { - if (igsd->buffer_offset + 4 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); - return 1; - } - n = igbinary_unserialize32(igsd TSRMLS_CC); - } else { - zend_error(E_WARNING, "igbinary_unserialize_array: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - // n cannot be larger than the number of minimum "objects" in the array - if (n > igsd->buffer_size - igsd->buffer_offset) { - zend_error(E_WARNING, "%s: data size %zu smaller that requested array length %zu.", "igbinary_unserialize_array", igsd->buffer_size - igsd->buffer_offset, n); - return 1; - } - - if (!object) { - Z_TYPE_PP(z) = IS_ARRAY; - ALLOC_HASHTABLE(Z_ARRVAL_PP(z)); - zend_hash_init(Z_ARRVAL_PP(z), n + 1, NULL, ZVAL_PTR_DTOR, 0); - - /* references */ - if (igsd->references_count + 1 >= igsd->references_capacity) { - while (igsd->references_count + 1 >= igsd->references_capacity) { - igsd->references_capacity *= 2; - } - - igsd->references = (void **) erealloc(igsd->references, sizeof(void *) * igsd->references_capacity); - if (igsd->references == NULL) - return 1; - } - - igsd->references[igsd->references_count++] = (void *) *z; - } - - /* empty array */ - if (n == 0) { - return 0; - } - - h = HASH_OF(*z); - - for (i = 0; i < n; i++) { - key = NULL; - - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); - zval_dtor(*z); - ZVAL_NULL(*z); - return 1; - } - - key_type = (enum igbinary_type) igbinary_unserialize8(igsd TSRMLS_CC); - - switch (key_type) { - case igbinary_type_long8p: - case igbinary_type_long8n: - case igbinary_type_long16p: - case igbinary_type_long16n: - case igbinary_type_long32p: - case igbinary_type_long32n: - case igbinary_type_long64p: - case igbinary_type_long64n: - if (igbinary_unserialize_long(igsd, key_type, &key_index TSRMLS_CC)) { - zval_dtor(*z); - ZVAL_NULL(*z); - return 1; - } - break; - case igbinary_type_string_id8: - case igbinary_type_string_id16: - case igbinary_type_string_id32: - if (igbinary_unserialize_string(igsd, key_type, &key, &key_len TSRMLS_CC)) { - zval_dtor(*z); - ZVAL_NULL(*z); - return 1; - } - break; - case igbinary_type_string8: - case igbinary_type_string16: - case igbinary_type_string32: - if (igbinary_unserialize_chararray(igsd, key_type, &key, &key_len TSRMLS_CC)) { - zval_dtor(*z); - ZVAL_NULL(*z); - return 1; - } - break; - case igbinary_type_string_empty: - key = ""; - key_len = 0; - break; - case igbinary_type_null: - continue; - default: - zend_error(E_WARNING, "igbinary_unserialize_array: unknown key type '%02x', position %zu", key_type, igsd->buffer_offset); - zval_dtor(*z); - ZVAL_NULL(*z); - return 1; - } - - - ALLOC_INIT_ZVAL(v); - if (igbinary_unserialize_zval(igsd, &v TSRMLS_CC)) { - zval_dtor(*z); - ZVAL_NULL(*z); - zval_ptr_dtor(&v); - return 1; - } - - if (key) { - /* Keys must include a terminating null. */ - /* Ensure buffer starts at the beginning. */ - igsd->string0_buf.len = 0; - smart_str_appendl(&igsd->string0_buf, key, key_len); - smart_str_0(&igsd->string0_buf); -/* - if (zend_symtable_find(h, key, key_len + 1, (void **)&old_v) == SUCCESS) { - var_push_dtor(var_hash, old_v); - } -*/ - zend_symtable_update(h, igsd->string0_buf.c, igsd->string0_buf.len + 1, &v, sizeof(v), NULL); - } else { -/* - if (zend_hash_index_find(h, key_index, (void **)&old_v) == SUCCESS) { - var_push_dtor(var_hash, old_v); - } -*/ - zend_hash_index_update(h, key_index, &v, sizeof(v), NULL); - } - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_object_ser */ -/** Unserializes object's property array of objects implementing Serializable -interface. */ -inline static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z, zend_class_entry *ce TSRMLS_DC) { - size_t n; - int ret; - php_unserialize_data_t var_hash; - - if (ce->unserialize == NULL) { - zend_error(E_WARNING, "Class %s has no unserializer", ce->name); - return 1; - } - - if (t == igbinary_type_object_ser8) { - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); - return 1; - } - n = igbinary_unserialize8(igsd TSRMLS_CC); - } else if (t == igbinary_type_object_ser16) { - if (igsd->buffer_offset + 2 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); - return 1; - } - n = igbinary_unserialize16(igsd TSRMLS_CC); - } else if (t == igbinary_type_object_ser32) { - if (igsd->buffer_offset + 4 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); - return 1; - } - n = igbinary_unserialize32(igsd TSRMLS_CC); - } else { - zend_error(E_WARNING, "igbinary_unserialize_object_ser: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - if (igsd->buffer_offset + n > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); - return 1; - } - - PHP_VAR_UNSERIALIZE_INIT(var_hash); - ret = ce->unserialize(z, ce, - (const unsigned char*)(igsd->buffer + igsd->buffer_offset), n, - (zend_unserialize_data *)&var_hash TSRMLS_CC); - PHP_VAR_UNSERIALIZE_DESTROY(var_hash); - - if (ret != SUCCESS || EG(exception)) { - return 1; - } - - igsd->buffer_offset += n; - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_object */ -/** Unserialize object. - * @see ext/standard/var_unserializer.c - */ -inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z TSRMLS_DC) { - zend_class_entry *ce; - zend_class_entry **pce; - - zval *h = NULL; - zval f; - - char *name = NULL; - size_t name_len = 0; - - int r; - - bool incomplete_class = false; - - zval *user_func; - zval *retval_ptr; - zval **args[1]; - zval *arg_func_name; - - if (t == igbinary_type_object8 || t == igbinary_type_object16 || t == igbinary_type_object32) { - if (igbinary_unserialize_chararray(igsd, t, &name, &name_len TSRMLS_CC)) { - return 1; - } - } else if (t == igbinary_type_object_id8 || t == igbinary_type_object_id16 || t == igbinary_type_object_id32) { - if (igbinary_unserialize_string(igsd, t, &name, &name_len TSRMLS_CC)) { - return 1; - } - } else { - zend_error(E_WARNING, "igbinary_unserialize_object: unknown object type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - do { - /* Try to find class directly */ - if (zend_lookup_class(name, name_len, &pce TSRMLS_CC) == SUCCESS) { - ce = *pce; - break; - } - - /* Check for unserialize callback */ - if ((PG(unserialize_callback_func) == NULL) || (PG(unserialize_callback_func)[0] == '\0')) { - incomplete_class = 1; - ce = PHP_IC_ENTRY; - break; - } - - /* Call unserialize callback */ - MAKE_STD_ZVAL(user_func); - ZVAL_STRING(user_func, PG(unserialize_callback_func), 1); - args[0] = &arg_func_name; - MAKE_STD_ZVAL(arg_func_name); - ZVAL_STRING(arg_func_name, name, 1); - if (call_user_function_ex(CG(function_table), NULL, user_func, &retval_ptr, 1, args, 0, NULL TSRMLS_CC) != SUCCESS) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "defined (%s) but not found", name); - incomplete_class = 1; - ce = PHP_IC_ENTRY; - zval_ptr_dtor(&user_func); - zval_ptr_dtor(&arg_func_name); - break; - } - if (retval_ptr) { - zval_ptr_dtor(&retval_ptr); - } - - /* The callback function may have defined the class */ - if (zend_lookup_class(name, name_len, &pce TSRMLS_CC) == SUCCESS) { - ce = *pce; - } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Function %s() hasn't defined the class it was called for", name); - incomplete_class = true; - ce = PHP_IC_ENTRY; - } - - zval_ptr_dtor(&user_func); - zval_ptr_dtor(&arg_func_name); - } while (0); - - /* previous user function call may have raised an exception */ - if (EG(exception)) { - return 1; - } - - object_init_ex(*z, ce); - - /* reference */ - if (igsd->references_count + 1 >= igsd->references_capacity) { - while (igsd->references_count + 1 >= igsd->references_capacity) { - igsd->references_capacity *= 2; - } - - igsd->references = (void **) erealloc(igsd->references, sizeof(void *) * igsd->references_capacity); - if (igsd->references == NULL) - return 1; - } - - igsd->references[igsd->references_count++] = (void *) *z; - - /* store incomplete class name */ - if (incomplete_class) { - php_store_class_name(*z, name, name_len); - } - - t = (enum igbinary_type) igbinary_unserialize8(igsd TSRMLS_CC); - switch (t) { - case igbinary_type_array8: - case igbinary_type_array16: - case igbinary_type_array32: - r = igbinary_unserialize_array(igsd, t, z, 1 TSRMLS_CC); - break; - case igbinary_type_object_ser8: - case igbinary_type_object_ser16: - case igbinary_type_object_ser32: - r = igbinary_unserialize_object_ser(igsd, t, z, ce TSRMLS_CC); - break; - default: - zend_error(E_WARNING, "igbinary_unserialize_object: unknown object inner type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - if (r) { - return r; - } - - if (Z_OBJCE_PP(z) != PHP_IC_ENTRY && zend_hash_exists(&Z_OBJCE_PP(z)->function_table, "__wakeup", sizeof("__wakeup"))) { - INIT_PZVAL(&f); - ZVAL_STRINGL(&f, "__wakeup", sizeof("__wakeup") - 1, 0); - call_user_function_ex(CG(function_table), z, &f, &h, 0, 0, 1, NULL TSRMLS_CC); - - if (h) { - zval_ptr_dtor(&h); - } - - if (EG(exception)) { - r = 1; - } - } - - return r; -} -/* }}} */ -/* {{{ igbinary_unserialize_ref */ -/** Unserializes array or object by reference. */ -inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval **z TSRMLS_DC) { - size_t n; - - if (t == igbinary_type_ref8 || t == igbinary_type_objref8) { - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_ref: end-of-data"); - return 1; - } - n = igbinary_unserialize8(igsd TSRMLS_CC); - } else if (t == igbinary_type_ref16 || t == igbinary_type_objref16) { - if (igsd->buffer_offset + 2 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_ref: end-of-data"); - return 1; - } - n = igbinary_unserialize16(igsd TSRMLS_CC); - } else if (t == igbinary_type_ref32 || t == igbinary_type_objref32) { - if (igsd->buffer_offset + 4 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_ref: end-of-data"); - return 1; - } - n = igbinary_unserialize32(igsd TSRMLS_CC); - } else { - zend_error(E_WARNING, "igbinary_unserialize_ref: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - if (n >= igsd->references_count) { - zend_error(E_WARNING, "igbinary_unserialize_ref: invalid reference"); - return 1; - } - - if (*z != NULL) { - zval_ptr_dtor(z); - } - - *z = igsd->references[n]; - Z_ADDREF_PP(z); - - if (t == igbinary_type_objref8 || t == igbinary_type_objref16 || t == igbinary_type_objref32) { - Z_SET_ISREF_TO_PP(z, false); - } - - return 0; -} -/* }}} */ -/* {{{ igbinary_unserialize_zval */ -/** Unserialize zval. */ -static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval **z TSRMLS_DC) { - enum igbinary_type t; - - long tmp_long; - double tmp_double; - char *tmp_chararray; - size_t tmp_size_t; - - if (igsd->buffer_offset + 1 > igsd->buffer_size) { - zend_error(E_WARNING, "igbinary_unserialize_zval: end-of-data"); - return 1; - } - - t = (enum igbinary_type) igbinary_unserialize8(igsd TSRMLS_CC); - - switch (t) { - case igbinary_type_ref: - if (igbinary_unserialize_zval(igsd, z TSRMLS_CC)) { - return 1; - } - /* Scalar types should be added to the references hash */ - /* unless they're already added */ - /* in references list: marked as ref */ - if (!Z_ISREF_PP(z)) switch (Z_TYPE_PP(z)) { - case IS_STRING: - case IS_LONG: - case IS_NULL: - case IS_DOUBLE: - case IS_BOOL: - /* reference */ - if (igsd->references_count + 1 >= igsd->references_capacity) { - while (igsd->references_count + 1 >= igsd->references_capacity) { - igsd->references_capacity *= 2; - } - - igsd->references = (void **) erealloc(igsd->references, sizeof(void *) * igsd->references_capacity); - if (igsd->references == NULL) - return 1; - } - - igsd->references[igsd->references_count++] = (void *) *z; - } - Z_SET_ISREF_TO_PP(z, true); - break; - case igbinary_type_objref8: - case igbinary_type_objref16: - case igbinary_type_objref32: - case igbinary_type_ref8: - case igbinary_type_ref16: - case igbinary_type_ref32: - if (igbinary_unserialize_ref(igsd, t, z TSRMLS_CC)) { - return 1; - } - break; - case igbinary_type_object8: - case igbinary_type_object16: - case igbinary_type_object32: - case igbinary_type_object_id8: - case igbinary_type_object_id16: - case igbinary_type_object_id32: - if (igbinary_unserialize_object(igsd, t, z TSRMLS_CC)) { - return 1; - } - break; - case igbinary_type_array8: - case igbinary_type_array16: - case igbinary_type_array32: - if (igbinary_unserialize_array(igsd, t, z, 0 TSRMLS_CC)) { - return 1; - } - break; - case igbinary_type_string_empty: - ZVAL_EMPTY_STRING(*z); - break; - case igbinary_type_string_id8: - case igbinary_type_string_id16: - case igbinary_type_string_id32: - if (igbinary_unserialize_string(igsd, t, &tmp_chararray, &tmp_size_t TSRMLS_CC)) { - return 1; - } - ZVAL_STRINGL(*z, tmp_chararray, tmp_size_t, 1); - break; - case igbinary_type_string8: - case igbinary_type_string16: - case igbinary_type_string32: - if (igbinary_unserialize_chararray(igsd, t, &tmp_chararray, &tmp_size_t TSRMLS_CC)) { - return 1; - } - ZVAL_STRINGL(*z, tmp_chararray, tmp_size_t, 1); - break; - case igbinary_type_long8p: - case igbinary_type_long8n: - case igbinary_type_long16p: - case igbinary_type_long16n: - case igbinary_type_long32p: - case igbinary_type_long32n: - case igbinary_type_long64p: - case igbinary_type_long64n: - if (igbinary_unserialize_long(igsd, t, &tmp_long TSRMLS_CC)) { - return 1; - } - ZVAL_LONG(*z, tmp_long); - break; - case igbinary_type_null: - ZVAL_NULL(*z); - break; - case igbinary_type_bool_false: - ZVAL_BOOL(*z, 0); - break; - case igbinary_type_bool_true: - ZVAL_BOOL(*z, 1); - break; - case igbinary_type_double: - if (igbinary_unserialize_double(igsd, t, &tmp_double TSRMLS_CC)) { - return 1; - } - ZVAL_DOUBLE(*z, tmp_double); - break; - default: - zend_error(E_WARNING, "igbinary_unserialize_zval: unknown type '%02x', position %zu", t, igsd->buffer_offset); - return 1; - } - - return 0; -} -/* }}} */ - -/* - * Local variables: - * tab-width: 2 - * c-basic-offset: 4 - * End: - * vim600: noet sw=4 ts=4 fdm=marker - * vim<600: noet sw=4 ts=4 - */ diff --git a/igbinary.h b/igbinary.h index 690d433f..9178ad29 100644 --- a/igbinary.h +++ b/igbinary.h @@ -1,72 +1,9 @@ -/* - +----------------------------------------------------------------------+ - | See COPYING file for further copyright information | - +----------------------------------------------------------------------+ - | Author: Oleg Grenrus | - | See CREDITS for contributors | - +----------------------------------------------------------------------+ -*/ - -#ifndef IGBINARY_H -#define IGBINARY_H -#ifdef PHP_WIN32 -# include "ig_win32.h" +#ifndef PHPEXT_IGBINARY_BASE_IGBINARY_H +#define PHPEXT_IGBINARY_BASE_IGBINARY_H +#include "php_version.h" +#if PHP_MAJOR_VERSION == 7 || PHP_MAJOR_VERSION == 8 +#include "src/php7/igbinary.h" #else -# include +#error "Unsupported php version for igbinary build" #endif -#include "php.h" - -#ifdef PHP_WIN32 -# if defined(IGBINARY_EXPORTS) || (!defined(COMPILE_DL_IGBINARY)) -# define IGBINARY_API __declspec(dllexport) -# elif defined(COMPILE_DL_IGBINARY) -# define IGBINARY_API __declspec(dllimport) -# else -# define IGBINARY_API /* nothing special */ -# endif -#elif defined(__GNUC__) && __GNUC__ >= 4 -# define IGBINARY_API __attribute__ ((visibility("default"))) -#else -# define IGBINARY_API /* nothing special */ #endif - -#define PHP_IGBINARY_VERSION "1.2.2-dev" - -/** Struct that contains pointers to memory allocation and deallocation functions. - * @see igbinary_serialize_data - */ -struct igbinary_memory_manager { - void *(*alloc)(size_t size, void *context); - void *(*realloc)(void *ptr, size_t new_size, void *context); - void (*free)(void *ptr, void *context); - void *context; -}; - -/** Serialize zval. - * Return buffer is allocated by this function with emalloc. - * @param[out] ret Return buffer - * @param[out] ret_len Size of return buffer - * @param[in] z Variable to be serialized - * @return 0 on success, 1 elsewhere. - */ -IGBINARY_API int igbinary_serialize(uint8_t **ret, size_t *ret_len, zval *z TSRMLS_DC); - -/** Serialize zval. - * Return buffer is allocated by this function with emalloc. - * @param[out] ret Return buffer - * @param[out] ret_len Size of return buffer - * @param[in] z Variable to be serialized - * @param[in] memory_manager Pointer to the structure that contains memory allocation functions. - * @return 0 on success, 1 elsewhere. - */ -IGBINARY_API int igbinary_serialize_ex(uint8_t **ret, size_t *ret_len, zval *z, struct igbinary_memory_manager *memory_manager TSRMLS_DC); - -/** Unserialize to zval. - * @param[in] buf Buffer with serialized data. - * @param[in] buf_len Buffer length. - * @param[out] z Unserialized zval - * @return 0 on success, 1 elsewhere. - */ -IGBINARY_API int igbinary_unserialize(const uint8_t *buf, size_t buf_len, zval **z TSRMLS_DC); - -#endif /* IGBINARY_H */ diff --git a/igbinary.php b/igbinary.php index 5371fee5..1991079b 100644 --- a/igbinary.php +++ b/igbinary.php @@ -1,36 +1,40 @@ * @version 1.0.0 * @package igbinary */ -/** Generates a storable representation of a value. +/** + * Generates a storable representation of a value. * This is useful for storing or passing PHP values around without losing their type and structure. * To make the serialized string into a PHP value again, use {@link igbinary_unserialize}. * @@ -46,23 +50,24 @@ * Likewise, when the object is restored using unserialize() the __wakeup member function is called. * * @param mixed $value The value to be serialized. - * @return string Returns a string containing a byte-stream representation of value that can be stored anywhere. - * @link http://www.php.net/serialize PHP default serialize + * @return string Returns a string containing a binary representation of value that can be stored anywhere. + * @link http://www.php.net/serialize PHP's default serialize */ function igbinary_serialize($value); /** Creates a PHP value from a stored representation. * igbinary_unserialize() takes a single serialized variable and converts it back into a PHP value. * - * If the variable being unserialized is an object, after successfully reconstructing the object - * PHP will automatically attempt to call the __wakeup() member function (if it exists). - * In case the passed string is not unserializeable, NULL is returned and E_WARNING is issued. + * If the variable being unserialized is an object, + * then after successfully reconstructing the object, + * PHP will automatically call the __wakeup() member function (if it exists). + * + * If the passed in string could not be unserialized, + * then NULL is returned and an E_WARNING is issued. * * @param string $str The serialized string. - * @return mixed The converted value is returned, and can be a boolean, integer, float, string, array or object. - * @link http://www.php.net/manual/en/function.unserialize.php PHP default unserialize - * @link http://www.php.net/~helly/php/ext/spl/interfaceSerializable.html Serializable + * @return mixed The unserialized value is returned. It can be a boolean, integer, float, string, array, object or null. + * @link http://www.php.net/manual/en/function.unserialize.php PHP's default unserialize + * @link https://secure.php.net/serializable Serializable interface */ function igbinary_unserialize($str); - -?> diff --git a/package.xml b/package.xml index eec96a8e..80b3f582 100644 --- a/package.xml +++ b/package.xml @@ -4,9 +4,9 @@ pecl.php.net igbinary extension Igbinary is a drop in replacement for the standard php serializer. Instead of - time and space consuming textual representation, igbinary stores php data - structures in a compact binary form. Savings are significant when using - memcached or similar memory based storages for serialized data. +the time and space consuming textual representation used by PHP's serialize(), igbinary stores php data +structures in a compact binary form. Savings are significant when using +memcached or similar memory based storages for serialized data. Oleg Grenrus phadej @@ -25,42 +25,85 @@ teddy.pecl@luuseri.com yes - 2014-08-29 - + + Tyson Andre + tandre + tandre@php.net + yes + + 2025-11-26 + - 1.2.2-dev - 1.1.1 + 3.2.17RC1 + 1.4.0 stable stable - New BSD + BSD-3-Clause -- Compatible with PHP 5.2 - 5.6 +* Support PHP 8.5 +* Fix some Windows builds +* Remove php_smart_string.h usage - - - - - + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -70,12 +113,15 @@ + + + @@ -83,37 +129,117 @@ + + + + - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - 5.2.0 + 7.0.0 1.4.0b1 @@ -123,6 +249,822 @@ igbinary + + 2024-08-10 + + + 3.2.16 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Fix test failures in php 8.4.0alpha1 due to tests calling deprecated functions (#390) + + + + 2023-12-02 + + + 3.2.15 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Fix crash in igbinary_unserialize_object_enum_case with opcache protected memory and non-constant value. + + + + 2023-02-26 + + + 3.2.14 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Fix build error in PHP 8.3-dev +* Fix test expectation errors in php 8.3-dev due to change to php's TypeErrors. + + + + 2023-02-02 + + + 3.2.13 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Speed up unserialization of typed properties by reducing hash table collisions when looking up property reference info. + + + + 2022-11-07 + + + 3.2.12 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Fix symbol error seen in php 8.2.0 loading zend_class_unserialize_deny, due to failing to load a header defining a macro. + + + + 2022-11-06 + + + 3.2.11 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Fix a bug that could prevent objects/arrays with reference cycles from being properly garbage collected. +* Fix bugs in unserializing PHP references to values found in php 7.4 typed properties (#363) + + + + 2022-11-06 + + + 3.2.10 + 1.4.0 + + + stable + stable + + BSD-3-Clause + +* Add a macro that callers can use to check if igbinary will accept the header for data being unserialized. +* Fix bug preventing the unserialization of data containing representations of strings larger than 4GB. + + + + 2022-10-17 + + + 3.2.9 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Fix invalid release artifact name in job to build dlls for https://github.com/igbinary/igbinary + + + + 2022-10-16 + + + 3.2.8 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Reduce excessive inlining to reduce shared library size. +* Miscellaneous optimizations. +* Update test expectations to handle changes to var_export output (fully qualified class names) in PHP 8.2. +* Throw an Error when igbinary_unserialize would add dynamic properties to class definitions that forbid them in PHP 8.0+ + (especially PHP 8.2 `readonly` classes) +* Emit a deprecation notice when igbinary_unserialize() adds dynamic properties to a class without `#[AllowDynamicProperties]` in PHP 8.2. + Doing that would become an Error in PHP 9.0. +* Set up CI job to build dlls on https://github.com/igbinary/igbinary - at the moment, the infrastructure used by the Windows for php team has been broken for months. + + + + 2021-08-11 + + + 3.2.7 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Update test expectations for php 8.2.0-dev. Add `#[AllowDynamicProperties]` Attribute to some tests to avoid notices. +* In php 8.1+, make igbinary_unserialize check to see if an equivalent interned string already exists when unserializing object property names, array keys, and class names + and use that instead of creating a brand new string. + (This deliberately doesn't create a new interned string if one doesn't already exist.) + (Before this change, igbinary would deduplicate strings when serializing, but would not check if strings were interned by PHP itself when unserializing) +* Avoid debug build assertion failure for `HT_ASSERT_RC1` the same way as PHP's unserialize - this is a case where ostensibly there are no other references to the array being unserialized. + + + + 2021-08-11 + + + 3.2.6 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Fix igbinary extension version found in reflection. + + + + 2021-08-07 + + + 3.2.5 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Fix change in behavior introduced in 3.2.2RC1 when unserializing arrays - the internal array pointer (for `next()`, `key()`, etc) pointed past the end of the array in php 7.0-7.2. + + + + 2021-07-24 + + + 3.2.4 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Forbid serializing classes that deny serialization/unserialization (anonymous classes, CURLFile, etc.) even when subclasses implement '__serialize' and '__unserialize' + + + + 2021-06-09 + + + 3.2.3 + 1.3.1 + + + stable + stable + + BSD-3-Clause + +* Fix build for php 8.1 after changes to enum internals. +* Update tests to suppress deprecations in php 8.1 and support run-tests.php changes in php 8.1 +* Don't emit a notice when unserialize_callback_func causes igbinary_unserialize to throw https://bugs.php.net/bug.php?id=81118 + + + + 2021-04-18 + + + 3.2.2 + 1.3.0 + + + stable + stable + + BSD-3-Clause + +* Eliminate impossible/redundant checks. +* Add a new type code for serialization and unserialization of PHP strings that are larger than 4GB. +* Add additional checks for overflow when serializing extremely large data structures. + (e.g. serializing more than 2**32 strings or 2**32 objects/references/arrays) +* Support serializing and unserializing php 8.1 enums (can only be unserialized in php 8.1+) + + + + 2021-01-11 + + + 3.2.2RC1 + 1.2.5 + + + beta + stable + + BSD-3-Clause + +* Update php version check to allow igbinary to be statically built in PHP 8.0+ +* Fix bug in out of memory error handling in __sleep, slightly speed up serializing with __sleep. +* Continue serializing remaining properties if a missing property name is returned from __sleep. +* Speed up serializing by optimizing for the case where there is no memory manager override. + When there is a memory manager override, only use that for allocating the string to return. + (benchmarks/serialize-scalar-int.b.php showed a speedup from 0.22 to 0.18 seconds for repeated serialization of a single scalar, + and from 0.186 to 0.180 seconds for benchmarks/serialize-stringarray.b.php for an array of strings) +* Speed up unserializing arrays in php 7.2-8.0 by adding optimized code for finding the hash bucket of + a string/integer key of an array, or creating a placeholder if it does not already exist. + + + + 2020-12-27 + + + 3.2.1 + 1.2.5 + + + stable + stable + + BSD-3-Clause + +* Fix crash when unserializing if __serialize was defined but __unserialize was undefined in php 8.0+ (due to typo). + + + + 2020-12-26 + + + 3.2.0 + 1.2.5 + + + stable + stable + + BSD-3-Clause + +* Use PHP's shared empty array instance when unserializing empty arrays in php 7.3+. + (helps slightly with memory usage when repeatedly unserializing, + when removing elements from arrays before unserializing them, + or when serializing values including an empty array that was unserialized) +* Emit a deprecation notice when serializing resources. + PHP itself is converting many resources to objects that throw an Error on serialization attempts. + Continue to represent resources as null in the serialized data. +* Fix memory management bug when unserializing invalid data (duplicate properties in objects (e.g. from `__sleep`) or duplicate fields in arrays (impossible for valid data)). +* Speed up calls to `__serialize`/`__unserialize` in php 8.0+. +* Fix error messages for unserialize_callback_func: make messages properly refer to the autoload function. +* Optimize unserializing alternative names for private/protected properties that were previously public. + + + + 2020-10-08 + + + 3.1.6 + 1.2.4 + + + stable + stable + + BSD-3-Clause + +* Fix build failure with older C standard (e.g. building on CentOS 6). +* Otherwise, identical to 3.1.6RC1. + + + + 2020-10-07 + + + 3.1.6RC1 + 1.2.4 + + + beta + stable + + BSD-3-Clause + +* Fix igbinary_serialize incorrectly deduplicating arrays/objects/references when they were garbage collected/freed during serialization. + + + + 2020-09-02 + + + 3.1.5 + 1.2.3 + + + stable + stable + + BSD-3-Clause + +* Update unit test expectation to match behavior in php 8 due to changes in php's handling of cyclic references in arrays. +* Support API changes in php 8.0.0beta3. + + + + 2020-08-05 + + + 3.1.4 + 1.2.3 + + + stable + stable + + BSD-3-Clause + +* Fix unserialization of PHP references to internal/user-defined classes using PHP 7.4's `__unserialize` (e.g. `ArrayObject`) + + + + 2020-08-04 + + + 3.1.3 + 1.2.3 + + + stable + stable + + BSD-3-Clause + +* Properly serialize reference groups of size 1 (these can be created by array_walk_recursive and other functions). + Note that this does not fix the general case where values not being serialized are in the same reference group as a value being serialized. +* PHP 8.0 compatibility fixes. + + + + 2020-01-21 + + + 3.1.2 + 1.2.3 + + + stable + stable + + BSD-3-Clause + +* Speed up object, array, reference, and string serialization. +* Speed up unserializing integers between 0 and 65535 (as values and array keys). +* Speed up unserializing objects with declared properties. + + + + 2020-01-16 + + + 3.1.1 + 1.2.3 + + + stable + stable + + BSD-3-Clause + +* Fix bug causing incorrect serialization for 1 in 2**32 strings on 64-bit php installations when string hashes collide. + (https://github.com/igbinary/igbinary/issues/260) + + + + 2021-01-11 + + + 3.1.1a1 + 1.2.2 + + + beta + beta + + BSD-3-Clause + +* Throw when an uninitialized php 7.4 typed property is included in the result of __sleep(), + instead of emitting a notice and attempting to represent the unset/uninitialized value as null (#258). + See https://bugs.php.net/bug.php?id=79002 + + Uninitialized properties without types (from __sleep) continue to cause igbinary to emit notices and are represented as null. + + + + 2019-12-27 + + + 3.1.0 + 1.2.2 + + + beta + beta + + BSD-3-Clause + +* Same as 3.1.0b4. + + + + 2019-12-20 + + + 3.1.0b4 + 1.2.2 + + + beta + beta + + BSD-3-Clause + +* Don't call __destruct for objects where deferred __unserialize calls were not started (e.g. due to Serializable::unserialize throwing). + + + + 2019-12-10 + + + 3.1.0b3 + 1.2.2 + + + beta + beta + + BSD-3-Clause + +* Skip over object properties that are uninitialized or unset when serializing, instead of serializing them as null. + This is done to avoid Errors when unserializing their values for php 7.4 typed properties. + + + + 2019-12-09 + + + 3.1.0b2 + 1.2.1 + + + beta + beta + + BSD-3-Clause + +* Fix crashes related to unserializing instances of classes with php 7.4 typed properties. + + + + 2019-12-08 + + + 3.1.0b1 + 1.2.1 + + + beta + beta + + BSD-3-Clause + +* Support php 7.4's __serialize/__unserialize the same way serialize()/unserialize() does. + This deliberately only supports __serialize/__unserialize in php 7.4, to making switching to/from serialize()/unserialize() as straightforward as possible. + + + + 2019-03-20 + + + 3.0.1 + 1.2.0 + + + stable + stable + + BSD-3-Clause + +* Fix version check when statically building igbinary inside of the php-src folder. + + + + 2019-02-17 + + + 3.0.0 + 1.2.0 + + + stable + stable + + BSD-3-Clause + + * Identical to 3.0.0a2 + + + + 2019-02-13 + + + 3.0.0a2 + 1.2.0 + + + alpha + stable + + BSD-3-Clause + +* Don't use empty string for serializing empty $_SESSION array, it breaks some save handlers. (Issue #231) + Continue treating the empty string as the empty $_SESSION array when unserializing. + + + + 2019-02-09 + + + 3.0.0a1 + 1.2.0 + + + alpha + stable + + BSD-3-Clause + +* Drop support for PHP 5. +* Drop support for APC (APC was only available for PHP5 - It is the predecessor of APCu) +* Emit a warning and return null if igbinary_unserialize() is passed more data to unserialize than expected. +* Fix compilation against PHP 7.4-dev. Igbinary does NOT yet properly serialize/unserialize all classes with PHP 7.4-dev's typed properties. +* The serialization format is exactly the same as igbinary 2.x + + + + 2018-10-20 + + + 2.0.8 + 1.1.2 + + + stable + stable + + BSD-3-Clause + +* Be more aggressive about deduplication when generating serialization of arrays in php 7.0+. +* Define HAVE_IGBINARY on Unix/Linux. (previously defined only on Windows) +* Update formatting/wording of documentation. + + + + 2018-06-27 + + + 2.0.7 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +* Fix compiler warnings about format strings, for errors that should not occur during normal igbinary usage. + + + + 2018-05-12 + + + 2.0.6 + 1.1.1 + + + beta + stable + + BSD-3-Clause + +* Same as 2.0.6RC1 + + + + 2018-04-01 + + + 2.0.6RC1 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +* Same as 2.0.6RC1 +* Fix a bug in Windows debug builds. +* Emit more specific warnings when __sleep() returns a declared property that was unset. +* Fix harmless compiler warnings during builds. +* Fix a build error on PHP7.3-dev. + + + + 2017-11-04 + + + 2.0.5 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +* Same as 2.0.5RC1 (no bugs were reported in that release candidate) + + + + 2017-10-15 + + + 2.0.5RC1 + 1.1.1 + + + beta + stable + + BSD-3-Clause + +* Improve performance when unserializing objects/arrays and serializing objects/arrays/strings in php 5/7. +* Update unserialization of integer object keys for php 7.2: Make those keys accessible when unserializing. +* Properly pick up presence of gcc for default compiler flags (`cc --version` doesn't contain gcc). + Add -O2 to default gcc compiler flags. +* Use empty string for serializing empty $_SESSION array, similar to "session.serialize_handler=php". + Older igbinary releases already unserialize the empty string to the empty array. + + + + 2017-04-14 + + + 2.0.4 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +* Fixes bug #129: Should not call __wakeup() on data which was created by Serializable::unserialize() + + + + 2017-03-31 + + + 2.0.3 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +- Fixes bug #126: Fatal error: "igbinary_serialize_zval: zval has unknown type 0" (IS_UNDEF) + Make this a warning instead of a fatal error (and serialize as null instead), since IS_UNDEF is a known type. + Later releases will fix the root cause of the warning, and consistently omit array/object/other entries for IS_UNDEF. + + + + 2017-02-28 + + + 2.0.2 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +- Compatible with PHP 5.2 - 7.1 +- Fixes crash in Memcached->setMulti (in php 7.0+) when the first level of array elements have references as values. + Other extensions using igbinary shouldn't be affected. + + + + 2016-11-19 + + + 2.0.1 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +- Compatible with PHP 5.2 - 7.0 +- Fixes bug in session decoder not calling wakeup in php 7.0+ +- (Enhancement) Reuses identical strings when unserializing objects and arrays in php 7.0+ + + + + 2016-10-30 + + + 2.0.0 + 1.1.1 + + + stable + stable + + BSD-3-Clause + +- Compatible with PHP 5.2 - 7.0 (Adds PHP 7 support) +- Serialization format is unchanged +- Performance improvements for serialization and unserialization in PHP 5.2 - 7.0 +- (PHP 7) Don't call __destruct if __wakeup threw an exception (or __wakeup wasn't called yet) +- Ports integration with other extensions to PHP 7.0 (session serialization, Memcached, Redis, APCu, etc.) +- Fixes Windows PECL builds for PHP 5.6+ +- Reword warnings for invalid header bytes of serialized data (in igbinary_unserialize). + + 2014-08-29 @@ -134,7 +1076,7 @@ stable stable - New BSD + BSD-3-Clause-3-Clause - Compatible with PHP 5.2 - 5.6 @@ -150,7 +1092,7 @@ stable stable - New BSD + BSD-3-Clause - PECL bug #22614, igbinary_unserialize(FALSE) must return FALSE - PHP bug #54662, unserializing nested objects cause crash @@ -168,7 +1110,7 @@ stable stable - New BSD + BSD-3-Clause - Initial PECL release diff --git a/php_igbinary.h b/php_igbinary.h index 957bebb3..025798a6 100644 --- a/php_igbinary.h +++ b/php_igbinary.h @@ -1,101 +1,22 @@ -/* - +----------------------------------------------------------------------+ - | See COPYING file for further copyright information | - +----------------------------------------------------------------------+ - | Author: Oleg Grenrus | - | See CREDITS for contributors | - +----------------------------------------------------------------------+ -*/ - -#ifndef PHP_IGBINARY_H -#define PHP_IGBINARY_H - -/** Module entry of igbinary. */ -extern zend_module_entry igbinary_module_entry; -#define phpext_igbinary_ptr &igbinary_module_entry - -#ifdef PHP_WIN32 -#define PHP_IGBINARY_API __declspec(dllexport) +#ifndef PHPEXT_IGBINARY_BASE_PHP_IGBINARY_H +#define PHPEXT_IGBINARY_BASE_PHP_IGBINARY_H +#include "php_version.h" +#if PHP_MAJOR_VERSION == 7 || PHP_MAJOR_VERSION == 8 +#include "ext/igbinary/src/php7/php_igbinary.h" #else -#define PHP_IGBINARY_API +#error "Unsupported php version for igbinary build" #endif -ZEND_BEGIN_MODULE_GLOBALS(igbinary) - zend_bool compact_strings; -ZEND_END_MODULE_GLOBALS(igbinary) - -#ifdef ZTS -#include "TSRM.h" -#endif - -#include "ext/standard/php_smart_str.h" - -/** Module init function. */ -PHP_MINIT_FUNCTION(igbinary); - -/** Module shutdown function. */ -PHP_MSHUTDOWN_FUNCTION(igbinary); - -/** Request init function. */ -PHP_RINIT_FUNCTION(igbinary); - -/** Request shutdown function. */ -PHP_RSHUTDOWN_FUNCTION(igbinary); - -/** Module info function for phpinfo(). */ -PHP_MINFO_FUNCTION(igbinary); - -/** string igbinary_serialize(mixed value). - * Returns the binary serialized value. - */ -PHP_FUNCTION(igbinary_serialize); - -/** mixed igbinary_unserialize(string data). - * Unserializes the given inputstring (value). +/** + * The below line is redundant + * (and just mentioning phpext_ in a comment is sufficient). + * This block is only here to make php-src/build/print_include.awk include this file, + * when igbinary is placed in php-src/ext/igbinary/ (instead of compiled separately with `phpize; ...`) */ -PHP_FUNCTION(igbinary_unserialize); - -#ifdef ZTS -#define IGBINARY_G(v) TSRMG(igbinary_globals_id, zend_igbinary_globals *, v) -#else -#define IGBINARY_G(v) (igbinary_globals.v) -#endif - -/** Binary protocol version of igbinary. */ -#define IGBINARY_FORMAT_VERSION 0x00000002 - -/** Backport macros from php 5.3 */ -#ifndef Z_ISREF_P -#define Z_ISREF_P(pz) PZVAL_IS_REF(pz) -#endif - -#ifndef Z_ISREF_PP -#define Z_ISREF_PP(ppz) Z_ISREF_P(*(ppz)) -#endif - -#ifndef Z_SET_ISREF_TO_P -#define Z_SET_ISREF_TO_P(pz, isref) (Z_ISREF_P(pz) = (isref)) -#endif - -#ifndef Z_SET_ISREF_TO_PP -#define Z_SET_ISREF_TO_PP(ppz, isref) Z_SET_ISREF_TO_P(*(ppz), isref) -#endif - -#ifndef Z_ADDREF_P -#define Z_ADDREF_P(pz) ZVAL_ADDREF(pz) +#ifndef phpext_igbinary_ptr +extern zend_module_entry igbinary_module_entry; +#define phpext_igbinary_ptr &igbinary_module_entry #endif +/** End line needed for putting igbinary in php-src/ext/igbinary to work */ -#ifndef Z_ADDREF_PP -#define Z_ADDREF_PP(ppz) Z_ADDREF_P(*(ppz)) #endif -#endif /* PHP_IGBINARY_H */ - - -/* - * Local variables: - * tab-width: 2 - * c-basic-offset: 0 - * End: - * vim600: noet sw=2 ts=2 fdm=marker - * vim<600: noet sw=2 ts=2 - */ diff --git a/hash.h b/src/php7/hash.h similarity index 66% rename from hash.h rename to src/php7/hash.h index 3fb57ece..96355ef2 100644 --- a/hash.h +++ b/src/php7/hash.h @@ -19,25 +19,36 @@ #endif #include +#include "zend_types.h" /** Key/value pair of hash_si. * @author Oleg Grenrus * @see hash_si */ -struct hash_si_pair -{ - char *key; /**< Pointer to key. */ - size_t key_len; /**< Key length. */ - uint32_t value; /**< Value. */ +struct hash_si_pair { + zend_string *key_zstr; /* Contains key, key length, and key hash */ + uint32_t key_hash; /**< Copy of ZSTR_H(key_zstr) (or 1 if hash is truncated to 0). Avoid dereferencing key_zstr if hashes are different. */ + uint32_t value; /**< Value. */ +}; + +enum hash_si_code { + hash_si_code_inserted, + hash_si_code_exists, + hash_si_code_exception +}; + +struct hash_si_result { + enum hash_si_code code; + uint32_t value; }; /** Hash-array. - * Like c++ map. - * Current implementation uses linear probing. + * Like c++ unordered_map. + * Current implementation uses linear probing (with interval 1, 3, 5, or 7). * @author Oleg Grenrus */ struct hash_si { - size_t size; /**< Allocated size of array. */ + size_t mask; /**< Bitmask for the array. size == mask+1 */ size_t used; /**< Used size of array. */ struct hash_si_pair *data; /**< Pointer to array or pairs of data. */ }; @@ -47,13 +58,13 @@ struct hash_si { * @param size initial size of the hash array. * @return 0 on success, 1 else. */ -int hash_si_init (struct hash_si *h, size_t size); +int hash_si_init(struct hash_si *h, uint32_t size); /** Frees hash_si structure. * Doesn't call free(h). * @param h pointer to hash_si struct. */ -void hash_si_deinit (struct hash_si *h); +void hash_si_deinit(struct hash_si *h); /** Inserts value into hash_si. * @param h Pointer to hash_si struct. @@ -62,45 +73,66 @@ void hash_si_deinit (struct hash_si *h); * @param value Value. * @return 0 on success, 1 or 2 else. */ +/* int hash_si_insert (struct hash_si *h, const char *key, size_t key_len, uint32_t value); +*/ /** Finds value from hash_si. - * Value returned thru value param. + * Value returned through value param. * @param h Pointer to hash_si struct. * @param key Pointer to key. * @param key_len Key length. * @param[out] value Found value. * @return 0 if found, 1 if not. */ +/* int hash_si_find (struct hash_si *h, const char *key, size_t key_len, uint32_t * value); +*/ + +/** Finds value from hash_si. + * Value returned through value param. + * @param h Pointer to hash_si struct. + * @param key zend_string with key + * @param[out] value Found value. + * @return 0 if found, 1 if not. + */ +struct hash_si_result hash_si_find_or_insert(struct hash_si *h, zend_string *key, uint32_t value); /** Remove value from hash_si. - * Removed value is available thru value param. + * Removed value is available through value param. * @param h Pointer to hash_si struct. * @param key Pointer to key. * @param key_len Key length. * @param[out] value Removed value. * @return 0 ivalue removed, 1 if not existed. */ +/* int hash_si_remove (struct hash_si *h, const char *key, size_t key_len, uint32_t * value); +*/ /** Travarses hash_si. * Calls traverse_function on every item. Traverse function should not modify hash * @param h Pointer to hash_si struct. * @param traverse_function Function to call on every item of hash_si. */ +/* void hash_si_traverse (struct hash_si *h, int (*traverse_function) (const char *key, size_t key_len, uint32_t value)); +*/ /** Returns size of hash_si. * @param h Pointer to hash_si struct. * @return Size of hash_si. */ -size_t hash_si_size (struct hash_si *h); +static zend_always_inline size_t hash_si_size(struct hash_si *h) { + return h->used; +} /** Returns capacity of hash_si. * @param h Pointer to hash_si struct. * @return Capacity of hash_si. */ -size_t hash_si_capacity (struct hash_si *h); +static zend_always_inline size_t hash_si_capacity(struct hash_si *h) { + return h->mask + 1; +} #endif /* HASH_H */ diff --git a/src/php7/hash_ptr.h b/src/php7/hash_ptr.h new file mode 100644 index 00000000..c71f72dc --- /dev/null +++ b/src/php7/hash_ptr.h @@ -0,0 +1,112 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + +----------------------------------------------------------------------+ + | Author: Oleg Grenrus | + | See CREDITS for contributors | + | This defines hash_si_ptr. | + | It is like hash_si, but the key is always a non-zero uintptr_t | + +----------------------------------------------------------------------+ +*/ + +#ifndef HASH_PTR_H +#define HASH_PTR_H + +#include + +#ifdef PHP_WIN32 +# include "ig_win32.h" +#else +# include /* defines uint32_t etc */ +#endif + +#include +#include "zend_types.h" + +// NULL converted to an integer, on sane platforms. +#define HASH_PTR_KEY_INVALID 0 + +/** Key/value pair of hash_si_ptr. + * @author Oleg Grenrus + * @see hash_si_ptr + */ +struct hash_si_ptr_pair { + uintptr_t key; /**< The key: The address of a pointer, casted to an int (won't be dereferenced). */ + uint32_t value; /**< Value. */ +}; + +/** Hash-array. + * Like c++ std::unordered_map, but does not allow HASH_PTR_KEY_INVALID as a key. + * Current implementation uses linear probing. + * @author Oleg Grenrus + */ +struct hash_si_ptr { + size_t size; /**< Allocated size of array. */ + size_t used; /**< Used size of array. */ + struct hash_si_ptr_pair *data; /**< Pointer to array or pairs of data. */ +}; + +/** Inits hash_si_ptr structure. + * @param h pointer to hash_si_ptr struct. + * @param size initial size of the hash array. + * @return 0 on success, 1 else. + */ +int hash_si_ptr_init(struct hash_si_ptr *h, size_t size); + +/** Frees hash_si_ptr structure. + * Doesn't call free(h). + * @param h pointer to hash_si_ptr struct. + */ +void hash_si_ptr_deinit(struct hash_si_ptr *h); + +/** Inserts value into hash_si_ptr. + * @param h Pointer to hash_si_ptr struct. + * @param key Pointer to key. + * @param key_len Key length. + * @param value Value. + * @return 0 on success, 1 or 2 else. + */ +/* +int hash_si_ptr_insert (struct hash_si_ptr *h, const uintptr_t key, uint32_t value); +*/ + +/** Finds value from hash_si_ptr. + * Value returned through value param. + * @param h Pointer to hash_si_ptr struct. + * @param key Pointer to key. + * @param key_len Key length. + * @param[out] value Found value. + * @return 0 if found, 1 if not. + */ +/* +int hash_si_ptr_find (struct hash_si_ptr *h, const uintptr_t key, uint32_t * value); +*/ + +/** Returns size of hash_si_ptr. + * @param h Pointer to hash_si_ptr struct. + * @return Size of hash_si_ptr. + */ +static zend_always_inline size_t hash_si_ptr_size(struct hash_si_ptr *h) { + return h->used; +} + +/** + * If the key does not exist, add a mapping from key to value and returns SIZE_MAX + * If the key does exist, return the corresponding value + * @param h Pointer to hash_si_ptr struct. + * @param key key to look up or add + * @param value value to insert if it doesn't already exist + * + * @return SIZE_MAX or old, unmodified key + */ +size_t hash_si_ptr_find_or_insert(struct hash_si_ptr *h, const uintptr_t key, uint32_t value); + +/** Returns capacity of hash_si_ptr. + * @param h Pointer to hash_si_ptr struct. + * @return Capacity of hash_si_ptr. + */ +zend_always_inline static size_t hash_si_ptr_capacity(struct hash_si_ptr *h) { + return h->size; +} + +#endif /* HASH_PTR_H */ diff --git a/src/php7/hash_si.c b/src/php7/hash_si.c new file mode 100644 index 00000000..4e58cf2e --- /dev/null +++ b/src/php7/hash_si.c @@ -0,0 +1,183 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + +----------------------------------------------------------------------+ + | Author: Oleg Grenrus | + | See CREDITS for contributors | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +# include "ig_win32.h" +#endif + +#include +#include +#include + +#include + +#include "hash.h" +#include "zend.h" + +/* {{{ hash_si_init */ +/** + * Initializes a hash_si value with the given capacity + */ +int hash_si_init(struct hash_si *h, uint32_t size) { + ZEND_ASSERT((size & (size - 1)) == 0 && size > 0); + + h->mask = size - 1; + h->used = 0; + h->data = (struct hash_si_pair *)ecalloc(size, sizeof(struct hash_si_pair)); + if (h->data == NULL) { + return 1; + } + + return 0; +} +/* }}} */ +/* {{{ hash_si_deinit */ +void hash_si_deinit(struct hash_si *h) { + struct hash_si_pair *const data = h->data; + if (h->used > 0) { + size_t i; + const size_t mask = h->mask; + + for (i = 0; i <= mask; i++) { + if (data[i].key_zstr != NULL) { + zend_string_release(data[i].key_zstr); + } + } + } + + efree(data); +} +/* }}} */ +/* {{{ get_key_hash */ +zend_always_inline static uint32_t get_key_hash(zend_string *key_zstr) { + /* Fetch the hash, computing and storing it in key_zstr if it was not computed before. */ + uint32_t key_hash = ZSTR_HASH(key_zstr); +#if SIZEOF_ZEND_LONG > 4 + if (UNEXPECTED(key_hash == 0)) { + /* A key_hash of uint32_t(0) would be treated like a gap when inserted. Change the hash used to 1 instead. */ + /* uint32_t(ZSTR_HASH) is 0 for 1 in 4 billion - optimized builds may use cmove so there are no branch mispredicts, changing to key_hash >> 32 doesn't speed up benchmark/serialize-stringarray */ + return 1; + } +#endif + return key_hash; +} +/* }}} */ +/* {{{ hash_si_rehash */ +/** Rehash/resize hash_si. + * @param h Pointer to hash_si struct. + */ +zend_always_inline static void hash_si_rehash(struct hash_si *h) { + size_t i; + size_t old_size; + size_t new_size; + size_t new_mask; + struct hash_si_pair *old_data; + struct hash_si_pair *new_data; + + /* Allocate a table with double the capacity (the next power of 2). */ + ZEND_ASSERT(h != NULL); + old_size = h->mask + 1; + new_size = old_size * 2; + new_mask = new_size - 1; + + old_data = h->data; + new_data = (struct hash_si_pair *)ecalloc(new_size, sizeof(struct hash_si_pair)); + h->data = new_data; + h->mask = new_mask; + + /* Copy old entries to new entries */ + for (i = 0; i < old_size; i++) { + const struct hash_si_pair *old_pair = &old_data[i]; + if (old_pair->key_zstr != NULL) { + uint32_t hv = old_pair->key_hash & new_mask; + /* We already computed the hash, avoid recomputing it. */ + /* Do linear probing for the next free slot. */ + while (new_data[hv].key_hash != 0) { + hv = (hv + 1) & new_mask; + } + new_data[hv] = old_data[i]; + } + } + + /* Free old entries */ + efree(old_data); +} +/* }}} */ +/* {{{ hash_si_find_or_insert */ +/** + * If the string key already exists in the map, return the associated value. + * If it doesn't exist, indicate that to the caller. + * + * This is used in igbinary_serialize to deduplicate strings. + */ +struct hash_si_result hash_si_find_or_insert(struct hash_si *h, zend_string *key_zstr, uint32_t value) { + struct hash_si_result result; + struct hash_si_pair *pair; + struct hash_si_pair *data; + uint32_t key_hash = get_key_hash(key_zstr); + size_t mask; + uint32_t hv; + + ZEND_ASSERT(h != NULL); + + mask = h->mask; + hv = key_hash & mask; + data = h->data; + + while (1) { + pair = &data[hv]; + if (pair->key_hash == 0) { + /* This is a brand new key */ + pair->key_zstr = key_zstr; + pair->key_hash = key_hash; + pair->value = value; + h->used++; + + /* The size increased, so check if we need to expand the map */ + if (UNEXPECTED(h->mask * 3 / 4 < h->used)) { + hash_si_rehash(h); + } + result.code = hash_si_code_inserted; + zend_string_addref(key_zstr); + return result; + } else if (pair->key_hash == key_hash && EXPECTED(zend_string_equals(pair->key_zstr, key_zstr))) { + /* This already exists in the hash map */ + result.code = hash_si_code_exists; + result.value = pair->value; + return result; + } + /* linear prob */ + hv = (hv + 1) & mask; + } +} +/* }}} */ +/* {{{ hash_si_traverse */ +/* +void hash_si_traverse(struct hash_si *h, int (*traverse_function) (const char *key, size_t key_len, uint32_t value)) { + size_t i; + + assert(h != NULL && traverse_function != NULL); + + for (i = 0; i < h->size; i++) { + if (h->data[i].key != NULL && traverse_function(h->data[i].key, h->data[i].key_len, h->data[i].value) != 1) { + return; + } + } +} +*/ +/* }}} */ + +/* + * Local variables: + * tab-width: 2 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php7/hash_si_ptr.c b/src/php7/hash_si_ptr.c new file mode 100644 index 00000000..e169f9bc --- /dev/null +++ b/src/php7/hash_si_ptr.c @@ -0,0 +1,173 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + | This is a specialized hash map mapping uintprt_t to int32_t | + +----------------------------------------------------------------------+ + | Author: Oleg Grenrus | + | Modified by Tyson Andre for fixed size, removing unused functions | + | See CREDITS for contributors | + +----------------------------------------------------------------------+ +*/ + +#include + +#include +#include +#include + +#include + +#include "hash_ptr.h" +#include "zend.h" +#include "igbinary_bswap.h" + +/* This assumes that pointers differ in low addresses rather than high addresses */ +inline static uint32_t inline_hash_of_address(uintptr_t ptr) { +#if UINTPTR_MAX > UINT32_MAX + uint64_t hash = ptr; + hash *= 0x5e2d58d8b3bce8d9; + // This is a single assembly instruction on recent compilers/platforms + hash = bswap_64(hash); + return hash; +#else + uint32_t hash = ptr; + hash *= 0x5e2d58d9; + // This is a single assembly instruction on recent compilers/platforms + hash = bswap_32(hash); + return hash; +#endif +} + +/* {{{ nextpow2 */ +/** Next power of 2. + * @param n Integer. + * @return next to n power of 2 . + */ +inline static uint32_t nextpow2(uint32_t n) { + uint32_t m = 1; + while (m < n) { + m = m << 1; + } + + return m; +} +/* }}} */ +/* {{{ hash_si_ptr_init */ +/** + * @param h the pointer to the hash map that will be initialized in place + * @param size the new capacity of the hash map + */ +int hash_si_ptr_init(struct hash_si_ptr *h, size_t size) { + size = nextpow2(size); + + h->size = size; + h->used = 0; + /* Set everything to 0. sets keys to HASH_PTR_KEY_INVALID. */ + h->data = (struct hash_si_ptr_pair *)ecalloc(size, sizeof(struct hash_si_ptr_pair)); + if (h->data == NULL) { + return 1; + } + + return 0; +} +/* }}} */ +/* {{{ hash_si_ptr_deinit */ +/** + * Frees the hash map h + * @param h Pointer to the hash map (hash_si_ptr struct) to free internal data structures of + */ +void hash_si_ptr_deinit(struct hash_si_ptr *h) { + efree(h->data); +} +/* }}} */ +/* {{{ hash_si_ptr_rehash */ +/** Rehash/resize hash_si_ptr. + * @param h Pointer to hash_si_ptr struct. + */ +inline static void hash_si_ptr_rehash(struct hash_si_ptr *h) { + size_t i; + size_t old_size; + size_t size; + size_t mask; + struct hash_si_ptr_pair *old_data; + struct hash_si_ptr_pair *new_data; + + ZEND_ASSERT(h != NULL); + + /* Allocate a table with double the capacity (the next power of 2). */ + old_size = h->size; + size = old_size * 2; + mask = size - 1; + old_data = h->data; + new_data = (struct hash_si_ptr_pair *)ecalloc(size, sizeof(struct hash_si_ptr_pair)); + + h->size = size; + h->data = new_data; + + /* Copy old entries to new entries */ + for (i = 0; i < old_size; i++) { + if (old_data[i].key != HASH_PTR_KEY_INVALID) { + uint32_t hv = inline_hash_of_address(old_data[i].key) & mask; + + /* Do linear probing for the next free slot. */ + while (new_data[hv].key != HASH_PTR_KEY_INVALID) { + ZEND_ASSERT(new_data[hv].key != old_data[i].key); + hv = (hv + 1) & mask; + } + + new_data[hv] = old_data[i]; + } + } + + /* Free old entries */ + efree(old_data); +} +/* }}} */ +/* {{{ hash_si_ptr_find_or_insert */ +/** + * @param h the pointer to the hash map. + * @param key the key (representing to look up (or insert, if it doesn't exist) + * @param value - If the key does not exist, this is the value to associate with key + * @return the old value, or SIZE_MAX if the key is brand new. + */ +size_t hash_si_ptr_find_or_insert(struct hash_si_ptr *h, const uintptr_t key, uint32_t value) { + size_t size; + size_t mask; + uint32_t hv; + + ZEND_ASSERT(h != NULL); + + size = h->size; + mask = size - 1; + hv = inline_hash_of_address(key) & mask; + + while (1) { + if (h->data[hv].key == HASH_PTR_KEY_INVALID) { + /* This is a brand new key */ + h->data[hv].key = key; + h->data[hv].value = value; + h->used++; + + /* The size increased, so check if we need to expand the map */ + if (UNEXPECTED((h->size >> 1) < h->used)) { + hash_si_ptr_rehash(h); + } + return SIZE_MAX; + } else if (h->data[hv].key == key) { + /* This already exists in the hash map */ + return h->data[hv].value; + } + /* linear prob */ + hv = (hv + 1) & mask; + } +} +/* }}} */ + +/* + * Local variables: + * tab-width: 2 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php7/ig_win32.h b/src/php7/ig_win32.h new file mode 100644 index 00000000..f09fa9e1 --- /dev/null +++ b/src/php7/ig_win32.h @@ -0,0 +1,33 @@ +#ifndef _IG_WIN32_H +#define _IG_WIN32_H + +#if PHP_WIN32 +# if defined(_MSC_VER) && _MSC_VER >= 1800 +# include +# include +# else +# include "win32/php_stdint.h" +# ifndef inline +# define inline __inline +# endif + +# ifndef __cplusplus +# if !0 +typedef enum { false = 0, true = 1 } _Bool; +# define bool _Bool +# endif +# else +typedef bool _Bool; +# define bool _Bool +# endif + +# define false 0 +# define true 1 +# define __bool_true_false_are_defined 1 +# endif /* __MSC_VER */ +# ifdef _DEBUG +# include +# endif +#endif /* PHP_WIN32 */ + +#endif /* _IG_WIN32_H */ diff --git a/src/php7/igbinary.c b/src/php7/igbinary.c new file mode 100644 index 00000000..04048c66 --- /dev/null +++ b/src/php7/igbinary.c @@ -0,0 +1,3697 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + +----------------------------------------------------------------------+ + | Author: Oleg Grenrus | + | See CREDITS for contributors | + +----------------------------------------------------------------------+ +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef PHP_WIN32 +# include "ig_win32.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "Zend/zend_alloc.h" +#include "Zend/zend_exceptions.h" +#include "Zend/zend_interfaces.h" +#include "Zend/zend_compile.h" /* ZEND_ACC_NOT_SERIALIZABLE */ +#include "ext/standard/info.h" +#include "ext/standard/php_var.h" +#include /* PRId32/PRId64 used by ZEND_LONG_FMT */ + +#if PHP_VERSION_ID >= 80100 +#include "Zend/zend_enum.h" +#endif + +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) +# include "ext/session/php_session.h" +#endif + +#include "ext/standard/php_incomplete_class.h" + +#if PHP_VERSION_ID < 70400 +#define zend_get_properties_for(struc, purpose) Z_OBJPROP_P((struc)) + +#define zend_release_properties(ht) do {} while (0) +#endif + +#if PHP_VERSION_ID < 70300 +#define zend_string_efree(s) zend_string_release((s)) +#define GC_ADDREF(p) (++GC_REFCOUNT((p))) +#endif + +#if defined(HAVE_APCU_SUPPORT) +# include "ext/apcu/apc_serializer.h" +#endif /* HAVE_APCU_SUPPORT */ + +#include "php_igbinary.h" + +#include "igbinary.h" +#include "igbinary_macros.h" + +#include +#include + +#ifndef PHP_WIN32 +# include +# include +# include +#endif + +#include +#include "hash.h" +#include "hash_ptr.h" +#include "zend_alloc.h" +#include "igbinary_zend_hash.h" + +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) +/** Session serializer function prototypes. */ +PS_SERIALIZER_FUNCS(igbinary); +#endif /* HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) */ + +#if defined(HAVE_APCU_SUPPORT) +/** Apc serializer function prototypes */ +static int APC_SERIALIZER_NAME(igbinary) (APC_SERIALIZER_ARGS); +static int APC_UNSERIALIZER_NAME(igbinary) (APC_UNSERIALIZER_ARGS); +#endif + +static zend_always_inline HashTable *HASH_OF_OBJECT(zval *p) { + ZEND_ASSERT(Z_TYPE_P(p) == IS_OBJECT); + return Z_OBJ_HT_P(p)->get_properties( +#if PHP_VERSION_ID >= 80000 + Z_OBJ_P(p) +#else + p +#endif + ); +} + +#if PHP_VERSION_ID < 70300 +#define zend_string_release_ex(s, persistent) zend_string_release((s)) + +static zend_always_inline void zval_ptr_dtor_str(zval *zval_ptr) +{ + if (Z_REFCOUNTED_P(zval_ptr) && !Z_DELREF_P(zval_ptr)) { + ZEND_ASSERT(Z_TYPE_P(zval_ptr) == IS_STRING); + ZEND_ASSERT(!ZSTR_IS_INTERNED(Z_STR_P(zval_ptr))); + ZEND_ASSERT(!(GC_FLAGS(Z_STR_P(zval_ptr)) & IS_STR_PERSISTENT)); + efree(Z_STR_P(zval_ptr)); + } +} +#endif + +#define RETURN_1_IF_NON_ZERO(cmd) \ + if (UNEXPECTED((cmd) != 0)) { \ + return 1; \ + } + +#ifdef ZEND_ACC_NOT_SERIALIZABLE +# define IGBINARY_IS_NOT_SERIALIZABLE(ce) UNEXPECTED((ce)->ce_flags & (ZEND_ACC_NOT_SERIALIZABLE | ZEND_ACC_ANON_CLASS)) +# define IGBINARY_IS_NOT_UNSERIALIZABLE(ce) IGBINARY_IS_NOT_SERIALIZABLE(ce) +#elif PHP_VERSION_ID >= 70400 +# define IGBINARY_IS_NOT_SERIALIZABLE(ce) UNEXPECTED((ce)->serialize == zend_class_serialize_deny) +# define IGBINARY_IS_NOT_UNSERIALIZABLE(ce) UNEXPECTED((ce)->unserialize == zend_class_unserialize_deny) +#else +// Because '__serialize' is not available prior to 7.4, this check is redundant. +# define IGBINARY_IS_NOT_SERIALIZABLE(ce) (0) +# define IGBINARY_IS_NOT_UNSERIALIZABLE(ce) (0) +#endif + + +/* {{{ Types */ +enum igbinary_type { + /* 00 */ igbinary_type_null, /**< Null. */ + + /* 01 */ igbinary_type_ref8, /**< Array reference. */ + /* 02 */ igbinary_type_ref16, /**< Array reference. */ + /* 03 */ igbinary_type_ref32, /**< Array reference. */ + + /* 04 */ igbinary_type_bool_false, /**< Boolean true. */ + /* 05 */ igbinary_type_bool_true, /**< Boolean false. */ + + /* 06 */ igbinary_type_long8p, /**< Long 8bit positive. */ + /* 07 */ igbinary_type_long8n, /**< Long 8bit negative. */ + /* 08 */ igbinary_type_long16p, /**< Long 16bit positive. */ + /* 09 */ igbinary_type_long16n, /**< Long 16bit negative. */ + /* 0a */ igbinary_type_long32p, /**< Long 32bit positive. */ + /* 0b */ igbinary_type_long32n, /**< Long 32bit negative. */ + + /* 0c */ igbinary_type_double, /**< Double. */ + + /* 0d */ igbinary_type_string_empty, /**< Empty string. */ + + /* 0e */ igbinary_type_string_id8, /**< String id. */ + /* 0f */ igbinary_type_string_id16, /**< String id. */ + /* 10 */ igbinary_type_string_id32, /**< String id. */ + + /* 11 */ igbinary_type_string8, /**< String. */ + /* 12 */ igbinary_type_string16, /**< String. */ + /* 13 */ igbinary_type_string32, /**< String. */ + + /* 14 */ igbinary_type_array8, /**< Array. */ + /* 15 */ igbinary_type_array16, /**< Array. */ + /* 16 */ igbinary_type_array32, /**< Array. */ + + /* 17 */ igbinary_type_object8, /**< Object. */ + /* 18 */ igbinary_type_object16, /**< Object. */ + /* 19 */ igbinary_type_object32, /**< Object. */ + + /* 1a */ igbinary_type_object_id8, /**< Object class name string id. */ + /* 1b */ igbinary_type_object_id16, /**< Object class name string id. */ + /* 1c */ igbinary_type_object_id32, /**< Object class name string id. */ + + /* 1d */ igbinary_type_object_ser8, /**< Object serialized data. */ + /* 1e */ igbinary_type_object_ser16, /**< Object serialized data. */ + /* 1f */ igbinary_type_object_ser32, /**< Object serialized data. */ + + /* 20 */ igbinary_type_long64p, /**< Long 64bit positive. */ + /* 21 */ igbinary_type_long64n, /**< Long 64bit negative. */ + + /* 22 */ igbinary_type_objref8, /**< Object reference. */ + /* 23 */ igbinary_type_objref16, /**< Object reference. */ + /* 24 */ igbinary_type_objref32, /**< Object reference. */ + + /* 25 */ igbinary_type_ref, /**< Simple reference */ + /* 26 */ igbinary_type_string64, /**< String larger than 4GB (originally, php strings had a limit of 32-bit lengths). */ + /* 27 */ igbinary_type_enum_case, /**< PHP 8.1 Enum case. */ +}; + +/* Defers calls to zval_ptr_dtor for values that are refcounted. */ +struct deferred_dtor_tracker { + zval *zvals; /**< refcounted objects and arrays to call dtor on after unserializing. See i_zval_ptr_dtor */ + size_t count; /**< count of refcounted in array for calls to dtor */ + size_t capacity; /**< capacity of refcounted in array for calls to dtor */ +}; + +/** Serializer data. + * @author Oleg Grenrus + */ +struct igbinary_serialize_data { + uint8_t *buffer; /**< Buffer. */ + size_t buffer_size; /**< Buffer size. */ + size_t buffer_capacity; /**< Buffer capacity. */ + bool scalar; /**< Serializing scalar. */ + bool compact_strings; /**< Check for duplicate strings. */ + struct hash_si strings; /**< Hash of already serialized strings. */ + struct hash_si_ptr references; /**< Hash of already serialized potential references. (non-NULL uintptr_t => int32_t) */ + uint32_t references_id; /**< Number of things that the unserializer might think are references. >= length of references */ + uint32_t string_count; /**< Serialized string count, used for back referencing */ + + struct deferred_dtor_tracker deferred_dtor_tracker; /**< refcounted objects and arrays to call dtor on after serializing. See i_zval_ptr_dtor */ +}; + +/* +Object { + reference {scalar, object, array, null} (convert to reference, share reference in zval_ref) + object {} (convert to zend_object, share zend_object* in reference) + array {} (convert to zend_array, share zend_array* in reference) + empty array {} (use ZVAL_EMPTY_ARRAY to create zvals) +} +*/ +enum zval_ref_type { + IG_REF_IS_REFERENCE, // Points to zend_reference + IG_REF_IS_OBJECT, // Points to zend_object + IG_REF_IS_ARRAY, // Points to zend_array +#if PHP_VERSION_ID >= 70300 + IG_REF_IS_EMPTY_ARRAY, // Use the macro ZVAL_EMPTY_ARRAY to create a pointer to the empty array with the correct type info flags. +#endif +}; + +struct igbinary_value_ref { + // We reuse temporary values for object properties that are references or arrays. + union { + zend_reference *reference; + zend_object *object; + zend_array *array; + } reference; + enum zval_ref_type type; +}; + +struct deferred_unserialize_call { + zval param; /* The array parameter passed to the __unserialize call */ + zend_object *object; /* The object which has a deferred call to __unserialize that is going to get called. */ +}; + +struct deferred_call { + union { + zend_object *wakeup; +#if PHP_VERSION_ID >= 70400 + /* Currently, zvals are safe to relocate */ + struct deferred_unserialize_call unserialize; +#endif + } data; +#if PHP_VERSION_ID >= 70400 + zend_bool is_unserialize; +#endif +}; +/** Unserializer data. + * @author Oleg Grenrus + */ +struct igbinary_unserialize_data { + const uint8_t *buffer; /**< Buffer with bytes to unserialize. */ + const uint8_t *buffer_end; /**< Buffer size. */ + const uint8_t *buffer_ptr; /**< Current read offset. */ + + zend_string **strings; /**< Unserialized strings. */ + size_t strings_count; /**< Unserialized string count. */ + size_t strings_capacity; /**< Unserialized string array capacity. */ + + struct igbinary_value_ref *references; /**< Unserialized Arrays/Objects/References */ + size_t references_count; /**< Unserialized array/objects count. */ + size_t references_capacity; /**< Unserialized array/object array capacity. */ + + struct deferred_call *deferred; /**< objects&data for calls to __unserialize/__wakeup */ + size_t deferred_capacity; /**< capacity of objects in array for calls to __unserialize/__wakeup */ + uint32_t deferred_count; /**< count of objects in array for calls to __unserialize/__wakeup. NOTE: Current php releases including 8.1 limit the total number of objects to a 32-bit integer. */ + zend_bool deferred_finished; /**< whether the deferred calls were performed */ + struct deferred_dtor_tracker deferred_dtor_tracker; /**< refcounted objects and arrays to call dtor on after unserializing. See i_zval_ptr_dtor */ +#if PHP_VERSION_ID >= 70400 + HashTable *ref_props; /**< objects&data for calls to __unserialize/__wakeup */ +#endif + zend_long cur_depth; /**< current recursion depth in igbinary_unserialize_zval */ + zend_long max_depth; /**< snapshot of unserialize_max_depth ini at entry */ +}; + +#define IGB_REF_VAL_2(igsd, n) ((igsd)->references[(n)]) +#define IGB_NEEDS_MORE_DATA(igsd, n) UNEXPECTED((size_t)((igsd)->buffer_end - (igsd)->buffer_ptr) < (n)) +#define IGB_REMAINING_BYTES(igsd) ((unsigned int)((igsd)->buffer_end - (igsd)->buffer_ptr)) +#define IGB_BUFFER_OFFSET(igsd) ((unsigned int)((igsd)->buffer_ptr - (igsd)->buffer)) + +#define WANT_CLEAR (0) +#define WANT_REF (1 << 1) + +/* }}} */ +/* {{{ Serializing functions prototypes */ +zend_always_inline static int igbinary_serialize_data_init(struct igbinary_serialize_data *igsd, bool scalar); +zend_always_inline static void igbinary_serialize_data_deinit(struct igbinary_serialize_data *igsd); + +zend_always_inline static void igbinary_serialize_header(struct igbinary_serialize_data *igsd); + +zend_always_inline static int igbinary_serialize8(struct igbinary_serialize_data *igsd, uint8_t i); +zend_always_inline static int igbinary_serialize16(struct igbinary_serialize_data *igsd, uint16_t i); +zend_always_inline static int igbinary_serialize32(struct igbinary_serialize_data *igsd, uint32_t i); +zend_always_inline static int igbinary_serialize64(struct igbinary_serialize_data *igsd, uint64_t i); + +zend_always_inline static int igbinary_serialize_null(struct igbinary_serialize_data *igsd); +zend_always_inline static int igbinary_serialize_bool(struct igbinary_serialize_data *igsd, int b); +zend_always_inline static int igbinary_serialize_long(struct igbinary_serialize_data *igsd, zend_long l); +zend_always_inline static int igbinary_serialize_double(struct igbinary_serialize_data *igsd, double d); +zend_always_inline static int igbinary_serialize_string(struct igbinary_serialize_data *igsd, zend_string *s); +zend_always_inline static int igbinary_serialize_chararray(struct igbinary_serialize_data *igsd, const char *s, size_t len); + +zend_always_inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, zval *z, bool object, bool incomplete_class, bool serialize_props); +zend_always_inline static int igbinary_serialize_array_ref(struct igbinary_serialize_data *igsd, zval *z, bool object); +zend_always_inline static int igbinary_serialize_array_sleep(struct igbinary_serialize_data *igsd, zval *z, HashTable *ht, zend_class_entry *ce); +zend_always_inline static int igbinary_serialize_object_name(struct igbinary_serialize_data *igsd, zend_string *name); +zend_always_inline static int igbinary_serialize_object(struct igbinary_serialize_data *igsd, zval *z); + +static int igbinary_serialize_zval(struct igbinary_serialize_data *igsd, zval *z); +/* }}} */ +/* {{{ Unserializing functions prototypes */ +zend_always_inline static int igbinary_unserialize_data_init(struct igbinary_unserialize_data *igsd); +zend_always_inline static void igbinary_unserialize_data_deinit(struct igbinary_unserialize_data *igsd); + +zend_always_inline static int igbinary_unserialize_header(struct igbinary_unserialize_data *igsd); + +zend_always_inline static uint8_t igbinary_unserialize8(struct igbinary_unserialize_data *igsd); +zend_always_inline static uint16_t igbinary_unserialize16(struct igbinary_unserialize_data *igsd); +zend_always_inline static uint32_t igbinary_unserialize32(struct igbinary_unserialize_data *igsd); +zend_always_inline static uint64_t igbinary_unserialize64(struct igbinary_unserialize_data *igsd); + +zend_always_inline static int igbinary_unserialize_long(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zend_long *ret); +zend_always_inline static int igbinary_unserialize_double(struct igbinary_unserialize_data *igsd, double *ret); +zend_always_inline static zend_string *igbinary_unserialize_string(struct igbinary_unserialize_data *igsd, enum igbinary_type t); +zend_always_inline static zend_string *igbinary_unserialize_chararray(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zend_bool check_interned); + +zend_always_inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags, zend_bool create_ref); +zend_always_inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags); +static int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, zend_class_entry *ce); + +zend_always_inline static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval *const z, int flags); +static int igbinary_unserialize_zval_inner(struct igbinary_unserialize_data *igsd, zval *const z, int flags); +/* }}} */ +/* {{{ arginfo */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_igbinary_serialize, 0, 0, 1) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_igbinary_unserialize, 0, 0, 1) + ZEND_ARG_INFO(0, str) +ZEND_END_ARG_INFO() +/* }}} */ +/* {{{ igbinary_functions[] */ +/** Exported php functions. */ +zend_function_entry igbinary_functions[] = { + PHP_FE(igbinary_serialize, arginfo_igbinary_serialize) + PHP_FE(igbinary_unserialize, arginfo_igbinary_unserialize) + PHP_FE_END +}; +/* }}} */ + +/* {{{ igbinary dependencies */ +static const zend_module_dep igbinary_module_deps[] = { + ZEND_MOD_REQUIRED("standard") +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) + ZEND_MOD_REQUIRED("session") +#endif +#if defined(HAVE_APCU_SUPPORT) + ZEND_MOD_OPTIONAL("apcu") +#endif + ZEND_MOD_END +}; +/* }}} */ + +/* {{{ igbinary_module_entry */ +zend_module_entry igbinary_module_entry = { + STANDARD_MODULE_HEADER_EX, NULL, + igbinary_module_deps, + "igbinary", + igbinary_functions, + PHP_MINIT(igbinary), + PHP_MSHUTDOWN(igbinary), + NULL, + NULL, + PHP_MINFO(igbinary), + PHP_IGBINARY_VERSION, + STANDARD_MODULE_PROPERTIES +}; +/* }}} */ + +ZEND_DECLARE_MODULE_GLOBALS(igbinary) + +/* {{{ ZEND_GET_MODULE */ +#ifdef COMPILE_DL_IGBINARY +ZEND_GET_MODULE(igbinary) +#endif +/* }}} */ + +/* {{{ INI entries */ +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN("igbinary.compact_strings", "1", PHP_INI_ALL, OnUpdateBool, compact_strings, zend_igbinary_globals, igbinary_globals) +PHP_INI_END() +/* }}} */ + +/* {{{ php_igbinary_init_globals */ +static void php_igbinary_init_globals(zend_igbinary_globals *igbinary_globals) { + igbinary_globals->compact_strings = 1; +} +/* }}} */ + +/* {{{ PHP_MINIT_FUNCTION */ +/** + * The module init function. + * This allocates the persistent resources of this PHP module. + */ +PHP_MINIT_FUNCTION(igbinary) { + (void)type; + (void)module_number; + ZEND_INIT_MODULE_GLOBALS(igbinary, php_igbinary_init_globals, NULL); + +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) + php_session_register_serializer("igbinary", + PS_SERIALIZER_ENCODE_NAME(igbinary), + PS_SERIALIZER_DECODE_NAME(igbinary)); +#endif + +#if defined(HAVE_APCU_SUPPORT) + apc_register_serializer("igbinary", + APC_SERIALIZER_NAME(igbinary), + APC_UNSERIALIZER_NAME(igbinary), + NULL); +#endif + + REGISTER_INI_ENTRIES(); + + return SUCCESS; +} +/* }}} */ +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +/** + * The module shutdown function. + * This cleans up all persistent resources of this PHP module. + */ +PHP_MSHUTDOWN_FUNCTION(igbinary) { + (void)type; + (void)module_number; + +#ifdef ZTS + ts_free_id(igbinary_globals_id); +#endif + + /* + * Clean up ini entries. + * Aside: It seems like the php_session_register_serializer unserializes itself, since MSHUTDOWN in ext/wddx/wddx.c doesn't exist? + */ + UNREGISTER_INI_ENTRIES(); + + return SUCCESS; +} +/* }}} */ +/* {{{ PHP_MINFO_FUNCTION */ +PHP_MINFO_FUNCTION(igbinary) { + (void)zend_module; + php_info_print_table_start(); + php_info_print_table_row(2, "igbinary support", "enabled"); + php_info_print_table_row(2, "igbinary version", PHP_IGBINARY_VERSION); +#if defined(HAVE_APCU_SUPPORT) + php_info_print_table_row(2, "igbinary APCu serializer ABI", APC_SERIALIZER_ABI); +#else + php_info_print_table_row(2, "igbinary APCu serializer ABI", "no"); +#endif +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) + php_info_print_table_row(2, "igbinary session support", "yes"); +#else + php_info_print_table_row(2, "igbinary session support", "no"); +#endif + php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); +} +/* }}} */ + +/* {{{ igsd management */ +/* Append to list of references to take out later. Returns SIZE_MAX on allocation error. */ +static zend_always_inline size_t igsd_append_ref(struct igbinary_unserialize_data *igsd, struct igbinary_value_ref v) +{ + size_t ref_n; + if (igsd->references_count + 1 >= igsd->references_capacity) { + igsd->references_capacity *= 2; + + struct igbinary_value_ref *new_references = erealloc(igsd->references, sizeof(igsd->references[0]) * igsd->references_capacity); + if (UNEXPECTED(new_references == NULL)) { + return SIZE_MAX; + } + igsd->references = new_references; + } + + ref_n = igsd->references_count++; + IGB_REF_VAL_2(igsd, ref_n) = v; + return ref_n; +} + +static zend_always_inline int igsd_ensure_defer_capacity(struct igbinary_unserialize_data *igsd) { + if (igsd->deferred_count >= igsd->deferred_capacity) { + if (igsd->deferred_capacity == 0) { + igsd->deferred_capacity = 2; + igsd->deferred = emalloc(sizeof(igsd->deferred[0]) * igsd->deferred_capacity); + } else { + igsd->deferred_capacity *= 2; + struct deferred_call *old_deferred = igsd->deferred; + igsd->deferred = erealloc(old_deferred, sizeof(igsd->deferred[0]) * igsd->deferred_capacity); + if (UNEXPECTED(igsd->deferred == NULL)) { + igsd->deferred = old_deferred; + return 1; + } + } + } + return 0; +} + +static inline int igsd_defer_wakeup(struct igbinary_unserialize_data *igsd, zend_object *object) { + // TODO: This won't be properly garbage collected if there is an OOM error, but would php terminate instead? + RETURN_1_IF_NON_ZERO(igsd_ensure_defer_capacity(igsd)); + + struct deferred_call *c = &igsd->deferred[igsd->deferred_count++]; + c->data.wakeup = object; +#if PHP_VERSION_ID >= 70400 + c->is_unserialize = 0; +#endif + return 0; +} + +/* igsd_defer_unserialize {{{ */ +#if PHP_VERSION_ID >= 70400 +static inline int igsd_defer_unserialize(struct igbinary_unserialize_data *igsd, zend_object *object, zval param) { + RETURN_1_IF_NON_ZERO(igsd_ensure_defer_capacity(igsd)); + + struct deferred_call *c = &igsd->deferred[igsd->deferred_count++]; + struct deferred_unserialize_call* call = &c->data.unserialize; + call->object = object; + ZEND_ASSERT(Z_TYPE(param) == IS_ARRAY); + call->param = param; + c->is_unserialize = 1; + return 0; +} +#endif +/* }}} */ + +/* {{{ igbinary_finish_deferred_calls + * After all object instances were unserialized, perform the deferred calls to __wakeup() on all of the objects implementing that method. + */ +static int igbinary_finish_deferred_calls(struct igbinary_unserialize_data *igsd) { +#if PHP_VERSION_ID >= 70400 && PHP_VERSION_ID < 80000 + zval unserialize_name; +#endif + zval wakeup_name; + uint32_t i; + struct deferred_call *deferred_arr; + uint32_t deferred_count = igsd->deferred_count; + zend_bool delayed_call_failed = 0; + igsd->deferred_finished = 1; + if (deferred_count == 0) { /* nothing to do */ + return 0; + } + deferred_arr = igsd->deferred; +#if PHP_VERSION_ID >= 70400 && PHP_VERSION_ID < 80000 + ZVAL_STRINGL(&unserialize_name, "__unserialize", sizeof("__unserialize") - 1); +#endif + ZVAL_STRINGL(&wakeup_name, "__wakeup", sizeof("__wakeup") - 1); + for (i = 0; i < deferred_count; i++) { + struct deferred_call *deferred = &deferred_arr[i]; +#if PHP_VERSION_ID >= 70400 + if (deferred->is_unserialize) { + struct deferred_unserialize_call *unserialize_call = &deferred->data.unserialize; + zend_object *const obj = unserialize_call->object; + ZEND_ASSERT(Z_TYPE(unserialize_call->param) == IS_ARRAY); + + if (!delayed_call_failed) { +#if PHP_VERSION_ID >= 80000 + /* Copy the parameter for __unserialize so that changes in __unserialize won't mutate the original. */ + // ZVAL_COPY(¶m, &unserialize_call->param); + BG(serialize_lock)++; + zend_call_known_instance_method_with_1_params( + obj->ce->__unserialize, obj, NULL, &unserialize_call->param); + if (EG(exception)) { + delayed_call_failed = 1; + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + } + BG(serialize_lock)--; +#else + zval retval; + zval zv; + ZVAL_OBJ(&zv, obj); + /* Copy the parameter for __unserialize so that changes in __unserialize won't mutate the original. */ + // ZVAL_COPY(¶m, &unserialize_call->param); + BG(serialize_lock)++; + if (call_user_function(CG(function_table), &zv, &unserialize_name, &retval, 1, &unserialize_call->param) == FAILURE || Z_ISUNDEF(retval)) + { + delayed_call_failed = 1; + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + } + BG(serialize_lock)--; + zval_ptr_dtor(&retval); +#endif + } else { + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + } + + zval_ptr_dtor(&unserialize_call->param); + } else +#endif + { + zend_object *obj = deferred->data.wakeup; + if (!delayed_call_failed) { + zval retval; /* return value of __wakeup */ + zval rval; + ZVAL_OBJ(&rval, obj); + if (UNEXPECTED(call_user_function(CG(function_table), &rval, &wakeup_name, &retval, 0, 0) == FAILURE || Z_ISUNDEF(retval))) { + delayed_call_failed = 1; + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + } + zval_ptr_dtor(&retval); + } else { + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + } + } + } + zval_ptr_dtor_str(&wakeup_name); +#if PHP_VERSION_ID >= 70400 && PHP_VERSION_ID < 80000 + zval_ptr_dtor_str(&unserialize_name); +#endif + return delayed_call_failed; +} +/* }}} */ +/* }}} */ + +/* {{{ igsd_ensure_deferred_dtor_capacity(struct igbinary_serialize_data *igsd) */ +static inline int igsd_ensure_deferred_dtor_capacity(struct deferred_dtor_tracker *tracker) { + if (tracker->count >= tracker->capacity) { + if (tracker->capacity == 0) { + tracker->capacity = 2; + tracker->zvals = emalloc(sizeof(tracker->zvals[0]) * tracker->capacity); + } else { + tracker->capacity *= 2; + zval *old_deferred_dtor = tracker->zvals; + tracker->zvals = erealloc(old_deferred_dtor, sizeof(tracker->zvals[0]) * tracker->capacity); + if (UNEXPECTED(tracker->zvals == NULL)) { + tracker->zvals = old_deferred_dtor; + return 1; + } + } + } + return 0; +} +/* }}} */ + +/* {{{ free_deferred_dtors(struct deferred_dtor_tracker *tracker) */ +static zend_always_inline void free_deferred_dtors(struct deferred_dtor_tracker *tracker) { + zval *const zvals = tracker->zvals; + if (zvals) { + const size_t n = tracker->count; + size_t i; + for (i = 0; i < n; i++) { + /* fprintf(stderr, "freeing i=%d id=%d refcount=%d\n", (int)i, (int)Z_OBJ_HANDLE(zvals[i]), (int)Z_REFCOUNT(zvals[i])); */ + zval_ptr_dtor(&zvals[i]); + } + efree(zvals); + } +} +/* }}} */ + +/* {{{ igsd_addref_and_defer_dtor(struct igbinary_serialize_data *igsd, zval *z) */ +static zend_always_inline int igsd_addref_and_defer_dtor(struct deferred_dtor_tracker *tracker, zval *z) { + if (!Z_REFCOUNTED_P(z)) { + return 0; + } + if (UNEXPECTED(igsd_ensure_deferred_dtor_capacity(tracker))) { + return 1; + } + + ZEND_ASSERT(Z_REFCOUNT_P(z) >= 1); /* Expect that there were references at the time this was serialized */ + ZVAL_COPY(&tracker->zvals[tracker->count++], z); /* Copy and increase reference count */ + return 0; +} +/* }}} */ +/* {{{ igsd_defer_dtor(struct igbinary_serialize_data *igsd, zval *z) */ +static inline int igsd_defer_dtor(struct deferred_dtor_tracker *tracker, zval *z) { + if (!Z_REFCOUNTED_P(z)) { + return 0; + } + if (igsd_ensure_deferred_dtor_capacity(tracker)) { + return 1; + } + + ZEND_ASSERT(Z_REFCOUNT_P(z) >= 1); /* Expect that there were references at the time this was serialized */ + ZVAL_COPY_VALUE(&tracker->zvals[tracker->count++], z); /* Copy without increasing reference count */ + return 0; +} +/* }}} */ +/* {{{ int igbinary_serialize(uint8_t**, size_t*, zval*) */ +IGBINARY_API int igbinary_serialize(uint8_t **ret, size_t *ret_len, zval *z) { + return igbinary_serialize_ex(ret, ret_len, z, NULL); +} +/* }}} */ +/* {{{ int igbinary_serialize_ex(uint8_t**, size_t*, zval*, igbinary_memory_manager*) */ +/** + * Serializes data, and writes the allocated byte buffer into ret and the buffer's length into ret_len. + * @param ret output parameter + * @param ret_len length of byte buffer ret + * @param z the zval (data) to serialize + * @param memory_manager (nullable) the memory manager to use for allocating/reallocating the buffer of serialized data. Used by extensions such as APCu + * @return 0 on success, 1 on failure + */ +IGBINARY_API int igbinary_serialize_ex(uint8_t **ret, size_t *ret_len, zval *z, struct igbinary_memory_manager *memory_manager) { + struct igbinary_serialize_data igsd; + uint8_t *tmpbuf; + int return_code; + // While we can't get passed references through the PHP_FUNCTIONs igbinary declares, third party code can invoke igbinary's methods with references. + // See https://github.com/php-memcached-dev/php-memcached/issues/326 + if (UNEXPECTED(Z_TYPE_P(z) == IS_INDIRECT)) { + z = Z_INDIRECT_P(z); + } + ZVAL_DEREF(z); + + if (UNEXPECTED(igbinary_serialize_data_init(&igsd, Z_TYPE_P(z) != IS_OBJECT && Z_TYPE_P(z) != IS_ARRAY))) { + zend_error(E_WARNING, "igbinary_serialize: cannot init igsd"); + return 1; + } + + igbinary_serialize_header(&igsd); + return_code = 0; + + if (UNEXPECTED(igbinary_serialize_zval(&igsd, z) != 0)) { + return_code = 1; + goto cleanup; + } + + /* Explicit null termination */ + /* TODO: Stop doing this in the next major version, serialized data can contain nulls in the middle and callers should check length */ + if (UNEXPECTED(igbinary_serialize8(&igsd, 0) != 0)) { + return_code = 1; + goto cleanup; + } + + /* shrink buffer to the real length, ignore errors */ + if (UNEXPECTED(memory_manager)) { + tmpbuf = memory_manager->alloc(igsd.buffer_size, memory_manager->context); + if (tmpbuf != NULL) { + memcpy(tmpbuf, igsd.buffer, igsd.buffer_size); + } + + if (tmpbuf == NULL) { + return_code = 1; + goto cleanup; + } + *ret = tmpbuf; + *ret_len = igsd.buffer_size - 1; + } else { + /* Set return values */ + *ret_len = igsd.buffer_size - 1; + *ret = igsd.buffer; + igsd.buffer = NULL; + } + +cleanup: + igbinary_serialize_data_deinit(&igsd); + return return_code; +} +/* }}} */ +/* {{{ int igbinary_unserialize(const uint8_t *, size_t, zval **) */ +/** + * Unserializes the data into z + * @param buf the read-only buffer with the serialized data + * @param buf_len the length of that buffer. + * @param z output parameter. Will contain the unserialized value(zval). + * @return 0 on success, 1 on failure + */ +IGBINARY_API int igbinary_unserialize(const uint8_t *buf, size_t buf_len, zval *z) { + struct igbinary_unserialize_data igsd; + int ret = 0; + + igbinary_unserialize_data_init(&igsd); + + igsd.buffer = buf; + igsd.buffer_ptr = buf; + igsd.buffer_end = buf + buf_len; + + if (UNEXPECTED(igbinary_unserialize_header(&igsd))) { + ret = 1; + goto cleanup; + } + + if (UNEXPECTED(igbinary_unserialize_zval(&igsd, z, WANT_CLEAR))) { + ret = 1; + goto cleanup; + } + if (Z_REFCOUNTED_P(z)) { +#if PHP_VERSION_ID >= 70200 + zend_refcounted *ref = Z_COUNTED_P(z); + gc_check_possible_root(ref); +#else + gc_check_possible_root(z); +#endif + } + + if (UNEXPECTED(igsd.buffer_ptr < igsd.buffer_end)) { + // https://github.com/igbinary/igbinary/issues/64 + zend_error(E_WARNING, "igbinary_unserialize: received more data to unserialize than expected"); + ret = 1; + goto cleanup; + } + + if (UNEXPECTED(igbinary_finish_deferred_calls(&igsd))) { + ret = 1; + goto cleanup; + } +cleanup: + igbinary_unserialize_data_deinit(&igsd); + + return ret; +} +/* }}} */ +/* {{{ proto string igbinary_unserialize(mixed value) */ +/** + * @see igbinary.php for more detailed API documentation. + */ +PHP_FUNCTION(igbinary_unserialize) { + char *string = NULL; + size_t string_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &string, &string_len) == FAILURE) { + RETURN_NULL(); + } + + if (string_len <= 0) { + RETURN_FALSE; + } + + if (igbinary_unserialize((uint8_t *)string, string_len, return_value) != 0) { + /* FIXME: is this a good place? a catch all */ + zval_ptr_dtor(return_value); + RETURN_NULL(); + } +} +/* }}} */ +/* {{{ proto mixed igbinary_serialize(string value) */ +/** + * @see igbinary.php for more detailed API documentation. + */ +PHP_FUNCTION(igbinary_serialize) { + zval *z; + uint8_t *string; + size_t string_len; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &z) == FAILURE) { + RETURN_NULL(); + } + + if (igbinary_serialize(&string, &string_len, z) != 0) { + RETURN_NULL(); + } + + RETVAL_STRINGL((char *)string, string_len); + efree(string); +} +/* }}} */ +#if HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) +/* {{{ Serializer encode function */ +/** + * This provides a serializer encode function for PHP's session module (using igbinary), + * if igbinary was compiled with session support. + * + * Session support has to be statically compiled into php to use igbinary, + * due to the lack of a cross-platform way to register a session serializer/unserializer + * when the session module isn't available. + */ +PS_SERIALIZER_ENCODE_FUNC(igbinary) +{ + zval *session_vars; + zend_string *result; + struct igbinary_serialize_data igsd; + + session_vars = &(PS(http_session_vars)); + if (Z_ISREF_P(session_vars)) { + session_vars = Z_REFVAL_P(session_vars); + } + if (igbinary_serialize_data_init(&igsd, false)) { + zend_error(E_WARNING, "igbinary_serialize: cannot init igsd"); + return ZSTR_EMPTY_ALLOC(); + } + + igbinary_serialize_header(&igsd); + + /** We serialize the passed in array of session_var (including the empty array, for #231) */ + /** the same way we would serialize a regular array. */ + /** The corresponding PS_SERIALIZER_DECODE_FUNC will unserialize the array and individually add the session variables. */ + if (igbinary_serialize_array(&igsd, session_vars, false, false, true) != 0) { + zend_error(E_WARNING, "igbinary_serialize: cannot serialize session variables"); + result = ZSTR_EMPTY_ALLOC(); + } else { + /* Copy the buffer to a new zend_string */ + result = zend_string_init((const char *)igsd.buffer, igsd.buffer_size, 0); + } + + igbinary_serialize_data_deinit(&igsd); + + return result; +} +/* }}} */ +/* {{{ Serializer decode function */ +/** + * This provides a serializer decode function for PHP's session module (using igbinary), + * if igbinary was compiled with session support. + * + * Session support has to be statically compiled into php to use igbinary, + * due to the lack of a cross-platform way to register a session serializer/unserializer + * when the session module isn't available. + * + * This is similar to PS_SERIALIZER_DECODE_FUNC(php) from ext/session/session.c + */ +PS_SERIALIZER_DECODE_FUNC(igbinary) { + HashTable *tmp_hash; + zval z; + zval *d; + zend_string *key; + int ret = 0; + + struct igbinary_unserialize_data igsd; + + if (!val || vallen == 0) { + return SUCCESS; + } + + if (igbinary_unserialize_data_init(&igsd) != 0) { + return FAILURE; + } + + igsd.buffer = (const uint8_t *)val; + igsd.buffer_ptr = igsd.buffer; + igsd.buffer_end = igsd.buffer + vallen; + + if (UNEXPECTED(igbinary_unserialize_header(&igsd))) { + ret = 1; + goto deinit; + } + + /** The serializer serialized the session variables as an array. So, we unserialize that array. */ + /** We then iterate over the array to set the individual session variables (managing the reference counts), then free the original array. */ + if (UNEXPECTED(igbinary_unserialize_zval(&igsd, &z, WANT_CLEAR))) { + ret = 1; + goto deinit; + } + + ret = igbinary_finish_deferred_calls(&igsd); + +deinit: + igbinary_unserialize_data_deinit(&igsd); + if (UNEXPECTED(ret)) { + return FAILURE; + } + + /* Validate that this is of the correct data type */ + tmp_hash = HASH_OF(&z); + if (tmp_hash == NULL) { + zval_ptr_dtor(&z); + return FAILURE; + } + + ZEND_HASH_FOREACH_STR_KEY_VAL(tmp_hash, key, d) { + if (key == NULL) { /* array key is a number, how? Skip it. */ + /* ??? */ + continue; + } + if (php_set_session_var(key, d, NULL)) { /* Added to session successfully */ + /* Refcounted types such as arrays, objects, references need to have references incremented manually, so that zval_ptr_dtor doesn't clean up pointers they include. */ + /* Non-refcounted types have the data copied. */ + Z_TRY_ADDREF_P(d); + } + } ZEND_HASH_FOREACH_END(); + + zval_ptr_dtor(&z); + + return SUCCESS; +} +/* }}} */ +#endif /* HAVE_PHP_SESSION && !defined(COMPILE_DL_SESSION) */ + +#if defined(HAVE_APCU_SUPPORT) +/* {{{ apc_serialize function */ +static int APC_SERIALIZER_NAME(igbinary) ( APC_SERIALIZER_ARGS ) { + (void)config; + + if (igbinary_serialize(buf, buf_len, (zval *)value) == 0) { + /* flipped semantics - We return 1 to indicate success to APCu (and 0 for failure) */ + return 1; + } + return 0; +} +/* }}} */ +/* {{{ apc_unserialize function */ +static int APC_UNSERIALIZER_NAME(igbinary) ( APC_UNSERIALIZER_ARGS ) { + (void)config; + + if (igbinary_unserialize(buf, buf_len, value) == 0) { + /* flipped semantics - We return 1 to indicate success to APCu (and 0 for failure) */ + return 1; + } + /* Failed. free return value */ + zval_ptr_dtor(value); + ZVAL_NULL(value); /* and replace the incomplete value with null just in case APCu uses it in the future */ + return 0; +} +/* }}} */ +#endif + +/* {{{ igbinary_serialize_data_init */ +/** + * Allocates data structures needed by igbinary_serialize_data, + * and sets properties of igsd to their defaults. + * @param igsd the struct to initialize + * @param scalar true if the data being serialized is a scalar + * @param memory_manager optional override of the memory manager + */ +inline static int igbinary_serialize_data_init(struct igbinary_serialize_data *igsd, bool scalar) { + void *buffer = emalloc(32); + if (UNEXPECTED(buffer == NULL)) { + return 1; + } + igsd->buffer_size = 0; + igsd->buffer_capacity = 32; + igsd->string_count = 0; + igsd->buffer = buffer; + igsd->scalar = scalar; + if (scalar) { + igsd->compact_strings = 0; + } else { + hash_si_init(&igsd->strings, 16); + hash_si_ptr_init(&igsd->references, 16); + igsd->references_id = 0; + igsd->compact_strings = (bool)IGBINARY_G(compact_strings); + igsd->deferred_dtor_tracker.zvals = NULL; + igsd->deferred_dtor_tracker.count = 0; + igsd->deferred_dtor_tracker.capacity = 0; + } + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_data_deinit */ +/** Deinits igbinary_serialize_data, freeing the allocated data structures. */ +inline static void igbinary_serialize_data_deinit(struct igbinary_serialize_data *igsd) { + bool scalar = igsd->scalar; + if (igsd->buffer) { + efree(igsd->buffer); + } + + if (!scalar) { + hash_si_deinit(&igsd->strings); + hash_si_ptr_deinit(&igsd->references); + free_deferred_dtors(&igsd->deferred_dtor_tracker); + } +} +/* }}} */ +/* {{{ igbinary_serialize_header */ +/** Serializes header ("\x00\x00\x00\x02"). */ +inline static void igbinary_serialize_header(struct igbinary_serialize_data *igsd) { + uint8_t* append_buffer = igsd->buffer; + ZEND_ASSERT(igsd->buffer_size == 0); + ZEND_ASSERT(igsd->buffer_capacity >= 4); + append_buffer[0] = 0; + append_buffer[1] = 0; + append_buffer[2] = 0; + append_buffer[3] = IGBINARY_FORMAT_VERSION; + igsd->buffer_size = 4; +} +/* }}} */ +static int igbinary_raise_capacity(struct igbinary_serialize_data *igsd, size_t size) { + do { + igsd->buffer_capacity *= 2; + } while (igsd->buffer_size + size >= igsd->buffer_capacity); + + uint8_t *const old_buffer = igsd->buffer; + igsd->buffer = erealloc(old_buffer, igsd->buffer_capacity); + if (UNEXPECTED(igsd->buffer == NULL)) { + /* We failed to allocate a larger buffer for the result. Free the memory used for the original buffer. */ + efree(old_buffer); + return 1; + } + + return 0; +} +/* {{{ igbinary_serialize_resize */ +/** Expands igbinary_serialize_data if necessary. */ +zend_always_inline static int igbinary_serialize_resize(struct igbinary_serialize_data *igsd, size_t size) { + if (igsd->buffer_size + size < igsd->buffer_capacity) { + return 0; + } + + return igbinary_raise_capacity(igsd, size); +} +/* }}} */ +/* {{{ igbinary_serialize8 */ +/** Serialize 8bit value. */ +zend_always_inline static int igbinary_serialize8(struct igbinary_serialize_data *igsd, uint8_t i) { + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 1)); + + igsd->buffer[igsd->buffer_size++] = i; + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize16 */ +/** Serialize 16bit value. */ +zend_always_inline static int igbinary_serialize16(struct igbinary_serialize_data *igsd, uint16_t i) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 2)); + + append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = (uint8_t)(i >> 8 & 0xff); + append_buffer[1] = (uint8_t)(i & 0xff); + igsd->buffer_size += 2; + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize32 */ +/** Serialize 32bit value. */ +zend_always_inline static int igbinary_serialize32(struct igbinary_serialize_data *igsd, uint32_t i) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 4)); + + append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = (uint8_t)(i >> 24 & 0xff); + append_buffer[1] = (uint8_t)(i >> 16 & 0xff); + append_buffer[2] = (uint8_t)(i >> 8 & 0xff); + append_buffer[3] = (uint8_t)(i & 0xff); + igsd->buffer_size += 4; + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize64 */ +/** Serialize 64bit value. */ +zend_always_inline static int igbinary_serialize64(struct igbinary_serialize_data *igsd, uint64_t i) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 8)); + + append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = (uint8_t)(i >> 56 & 0xff); + append_buffer[1] = (uint8_t)(i >> 48 & 0xff); + append_buffer[2] = (uint8_t)(i >> 40 & 0xff); + append_buffer[3] = (uint8_t)(i >> 32 & 0xff); + append_buffer[4] = (uint8_t)(i >> 24 & 0xff); + append_buffer[5] = (uint8_t)(i >> 16 & 0xff); + append_buffer[6] = (uint8_t)(i >> 8 & 0xff); + append_buffer[7] = (uint8_t)(i & 0xff); + igsd->buffer_size += 8; + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize8 */ +/** Serialize 8bit value + 8bit value. */ +zend_always_inline static int igbinary_serialize8_and_8(struct igbinary_serialize_data *igsd, uint8_t i, uint8_t v) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 2)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + + append_buffer[0] = i; + append_buffer[1] = v; + igsd->buffer_size += 2; + return 0; +} +/* }}} */ +/** Serialize 8bit value + 16bit value. */ +zend_always_inline static int igbinary_serialize8_and_16(struct igbinary_serialize_data *igsd, uint8_t i, uint16_t v) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 3)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + + append_buffer[0] = i; + append_buffer[1] = (uint8_t)(v >> 8 & 0xff); + append_buffer[2] = (uint8_t)(v & 0xff); +; + igsd->buffer_size += 3; + return 0; +} +/* }}} */ +/** Serialize 8bit value + 32bit value. */ +zend_always_inline static int igbinary_serialize8_and_32(struct igbinary_serialize_data *igsd, uint8_t i, uint32_t v) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 5)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + + append_buffer[0] = i; + append_buffer[1] = (uint8_t)(v >> 24 & 0xff); + append_buffer[2] = (uint8_t)(v >> 16 & 0xff); + append_buffer[3] = (uint8_t)(v >> 8 & 0xff); + append_buffer[4] = (uint8_t)(v & 0xff); +; + igsd->buffer_size += 5; + return 0; +} +/* }}} */ +/** Serialize 8bit value + 64bit value. */ +inline static int igbinary_serialize8_and_64(struct igbinary_serialize_data *igsd, uint8_t i, uint64_t v) { + uint8_t *append_buffer; + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, 9)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + + append_buffer[0] = i; + append_buffer[1] = (uint8_t)(v >> 56 & 0xff); + append_buffer[2] = (uint8_t)(v >> 48 & 0xff); + append_buffer[3] = (uint8_t)(v >> 40 & 0xff); + append_buffer[4] = (uint8_t)(v >> 32 & 0xff); + append_buffer[5] = (uint8_t)(v >> 24 & 0xff); + append_buffer[6] = (uint8_t)(v >> 16 & 0xff); + append_buffer[7] = (uint8_t)(v >> 8 & 0xff); + append_buffer[8] = (uint8_t)(v & 0xff); +; + igsd->buffer_size += 9; + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_null */ +/** Serializes null. */ +inline static int igbinary_serialize_null(struct igbinary_serialize_data *igsd) { + return igbinary_serialize8(igsd, igbinary_type_null); +} +/* }}} */ +/* {{{ igbinary_serialize_bool */ +/** Serializes bool. */ +inline static int igbinary_serialize_bool(struct igbinary_serialize_data *igsd, int b) { + return igbinary_serialize8(igsd, (uint8_t)(b ? igbinary_type_bool_true : igbinary_type_bool_false)); +} +/* }}} */ +/* {{{ igbinary_serialize_long */ +/** Serializes zend_long. */ +inline static int igbinary_serialize_long(struct igbinary_serialize_data *igsd, zend_long l) { + const bool p = l >= 0; + // k is the absolute value of l + const zend_ulong k = p ? (zend_ulong)l : -(zend_ulong)l; + + if (k <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, + p ? igbinary_type_long8p : igbinary_type_long8n, + (uint8_t)k + )); + } else if (k <= 0xffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, + p ? igbinary_type_long16p : igbinary_type_long16n, + (uint16_t)k + )); +#if SIZEOF_ZEND_LONG == 8 + } else if (k <= 0xffffffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, + p ? igbinary_type_long32p : igbinary_type_long32n, + (uint32_t)k + )); + } else { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_64(igsd, + p ? igbinary_type_long64p : igbinary_type_long64n, + (uint64_t)k + )); + } +#elif SIZEOF_ZEND_LONG == 4 + } else { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, + p ? igbinary_type_long32p : igbinary_type_long32n, + (uint32_t)k + )); + } +#else +#error "Strange sizeof(zend_long)." +#endif + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_double */ +/** Serializes double. */ +inline static int igbinary_serialize_double(struct igbinary_serialize_data *igsd, double d) { + union { + double d; + uint64_t u; + } u; + + u.d = d; + + return igbinary_serialize8_and_64(igsd, igbinary_type_double, u.u); +} +/* }}} */ +/* {{{ igbinary_serialize_string */ +/** + * Serializes string. + * + * When compact_strings is true, + * this will serialize the string as igbinary_type_string* (A length followed by a character array) the first time, + * and serialize subsequent references to the same string as igbinary_type_string_id*. + * + * When compact_strings is false, this will always serialize the string as a character array. + * compact_strings speeds up serialization, but slows down serialization and uses more space to represent the serialization. + * + * Serializes each string once. + * After first time uses pointers (igbinary_type_string_id*) instead of igbinary_type_string*. + */ +inline static int igbinary_serialize_string(struct igbinary_serialize_data *igsd, zend_string *s) { + const size_t len = ZSTR_LEN(s); + if (len == 0) { + /* The empty string is always serialized as igbinary_serialize_string (1 byte instead of 2) */ + return igbinary_serialize8(igsd, igbinary_type_string_empty); + } + + if (!igsd->scalar && igsd->compact_strings) { + struct hash_si_result result = hash_si_find_or_insert(&igsd->strings, s, igsd->string_count); + if (result.code == hash_si_code_exists) { + uint32_t value = result.value; + if (value <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, (uint8_t)igbinary_type_string_id8, (uint8_t)value)); + } else if (value <= 0xffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, (uint8_t)igbinary_type_string_id16, (uint16_t)value)); + } else { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, (uint8_t)igbinary_type_string_id32, value)); + } + return 0; + } else if (EXPECTED(result.code == hash_si_code_inserted)) { + /* Fall through to igbinary_serialize_chararray */ + } else { + return 1; /* Failed to allocate copy of string */ + } + } + + igsd->string_count++; /* A new string is being serialized - update count so that duplicate class names can be used. */ + if (UNEXPECTED(igsd->string_count == 0)) { + zend_error(E_WARNING, "igbinary_serialize: Saw too many strings"); + return 1; + } + return igbinary_serialize_chararray(igsd, ZSTR_VAL(s), len); +} +/* }}} */ + +#if SIZEOF_SIZE_T > 4 +static ZEND_COLD zend_never_inline int igbinary_serialize_extremely_long_chararray(struct igbinary_serialize_data *igsd, const char *s, size_t len) { + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, len + 9)); + uint8_t *append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = igbinary_type_string64; + append_buffer[1] = (uint8_t)(len >> 56 & 0xff); + append_buffer[2] = (uint8_t)(len >> 48 & 0xff); + append_buffer[3] = (uint8_t)(len >> 40 & 0xff); + append_buffer[4] = (uint8_t)(len >> 32 & 0xff); + append_buffer[5] = (uint8_t)(len >> 24 & 0xff); + append_buffer[6] = (uint8_t)(len >> 16 & 0xff); + append_buffer[7] = (uint8_t)(len >> 8 & 0xff); + append_buffer[8] = (uint8_t)(len & 0xff); + + memcpy(append_buffer + 9, s, len); + igsd->buffer_size += 9 + len; + + return 0; +} +#endif + +/* {{{ igbinary_serialize_chararray */ +/** Serializes string data as the type followed by the length followed by the raw character array. */ +inline static int igbinary_serialize_chararray(struct igbinary_serialize_data *igsd, const char *s, size_t len) { + uint8_t *append_buffer; + int offset; + if (len <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, len + 2)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = igbinary_type_string8; + append_buffer[1] = len; + offset = 2; + } else if (len <= 0xffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, len + 3)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = igbinary_type_string16; + append_buffer[1] = (uint8_t)(len >> 8 & 0xff); + append_buffer[2] = (uint8_t)(len & 0xff); + offset = 3; + } else { +#if SIZEOF_SIZE_T > 4 + if (UNEXPECTED(len > 0xffffffff)) { + return igbinary_serialize_extremely_long_chararray(igsd, s, len); + } +#endif + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, len + 5)); + append_buffer = &igsd->buffer[igsd->buffer_size]; + append_buffer[0] = igbinary_type_string32; + append_buffer[1] = (uint8_t)(len >> 24 & 0xff); + append_buffer[2] = (uint8_t)(len >> 16 & 0xff); + append_buffer[3] = (uint8_t)(len >> 8 & 0xff); + append_buffer[4] = (uint8_t)(len & 0xff); + offset = 5; + } + + memcpy(append_buffer + offset, s, len); + igsd->buffer_size += offset + len; + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_array */ +/** + * Serializes an array's or object's inner properties. + * If properties or keys are unexpectedly added (e.g. by __sleep() or serialize() elsewhere), this will skip serializing them. + * If properties or keys are unexpectedly removed, this will add igbinary_type_null as padding for the corresponding entries. + */ +inline static int igbinary_serialize_array(struct igbinary_serialize_data *igsd, zval *z, bool object, bool incomplete_class, bool serialize_props) { + /* If object=true: z is IS_OBJECT */ + /* If object=false: z is either IS_ARRAY, or IS_REFERENCE pointing to an IS_ARRAY. */ + HashTable *h; + /* At the time of writing, struct _zend_array had uint32_t nNumOfElements */ + uint32_t n; + zval *d; + zval *z_original; + + zend_string *key; + zend_long key_index; + + z_original = z; + ZVAL_DEREF(z); + + ZEND_ASSERT((!object || !serialize_props) && (object || !incomplete_class)); + + /* hash */ + if (object) { + h = zend_get_properties_for(z, ZEND_PROP_PURPOSE_SERIALIZE); + n = h ? zend_hash_num_elements(h) : 0; + /* incomplete class magic member */ + + if (incomplete_class && n > 0) { + --n; + } + } else { + h = Z_ARRVAL_P(z); + n = zend_hash_num_elements(h); + /* if it is an array or a reference to an array, then add a reference unique to that **reference** to that array */ + if (serialize_props) { + int ref_ser = igbinary_serialize_array_ref(igsd, z_original, false); + if (ref_ser != 2) { + /* 1 means out of memory or error, 0 means this already exists, 2 means this is new. */ + return ref_ser; + } + } + } + + if (n <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, igbinary_type_array8, (uint8_t)n)); + + if (n == 0) { + if (object) { + zend_release_properties(h); + } + return 0; + } + } else if (n <= 0xffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, igbinary_type_array16, (uint16_t)n)); + } else { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, igbinary_type_array32, n)); + } + + /* serialize properties. */ + ZEND_HASH_FOREACH_KEY_VAL(h, key_index, key, d) { + /* skip magic member in incomplete classes */ + /* Match it exactly and permit null bytes in the middle so that the counts will match. */ + if (incomplete_class && zend_string_equals_literal(key, MAGIC_MEMBER)) { + continue; + } + + /* https://wiki.php.net/phpng-int - This is a declared property of an object, or an element of $GLOBALS */ + if (Z_TYPE_P(d) == IS_INDIRECT) { + d = Z_INDIRECT_P(d); + } + if (Z_TYPE_P(d) == IS_UNDEF) { + /* This is an undefined declared typed property of an object. */ + /* This can't be a value or a reference in an array - except maybe for $GLOBALS, which has other issues. */ + ZEND_ASSERT(!serialize_props); + RETURN_1_IF_NON_ZERO(igbinary_serialize_null(igsd)); + continue; + } + + if (key == NULL) { + /* Key is numeric */ + RETURN_1_IF_NON_ZERO(igbinary_serialize_long(igsd, key_index)); + } else { + /* Key is string */ + RETURN_1_IF_NON_ZERO(igbinary_serialize_string(igsd, key)); + } + + /* we should still add element even if it's not OK, + * since we already wrote the length of the array before */ + RETURN_1_IF_NON_ZERO(igbinary_serialize_zval(igsd, d)); + } ZEND_HASH_FOREACH_END(); + if (object) { + zend_release_properties(h); + } + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_array_ref */ +/** Serializes array reference (or reference in an object). Returns 0 on success. */ +/* TODO: Use different result codes for missing keys and errors */ +zend_always_inline static int igbinary_serialize_array_ref(struct igbinary_serialize_data *igsd, zval * const z, bool object) { + size_t t; + uintptr_t key; /* The address of the pointer to the zend_refcounted struct or other struct */ + static int INVALID_KEY; /* Not used, but we take the pointer of this knowing other zvals won't share it*/ + + /* Similar to php_var_serialize_intern's first part, as well as php_add_var_hash, for printing R: (reference) or r:(object) */ + /* However, it differs from the built in serialize() in that references to objects are preserved when serializing and unserializing? (TODO check, test for backwards compatibility) */ + + ZEND_ASSERT(Z_ISREF_P(z) || (object && Z_TYPE_P(z) == IS_OBJECT) || Z_TYPE_P(z) == IS_ARRAY); + // zend_bool is_ref = Z_ISREF_P(z); + /* Do I have to dereference object references so that reference ids will be the same as in php5? */ + /* If I do, then more tests fail. */ + /* is_ref || IS_OBJECT implies it has a unique refcounted struct */ + // NOTE: The original code would always use the same memory address - Z_COUNTED_P is the start of an object/array/reference +// if (object && Z_TYPE_P(z) == IS_OBJECT) { +// key = (uintptr_t)Z_OBJ_P(z); /* expand object handle(uint32_t), cast to 32-bit/64-bit pointer */ +// } else if (is_ref) { +// /* NOTE: PHP switched from `zval*` to `zval` for the values stored in HashTables. +// * If an array has two references to the same ZVAL, then those references will have different zvals. +// * We use Z_COUNTED_P(ref), which will be the same if (and only if) the references are the same. */ +// /* is_ref implies there is a unique reference counting pointer for the reference */ +// key = (uintptr_t)Z_COUNTED_P(z); +// } else if (EXPECTED(Z_TYPE_P(z) == IS_ARRAY)) { +// key = (uintptr_t)Z_ARR_P(z); +// } else { +// /* Nothing else is going to reference this when this is serialized, this isn't ref counted or an object, shouldn't be reached. */ +// /* Increment the reference id for the deserializer, give up. */ +// ++igsd->references_id; +// php_error_docref(NULL, E_NOTICE, "igbinary_serialize_array_ref expected either object or reference (param object=%s), got neither (zend_type=%d)", object ? "true" : "false", (int)Z_TYPE_P(z)); +// return 1; +// } + + /* FIXME hack? If the top-level element was an array, we assume that it can't be a reference when we serialize it, */ + /* because that's the way it was serialized in php5. */ + /* Does this work with different forms of recursive arrays? */ + if (igsd->references_id == 0 && !object) { + key = (uintptr_t)&INVALID_KEY; + } else { + key = (uintptr_t)z->value.ptr; + } + + t = hash_si_ptr_find_or_insert(&igsd->references, key, igsd->references_id); + if (t == SIZE_MAX) { + /* This is a brand new object/array/reference entry with a key equal to igsd->references_id */ + igsd->references_id++; + if (UNEXPECTED(igsd->references_id == 0)) { + zend_error(E_WARNING, "igbinary_serialize: Saw too many references"); + return 1; + } + /* We only need to call this if the array/object is new, in case __serialize or other methods return temporary arrays or modify arrays that were serialized earlier */ + igsd_addref_and_defer_dtor(&igsd->deferred_dtor_tracker, z); + return 2; + } + + /* TODO: Properly handle running out of memory in this helper function. */ + /* It returns 1 both for running out of memory and inserting a new entry. */ + enum igbinary_type type; + if (t <= 0xff) { + type = object ? igbinary_type_objref8 : igbinary_type_ref8; + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, (uint8_t)type, (uint8_t)t)); + } else if (t <= 0xffff) { + type = object ? igbinary_type_objref16 : igbinary_type_ref16; + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, (uint8_t)type, (uint16_t)t)) + } else { + type = object ? igbinary_type_objref32 : igbinary_type_ref32; + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, (uint8_t)type, (uint32_t)t)) + } + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_array_sleep_single_prop_value */ +/** Serializes one value of an object's properties array, for use with the __sleep function. */ +inline static int igbinary_serialize_array_sleep_single_prop_value(struct igbinary_serialize_data *igsd, zval *z, zval *v, zend_class_entry *ce, zend_string *prop_name) { + /* Precondition: All args are non-null */ + if (Z_TYPE_P(v) == IS_INDIRECT) { + v = Z_INDIRECT_P(v); + if (UNEXPECTED(Z_TYPE_P(v) == IS_UNDEF)) { +#if PHP_VERSION_ID >= 70400 + if (UNEXPECTED(zend_get_typed_property_info_for_slot(Z_OBJ_P(z), v) != NULL)) { + zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization (in __sleep)", ZSTR_VAL(ce->name), ZSTR_VAL(prop_name)); + return 1; + } +#endif + goto serialize_untyped_uninitialized_prop; + } + } else { + if (UNEXPECTED(Z_TYPE_P(v) == IS_UNDEF)) { +serialize_untyped_uninitialized_prop: + php_error_docref(NULL, E_NOTICE, "\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(prop_name)); + return igbinary_serialize_null(igsd); + } + } + return igbinary_serialize_zval(igsd, v); +} +/* }}} */ +/* {{{ igbinary_serialize_array_sleep_inner */ +/** Serializes object's properties array with __sleep -function. */ +inline static int igbinary_serialize_array_sleep_inner(struct igbinary_serialize_data *igsd, zval *z, HashTable *h, HashTable *object_properties, zend_class_entry *ce) { + zval *d; + zval *v; + + ZEND_HASH_FOREACH_VAL(h, d) { + if (UNEXPECTED(d == NULL || Z_TYPE_P(d) != IS_STRING)) { + php_error_docref(NULL, E_NOTICE, "__sleep should return an array only " + "containing the names of instance-variables to " + "serialize"); + + /* we should still add element even if it's not OK, + * since we already wrote the length of the array before + * serialize null as key-value pair */ + /* TODO: Allow creating a tmp string, like php's serialize() */ + RETURN_1_IF_NON_ZERO(igbinary_serialize_null(igsd)); + continue; + } + zend_string *prop_name = Z_STR_P(d); + + if ((v = zend_hash_find(object_properties, prop_name)) != NULL) { + RETURN_1_IF_NON_ZERO(igbinary_serialize_string(igsd, prop_name)); + + RETURN_1_IF_NON_ZERO(igbinary_serialize_array_sleep_single_prop_value(igsd, z, v, ce, prop_name)); + } else { + zend_string *mangled_prop_name; + + v = NULL; + + int res; + /* try private */ + mangled_prop_name = zend_mangle_property_name(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), + ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), 0); + v = zend_hash_find(object_properties, mangled_prop_name); + + /* try protected */ + if (v == NULL) { + zend_string_efree(mangled_prop_name); + mangled_prop_name = zend_mangle_property_name("*", 1, + ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), 0); + + v = zend_hash_find(object_properties, mangled_prop_name); + /* Neither protected nor private property exists */ + if (v == NULL) { + zend_string_efree(mangled_prop_name); + + php_error_docref(NULL, E_NOTICE, "\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(prop_name)); + RETURN_1_IF_NON_ZERO(igbinary_serialize_string(igsd, prop_name)); + + RETURN_1_IF_NON_ZERO(igbinary_serialize_null(igsd)); + + continue; + } + } + + res = igbinary_serialize_string(igsd, mangled_prop_name); + /* igbinary_serialize_string will increase the reference count. */ + zend_string_release_ex(mangled_prop_name, 0); + + RETURN_1_IF_NON_ZERO(res); + RETURN_1_IF_NON_ZERO(igbinary_serialize_array_sleep_single_prop_value(igsd, z, v, ce, prop_name)); + } + } ZEND_HASH_FOREACH_END(); + + return 0; +} +/* }}} */ +/* {{{ igbinary_serialize_array_sleep */ +/** Serializes object's properties array with __sleep -function. */ +inline static int igbinary_serialize_array_sleep(struct igbinary_serialize_data *igsd, zval *z, HashTable *h, zend_class_entry *ce) { + HashTable *object_properties; + uint32_t n = zend_hash_num_elements(h); + + /* Serialize array id. */ + if (n <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, igbinary_type_array8, (uint8_t)n)) + if (n == 0) { + return 0; + } + } else if (n <= 0xffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, igbinary_type_array16, (uint16_t)n)) + } else { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, igbinary_type_array32, n)) + } + + object_properties = zend_get_properties_for(z, ZEND_PROP_PURPOSE_SERIALIZE); + + int r = igbinary_serialize_array_sleep_inner(igsd, z, h, object_properties, ce); + zend_release_properties(object_properties); + return r; +} +/* }}} */ +/* {{{ igbinary_serialize_object_name */ +/** + * Serialize a PHP object's class name. + * Note that this deliberately ignores the compact_strings setting. + */ +inline static int igbinary_serialize_object_name(struct igbinary_serialize_data *igsd, zend_string *class_name) { + struct hash_si_result result = hash_si_find_or_insert(&igsd->strings, class_name, igsd->string_count); + if (result.code == hash_si_code_inserted) { + const size_t name_len = ZSTR_LEN(class_name); + igsd->string_count += 1; + if (UNEXPECTED(igsd->string_count == 0)) { + zend_error(E_WARNING, "igbinary_serialize: Saw too many strings"); + return 1; + } + + if (name_len <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, igbinary_type_object8, (uint8_t)name_len)) + } else if (EXPECTED(name_len <= 0xffff)) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, igbinary_type_object16, (uint16_t)name_len)) + } else { +#if SIZEOF_SIZE_T > 4 + if (UNEXPECTED(name_len > 0xffffffff)) { + zend_error(E_WARNING, "igbinary_serialize_object_name: class name does not fit in 32 bits"); + return 1; + } +#endif + + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, igbinary_type_object32, (uint32_t)name_len)) + } + + RETURN_1_IF_NON_ZERO(igbinary_serialize_resize(igsd, name_len)); + + memcpy(igsd->buffer + igsd->buffer_size, ZSTR_VAL(class_name), name_len); + igsd->buffer_size += name_len; + } else if (EXPECTED(result.code == hash_si_code_exists)) { + /* already serialized string */ + uint32_t value = result.value; + if (value <= 0xff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_8(igsd, (uint8_t)igbinary_type_object_id8, (uint8_t)value)) + } else if (value <= 0xffff) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_16(igsd, (uint8_t)igbinary_type_object_id16, (uint16_t)value)) + } else { + RETURN_1_IF_NON_ZERO(igbinary_serialize8_and_32(igsd, (uint8_t)igbinary_type_object_id32, (uint32_t)value)) + } + } else { + return 1; /* Failed to allocate copy of string */ + } + + return 0; +} +/* }}} */ +/* igbinary_serialize_object_old_serializer_class {{{ */ +static ZEND_COLD int igbinary_serialize_object_old_serializer_class(struct igbinary_serialize_data* igsd, zval *z, zend_class_entry *ce) { + unsigned char *serialized_data = NULL; + size_t serialized_len; + int r = 0; + + if (ce->serialize(z, &serialized_data, &serialized_len, (zend_serialize_data *)NULL) == SUCCESS && !EG(exception)) { + if (UNEXPECTED(igbinary_serialize_object_name(igsd, ce->name) != 0)) { + goto failure; + } + + if (serialized_len <= 0xff) { + if (UNEXPECTED(igbinary_serialize8_and_8(igsd, igbinary_type_object_ser8, (uint8_t)serialized_len) != 0)) { + goto failure; + } + } else if (serialized_len <= 0xffff) { + if (UNEXPECTED(igbinary_serialize8_and_16(igsd, (uint8_t)igbinary_type_object_ser16, (uint16_t)serialized_len) != 0)) { + goto failure; + } + } else { +#if SIZEOF_ZEND_LONG > 4 + if (UNEXPECTED(serialized_len > 0xffffffff)) { + zend_error(E_WARNING, "igbinary_serialize_object_old_serializer_class: serialize() data 4GB or larger is not supported"); + goto failure; + } +#endif + if (UNEXPECTED(igbinary_serialize8_and_32(igsd, (uint8_t)igbinary_type_object_ser32, (uint32_t)serialized_len) != 0)) { + goto failure; + } + } + + if (UNEXPECTED(igbinary_serialize_resize(igsd, serialized_len))) { + goto failure; + } + + memcpy(igsd->buffer + igsd->buffer_size, serialized_data, serialized_len); + igsd->buffer_size += serialized_len; + } else if (EG(exception)) { + /* exception, return failure */ +failure: + r = 1; + } else { + /* Serialization callback failed, assume null output */ + r = igbinary_serialize_null(igsd); + } + + if (serialized_data) { + efree(serialized_data); + } + + return r; +} +/* }}} */ +/* igbinary_var_serialize_call_magic_serialize {{{ */ +// Source: ext/standard/var.c from php-src +#if PHP_VERSION_ID >= 70400 +inline static int igbinary_var_serialize_call_magic_serialize(zval *retval, zval *obj) +{ +#if PHP_VERSION_ID >= 80000 + BG(serialize_lock)++; + zend_call_known_instance_method_with_0_params( + Z_OBJCE_P(obj)->__serialize, Z_OBJ_P(obj), retval); + BG(serialize_lock)--; + + if (EG(exception)) { + zval_ptr_dtor(retval); + return 1; + } +#else + zval fname; + int res; + + ZVAL_STRINGL(&fname, "__serialize", sizeof("__serialize") - 1); + // XXX does this work with the standard serializer? + BG(serialize_lock)++; + res = call_user_function(CG(function_table), obj, &fname, retval, 0, 0); + BG(serialize_lock)--; + zval_ptr_dtor_str(&fname); + + if (res == FAILURE || Z_ISUNDEF_P(retval)) { + zval_ptr_dtor(retval); + return 1; + } +#endif + + if (Z_TYPE_P(retval) != IS_ARRAY) { + zval_ptr_dtor(retval); + zend_type_error("%s::__serialize() must return an array", ZSTR_VAL(Z_OBJCE_P(obj)->name)); + return 1; + } + + return 0; +} +#endif +/* }}} */ +/* igbinary_serialize_object_new_serializer {{{ */ +#if PHP_VERSION_ID >= 70400 +inline static int igbinary_serialize_object_new_serializer(struct igbinary_serialize_data* igsd, zval *z, zend_class_entry *ce) { + zval retval; + int r = 0; + + // Call retval = __serialize(z), which returns an array or fails. + if (igbinary_var_serialize_call_magic_serialize(&retval, z)) { + // retval is already freed + if (!EG(exception)) { + // When does this happen? + igbinary_serialize_null(igsd); + return 0; + } + return 1; + } + // fprintf(stderr, "Serialize returned a value of type %d\n", Z_TYPE(retval)); + if (UNEXPECTED(igbinary_serialize_object_name(igsd, ce->name) != 0)) { + zval_ptr_dtor(&retval); + return 1; + } + // This serializes an object name followed by an array, in the same format used when no serializers exist. + // object is false because this is the array returned by __serialize() for the object. + // TODO: Add tests that this works properly when the same array is reused. + r = igbinary_serialize_array(igsd, &retval, false, false, false); + zval_ptr_dtor(&retval); + if (UNEXPECTED(r)) { + return 1; + } + return EG(exception) != NULL; +} +#endif +/* }}} */ +/* {{{ igbinary_serialize_object_enum_case */ +#if PHP_VERSION_ID >= 80100 +inline static int igbinary_serialize_object_enum_case(struct igbinary_serialize_data *igsd, zend_object *obj, zend_class_entry *ce) { + if (UNEXPECTED(igbinary_serialize_object_name(igsd, ce->name) != 0)) { + return 1; + } + if (UNEXPECTED(igbinary_serialize8(igsd, (uint8_t)igbinary_type_enum_case))) { + return 1; + } + zval *case_name = zend_enum_fetch_case_name(obj); + ZEND_ASSERT(Z_TYPE_P(case_name) == IS_STRING); + return igbinary_serialize_string(igsd, Z_STR_P(case_name)); +} +#endif +/* }}} */ +/* {{{ igbinary_serialize_object */ +/** Serialize object. + * @see ext/standard/var.c + * */ +inline static int igbinary_serialize_object(struct igbinary_serialize_data *igsd, zval *z) { + PHP_CLASS_ATTRIBUTES; + + zend_class_entry *ce; + + int r = 0; + + int ref_ser = igbinary_serialize_array_ref(igsd, z, true); + if (ref_ser != 2) { + return ref_ser; + } + + ce = Z_OBJCE_P(z); + + if (IGBINARY_IS_NOT_SERIALIZABLE(ce)) { + zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed", ZSTR_VAL(ce->name)); + return 1; + } + +#if PHP_VERSION_ID >= 80100 + if (ce->ce_flags & ZEND_ACC_ENUM) { + return igbinary_serialize_object_enum_case(igsd, Z_OBJ_P(z), ce); + } +#endif + +#if PHP_VERSION_ID >= 70400 +#if PHP_VERSION_ID >= 80000 + if (ce->__serialize) +#else + if (zend_hash_str_exists(&ce->function_table, "__serialize", sizeof("__serialize")-1)) +#endif + { + // fprintf(stderr, "Going to serialize %s\n", ZSTR_VAL(ce->name)); + return igbinary_serialize_object_new_serializer(igsd, z, ce); + } +#endif + if (ce->serialize != NULL) { + return igbinary_serialize_object_old_serializer_class(igsd, z, ce); + } + + /* serialize class name */ + PHP_SET_CLASS_ATTRIBUTES(z); + if (igbinary_serialize_object_name(igsd, class_name) != 0) { + PHP_CLEANUP_CLASS_ATTRIBUTES(); + return 1; + } + PHP_CLEANUP_CLASS_ATTRIBUTES(); + + if (ce && ce != PHP_IC_ENTRY && zend_hash_str_exists(&ce->function_table, "__sleep", sizeof("__sleep") - 1)) { + zval h; + zval f; + /* function name string */ + /* TODO use ZSTR_KNOWN(ZEND_STR_SLEEP) instead when available*/ + /* zval *zv = zend_hash_find_known_hash(&ce->function_table, ZSTR_KNOWN(ZEND_STR_SLEEP)); */ + ZVAL_STRINGL(&f, "__sleep", sizeof("__sleep") - 1); + + /* calling z->__sleep */ + r = call_user_function(CG(function_table), z, &f, &h, 0, 0); + + zval_ptr_dtor_str(&f); + + if (r == SUCCESS && !EG(exception)) { + HashTable *ht; + r = 0; + + if (Z_TYPE(h) == IS_UNDEF) { + /* FIXME: is this ok? */ + /* Valid, but skip */ + } else if ((ht = HASH_OF(&h)) != NULL) { + /* NOTE: PHP permits returning an object in __sleep */ + r = igbinary_serialize_array_sleep(igsd, z, ht, ce); + } else { + php_error_docref(NULL, E_NOTICE, "__sleep should return an array only " + "containing the names of instance-variables to " + "serialize"); + + /* empty array */ + r = igbinary_serialize8_and_8(igsd, igbinary_type_array8, 0); + } + } else { + r = 1; + } + + /* cleanup */ + zval_ptr_dtor(&h); + + return r; + } + return igbinary_serialize_array(igsd, z, true, incomplete_class, false); +} +/* }}} */ +/* {{{ igbinary_warn_serialize_resource */ +static ZEND_COLD int igbinary_warn_serialize_resource(zval *z) { + const char *resource_type; + resource_type = zend_rsrc_list_get_rsrc_type(Z_RES_P(z)); + if (!resource_type) { + resource_type = "Unknown"; + } + php_error_docref(NULL, E_DEPRECATED, "Cannot serialize resource(%s) and resources may be converted to objects that cannot be serialized in future php releases. Serializing the value as null instead", resource_type); + return EG(exception) != NULL; +} +/* }}} */ +/* {{{ igbinary_serialize_check_stack_limit */ +/** Mirrors php_serialize_check_stack_limit() in ext/standard/var.c: turns deep + * recursion into a catchable Error instead of a C stack-overflow crash. No-op + * on builds without the Zend stack-limit feature (PHP < 8.3). The error is + * thrown directly (as PHP 8.3 core does) since zend_call_stack_size_error() + * has no public declaration before 8.4. */ +static zend_always_inline bool igbinary_serialize_check_stack_limit(void) { +#ifdef ZEND_CHECK_STACK_LIMIT + if (UNEXPECTED(zend_call_stack_overflowed(EG(stack_limit)))) { + zend_throw_error(NULL, "Maximum call stack size reached. Infinite recursion?"); + return true; + } +#endif + return false; +} +/* }}} */ +/* {{{ igbinary_serialize_zval */ +/** Serialize zval. */ +static int igbinary_serialize_zval(struct igbinary_serialize_data *igsd, zval *z) { + if (UNEXPECTED(igbinary_serialize_check_stack_limit())) { + return 1; + } + if (Z_ISREF_P(z)) { + if (Z_REFCOUNT_P(z) >= 2) { + RETURN_1_IF_NON_ZERO(igbinary_serialize8(igsd, (uint8_t)igbinary_type_ref)) + + switch (Z_TYPE_P(Z_REFVAL_P(z))) { + case IS_ARRAY: + return igbinary_serialize_array(igsd, z, false, false, true); + case IS_OBJECT: + break; /* Fall through */ + default: + { + /* Serialize a reference if zval already added */ + int ref_ser = igbinary_serialize_array_ref(igsd, z, false); + if (ref_ser != 2) { + return ref_ser; + } + /* Fall through */ + } + } + } + + z = Z_REFVAL_P(z); + } + switch (Z_TYPE_P(z)) { + case IS_RESOURCE: + if (igbinary_warn_serialize_resource(z)) { + return 1; + } + return igbinary_serialize_null(igsd); + case IS_OBJECT: + return igbinary_serialize_object(igsd, z); + case IS_ARRAY: + /* if is_ref, then php5 would have called igbinary_serialize_array_ref */ + return igbinary_serialize_array(igsd, z, false, false, true); + case IS_STRING: + return igbinary_serialize_string(igsd, Z_STR_P(z)); + case IS_LONG: + return igbinary_serialize_long(igsd, Z_LVAL_P(z)); + case IS_UNDEF: + // https://github.com/igbinary/igbinary/issues/134 + // TODO: In a new major version, could have a separate type for IS_UNDEF, which would unset the property in an object context? + // fallthrough + case IS_NULL: + return igbinary_serialize_null(igsd); + case IS_TRUE: + return igbinary_serialize_bool(igsd, 1); + case IS_FALSE: + return igbinary_serialize_bool(igsd, 0); + case IS_DOUBLE: + return igbinary_serialize_double(igsd, Z_DVAL_P(z)); + default: + zend_error(E_ERROR, "igbinary_serialize_zval: zval has unknown type %d", (int)Z_TYPE_P(z)); + return 1; + } + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_data_init */ +/** Inits igbinary_unserialize_data. */ +inline static int igbinary_unserialize_data_init(struct igbinary_unserialize_data *igsd) { + struct igbinary_value_ref *references; + zend_string **strings; + /* Initialize the recursion guard fields before any path that can fail, */ + /* so callers that ignore the return value still see defined values. */ + igsd->cur_depth = 0; +#if PHP_VERSION_ID >= 70400 + igsd->max_depth = zend_ini_long((char *)"unserialize_max_depth", sizeof("unserialize_max_depth") - 1, 0); +#else + /* unserialize_max_depth ini was added in PHP 7.4. */ + /* Use the same default cap to keep the recursion guard active on older builds. */ + igsd->max_depth = 4096; +#endif + references = emalloc(sizeof(igsd->references[0]) * 4); + if (UNEXPECTED(references == NULL)) { + return 1; + } + strings = (zend_string **)emalloc(sizeof(zend_string *) * 4); + if (UNEXPECTED(strings == NULL)) { + /* We failed to allocate memory for strings. Fail and free everything we allocated */ + efree(references); + return 1; + } + igsd->buffer = NULL; + igsd->buffer_end = NULL; + igsd->buffer_ptr = NULL; + + igsd->strings = NULL; + igsd->strings_count = 0; + igsd->strings_capacity = 4; + + igsd->references = references; + igsd->references_count = 0; + igsd->references_capacity = 4; + + igsd->strings = strings; + + /** Don't bother allocating zvals which __wakeup or __unserialize, probably not common */ + igsd->deferred = NULL; + igsd->deferred_count = 0; + igsd->deferred_capacity = 0; + igsd->deferred_finished = 0; + + igsd->deferred_dtor_tracker.zvals = NULL; + igsd->deferred_dtor_tracker.count = 0; + igsd->deferred_dtor_tracker.capacity = 0; +#if PHP_VERSION_ID >= 70400 + igsd->ref_props = NULL; +#endif + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_data_deinit */ +/** Deinits igbinary_unserialize_data. */ +inline static void igbinary_unserialize_data_deinit(struct igbinary_unserialize_data *igsd) { + if (igsd->strings) { + size_t i; + size_t strings_count = igsd->strings_count; + zend_string **strings = igsd->strings; + for (i = 0; i < strings_count; i++) { + zend_string *s = strings[i]; +#if ZEND_DEBUG + ZEND_ASSERT(GC_REFCOUNT(s) >= 1); +#endif + zend_string_release_ex(s, 0); /* Should only create interned or non-persistent strings when unserializing */ + } + + efree(strings); + } + + if (igsd->references) { + efree(igsd->references); + } + if (igsd->deferred) { + struct deferred_call *calls = igsd->deferred; +#if PHP_VERSION_ID >= 70400 + uint32_t i; + uint32_t n = igsd->deferred_count; + for (i = 0; i < n; i++) { + struct deferred_call *call = &calls[i]; + if (call->is_unserialize) { + if (!igsd->deferred_finished) { + struct deferred_unserialize_call *unserialize_call = &call->data.unserialize; + GC_ADD_FLAGS(unserialize_call->object, IS_OBJ_DESTRUCTOR_CALLED); + zval_ptr_dtor(&unserialize_call->param); + } + } + } +#endif + efree(calls); + } + free_deferred_dtors(&igsd->deferred_dtor_tracker); +#if PHP_VERSION_ID >= 70400 + if (igsd->ref_props) { + zend_hash_destroy(igsd->ref_props); + FREE_HASHTABLE(igsd->ref_props); + } +#endif + + return; +} +/* }}} */ +/* {{{ igbinary_unserialize_header_emit_warning */ +/** + * Warns about invalid byte headers + * Precondition: igsd->buffer_size >= 4 */ +static ZEND_COLD void igbinary_unserialize_header_emit_warning(struct igbinary_unserialize_data *igsd, int version) { + int i; + char buf[9], *it; + for (i = 0; i < 4; i++) { + if (!isprint((int)igsd->buffer[i])) { + if (version != 0 && (((unsigned int)version) & 0xff000000) == (unsigned int)version) { + // Check if high order byte was set instead of low order byte + zend_error(E_WARNING, "igbinary_unserialize_header: unsupported version: %u, should be %u or %u (wrong endianness?)", (unsigned int)version, 0x00000001, (unsigned int)IGBINARY_FORMAT_VERSION); + return; + } + // Binary data, or a version number from a future release. + zend_error(E_WARNING, "igbinary_unserialize_header: unsupported version: %u, should be %u or %u", (unsigned int)version, 0x00000001, (unsigned int)IGBINARY_FORMAT_VERSION); + return; + } + } + + /* To avoid confusion, if the first 4 bytes are all printable, print those instead of the integer representation to make debugging easier. */ + /* E.g. strings such as "a:2:" are emitted when an Array is serialized with serialize() instead of igbinary_serialize(), */ + /* and subsequently passed to igbinary_unserialize instead of unserialize(). */ + for (it = buf, i = 0; i < 4; i++) { + char c = igsd->buffer[i]; + if (c == '"' || c == '\\') { + *it++ = '\\'; + } + *it++ = c; + } + *it = '\0'; + zend_error(E_WARNING, "igbinary_unserialize_header: unsupported version: \"%s\"..., should begin with a binary version header of \"\\x00\\x00\\x00\\x01\" or \"\\x00\\x00\\x00\\x%02x\"", buf, (int)IGBINARY_FORMAT_VERSION); +} +/* }}} */ +/* {{{ igbinary_unserialize_header */ +/** Unserialize header. Check for version. */ +inline static int igbinary_unserialize_header(struct igbinary_unserialize_data *igsd) { + uint32_t version; + + if (IGB_NEEDS_MORE_DATA(igsd, 5)) { + zend_error(E_WARNING, "igbinary_unserialize_header: expected at least 5 bytes of data, got %u byte(s)", IGB_REMAINING_BYTES(igsd)); + return 1; + } + + version = igbinary_unserialize32(igsd); + + /* Support older version 1 and the current format 2 */ + if (EXPECTED(version == IGBINARY_FORMAT_VERSION || version == 0x00000001)) { + return 0; + } else { + igbinary_unserialize_header_emit_warning(igsd, version); + return 1; + } +} +/* }}} */ +/* {{{ igbinary_unserialize8 */ +/** Unserialize 8bit value. */ +inline static uint8_t igbinary_unserialize8(struct igbinary_unserialize_data *igsd) { + return *(igsd->buffer_ptr++); +} +/* }}} */ +/* {{{ igbinary_unserialize16 */ +/** Unserialize 16bit value. */ +inline static uint16_t igbinary_unserialize16(struct igbinary_unserialize_data *igsd) { + uint16_t ret = + ((uint16_t)(igsd->buffer_ptr[0]) << 8) | + ((uint16_t)(igsd->buffer_ptr[1])); + igsd->buffer_ptr += 2; + return ret; +} +/* }}} */ +/* {{{ igbinary_unserialize32 */ +/** Unserialize 32bit value. */ +inline static uint32_t igbinary_unserialize32(struct igbinary_unserialize_data *igsd) { + uint32_t ret = + ((uint32_t)(igsd->buffer_ptr[0]) << 24) | + ((uint32_t)(igsd->buffer_ptr[1]) << 16) | + ((uint32_t)(igsd->buffer_ptr[2]) << 8) | + ((uint32_t)(igsd->buffer_ptr[3])); + igsd->buffer_ptr += 4; + return ret; +} +/* }}} */ +/* {{{ igbinary_unserialize64 */ +/** Unserialize 64bit value. */ +inline static uint64_t igbinary_unserialize64(struct igbinary_unserialize_data *igsd) { + uint64_t ret = + ((uint64_t)((igsd->buffer_ptr[0])) << 56) | + ((uint64_t)((igsd->buffer_ptr[1])) << 48) | + ((uint64_t)((igsd->buffer_ptr[2])) << 40) | + ((uint64_t)((igsd->buffer_ptr[3])) << 32) | + ((uint64_t)((igsd->buffer_ptr[4])) << 24) | + ((uint64_t)((igsd->buffer_ptr[5])) << 16) | + ((uint64_t)((igsd->buffer_ptr[6])) << 8) | + ((uint64_t)((igsd->buffer_ptr[7])) << 0); + igsd->buffer_ptr += 8; + return ret; +} +/* }}} */ +/* {{{ igbinary_unserialize_long */ +/** Unserializes zend_long */ +inline static int igbinary_unserialize_long(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zend_long *ret) { + uint32_t tmp32; +#if SIZEOF_ZEND_LONG == 8 + uint64_t tmp64; +#endif + + if (t == igbinary_type_long8p || t == igbinary_type_long8n) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + return 1; + } + + *ret = (zend_long)(t == igbinary_type_long8n ? -1 : 1) * igbinary_unserialize8(igsd); + } else if (t == igbinary_type_long16p || t == igbinary_type_long16n) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + return 1; + } + + *ret = (zend_long)(t == igbinary_type_long16n ? -1 : 1) * igbinary_unserialize16(igsd); + } else if (t == igbinary_type_long32p || t == igbinary_type_long32n) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + return 1; + } + + /* check for boundaries (perform only one comparison in common case) */ + tmp32 = igbinary_unserialize32(igsd); +#if SIZEOF_ZEND_LONG == 4 + if (UNEXPECTED(tmp32 >= 0x80000000 && (tmp32 > 0x80000000 || t == igbinary_type_long32p))) { + zend_error(E_WARNING, "igbinary_unserialize_long: 64bit long on 32bit platform?"); + tmp32 = 0; /* t == igbinary_type_long32p ? LONG_MAX : LONG_MIN; */ + } +#endif + *ret = (zend_long)(t == igbinary_type_long32n ? -1 : 1) * tmp32; + } else { + ZEND_ASSERT(t == igbinary_type_long64p || t == igbinary_type_long64n); +#if SIZEOF_ZEND_LONG == 8 + if (IGB_NEEDS_MORE_DATA(igsd, 8)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + return 1; + } + + /* check for boundaries (perform only one comparison in common case) */ + tmp64 = igbinary_unserialize64(igsd); + if (UNEXPECTED(tmp64 >= 0x8000000000000000 && (tmp64 > 0x8000000000000000 || t == igbinary_type_long64p))) { + zend_error(E_WARNING, "igbinary_unserialize_long: too big 64bit long."); + tmp64 = 0; /* t == igbinary_type_long64p ? LONG_MAX : LONG_MIN */ + } + + *ret = (zend_long)(t == igbinary_type_long64n ? -1 : 1) * tmp64; +#elif SIZEOF_ZEND_LONG == 4 + /* can't put 64bit long into 32bit one, placeholder zero */ + *ret = 0; + igbinary_unserialize64(igsd); + zend_error(E_WARNING, "igbinary_unserialize_long: 64bit long on 32bit platform"); +#else +#error "Strange sizeof(zend_long)." +#endif + } + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_double */ +/** Unserializes double. */ +inline static int igbinary_unserialize_double(struct igbinary_unserialize_data *igsd, double *ret) { + union { + double d; + uint64_t u; + } u; + + if (IGB_NEEDS_MORE_DATA(igsd, 8)) { + zend_error(E_WARNING, "igbinary_unserialize_double: end-of-data"); + return 1; + } + + u.u = igbinary_unserialize64(igsd); + + *ret = u.d; + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_string */ +/** Unserializes string. Unserializes both actual string or by string id. Returns NULL on error. */ +inline static zend_string *igbinary_unserialize_string(struct igbinary_unserialize_data *igsd, enum igbinary_type t) { + size_t i; + zend_string *zstr; + if (t == igbinary_type_string_id8 || t == igbinary_type_object_id8) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_string: end-of-data"); + return NULL; + } + i = igbinary_unserialize8(igsd); + } else if (t == igbinary_type_string_id16 || t == igbinary_type_object_id16) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_string: end-of-data"); + return NULL; + } + i = igbinary_unserialize16(igsd); + } else if (t == igbinary_type_string_id32 || t == igbinary_type_object_id32) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_string: end-of-data"); + return NULL; + } + i = igbinary_unserialize32(igsd); + } else { + zend_error(E_WARNING, "igbinary_unserialize_string: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return NULL; + } + + if (i >= igsd->strings_count) { + zend_error(E_WARNING, "igbinary_unserialize_string: string index is out-of-bounds"); + return NULL; + } + + zstr = igsd->strings[i]; + // Add one more ref (currently not using any interned strings) - Callers of this will decrease refs as needed +#if PHP_VERSION_ID >= 80100 + zend_string_addref(zstr); +#else + ZEND_ASSERT(!ZSTR_IS_INTERNED(zstr)); + GC_ADDREF(zstr); +#endif + return zstr; +} +/* }}} */ +/* igbinary_unserialize_extremely_long_chararray {{{ */ +static ZEND_COLD zend_never_inline zend_string* igbinary_unserialize_extremely_long_chararray(struct igbinary_unserialize_data *igsd) { +#if SIZEOF_SIZE_T <= 4 + (void)igsd; + zend_error(E_WARNING, "igbinary_unserialize_chararray: cannot unserialize 64-bit data on 32-bit platform"); + return NULL; +#else + if (IGB_NEEDS_MORE_DATA(igsd, 8)) { + zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); + return NULL; + } + size_t l = igbinary_unserialize64(igsd); + if (IGB_NEEDS_MORE_DATA(igsd, l)) { + zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); + return NULL; + } + + if (igsd->strings_count + 1 > igsd->strings_capacity) { + zend_string **new_strings; + igsd->strings_capacity *= 2; + + new_strings = (zend_string **)erealloc(igsd->strings, sizeof(zend_string *) * igsd->strings_capacity); + if (new_strings == NULL) { + // The cleanup function will take care of destroying the allocated zend_strings. + return NULL; + } + igsd->strings = new_strings; + } + + zend_string *zstr = zend_string_init((const char*)igsd->buffer_ptr, l, 0); + + igsd->buffer_ptr += l; + + GC_ADDREF(zstr); /* definitely not interned. Add a reference in case the first reference gets deleted before reusing the temporary string */ + + igsd->strings[igsd->strings_count] = zstr; + igsd->strings_count += 1; + return zstr; +#endif +} +/* }}} */ +/* {{{ igbinary_unserialize_chararray */ +/** Unserializes chararray of string. Returns NULL on error. */ +inline static zend_string *igbinary_unserialize_chararray(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zend_bool check_interned) { + size_t l; + zend_string *zstr; + + if (t == igbinary_type_string8 || t == igbinary_type_object8) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); + return NULL; + } + l = igbinary_unserialize8(igsd); + /* Requires converting these into interned strings. Maybe add one-char in v3 of igbinary format? + if (l == 1) { + zstr = ZSTR_CHAR((zend_uchar)igsd->buffer[igsd->buffer_offset]); + igsd->strings[igsd->strings_count] = zstr; + igsd->strings_count += 1; + igsd->buffer_offset++; + return zstr; + } + */ + } else if (t == igbinary_type_string16 || t == igbinary_type_object16) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); + return NULL; + } + l = igbinary_unserialize16(igsd); + } else if (EXPECTED(t == igbinary_type_string32 || t == igbinary_type_object32)) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); + return NULL; + } + l = igbinary_unserialize32(igsd); + } else if (t == igbinary_type_string64) { + return igbinary_unserialize_extremely_long_chararray(igsd); + } else { + zend_error(E_WARNING, "igbinary_unserialize_chararray: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return NULL; + } + if (IGB_NEEDS_MORE_DATA(igsd, l)) { + zend_error(E_WARNING, "igbinary_unserialize_chararray: end-of-data"); + return NULL; + } + + if (igsd->strings_count + 1 > igsd->strings_capacity) { + zend_string **new_strings; + igsd->strings_capacity *= 2; + + new_strings = (zend_string **)erealloc(igsd->strings, sizeof(zend_string *) * igsd->strings_capacity); + if (new_strings == NULL) { + // The cleanup function will take care of destroying the allocated zend_strings. + return NULL; + } + igsd->strings = new_strings; + } + +#if PHP_VERSION_ID >= 80100 + if (check_interned && l < 100) { + /* + * Reuse interned strings if possible for the following reasons: + * 1. Save memory (e.g. for strings in property default values of classes, for arrays repeating string field names, etc.) + * 2. Speed up checking if strings are identical. + * 3. Speed up the code that ends up using the return value of igbinary_unserialize(). + * + * Note that this change has mixed results. unserialize-object-array and unserialize-stdclass have performance improve because all strings are already interned, + * but unserialize-stringarray on unstructured text has worse performance. + */ + zstr = zend_string_init_existing_interned((const char*)igsd->buffer_ptr, l, 0); + zend_string_addref(zstr); + } else +#endif + { + zstr = zend_string_init((const char*)igsd->buffer_ptr, l, 0); + GC_ADDREF(zstr); /* definitely not interned. Add a reference in case the first reference gets deleted before reusing the temporary string */ + } + + igsd->buffer_ptr += l; + + igsd->strings[igsd->strings_count] = zstr; + igsd->strings_count += 1; + return zstr; +} +/* }}} */ +/* {{{ igbinary_unserialize_array */ +/** Unserializes a PHP array. */ +zend_always_inline static int igbinary_unserialize_array(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags, zend_bool create_ref) { + /* WANT_REF means that z will be wrapped by an IS_REFERENCE */ + uint32_t n; + uint32_t i; + + enum igbinary_type key_type; + + HashTable *h; + + if (t == igbinary_type_array8) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); + return 1; + } + n = igbinary_unserialize8(igsd); + if (n == 0) { + if (create_ref) { + /* NOTE: igbinary uses ZVAL_ARR() when reading the created reference, which assumes that the original array is refcounted. */ + /* This would have to be fixed to switch to ZVAL_EMPTY_ARRAY for the empty array (there's probably not benefit to making that switch). */ + /* Otherwise, using ZVAL_EMPTY_ARRAY would segfault. */ +#if PHP_VERSION_ID >= 70300 + ZVAL_EMPTY_ARRAY(z); +#else + array_init_size(z, 0); +#endif + struct igbinary_value_ref ref; + if (flags & WANT_REF) { + ZVAL_NEW_REF(z, z); + ref.reference.reference = Z_REF_P(z); + ref.type = IG_REF_IS_REFERENCE; + } else { +#if PHP_VERSION_ID >= 70300 + ref.type = IG_REF_IS_EMPTY_ARRAY; +#else + ref.reference.array = Z_ARR_P(z); + ref.type = IG_REF_IS_ARRAY; +#endif + } + /* add the new array to the list of unserialized references */ + RETURN_1_IF_NON_ZERO(igsd_append_ref(igsd, ref) == SIZE_MAX); + } else { + /* This only matters if __unserialize() would get called with an empty array */ +#if PHP_VERSION_ID >= 70300 + ZVAL_EMPTY_ARRAY(z); +#else + array_init_size(z, 0); +#endif + } + return 0; + } + } else if (t == igbinary_type_array16) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); + return 1; + } + n = igbinary_unserialize16(igsd); + } else if (t == igbinary_type_array32) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); + return 1; + } + n = igbinary_unserialize32(igsd); + } else { + zend_error(E_WARNING, "igbinary_unserialize_array: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + + /* n cannot be larger than the number of minimum "objects" in the array */ + if (IGB_NEEDS_MORE_DATA(igsd, n)) { + zend_error(E_WARNING, "igbinary_unserialize_array: data size %zu smaller that requested array length %zu.", (size_t)IGB_REMAINING_BYTES(igsd), (size_t)n); + return 1; + } + + zval *z_deref = z; + if (flags & WANT_REF) { + if (!Z_ISREF_P(z)) { + ZVAL_NEW_REF(z, z); + z_deref = Z_REFVAL_P(z); + } + } + array_init_size(z_deref, n); + h = Z_ARRVAL_P(z_deref); +#if PHP_VERSION_ID >= 70200 + /* The array may contain references to itself, in which case we'll be modifying an + * rc>1 array. This is okay, since the array is, ostensibly, only visible to + * unserialize (in practice unserialization handlers also see it). */ + HT_ALLOW_COW_VIOLATION(h); +#endif + if (create_ref) { + /* Only create a reference if this is not from __unserialize(), because the existence of __unserialize can change */ + struct igbinary_value_ref ref; + if (flags & WANT_REF) { + // We converted to reference earlier. + ref.reference.reference = Z_REF_P(z); + ref.type = IG_REF_IS_REFERENCE; + } else { + ref.reference.array = Z_ARR_P(z_deref); + ref.type = IG_REF_IS_ARRAY; + } + /* add the new array to the list of unserialized references */ + RETURN_1_IF_NON_ZERO(igsd_append_ref(igsd, ref) == SIZE_MAX); + } + + for (i = 0; i < n; i++) { + zval *vp; + zend_long key_index = 0; + zend_string *key_str = NULL; /* NULL means use key_index */ + + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_array: end-of-data"); +cleanup: + zval_ptr_dtor_nogc(z); + ZVAL_NULL(z); + return 1; + } + + key_type = (enum igbinary_type)igbinary_unserialize8(igsd); + + switch (key_type) { + case igbinary_type_long8p: + /* Manually inline igbinary_unserialize_long() for array keys from 0 to 255, because they're the most common among integers. */ + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + goto cleanup; + } + + key_index = igbinary_unserialize8(igsd); + break; + case igbinary_type_long16p: + /* and for array keys from 0 to 65535. */ + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + goto cleanup; + } + + key_index = igbinary_unserialize16(igsd); + break; + case igbinary_type_long8n: + case igbinary_type_long16n: + case igbinary_type_long32p: + case igbinary_type_long32n: + case igbinary_type_long64p: + case igbinary_type_long64n: + if (UNEXPECTED(igbinary_unserialize_long(igsd, key_type, &key_index))) { + goto cleanup; + } + break; + case igbinary_type_string_id8: + case igbinary_type_string_id16: + case igbinary_type_string_id32: + key_str = igbinary_unserialize_string(igsd, key_type); + if (UNEXPECTED(key_str == NULL)) { + goto cleanup; + } + break; + case igbinary_type_string8: + case igbinary_type_string16: + case igbinary_type_string32: + case igbinary_type_string64: + key_str = igbinary_unserialize_chararray(igsd, key_type, 1); + if (UNEXPECTED(key_str == NULL)) { + goto cleanup; + } + break; + case igbinary_type_string_empty: + key_str = ZSTR_EMPTY_ALLOC(); + break; + case igbinary_type_null: + continue; + default: + zend_error(E_WARNING, "igbinary_unserialize_array: unknown key type '%02x', position %zu", key_type, (size_t)IGB_BUFFER_OFFSET(igsd)); + goto cleanup; + } + + /* first add key into array so references can properly and not stack allocated zvals */ + /* Use NULL because inserting UNDEF into array does not add a new element */ + if (key_str != NULL) { + vp = igbinary_zend_hash_add_or_find(h, key_str); + /* If there was an old value instead of an inserted IS_NULL zval (unlikely) and that old value was refcounted, then add a reference and defer freeing that reference */ + if (UNEXPECTED(igsd_addref_and_defer_dtor(&igsd->deferred_dtor_tracker, vp))) { + return 1; + } + + zend_string_release(key_str); + } else { + /* If there was an old value instead of an inserted IS_NULL zval (unlikely) and that old value was refcounted, then add a reference and defer freeing that reference */ + vp = igbinary_zend_hash_index_add_or_find(h, key_index); + if (UNEXPECTED(igsd_addref_and_defer_dtor(&igsd->deferred_dtor_tracker, vp))) { + return 1; + } + } + + ZEND_ASSERT(vp != NULL); + if (Z_TYPE_P(vp) == IS_INDIRECT) { + /* TODO: In php 8.1+, IS_INDIRECT checks and macros may become unnecessary + * due to $GLOBALS being converted to a regular array in https://wiki.php.net/rfc/restrict_globals_usage */ + vp = Z_INDIRECT_P(vp); + } + + ZEND_ASSERT(vp != NULL); + if (UNEXPECTED(igbinary_unserialize_zval(igsd, vp, WANT_CLEAR))) { + return 1; + } + } + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_object_properties */ +/** Unserializes the array of object properties and adds those to the object z. */ +inline static int igbinary_unserialize_object_properties(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, const zend_class_entry *ce) { + /* WANT_REF means that z will be wrapped by an IS_REFERENCE */ + uint32_t n; + + zval v; + zval *z_deref; + + HashTable *h; + zend_bool did_extend; + + if (t == igbinary_type_array8) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_object_properties: end-of-data"); + return 1; + } + n = igbinary_unserialize8(igsd); + } else if (t == igbinary_type_array16) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_object_properties: end-of-data"); + return 1; + } + n = igbinary_unserialize16(igsd); + } else if (t == igbinary_type_array32) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_object_properties: end-of-data"); + return 1; + } + n = igbinary_unserialize32(igsd); + } else { + zend_error(E_WARNING, "igbinary_unserialize_object_properties: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + + /* n cannot be larger than the number of minimum "objects" in the array */ + if (IGB_NEEDS_MORE_DATA(igsd, n)) { + zend_error(E_WARNING, "%s: data size %zu smaller that requested array length %zu.", "igbinary_unserialize_object_properties", (size_t)IGB_REMAINING_BYTES(igsd), (size_t)n); + return 1; + } + + z_deref = z; + ZVAL_DEREF(z_deref); + + /* empty array */ + if (n == 0) { + return 0; + } + + h = HASH_OF_OBJECT(z_deref); + + did_extend = 0; + + do { + n--; + zval *vp; + enum igbinary_type key_type; + zend_string *key_str = NULL; /* NULL means use key_index */ + + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_object_properties: end-of-data"); + zval_ptr_dtor_nogc(z); + ZVAL_NULL(z); + return 1; + } + + key_type = (enum igbinary_type)igbinary_unserialize8(igsd); + + switch (key_type) { + case igbinary_type_long8p: + case igbinary_type_long8n: + case igbinary_type_long16p: + case igbinary_type_long16n: + case igbinary_type_long32p: + case igbinary_type_long32n: + case igbinary_type_long64p: + case igbinary_type_long64n: + { + zend_long key_index = 0; + if (UNEXPECTED(igbinary_unserialize_long(igsd, key_type, &key_index))) { + zval_ptr_dtor_nogc(z); + ZVAL_UNDEF(z); + return 1; + } + key_str = zend_long_to_str(key_index); + if (UNEXPECTED(key_str == NULL)) { + zval_ptr_dtor_nogc(z); + ZVAL_UNDEF(z); + return 1; + } + break; + } + case igbinary_type_string_id8: + case igbinary_type_string_id16: + case igbinary_type_string_id32: + key_str = igbinary_unserialize_string(igsd, key_type); + if (UNEXPECTED(key_str == NULL)) { + zval_ptr_dtor_nogc(z); + ZVAL_UNDEF(z); + return 1; + } + break; + case igbinary_type_string8: + case igbinary_type_string16: + case igbinary_type_string32: + case igbinary_type_string64: + key_str = igbinary_unserialize_chararray(igsd, key_type, 1); + if (UNEXPECTED(key_str == NULL)) { + zval_ptr_dtor_nogc(z); + ZVAL_UNDEF(z); + return 1; + } + break; + case igbinary_type_string_empty: + key_str = ZSTR_EMPTY_ALLOC(); + break; + case igbinary_type_null: + continue; /* Skip unserializing this element, serialized with no value. In C, this applies to loop, not switch. */ + default: + zend_error(E_WARNING, "igbinary_unserialize_object_properties: unknown key type '%02x', position %zu", key_type, (size_t)IGB_BUFFER_OFFSET(igsd)); + zval_ptr_dtor_nogc(z); + ZVAL_UNDEF(z); + return 1; + } + + /* first add key into array so references can properly and not stack allocated zvals */ + /* Use NULL because inserting UNDEF into array does not add a new element */ + ZVAL_NULL(&v); + zval *prototype_value = zend_hash_find(h, key_str); + +#if PHP_VERSION_ID >= 70200 + /* If the visibility of a property has changed between serialization and unserialization, or if a private + * or protected property needs to be restored, but the serialized data was generated by a __serialize method + * while no __unserialize method exists in the class, the property value cannot be found during hydration + * in the object's hash table. The reason for this is that in these cases the property name in the serialized + * representation does not match the prefixed/mangled property name of the current class definition. + * To resolve this, an attempt is made to determine the correctly prefixed property name by performing a lookup + * with the unprefixed property name in the properties_info hash table of the class. This aligns the behavior + * to the serializer of PHP >= 7.2. */ + if (prototype_value == NULL) { + zend_property_info *fallback_info = NULL; + const char *unmangled_class = NULL; + const char *unmangled_prop; + size_t unmangled_prop_len; + + if (UNEXPECTED(zend_unmangle_property_name_ex(key_str, &unmangled_class, &unmangled_prop, &unmangled_prop_len) == FAILURE)) { + zend_string_release(key_str); + return 1; + } + + if (unmangled_class == NULL) { + fallback_info = zend_hash_find_ptr(&ce->properties_info, key_str); + } else if (!strcmp(unmangled_class, "*") || !strcasecmp(unmangled_class, ZSTR_VAL(ce->name))) { + fallback_info = zend_hash_str_find_ptr(&ce->properties_info, unmangled_prop, unmangled_prop_len); + } + + if (fallback_info != NULL) { +#if PHP_VERSION_ID >= 80400 + if ((fallback_info->flags & ZEND_ACC_VIRTUAL)) { + php_error_docref(NULL, E_WARNING, + "Cannot unserialize value for virtual property %s::$%s", + ZSTR_VAL(fallback_info->ce->name), unmangled_prop); + zend_string_release(key_str); + return 1; + } +#endif + zend_string_release(key_str); + key_str = zend_string_copy(fallback_info->name); + prototype_value = zend_hash_find(h, key_str); + } + } +#endif + +#if PHP_VERSION_ID >= 70400 + zend_property_info *info = NULL; +#endif + if (prototype_value != NULL) { + if (Z_TYPE_P(prototype_value) == IS_INDIRECT) { + /* This is a declared object property */ + prototype_value = Z_INDIRECT_P(prototype_value); +#if PHP_VERSION_ID >= 70400 + info = zend_get_typed_property_info_for_slot(Z_OBJ_P(z_deref), prototype_value); + if (info) { + if (Z_ISREF_P(prototype_value)) { + /* If the value is overwritten, remove old type source from ref. */ + ZEND_REF_DEL_TYPE_SOURCE(Z_REF_P(prototype_value), info); + } + + if (igsd->ref_props) { + /* Remove old entry from ref_props table, if it exists. */ + zend_hash_index_del( + igsd->ref_props, ((uintptr_t) prototype_value) >> ZEND_MM_ALIGNMENT_LOG2); + } + } +#endif + } + /* This is written to avoid the overhead of a second zend_hash_update call. See https://github.com/php/php-src/pull/5095 */ + /* Use igsd_addref_and_defer_dtor instead (like php-src), in case of gc causing issues. */ + /* Something already added a reference, so just defer the destructor */ + if (UNEXPECTED(igsd_defer_dtor(&igsd->deferred_dtor_tracker, prototype_value))) { + zend_string_release(key_str); + return 1; + } + /* Just override the original value directly */ + ZVAL_COPY_VALUE(prototype_value, &v); + vp = prototype_value; + } else { +#if PHP_VERSION_ID >= 80000 + /* ZEND_ACC_NO_DYNAMIC_PROPERTIES was introduced in php 8.0 but was largely used with zend_class_unserialize_deny, though external pecls might set this flag. */ + if (UNEXPECTED(ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) { + zend_throw_error(NULL, "Cannot create dynamic property %s::$%s in igbinary_unserialize", + ZSTR_VAL(ce->name), zend_get_unmangled_property_name(key_str)); + zend_string_release(key_str); + return 1; + } +#endif +#if PHP_VERSION_ID >= 80200 + /* PHP 8.2 deprecated the creation of dynamic properties by default without `#[AllowDynamicProperties]`. */ + if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES))) { + php_error_docref(NULL, E_DEPRECATED, "Creation of dynamic property %s::$%s is deprecated", + ZSTR_VAL(ce->name), zend_get_unmangled_property_name(key_str)); + if (UNEXPECTED(EG(exception))) { + zend_string_release(key_str); + return 1; + } + } +#endif + if (!did_extend) { + /* remaining_elements is at least one, because we're looping from n-1..0 */ + uint32_t remaining_elements = n + 1; + /* Copied from var_unserializer.re. Need to ensure that IGB_REF_VAL doesn't point to invalid data. */ + /* Worst case: All remaining_elements of the added properties are dynamic. */ + zend_hash_extend(h, zend_hash_num_elements(h) + remaining_elements, (h->u.flags & HASH_FLAG_PACKED)); + did_extend = 1; + } + vp = zend_hash_add_new(h, key_str, &v); + } + + zend_string_release(key_str); + + /* Should only be indirect for typed properties? */ + ZEND_ASSERT(Z_TYPE_P(vp) != IS_INDIRECT); + + if (UNEXPECTED(igbinary_unserialize_zval(igsd, vp, WANT_CLEAR))) { +#if PHP_VERSION_ID >= 70400 + if (info && Z_ISREF_P(vp)) { + /* Add type source even if we failed to unserialize. + * The data is still stored in the property. */ + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(vp), info); + } +#endif + /* Unserializing a property into this zval has failed. */ + /* zval_ptr_dtor(z); */ + /* zval_ptr_dtor(vp); */ + return 1; + } +#if PHP_VERSION_ID >= 70400 + if (UNEXPECTED(info)) { + if (!zend_verify_prop_assignable_by_ref(info, vp, /* strict */ 1)) { + zval_ptr_dtor(vp); + ZVAL_UNDEF(vp); + return 1; + } + if (Z_ISREF_P(vp)) { + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(vp), info); + } else { + /* Remember to which property this slot belongs, so we can add a + * type source if it is turned into a reference lateron. */ + if (!igsd->ref_props) { + igsd->ref_props = emalloc(sizeof(HashTable)); + zend_hash_init(igsd->ref_props, 8, NULL, NULL, 0); + } + zend_hash_index_update_ptr( + igsd->ref_props, ((uintptr_t) vp) >> ZEND_MM_ALIGNMENT_LOG2, info); + } + } +#endif + } while (n > 0); + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_object_ser */ +/** Unserializes object's property array. This is used to serialize objects implementing Serializable -interface. */ +static ZEND_COLD int igbinary_unserialize_object_ser(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, zend_class_entry *ce) { + size_t n; + int ret; + php_unserialize_data_t var_hash; + + if (ce->unserialize == NULL) { + /* Should be impossible. */ + zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name)); + return 1; + } + + if (IGBINARY_IS_NOT_UNSERIALIZABLE(ce)) { + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", ZSTR_VAL(ce->name)); + return 1; + } + + if (t == igbinary_type_object_ser8) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); + return 1; + } + n = igbinary_unserialize8(igsd); + } else if (t == igbinary_type_object_ser16) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); + return 1; + } + n = igbinary_unserialize16(igsd); + } else if (t == igbinary_type_object_ser32) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); + return 1; + } + n = igbinary_unserialize32(igsd); + } else { + zend_error(E_WARNING, "igbinary_unserialize_object_ser: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + + if (IGB_NEEDS_MORE_DATA(igsd, n)) { + zend_error(E_WARNING, "igbinary_unserialize_object_ser: end-of-data"); + return 1; + } + + PHP_VAR_UNSERIALIZE_INIT(var_hash); + ret = ce->unserialize( + z, + ce, + (const unsigned char *)igsd->buffer_ptr, + n, + (zend_unserialize_data *)&var_hash + ); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + + if (ret != SUCCESS || EG(exception)) { + return 1; + } + + igsd->buffer_ptr += n; + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_object_enum_case */ +#if PHP_VERSION_ID >= 80100 +/** Unserializes object's property array. This is used to serialize objects implementing Serializable -interface. */ +static int igbinary_unserialize_object_enum_case(struct igbinary_unserialize_data *igsd, zval *const z, zend_class_entry *ce) { + if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_ENUM))) { + zend_error(E_WARNING, "igbinary_unserialize_object_enum_case: Class '%s' is not an enum", ZSTR_VAL(ce->name)); + return 1; + } + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_object_enum_case: end-of-data"); + return 1; + } + + enum igbinary_type t = (enum igbinary_type)igbinary_unserialize8(igsd); + zend_string *case_name; + switch (t) { + case igbinary_type_string8: + case igbinary_type_string16: + case igbinary_type_string32: + case igbinary_type_string64: + case_name = igbinary_unserialize_chararray(igsd, t, 1); + break; + default: + case_name = igbinary_unserialize_string(igsd, t); + break; + } + if (UNEXPECTED(!case_name)) { + return 1; + } + + zval *zv = zend_hash_find(CE_CONSTANTS_TABLE(ce), case_name); + if (UNEXPECTED(!zv)) { + zend_error(E_WARNING, "igbinary_unserialize_object_enum_case: Undefined constant %s::%s", ZSTR_VAL(ce->name), ZSTR_VAL(case_name)); + zend_string_release(case_name); + return 1; + } + + zend_class_constant *c = Z_PTR_P(zv); + if (UNEXPECTED(!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE))) { + zend_error(E_WARNING, "igbinary_unserialize_object_enum_case: %s::%s is not an enum case", ZSTR_VAL(ce->name), ZSTR_VAL(case_name)); + zend_string_release(case_name); + return 1; + } + zend_string_release(case_name); + zval *value = &c->value; + if (Z_TYPE_P(value) == IS_CONSTANT_AST) { + zval_update_constant_ex(value, c->ce); + if (UNEXPECTED(EG(exception) != NULL)) { + return 1; + } + } + ZEND_ASSERT(Z_TYPE_P(value) == IS_OBJECT); + /* increment the reference count and copy the enum case object into the constructed value. */ + ZVAL_COPY(z, value); + + return 0; +} +#endif +/* }}} */ +/* {{{ igbinary_unserialize_object */ +/** Unserialize an object. + * @see ext/standard/var_unserializer.c in the php-src repo. Parts of this code are based on that. + */ +zend_always_inline static int igbinary_unserialize_object(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *z, int flags) { + zend_class_entry *ce; + + size_t ref_n; + + zend_string *class_name; + + int r; + + bool incomplete_class = false; + bool is_from_serialized_data = false; + + if (t == igbinary_type_object8 || t == igbinary_type_object16 || t == igbinary_type_object32) { + class_name = igbinary_unserialize_chararray(igsd, t, 1); + } else if (t == igbinary_type_object_id8 || t == igbinary_type_object_id16 || t == igbinary_type_object_id32) { + class_name = igbinary_unserialize_string(igsd, t); + } else { + zend_error(E_WARNING, "igbinary_unserialize_object: unknown object type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + + if (class_name == NULL) { + return 1; + } + + do { + zval user_func; + zval retval; + zval args[1]; + const char* user_func_name; + + /* Try to find class directly */ + if (EXPECTED((ce = zend_lookup_class(class_name)) != NULL)) { + /* FIXME: lookup class may cause exception in load callback */ + break; + } + + user_func_name = PG(unserialize_callback_func); + /* Check for unserialize callback */ + if ((user_func_name == NULL) || (user_func_name[0] == '\0')) { + incomplete_class = 1; + ce = PHP_IC_ENTRY; + break; + } + + /* Call unserialize callback */ + ZVAL_STRING(&user_func, user_func_name); + ZVAL_STR(&args[0], class_name); + if (call_user_function(CG(function_table), NULL, &user_func, &retval, 1, args) != SUCCESS) { + php_error_docref(NULL, E_WARNING, "defined (%s) but not found", Z_STRVAL(user_func)); + incomplete_class = 1; + ce = PHP_IC_ENTRY; + zval_ptr_dtor_nogc(&user_func); + break; + } + /* FIXME: always safe? */ + zval_ptr_dtor(&retval); + zval_ptr_dtor_str(&user_func); + + /* User function call may have raised an exception */ + if (EG(exception)) { + zend_string_release_ex(class_name, 0); + return 1; + } + + /* The callback function may have defined the class */ + ce = zend_lookup_class(class_name); + if (!ce) { + php_error_docref(NULL, E_WARNING, "Function %s() hasn't defined the class it was called for", PG(unserialize_callback_func)); + incomplete_class = true; + ce = PHP_IC_ENTRY; + } + } while (0); + + if (IGBINARY_IS_NOT_UNSERIALIZABLE(ce)) { + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", ZSTR_VAL(ce->name)); + zend_string_release(class_name); + return 1; + } + + /* add this to the list of unserialized references, get the index */ + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_object: end-of-data"); + zend_string_release(class_name); + return 1; + } + + { + /* The actual value of ref is unused. We use ref_n later in this function, after creating the object. */ + struct igbinary_value_ref ref = {{0}, 0}; + ref_n = igsd_append_ref(igsd, ref); + if (UNEXPECTED(ref_n == SIZE_MAX)) { + zend_string_release(class_name); + return 1; + } + } + + t = (enum igbinary_type)igbinary_unserialize8(igsd); + switch (t) { + case igbinary_type_array8: + case igbinary_type_array16: + case igbinary_type_array32: + { + if (UNEXPECTED(object_init_ex(z, ce) != SUCCESS)) { + php_error_docref(NULL, E_NOTICE, "igbinary unable to create object for class entry"); + r = 1; + break; + } + if (incomplete_class) { +#if PHP_VERSION_ID >= 80000 + php_store_class_name(z, class_name); +#else + php_store_class_name(z, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); +#endif +#if PHP_VERSION_ID >= 70400 + } else { +#if PHP_VERSION_ID >= 80000 + if (ce->__unserialize) +#else + if (zend_hash_str_exists(&ce->function_table, "__unserialize", sizeof("__unserialize") - 1)) +#endif + { + ZEND_ASSERT(Z_TYPE_P(z) == IS_OBJECT); + struct igbinary_value_ref *ref = &IGB_REF_VAL_2(igsd, ref_n); + if ((flags & WANT_REF) != 0) { + ZVAL_MAKE_REF(z); + ref->reference.reference = Z_REF_P(z); + ref->type = IG_REF_IS_REFERENCE; + } else { + ref->reference.object = Z_OBJ_P(z); + ref->type = IG_REF_IS_OBJECT; + } + /* Unserialize the array as an array for a deferred call to __unserialize */ + zval param; + int result; + zend_string_release(class_name); + result = igbinary_unserialize_array(igsd, t, ¶m, 0, false); + ZVAL_DEREF(z); + ZEND_ASSERT(Z_TYPE_P(z) == IS_OBJECT); + igsd_defer_unserialize(igsd, Z_OBJ_P(z), param); + return result; + } +#endif + } + struct igbinary_value_ref *ref = &IGB_REF_VAL_2(igsd, ref_n); + if ((flags & WANT_REF) != 0) { + ZVAL_MAKE_REF(z); + ref->reference.reference = Z_REF_P(z); + ref->type = IG_REF_IS_REFERENCE; + } else { + ref->reference.object = Z_OBJ_P(z); + ref->type = IG_REF_IS_OBJECT; + } + + r = igbinary_unserialize_object_properties(igsd, t, z, ce); + break; + } + case igbinary_type_object_ser8: + case igbinary_type_object_ser16: + case igbinary_type_object_ser32: + { + is_from_serialized_data = true; + /* FIXME will this break if z isn't an object? */ + r = igbinary_unserialize_object_ser(igsd, t, z, ce); + if (r != 0) { + break; + } + + if (incomplete_class) { +#if PHP_VERSION_ID >= 80000 + php_store_class_name(z, class_name); +#else + php_store_class_name(z, ZSTR_VAL(class_name), ZSTR_LEN(class_name)); +#endif + } + struct igbinary_value_ref *ref = &IGB_REF_VAL_2(igsd, ref_n); + if ((flags & WANT_REF) != 0) { + ZVAL_MAKE_REF(z); + ref->reference.reference = Z_REF_P(z); + ref->type = IG_REF_IS_REFERENCE; + } else { + ref->reference.object = Z_OBJ_P(z); + ref->type = IG_REF_IS_OBJECT; + } + break; + } + case igbinary_type_enum_case: +#if PHP_VERSION_ID >= 80100 + if (UNEXPECTED(incomplete_class)) { + zend_error(E_WARNING, "igbinary_unserialize_object_enum_case: Class '%s' does not exist", ZSTR_VAL(class_name)); + zend_string_release(class_name); + return 1; + } + zend_string_release(class_name); + r = igbinary_unserialize_object_enum_case(igsd, z, ce); + if (r) { + return r; + } + + struct igbinary_value_ref *ref = &IGB_REF_VAL_2(igsd, ref_n); + if ((flags & WANT_REF) != 0) { + ZVAL_MAKE_REF(z); + ref->reference.reference = Z_REF_P(z); + ref->type = IG_REF_IS_REFERENCE; + } else { + ref->reference.object = Z_OBJ_P(z); + ref->type = IG_REF_IS_OBJECT; + } + return 0; +#else + zend_error(E_WARNING, "igbinary_unserialize_object: Cannot unserialize enum cases prior to php 8.1 at position %zu", (size_t)IGB_BUFFER_OFFSET(igsd)); + r = 1; +#endif + break; + default: + zend_error(E_WARNING, "igbinary_unserialize_object: unknown object inner type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + r = 1; + } + zend_string_release(class_name); + class_name = NULL; + + /* If unserialize was successful, defer the call to __wakeup if __wakeup exists for this object. */ + /* (But don't call __wakeup() if Serializable::unserialize was called */ + if (r == 0 && !is_from_serialized_data) { + struct igbinary_value_ref *const ref = &IGB_REF_VAL_2(igsd, ref_n); + zend_object *object; + if (ref->type == IG_REF_IS_OBJECT) { + object = ref->reference.object; + } else if (EXPECTED(ref->type == IG_REF_IS_REFERENCE)) { + /* May have created a reference while deserializing an object, if it was recursive. */ + zval ztemp = ref->reference.reference->val; + if (UNEXPECTED(Z_TYPE(ztemp) != IS_OBJECT)) { + zend_error(E_WARNING, "igbinary_unserialize_object preparing to __wakeup/__unserialize: got reference to non-object somehow (inner type '%02x', position %zu)", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + object = Z_OBJ(ztemp); + } else { + zend_error(E_WARNING, "igbinary_unserialize_object preparing to __wakeup/__unserialize: created non-object somehow (inner type '%02x', position %zu)", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + if (object->ce != PHP_IC_ENTRY) { + if (zend_hash_str_exists(&object->ce->function_table, "__wakeup", sizeof("__wakeup") - 1)) { + if (UNEXPECTED(igsd_defer_wakeup(igsd, object))) { + return 1; + } + } + } + } + + return r; +} +/* }}} */ +/* {{{ igbinary_unserialize_ref */ +/** Unserializes an array or object by reference. */ +zend_always_inline static int igbinary_unserialize_ref(struct igbinary_unserialize_data *igsd, enum igbinary_type t, zval *const z, int flags) { + size_t n; + + if (t == igbinary_type_ref8 || t == igbinary_type_objref8) { + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_ref: end-of-data"); + return 1; + } + n = igbinary_unserialize8(igsd); + } else if (t == igbinary_type_ref16 || t == igbinary_type_objref16) { + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_ref: end-of-data"); + return 1; + } + n = igbinary_unserialize16(igsd); + } else if (t == igbinary_type_ref32 || t == igbinary_type_objref32) { + if (IGB_NEEDS_MORE_DATA(igsd, 4)) { + zend_error(E_WARNING, "igbinary_unserialize_ref: end-of-data"); + return 1; + } + n = igbinary_unserialize32(igsd); + } else { + zend_error(E_WARNING, "igbinary_unserialize_ref: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + + if (n >= igsd->references_count) { + zend_error(E_WARNING, "igbinary_unserialize_ref: invalid reference %zu >= %zu", (size_t)n, (size_t)igsd->references_count); + return 1; + } + + if (z != NULL) { + /* FIXME: check with is refcountable or some such */ + zval_ptr_dtor(z); + ZVAL_UNDEF(z); + } + + struct igbinary_value_ref *ref_ptr = &IGB_REF_VAL_2(igsd, n); + struct igbinary_value_ref ref = *ref_ptr; + + /** + * Permanently convert the zval in IGB_REF_VAL() into a IS_REFERENCE if it wasn't already one. + * TODO: Can there properly be multiple reference groups to an object? + * Similar to https://github.com/php/php-src/blob/master/ext/standard/var_unserializer.re , for "R:" + * Using `flags` because igbinary_unserialize_ref might be used both for copy on writes ($a = $b = [2]) and by PHP references($a = &$b). + */ + if ((flags & WANT_REF) != 0) { + /* Want to create an IS_REFERENCE, not just to share the same value until modified. */ + switch (ref.type) { + case IG_REF_IS_OBJECT: + ZVAL_OBJ(z, ref.reference.object); + Z_ADDREF_P(z); + ZVAL_MAKE_REF(z); /* Convert original zval data to a reference */ + /* replace the entry in IGB_REF_VAL with a reference. */ + ref_ptr->reference.reference = Z_REF_P(z); + ref_ptr->type = IG_REF_IS_REFERENCE; + break; + case IG_REF_IS_ARRAY: + ZVAL_ARR(z, ref.reference.array); + /* All arrays built by igbinary when unserializing are refcounted, except IG_REF_IS_EMPTY_ARRAY. */ + /* If they were not refcounted, the ZVAL_ARR call would probably also need to be changed. */ + Z_ADDREF_P(z); + ZVAL_MAKE_REF(z); /* Convert original zval data to a reference */ + /* replace the entry in IGB_REF_VAL with a reference. */ + ref_ptr->reference.reference = Z_REF_P(z); + ref_ptr->type = IG_REF_IS_REFERENCE; + break; +#if PHP_VERSION_ID >= 70300 + case IG_REF_IS_EMPTY_ARRAY: + ZVAL_EMPTY_ARRAY(z); + ZVAL_MAKE_REF(z); /* Convert original zval data to a reference */ + /* replace the entry in IGB_REF_VAL with a reference. */ + ref_ptr->reference.reference = Z_REF_P(z); + ref_ptr->type = IG_REF_IS_REFERENCE; + break; +#endif + case IG_REF_IS_REFERENCE: + // This is already a reference, convert into reference count. + ZVAL_REF(z, ref.reference.reference); + Z_ADDREF_P(z); + break; + } + } else { + switch (ref.type) { + case IG_REF_IS_OBJECT: + ZVAL_OBJ(z, ref.reference.object); + Z_ADDREF_P(z); + break; + case IG_REF_IS_ARRAY: + ZVAL_ARR(z, ref.reference.array); + Z_ADDREF_P(z); + break; +#if PHP_VERSION_ID >= 70300 + case IG_REF_IS_EMPTY_ARRAY: + ZVAL_EMPTY_ARRAY(z); + break; +#endif + case IG_REF_IS_REFERENCE: + ZVAL_COPY(z, &(ref.reference.reference->val)); + break; + } + } + + return 0; +} +/* }}} */ +/* {{{ igbinary_unserialize_zval */ +/** Unserialize a zval of any serializable type (zval is PHP's internal representation of a value). + * Recursion-depth wrapper -- enforces the unserialize_max_depth ini setting before + * delegating to igbinary_unserialize_zval_inner, mirroring the protection PHP core's + * unserialize() applies via php_var_unserialize. */ +zend_always_inline static int igbinary_unserialize_zval(struct igbinary_unserialize_data *igsd, zval *const z, int flags) { + int ret; + if (UNEXPECTED(igsd->cur_depth >= igsd->max_depth && igsd->max_depth > 0)) { + php_error_docref(NULL, E_WARNING, + "Maximum depth of " ZEND_LONG_FMT " exceeded. " + "The depth limit can be changed using the unserialize_max_depth ini setting", + igsd->max_depth); + return 1; + } + igsd->cur_depth++; + ret = igbinary_unserialize_zval_inner(igsd, z, flags); + igsd->cur_depth--; + return ret; +} +/* }}} */ +/* {{{ igbinary_unserialize_zval_inner */ +static int igbinary_unserialize_zval_inner(struct igbinary_unserialize_data *igsd, zval *const z, int flags) { + enum igbinary_type t; + + zend_long tmp_long; + double tmp_double; + zend_string *tmp_str; + + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_zval: end-of-data"); + return 1; + } + + t = (enum igbinary_type)igbinary_unserialize8(igsd); + + switch (t) { + case igbinary_type_ref: + if (UNEXPECTED(igbinary_unserialize_zval(igsd, z, WANT_REF))) { + return 1; + } + + /* If it is already a ref, nothing to do */ + if (Z_ISREF_P(z)) { + break; + } + + const zend_uchar type = Z_TYPE_P(z); + /* Permanently convert the zval in IGB_REF_VAL() into a IS_REFERENCE if it wasn't already one. */ + /* TODO: Support multiple reference groups to the same object */ + /* Similar to https://github.com/php/php-src/blob/master/ext/standard/var_unserializer.re , for "R:" */ + if (!Z_ISREF_P(z)) { +#if PHP_VERSION_ID >= 70400 + zend_property_info *info = NULL; + if (igsd->ref_props) { + info = zend_hash_index_find_ptr(igsd->ref_props, ((uintptr_t) z) >> ZEND_MM_ALIGNMENT_LOG2); + } +#endif + ZVAL_NEW_REF(z, z); +#if PHP_VERSION_ID >= 70400 + if (info) { + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(z), info); + } +#endif + } + switch (type) { + case IS_STRING: + case IS_LONG: + case IS_NULL: + case IS_DOUBLE: + case IS_FALSE: + case IS_TRUE: + { + struct igbinary_value_ref ref; + ref.reference.reference = Z_REF_P(z); + ref.type = IG_REF_IS_REFERENCE; + /* add the unserialized scalar to the list of unserialized references. Objects and arrays were already added in igbinary_unserialize_zval. */ + RETURN_1_IF_NON_ZERO(igsd_append_ref(igsd, ref) == SIZE_MAX); + break; + } + default: + break; + } + break; + case igbinary_type_objref8: + case igbinary_type_objref16: + case igbinary_type_objref32: + case igbinary_type_ref8: + case igbinary_type_ref16: + case igbinary_type_ref32: + if (UNEXPECTED(igbinary_unserialize_ref(igsd, t, z, flags))) { + return 1; + } + break; + case igbinary_type_object8: + case igbinary_type_object16: + case igbinary_type_object32: + case igbinary_type_object_id8: + case igbinary_type_object_id16: + case igbinary_type_object_id32: + if (UNEXPECTED(igbinary_unserialize_object(igsd, t, z, flags))) { + return 1; + } + break; + case igbinary_type_array8: + case igbinary_type_array16: + case igbinary_type_array32: + if (UNEXPECTED(igbinary_unserialize_array(igsd, t, z, flags, true))) { + return 1; + } + break; + case igbinary_type_string_empty: + ZVAL_EMPTY_STRING(z); + break; + case igbinary_type_string_id8: + case igbinary_type_string_id16: + case igbinary_type_string_id32: + tmp_str = igbinary_unserialize_string(igsd, t); + if (UNEXPECTED(tmp_str == NULL)) { + return 1; + } + ZVAL_STR(z, tmp_str); + break; + case igbinary_type_string8: + case igbinary_type_string16: + case igbinary_type_string32: + case igbinary_type_string64: + tmp_str = igbinary_unserialize_chararray(igsd, t, 0); + if (UNEXPECTED(tmp_str == NULL)) { + return 1; + } + ZVAL_STR(z, tmp_str); + break; + case igbinary_type_long8p: + /* Manually inline igbinary_unserialize_long() for values from 0 to 255, because they're the most common among integers in many applications. */ + if (IGB_NEEDS_MORE_DATA(igsd, 1)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + return 1; + } + + ZVAL_LONG(z, igbinary_unserialize8(igsd)); + break; + case igbinary_type_long16p: + /* Manually inline igbinary_unserialize_long() for values from 0 to 255, because they're the most common among integers in many applications. */ + if (IGB_NEEDS_MORE_DATA(igsd, 2)) { + zend_error(E_WARNING, "igbinary_unserialize_long: end-of-data"); + return 1; + } + + ZVAL_LONG(z, igbinary_unserialize16(igsd)); + break; + case igbinary_type_long8n: + case igbinary_type_long16n: + case igbinary_type_long32p: + case igbinary_type_long32n: + case igbinary_type_long64p: + case igbinary_type_long64n: + if (UNEXPECTED(igbinary_unserialize_long(igsd, t, &tmp_long))) { + return 1; + } + ZVAL_LONG(z, tmp_long); + break; + case igbinary_type_null: + ZVAL_NULL(z); + break; + case igbinary_type_bool_false: + ZVAL_BOOL(z, 0); + break; + case igbinary_type_bool_true: + ZVAL_BOOL(z, 1); + break; + case igbinary_type_double: + if (UNEXPECTED(igbinary_unserialize_double(igsd, &tmp_double))) { + return 1; + } + ZVAL_DOUBLE(z, tmp_double); + break; + default: + zend_error(E_WARNING, "igbinary_unserialize_zval: unknown type '%02x', position %zu", t, (size_t)IGB_BUFFER_OFFSET(igsd)); + return 1; + } + + return 0; +} +/* }}} */ + +/* + * Local variables: + * tab-width: 2 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/src/php7/igbinary.h b/src/php7/igbinary.h new file mode 100644 index 00000000..32eb76cd --- /dev/null +++ b/src/php7/igbinary.h @@ -0,0 +1,95 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + +----------------------------------------------------------------------+ + | Author: Oleg Grenrus | + | See CREDITS for contributors | + +----------------------------------------------------------------------+ +*/ + +#ifndef IGBINARY_H +#define IGBINARY_H +#include + +/* Forward declarations. */ +struct zval; + +/* Constants and constant macros */ +/** Binary protocol version of igbinary. */ +#define IGBINARY_FORMAT_VERSION 0x00000002 + +#define PHP_IGBINARY_VERSION "3.2.17RC1" + +/* Macros */ + +#ifdef PHP_WIN32 +# if defined(IGBINARY_EXPORTS) || (!defined(COMPILE_DL_IGBINARY)) +# define IGBINARY_API __declspec(dllexport) +# elif defined(COMPILE_DL_IGBINARY) +# define IGBINARY_API __declspec(dllimport) +# else +# define IGBINARY_API /* nothing special */ +# endif +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define IGBINARY_API __attribute__ ((visibility("default"))) +#else +# define IGBINARY_API /* nothing special */ +#endif + +/** Struct that contains pointers to memory allocation and deallocation functions. + * @see igbinary_serialize_data + */ +struct igbinary_memory_manager { + void *(*alloc)(size_t size, void *context); + void *(*realloc)(void *ptr, size_t new_size, void *context); + void (*free)(void *ptr, void *context); + void *context; +}; + +/** Serialize zval. + * Return buffer is allocated by this function with emalloc. + * @param[out] ret Return buffer + * @param[out] ret_len Size of return buffer + * @param[in] z Variable to be serialized + * @return 0 on success, 1 elsewhere. + */ +IGBINARY_API int igbinary_serialize(uint8_t **ret, size_t *ret_len, zval *z); + +/** Serialize zval. + * Return buffer is allocated by this function with emalloc. + * @param[out] ret Return buffer + * @param[out] ret_len Size of return buffer + * @param[in] z Variable to be serialized + * @param[in] memory_manager Pointer to the structure that contains memory allocation functions. + * @return 0 on success, 1 elsewhere. + */ +IGBINARY_API int igbinary_serialize_ex(uint8_t **ret, size_t *ret_len, zval *z, struct igbinary_memory_manager *memory_manager); + +/** Unserialize to zval. + * @param[in] buf Buffer with serialized data. + * @param[in] buf_len Buffer length. + * @param[out] z Unserialized zval + * @return 0 on success, 1 elsewhere. + */ +IGBINARY_API int igbinary_unserialize(const uint8_t *buf, size_t buf_len, zval *z); + +static zend_always_inline int _igbinary_has_valid_header(const uint8_t *buf, size_t buf_len) { + if (buf_len < 5) { + /* Must have 4 header bytes and at least one byte of data */ + return 0; + } + /* Unserialize 32bit value the same way on big-endian and little-endian architectures. + * This compiles to a load+optional bswap when compiler optimizations are enabled. */ + const uint32_t ret = + ((uint32_t)(buf[0]) << 24) | + ((uint32_t)(buf[1]) << 16) | + ((uint32_t)(buf[2]) << 8) | + ((uint32_t)(buf[3])); + return ret == 1 || ret == 2; +} +/** This is defined as a macro and a static C function + * to allow callers to use the macro from newer igbinary versions even with older igbinary installations. */ +#define igbinary_has_valid_header(buf, buf_len) _igbinary_has_valid_header((buf), (buf_len)) + + +#endif /* IGBINARY_H */ diff --git a/src/php7/igbinary_bswap.h b/src/php7/igbinary_bswap.h new file mode 100644 index 00000000..56df5dd8 --- /dev/null +++ b/src/php7/igbinary_bswap.h @@ -0,0 +1,71 @@ +#ifndef IGBINARY_BSWAP +#define IGBINARY_BSWAP +#ifdef _MSC_VER + +// bswap compiler checks copied from https://github.com/google/cityhash/blob/8af9b8c2b889d80c22d6bc26ba0df1afb79a30db/src/city.cc#L50 +// The original code for detecting bswap had the MIT License below. +// +// Copyright (c) 2011 Google, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) + +// Mac OS X / Darwin features +#include +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#elif defined(__sun) || defined(sun) + +#include +#define bswap_32(x) BSWAP_32(x) +#define bswap_64(x) BSWAP_64(x) + +#elif defined(__FreeBSD__) + +#include +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) + +#elif defined(__OpenBSD__) + +#include +#define bswap_32(x) swap32(x) +#define bswap_64(x) swap64(x) + +#elif defined(__NetBSD__) + +#include +#include +#if defined(__BSWAP_RENAME) && !defined(__bswap_32) +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) +#endif + +#else + +#include + +#endif +#endif diff --git a/src/php7/igbinary_macros.h b/src/php7/igbinary_macros.h new file mode 100644 index 00000000..7d7fd5e5 --- /dev/null +++ b/src/php7/igbinary_macros.h @@ -0,0 +1,18 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + +----------------------------------------------------------------------+ + | See CREDITS for contributors | + +----------------------------------------------------------------------+ +*/ + +// If a macro is needed by *only* igbinary, put it in this file. +#ifndef PHP_IGBINARY_MACROS_H +#define PHP_IGBINARY_MACROS_H + +/** Backport macros from php 7.3 */ +#ifndef GC_ADD_FLAGS +#define GC_ADD_FLAGS(obj, flag) GC_FLAGS(obj) |= flag +#endif + +#endif diff --git a/src/php7/igbinary_zend_hash.h b/src/php7/igbinary_zend_hash.h new file mode 100644 index 00000000..63f9abb8 --- /dev/null +++ b/src/php7/igbinary_zend_hash.h @@ -0,0 +1,348 @@ +#ifndef IGBINARY_ZEND_HASH +#define IGBINARY_ZEND_HASH +/* + +----------------------------------------------------------------------+ + | igbinary optimizations for find_or_insert adapted from Zend Engine. | + | Adaptations written by Tyson Andre for igbinary. | + | original license of php-src/Zend/zend_hash.c is below. | + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2018 Zend Technologies Ltd. (http://www.zend.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.00 of the Zend license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.zend.com/license/2_00.txt. | + | If you did not receive a copy of the Zend license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@zend.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Andi Gutmans | + | Zeev Suraski | + | Dmitry Stogov | + +----------------------------------------------------------------------+ +*/ + +/* See https://nikic.github.io/2014/12/22/PHPs-new-hashtable-implementation.html for an overview of PHP's hash table information */ + +#if PHP_VERSION_ID < 70200 || PHP_VERSION_ID >= 80100 || IGBINARY_FORCE_REFERENCE_HASH_TABLE_FUNCTIONS +/* {{{ igbinary_zend_hash_add_or_find(HashTable *ht, zend_string *key) unoptimized reference implementation. */ +/* This will either return the pointer to the existing value, or add a brand new entry to the hash table with a PHP IS_NULL value. */ +static zend_always_inline zval *igbinary_zend_hash_add_or_find(HashTable *ht, zend_string *key) { + zval *vp; + zval val; + if (UNEXPECTED((vp = zend_hash_find(ht, key)) != NULL)) { + return vp; + } + ZVAL_NULL(&val); + return zend_hash_add_new(ht, key, &val); +} +/* }}} */ +/* {{{ igbinary_zend_hash_add_or_find(HashTable *ht, zend_long key) unoptimized reference implementation. */ +/* This will either return the pointer to the existing value, or add a brand new entry to the hash table with a PHP IS_NULL value. */ +static zend_always_inline zval *igbinary_zend_hash_index_add_or_find(HashTable *ht, zend_long key) { + zval *vp; + zval val; + if (UNEXPECTED((vp = zend_hash_index_find(ht, key)) != NULL)) { + return vp; + } + ZVAL_NULL(&val); + return zend_hash_index_add_new(ht, key, &val); +} +/* }}} */ + +/* This is either too old to bother implementing specializations for, or too new for the HashTable implementation to be finalized and tested */ + +#else +/* PHP 7.2 to 8.0 */ + +#if PHP_VERSION_ID < 70300 +# define IS_ARRAY_PERSISTENT HASH_FLAG_PERSISTENT +# define zend_hash_real_init_mixed(ht) zend_hash_real_init((ht), 0) +# define zend_hash_real_init_packed(ht) zend_hash_real_init((ht), 1) +# define HT_SIZE_TO_MASK(size) (-(size)) +# define HT_FLAGS(ht) ((ht)->u.flags) +static zend_always_inline zend_bool zend_string_equal_content(zend_string *s1, zend_string *s2) +{ + return ZSTR_LEN(s1) == ZSTR_LEN(s2) && !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); +} +#endif /* PHP_VERSION_ID < 70300 */ + +/* {{{ igbinary_zend_hash_find_bucket(const HashTable *ht, zend_string *key, zend_bool known_hash) */ +static zend_always_inline Bucket *igbinary_zend_hash_find_bucket(const HashTable *ht, zend_string *key, zend_bool known_hash) +{ + zend_ulong h; + uint32_t nIndex; + uint32_t idx; + Bucket *p, *arData; + + if (known_hash) { + h = ZSTR_H(key); + } else { + h = zend_string_hash_val(key); + } + arData = ht->arData; + nIndex = h | ht->nTableMask; + idx = HT_HASH_EX(arData, nIndex); + + if (UNEXPECTED(idx == HT_INVALID_IDX)) { + return NULL; + } + p = HT_HASH_TO_BUCKET_EX(arData, idx); + if (p->key == key) { /* check for the same interned string */ + return p; + } + + while (1) { + if (p->h == ZSTR_H(key) && + EXPECTED(p->key) && + zend_string_equal_content(p->key, key)) { + return p; + } + idx = Z_NEXT(p->val); + if (idx == HT_INVALID_IDX) { + return NULL; + } + p = HT_HASH_TO_BUCKET_EX(arData, idx); + if (p->key == key) { /* check for the same interned string */ + return p; + } + } +} +/* }}} */ +/* {{{ void igbinary_zend_hash_do_resize(HashTable *ht) */ +static void ZEND_FASTCALL igbinary_zend_hash_do_resize(HashTable *ht) +{ + + // IS_CONSISTENT(ht); + // HT_ASSERT_RC1(ht); + + if (ht->nNumUsed > ht->nNumOfElements + (ht->nNumOfElements >> 5)) { /* additional term is there to amortize the cost of compaction */ + zend_hash_rehash(ht); + } else if (ht->nTableSize < HT_MAX_SIZE) { /* Let's double the table size */ + void *new_data, *old_data = HT_GET_DATA_ADDR(ht); + uint32_t nSize = ht->nTableSize + ht->nTableSize; + Bucket *old_buckets = ht->arData; + + ht->nTableSize = nSize; +#if PHP_VERSION_ID >= 70300 + new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); +#else + new_data = pemalloc(HT_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); +#endif + ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); + HT_SET_DATA_ADDR(ht, new_data); + memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); + pefree(old_data, GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); + zend_hash_rehash(ht); + } else { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%u * %zu + %zu)", ht->nTableSize * 2, sizeof(Bucket) + sizeof(uint32_t), sizeof(Bucket)); + } +} +/* }}} */ +static void ZEND_FASTCALL zend_hash_packed_grow(HashTable *ht) /* {{{ */ +{ + // HT_ASSERT_RC1(ht); + if (ht->nTableSize >= HT_MAX_SIZE) { + zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%u * %zu + %zu)", ht->nTableSize * 2, sizeof(Bucket), sizeof(Bucket)); + } + ht->nTableSize += ht->nTableSize; + HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); +} +/* }}} */ +static zend_always_inline Bucket *zend_hash_index_find_bucket(const HashTable *ht, zend_ulong h) /* {{{ */ +{ + uint32_t nIndex; + uint32_t idx; + Bucket *p, *arData; + + arData = ht->arData; + nIndex = h | ht->nTableMask; + idx = HT_HASH_EX(arData, nIndex); + while (idx != HT_INVALID_IDX) { + ZEND_ASSERT(idx < HT_IDX_TO_HASH(ht->nTableSize)); + p = HT_HASH_TO_BUCKET_EX(arData, idx); + if (p->h == h && !p->key) { + return p; + } + idx = Z_NEXT(p->val); + } + return NULL; +} /* }}} */ + +/* Methods used by igbinary.c */ +/* {{{ zval *igbinary_zend_hash_add_or_find(HashTable *ht, zend_string *key) */ +/* Source: zend_hash_add_or_update_i with adaptions */ +static zend_always_inline zval *igbinary_zend_hash_add_or_find(HashTable *ht, zend_string *key) +{ + zend_ulong h; + uint32_t nIndex; + uint32_t idx; + Bucket *p, *arData; + + // IS_CONSISTENT(ht); + // HT_ASSERT_RC1(ht); + +#if PHP_VERSION_ID >= 70400 + if (UNEXPECTED(HT_FLAGS(ht) & (HASH_FLAG_UNINITIALIZED|HASH_FLAG_PACKED))) +#else + if (UNEXPECTED(((HT_FLAGS(ht) ^ HASH_FLAG_INITIALIZED) & (HASH_FLAG_INITIALIZED|HASH_FLAG_PACKED)))) +#endif + { +#if PHP_VERSION_ID >= 70400 + if (EXPECTED(HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED)) +#else + if (EXPECTED(!(HT_FLAGS(ht) & HASH_FLAG_INITIALIZED))) +#endif + { + zend_hash_real_init_mixed(ht); + if (!ZSTR_IS_INTERNED(key)) { + zend_string_addref(key); + HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS; + zend_string_hash_val(key); + } + goto add_to_hash; + } else { + zend_hash_packed_to_hash(ht); + if (!ZSTR_IS_INTERNED(key)) { + zend_string_addref(key); + HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS; + zend_string_hash_val(key); + } + } + } else { + p = igbinary_zend_hash_find_bucket(ht, key, 0); + + if (p) { + return &p->val; + } + if (!ZSTR_IS_INTERNED(key)) { + zend_string_addref(key); + HT_FLAGS(ht) &= ~HASH_FLAG_STATIC_KEYS; + } + } + + /* If the Hash table is full, resize it */ + if ((ht)->nNumUsed >= (ht)->nTableSize) { + igbinary_zend_hash_do_resize(ht); + } + +add_to_hash: + idx = ht->nNumUsed++; + ht->nNumOfElements++; +#if PHP_VERSION_ID < 70300 + if (ht->nInternalPointer == HT_INVALID_IDX) { + ht->nInternalPointer = idx; + } + zend_hash_iterators_update(ht, HT_INVALID_IDX, idx); +#endif + arData = ht->arData; + p = arData + idx; + p->key = key; + p->h = h = ZSTR_H(key); + nIndex = h | ht->nTableMask; + Z_NEXT(p->val) = HT_HASH_EX(arData, nIndex); + HT_HASH_EX(arData, nIndex) = HT_IDX_TO_HASH(idx); + ZVAL_NULL(&p->val); + + return &p->val; +} +/* }}} */ +static zend_always_inline zval *igbinary_zend_hash_index_add_or_find(HashTable *ht, zend_ulong h) /* {{{ */ +{ + uint32_t nIndex; + uint32_t idx; + Bucket *p; + + // IS_CONSISTENT(ht); + // HT_ASSERT_RC1(ht); + + if (HT_FLAGS(ht) & HASH_FLAG_PACKED) { + if (h < ht->nNumUsed) { + p = ht->arData + h; + if (Z_TYPE(p->val) != IS_UNDEF) { +replace: + return &p->val; + } else { /* we have to keep the order :( */ + goto convert_to_hash; + } + } else if (EXPECTED(h < ht->nTableSize)) { +add_to_packed: + p = ht->arData + h; + /* incremental initialization of empty Buckets */ + if (h > ht->nNumUsed) { + Bucket *q = ht->arData + ht->nNumUsed; + while (q != p) { + ZVAL_UNDEF(&q->val); + q++; + } + } + ht->nNextFreeElement = ht->nNumUsed = h + 1; + goto add; + } else if ((h >> 1) < ht->nTableSize && + (ht->nTableSize >> 1) < ht->nNumOfElements) { + zend_hash_packed_grow(ht); + goto add_to_packed; + } else { + if (ht->nNumUsed >= ht->nTableSize) { + ht->nTableSize += ht->nTableSize; + } +convert_to_hash: + zend_hash_packed_to_hash(ht); + } +#if PHP_VERSION_ID >= 70400 + } else if (HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED) { +#else + } else if (!(HT_FLAGS(ht) & HASH_FLAG_INITIALIZED)) { +#endif + if (h < ht->nTableSize) { + zend_hash_real_init_packed(ht); + goto add_to_packed; + } + zend_hash_real_init_mixed(ht); + } else { + p = zend_hash_index_find_bucket(ht, h); + if (p) { + goto replace; + } + /* If the Hash table is full, resize it */ + if ((ht)->nNumUsed >= (ht)->nTableSize) { + igbinary_zend_hash_do_resize(ht); + } + } + + idx = ht->nNumUsed++; + nIndex = h | ht->nTableMask; + p = ht->arData + idx; + Z_NEXT(p->val) = HT_HASH(ht, nIndex); + HT_HASH(ht, nIndex) = HT_IDX_TO_HASH(idx); + if ((zend_long)h >= ht->nNextFreeElement) { + ht->nNextFreeElement = (zend_long)h < ZEND_LONG_MAX ? h + 1 : ZEND_LONG_MAX; + } +add: + ht->nNumOfElements++; +#if PHP_VERSION_ID < 70300 + if (ht->nInternalPointer == HT_INVALID_IDX) { + ht->nInternalPointer = ht->nNumUsed - 1; + } + zend_hash_iterators_update(ht, HT_INVALID_IDX, ht->nNumUsed - 1); +#endif + p->h = h; + p->key = NULL; + ZVAL_NULL(&p->val); + + return &p->val; +} +/* }}} */ + +#endif /* PHP 7.2 to 8.0 */ +/* + * Local variables: + * tab-width: 2 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ +#endif /* IGBINARY_ZEND_HASH */ diff --git a/src/php7/php_igbinary.h b/src/php7/php_igbinary.h new file mode 100644 index 00000000..d64c3702 --- /dev/null +++ b/src/php7/php_igbinary.h @@ -0,0 +1,81 @@ +/* + +----------------------------------------------------------------------+ + | See COPYING file for further copyright information | + +----------------------------------------------------------------------+ + | Author: Oleg Grenrus | + | See CREDITS for contributors | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_IGBINARY_H +#define PHP_IGBINARY_H +// Note: php_igbinary.h should contain publicly exposed variables, functions, and macros of igbinary. +// If a macro is needed by *only* igbinary, put it in igbinary_macros.h + +#include "php.h" + +/** Module entry of igbinary. */ +extern zend_module_entry igbinary_module_entry; +#define phpext_igbinary_ptr &igbinary_module_entry + +#ifdef PHP_WIN32 +#define PHP_IGBINARY_API __declspec(dllexport) +#else +#define PHP_IGBINARY_API +#endif + +ZEND_BEGIN_MODULE_GLOBALS(igbinary) + zend_bool compact_strings; +ZEND_END_MODULE_GLOBALS(igbinary) + +#ifdef ZTS +#include "TSRM.h" +#endif + +#if PHP_VERSION_ID < 70200 +#include "ext/standard/php_smart_string.h" +#else +#include "Zend/zend_smart_string.h" +#endif + +/** Module init function. */ +PHP_MINIT_FUNCTION(igbinary); + +/** Module shutdown function. */ +PHP_MSHUTDOWN_FUNCTION(igbinary); + +/** Request init function. */ +PHP_RINIT_FUNCTION(igbinary); + +/** Request shutdown function. */ +PHP_RSHUTDOWN_FUNCTION(igbinary); + +/** Module info function for phpinfo(). */ +PHP_MINFO_FUNCTION(igbinary); + +/** string igbinary_serialize(mixed value). + * Returns the binary serialized value. + */ +PHP_FUNCTION(igbinary_serialize); + +/** mixed igbinary_unserialize(string data). + * Unserializes the given inputstring (value). + */ +PHP_FUNCTION(igbinary_unserialize); + +#ifdef ZTS +#define IGBINARY_G(v) TSRMG(igbinary_globals_id, zend_igbinary_globals *, v) +#else +#define IGBINARY_G(v) (igbinary_globals.v) +#endif + +#endif /* PHP_IGBINARY_H */ + +/* + * Local variables: + * tab-width: 2 + * c-basic-offset: 0 + * End: + * vim600: noet sw=2 ts=2 fdm=marker + * vim<600: noet sw=2 ts=2 + */ diff --git a/tests/__serialize_001.phpt b/tests/__serialize_001.phpt new file mode 100644 index 00000000..29a86566 --- /dev/null +++ b/tests/__serialize_001.phpt @@ -0,0 +1,34 @@ +--TEST-- +__serialize() mechanism (001): Basics +--SKIPIF-- + +--FILE-- + $this->prop, 42 => $this->prop2]; + } + public function __unserialize(array $data) { + $this->prop = $data["value"]; + $this->prop2 = $data[42]; + } +} + +$test = new Test; +$test->prop = "foobar"; +$test->prop2 = "barfoo"; +var_dump(bin2hex($s = igbinary_serialize($test))); +var_dump(igbinary_unserialize($s)); + +?> +--EXPECT-- +string(74) "000000021704546573741402110576616c75651106666f6f626172062a1106626172666f6f" +object(Test)#2 (2) { + ["prop"]=> + string(6) "foobar" + ["prop2"]=> + string(6) "barfoo" +} diff --git a/tests/__serialize_002.phpt b/tests/__serialize_002.phpt new file mode 100644 index 00000000..0800bde6 --- /dev/null +++ b/tests/__serialize_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +__serialize() mechanism (002): TypeError on invalid return type +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +?> +--EXPECT-- +Test::__serialize() must return an array diff --git a/tests/__serialize_003.phpt b/tests/__serialize_003.phpt new file mode 100644 index 00000000..7708de79 --- /dev/null +++ b/tests/__serialize_003.phpt @@ -0,0 +1,69 @@ +--TEST-- +__serialize() mechanism (003): Interoperability of different serialization mechanisms +--SKIPIF-- + +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- + "value"]; + } + + public function __unserialize(array $data) { + echo "__unserialize() called\n"; + var_dump($data); + } + + public function serialize() { + echo "serialize() called\n"; + return "payload"; + } + + public function unserialize($payload) { + echo "unserialize() called\n"; + var_dump($payload); + } +} + +$test = new Test; +var_dump(bin2hex($s = igbinary_serialize($test))); +var_dump(igbinary_unserialize($s)); + +var_dump(igbinary_unserialize(hex2bin('000000021704546573741d077061796c6f6164'))); + +?> +--EXPECT-- +__serialize() called +string(48) "00000002170454657374140111036b6579110576616c7565" +__unserialize() called +array(1) { + ["key"]=> + string(5) "value" +} +object(Test)#2 (0) { +} +unserialize() called +string(7) "payload" +object(Test)#2 (0) { +} diff --git a/tests/__serialize_004.phpt b/tests/__serialize_004.phpt new file mode 100644 index 00000000..b188c8d5 --- /dev/null +++ b/tests/__serialize_004.phpt @@ -0,0 +1,137 @@ +--TEST-- +__serialize() mechanism (004): Delayed __unserialize() calls +--SKIPIF-- + +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +data = $data; + } + public function __wakeup() { + echo "__wakeup() called\n"; + var_dump($this->data); + $this->woken_up = true; + } +} + +#[AllowDynamicProperties] +class Unserialize { + public $data; + public function __construct(array $data) { + $this->data = $data; + } + public function __serialize() { + return $this->data; + } + public function __unserialize(array $data) { + $this->data = $data; + echo "__unserialize() called\n"; + var_dump($this->data); + $this->unserialized = true; + } +} + +$obj = new Wakeup([new Unserialize([new Wakeup([new Unserialize([])])])]); +var_dump(bin2hex($s = igbinary_serialize($obj))); +var_dump(igbinary_unserialize($s)); + +?> +--EXPECT-- +string(110) "00000002170657616b657570140111046461746114010600170b556e73657269616c697a65140106001a0014010e01140106001a021400" +__unserialize() called +array(0) { +} +__wakeup() called +array(1) { + [0]=> + object(Unserialize)#8 (2) { + ["data"]=> + array(0) { + } + ["unserialized"]=> + bool(true) + } +} +__unserialize() called +array(1) { + [0]=> + object(Wakeup)#7 (2) { + ["data"]=> + array(1) { + [0]=> + object(Unserialize)#8 (2) { + ["data"]=> + array(0) { + } + ["unserialized"]=> + bool(true) + } + } + ["woken_up"]=> + bool(true) + } +} +__wakeup() called +array(1) { + [0]=> + object(Unserialize)#6 (2) { + ["data"]=> + array(1) { + [0]=> + object(Wakeup)#7 (2) { + ["data"]=> + array(1) { + [0]=> + object(Unserialize)#8 (2) { + ["data"]=> + array(0) { + } + ["unserialized"]=> + bool(true) + } + } + ["woken_up"]=> + bool(true) + } + } + ["unserialized"]=> + bool(true) + } +} +object(Wakeup)#5 (2) { + ["data"]=> + array(1) { + [0]=> + object(Unserialize)#6 (2) { + ["data"]=> + array(1) { + [0]=> + object(Wakeup)#7 (2) { + ["data"]=> + array(1) { + [0]=> + object(Unserialize)#8 (2) { + ["data"]=> + array(0) { + } + ["unserialized"]=> + bool(true) + } + } + ["woken_up"]=> + bool(true) + } + } + ["unserialized"]=> + bool(true) + } + } + ["woken_up"]=> + bool(true) +} diff --git a/tests/__serialize_005.phpt b/tests/__serialize_005.phpt new file mode 100644 index 00000000..0f079eb9 --- /dev/null +++ b/tests/__serialize_005.phpt @@ -0,0 +1,60 @@ +--TEST-- +__serialize() mechanism (005): parent::__unserialize() is safe +--SKIPIF-- + +--FILE-- +data = $data; + } + public function __serialize() { + return $this->data; + } + public function __unserialize(array $data) { + $this->data = $data; + } +} + +class B extends A { + public function __construct(array $data, array $data2) { + parent::__construct($data); + $this->data2 = $data2; + } + public function __serialize() { + return [$this->data2, parent::__serialize()]; + } + public function __unserialize(array $payload) { + [$data2, $data] = $payload; + parent::__unserialize($data); + $this->data2 = $data2; + } +} + +$common = new stdClass; +$obj = new B([$common], [$common]); +var_dump(bin2hex($s = igbinary_serialize($obj))); +var_dump(igbinary_unserialize($s)); + +?> +--EXPECT-- +string(70) "0000000217014214020600140106001708737464436c61737314000601140106002202" +object(B)#3 (2) { + ["data":"A":private]=> + array(1) { + [0]=> + object(stdClass)#4 (0) { + } + } + ["data2":protected]=> + array(1) { + [0]=> + object(stdClass)#4 (0) { + } + } +} diff --git a/tests/__serialize_006.phpt b/tests/__serialize_006.phpt new file mode 100644 index 00000000..b627edbc --- /dev/null +++ b/tests/__serialize_006.phpt @@ -0,0 +1,34 @@ +--TEST-- +__serialize() mechanism (006): DateTime +--SKIPIF-- + +--FILE-- + +--EXPECT-- +string(164) "0000000217084461746554696d651403110464617465111a323031392d31322d30382031323a33343a30302e303030303030110d74696d657a6f6e655f747970650603110874696d657a6f6e651103555443" +object(DateTime)#2 (3) { + ["date"]=> + string(26) "2019-12-08 12:34:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} +string(184) "0000000217084461746554696d651403110464617465111a323031392d31322d30382031323a33343a30302e303030303030110d74696d657a6f6e655f747970650603110874696d657a6f6e65110d506163696669632f4e61757275" +object(DateTime)#1 (3) { + ["date"]=> + string(26) "2019-12-08 12:34:00.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Pacific/Nauru" +} diff --git a/tests/__serialize_007.phpt b/tests/__serialize_007.phpt new file mode 100644 index 00000000..96a8f30e --- /dev/null +++ b/tests/__serialize_007.phpt @@ -0,0 +1,50 @@ +--TEST-- +__serialize() mechanism (007): handle __unserialize throwing +--SKIPIF-- + +--FILE-- +prop, $this->prop2]; + } + public function __unserialize(array $data) { + $this->prop = $data[0]; + $this->prop2 = $data[1]; + throw new RuntimeException($this->prop); + } + + public function __destruct() { + // should not be called + echo "Called destruct prop=$this->prop\n"; + } +} + +$test = new Test; +$test->prop = 'XX'; +$test->prop2 = [$test]; +// 00000002 - igbinary header +// 17 04 54657374 - object of class with name "Test" +// 14 02 - 2 properties +// 06 00 - uint8(0) => +// 11 02 5858 - 'XX' +// 06 01 - uint8(1) => +// 14 01 - array(size=2) +// 06 00 - uint8(0) => +// 22 00 - igbinary_type_objref8 (pointer to the first referenceable item, i.e. the instance of "Test" +var_dump(bin2hex($s = igbinary_serialize($test))); +try { + var_dump(igbinary_unserialize($s)); +} catch (RuntimeException $e) { + echo "Caught: {$e->getMessage()}\n"; +} +$test->prop = 'not from igbinary_unserialize'; + +?> +--EXPECT-- +string(52) "0000000217045465737414020600110258580601140106002200" +Caught: XX +Called destruct prop=not from igbinary_unserialize diff --git a/tests/__serialize_008.phpt b/tests/__serialize_008.phpt new file mode 100644 index 00000000..7adea361 --- /dev/null +++ b/tests/__serialize_008.phpt @@ -0,0 +1,59 @@ +--TEST-- +__serialize() mechanism (007): handle __destruct of returned data +--SKIPIF-- + += 90000) { echo "skip requires php < 9.0 when testing that the deprecation has no impact on igbinary functionality\n"; } ?> +--FILE-- += 80200) { require_once __DIR__ . '/php82_suppress_dynamic_properties_warning.inc'; } + +class DestructorThrows { + public function __construct(string $val) { + $this->val = $val; + } + public function __destruct() { + echo "DestructorThrows called val=$this->val\n"; + throw new RuntimeException($this->val); + } +} + +class Xyz { + public $Xyz; + public function __serialize() { + return [new DestructorThrows($this->Xyz)]; + } + public function __unserialize(array $data) { + $this->Xyz = $data[0]; + } + + public function __destruct() { + // should not be called + echo "Called destruct prop=$this->Xyz\n"; + } +} + +$test = new Xyz; +$test->Xyz = 'Xyz'; + +try { + var_dump(bin2hex($s = igbinary_serialize($test))); +} catch (RuntimeException $e) { + echo "message={$e->getMessage()}\n"; +} +unset($test); + +$test = new Xyz; +$test->Xyz = ''; +try { + var_dump(bin2hex($s = igbinary_serialize($test))); +} catch (RuntimeException $e) { + echo "message={$e->getMessage()}\n"; +} +?> +--EXPECT-- +DestructorThrows called val=Xyz +message=Xyz +DestructorThrows called val= +Called destruct prop=Xyz +message= +Called destruct prop= diff --git a/tests/__serialize_009.phpt b/tests/__serialize_009.phpt new file mode 100644 index 00000000..c2720714 --- /dev/null +++ b/tests/__serialize_009.phpt @@ -0,0 +1,104 @@ +--TEST-- +__serialize() mechanism (009): Object/reference ids should be the same whether or not __serialize is used. +--SKIPIF-- + +--FILE-- +value = $value; + } +} + +class Test { + public $prop; + public function __construct($value) { + $this->prop = $value; + } + public function __serialize() { + return ["value" => $this->prop]; + } + public function __unserialize(array $data) { + $this->prop = $data["value"]; + } +} +$vest = new Vest('first'); +$vest2 = new Vest(null); +$vest3 = new Vest($vest2); +$sv = igbinary_serialize([$vest, $vest2, $vest3, $vest, $vest2]); + +$test = new Test('first'); +$test2 = new Test(null); +$test3 = new Test($test2); +$s = igbinary_serialize([$test, $test2, $test3, $test, $test2]); +// The only difference in the serialization should be the first byte of the only occurrence of the class name. +var_dump(bin2hex($sv)); +var_dump(bin2hex($s)); +var_dump(igbinary_unserialize($s)); +var_dump(igbinary_unserialize($sv)); + +?> +--EXPECT-- +string(114) "00000002140506001704566573741401110576616c75651105666972737406011a0014010e010006021a0014010e0122020603220106042202" +string(114) "00000002140506001704546573741401110576616c75651105666972737406011a0014010e010006021a0014010e0122020603220106042202" +array(5) { + [0]=> + object(Test)#7 (1) { + ["prop"]=> + string(5) "first" + } + [1]=> + object(Test)#8 (1) { + ["prop"]=> + NULL + } + [2]=> + object(Test)#9 (1) { + ["prop"]=> + object(Test)#8 (1) { + ["prop"]=> + NULL + } + } + [3]=> + object(Test)#7 (1) { + ["prop"]=> + string(5) "first" + } + [4]=> + object(Test)#8 (1) { + ["prop"]=> + NULL + } +} +array(5) { + [0]=> + object(Vest)#8 (1) { + ["value"]=> + string(5) "first" + } + [1]=> + object(Vest)#7 (1) { + ["value"]=> + NULL + } + [2]=> + object(Vest)#9 (1) { + ["value"]=> + object(Vest)#7 (1) { + ["value"]=> + NULL + } + } + [3]=> + object(Vest)#8 (1) { + ["value"]=> + string(5) "first" + } + [4]=> + object(Vest)#7 (1) { + ["value"]=> + NULL + } +} diff --git a/tests/__serialize_010.phpt b/tests/__serialize_010.phpt new file mode 100644 index 00000000..8c1e2732 --- /dev/null +++ b/tests/__serialize_010.phpt @@ -0,0 +1,63 @@ +--TEST-- +__serialize() mechanism (010): handle references in array returned by __serialize +--SKIPIF-- + +--FILE-- +prop, $this->prop2]; + } + public function __unserialize(array $data) { + $this->prop = $data[0]; + $this->prop2 = $data[1]; + if (!$this->prop) { + throw new RuntimeException("Threw from __unserialize"); + } + } + + public function __destruct() { + // should not be called + echo "Called destruct\n"; + } +} + +$test = new Test; +$test->prop = &$test; +$test->prop2 = [&$test]; +var_dump(bin2hex($s = igbinary_serialize($test))); +var_dump(igbinary_unserialize($s)); +unset($test->prop); +$test->prop = false; +var_dump(bin2hex($s = igbinary_serialize($test))); +try { + igbinary_unserialize($s); +} catch (RuntimeException $e) { + echo "message={$e->getMessage()}\n"; + +} +echo "Calling gc_collect_cycles\n"; +gc_collect_cycles(); +echo "After call to gc_collect_cycles\n"; + +?> +--EXPECT-- +string(50) "00000002170454657374140206002200060114010600252200" +object(Test)#2 (2) { + ["prop"]=> + *RECURSION* + ["prop2"]=> + array(1) { + [0]=> + *RECURSION* + } +} +string(48) "000000021704546573741402060004060114010600252200" +message=Threw from __unserialize +Calling gc_collect_cycles +Called destruct +After call to gc_collect_cycles +Called destruct \ No newline at end of file diff --git a/tests/__serialize_011.phpt b/tests/__serialize_011.phpt new file mode 100644 index 00000000..de4e1479 --- /dev/null +++ b/tests/__serialize_011.phpt @@ -0,0 +1,124 @@ +--TEST-- +__unserializing deeply nested structures +--SKIPIF-- + +--FILE-- +childs]; + } + + public function __unserialize(array $data) + { + list($this->childs) = $data; + } +} + +function createTree ($width, $depth) { + $root = new Node(); + + $nextLevel = [$root]; + + for ($level=1; $level<$depth; $level++) { + $levelRoots = $nextLevel; + $nextLevel = []; + + while (count($levelRoots) > 0) { + $levelRoot = array_shift($levelRoots); + + for ($w = 0; $w < $width; $w++) { + $tester = new Node(); + + $levelRoot->childs[] = $tester; + + $nextLevel[] = $tester; + } + } + } + + return $root; +} + + +$width = 3; +ob_implicit_flush(); + +foreach (range(1, 8) as $depth) { + $tree = createTree($width, $depth); + + echo "Testcase tree $width x $depth".PHP_EOL; + + echo "> Serializing now".PHP_EOL; + $serialized = igbinary_serialize($tree); + + echo "> Unserializing now".PHP_EOL; + $tree = igbinary_unserialize($serialized); + + // Lets test whether all is ok! + $expectedSize = ($width**$depth - 1)/($width-1); + + $nodes = [$tree]; + $count = 0; + + while (count($nodes) > 0) { + $count++; + + $node = array_shift($nodes); + foreach ($node->childs as $node) { + $nodes[] = $node; + } + } + + echo "> Unserialized total node count was $count, expected $expectedSize: ".($expectedSize === $count ? 'CORRECT!' : 'INCORRECT'); + + echo PHP_EOL; + echo PHP_EOL; +} +?> +--EXPECT-- +Testcase tree 3 x 1 +> Serializing now +> Unserializing now +> Unserialized total node count was 1, expected 1: CORRECT! + +Testcase tree 3 x 2 +> Serializing now +> Unserializing now +> Unserialized total node count was 4, expected 4: CORRECT! + +Testcase tree 3 x 3 +> Serializing now +> Unserializing now +> Unserialized total node count was 13, expected 13: CORRECT! + +Testcase tree 3 x 4 +> Serializing now +> Unserializing now +> Unserialized total node count was 40, expected 40: CORRECT! + +Testcase tree 3 x 5 +> Serializing now +> Unserializing now +> Unserialized total node count was 121, expected 121: CORRECT! + +Testcase tree 3 x 6 +> Serializing now +> Unserializing now +> Unserialized total node count was 364, expected 364: CORRECT! + +Testcase tree 3 x 7 +> Serializing now +> Unserializing now +> Unserialized total node count was 1093, expected 1093: CORRECT! + +Testcase tree 3 x 8 +> Serializing now +> Unserializing now +> Unserialized total node count was 3280, expected 3280: CORRECT! diff --git a/tests/__serialize_012.phpt b/tests/__serialize_012.phpt new file mode 100644 index 00000000..b62613c9 --- /dev/null +++ b/tests/__serialize_012.phpt @@ -0,0 +1,55 @@ +--TEST-- +Test unserialization of classes derived from ArrayIterator +--SKIPIF-- + +--FILE-- + + object(Foo1)#3 (1) { + ["storage":"ArrayIterator":private]=> + array(0) { + } + } + [1]=> + object(Foo2)#4 (0) { + } +} +array(2) { + [0]=> + object(__PHP_Incomplete_Class)#4 (5) { + ["__PHP_Incomplete_Class_Name"]=> + string(4) "Bar1" + ["0"]=> + int(0) + ["1"]=> + array(0) { + } + ["2"]=> + array(0) { + } + ["3"]=> + NULL + } + [1]=> + object(__PHP_Incomplete_Class)#3 (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(4) "Bar2" + } +} diff --git a/tests/__serialize_013.phpt b/tests/__serialize_013.phpt new file mode 100644 index 00000000..47693b6b --- /dev/null +++ b/tests/__serialize_013.phpt @@ -0,0 +1,139 @@ +--TEST-- +__serialize() mechanism (013): Properties are still typed after unserialization +--SKIPIF-- += 80000) { echo "skip different error message format"; } +?> +--FILE-- +i = 1; +$t->s = 'other'; +$t->o = $t; +$t->std = (object)['key' => 'value']; +$t->a = [$t->std]; +var_dump($t); + +var_dump(bin2hex($s = igbinary_serialize($t))); +$t2 = igbinary_unserialize($s); +var_dump($t2); +try { + $t2->i = 'x'; +} catch (Error $e) { + echo "i: " . $e->getMessage() . "\n"; +} +$t2->s = null; +try { + $t2->s = false; +} catch (Error $e) { + echo "s: " . $e->getMessage() . "\n"; +} +$t2->s = 'other'; +try { + $t2->o = null; +} catch (Error $e) { + echo "o: " . $e->getMessage() . "\n"; +} +try { + $t2->a = null; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +try { + $t2->stdClass = $t; +} catch (Error $e) { + echo "stdClass: " . $e->getMessage() . "\n"; +} +try { + $t2->a = $t2; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +var_dump($t2); +--EXPECT-- +object(Test)#1 (5) { + ["i"]=> + int(1) + ["s"]=> + string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + array(1) { + [0]=> + object(stdClass)#2 (1) { + ["key"]=> + string(5) "value" + } + } + ["std"]=> + object(stdClass)#2 (1) { + ["key"]=> + string(5) "value" + } +} +string(142) "000000021704546573741406110169060111017311056f7468657211016f220000110161140106001708737464436c617373140111036b6579110576616c756511037374642202" +object(Test)#3 (5) { + ["i"]=> + int(1) + ["s"]=> + string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + array(1) { + [0]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } + } + ["std"]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } +} +i: Typed property Test::$i must be int, string used +s: Typed property Test::$s must be string or null, bool used +o: Typed property Test::$o must be object, null used +a: Typed property Test::$a must be array, null used +stdClass: Typed property Test::$stdClass must be an instance of stdClass, Test used +a: Typed property Test::$a must be array, Test used +object(Test)#3 (5) { + ["i"]=> + int(1) + ["s"]=> + string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + array(1) { + [0]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } + } + ["std"]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } +} diff --git a/tests/__serialize_013_php8.phpt b/tests/__serialize_013_php8.phpt new file mode 100644 index 00000000..36bae972 --- /dev/null +++ b/tests/__serialize_013_php8.phpt @@ -0,0 +1,139 @@ +--TEST-- +__serialize() mechanism (013): Properties are still typed after unserialization (php8) +--SKIPIF-- + +--FILE-- +i = 1; +$t->s = 'other'; +$t->o = $t; +$t->std = (object)['key' => 'value']; +$t->a = [$t->std]; +var_dump($t); + +var_dump(bin2hex($s = igbinary_serialize($t))); +$t2 = igbinary_unserialize($s); +var_dump($t2); +try { + $t2->i = 'x'; +} catch (Error $e) { + echo "i: " . $e->getMessage() . "\n"; +} +$t2->s = null; +try { + $t2->s = false; +} catch (Error $e) { + echo "s: " . str_replace('false', 'bool', $e->getMessage()) . "\n"; +} +$t2->s = 'other'; +try { + $t2->o = null; +} catch (Error $e) { + echo "o: " . $e->getMessage() . "\n"; +} +try { + $t2->a = null; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +try { + $t2->stdClass = $t; +} catch (Error $e) { + echo "stdClass: " . $e->getMessage() . "\n"; +} +try { + $t2->a = $t2; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +var_dump($t2); +?> +--EXPECT-- +object(Test)#1 (5) { + ["i"]=> + int(1) + ["s"]=> + string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + array(1) { + [0]=> + object(stdClass)#2 (1) { + ["key"]=> + string(5) "value" + } + } + ["std"]=> + object(stdClass)#2 (1) { + ["key"]=> + string(5) "value" + } +} +string(142) "000000021704546573741406110169060111017311056f7468657211016f220000110161140106001708737464436c617373140111036b6579110576616c756511037374642202" +object(Test)#3 (5) { + ["i"]=> + int(1) + ["s"]=> + string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + array(1) { + [0]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } + } + ["std"]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } +} +i: Cannot assign string to property Test::$i of type int +s: Cannot assign bool to property Test::$s of type ?string +o: Cannot assign null to property Test::$o of type object +a: Cannot assign null to property Test::$a of type array +stdClass: Cannot assign Test to property Test::$stdClass of type stdClass +a: Cannot assign Test to property Test::$a of type array +object(Test)#3 (5) { + ["i"]=> + int(1) + ["s"]=> + string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + array(1) { + [0]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } + } + ["std"]=> + object(stdClass)#4 (1) { + ["key"]=> + string(5) "value" + } +} diff --git a/tests/__serialize_014.phpt b/tests/__serialize_014.phpt new file mode 100644 index 00000000..221432e1 --- /dev/null +++ b/tests/__serialize_014.phpt @@ -0,0 +1,64 @@ +--TEST-- +__serialize() mechanism (014): Uninitialized properties can be serialized and unserialized +--SKIPIF-- += 80000) { echo "skip different error message format"; } +?> +--FILE-- +o = new stdClass(); +unset($m->o); +$m->s = 'other'; +unset($m->s); +$m->i = 42; +unset($m->i); +// Should have the same serialized representation. +var_dump($m); +var_dump(bin2hex($s = igbinary_serialize($m))); +try { + $m->i = 'i'; +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} +--EXPECT-- +object(MyClass)#1 (0) { + ["o"]=> + uninitialized(stdClass) + ["s"]=> + uninitialized(string) + ["i"]=> + uninitialized(?int) +} +string(36) "0000000217074d79436c6173731403000000" +object(MyClass)#2 (0) { + ["o"]=> + uninitialized(stdClass) + ["s"]=> + uninitialized(string) + ["i"]=> + uninitialized(?int) +} +object(MyClass)#2 (0) { + ["o"]=> + uninitialized(stdClass) + ["s"]=> + uninitialized(string) + ["i"]=> + uninitialized(?int) +} +string(36) "0000000217074d79436c6173731403000000" +Typed property MyClass::$i must be int or null, string used diff --git a/tests/__serialize_014_php8.phpt b/tests/__serialize_014_php8.phpt new file mode 100644 index 00000000..61dae5ea --- /dev/null +++ b/tests/__serialize_014_php8.phpt @@ -0,0 +1,61 @@ +--TEST-- +__serialize() mechanism (014): Uninitialized properties can be serialized and unserialized +--SKIPIF-- + +--FILE-- +o = new stdClass(); +unset($m->o); +$m->s = 'other'; +unset($m->s); +$m->i = 42; +unset($m->i); +// Should have the same serialized representation. +var_dump($m); +var_dump(bin2hex($s = igbinary_serialize($m))); +try { + $m->i = 'i'; +} catch (TypeError $e) { + echo $e->getMessage() . "\n"; +} +--EXPECT-- +object(MyClass)#1 (0) { + ["o"]=> + uninitialized(stdClass) + ["s"]=> + uninitialized(string) + ["i"]=> + uninitialized(?int) +} +string(36) "0000000217074d79436c6173731403000000" +object(MyClass)#2 (0) { + ["o"]=> + uninitialized(stdClass) + ["s"]=> + uninitialized(string) + ["i"]=> + uninitialized(?int) +} +object(MyClass)#2 (0) { + ["o"]=> + uninitialized(stdClass) + ["s"]=> + uninitialized(string) + ["i"]=> + uninitialized(?int) +} +string(36) "0000000217074d79436c6173731403000000" +Cannot assign string to property MyClass::$i of type ?int \ No newline at end of file diff --git a/tests/__serialize_015.phpt b/tests/__serialize_015.phpt new file mode 100644 index 00000000..dd4d58ef --- /dev/null +++ b/tests/__serialize_015.phpt @@ -0,0 +1,81 @@ +--TEST-- +__serialize() mechanism (015): Uninitialized properties from __sleep should throw when serializing +--SKIPIF-- + +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +$name = $value; + } +} +class SimplePrivate { + private ?int $i; + public function __sleep() { + return ['i']; + } + public function __set($name, $value) { + $this->$name = $value; + } +} +// 00000002 -- header +// 17 03 4d79436c617373 -- object of type "MyClass" +// 14 03 000000 -- with 3 uninitialized properties +$m = new OSI(); +function try_serialize_invalid($o) { + try { + var_dump(bin2hex($s = igbinary_serialize($o))); + } catch (Error $e) { + printf("Caught %s: %s\n", get_class($e), $e->getMessage()); + } +} +// These should throw whether or not the uninitialized property is nullable. +try_serialize_invalid(new OSI()); +try_serialize_invalid(new SimplePublic()); +try_serialize_invalid(new SimpleProtected()); +try_serialize_invalid(new SimplePrivate()); +$s = new SimplePublic(); +$s->i = null; +try_serialize_invalid($s); +$s = new SimpleProtected(); +$s->i = 0; +try_serialize_invalid($s); +$s = new SimplePrivate(); +$s->i = null; +try_serialize_invalid($s); + +--EXPECT-- +Caught Error: Typed property OSI::$o must not be accessed before initialization (in __sleep) +Caught Error: Typed property SimplePublic::$i must not be accessed before initialization (in __sleep) +Caught Error: Typed property SimpleProtected::$i must not be accessed before initialization (in __sleep) +Caught Error: Typed property SimplePrivate::$i must not be accessed before initialization (in __sleep) +string(48) "00000002170c53696d706c655075626c6963140111016900" +string(62) "00000002170f53696d706c6550726f74656374656414011104002a00690600" +string(80) "00000002170d53696d706c6550726976617465140111100053696d706c6550726976617465006900" diff --git a/tests/__serialize_016.phpt b/tests/__serialize_016.phpt new file mode 100644 index 00000000..36a43a30 --- /dev/null +++ b/tests/__serialize_016.phpt @@ -0,0 +1,185 @@ +--TEST-- +__serialize() mechanism (016): Properties are still typed after unserialization (references) +--SKIPIF-- += 80000) { echo "skip different error message format"; } +?> +--FILE-- +i = 1; +$t->s = 'other'; +$t->o = $t; +$t->std = (object)['key' => 'value']; +$t->a = [&$t->std, &$t->i, &$t->s, &$t->o]; +$t->std->key = &$t->a; +var_dump($t); + +var_dump(bin2hex($s = igbinary_serialize($t))); +$t2 = igbinary_unserialize($s); +var_dump($t2); +try { + $t2->i = 'x'; +} catch (Error $e) { + echo "i: " . $e->getMessage() . "\n"; +} +$t2->s = null; +try { + $t2->s = false; +} catch (Error $e) { + echo "s: " . $e->getMessage() . "\n"; +} +$t2->s = 'other'; +try { + $t2->o = null; +} catch (Error $e) { + echo "o: " . $e->getMessage() . "\n"; +} +try { + $t2->a = null; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +try { + $t2->stdClass = $t; +} catch (Error $e) { + echo "stdClass: " . $e->getMessage() . "\n"; +} +try { + $t2->a = $t2; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +var_dump($t2); +--EXPECT-- +object(Test)#1 (5) { + ["i"]=> + &int(1) + ["s"]=> + &string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + &array(4) { + [0]=> + &object(stdClass)#2 (1) { + ["key"]=> + *RECURSION* + } + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + ["std"]=> + &object(stdClass)#2 (1) { + ["key"]=> + &array(4) { + [0]=> + *RECURSION* + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + } +} +string(176) "0000000217045465737414061101692506011101732511056f7468657211016f252200001101612514040600251708737464436c617373140111036b65792501030601250101060225010206032522001103737464252204" +object(Test)#3 (5) { + ["i"]=> + &int(1) + ["s"]=> + &string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + &array(4) { + [0]=> + &object(stdClass)#4 (1) { + ["key"]=> + *RECURSION* + } + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + ["std"]=> + &object(stdClass)#4 (1) { + ["key"]=> + &array(4) { + [0]=> + *RECURSION* + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + } +} +i: Typed property Test::$i must be int, string used +s: Typed property Test::$s must be string or null, bool used +o: Typed property Test::$o must be object, null used +a: Typed property Test::$a must be array, null used +stdClass: Typed property Test::$stdClass must be an instance of stdClass, Test used +a: Typed property Test::$a must be array, Test used +object(Test)#3 (5) { + ["i"]=> + &int(1) + ["s"]=> + &string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + &array(4) { + [0]=> + &object(stdClass)#4 (1) { + ["key"]=> + *RECURSION* + } + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + ["std"]=> + &object(stdClass)#4 (1) { + ["key"]=> + &array(4) { + [0]=> + *RECURSION* + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + } +} \ No newline at end of file diff --git a/tests/__serialize_016_php8.phpt b/tests/__serialize_016_php8.phpt new file mode 100644 index 00000000..9c9bf164 --- /dev/null +++ b/tests/__serialize_016_php8.phpt @@ -0,0 +1,191 @@ +--TEST-- +__serialize() mechanism (016): Properties are still typed after unserialization (references) +--SKIPIF-- += 90000) { echo "skip requires php < 9.0 when testing that the deprecation has no impact on igbinary functionality\n"; } +?> +--FILE-- += 80200) { require_once __DIR__ . '/php82_suppress_dynamic_properties_warning.inc'; } + +/** @property mixed $std undeclared dynamic property */ +class Test { + public int $i = 0; + public ?string $s = 's'; + public object $o; + public stdClass $stdClass; + public array $a = []; +} +$t = new Test(); +$t->i = 1; +$t->s = 'other'; +$t->o = $t; +$t->std = (object)['key' => 'value']; +$t->a = [&$t->std, &$t->i, &$t->s, &$t->o]; +$t->std->key = &$t->a; +var_dump($t); + +var_dump(bin2hex($s = igbinary_serialize($t))); +$t2 = igbinary_unserialize($s); +var_dump($t2); +try { + $t2->i = 'x'; +} catch (Error $e) { + echo "i: " . $e->getMessage() . "\n"; +} +$t2->s = null; +try { + $t2->s = false; +} catch (Error $e) { + // The error message is "Cannot assign false" in php 8.3+ + echo "s: " . str_replace('false', 'bool', $e->getMessage()) . "\n"; +} +$t2->s = 'other'; +try { + $t2->o = null; +} catch (Error $e) { + echo "o: " . $e->getMessage() . "\n"; +} +try { + $t2->a = null; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +try { + $t2->stdClass = $t; +} catch (Error $e) { + echo "stdClass: " . $e->getMessage() . "\n"; +} +try { + $t2->a = $t2; +} catch (Error $e) { + echo "a: " . $e->getMessage() . "\n"; +} +var_dump($t2); +?> +--EXPECT-- +object(Test)#1 (5) { + ["i"]=> + &int(1) + ["s"]=> + &string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + &array(4) { + [0]=> + &object(stdClass)#2 (1) { + ["key"]=> + *RECURSION* + } + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + ["std"]=> + &object(stdClass)#2 (1) { + ["key"]=> + &array(4) { + [0]=> + *RECURSION* + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + } +} +string(176) "0000000217045465737414061101692506011101732511056f7468657211016f252200001101612514040600251708737464436c617373140111036b65792501030601250101060225010206032522001103737464252204" +object(Test)#3 (5) { + ["i"]=> + &int(1) + ["s"]=> + &string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + &array(4) { + [0]=> + &object(stdClass)#4 (1) { + ["key"]=> + *RECURSION* + } + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + ["std"]=> + &object(stdClass)#4 (1) { + ["key"]=> + &array(4) { + [0]=> + *RECURSION* + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + } +} +i: Cannot assign string to property Test::$i of type int +s: Cannot assign bool to property Test::$s of type ?string +o: Cannot assign null to property Test::$o of type object +a: Cannot assign null to property Test::$a of type array +stdClass: Cannot assign Test to property Test::$stdClass of type stdClass +a: Cannot assign Test to property Test::$a of type array +object(Test)#3 (5) { + ["i"]=> + &int(1) + ["s"]=> + &string(5) "other" + ["o"]=> + *RECURSION* + ["stdClass"]=> + uninitialized(stdClass) + ["a"]=> + &array(4) { + [0]=> + &object(stdClass)#4 (1) { + ["key"]=> + *RECURSION* + } + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + ["std"]=> + &object(stdClass)#4 (1) { + ["key"]=> + &array(4) { + [0]=> + *RECURSION* + [1]=> + &int(1) + [2]=> + &string(5) "other" + [3]=> + *RECURSION* + } + } +} diff --git a/tests/__serialize_017.phpt b/tests/__serialize_017.phpt new file mode 100644 index 00000000..fe3796e8 --- /dev/null +++ b/tests/__serialize_017.phpt @@ -0,0 +1,39 @@ +--TEST-- +__serialize() mechanism (001): Basics +--SKIPIF-- + +--FILE-- + $this->prop]; + } + public function __unserialize(array $data) { + echo "In __unserialize\n"; + if (!$data['prop']) { + throw new RuntimeException("bad prop"); + } + } + public function __destruct() { + echo "In __destruct\n"; + } +} + +$test = new Test; +$test->prop = new Test(); +var_dump(bin2hex($s = igbinary_serialize($test))); +try { + igbinary_unserialize($s); +} catch (RuntimeException $e) { + echo "Caught {$e->getMessage()}\n"; +} + +?> +--EXPECT-- +string(50) "000000021704546573741401110470726f701a0014010e0100" +In __unserialize +Caught bad prop +In __destruct +In __destruct \ No newline at end of file diff --git a/tests/__serialize_018.phpt b/tests/__serialize_018.phpt new file mode 100644 index 00000000..4eef786c --- /dev/null +++ b/tests/__serialize_018.phpt @@ -0,0 +1,80 @@ +--TEST-- +__serialize() freed on unserialize exception without calling destructor. +--SKIPIF-- + +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- + $this->prop, "value" => $this->prop2]; + } + public function __unserialize(array $data) { + echo "In __unserialize Test\n"; + } + + public function __destruct() { + echo "In __destruct Test\n"; + } +} + +$test = new Test; +$test->prop = new ThrowsInUnserialize(); +$test->prop2 = "barfoo"; +var_dump(bin2hex($s = igbinary_serialize($test))); +try { + var_dump(igbinary_unserialize($s)); +} catch (Error $e) { + echo "Caught: {$e->getMessage()}\n"; +} +gc_collect_cycles(); +echo "After gc_collect_cycles\n"; +unset($test); +gc_collect_cycles(); + +echo "And for serialize/unserialize\n"; +$test = new Test; +$test->prop = new ThrowsInUnserialize(); +$test->prop2 = "barfoo"; + +var_dump(bin2hex($s = serialize($test))); +try { + var_dump(unserialize($s)); +} catch (Error $e) { + echo "Caught: {$e->getMessage()}\n"; +} +?> +--EXPECT-- +string(122) "000000021704546573741402060017135468726f7773496e556e73657269616c697a651d09746573742064617461110576616c75651106626172666f6f" +Unserializing test data +In __destruct ThrowsInUnserialize +Caught: test data +After gc_collect_cycles +In __destruct Test +In __destruct ThrowsInUnserialize +And for serialize/unserialize +string(168) "4f3a343a2254657374223a323a7b693a303b433a31393a225468726f7773496e556e73657269616c697a65223a393a7b7465737420646174617d733a353a2276616c7565223b733a363a22626172666f6f223b7d" +Unserializing test data +In __destruct ThrowsInUnserialize +Caught: test data +In __destruct Test +In __destruct ThrowsInUnserialize \ No newline at end of file diff --git a/tests/__serialize_019.phpt b/tests/__serialize_019.phpt new file mode 100644 index 00000000..548e8003 --- /dev/null +++ b/tests/__serialize_019.phpt @@ -0,0 +1,57 @@ +--TEST-- +__serialize() freed on unserialize exception without calling destructor. +--SKIPIF-- + +--FILE-- + $this->prop, "value" => $this->prop2]; + } + public function __unserialize(array $data) { + echo "In __unserialize Test\n"; + $this->prop = $data[0]; + $this->prop2 = $data['value']; + unset($data[0]); + unset($data['value']); + } + + public function __destruct() { + echo "In __destruct Test\n"; + } +} + +$obj = new stdClass(); +$testObj = new Test(); +$testObj->prop = 123; +$testObj->prop2 = ['xyz']; +$obj->test = 'bar'; +$obj->value = &$testObj; + +var_dump(bin2hex($s = igbinary_serialize($obj))); +var_dump(igbinary_unserialize($s)); +echo "Done\n"; +?> +--EXPECT-- +string(116) "000000021708737464436c61737314021104746573741103626172110576616c75652517045465737414020600067b0e0314010600110378797a" +In __unserialize Test +object(stdClass)#3 (2) { + ["test"]=> + string(3) "bar" + ["value"]=> + object(Test)#4 (2) { + ["prop"]=> + int(123) + ["prop2"]=> + array(1) { + [0]=> + string(3) "xyz" + } + } +} +In __destruct Test +Done +In __destruct Test diff --git a/tests/__serialize_020.phpt b/tests/__serialize_020.phpt new file mode 100644 index 00000000..cf5bf150 --- /dev/null +++ b/tests/__serialize_020.phpt @@ -0,0 +1,643 @@ +--TEST-- +issue when serializing/deserializing nested objects with __serialize +--SKIPIF-- + +--FILE-- +events[] = $event; + $this->transports[$event->getTransport()] = true; + } + + public function getEvents(?string $name = null): array + { + return $this->events; + } +} + +final class MessageEvent extends Event +{ + private $propagationStopped = false; + private $message; + private $envelope; + private $transport; + private $queued; + + public function __construct(RawMessage $message, Envelope $envelope, string $transport, bool $queued = false) + { + $this->message = $message; + $this->envelope = $envelope; + $this->transport = $transport; + $this->queued = $queued; + } + + public function getTransport(): string + { + return $this->transport; + } + + public function getMessage(): RawMessage + { + return $this->message; + } +} + +class Envelope +{ + protected $sender; + protected $recipients = []; + protected $senderSet = false; + protected $recipientsSet = false; + protected $message; + + public function __construct(Address $sender, array $recipients) + { + $this->setSender($sender); + $this->setRecipients($recipients); + } + + public static function create(RawMessage $message): self + { + return new DelayedEnvelope($message); + } + + public function setSender(Address $sender): void + { + $this->sender = $sender; + } + + public function setRecipients(array $recipients): void + { + $this->recipients = []; + foreach ($recipients as $recipient) { + $this->recipients[] = new Address($recipient->getAddress()); + } + } + + public function getRecipients(): array + { + return $this->recipients; + } +} + +final class DelayedEnvelope extends Envelope +{ + + public function __construct(Message $message) + { + $this->message = $message; + } + + public function setSender(Address $sender): void + { + parent::setSender($sender); + + $this->senderSet = true; + } + + public function setRecipients(array $recipients): void + { + parent::setRecipients($recipients); + + $this->recipientsSet = parent::getRecipients(); + } +} + +final class Address +{ + private $address; + private $name; + + public function __construct(string $address, string $name = '') + { + $this->address = trim($address); + $this->name = trim(str_replace(["\n", "\r"], '', $name)); + } + + /** + * @param Address|string $address + */ + public static function create($address): self + { + if ($address instanceof self) { + return $address; + } + if (\is_string($address)) { + if (false === strpos($address, '<')) { + return new self($address); + } + + return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"')); + } + + throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s" given).', get_debug_type($address))); + } + + public static function createArray(array $addresses): array + { + $addrs = []; + foreach ($addresses as $address) { + $addrs[] = self::create($address); + } + + return $addrs; + } +} + +abstract class AbstractHeader +{ + private static $encoder; + + private $name; + private $lineLength = 76; + private $lang; + private $charset = 'utf-8'; + protected $addresses = []; + + public function __construct(string $name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + +} + +final class Headers +{ + private $headers = []; + private $lineLength = 76; + + public function __construct(...$headers) + { + foreach ($headers as $header) { + $this->add($header); + } + } + + public function __clone() + { + foreach ($this->headers as $name => $collection) { + foreach ($collection as $i => $header) { + $this->headers[$name][$i] = clone $header; + } + } + } + + public function addMailboxListHeader(string $name, array $addresses): self + { + return $this->add(new MailboxListHeader($name, Address::createArray($addresses))); + } + + public function add($header): self + { + self::checkHeaderClass($header); + + $name = strtolower($header->getName()); + + $this->headers[$name][] = $header; + + return $this; + } + + public function get(string $name) + { + $name = strtolower($name); + if (!isset($this->headers[$name])) { + return null; + } + + $values = array_values($this->headers[$name]); + + return array_shift($values); + } + + public function all(?string $name = null): iterable + { + if (null === $name) { + foreach ($this->headers as $name => $collection) { + foreach ($collection as $header) { + yield $name => $header; + } + } + } elseif (isset($this->headers[strtolower($name)])) { + foreach ($this->headers[strtolower($name)] as $header) { + yield $header; + } + } + } + + public static function checkHeaderClass($header): void + { + $name = strtolower($header->getName()); + } +} + +final class MailboxListHeader extends AbstractHeader +{ + public function __construct(string $name, array $addresses) + { + parent::__construct($name); + + $this->addAddresses($addresses); + } + + public function addAddresses(array $addresses) + { + foreach ($addresses as $address) { + $this->addAddress($address); + } + } + + /** + * @throws RfcComplianceException + */ + public function addAddress(Address $address) + { + $this->addresses[] = $address; + } +} + +class RawMessage +{ + protected $message; + protected $headers; + protected $body; + protected $text; + protected $textCharset; + protected $html; + protected $htmlCharset; + protected $attachments = []; + + public function __construct($message) + { + $this->message = $message; + } + + public function __serialize(): array + { + return [$this->message]; + } + + public function __unserialize(array $data): void + { + [$this->message] = $data; + } +} + +class Message extends RawMessage +{ + + public function __construct(?Headers $headers = null, ?AbstractPart $body = null) + { + $this->headers = $headers ? clone $headers : new Headers(); + $this->body = $body; + } + + public function __clone() + { + $this->headers = clone $this->headers; + + if (null !== $this->body) { + $this->body = clone $this->body; + } + } + + public function getHeaders(): Headers { return $this->headers; } + + public function __serialize(): array + { + return [$this->headers]; + } + + public function __unserialize(array $data): void + { + [$this->headers] = $data; + } +} + + +class Email extends Message +{ + public function to(...$addresses) + { + return $this->setListAddressHeaderBody('To', $addresses); + } + + private function setListAddressHeaderBody(string $name, array $addresses) + { + $addresses = Address::createArray($addresses); + $headers = $this->getHeaders(); + $headers->addMailboxListHeader($name, $addresses); + + return $this; + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data; + + parent::__unserialize($parentData); + } +} + +$messageEvents = new MessageEvents(); +$messageEvents->add(new MessageEvent($message1 = (new Email())->to('alice@example.com'), Envelope::create($message1), 'null://null')); +$messageEvents->add(new MessageEvent($message2 = (new Email())->to('bob@example.com'), Envelope::create($message2), 'null://null')); + +var_dump($messageEvents); // Comment/uncomment to trigger the bug + +var_dump('headers_before', $messageEvents->getEvents()[0]->getMessage()->getHeaders() === $messageEvents->getEvents()[1]->getMessage()->getHeaders()); + +$ser = igbinary_serialize($messageEvents); + +$messageEvents = igbinary_unserialize($ser); + +// should dump "false", but dumps "true" the "var_dump($messageEvents)" is not commented +var_dump('headers_after', $messageEvents->getEvents()[0]->getMessage()->getHeaders() === $messageEvents->getEvents()[1]->getMessage()->getHeaders()); +?> +--EXPECT-- +object(MessageEvents)#1 (2) { + ["events":"MessageEvents":private]=> + array(2) { + [0]=> + object(MessageEvent)#2 (5) { + ["propagationStopped":"MessageEvent":private]=> + bool(false) + ["message":"MessageEvent":private]=> + object(Email)#3 (8) { + ["message":protected]=> + NULL + ["headers":protected]=> + object(Headers)#4 (2) { + ["headers":"Headers":private]=> + array(1) { + ["to"]=> + array(1) { + [0]=> + object(MailboxListHeader)#6 (5) { + ["name":"AbstractHeader":private]=> + string(2) "To" + ["lineLength":"AbstractHeader":private]=> + int(76) + ["lang":"AbstractHeader":private]=> + NULL + ["charset":"AbstractHeader":private]=> + string(5) "utf-8" + ["addresses":protected]=> + array(1) { + [0]=> + object(Address)#5 (2) { + ["address":"Address":private]=> + string(17) "alice@example.com" + ["name":"Address":private]=> + string(0) "" + } + } + } + } + } + ["lineLength":"Headers":private]=> + int(76) + } + ["body":protected]=> + NULL + ["text":protected]=> + NULL + ["textCharset":protected]=> + NULL + ["html":protected]=> + NULL + ["htmlCharset":protected]=> + NULL + ["attachments":protected]=> + array(0) { + } + } + ["envelope":"MessageEvent":private]=> + object(DelayedEnvelope)#7 (5) { + ["sender":protected]=> + NULL + ["recipients":protected]=> + array(0) { + } + ["senderSet":protected]=> + bool(false) + ["recipientsSet":protected]=> + bool(false) + ["message":protected]=> + object(Email)#3 (8) { + ["message":protected]=> + NULL + ["headers":protected]=> + object(Headers)#4 (2) { + ["headers":"Headers":private]=> + array(1) { + ["to"]=> + array(1) { + [0]=> + object(MailboxListHeader)#6 (5) { + ["name":"AbstractHeader":private]=> + string(2) "To" + ["lineLength":"AbstractHeader":private]=> + int(76) + ["lang":"AbstractHeader":private]=> + NULL + ["charset":"AbstractHeader":private]=> + string(5) "utf-8" + ["addresses":protected]=> + array(1) { + [0]=> + object(Address)#5 (2) { + ["address":"Address":private]=> + string(17) "alice@example.com" + ["name":"Address":private]=> + string(0) "" + } + } + } + } + } + ["lineLength":"Headers":private]=> + int(76) + } + ["body":protected]=> + NULL + ["text":protected]=> + NULL + ["textCharset":protected]=> + NULL + ["html":protected]=> + NULL + ["htmlCharset":protected]=> + NULL + ["attachments":protected]=> + array(0) { + } + } + } + ["transport":"MessageEvent":private]=> + string(11) "null://null" + ["queued":"MessageEvent":private]=> + bool(false) + } + [1]=> + object(MessageEvent)#8 (5) { + ["propagationStopped":"MessageEvent":private]=> + bool(false) + ["message":"MessageEvent":private]=> + object(Email)#9 (8) { + ["message":protected]=> + NULL + ["headers":protected]=> + object(Headers)#10 (2) { + ["headers":"Headers":private]=> + array(1) { + ["to"]=> + array(1) { + [0]=> + object(MailboxListHeader)#12 (5) { + ["name":"AbstractHeader":private]=> + string(2) "To" + ["lineLength":"AbstractHeader":private]=> + int(76) + ["lang":"AbstractHeader":private]=> + NULL + ["charset":"AbstractHeader":private]=> + string(5) "utf-8" + ["addresses":protected]=> + array(1) { + [0]=> + object(Address)#11 (2) { + ["address":"Address":private]=> + string(15) "bob@example.com" + ["name":"Address":private]=> + string(0) "" + } + } + } + } + } + ["lineLength":"Headers":private]=> + int(76) + } + ["body":protected]=> + NULL + ["text":protected]=> + NULL + ["textCharset":protected]=> + NULL + ["html":protected]=> + NULL + ["htmlCharset":protected]=> + NULL + ["attachments":protected]=> + array(0) { + } + } + ["envelope":"MessageEvent":private]=> + object(DelayedEnvelope)#13 (5) { + ["sender":protected]=> + NULL + ["recipients":protected]=> + array(0) { + } + ["senderSet":protected]=> + bool(false) + ["recipientsSet":protected]=> + bool(false) + ["message":protected]=> + object(Email)#9 (8) { + ["message":protected]=> + NULL + ["headers":protected]=> + object(Headers)#10 (2) { + ["headers":"Headers":private]=> + array(1) { + ["to"]=> + array(1) { + [0]=> + object(MailboxListHeader)#12 (5) { + ["name":"AbstractHeader":private]=> + string(2) "To" + ["lineLength":"AbstractHeader":private]=> + int(76) + ["lang":"AbstractHeader":private]=> + NULL + ["charset":"AbstractHeader":private]=> + string(5) "utf-8" + ["addresses":protected]=> + array(1) { + [0]=> + object(Address)#11 (2) { + ["address":"Address":private]=> + string(15) "bob@example.com" + ["name":"Address":private]=> + string(0) "" + } + } + } + } + } + ["lineLength":"Headers":private]=> + int(76) + } + ["body":protected]=> + NULL + ["text":protected]=> + NULL + ["textCharset":protected]=> + NULL + ["html":protected]=> + NULL + ["htmlCharset":protected]=> + NULL + ["attachments":protected]=> + array(0) { + } + } + } + ["transport":"MessageEvent":private]=> + string(11) "null://null" + ["queued":"MessageEvent":private]=> + bool(false) + } + } + ["transports":"MessageEvents":private]=> + array(1) { + ["null://null"]=> + bool(true) + } +} +string(14) "headers_before" +bool(false) +string(13) "headers_after" +bool(false) diff --git a/tests/__serialize_021.phpt b/tests/__serialize_021.phpt new file mode 100644 index 00000000..5cb3c7f0 --- /dev/null +++ b/tests/__serialize_021.phpt @@ -0,0 +1,49 @@ +--TEST-- +__serialize() mechanism (021): Temporary references in serialized arrays should be properly deduplicated +--FILE-- +i = $i; + } + + public function __serialize() { + $j = $this->i + 0x7700; + return [&$j, &$j]; + } + + public function __unserialize(array $data) { + list($this->i) = $data; + } +} + +$values = []; +for ($i = 0; $i < 256; $i++) { + $values[] = new Test($i); +} +$ser = igbinary_serialize($values); +$result = igbinary_unserialize($ser); + +printf("Count=%d\n", count($values)); +$j = 0; +foreach ($values as $i => $v) { + if ($i !== $j) { + echo "Unexpected key $i, expected $j\n"; + } + if ($v->i !== $j) { + echo "Unexpected value {$v->i}, expected $j\n"; + } + $j++; +} +echo "Done\n"; + +?> +--EXPECT-- +Count=256 +Done diff --git a/tests/__serialize_022.phpt b/tests/__serialize_022.phpt new file mode 100644 index 00000000..11cd634b --- /dev/null +++ b/tests/__serialize_022.phpt @@ -0,0 +1,43 @@ +--TEST-- +__serialize() mechanism (021): Test __serialize without __unserialize +--SKIPIF-- + +--FILE-- + 'value']; + + public function __serialize() { + return self::SOME_CONST; + } + + public static function test_serialize() { + $x = igbinary_serialize([self::SOME_CONST, new self(), new self()]); + // aggressively reuses arrays + echo urlencode($x), "\n"; + var_dump(igbinary_unserialize($x)); + } +} + +Test::test_serialize(); +?> +--EXPECT-- +%00%00%00%02%14%03%06%00%14%01%11%03key%11%05value%06%01%17%04Test%14%01%0E%00%0E%01%06%02%1A%02%14%01%0E%00%0E%01 +array(3) { + [0]=> + array(1) { + ["key"]=> + string(5) "value" + } + [1]=> + object(Test)#2 (1) { + ["key"]=> + string(5) "value" + } + [2]=> + object(Test)#1 (1) { + ["key"]=> + string(5) "value" + } +} diff --git a/tests/__serialize_023.phpt b/tests/__serialize_023.phpt new file mode 100644 index 00000000..8b73515e --- /dev/null +++ b/tests/__serialize_023.phpt @@ -0,0 +1,64 @@ +--TEST-- +Check hydration of properties with different property visibility levels when __serialize is present but __unserialize is missing +--SKIPIF-- + +--FILE-- + $this->private_property * 10, + 'protected_property' => $this->protected_property * 10, + 'public_property' => $this->public_property * 10, + ]; + } +} + +$original = new Test(); +var_dump($original); + +// Serialize / Unserialize with PHP and Igbinary. The behavior must be the same. +echo "\nPHP behavior:\n"; +$serialized_php = serialize($original); +$unserialized_php = unserialize($serialized_php); +var_dump($unserialized_php); + +echo "\nIgbinary behavior:\n"; +$serialized_igb = igbinary_serialize($original); +$unserialized_igb = igbinary_unserialize($serialized_igb); +var_dump($unserialized_igb); + +?> +--EXPECT-- +object(Test)#1 (3) { + ["private_property":"Test":private]=> + int(1) + ["protected_property":protected]=> + int(2) + ["public_property"]=> + int(3) +} + +PHP behavior: +object(Test)#2 (3) { + ["private_property":"Test":private]=> + int(10) + ["protected_property":protected]=> + int(20) + ["public_property"]=> + int(30) +} + +Igbinary behavior: +object(Test)#3 (3) { + ["private_property":"Test":private]=> + int(10) + ["protected_property":protected]=> + int(20) + ["public_property"]=> + int(30) +} diff --git a/tests/__serialize_024.phpt b/tests/__serialize_024.phpt new file mode 100644 index 00000000..ed10ac01 --- /dev/null +++ b/tests/__serialize_024.phpt @@ -0,0 +1,70 @@ +--TEST-- +Check that __serialize and __unserialize are used properly for serialization and unserialization when present +--SKIPIF-- + +--FILE-- + $this->private_property * 10, + 'protected_property' => $this->protected_property * 10, + 'public_property' => $this->public_property * 10, + ]; + } + + public function __unserialize(array $data) { + $this->private_property = $data['private_property'] * 100; + $this->protected_property = $data['protected_property'] * 100; + $this->public_property = $data['public_property'] * 100; + } +} + +$original = new Test(); +var_dump($original); + +// Serialize / Unserialize with PHP and Igbinary. The behavior must be the same. +echo "\nPHP behavior:\n"; +$serialized_php = serialize($original); +$unserialized_php = unserialize($serialized_php); +var_dump($unserialized_php); + +echo "\nIgbinary behavior:\n"; +$serialized_igb = igbinary_serialize($original); +$unserialized_igb = igbinary_unserialize($serialized_igb); +var_dump($unserialized_igb); + +?> +--EXPECT-- +object(Test)#1 (3) { + ["private_property":"Test":private]=> + int(1) + ["protected_property":protected]=> + int(2) + ["public_property"]=> + int(3) +} + +PHP behavior: +object(Test)#2 (3) { + ["private_property":"Test":private]=> + int(1000) + ["protected_property":protected]=> + int(2000) + ["public_property"]=> + int(3000) +} + +Igbinary behavior: +object(Test)#3 (3) { + ["private_property":"Test":private]=> + int(1000) + ["protected_property":protected]=> + int(2000) + ["public_property"]=> + int(3000) +} diff --git a/tests/__serialize_025.phpt b/tests/__serialize_025.phpt new file mode 100644 index 00000000..6c7b8dc7 --- /dev/null +++ b/tests/__serialize_025.phpt @@ -0,0 +1,43 @@ +--TEST-- +Hydrating virtual properties should fail, just like with the PHP serializer +--SKIPIF-- + +--FILE-- + 1; + } + + public function __serialize() { + return [ + 'virtual_property' => 2, + ]; + } +} + +$original = new Test(); + +// Serialize / Unserialize with PHP and Igbinary. The behavior must be the same. +echo "\nPHP behavior:"; +$serialized_php = serialize($original); +$unserialized_php = unserialize($serialized_php); +var_dump($unserialized_php); + +echo "\nIgbinary behavior:"; +$serialized_igb = igbinary_serialize($original); +$unserialized_igb = igbinary_unserialize($serialized_igb); +var_dump($unserialized_igb); + +?> +--EXPECTF-- +PHP behavior: +Warning: unserialize(): Cannot unserialize value for virtual property Test::$virtual_property in %s on line %d + +Warning: unserialize(): Error at offset 38 of 43 bytes in %s on line %d +bool(false) + +Igbinary behavior: +Warning: igbinary_unserialize(): Cannot unserialize value for virtual property Test::$virtual_property in %s on line %d +NULL \ No newline at end of file diff --git a/tests/__serialize_026.phpt b/tests/__serialize_026.phpt new file mode 100644 index 00000000..a7cfecc0 --- /dev/null +++ b/tests/__serialize_026.phpt @@ -0,0 +1,51 @@ +--TEST-- +Igbinary should behave like the PHP serializer when the __serialize method of a derived class sets a value to a private property of a base class +--SKIPIF-- + +--FILE-- + 2, + ]; + } +} + +$original = new DerivedClass(); +var_dump($original); + +// Serialize / Unserialize with PHP and Igbinary. The behavior must be the same. +echo "\nPHP behavior:\n"; +$serialized_php = serialize($original); +$unserialized_php = unserialize($serialized_php); +var_dump($unserialized_php); + +echo "\nIgbinary behavior:\n"; +$serialized_igb = igbinary_serialize($original); +$unserialized_igb = igbinary_unserialize($serialized_igb); +var_dump($unserialized_igb); + +?> +--EXPECT-- +object(DerivedClass)#1 (1) { + ["private_property":"BaseClass":private]=> + int(1) +} + +PHP behavior: +object(DerivedClass)#2 (1) { + ["private_property":"BaseClass":private]=> + int(2) +} + +Igbinary behavior: +object(DerivedClass)#3 (1) { + ["private_property":"BaseClass":private]=> + int(2) +} \ No newline at end of file diff --git a/tests/igbinary_001.phpt b/tests/igbinary_001.phpt index 559e759a..f1c77ae3 100644 --- a/tests/igbinary_001.phpt +++ b/tests/igbinary_001.phpt @@ -3,7 +3,7 @@ Check for igbinary presence --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_002.phpt b/tests/igbinary_002.phpt index f3cb5efb..36277624 100644 --- a/tests/igbinary_002.phpt +++ b/tests/igbinary_002.phpt @@ -2,7 +2,7 @@ Check for null serialisation --SKIPIF-- --FILE-- - --EXPECT-- diff --git a/tests/igbinary_003.phpt b/tests/igbinary_003.phpt index 78b73f97..f237c525 100644 --- a/tests/igbinary_003.phpt +++ b/tests/igbinary_003.phpt @@ -1,8 +1,10 @@ --TEST-- Check for bool serialisation ---SKIPIF-- +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- - diff --git a/tests/igbinary_004.phpt b/tests/igbinary_004.phpt index cca60336..ca25fbc5 100644 --- a/tests/igbinary_004.phpt +++ b/tests/igbinary_004.phpt @@ -2,7 +2,7 @@ Check for integer serialisation --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_005.phpt b/tests/igbinary_005.phpt index 8dcfaf30..558e7b11 100644 --- a/tests/igbinary_005.phpt +++ b/tests/igbinary_005.phpt @@ -2,7 +2,7 @@ Check for double serialisation --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_006.phpt b/tests/igbinary_006.phpt index edd940c1..8fd68d6f 100644 --- a/tests/igbinary_006.phpt +++ b/tests/igbinary_006.phpt @@ -2,7 +2,7 @@ Check for simple string serialization --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_007.phpt b/tests/igbinary_007.phpt index 6abee287..5b3bb128 100644 --- a/tests/igbinary_007.phpt +++ b/tests/igbinary_007.phpt @@ -2,7 +2,7 @@ Check for simple array serialization --SKIPIF-- --FILE-- - --EXPECT-- -empty array: +empty array: 1400 OK array(1, 2, 3) diff --git a/tests/igbinary_008.phpt b/tests/igbinary_008.phpt index 6e6f1b36..2d36dfbe 100644 --- a/tests/igbinary_008.phpt +++ b/tests/igbinary_008.phpt @@ -2,7 +2,7 @@ Check for array+string serialization --SKIPIF-- --FILE-- - 7) { + echo "skip requires php 7.x\n"; +} +--INI-- +pcre.jit=0 +--FILE-- + + &array(1) { + [0]=> + array(1) { + [0]=> + *RECURSION* + } + } +} + +Expected +array(1) { + [0]=> + &array(1) { + [0]=> + *RECURSION* + } +} + +(Was normalized) +cyclic $a = array(array(&$a)); $a[0] - testing functionality +14010600251401060014010600250101 +OK +But var dump differs: +Actual: +array(1) { + [0]=> + &array(1) { + [0]=> + array(1) { + [0]=> + *RECURSION* + } + } +} + +Expected +array(1) { + [0]=> + &array(1) { + [0]=> + *RECURSION* + } +} + +(Was normalized) diff --git a/tests/igbinary_010.phpt b/tests/igbinary_010.phpt index 875d9b65..bf26a979 100644 --- a/tests/igbinary_010.phpt +++ b/tests/igbinary_010.phpt @@ -2,7 +2,7 @@ Array test --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_012.phpt b/tests/igbinary_012.phpt index 70c0279b..4ee80c11 100644 --- a/tests/igbinary_012.phpt +++ b/tests/igbinary_012.phpt @@ -1,13 +1,15 @@ --TEST-- Object test ---SKIPIF-- +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- - diff --git a/tests/igbinary_013.phpt b/tests/igbinary_013.phpt index 76e936a4..4c343770 100644 --- a/tests/igbinary_013.phpt +++ b/tests/igbinary_013.phpt @@ -2,7 +2,7 @@ Object-Array test --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_014.phpt b/tests/igbinary_014.phpt index 494bfd4a..9d9456c4 100644 --- a/tests/igbinary_014.phpt +++ b/tests/igbinary_014.phpt @@ -2,7 +2,7 @@ Object-Reference test --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_015b.phpt b/tests/igbinary_015b.phpt index 619eb6c9..d41140d4 100644 --- a/tests/igbinary_015b.phpt +++ b/tests/igbinary_015b.phpt @@ -6,19 +6,19 @@ if (!extension_loaded('session')) { exit('skip session extension not loaded'); } -ob_start(); +ob_start(); phpinfo(INFO_MODULES); $str = ob_get_clean(); - -$array = explode("\n", $str); -$array = preg_grep('/^igbinary session support.*yes/', $array); + +$array = explode("\n", $str); +$array = preg_grep('/^igbinary session support.*yes/', $array); if (!$array) { exit('skip igbinary session handler not available'); } --INI-- --FILE-- - diff --git a/tests/igbinary_015c.phpt b/tests/igbinary_015c.phpt new file mode 100644 index 00000000..b5d9cc7f --- /dev/null +++ b/tests/igbinary_015c.phpt @@ -0,0 +1,70 @@ +--TEST-- +Check for serialization handler +--SKIPIF-- + +--FILE-- + +--EXPECT-- +read(abcdef10231512dfaz_12311) +write(abcdef10231512dfaz_12311): data:(000000021400) diff --git a/tests/igbinary_016.phpt b/tests/igbinary_016.phpt index 4a6c48ce..5e2195df 100644 --- a/tests/igbinary_016.phpt +++ b/tests/igbinary_016.phpt @@ -1,8 +1,10 @@ --TEST-- Object test, __sleep --SKIPIF-- +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- - diff --git a/tests/igbinary_017.phpt b/tests/igbinary_017.phpt index ee3e0ba9..f055f947 100644 --- a/tests/igbinary_017.phpt +++ b/tests/igbinary_017.phpt @@ -1,8 +1,10 @@ --TEST-- Object test, __wakeup --SKIPIF-- +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- - diff --git a/tests/igbinary_018.phpt b/tests/igbinary_018.phpt index 2d17126b..8a388423 100644 --- a/tests/igbinary_018.phpt +++ b/tests/igbinary_018.phpt @@ -1,8 +1,10 @@ --TEST-- Object test, __sleep error cases --SKIPIF-- +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- - diff --git a/tests/igbinary_019.phpt b/tests/igbinary_019.phpt index c6f650e7..377a452b 100644 --- a/tests/igbinary_019.phpt +++ b/tests/igbinary_019.phpt @@ -2,7 +2,7 @@ Object test, __autoload --SKIPIF-- --FILE-- - diff --git a/tests/igbinary_020.phpt b/tests/igbinary_020.phpt index bbc4f49b..f8d0a7c3 100644 --- a/tests/igbinary_020.phpt +++ b/tests/igbinary_020.phpt @@ -2,11 +2,16 @@ Object test, incomplete class --SKIPIF-- --FILE-- -= 80200) { + // TODO undo workaround when #[AllowDynamicProperties] is added to __PHP_Incomplete_Class + error_reporting(E_ALL & ~E_DEPRECATED); +} + function test($type, $variable, $test) { $serialized = pack('H*', $variable); $unserialized = igbinary_unserialize($serialized); @@ -27,7 +32,7 @@ test('incom', '0000000217034f626a140211016106011101620602', false); * to pass, differences between the output and the * expected text are interpreted as failure * - * see php5/README.TESTING for further information on + * see TESTING.md for further information on * writing regression tests */ ?> diff --git a/tests/igbinary_021.phpt b/tests/igbinary_021.phpt index 1cfc633e..d8e3b96c 100644 --- a/tests/igbinary_021.phpt +++ b/tests/igbinary_021.phpt @@ -1,8 +1,10 @@ --TEST-- Object Serializable interface ---SKIPIF-- +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- - diff --git a/tests/igbinary_022.phpt b/tests/igbinary_022.phpt index 3ab441c6..1af8cb01 100644 --- a/tests/igbinary_022.phpt +++ b/tests/igbinary_022.phpt @@ -1,10 +1,12 @@ --TEST-- -Object test, unserialize_callback_func +Object test, unserialize_callback_func (PHP < 8.2) --SKIPIF-- += 80200) echo "skip requires php < 8.2\n"; ?> --INI-- +error_reporting=E_ALL unserialize_callback_func=autoload --FILE-- -a = $a; - $this->b = $b; + function __construct($a, $b) { + $this->a = $a; + $this->b = $b; + } } } } test('autoload', '0000000217034f626a140211016106011101620602', false); +test('autoload', '0000000217034f626b140211016106011101620602', false); +ini_set('unserialize_callback_func', strtolower('Missing_autoload')); +test('missing_autoload', '0000000217034f626c140211016106011101620602', false); /* * you can add regression tests for your extension here @@ -41,11 +49,27 @@ test('autoload', '0000000217034f626a140211016106011101620602', false); * to pass, differences between the output and the * expected text are interpreted as failure * - * see php5/README.TESTING for further information on + * see TESTING.md for further information on * writing regression tests */ ?> ---EXPECT-- +--EXPECTF-- +Autoloading Obj autoload 17034f626a140211016106011101620602 OK +Autoloading Obk + +%s: igbinary_unserialize(): Function autoload() hasn't defined the class it was called for in %sigbinary_022.php on line 8 +autoload +17034f626b140211016106011101620602 + +%s: test(): %s to load the class definition %Sin %sigbinary_022.php on line 12 +ERROR + +%s: igbinary_unserialize(): defined (missing_autoload) but not found in %sigbinary_022.php on line 8 +missing_autoload +17034f626c140211016106011101620602 + +%s: test(): %s to load the class definition %Sin %sigbinary_022.php on line 12 +ERROR diff --git a/tests/igbinary_022_php82.phpt b/tests/igbinary_022_php82.phpt new file mode 100644 index 00000000..3d5b060d --- /dev/null +++ b/tests/igbinary_022_php82.phpt @@ -0,0 +1,98 @@ +--TEST-- +Object test, unserialize_callback_func (PHP >= 8.2) +--SKIPIF-- + +--INI-- +error_reporting=E_ALL +unserialize_callback_func=autoload +--FILE-- +getMessage()}\n"; + return; + } + + echo $type, "\n"; + echo substr(bin2hex($serialized), 8), "\n"; + var_dump($unserialized); + // The read_property handler is set to incomplete_class_get_property, + // making this always return null for __PHP_Incomplete_Class + var_dump($unserialized->b); + echo $unserialized->b == 2 ? 'OK' : 'ERROR'; + echo "\n"; +} + +function autoload($classname) { + echo "Autoloading $classname\n"; + if (!class_exists(Obj::class)) { + class Obj { + var $a; + var $b; + + function __construct($a, $b) { + $this->a = $a; + $this->b = $b; + } + } + } +} + +test('autoload', '0000000217034f626a140211016106011101620602'); // "Obj" +test('autoload', '0000000217034f626b140211016106011101620602'); // "Obk" failing to autoload +ini_set('unserialize_callback_func', strtolower('Missing_autoload')); +test('missing_autoload', '0000000217034f626c140211016106011101620602'); // Obk" with missing autoload function + +/* + * you can add regression tests for your extension here + * + * the output of your test code has to be equal to the + * text in the --EXPECT-- section below for the tests + * to pass, differences between the output and the + * expected text are interpreted as failure + * + * see TESTING.md for further information on + * writing regression tests + */ +?> +--EXPECTF-- +Autoloading Obj +autoload +17034f626a140211016106011101620602 +object(Obj)#1 (2) { + ["a"]=> + int(1) + ["b"]=> + int(2) +} +int(2) +OK +Autoloading Obk + +Warning: igbinary_unserialize(): Function autoload() hasn't defined the class it was called for in %sigbinary_022_php82.php on line 9 +autoload +17034f626b140211016106011101620602 +object(__PHP_Incomplete_Class)#1 (3) { + ["__PHP_Incomplete_Class_Name"]=> + string(3) "Obk" + ["a"]=> + int(1) + ["b"]=> + int(2) +} + +Warning: test(): The script tried to access a property on an incomplete object. Please ensure that the class definition "Obk" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definition in %sigbinary_022_php82.php on line 21 +NULL + +Warning: test(): The script tried to access a property on an incomplete object. Please ensure that the class definition "Obk" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definition in %sigbinary_022_php82.php on line 22 +ERROR +missing_autoload +Caught Invalid callback missing_autoload, function "missing_autoload" not found or invalid function name diff --git a/tests/igbinary_023.phpt b/tests/igbinary_023.phpt index f6554422..0078060f 100644 --- a/tests/igbinary_023.phpt +++ b/tests/igbinary_023.phpt @@ -1,10 +1,10 @@ --TEST-- Resource --SKIPIF-- - diff --git a/tests/igbinary_025.phpt b/tests/igbinary_025.phpt index abf7ca7d..7f81d6c3 100644 --- a/tests/igbinary_025.phpt +++ b/tests/igbinary_025.phpt @@ -1,8 +1,10 @@ --TEST-- Object test, array of objects with __sleep --SKIPIF-- +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- -c = $c; + } + + function __sleep() { + return array('c'); + } +} + +$obj = new Obj(4); + +test('array', $obj, true); + +?> +--EXPECT-- +object(Obj)#1 (1) { + ["c":"Obj":private]=> + int(4) +} +object(Obj)#2 (1) { + ["c":"Obj":private]=> + int(4) +} diff --git a/tests/igbinary_026.phpt b/tests/igbinary_026.phpt index 56fcce87..c3f43866 100644 --- a/tests/igbinary_026.phpt +++ b/tests/igbinary_026.phpt @@ -4,11 +4,14 @@ Cyclic array test report_memleaks=0 --SKIPIF-- 7) { + echo "skip requires php 7.x\n"; +} --FILE-- - array( + 'b' => 'c', + 'd' => 'e' + ), +); + +$a['f'] = &$a; + +test('array', $a, false); + +$a = array("foo" => &$b); +$b = array(1, 2, $a); + +$exp = $a; +$act = igbinary_unserialize(igbinary_serialize($a)); + +ob_start(); +var_dump($exp); +$dump_exp = ob_get_clean(); +ob_start(); +var_dump($act); +$dump_act = ob_get_clean(); + +if ($dump_act !== $dump_exp) { + echo "Var dump differs:\nActual:\n", $dump_act, "\nExpected:\n", $dump_exp, "\n"; +} else { + echo "Var dump OK\n"; +} + +$act['foo'][1] = 'test value'; +$exp['foo'][1] = 'test value'; +if ($act['foo'][1] !== $act['foo'][2]['foo'][1]) { + echo "Recursive elements differ:\n"; + echo "Actual\n"; + var_dump($act); + var_dump($act['foo']); + echo "Expected\n"; + var_dump($exp); + var_dump($exp['foo']); +} + +?> +--EXPECT-- +array +140211016114021101621101631101641101651101662514020e0001010e05250102 +OK +Var dump differs: +Actual: +array(1) { + ["foo"]=> + &array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + array(1) { + ["foo"]=> + *RECURSION* + } + } +} + +Expected: +array(1) { + ["foo"]=> + &array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + *RECURSION* + } +} diff --git a/tests/igbinary_026b.phpt b/tests/igbinary_026b.phpt index afe3c4fc..6d0ad0d9 100644 --- a/tests/igbinary_026b.phpt +++ b/tests/igbinary_026b.phpt @@ -4,14 +4,12 @@ Cyclic array test 2 report_memleaks=0 --SKIPIF-- 7) { + echo "skip requires php 7.x\n"; } - --FILE-- &$b); +$b = array(1, 2, $a); + +/* all three statements below should produce same output however PHP stock + * unserialize/serialize produces different output (5.2.16). I consider this is + * a PHP bug. - Oleg Grenrus + */ + +/* NOTE: This is different in php 8 because igbinary_unserialize() is declared to return a reference, not a value */ + +//$k = $a; +//$k = unserialize(serialize($a)); +$k = igbinary_unserialize(igbinary_serialize($a)); + +function check($a, $k) { + ob_start(); + var_dump($a); + $a_str = ob_get_clean(); + ob_start(); + var_dump($k); + $k_str = ob_get_clean(); + + if ($a_str !== $k_str) { + echo "Output differs\n"; + echo "Expected:\n", $a_str, "\n"; + echo "Actual:\n", $k_str, "\n"; + } else { + echo "OK\n"; + } +} + +check($a, $k); + +$a["foo"][2]["foo"][1] = "b"; +$k["foo"][2]["foo"][1] = "b"; + +check($a, $k); + +?> +--EXPECT-- +Output differs +Expected: +array(1) { + ["foo"]=> + &array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + *RECURSION* + } +} + +Actual: +array(1) { + ["foo"]=> + &array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + array(1) { + ["foo"]=> + *RECURSION* + } + } +} + +OK diff --git a/tests/igbinary_027.phpt b/tests/igbinary_027.phpt index 4cf1f8af..77b4a7cf 100644 --- a/tests/igbinary_027.phpt +++ b/tests/igbinary_027.phpt @@ -6,19 +6,19 @@ if (!extension_loaded('session')) { exit('skip session extension not loaded'); } -ob_start(); +ob_start(); phpinfo(INFO_MODULES); $str = ob_get_clean(); - -$array = explode("\n", $str); -$array = preg_grep('/^igbinary session support.*yes/', $array); + +$array = explode("\n", $str); +$array = preg_grep('/^igbinary session support.*yes/', $array); if (!$array) { exit('skip igbinary session handler not available'); } --FILE-- - diff --git a/tests/igbinary_028.phpt b/tests/igbinary_028.phpt index 64645705..d07860bf 100644 --- a/tests/igbinary_028.phpt +++ b/tests/igbinary_028.phpt @@ -6,19 +6,19 @@ if (!extension_loaded('session')) { exit('skip session extension not loaded'); } -ob_start(); +ob_start(); phpinfo(INFO_MODULES); $str = ob_get_clean(); - -$array = explode("\n", $str); -$array = preg_grep('/^igbinary session support.*yes/', $array); + +$array = explode("\n", $str); +$array = preg_grep('/^igbinary session support.*yes/', $array); if (!$array) { exit('skip igbinary session handler not available'); } --FILE-- - diff --git a/tests/igbinary_029.phpt b/tests/igbinary_029.phpt index 6b8b6d3f..d54495f8 100644 --- a/tests/igbinary_029.phpt +++ b/tests/igbinary_029.phpt @@ -1,11 +1,11 @@ --TEST-- Igbinary module info --SKIPIF-- - --FILE-- -= 70200) { + echo "Skip php 7.1 or 7.0 required\n"; +} +?> --FILE-- -{"1"} = "manual"; $datas = array( 87817, @@ -18,6 +25,7 @@ $datas = array( "dakjdh98389\000", null, (object)array(1,2,3), + $o, ); error_reporting(0); @@ -39,14 +47,20 @@ foreach ($datas as $data) { } // padded - $str .= "98398afa\000y21_ "; - $v = igbinary_unserialize($str); - if ($v !== $data && !(is_object($data) && $v == $data)) { - echo "padded should get original\n"; + $str2 = $str . "98398afa\000y21_ "; + $v = igbinary_unserialize($str2); + if ($v !== NULL) { + echo "Should return null with padding\n"; + var_dump($v); + } + $str3 = $str . "\x00"; + $v = igbinary_unserialize($str3); + if ($v !== NULL) { + echo "Should return null with single byte of padding\n"; var_dump($v); - echo "vs.\n"; - var_dump($data); } } - +echo "Success!\n"; +?> --EXPECT-- +Success! diff --git a/tests/igbinary_030_php72.phpt b/tests/igbinary_030_php72.phpt new file mode 100644 index 00000000..8a1b5b7a --- /dev/null +++ b/tests/igbinary_030_php72.phpt @@ -0,0 +1,65 @@ +--TEST-- +Unserialize invalid data (php 7.2+) +--SKIPIF-- + +--FILE-- +{"1"} = "manual"; +$datas = array( + 87817, + -1, + array(1,2,3,"testing" => 10, "foo"), + true, + false, + 0.187182, + "dakjdh98389\000", + null, + (object)array(1,2,3), + $o, +); + +error_reporting(0); +foreach ($datas as $data) { + $str = igbinary_serialize($data); + $len = strlen($str); + + // truncated + for ($i = 0; $i < $len - 1; $i++) { + $v = igbinary_unserialize(substr($str, 0, $i)); + if (is_object($data) && $v !== null && $v == $data) { + continue; + } elseif ($v !== null && $v != FALSE && $v !== $data) { + echo "output at $i:\n"; + var_dump($v); + echo "vs.\n"; + var_dump($data); + } + } + + // padded + $str2 = $str . "98398afa\000y21_ "; + $v = igbinary_unserialize($str2); + if ($v !== NULL) { + echo "Should return null with padding\n"; + var_dump($v); + } + $str3 = $str . "\x00"; + $v = igbinary_unserialize($str3); + if ($v !== NULL) { + echo "Should return null with single byte of padding\n"; + var_dump($v); + } +} +echo "Success!\n"; +?> +--EXPECT-- +Success! diff --git a/tests/igbinary_031.phpt b/tests/igbinary_031.phpt index 101aad2a..356ccaa8 100644 --- a/tests/igbinary_031.phpt +++ b/tests/igbinary_031.phpt @@ -1,8 +1,10 @@ --TEST-- Object Serializable interface throws exceptions ---SKIPIF-- +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED --FILE-- -=")) { - if ($e->getPrevious()) { - $e = $e->getPrevious(); - } + if ($e->getPrevious()) { + $e = $e->getPrevious(); } echo $e->getMessage(), "\n"; @@ -67,10 +67,8 @@ echo "b, b, d\n"; try { test(array($b, $b, $d)); } catch (Exception $e) { - if (version_compare(phpversion(), "5.3.0", ">=")) { - if ($e->getPrevious()) { - $e = $e->getPrevious(); - } + if ($e->getPrevious()) { + $e = $e->getPrevious(); } echo $e->getMessage(), "\n"; diff --git a/tests/igbinary_032.phpt b/tests/igbinary_032.phpt index ff3f0734..eca071be 100644 --- a/tests/igbinary_032.phpt +++ b/tests/igbinary_032.phpt @@ -1,12 +1,14 @@ --TEST-- Object test, __sleep and __wakeup exceptions +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED --SKIPIF-- diff --git a/tests/igbinary_033.phpt b/tests/igbinary_033.phpt index 76cafc15..ba62fd3b 100644 --- a/tests/igbinary_033.phpt +++ b/tests/igbinary_033.phpt @@ -6,7 +6,7 @@ if(!extension_loaded('igbinary')) { echo "skip no igbinary"; } --FILE-- -parent = $obj; } } +error_reporting(E_ALL); $obj1 = new Foo(); diff --git a/tests/igbinary_034.phpt b/tests/igbinary_034.phpt index 4b129222..5f4be860 100644 --- a/tests/igbinary_034.phpt +++ b/tests/igbinary_034.phpt @@ -6,7 +6,7 @@ if(!extension_loaded('igbinary')) { echo "skip no igbinary"; } --FILE-- - ---EXPECT-- +--EXPECTF-- +After testing 100 random values + +Warning: igbinary_unserialize: received more data to unserialize than expected in %s on line 48 +NULL diff --git a/tests/igbinary_041.phpt b/tests/igbinary_041.phpt index f2a904df..31a8c9b5 100644 --- a/tests/igbinary_041.phpt +++ b/tests/igbinary_041.phpt @@ -1,7 +1,7 @@ --TEST-- Check for double NaN, Inf, -Inf, 0, and -0. IEEE 754 doubles --FILE-- -getVersion(), '3.1.7', '<')) { - echo "skip require APC version 3.1.7 or above"; -} - ---INI-- -apc.enable_cli=1 -apc.serializer=igbinary ---FILE-- - - int(10) -} diff --git a/tests/igbinary_045b.phpt b/tests/igbinary_045b.phpt index 96613042..9b037f1c 100644 --- a/tests/igbinary_045b.phpt +++ b/tests/igbinary_045b.phpt @@ -23,10 +23,10 @@ class Bar { } $a = new Bar; -apc_store('foo', $a); +apcu_store('foo', $a); unset($a); -var_dump(apc_fetch('foo')); +var_dump(apcu_fetch('foo')); --EXPECTF-- igbinary object(Bar)#%d (1) { diff --git a/tests/igbinary_045c.phpt b/tests/igbinary_045c.phpt new file mode 100644 index 00000000..5508075e --- /dev/null +++ b/tests/igbinary_045c.phpt @@ -0,0 +1,102 @@ +--TEST-- +APCu serializer registration - more data types +--INI-- +apc.enable_cli=1 +apc.serializer=igbinary +--SKIPIF-- +getVersion(), '5.1.6') < 0) { + echo "skip require APCu version 5.1.6 or above"; + return; +} +?> +--FILE-- +foo = $a; +apcu_store('objloop', $a); +unset($a); + +var_dump(apcu_fetch('objloop')); + +apcu_store('nullval', null); +var_dump(apcu_fetch('nullval')); + +apcu_store('intval', 777); +var_dump(apcu_fetch('intval')); + +$o = new stdClass(); +$o->prop = 5; +$a = [$o, $o]; +apcu_store('simplearrayval', $a); +$unserialized = apcu_fetch('simplearrayval'); +var_dump($unserialized); +if ($unserialized[0] === $unserialized[1]) { + echo "SAME\n"; +} else { + echo "DIFFERENT\n"; + printf("%s\n", bin2hex(igbinary_serialize($a))); +} +unset($o); +unset($a); +unset($unserialized); + +$o = new stdClass(); +$o->prop = 6; +$a = [&$o, &$o]; +apcu_store('refarrayval', $a); +$unserialized = apcu_fetch('refarrayval'); +var_dump($unserialized); +if ($unserialized[0] === $unserialized[1]) { + echo "SAME\n"; +} else { + echo "DIFFERENT\n"; + printf("%s\n", bin2hex(igbinary_serialize($a))); +} +?> +--EXPECTF-- +igbinary +object(Bar)#%d (1) { + ["foo"]=> + *RECURSION* +} +NULL +int(777) +array(2) { + [0]=> + object(stdClass)#%d (1) { + ["prop"]=> + int(5) + } + [1]=> + object(stdClass)#%d (1) { + ["prop"]=> + int(5) + } +} +SAME +array(2) { + [0]=> + &object(stdClass)#%d (1) { + ["prop"]=> + int(6) + } + [1]=> + &object(stdClass)#%d (1) { + ["prop"]=> + int(6) + } +} +SAME diff --git a/tests/igbinary_046.phpt b/tests/igbinary_046.phpt index aef68ca2..8fae4202 100644 --- a/tests/igbinary_046.phpt +++ b/tests/igbinary_046.phpt @@ -1,21 +1,22 @@ --TEST-- Correctly unserialize scalar refs. ---SKIPIF-- --INI-- igbinary.compact_strings = On --FILE-- - &string(1) "V" diff --git a/tests/igbinary_046b.phpt b/tests/igbinary_046b.phpt new file mode 100644 index 00000000..5665c3c2 --- /dev/null +++ b/tests/igbinary_046b.phpt @@ -0,0 +1,33 @@ +--TEST-- +Correctly unserialize multiple object refs. +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- + + &string(1) "V" + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + &string(1) "V" +} diff --git a/tests/igbinary_046c.phpt b/tests/igbinary_046c.phpt new file mode 100644 index 00000000..d4714f26 --- /dev/null +++ b/tests/igbinary_046c.phpt @@ -0,0 +1,33 @@ +--TEST-- +Correctly unserialize multiple array refs. +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- + + &string(1) "V" + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + &string(1) "V" +} diff --git a/tests/igbinary_046d.phpt b/tests/igbinary_046d.phpt new file mode 100644 index 00000000..7baf9080 --- /dev/null +++ b/tests/igbinary_046d.phpt @@ -0,0 +1,66 @@ +--TEST-- +Correctly unserialize multiple object refs and non-refs. +--INI-- +igbinary.compact_strings = On +--FILE-- + + object(stdClass)#%d (0) { + } + [1]=> + &object(stdClass)#%d (0) { + } + [2]=> + &object(stdClass)#%d (0) { + } + [3]=> + object(stdClass)#%d (0) { + } +} +a:4:{i:0;O:8:"stdClass":0:{}i:1;R:2;i:2;R:2;i:3;r:2;} +00000002140406001708737464436c61737314000601252201060225220106032201 +a:4:{i:0;O:8:"stdClass":0:{}i:1;R:2;i:2;R:2;i:3;r:2;} +array(4) { + [0]=> + object(stdClass)#%d (0) { + } + [1]=> + &object(stdClass)#%d (0) { + } + [2]=> + &object(stdClass)#%d (0) { + } + [3]=> + object(stdClass)#%d (0) { + } +} +array(4) { + [0]=> + object(stdClass)#%d (0) { + } + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + object(stdClass)#%d (0) { + } +} diff --git a/tests/igbinary_047.phpt b/tests/igbinary_047.phpt index ef135159..317da80f 100644 --- a/tests/igbinary_047.phpt +++ b/tests/igbinary_047.phpt @@ -2,11 +2,6 @@ Check for serialization handler, SessionHandlerInterface --SKIPIF-- = 5.4.0) -if (version_compare(phpversion(), "5.4.0", "<")) { - exit("skip php version less than 5.4.x"); -} - if (!extension_loaded('session')) { exit('skip session extension not loaded'); } @@ -20,8 +15,7 @@ $array = preg_grep('/^igbinary session support.*yes/', $array); if (!$array) { exit('skip igbinary session handler not available'); } - - +?> --FILE-- Returns the number of deleted sessions on success, or false on failure. + // > Note this value is returned internally to PHP for processing. + public function gc($time): int { + return 0; } } @@ -68,7 +64,7 @@ class Bar { ini_set('session.serialize_handler', 'igbinary'); $handler = new S(); -session_set_save_handler($handler, true); +@session_set_save_handler($handler, true); $db_object = new Foo(); $session_object = new Bar(); @@ -89,7 +85,7 @@ echo $output; * to pass, differences between the output and the * expected text are interpreted as failure * - * see php5/README.TESTING for further information on + * see TESTING.md for further information on * writing regression tests */ ?> diff --git a/tests/igbinary_048.phpt b/tests/igbinary_048.phpt new file mode 100644 index 00000000..c9a9d3d2 --- /dev/null +++ b/tests/igbinary_048.phpt @@ -0,0 +1,26 @@ +--TEST-- +Object test, __set not called for private attr in extended class +--FILE-- +a = [1, 2, 3]; +$x->nonexistent = 'aaa'; + +igbinary_unserialize(igbinary_serialize($x)); +--EXPECT-- +magic function called for nonexistent with 'aaa' diff --git a/tests/igbinary_048b.phpt b/tests/igbinary_048b.phpt new file mode 100644 index 00000000..e6203608 --- /dev/null +++ b/tests/igbinary_048b.phpt @@ -0,0 +1,26 @@ +--TEST-- +Object test, __set not called for private attr in extended class +--FILE-- +a = [1, 2, 3]; +$x->nonexistent = 'aaa'; + +unserialize(serialize($x)); +--EXPECT-- +magic function called for nonexistent with 'aaa' diff --git a/tests/igbinary_049.phpt b/tests/igbinary_049.phpt new file mode 100644 index 00000000..73c4bdc4 --- /dev/null +++ b/tests/igbinary_049.phpt @@ -0,0 +1,120 @@ +--TEST-- +Correctly unserialize multiple references in arrays +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- +prop = &$a[8]; +$a[11] = &$a[10]; +$a[12] = $a[9]; +$ig_ser = igbinary_serialize($a); +printf("ig_ser=%s\n", bin2hex($ig_ser)); +$ig = igbinary_unserialize($ig_ser); +var_dump($ig); +$f = &$ig[3]; +$f = 'V'; +$g = &$ig[5]; +$g = 'H'; +$h = $ig[10]; +$h->prop = 'S'; +var_dump($ig); +--EXPECTF-- +ig_ser=00000002140d0600251101410601250101060225010106032501010604250406052501020606251703466f6f1400060725220306082522030609140106000621060a251708737464436c6173731401110470726f70252203060b252205060c0104 +array(13) { + [0]=> + &string(1) "A" + [1]=> + &string(1) "A" + [2]=> + &string(1) "A" + [3]=> + &string(1) "A" + [4]=> + &bool(false) + [5]=> + &bool(false) + [6]=> + &object(Foo)#%d (0) { + } + [7]=> + &object(Foo)#%d (0) { + } + [8]=> + &object(Foo)#%d (0) { + } + [9]=> + array(1) { + [0]=> + int(33) + } + [10]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &object(Foo)#%d (0) { + } + } + [11]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &object(Foo)#%d (0) { + } + } + [12]=> + array(1) { + [0]=> + int(33) + } +} +array(13) { + [0]=> + &string(1) "V" + [1]=> + &string(1) "V" + [2]=> + &string(1) "V" + [3]=> + &string(1) "V" + [4]=> + &string(1) "H" + [5]=> + &string(1) "H" + [6]=> + &string(1) "S" + [7]=> + &string(1) "S" + [8]=> + &string(1) "S" + [9]=> + array(1) { + [0]=> + int(33) + } + [10]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + [11]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + [12]=> + array(1) { + [0]=> + int(33) + } +} diff --git a/tests/igbinary_049b.phpt b/tests/igbinary_049b.phpt new file mode 100644 index 00000000..fb1389e2 --- /dev/null +++ b/tests/igbinary_049b.phpt @@ -0,0 +1,75 @@ +--TEST-- +Correctly unserialize multiple references in objects +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- +x0 = NULL; +$a->x1 = &$a->x0; +$a->x2 = &$a->x1; +$a->x3 = &$a->x2; +$a->x4 = false; +$a->x5 = &$a->x4; +$a->x6 = new Foo(); +$a->x7 = &$a->x6; +$a->x8 = &$a->x7; +$a->x9 = array(33); +$a->x10 = new stdClass(); +$a->x10->prop = &$a->x8; +$a->x11 = &$a->x10; +$a->x12 = $a->x9; +$ig_ser = igbinary_serialize($a); +printf("ig_ser=%s\n", bin2hex($ig_ser)); +$ig = igbinary_unserialize($ig_ser); +$f = &$ig->x3; +$f = 'V'; +$g = &$ig->x5; +$g = 'H'; +$h = $ig->x10; +$h->prop = 'S'; +var_dump($ig); +--EXPECTF-- +ig_ser=000000021708737464436c617373140d1102783025001102783125010111027832250101110278332501011102783425041102783525010211027836251703466f6f14001102783725220311027838252203110278391401060006211103783130251a001401110470726f70252203110378313125220511037831320104 +object(stdClass)#%d (13) { + ["x0"]=> + &string(1) "V" + ["x1"]=> + &string(1) "V" + ["x2"]=> + &string(1) "V" + ["x3"]=> + &string(1) "V" + ["x4"]=> + &string(1) "H" + ["x5"]=> + &string(1) "H" + ["x6"]=> + &string(1) "S" + ["x7"]=> + &string(1) "S" + ["x8"]=> + &string(1) "S" + ["x9"]=> + array(1) { + [0]=> + int(33) + } + ["x10"]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + ["x11"]=> + &object(stdClass)#%d (1) { + ["prop"]=> + &string(1) "S" + } + ["x12"]=> + array(1) { + [0]=> + int(33) + } +} diff --git a/tests/igbinary_050.phpt b/tests/igbinary_050.phpt new file mode 100644 index 00000000..ab042a2e --- /dev/null +++ b/tests/igbinary_050.phpt @@ -0,0 +1,40 @@ +--TEST-- +Correctly unserialize cyclic object references +--SKIPIF-- +--INI-- +igbinary.compact_strings = On +--FILE-- +foo = &$a; +$a->bar = &$a; +$b = new stdClass(); +$b->cyclic = &$a; +printf("%s\n", serialize($b)); +$ig_ser = igbinary_serialize($b); +printf("%s\n", bin2hex($ig_ser)); +$ig = igbinary_unserialize($ig_ser); +printf("%s\n", serialize($ig)); +var_dump($ig); +$f = &$ig->cyclic->foo; +$f = 'V'; +var_dump($ig); +// Note: While the php7 unserializer consistently makes a distinction between refs to an object and non-refs, +// the php5 serializer does not. +--EXPECTF-- +O:8:"stdClass":1:{s:6:"cyclic";O:8:"stdClass":2:{s:3:"foo";R:2;s:3:"bar";R:2;}} +000000021708737464436c617373140111066379636c6963251a0014021103666f6f2522011103626172252201 +O:8:"stdClass":1:{s:6:"cyclic";O:8:"stdClass":2:{s:3:"foo";R:2;s:3:"bar";R:2;}} +object(stdClass)#3 (1) { + ["cyclic"]=> + &object(stdClass)#4 (2) { + ["foo"]=> + *RECURSION* + ["bar"]=> + *RECURSION* + } +} +object(stdClass)#3 (1) { + ["cyclic"]=> + &string(1) "V" +} diff --git a/tests/igbinary_051.phpt b/tests/igbinary_051.phpt new file mode 100644 index 00000000..5c1cfa5d --- /dev/null +++ b/tests/igbinary_051.phpt @@ -0,0 +1,48 @@ +--TEST-- +Object test, __wakeup (With multiple references) +--SKIPIF-- +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +a = $a; + $this->b = $b; + } + + function __wakeup() { + $this->b = $this->a * 3; + } +} + +function main() { + $o = new Obj(1, 2); + $variable = array(&$o, &$o); + $serialized = igbinary_serialize($variable); + $unserialized = igbinary_unserialize($serialized); + + echo substr(bin2hex($serialized), 8), "\n"; + echo $unserialized[0]->b === 3 && $unserialized[0]->a === 1 ? 'OK' : 'ERROR'; + echo "\n"; + $unserialized[0] = 'a'; + var_dump($unserialized); +} + +main(); +--EXPECT-- +140206002517034f626a1402110161060111016206020601252201 +OK +array(2) { + [0]=> + &string(1) "a" + [1]=> + &string(1) "a" +} diff --git a/tests/igbinary_052.phpt b/tests/igbinary_052.phpt new file mode 100644 index 00000000..43f43bf2 --- /dev/null +++ b/tests/igbinary_052.phpt @@ -0,0 +1,83 @@ +--TEST-- +Object Serializable interface can be serialized in references +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +a = $a; + $this->b = $b; + } + + public function serialize() { + $c = self::$count++; + echo "call serialize\n"; + return pack('NN', $this->a, $this->b); + } + + public function unserialize($serialized) { + $tmp = unpack('N*', $serialized); + $this->__construct($tmp[1], $tmp[2]); + $c = self::$count++; + echo "call unserialize\n"; + } +} + +function main() { + $a = new Obj(1, 0); + $b = new Obj(42, 43); + $variable = array(&$a, &$a, $b); + $serialized = igbinary_serialize($variable); + printf("%s\n", bin2hex($serialized)); + $unserialized = igbinary_unserialize($serialized); + var_dump($unserialized); + $unserialized[0] = 'A'; + var_dump($unserialized[1]); +} +main(); +--EXPECTF-- +call serialize +call serialize +00000002140306002517034f626a1d080000000100000000060125220106021a001d080000002a0000002b +call unserialize +call unserialize +array(3) { + [0]=> + &object(Obj)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(0) + } + [1]=> + &object(Obj)#%d (2) { + ["a"]=> + int(1) + ["b"]=> + int(0) + } + [2]=> + object(Obj)#%d (2) { + ["a"]=> + int(42) + ["b"]=> + int(43) + } +} +string(1) "A" diff --git a/tests/igbinary_053.phpt b/tests/igbinary_053.phpt new file mode 100644 index 00000000..521d49b6 --- /dev/null +++ b/tests/igbinary_053.phpt @@ -0,0 +1,59 @@ +--TEST-- +__wakeup can modify properties without affecting other objects +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +a = $a; + } + + public function __wakeup() { + echo "call wakeup\n"; + $this->a[] = "end"; + } +} + +function main() { + $array = ["test"]; // array (not a reference, but should be copied on write) + $a = new Obj($array); + $b = new Obj($array); + $variable = [$a, $b]; + $serialized = igbinary_serialize($variable); + printf("%s\n", bin2hex($serialized)); + $unserialized = igbinary_unserialize($serialized); + var_dump($unserialized); +} +main(); +--EXPECTF-- +000000021402060017034f626a14011101611401060011047465737406011a0014010e010102 +call wakeup +call wakeup +array(2) { + [0]=> + object(Obj)#%d (1) { + ["a"]=> + array(2) { + [0]=> + string(4) "test" + [1]=> + string(3) "end" + } + } + [1]=> + object(Obj)#%d (1) { + ["a"]=> + array(2) { + [0]=> + string(4) "test" + [1]=> + string(3) "end" + } + } +} diff --git a/tests/igbinary_054.phpt b/tests/igbinary_054.phpt new file mode 100644 index 00000000..3e2aaabc --- /dev/null +++ b/tests/igbinary_054.phpt @@ -0,0 +1,74 @@ +--TEST-- +__wakeup can add dynamic properties without affecting other objects +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +a being a dynamic property. + + function __construct($a) { + $this->a = $a; + } + + public function __wakeup() { + echo "Calling __wakeup\n"; + for ($i = 0; $i < 10000; $i++) { + $this->{'b' . $i} = 42; + } + } +} + +function main() { + $array = array("roh"); // array (not a reference, but should be copied on write) + $a = new Obj($array); + $b = new Obj($array); + $c = new Obj(null); + $variable = array($a, $b, $c); + $serialized = igbinary_serialize($variable); + printf("%s\n", bin2hex($serialized)); + $unserialized = igbinary_unserialize($serialized); + echo "Called igbinary_unserialize\n"; + for ($a = 0; $a < 3; $a++) { + for ($i = 0; $i < 10000; $i++) { + if ($unserialized[$a]->{'b' . $i} !== 42) { + echo "Fail $a b$i\n"; + return; + } + unset($unserialized[$a]->{'b' . $i}); + } + } + var_dump($unserialized); +} +main(); +--EXPECTF-- +000000021403060017034f626a1401110161140106001103726f6806011a0014010e01010206021a0014010e0100 +Calling __wakeup +Calling __wakeup +Calling __wakeup +Called igbinary_unserialize +array(3) { + [0]=> + object(Obj)#%d (1) { + ["a"]=> + array(1) { + [0]=> + string(3) "roh" + } + } + [1]=> + object(Obj)#%d (1) { + ["a"]=> + array(1) { + [0]=> + string(3) "roh" + } + } + [2]=> + object(Obj)#%d (1) { + ["a"]=> + NULL + } +} diff --git a/tests/igbinary_055.phpt b/tests/igbinary_055.phpt new file mode 100644 index 00000000..33c03616 --- /dev/null +++ b/tests/igbinary_055.phpt @@ -0,0 +1,35 @@ +--TEST-- +__wakeup can replace a copy of the object referring to the root node. +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +a = $a; + } + + public function __wakeup() { + echo "Calling __wakeup\n"; + $this->a = "replaced"; + } +} + +$a = new stdClass(); +$a->obj = new Obj($a);; +$serialized = igbinary_serialize($a); +printf("%s\n", bin2hex($serialized)); +$unserialized = igbinary_unserialize($serialized); +var_dump($unserialized); +--EXPECTF-- +000000021708737464436c617373140111036f626a17034f626a14011101612200 +Calling __wakeup +object(stdClass)#%d (1) { + ["obj"]=> + object(Obj)#%d (1) { + ["a"]=> + string(8) "replaced" + } +} diff --git a/tests/igbinary_057.phpt b/tests/igbinary_057.phpt new file mode 100644 index 00000000..7d86d134 --- /dev/null +++ b/tests/igbinary_057.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test serializing more strings than the capacity of the initial strings table. +--SKIPIF-- +--FILE-- + +--EXPECT-- +1432060011016106011101620602110163060311016406041101650605110166060611016706071101680608110169060911016a060a11016b060b11016c060c11016d060d11016e060e11016f060f11017006101101710611110172061211017306131101740614110175061511017606161101770617110178061811017906190e00061a0e01061b0e02061c0e03061d0e04061e0e05061f0e0606200e0706210e0806220e0906230e0a06240e0b06250e0c06260e0d06270e0e06280e0f06290e10062a0e11062b0e12062c0e13062d0e14062e0e15062f0e1606300e1706310e18 +a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y diff --git a/tests/igbinary_058.phpt b/tests/igbinary_058.phpt new file mode 100644 index 00000000..5b007bcb --- /dev/null +++ b/tests/igbinary_058.phpt @@ -0,0 +1,67 @@ +--TEST-- +Should not call __destruct if __wakeup throws an exception +--INI-- +igbinary.compact_strings = On +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +id = $id; + $this->throws = $throws; + $this->dynamic = "original"; + } + public function __wakeup() { + printf("Calling __wakeup %s\n", $this->id); + $this->dynamic = "copy"; + if ($this->throws) { + throw new Exception("__wakeup threw"); + } + } + + public function __destruct() { + printf("Calling __destruct %s dynamic=%s\n", $this->id, $this->dynamic); + } +} +function main() { + $a = new Thrower("a", true); + $serialized = igbinary_serialize($a); + try { + igbinary_unserialize($serialized); + } catch (Exception $e) { + printf("Caught %s\n", $e->getMessage()); + } + $a = null; + print("Done a\n"); + $b = new Thrower("b", false); + $serialized = igbinary_serialize($b); + $bCopy = igbinary_unserialize($serialized); + print("Unserialized b\n"); + var_dump($bCopy); + + $bCopy = null; + $b = null; + print("Done b\n"); +} +main(); +--EXPECT-- +Calling __wakeup a +Caught __wakeup threw +Calling __destruct a dynamic=original +Done a +Calling __wakeup b +Unserialized b +object(Thrower)#2 (3) { + ["id"]=> + string(1) "b" + ["throws"]=> + bool(false) + ["dynamic"]=> + string(4) "copy" +} +Calling __destruct b dynamic=copy +Calling __destruct b dynamic=original +Done b diff --git a/tests/igbinary_058b.phpt b/tests/igbinary_058b.phpt new file mode 100644 index 00000000..234f6bde --- /dev/null +++ b/tests/igbinary_058b.phpt @@ -0,0 +1,58 @@ +--TEST-- +Should not call __destruct if __wakeup throws an exception (in arrays) +--INI-- +igbinary.compact_strings = On +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +id = $id; + $this->throws = $throws; + $this->dynamic = "original"; + } + public function __wakeup() { + printf("Calling __wakeup %s\n", $this->id); + $this->dynamic = "copy"; + if ($this->throws) { + throw new Exception("__wakeup threw for id " . $this->id); + } + } + + public function __destruct() { + printf("Calling __destruct %s dynamic=%s\n", $this->id, $this->dynamic); + } +} +function main() { + $values = [ + 0 => new Thrower("a", false), + 'foo' => 'last', + 'key1' => new Thrower("b", false), + 2 => new Thrower("c", true), + 'last' => new Thrower("d", false), + ]; + $serialized = igbinary_serialize($values); + $values = null; + printf("Going to unserialize\n"); + try { + igbinary_unserialize($serialized); + } catch (Exception $e) { + printf("Caught %s\n", $e->getMessage()); + } +} +main(); +--EXPECT-- +Calling __destruct a dynamic=original +Calling __destruct b dynamic=original +Calling __destruct c dynamic=original +Calling __destruct d dynamic=original +Going to unserialize +Calling __wakeup a +Calling __wakeup b +Calling __wakeup c +Calling __destruct a dynamic=copy +Calling __destruct b dynamic=copy +Caught __wakeup threw for id c diff --git a/tests/igbinary_059.phpt b/tests/igbinary_059.phpt new file mode 100644 index 00000000..1590697d --- /dev/null +++ b/tests/igbinary_059.phpt @@ -0,0 +1,56 @@ +--TEST-- +igbinary_unserialize should never convert from packed array to hash when references exist (Bug #48) +--SKIPIF-- +--FILE-- += 16) is added (converted to a hash), causing a segfault. + foreach (array(0, 50) as $i) { + $inner = new stdClass(); + $inner->a = $i; + $result[0][0][$i] = $inner; + $result[1][] = $inner; + } + $serialized = igbinary_serialize($result); + printf("%s\n", bin2hex(substr($serialized, 4))); + flush(); + $unserialized = igbinary_unserialize($serialized); + var_dump($unserialized); +} +main(); +?> +--EXPECTF-- +1402060014010600140206001708737464436c6173731401110161060006321a0014010e010632060114020600220306012204 +array(2) { + [0]=> + array(1) { + [0]=> + array(2) { + [0]=> + object(stdClass)#%d (1) { + ["a"]=> + int(0) + } + [50]=> + object(stdClass)#%d (1) { + ["a"]=> + int(50) + } + } + } + [1]=> + array(2) { + [0]=> + object(stdClass)#%d (1) { + ["a"]=> + int(0) + } + [1]=> + object(stdClass)#%d (1) { + ["a"]=> + int(50) + } + } +} diff --git a/tests/igbinary_060.phpt b/tests/igbinary_060.phpt new file mode 100644 index 00000000..fd264cb7 --- /dev/null +++ b/tests/igbinary_060.phpt @@ -0,0 +1,31 @@ +--TEST-- +Igbinary_unserialize_header warning +--SKIPIF-- +--FILE-- +c = $this->b . 'OnWakeup'; + } +} + +error_reporting(E_ALL); +// Start session +session_start(); +// The serialization of DateTime varies in php5 and php 7. +$_SESSION['date'] = new \DateTime('@1234567890'); +$a = new A(); +$a->b = 'value'; +$_SESSION['a'] = $a; +var_dump($_SESSION['date']->getTimestamp()); +$mydata = session_encode(); +unset($_SESSION['date']); +var_dump(session_decode($mydata)); +var_dump($_SESSION['date']->getTimestamp()); +var_dump($_SESSION['a']->c); +?> +--EXPECT-- +int(1234567890) +bool(true) +int(1234567890) +string(13) "valueOnWakeup" diff --git a/tests/igbinary_062.phpt b/tests/igbinary_062.phpt new file mode 100644 index 00000000..0963f4b0 --- /dev/null +++ b/tests/igbinary_062.phpt @@ -0,0 +1,56 @@ +--TEST-- +igbinary should not call __wakeup() if Serializable::unserialize was used to unserialize the object data (like `unserialize`) +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +prop . "\n"; + return $this->prop; + } + public function unserialize($data) { + echo "In unserialize $data\n"; + $this->prop = $data; + } + + public function __wakeup() { + echo "In __wakeup, unexpectedly\n"; + } +} + +function testA() { + $a = new A(); + $a->prop = 'other'; + $ser = serialize($a); + $b = unserialize($ser); + var_dump($b); + + $c = new A(); + $c->prop = 'igprop'; + $serC = igbinary_serialize($c); + echo bin2hex($serC) . "\n"; + $d = igbinary_unserialize($serC); + var_dump($d); + +} + +testA(); +?> +--EXPECTF-- +In serialize other +In unserialize other +object(A)#%d (1) { + ["prop"]=> + string(5) "other" +} +In serialize igprop +000000021701411d06696770726f70 +In unserialize igprop +object(A)#%d (1) { + ["prop"]=> + string(6) "igprop" +} diff --git a/tests/igbinary_063_php7.phpt b/tests/igbinary_063_php7.phpt new file mode 100644 index 00000000..391bdeb1 --- /dev/null +++ b/tests/igbinary_063_php7.phpt @@ -0,0 +1,63 @@ +--TEST-- +Accessing unserialized numbers. +--SKIPIF-- += 70200) { + echo "Skip php 7.1 or 7.0 required"; +} +?> +--FILE-- + 'x', 1234 => 33); +var_dump($data); +$x = "1"; +$y = 1; +$z = "1234"; +$w = 1234; +var_dump(isset($data->{$x}) ? $data->{$x} : "unset"); +error_reporting(0); +$str = igbinary_serialize($data); + +$unserialized = igbinary_unserialize($str); +var_dump($unserialized); +var_dump(isset($unserialized->{$x}) ? $unserialized->{$x} : "unset str"); +var_dump(isset($unserialized->{$y}) ? $unserialized->{$y} : "unset int"); +var_dump(isset($unserialized->{$z}) ? $unserialized->{$z} : "unset str 1234"); +var_dump(isset($unserialized->{$w}) ? $unserialized->{$w} : "unset int 1234"); +var_dump(isset($unserialized->{-1}) ? $unserialized->{-1} : "unset int -1"); +?> +--EXPECT-- +object(stdClass)#1 (5) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) + [-1]=> + string(1) "x" + [1234]=> + int(33) +} +string(5) "unset" +object(stdClass)#2 (5) { + ["0"]=> + int(1) + ["1"]=> + int(2) + ["2"]=> + int(3) + ["-1"]=> + string(1) "x" + ["1234"]=> + int(33) +} +int(2) +int(2) +int(33) +int(33) +string(1) "x" diff --git a/tests/igbinary_063_php72.phpt b/tests/igbinary_063_php72.phpt new file mode 100644 index 00000000..6f41ff4b --- /dev/null +++ b/tests/igbinary_063_php72.phpt @@ -0,0 +1,63 @@ +--TEST-- +Accessing unserialized numbers. +--SKIPIF-- + +--FILE-- + 'x', 1234 => 33); +var_dump($data); +$x = "1"; +$y = 1; +$z = "1234"; +$w = 1234; +var_dump(isset($data->{$x}) ? $data->{$x} : "unset"); +error_reporting(0); +$str = igbinary_serialize($data); + +$unserialized = igbinary_unserialize($str); +var_dump($unserialized); +var_dump(isset($unserialized->{$x}) ? $unserialized->{$x} : "unset str"); +var_dump(isset($unserialized->{$y}) ? $unserialized->{$y} : "unset int"); +var_dump(isset($unserialized->{$z}) ? $unserialized->{$z} : "unset str 1234"); +var_dump(isset($unserialized->{$w}) ? $unserialized->{$w} : "unset int 1234"); +var_dump(isset($unserialized->{-1}) ? $unserialized->{-1} : "unset int -1"); +?> +--EXPECT-- +object(stdClass)#1 (5) { + ["0"]=> + int(1) + ["1"]=> + int(2) + ["2"]=> + int(3) + ["-1"]=> + string(1) "x" + ["1234"]=> + int(33) +} +int(2) +object(stdClass)#2 (5) { + ["0"]=> + int(1) + ["1"]=> + int(2) + ["2"]=> + int(3) + ["-1"]=> + string(1) "x" + ["1234"]=> + int(33) +} +int(2) +int(2) +int(33) +int(33) +string(1) "x" diff --git a/tests/igbinary_064.phpt b/tests/igbinary_064.phpt new file mode 100644 index 00000000..af75e49f --- /dev/null +++ b/tests/igbinary_064.phpt @@ -0,0 +1,139 @@ +--TEST-- +Works when there are hash collisions in strings when serializing. +--SKIPIF-- + +--FILE-- +x = $x; + } +} +class Ez { + public $FyEz = 'EzEz'; +} + +class G8 { + public $FyG8; +} + +$data = array(new Fy('G8G8'), new Fy('EzG8'), new Ez(), new G8(), new Ez(), 'G8' => new G8(), 'F8Ez' => new G8(), array(new G8())); +var_dump($data); +echo "\n"; +$str = igbinary_serialize($data); +echo bin2hex($str) . "\n"; +$unserialized = igbinary_unserialize($str); +var_dump($unserialized); +echo "\n"; +var_export(serialize($data) === serialize($unserialized)); +?> +--EXPECT-- +array(8) { + [0]=> + object(Fy)#1 (2) { + ["EzFy"]=> + int(2) + ["x"]=> + string(4) "G8G8" + } + [1]=> + object(Fy)#2 (2) { + ["EzFy"]=> + int(2) + ["x"]=> + string(4) "EzG8" + } + [2]=> + object(Ez)#3 (1) { + ["FyEz"]=> + string(4) "EzEz" + } + [3]=> + object(G8)#4 (1) { + ["FyG8"]=> + NULL + } + [4]=> + object(Ez)#5 (1) { + ["FyEz"]=> + string(4) "EzEz" + } + ["G8"]=> + object(G8)#6 (1) { + ["FyG8"]=> + NULL + } + ["F8Ez"]=> + object(G8)#7 (1) { + ["FyG8"]=> + NULL + } + [5]=> + array(1) { + [0]=> + object(G8)#8 (1) { + ["FyG8"]=> + NULL + } + } +} + +00000002140806001702467914021104457a4679060211017811044738473806011a0014020e0106020e021104457a473806021702457a140111044679457a1104457a457a06031702473814011104467947380006041a0514010e060e070e081a0814010e090011044638457a1a0814010e09000605140106001a0814010e0900 +array(8) { + [0]=> + object(Fy)#9 (2) { + ["EzFy"]=> + int(2) + ["x"]=> + string(4) "G8G8" + } + [1]=> + object(Fy)#10 (2) { + ["EzFy"]=> + int(2) + ["x"]=> + string(4) "EzG8" + } + [2]=> + object(Ez)#11 (1) { + ["FyEz"]=> + string(4) "EzEz" + } + [3]=> + object(G8)#12 (1) { + ["FyG8"]=> + NULL + } + [4]=> + object(Ez)#13 (1) { + ["FyEz"]=> + string(4) "EzEz" + } + ["G8"]=> + object(G8)#14 (1) { + ["FyG8"]=> + NULL + } + ["F8Ez"]=> + object(G8)#15 (1) { + ["FyG8"]=> + NULL + } + [5]=> + array(1) { + [0]=> + object(G8)#16 (1) { + ["FyG8"]=> + NULL + } + } +} + +true diff --git a/tests/igbinary_065.phpt b/tests/igbinary_065.phpt new file mode 100644 index 00000000..7d47bc40 --- /dev/null +++ b/tests/igbinary_065.phpt @@ -0,0 +1,50 @@ +--TEST-- +Don't emit zval has unknown type 0 (IS_UNDEF) +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +x); + unset($this->y); + unset($this->z); + $this->set = 'setVal'; + $this->priv = null; + $this->omitted = 'otherVal'; + + return ['kept', 'x', 'y', 'z', 'set', 'priv']; + } +} +error_reporting(E_ALL); +// TODO: emit 'Notice: igbinary_serialize(): "x" returned as member variable from __sleep() but does not exist' instead. +$serialized = igbinary_serialize(new MyClass()); +echo bin2hex($serialized) . "\n"; +var_export_normalized(igbinary_unserialize($serialized)); +echo "\n"; +?> +--EXPECTF-- +Notice: igbinary_serialize(): "x" returned as member variable from __sleep() but does not exist in %s on line 25 + +Notice: igbinary_serialize(): "y" returned as member variable from __sleep() but does not exist in %s on line 25 + +Notice: igbinary_serialize(): "z" returned as member variable from __sleep() but does not exist in %s on line 25 +0000000217074d79436c617373140611046b6570740602110178001104002a007900110a004d79436c617373007a001106002a00736574110673657456616c110d004d79436c617373007072697600 +MyClass::__set_state(array( + 'kept' => 2, + 'x' => NULL, + 'y' => NULL, + 'z' => NULL, + 'set' => 'setVal', + 'priv' => NULL, + 'omitted' => 'myVal', +)) diff --git a/tests/igbinary_066.phpt b/tests/igbinary_066.phpt new file mode 100644 index 00000000..162e9632 --- /dev/null +++ b/tests/igbinary_066.phpt @@ -0,0 +1,75 @@ +--TEST-- +Test serializing different empty arrays +--FILE-- + array(), '2nd' => array(), '3rd' => array())))); +echo "\n"; +var_dump(igbinary_unserialize(igbinary_serialize(array('1st' => $a, '2nd' => $a, '3rd' => $a)))); +echo "\n"; +$result = igbinary_unserialize(igbinary_serialize(array('1st' => $a, '2nd' => &$a, '3rd' => &$a, '4th' => array()))); +var_dump($result); +$result['2nd'][] = 2; +var_dump($result); +echo "\n"; +?> +--EXPECT-- +array(3) { + ["1st"]=> + array(0) { + } + ["2nd"]=> + array(0) { + } + ["3rd"]=> + array(0) { + } +} + +array(3) { + ["1st"]=> + array(0) { + } + ["2nd"]=> + array(0) { + } + ["3rd"]=> + array(0) { + } +} + +array(4) { + ["1st"]=> + array(0) { + } + ["2nd"]=> + &array(0) { + } + ["3rd"]=> + &array(0) { + } + ["4th"]=> + array(0) { + } +} +array(4) { + ["1st"]=> + array(0) { + } + ["2nd"]=> + &array(1) { + [0]=> + int(2) + } + ["3rd"]=> + &array(1) { + [0]=> + int(2) + } + ["4th"]=> + array(0) { + } +} diff --git a/tests/igbinary_067.phpt b/tests/igbinary_067.phpt new file mode 100644 index 00000000..fd36cc56 --- /dev/null +++ b/tests/igbinary_067.phpt @@ -0,0 +1,55 @@ +--TEST-- +Test serializing multiple reference groups to the same empty array +--SKIPIF-- + +--FILE-- + $value) { + echo "$k: " . json_encode($value) . "\n"; + } +} + +function main() { + $a = array(); + $b = $a; + $c = $a; + $value = array(&$b, $a, &$b, &$c, &$c); + $ser = igbinary_serialize($value); + echo bin2hex($ser) . "\n"; + $v = igbinary_unserialize($ser); + dump($v); + $v[0][] = 2; + dump($v); + $v[3][] = 3; + dump($v); + var_export($a); +} +main(); +?> +--EXPECT-- +000000021405060025140006011400060225010106032514000604250103 +5 values +0: [] +1: [] +2: [] +3: [] +4: [] +5 values +0: [2] +1: [] +2: [2] +3: [] +4: [] +5 values +0: [2] +1: [] +2: [2] +3: [3] +4: [3] +array ( +) diff --git a/tests/igbinary_068.phpt b/tests/igbinary_068.phpt new file mode 100644 index 00000000..0fb07b98 --- /dev/null +++ b/tests/igbinary_068.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test serializing and unserializing PHP_INT_MIN +--FILE-- + +--EXPECT-- +true +true +true diff --git a/tests/igbinary_069.phpt b/tests/igbinary_069.phpt new file mode 100644 index 00000000..18b8f80c --- /dev/null +++ b/tests/igbinary_069.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test serializing and unserializing many duplicate strings +--FILE-- + +--FILE-- + $this->prop, 42 => $this->prop2]; + } + public function __unserialize(array $data) { + $this->prop = 'unser' . $data["value"]; + $this->prop2 = 'unser' . $data[42]; + } +} +$test = new Test; +$test->prop = "foobar"; +$test->prop2 = "barfoo"; +$s = igbinary_serialize($test); +echo bin2hex($s) . "\n"; +var_dump(igbinary_unserialize($s)); +?> +--EXPECT-- +000000021704546573741402110576616c75651106666f6f626172062a1106626172666f6f +object(Test)#2 (2) { + ["prop"]=> + string(11) "unserfoobar" + ["prop2"]=> + string(11) "unserbarfoo" +} diff --git a/tests/igbinary_071.phpt b/tests/igbinary_071.phpt new file mode 100644 index 00000000..7b548590 --- /dev/null +++ b/tests/igbinary_071.phpt @@ -0,0 +1,118 @@ +--TEST-- +igbinary_unserialize with references to typed properties shall skip the references or fail +--SKIPIF-- += 80000) { echo "skip different error message format"; } +?> +--FILE-- +a = 1234; +$a->b = &$a->a; +var_dump(bin2hex($s = igbinary_serialize($a))); +var_dump(igbinary_unserialize($s)); +echo "Test B\n"; +$b = new B(); +$b->a = -1234; +$b->b = &$b->a; +var_dump(bin2hex($s = igbinary_serialize($b))); +var_dump(igbinary_unserialize($s)); + +$z = new Z(); +$z->a = null; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'A', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(igbinary_unserialize(str_replace('Z', 'B', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$z = new Z(); +$z->a = 1; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'C', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$z = new Z(); +$z->a = 'x'; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'C', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$z = new Z(); +$z->a = 1; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'D', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +/* +try { + var_dump(unserialize('O:1:"D":2:{s:1:"a";i:1;s:1:"b";R:2;}')); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + */ + +?> +--EXPECT-- +string(44) "000000021701411402110161250804d2110162250101" +object(A)#2 (2) { + ["a"]=> + &int(1234) + ["b"]=> + &int(1234) +} +Test B +string(44) "000000021701421402110161250904d2110162250101" +object(B)#3 (2) { + ["a"]=> + &int(-1234) + ["b"]=> + &int(-1234) +} +Typed property A::$a must be int, null used +Typed property B::$b must be int, null used +Typed property C::$b must be string, int used +Typed property C::$a must be int, string used +Reference with value of type int held by property D::$a of type int is not compatible with property D::$b of type float diff --git a/tests/igbinary_071_php8.phpt b/tests/igbinary_071_php8.phpt new file mode 100644 index 00000000..3c27334b --- /dev/null +++ b/tests/igbinary_071_php8.phpt @@ -0,0 +1,115 @@ +--TEST-- +igbinary_unserialize with references to typed properties shall skip the references or fail +--SKIPIF-- + +--FILE-- +a = 1234; +$a->b = &$a->a; +var_dump(bin2hex($s = igbinary_serialize($a))); +var_dump(igbinary_unserialize($s)); +echo "Test B\n"; +$b = new B(); +$b->a = -1234; +$b->b = &$b->a; +var_dump(bin2hex($s = igbinary_serialize($b))); +var_dump(igbinary_unserialize($s)); + +$z = new Z(); +$z->a = null; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'A', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + var_dump(igbinary_unserialize(str_replace('Z', 'B', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$z = new Z(); +$z->a = 1; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'C', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$z = new Z(); +$z->a = 'x'; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'C', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$z = new Z(); +$z->a = 1; +$z->b = &$z->a; +$s = igbinary_serialize($z); +try { + var_dump(igbinary_unserialize(str_replace('Z', 'D', $s))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +/* +try { + var_dump(unserialize('O:1:"D":2:{s:1:"a";i:1;s:1:"b";R:2;}')); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + */ + +?> +--EXPECT-- +string(44) "000000021701411402110161250804d2110162250101" +object(A)#2 (2) { + ["a"]=> + &int(1234) + ["b"]=> + &int(1234) +} +Test B +string(44) "000000021701421402110161250904d2110162250101" +object(B)#3 (2) { + ["a"]=> + &int(-1234) + ["b"]=> + &int(-1234) +} +Cannot assign null to property A::$a of type int +Cannot assign null to property B::$b of type int +Cannot assign int to property C::$b of type string +Cannot assign string to property C::$a of type int +Reference with value of type int held by property D::$a of type int is not compatible with property D::$b of type float \ No newline at end of file diff --git a/tests/igbinary_072.phpt b/tests/igbinary_072.phpt new file mode 100644 index 00000000..28a88387 --- /dev/null +++ b/tests/igbinary_072.phpt @@ -0,0 +1,52 @@ +--TEST-- +igbinary and __PHP_INCOMPLETE_CLASS +--FILE-- += 80200) { require_once __DIR__ . '/php82_suppress_dynamic_properties_warning.inc'; } +class Test {} +function test_ser_unser($obj) { + var_dump(bin2hex($s = igbinary_serialize($obj))); + $s = str_replace('Test', 'Best', $s); + $obj2 = igbinary_unserialize($s); + var_dump($obj2); + var_dump(bin2hex($s = igbinary_serialize($obj2))); + var_dump(igbinary_unserialize($s)); +} +test_ser_unser(new Test()); +echo "Testing with properties\n"; +$obj = new Test(); +$obj->dynamicProp = 'value'; +$obj->nullProp = null; +test_ser_unser($obj); +?> +--EXPECT-- +string(24) "000000021704546573741400" +object(__PHP_Incomplete_Class)#2 (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(4) "Best" +} +string(24) "000000021704426573741400" +object(__PHP_Incomplete_Class)#3 (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(4) "Best" +} +Testing with properties +string(86) "000000021704546573741402110b64796e616d696350726f70110576616c756511086e756c6c50726f7000" +object(__PHP_Incomplete_Class)#1 (3) { + ["__PHP_Incomplete_Class_Name"]=> + string(4) "Best" + ["dynamicProp"]=> + string(5) "value" + ["nullProp"]=> + NULL +} +string(86) "000000021704426573741402110b64796e616d696350726f70110576616c756511086e756c6c50726f7000" +object(__PHP_Incomplete_Class)#3 (3) { + ["__PHP_Incomplete_Class_Name"]=> + string(4) "Best" + ["dynamicProp"]=> + string(5) "value" + ["nullProp"]=> + NULL +} \ No newline at end of file diff --git a/tests/igbinary_073.phpt b/tests/igbinary_073.phpt new file mode 100644 index 00000000..e97b742a --- /dev/null +++ b/tests/igbinary_073.phpt @@ -0,0 +1,35 @@ +--TEST-- +igbinary and large Serializable +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +prop; + } + public function unserialize($s) { + $this->prop = $s; + } +} +function var_export_normalized($value) { + echo ltrim(var_export($value, true), "\\"); +} +$t = new Test(); +$t->prop = str_repeat('0', 256); +var_export_normalized(bin2hex($s = igbinary_serialize($t))); +echo "\n"; +var_export_normalized(igbinary_unserialize($s)); +echo "\n"; +$t->prop = str_repeat('0', 1 << 16); +$t2 = igbinary_unserialize(igbinary_serialize($t)); +var_dump($t2->prop === $t->prop); +?> +--EXPECT-- +'000000021704546573741e010030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030' +Test::__set_state(array( + 'prop' => '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', +)) +bool(true) diff --git a/tests/igbinary_074.phpt b/tests/igbinary_074.phpt new file mode 100644 index 00000000..d505f681 --- /dev/null +++ b/tests/igbinary_074.phpt @@ -0,0 +1,33 @@ +--TEST-- +igbinary and not enough data for array +--FILE-- + +--EXPECTF-- +One byte + +Warning: igbinary_unserialize_array: end-of-data in %s on line 3 +Two byte + +Warning: igbinary_unserialize_array: end-of-data in %s on line 5 + +Warning: igbinary_unserialize_array: end-of-data in %s on line 6 + +Warning: igbinary_unserialize_array: data size 0 smaller that requested array length 1. in %s on line 7 +Four byte + +Warning: igbinary_unserialize_array: end-of-data in %s on line 9 + +Warning: igbinary_unserialize_array: end-of-data in %s on line 10 + +Warning: igbinary_unserialize_array: data size 0 smaller that requested array length 1. in %s on line 11 diff --git a/tests/igbinary_075.phpt b/tests/igbinary_075.phpt new file mode 100644 index 00000000..03e9fca6 --- /dev/null +++ b/tests/igbinary_075.phpt @@ -0,0 +1,31 @@ +--TEST-- +igbinary and not enough data for array +--FILE-- + +--EXPECTF-- +string(18) "000000021701581400" +One byte +igbinary_unserialize_object_properties: end-of-data +NULL +Two byte +igbinary_unserialize_object_properties: end-of-data +NULL +igbinary_unserialize_object_properties: end-of-data +Four byte +igbinary_unserialize_object_properties: end-of-data +NULL +igbinary_unserialize_object_properties: end-of-data \ No newline at end of file diff --git a/tests/igbinary_076.phpt b/tests/igbinary_076.phpt new file mode 100644 index 00000000..c7a943bd --- /dev/null +++ b/tests/igbinary_076.phpt @@ -0,0 +1,19 @@ +--TEST-- +igbinary and edge cases unserializing array keys +--FILE-- + true]))); +// 3-byte string truncated in the middle of the array key +var_dump(igbinary_unserialize("\x00\x00\x00\x02\x14\x01\x11\x03\x6b\x65")); +// null instead of a string - skip over the entry +var_dump(igbinary_unserialize("\x00\x00\x00\x02\x14\x01\x00")); +?> +--EXPECTF-- +string(24) "00000002140111036b657905" +igbinary_unserialize_chararray: end-of-data +NULL +array(0) { +} diff --git a/tests/igbinary_077.phpt b/tests/igbinary_077.phpt new file mode 100644 index 00000000..432c6b98 --- /dev/null +++ b/tests/igbinary_077.phpt @@ -0,0 +1,14 @@ +--TEST-- +igbinary and large arrays +--FILE-- + +--EXPECTF-- +string(2062) "0000000215010006000600060106010602060206030603060406040605060506060606060706070608060806090609060a060a060b060b060c060c060d060d060e060e060f060f06100610061106110612061206130613061406140615061506160616061706170618061806190619061a061a061b061b061c061c061d061d061e061e061f061f06200620062106210622062206230623062406240625062506260626062706270628062806290629062a062a062b062b062c062c062d062d062e062e062f062f06300630063106310632063206330633063406340635063506360636063706370638063806390639063a063a063b063b063c063c063d063d063e063e063f063f06400640064106410642064206430643064406440645064506460646064706470648064806490649064a064a064b064b064c064c064d064d064e064e064f064f06500650065106510652065206530653065406540655065506560656065706570658065806590659065a065a065b065b065c065c065d065d065e065e065f065f06600660066106610662066206630663066406640665066506660666066706670668066806690669066a066a066b066b066c066c066d066d066e066e066f066f06700670067106710672067206730673067406740675067506760676067706770678067806790679067a067a067b067b067c067c067d067d067e067e067f067f06800680068106810682068206830683068406840685068506860686068706870688068806890689068a068a068b068b068c068c068d068d068e068e068f068f06900690069106910692069206930693069406940695069506960696069706970698069806990699069a069a069b069b069c069c069d069d069e069e069f069f06a006a006a106a106a206a206a306a306a406a406a506a506a606a606a706a706a806a806a906a906aa06aa06ab06ab06ac06ac06ad06ad06ae06ae06af06af06b006b006b106b106b206b206b306b306b406b406b506b506b606b606b706b706b806b806b906b906ba06ba06bb06bb06bc06bc06bd06bd06be06be06bf06bf06c006c006c106c106c206c206c306c306c406c406c506c506c606c606c706c706c806c806c906c906ca06ca06cb06cb06cc06cc06cd06cd06ce06ce06cf06cf06d006d006d106d106d206d206d306d306d406d406d506d506d606d606d706d706d806d806d906d906da06da06db06db06dc06dc06dd06dd06de06de06df06df06e006e006e106e106e206e206e306e306e406e406e506e506e606e606e706e706e806e806e906e906ea06ea06eb06eb06ec06ec06ed06ed06ee06ee06ef06ef06f006f006f106f106f206f206f306f306f406f406f506f506f606f606f706f706f806f806f906f906fa06fa06fb06fb06fc06fc06fd06fd06fe06fe06ff06ff" +bool(true) +bool(true) diff --git a/tests/igbinary_078.phpt b/tests/igbinary_078.phpt new file mode 100644 index 00000000..ec347714 --- /dev/null +++ b/tests/igbinary_078.phpt @@ -0,0 +1,25 @@ +--TEST-- +igbinary and large arrays +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +prop = $value; + } + public function __sleep() { + return null; + } +} +var_dump(bin2hex($s = igbinary_serialize(new BadSleep('override')))); +var_dump(igbinary_unserialize($s)); +?> +--EXPECTF-- +Notice: igbinary_serialize(): __sleep should return an array only containing the names of instance-variables to serialize in %s on line %d +string(32) "000000021708426164536c6565701400" +object(BadSleep)#1 (1) { + ["prop"]=> + string(1) "x" +} \ No newline at end of file diff --git a/tests/igbinary_079.phpt b/tests/igbinary_079.phpt new file mode 100644 index 00000000..3332aa73 --- /dev/null +++ b/tests/igbinary_079.phpt @@ -0,0 +1,49 @@ +--TEST-- +igbinary and many arrays +--FILE-- + +--EXPECTF-- +string(60) "000000021404060014010600060006011401060006010602010106030102" +array(4) { + [0]=> + array(1) { + [0]=> + int(0) + } + [1]=> + array(1) { + [0]=> + int(1) + } + [2]=> + array(1) { + [0]=> + int(0) + } + [3]=> + array(1) { + [0]=> + int(1) + } +} +bool(true) +bool(true) diff --git a/tests/igbinary_080.phpt b/tests/igbinary_080.phpt new file mode 100644 index 00000000..87600103 --- /dev/null +++ b/tests/igbinary_080.phpt @@ -0,0 +1,26 @@ +--TEST-- +igbinary with hash collision serializing strings +--FILE-- +"3010480803", 'user_id'=>12346]; +$serialized = igbinary_serialize($var); +$unserialized = igbinary_unserialize($serialized); +var_dump($unserialized); +$var=['id'=>"3010480803", 'user_id'=>"3010480804"]; +$serialized = igbinary_serialize($var); +$unserialized = igbinary_unserialize($serialized); +var_dump($unserialized); +?> +--EXPECT-- +array(2) { + ["id"]=> + string(10) "3010480803" + ["user_id"]=> + int(12346) +} +array(2) { + ["id"]=> + string(10) "3010480803" + ["user_id"]=> + string(10) "3010480804" +} diff --git a/tests/igbinary_081.phpt b/tests/igbinary_081.phpt new file mode 100644 index 00000000..b7410a17 --- /dev/null +++ b/tests/igbinary_081.phpt @@ -0,0 +1,27 @@ +--TEST-- +igbinary with reference group of size 1 created by array_walk_recursive +--FILE-- +env = &$e; + } +} + +$arrayObject = new ArrayObject(); + +$testClass = new TestClass(); +$testClass->setEnv($arrayObject); + +var_dump(igbinary_unserialize(igbinary_serialize($testClass))); +?> +--EXPECTF-- +object(TestClass)#%d (1) { + ["env":"TestClass":private]=> + object(ArrayObject)#%d (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } + } +} diff --git a/tests/igbinary_082_php74.phpt b/tests/igbinary_082_php74.phpt new file mode 100644 index 00000000..b32af414 --- /dev/null +++ b/tests/igbinary_082_php74.phpt @@ -0,0 +1,32 @@ +--TEST-- +igbinary object with typed properties with reference to ArrayObject +--SKIPIF-- + +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +env = &$e; + } +} + +$arrayObject = new ArrayObject(); + +$testClass = new TestClass(); +$testClass->setEnv($arrayObject); + +var_dump(igbinary_unserialize(igbinary_serialize($testClass))); +?> +--EXPECTF-- +object(TestClass)#%d (1) { + ["env":"TestClass":private]=> + object(ArrayObject)#%d (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } + } +} diff --git a/tests/igbinary_083.phpt b/tests/igbinary_083.phpt new file mode 100644 index 00000000..6651aaa0 --- /dev/null +++ b/tests/igbinary_083.phpt @@ -0,0 +1,42 @@ +--TEST-- +igbinary object with reference to ArrayObject +--INI-- +; Note that php 8.1 deprecates using Serializable without __serialize/__unserialize but we are testing Serialize for igbinary. Suppress deprecations. +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- + +--EXPECT-- +Called serialize +%00%00%00%02%14%04%06%00%25%00%06%01%25%22%01%06%02%25%11%06endcap%06%03%25%01%02 +array(4) { + [0]=> + &NULL + [1]=> + &NULL + [2]=> + &string(6) "endcap" + [3]=> + &string(6) "endcap" +} \ No newline at end of file diff --git a/tests/igbinary_084.phpt b/tests/igbinary_084.phpt new file mode 100644 index 00000000..4090a293 --- /dev/null +++ b/tests/igbinary_084.phpt @@ -0,0 +1,29 @@ +--TEST-- +Properly free duplicate properties when unserializing invalid data +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +pub = new Test(); +$s = igbinary_serialize($t); +echo urlencode($s), "\n"; +$unser = igbinary_unserialize($s); +var_dump($unser); +?> +--EXPECT-- +%00%00%00%02%17%04Test%14%02%11%03pub%1A%00%14%02%0E%01%00%0E%01%00%0E%01%22%01 +object(Test)#3 (1) { + ["pub"]=> + object(Test)#4 (1) { + ["pub"]=> + NULL + } +} diff --git a/tests/igbinary_084b.phpt b/tests/igbinary_084b.phpt new file mode 100644 index 00000000..1ccfbd1c --- /dev/null +++ b/tests/igbinary_084b.phpt @@ -0,0 +1,37 @@ +--TEST-- +Properly free duplicate undeclared properties when unserializing invalid data +--SKIPIF-- += 90000) { echo "skip requires php < 9.0 when testing that the deprecation has no impact on igbinary functionality\n"; } +?> +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- += 80200) { require_once __DIR__ . '/php82_suppress_dynamic_properties_warning.inc'; } +class Test { + public function __construct( ) { + $this->pub = null; + } + + public function __sleep() { + // TODO: Could start detecting duplicates and emitting a notice as well + return ["pub", "pub"]; + } +} +$t = new Test(); +$t->pub = new Test(); +$s = igbinary_serialize($t); +echo urlencode($s), "\n"; +$unser = igbinary_unserialize($s); +var_dump($unser); +?> +--EXPECT-- +%00%00%00%02%17%04Test%14%02%11%03pub%1A%00%14%02%0E%01%00%0E%01%00%0E%01%22%01 +object(Test)#3 (1) { + ["pub"]=> + object(Test)#4 (1) { + ["pub"]=> + NULL + } +} diff --git a/tests/igbinary_085.phpt b/tests/igbinary_085.phpt new file mode 100644 index 00000000..1bb6f165 --- /dev/null +++ b/tests/igbinary_085.phpt @@ -0,0 +1,29 @@ +--TEST-- +Properly free unexpected duplicate fields when unserializing arrays +--FILE-- + $s, 'yy' => $s]); +echo urlencode($ser), "\n"; +$result = igbinary_unserialize(str_replace('xx', 'yy', $ser)); +var_dump($result); +$ser = igbinary_serialize([0x66 => $s, 0x77 => $s]); +echo urlencode($ser), "\n"; +$result2 = igbinary_unserialize(str_replace("\x66", "\x77", $ser)); +var_dump($result2); +?> +--EXPECT-- +%00%00%00%02%14%02%11%02xx%17%08stdClass%14%00%11%02yy%22%01 +array(1) { + ["yy"]=> + object(stdClass)#2 (0) { + } +} +%00%00%00%02%14%02%06f%17%08stdClass%14%00%06w%22%01 +array(1) { + [119]=> + object(stdClass)#3 (0) { + } +} \ No newline at end of file diff --git a/tests/igbinary_086.phpt b/tests/igbinary_086.phpt new file mode 100644 index 00000000..bdbccc1c --- /dev/null +++ b/tests/igbinary_086.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test serializing many different classes +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +object(C0)#1 (0) { +} +object(C67800)#679 (0) { +} diff --git a/tests/igbinary_087.phpt b/tests/igbinary_087.phpt new file mode 100644 index 00000000..3ef21d3b --- /dev/null +++ b/tests/igbinary_087.phpt @@ -0,0 +1,37 @@ +--TEST-- +Test serializing many values in __sleep +--SKIPIF-- + +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +{"p$i"} = "$i"; +} +$ser = igbinary_serialize($x); +$unser = igbinary_unserialize($ser); +echo urlencode($ser), "\n"; +var_dump($unser == $x); +unset($unser); +for ($i = 0; $i < 70000; $i++) { + $x->{"p$i"} = "other$i"; +} +var_dump(igbinary_unserialize(igbinary_serialize($x)) == $x); + +?> +--EXPECT-- +Called __sleep props=300 +%00%00%00%02%17%01X%15%01%2C%11%02p0%11%010%11%02p1%11%011%11%02p2%11%012%11%02p3%11%013%11%02p4%11%014%11%02p5%11%015%11%02p6%11%016%11%02p7%11%017%11%02p8%11%018%11%02p9%11%019%11%03p10%11%0210%11%03p11%11%0211%11%03p12%11%0212%11%03p13%11%0213%11%03p14%11%0214%11%03p15%11%0215%11%03p16%11%0216%11%03p17%11%0217%11%03p18%11%0218%11%03p19%11%0219%11%03p20%11%0220%11%03p21%11%0221%11%03p22%11%0222%11%03p23%11%0223%11%03p24%11%0224%11%03p25%11%0225%11%03p26%11%0226%11%03p27%11%0227%11%03p28%11%0228%11%03p29%11%0229%11%03p30%11%0230%11%03p31%11%0231%11%03p32%11%0232%11%03p33%11%0233%11%03p34%11%0234%11%03p35%11%0235%11%03p36%11%0236%11%03p37%11%0237%11%03p38%11%0238%11%03p39%11%0239%11%03p40%11%0240%11%03p41%11%0241%11%03p42%11%0242%11%03p43%11%0243%11%03p44%11%0244%11%03p45%11%0245%11%03p46%11%0246%11%03p47%11%0247%11%03p48%11%0248%11%03p49%11%0249%11%03p50%11%0250%11%03p51%11%0251%11%03p52%11%0252%11%03p53%11%0253%11%03p54%11%0254%11%03p55%11%0255%11%03p56%11%0256%11%03p57%11%0257%11%03p58%11%0258%11%03p59%11%0259%11%03p60%11%0260%11%03p61%11%0261%11%03p62%11%0262%11%03p63%11%0263%11%03p64%11%0264%11%03p65%11%0265%11%03p66%11%0266%11%03p67%11%0267%11%03p68%11%0268%11%03p69%11%0269%11%03p70%11%0270%11%03p71%11%0271%11%03p72%11%0272%11%03p73%11%0273%11%03p74%11%0274%11%03p75%11%0275%11%03p76%11%0276%11%03p77%11%0277%11%03p78%11%0278%11%03p79%11%0279%11%03p80%11%0280%11%03p81%11%0281%11%03p82%11%0282%11%03p83%11%0283%11%03p84%11%0284%11%03p85%11%0285%11%03p86%11%0286%11%03p87%11%0287%11%03p88%11%0288%11%03p89%11%0289%11%03p90%11%0290%11%03p91%11%0291%11%03p92%11%0292%11%03p93%11%0293%11%03p94%11%0294%11%03p95%11%0295%11%03p96%11%0296%11%03p97%11%0297%11%03p98%11%0298%11%03p99%11%0299%11%04p100%11%03100%11%04p101%11%03101%11%04p102%11%03102%11%04p103%11%03103%11%04p104%11%03104%11%04p105%11%03105%11%04p106%11%03106%11%04p107%11%03107%11%04p108%11%03108%11%04p109%11%03109%11%04p110%11%03110%11%04p111%11%03111%11%04p112%11%03112%11%04p113%11%03113%11%04p114%11%03114%11%04p115%11%03115%11%04p116%11%03116%11%04p117%11%03117%11%04p118%11%03118%11%04p119%11%03119%11%04p120%11%03120%11%04p121%11%03121%11%04p122%11%03122%11%04p123%11%03123%11%04p124%11%03124%11%04p125%11%03125%11%04p126%11%03126%11%04p127%11%03127%11%04p128%11%03128%11%04p129%11%03129%11%04p130%11%03130%11%04p131%11%03131%11%04p132%11%03132%11%04p133%11%03133%11%04p134%11%03134%11%04p135%11%03135%11%04p136%11%03136%11%04p137%11%03137%11%04p138%11%03138%11%04p139%11%03139%11%04p140%11%03140%11%04p141%11%03141%11%04p142%11%03142%11%04p143%11%03143%11%04p144%11%03144%11%04p145%11%03145%11%04p146%11%03146%11%04p147%11%03147%11%04p148%11%03148%11%04p149%11%03149%11%04p150%11%03150%11%04p151%11%03151%11%04p152%11%03152%11%04p153%11%03153%11%04p154%11%03154%11%04p155%11%03155%11%04p156%11%03156%11%04p157%11%03157%11%04p158%11%03158%11%04p159%11%03159%11%04p160%11%03160%11%04p161%11%03161%11%04p162%11%03162%11%04p163%11%03163%11%04p164%11%03164%11%04p165%11%03165%11%04p166%11%03166%11%04p167%11%03167%11%04p168%11%03168%11%04p169%11%03169%11%04p170%11%03170%11%04p171%11%03171%11%04p172%11%03172%11%04p173%11%03173%11%04p174%11%03174%11%04p175%11%03175%11%04p176%11%03176%11%04p177%11%03177%11%04p178%11%03178%11%04p179%11%03179%11%04p180%11%03180%11%04p181%11%03181%11%04p182%11%03182%11%04p183%11%03183%11%04p184%11%03184%11%04p185%11%03185%11%04p186%11%03186%11%04p187%11%03187%11%04p188%11%03188%11%04p189%11%03189%11%04p190%11%03190%11%04p191%11%03191%11%04p192%11%03192%11%04p193%11%03193%11%04p194%11%03194%11%04p195%11%03195%11%04p196%11%03196%11%04p197%11%03197%11%04p198%11%03198%11%04p199%11%03199%11%04p200%11%03200%11%04p201%11%03201%11%04p202%11%03202%11%04p203%11%03203%11%04p204%11%03204%11%04p205%11%03205%11%04p206%11%03206%11%04p207%11%03207%11%04p208%11%03208%11%04p209%11%03209%11%04p210%11%03210%11%04p211%11%03211%11%04p212%11%03212%11%04p213%11%03213%11%04p214%11%03214%11%04p215%11%03215%11%04p216%11%03216%11%04p217%11%03217%11%04p218%11%03218%11%04p219%11%03219%11%04p220%11%03220%11%04p221%11%03221%11%04p222%11%03222%11%04p223%11%03223%11%04p224%11%03224%11%04p225%11%03225%11%04p226%11%03226%11%04p227%11%03227%11%04p228%11%03228%11%04p229%11%03229%11%04p230%11%03230%11%04p231%11%03231%11%04p232%11%03232%11%04p233%11%03233%11%04p234%11%03234%11%04p235%11%03235%11%04p236%11%03236%11%04p237%11%03237%11%04p238%11%03238%11%04p239%11%03239%11%04p240%11%03240%11%04p241%11%03241%11%04p242%11%03242%11%04p243%11%03243%11%04p244%11%03244%11%04p245%11%03245%11%04p246%11%03246%11%04p247%11%03247%11%04p248%11%03248%11%04p249%11%03249%11%04p250%11%03250%11%04p251%11%03251%11%04p252%11%03252%11%04p253%11%03253%11%04p254%11%03254%11%04p255%11%03255%11%04p256%11%03256%11%04p257%11%03257%11%04p258%11%03258%11%04p259%11%03259%11%04p260%11%03260%11%04p261%11%03261%11%04p262%11%03262%11%04p263%11%03263%11%04p264%11%03264%11%04p265%11%03265%11%04p266%11%03266%11%04p267%11%03267%11%04p268%11%03268%11%04p269%11%03269%11%04p270%11%03270%11%04p271%11%03271%11%04p272%11%03272%11%04p273%11%03273%11%04p274%11%03274%11%04p275%11%03275%11%04p276%11%03276%11%04p277%11%03277%11%04p278%11%03278%11%04p279%11%03279%11%04p280%11%03280%11%04p281%11%03281%11%04p282%11%03282%11%04p283%11%03283%11%04p284%11%03284%11%04p285%11%03285%11%04p286%11%03286%11%04p287%11%03287%11%04p288%11%03288%11%04p289%11%03289%11%04p290%11%03290%11%04p291%11%03291%11%04p292%11%03292%11%04p293%11%03293%11%04p294%11%03294%11%04p295%11%03295%11%04p296%11%03296%11%04p297%11%03297%11%04p298%11%03298%11%04p299%11%03299 +bool(true) +Called __sleep props=70000 +bool(true) \ No newline at end of file diff --git a/tests/igbinary_088.phpt b/tests/igbinary_088.phpt new file mode 100644 index 00000000..dc27183f --- /dev/null +++ b/tests/igbinary_088.phpt @@ -0,0 +1,39 @@ +--TEST-- +Test serializing wrong values in __sleep +--SKIPIF-- + +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +{"p$i"} = "name$i"; +} +$ser = igbinary_serialize($x); +$unser = igbinary_unserialize($ser); +echo str_replace(['\\', '%'], ['\\\\', '\x'], urlencode($ser)), "\n"; +var_dump($unser); + +?> +--EXPECTF-- +Notice: igbinary_serialize(): "name0" returned as member variable from __sleep() but does not exist in %s on line 12 + +Notice: igbinary_serialize(): "name1" returned as member variable from __sleep() but does not exist in %s on line 12 + +Notice: igbinary_serialize(): "name2" returned as member variable from __sleep() but does not exist in %s on line 12 +\x00\x00\x00\x02\x17\x01X\x14\x03\x11\x05name0\x00\x11\x05name1\x00\x11\x05name2\x00 +object(X)#2 (3) { + ["name0"]=> + NULL + ["name1"]=> + NULL + ["name2"]=> + NULL +} \ No newline at end of file diff --git a/tests/igbinary_089.phpt b/tests/igbinary_089.phpt new file mode 100644 index 00000000..703fd523 --- /dev/null +++ b/tests/igbinary_089.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test serializing string > 4G +--INI-- +memory_limit=15G +display_errors=stderr +error_reporting=E_ALL +--CONFLICTS-- +high_memory +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +len=4200000009 +0000000213fa56ea002a2a2a2a2a2a2a2a2a2a2a +bool(true) +Warning: igbinary_unserialize_chararray: end-of-data in %sigbinary_089.php on line 10 +NULL \ No newline at end of file diff --git a/tests/igbinary_089_32bit.phpt b/tests/igbinary_089_32bit.phpt new file mode 100644 index 00000000..160a6811 --- /dev/null +++ b/tests/igbinary_089_32bit.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test unserializing invalid 64-bit string header on 32-bit platform +--INI-- +display_errors=stderr +error_reporting=E_ALL +--CONFLICTS-- +high_memory +--SKIPIF-- + 4) { print "skip requires 32-bit\n"; } +?> +--FILE-- + +--EXPECTF-- +Warning: igbinary_unserialize_chararray: %s in %sigbinary_089_32bit.php on line 3 +NULL \ No newline at end of file diff --git a/tests/igbinary_090.phpt b/tests/igbinary_090.phpt new file mode 100644 index 00000000..4532300a --- /dev/null +++ b/tests/igbinary_090.phpt @@ -0,0 +1,34 @@ +--TEST-- +Check for handling of IS_INDIRECT in arrays +--FILE-- + $value) { + if (!in_array($key, ['globalVar', 'otherGlobalVar'])) { + unset($x[$key]); + } + } + var_dump($x); + $ser = igbinary_serialize($x); + echo urlencode($ser) . "\n"; + var_dump(igbinary_unserialize($ser)); + +}); +--EXPECT-- +array(2) { + ["globalVar"]=> + &int(123) + ["otherGlobalVar"]=> + &int(123) +} +%00%00%00%02%14%02%11%09globalVar%25%06%7B%11%0EotherGlobalVar%25%01%01 +array(2) { + ["globalVar"]=> + &int(123) + ["otherGlobalVar"]=> + &int(123) +} diff --git a/tests/igbinary_091.phpt b/tests/igbinary_091.phpt new file mode 100644 index 00000000..b0a1a812 --- /dev/null +++ b/tests/igbinary_091.phpt @@ -0,0 +1,29 @@ +--TEST-- +Check for handling of anonymous classes +--INI-- +error_reporting=E_ALL & ~E_DEPRECATED +--FILE-- +getMessage() . "\n"; + } +} +check_serialize_throws(new class () {}); +// TODO: Update behavior based on https://bugs.php.net/bug.php?id=81111 +/** +check_serialize_throws(new class () { + public function __serialize() { return []; } + public function __unserialize($value) { } +}); + */ +check_serialize_throws(new class () implements Serializable { + public function serialize() { return ''; } + public function unserialize($ser) { return new self(); } +}); +?> +--EXPECTF-- +Caught: Serialization of 'class@anonymous' is not allowed +Caught: Serialization of '%s@anonymous' is not allowed diff --git a/tests/igbinary_092.phpt b/tests/igbinary_092.phpt new file mode 100644 index 00000000..4f44ddf4 --- /dev/null +++ b/tests/igbinary_092.phpt @@ -0,0 +1,35 @@ +--TEST-- +Object test, unserialize_callback_func +--INI-- +error_reporting=E_ALL +--FILE-- +b == 2 ? 'OK' : 'ERROR'; + echo "\n"; + } catch (Throwable $e) { + printf("Caught %s: %s\n", get_class($e), $e->getMessage()); + } +} + +class MyUnserializer { + public static function handleUnserialize(string $class) { + throw new RuntimeException('handleUnserialize: Class not found: ' . $class); + } +} + +ini_set('unserialize_callback_func', strtoupper('MyUnserializer::handleUnserialize')); +test('throwing_autoload', '0000000217034f626a140211016106011101620602', false); +?> +--EXPECTF-- +Caught RuntimeException: handleUnserialize: Class not found: Obj diff --git a/tests/igbinary_093.phpt b/tests/igbinary_093.phpt new file mode 100644 index 00000000..6b111b39 --- /dev/null +++ b/tests/igbinary_093.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test refusing to serialize/unserialize unserializable anonymous classes +--INI-- +error_reporting=E_ALL +--FILE-- +getMessage() . "\n"; + } +} + +check_serialize_throws(new class () { + public function __serialize() { return []; } + public function __unserialize($value) { } +}); +check_serialize_throws(function () { }); + +?> +--EXPECTF-- +Caught: Serialization of 'class@anonymous' is not allowed +Caught: Serialization of 'Closure' is not allowed diff --git a/tests/igbinary_094.phpt b/tests/igbinary_094.phpt new file mode 100644 index 00000000..5bf353a2 --- /dev/null +++ b/tests/igbinary_094.phpt @@ -0,0 +1,30 @@ +--TEST-- +Test refusing to serialize/unserialize unserializable internal classes +--INI-- +error_reporting=E_ALL +--SKIPIF-- + +--FILE-- +getMessage() . "\n"; + } +} +class Something extends CURLFile { + public function __serialize() { return []; } + public function __unserialize($value) { return new self('file'); } +} + +check_serialize_throws(new CURLFile('file')); +check_serialize_throws(new Something('file')); +?> +--EXPECTF-- +Caught: Serialization of 'CURLFile' is not allowed +Caught: Serialization of 'Something' is not allowed diff --git a/tests/igbinary_095.phpt b/tests/igbinary_095.phpt new file mode 100644 index 00000000..5de7d03a --- /dev/null +++ b/tests/igbinary_095.phpt @@ -0,0 +1,93 @@ +--TEST-- +Test handling php 8.1 readonly properties +--SKIPIF-- + +--FILE-- +var = $intersection; + } +} + +class Y { + public readonly mixed $var; + + public function __construct( + public readonly int $a, + private readonly ArrayAccess&Countable $intersection, + protected readonly ?string $default = null, + ) { + $this->var = $intersection; + } + + public function __serialize(): array { + return [ + 'a' => $this->a, + 'intersection' => $this->intersection, + 'default' => $this->default, + 'var' => $this->var, + ]; + } + public function __unserialize(array $data) { + [ + 'a' => $this->a, + 'intersection' => $this->intersection, + 'default' => $this->default, + 'var' => $this->var, + ] = $data; + } +} + +$ser = igbinary_serialize(new X(1, new ArrayObject())); +echo urlencode($ser), "\n"; +var_dump(igbinary_unserialize($ser)); +$ser = igbinary_serialize(new Y(1, new ArrayObject())); +echo urlencode($ser), "\n"; +var_dump(igbinary_unserialize($ser)); +?> +--EXPECT-- +%00%00%00%02%17%01X%14%04%11%03var%17%0BArrayObject%14%04%06%00%06%00%06%01%14%00%06%02%14%00%06%03%00%11%01a%06%01%11%0F%00X%00intersection%22%01%11%0A%00%2A%00default%00 +object(X)#1 (4) { + ["var"]=> + object(ArrayObject)#2 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } + } + ["a"]=> + int(1) + ["intersection":"X":private]=> + object(ArrayObject)#2 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } + } + ["default":protected]=> + NULL +} +%00%00%00%02%17%01Y%14%04%11%01a%06%01%11%0Cintersection%17%0BArrayObject%14%04%06%00%06%00%06%01%14%00%06%02%14%00%06%03%00%11%07default%00%11%03var%22%01 +object(Y)#1 (4) { + ["var"]=> + object(ArrayObject)#2 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } + } + ["a"]=> + int(1) + ["intersection":"Y":private]=> + object(ArrayObject)#2 (1) { + ["storage":"ArrayObject":private]=> + array(0) { + } + } + ["default":protected]=> + NULL +} diff --git a/tests/igbinary_096.phpt b/tests/igbinary_096.phpt new file mode 100644 index 00000000..95d8d23f --- /dev/null +++ b/tests/igbinary_096.phpt @@ -0,0 +1,29 @@ +--TEST-- +Test unserialization creates valid current() +--INI-- +display_errors=stderr +error_reporting=E_ALL +--FILE-- + [ + 'two' => [ + 'three' => [ + 'four' + ], + ], + ], +]; +$test = current(current(current(current($arr)))); + +var_dump($test); + +// Note: after unserialization the current is the last array element. +$arr2 = igbinary_unserialize(igbinary_serialize($arr)); +$test2 = current(current(current(current($arr2)))); + +var_dump($test2); +?> +--EXPECT-- +string(4) "four" +string(4) "four" \ No newline at end of file diff --git a/tests/igbinary_097.phpt b/tests/igbinary_097.phpt new file mode 100644 index 00000000..fd64c194 --- /dev/null +++ b/tests/igbinary_097.phpt @@ -0,0 +1,46 @@ +--TEST-- +Test serialize globals +--SKIPIF-- += 80100) print "skip php >= 8.1\n"; // https://wiki.php.net/rfc/restrict_globals_usage +?> +--FILE-- + $_) { + if ($key !== 'GLOBALS') { + unset($GLOBALS[$key]); + } + } + $ser = igbinary_serialize($GLOBALS); + echo urlencode($ser) . "\n"; + var_dump(igbinary_unserialize($ser)); + $GLOBALS['globalVar'] = new stdClass(); + $ser = igbinary_serialize($GLOBALS); + echo urlencode($ser) . "\n"; + var_dump(igbinary_unserialize($ser)); +}); +--EXPECTF-- +%00%00%00%02%14%01%11%07GLOBALS%14%01%0E%00%01%01 +array(1) { + ["GLOBALS"]=> + array(1) { + ["GLOBALS"]=> + *RECURSION* + } +} +%00%00%00%02%14%02%11%07GLOBALS%14%02%0E%00%01%01%11%09globalVar%17%08stdClass%14%00%0E%01%22%02 +array(2) { + ["GLOBALS"]=> + array(2) { + ["GLOBALS"]=> + *RECURSION* + ["globalVar"]=> + object(stdClass)#3 (0) { + } + } + ["globalVar"]=> + object(stdClass)#3 (0) { + } +} diff --git a/tests/igbinary_098.phpt b/tests/igbinary_098.phpt new file mode 100644 index 00000000..f29462b3 --- /dev/null +++ b/tests/igbinary_098.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test PHP 8.2 readonly classes +--SKIPIF-- + +--FILE-- +a = 8; + return $c; + } +} +$c = new C(); +$ser = igbinary_serialize($c); +echo urlencode($ser), "\n"; +var_dump(igbinary_unserialize($ser)); +$c = C::create(); +$ser = igbinary_serialize($c); +echo urlencode($ser), "\n"; +var_dump(igbinary_unserialize($ser)); +$invalid1 = str_replace('a', 'b', $ser); +try { + var_dump(igbinary_unserialize($invalid1)); +} catch (Error $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} +--EXPECT-- +%00%00%00%02%17%01C%14%01%00 +object(C)#2 (0) { + ["a"]=> + uninitialized(int) +} +%00%00%00%02%17%01C%14%01%11%01a%06%08 +object(C)#1 (1) { + ["a"]=> + int(8) +} +Error: Cannot create dynamic property C::$b in igbinary_unserialize diff --git a/tests/igbinary_099.phpt b/tests/igbinary_099.phpt new file mode 100644 index 00000000..2bf5745d --- /dev/null +++ b/tests/igbinary_099.phpt @@ -0,0 +1,36 @@ +--TEST-- +Test PHP 8.2 deprecation of creation of dynamic properties +--SKIPIF-- + +--FILE-- +getMessage()); + } +} +--EXPECTF-- +Deprecated: igbinary_unserialize(): Creation of dynamic property C::$a is deprecated in %sigbinary_099.php on line 12 +object(C)#2 (1) { + ["a"]=> + int(8) +} + +Deprecated: igbinary_unserialize(): Creation of dynamic property C::$a is deprecated in %sigbinary_099.php on line 12 +object(C)#2 (1) { + ["a":protected]=> + int(8) +} diff --git a/tests/igbinary_100.phpt b/tests/igbinary_100.phpt new file mode 100644 index 00000000..ba0e166a --- /dev/null +++ b/tests/igbinary_100.phpt @@ -0,0 +1,56 @@ +--TEST-- +Check if igbinary behaves like the serializer of PHP >= 7.2 when the visibility levels of properties are changed between serialization and unserialization +--SKIPIF-- + +--FILE-- + +--EXPECT-- +PHP behavior: +object(Test)#1 (3) { + ["private_property"]=> + int(1) + ["protected_property":"Test":private]=> + int(2) + ["public_property":protected]=> + int(3) +} + +Igbinary behavior: +object(Test)#2 (3) { + ["private_property"]=> + int(1) + ["protected_property":"Test":private]=> + int(2) + ["public_property":protected]=> + int(3) +} diff --git a/tests/igbinary_101.phpt b/tests/igbinary_101.phpt new file mode 100644 index 00000000..7e6ff5d3 --- /dev/null +++ b/tests/igbinary_101.phpt @@ -0,0 +1,36 @@ +--TEST-- +Name of virtual-property must not be truncated in warning message when visibility was originally protected/private +--SKIPIF-- + +--FILE-- + 1; + } +} + +echo "PHP behavior:"; +unserialize($serialized_php); + +echo "\nIgbinary behavior:"; +igbinary_unserialize($serialized_igb); + +?> +--EXPECTF-- +PHP behavior: +Warning: unserialize(): Cannot unserialize value for virtual property Test::$ in %s on line %d + +Warning: unserialize(): Error at offset %d of %d bytes in %s on line %d + +Igbinary behavior: +Warning: igbinary_unserialize(): Cannot unserialize value for virtual property Test::$prop in %s on line %d diff --git a/tests/igbinary_bug54662.phpt b/tests/igbinary_bug54662.phpt index a35bd1ac..655a204c 100644 --- a/tests/igbinary_bug54662.phpt +++ b/tests/igbinary_bug54662.phpt @@ -8,8 +8,6 @@ Nested objects cause segfault, php bug #54662 * Proof of concept, segmentation fault (spl_array.c/igbinary.c) * when using nested objects. * - * PHP 5.3.6, Igbinary 1.x - * * @author Aleksey Korzun */ diff --git a/tests/igbinary_bug72134.phpt b/tests/igbinary_bug72134.phpt new file mode 100644 index 00000000..39744968 --- /dev/null +++ b/tests/igbinary_bug72134.phpt @@ -0,0 +1,48 @@ +--TEST-- +igbinary_unserialize regression test for segfault on 3rd call for objects with dynamic property +--FILE-- +i = 1; +$igb = igbinary_serialize($value); +for ($i=0; $i < 30; $i++) +{ + // This used to segfault at the third attempt + echo igbinary_unserialize($igb)->bar . PHP_EOL; +} +--EXPECT-- +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test diff --git a/tests/igbinary_enums_1.phpt b/tests/igbinary_enums_1.phpt new file mode 100644 index 00000000..562a5e60 --- /dev/null +++ b/tests/igbinary_enums_1.phpt @@ -0,0 +1,41 @@ +--TEST-- +Test unserializing valid enums +--SKIPIF-- + +--FILE-- + 'Diamonds']); +echo urlencode($serArray), "\n"; +var_dump(igbinary_unserialize($serArray)); + +?> +--EXPECT-- +%00%00%00%02%17%04Suit%27%11%06Hearts +enum(Suit::Hearts) +enum(Suit::Hearts) +%00%00%00%02%14%06%06%00%17%04Suit%27%11%06Hearts%06%01%1A%00%27%11%08Diamonds%06%02%1A%00%27%11%06Spades%06%03%1A%00%27%11%05Clubs%06%04%22%04%0E%02%0E%02 +array(6) { + [0]=> + enum(Suit::Hearts) + [1]=> + enum(Suit::Diamonds) + [2]=> + enum(Suit::Spades) + [3]=> + enum(Suit::Clubs) + [4]=> + enum(Suit::Clubs) + ["Diamonds"]=> + string(8) "Diamonds" +} diff --git a/tests/igbinary_enums_2.phpt b/tests/igbinary_enums_2.phpt new file mode 100644 index 00000000..e9452185 --- /dev/null +++ b/tests/igbinary_enums_2.phpt @@ -0,0 +1,66 @@ +--TEST-- +Test unserializing valid and invalid enums +--SKIPIF-- + +--FILE-- + Suit::Hearts]; +$arr[1] = &$arr['Hearts']; +$serArray = igbinary_serialize($arr); +// PHP 8.1 added support for %0 as a null byte in EXPECTF in https://github.com/php/php-src/pull/7069 +// Igbinary's use case of urlencode on binary data is rare. +// So replace % with \x +echo str_replace(['\\', '%'], ['\\\\', '\x'], urlencode($serArray)), "\n"; +$result = igbinary_unserialize($serArray); +var_dump($result); +$result[1] = 'new'; +var_dump($result); +$serInvalid = str_replace('Hearts', 'HEARTS', $serArray); +var_dump(igbinary_unserialize($serInvalid)); + +$serInvalidConst = str_replace('Hearts', 'vvvvvv', $serArray); +var_dump(igbinary_unserialize($serInvalidConst)); + +$serMissingClass = str_replace('Suit', 'Club', $serArray); +var_dump(igbinary_unserialize($serMissingClass)); + +$serInvalidClass = str_replace('Suit', 'ABCD', $serArray); +var_dump(igbinary_unserialize($serInvalidClass)); +?> +--EXPECTF-- +\x00\x00\x00\x02\x14\x02\x11\x06Hearts\x25\x17\x04Suit\x27\x0E\x00\x06\x01\x25\x22\x01 +array(2) { + ["Hearts"]=> + &enum(Suit::Hearts) + [1]=> + &enum(Suit::Hearts) +} +array(2) { + ["Hearts"]=> + &string(3) "new" + [1]=> + &string(3) "new" +} + +Warning: igbinary_unserialize_object_enum_case: Suit::HEARTS is not an enum case in %s on line 25 +NULL + +Warning: igbinary_unserialize_object_enum_case: Undefined constant Suit::vvvvvv in %s on line 28 +NULL + +Warning: igbinary_unserialize_object_enum_case: Class 'Club' does not exist in %s on line 31 +NULL + +Warning: igbinary_unserialize_object_enum_case: Class 'ABCD' is not an enum in %s on line 34 +NULL \ No newline at end of file diff --git a/tests/igbinary_enums_3.phpt b/tests/igbinary_enums_3.phpt new file mode 100644 index 00000000..c6c197f4 --- /dev/null +++ b/tests/igbinary_enums_3.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test unserializing valid enums inferring value +--SKIPIF-- + +--FILE-- +value); +?> +--EXPECT-- +enum(X::X) +%00%00%00%02%17%01X%27%0E%00 +string(2) "ab" diff --git a/tests/igbinary_enums_3_php83.phpt b/tests/igbinary_enums_3_php83.phpt new file mode 100644 index 00000000..27914d26 --- /dev/null +++ b/tests/igbinary_enums_3_php83.phpt @@ -0,0 +1,22 @@ +--TEST-- +Test unserializing valid enums inferring value +--SKIPIF-- + +--FILE-- +value); +?> +--EXPECT-- +enum(X::X) +%00%00%00%02%17%01X%27%0E%00 +string(8) "dynamicX" diff --git a/tests/igbinary_serialize_deep_nesting.phpt b/tests/igbinary_serialize_deep_nesting.phpt new file mode 100644 index 00000000..c6e70161 --- /dev/null +++ b/tests/igbinary_serialize_deep_nesting.phpt @@ -0,0 +1,38 @@ +--TEST-- +Deeply nested input to igbinary_serialize() throws a catchable Error instead of crashing +--SKIPIF-- + +--INI-- +zend.max_allowed_stack_size=512K +--FILE-- +next = $newNode; + $node = $newNode; +} + +try { + igbinary_serialize($firstNode); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +// Unlink iteratively so the engine does not recurse while destroying the chain. +while ($next = $firstNode->next) { + $firstNode->next = $next->next; +} + +?> +--EXPECT-- +Maximum call stack size reached. Infinite recursion? diff --git a/tests/igbinary_unserialize_max_depth.phpt b/tests/igbinary_unserialize_max_depth.phpt new file mode 100644 index 00000000..7d60de7c --- /dev/null +++ b/tests/igbinary_unserialize_max_depth.phpt @@ -0,0 +1,40 @@ +--TEST-- +igbinary_unserialize honors unserialize_max_depth ini setting +--SKIPIF-- + +--FILE-- + $deep]; +} +$ok = igbinary_serialize($deep); + +// Cap below payload depth: bail with warning, return null. +ini_set('unserialize_max_depth', '100'); +var_dump(igbinary_unserialize($ok)); + +// Cap above payload depth: round-trips successfully. +ini_set('unserialize_max_depth', '500'); +var_dump(igbinary_unserialize($ok) !== null); + +// Cap = 0 disables the limit, matching PHP core unserialize() semantics. +ini_set('unserialize_max_depth', '0'); +var_dump(igbinary_unserialize($ok) !== null); + +// Synthetic deeply-nested payload: depth-bomb is rejected at the small cap +// without ever recursing past 100 frames, proving the crash protection. +ini_set('unserialize_max_depth', '100'); +$bomb = "\x00\x00\x00\x02" . str_repeat("\x14\x01\x11\x01x", 5000) . "\x00"; +var_dump(igbinary_unserialize($bomb)); +?> +--EXPECTF-- +Warning: igbinary_unserialize(): Maximum depth of 100 exceeded. The depth limit can be changed using the unserialize_max_depth ini setting in %s on line %d +NULL +bool(true) +bool(true) + +Warning: igbinary_unserialize(): Maximum depth of 100 exceeded. The depth limit can be changed using the unserialize_max_depth ini setting in %s on line %d +NULL diff --git a/tests/igbinary_unserialize_v1_compatible.phpt b/tests/igbinary_unserialize_v1_compatible.phpt index 82e5e151..55f15c58 100644 --- a/tests/igbinary_unserialize_v1_compatible.phpt +++ b/tests/igbinary_unserialize_v1_compatible.phpt @@ -1,6 +1,7 @@ --TEST-- Unserialize backwards compatible with v1. ---SKIPIF-- +--INI-- +pcre.jit=0 --FILE-- 1, ), array( - 'var' => 'O:8:"stdClass":4:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;}', - 'var_e' => (object)array(1, 2, 3, 4), + 'var' => 'O:8:"stdClass":4:{s:1:"a";i:1;s:1:"b";i:2;s:1:"c";i:3;s:1:"d";i:4;}', + 'var_e' => (object)array("a"=>1, "b"=>2, "c"=>3, "d"=>4), 'type' => 'object', 'description' => 'object', - 'data' => 'AAAAARcIc3RkQ2xhc3MUBAYABgEGAQYCBgIGAwYDBgQ=', + 'data' => 'AAAAAhcIc3RkQ2xhc3MUBBEBYQYBEQFiBgIRAWMGAxEBZAYE', 'version' => 1, ), array( diff --git a/tests/php82_suppress_dynamic_properties_warning.inc b/tests/php82_suppress_dynamic_properties_warning.inc new file mode 100644 index 00000000..85af8d87 --- /dev/null +++ b/tests/php82_suppress_dynamic_properties_warning.inc @@ -0,0 +1,20 @@ + +--EXPECT-- +%00%00%00%02%17%04Test%14%02%11%0A%00Test%00priv%00%0E%01%00 +object(Test)#1 (1) { + ["priv":"Test":private]=> + NULL +} diff --git a/tests/typed_property_ref_assignment_failure.phpt b/tests/typed_property_ref_assignment_failure.phpt new file mode 100644 index 00000000..fb2251b1 --- /dev/null +++ b/tests/typed_property_ref_assignment_failure.phpt @@ -0,0 +1,31 @@ +--TEST-- +Failure to assign ref to typed property +--SKIPIF-- + +--FILE-- +prop = new stdClass(); +$b->prop->y = &$b->prop; +$ser = igbinary_serialize($b); +echo str_replace('%', '\\x', urlencode($ser)), "\n"; + +// Should reject stdClass reference +$ser2 = str_replace('Best', 'Test', $ser); +try { + var_dump(igbinary_unserialize($ser2)); +} catch (Error $e) { + printf("Caught %s: %s\n", get_class($e), $e->getMessage()); +} +?> +--EXPECTF-- +\x00\x00\x00\x02\x17\x04Best\x14\x01\x11\x04prop\x25\x17\x08stdClass\x14\x01\x11\x01y\x25\x22\x01 +Caught TypeError: %s property Test::$prop %s diff --git a/tests/typed_property_ref_overwrite.phpt b/tests/typed_property_ref_overwrite.phpt new file mode 100644 index 00000000..53d61e9b --- /dev/null +++ b/tests/typed_property_ref_overwrite.phpt @@ -0,0 +1,40 @@ +--TEST-- +Overwriting a typed property reference +--SKIPIF-- + +--FILE-- +prop = $orig; +$orig->drop = null; +$ser = igbinary_serialize($orig); +unset($orig); +gc_collect_cycles(); // also tests that igbinary properly marks unserialized data as a possible gc cycle root. + +var_dump(igbinary_unserialize($ser)); +gc_collect_cycles(); + +echo "Test overwrite reference group\n"; +$ser2 = str_replace('drop', 'prop', $ser); +var_dump(igbinary_unserialize($ser2)); + +?> +--EXPECT-- +object(Test)#1 (2) { + ["prop"]=> + *RECURSION* + ["drop"]=> + NULL +} +Test overwrite reference group +object(Test)#1 (2) { + ["prop"]=> + NULL + ["drop"]=> + NULL +} diff --git a/tests/typed_property_ref_overwrite2.phpt b/tests/typed_property_ref_overwrite2.phpt new file mode 100644 index 00000000..4775ae25 --- /dev/null +++ b/tests/typed_property_ref_overwrite2.phpt @@ -0,0 +1,42 @@ +--TEST-- +Overwriting a typed property that is not yet a reference +--SKIPIF-- + +--FILE-- +drop = null; +$orig->prop = new Test(); +$orig->prop->prop = &$orig->prop; +$ser = igbinary_serialize($orig); +echo urlencode($ser), "\n"; + +$ser2 = str_replace('drop', 'prop', $ser); +$result = igbinary_unserialize($ser2); +var_dump($result); +try { + $result->prop = 1; +} catch (TypeError $e) { + printf("Caught %s\n", get_class($e)); +} + +?> +--EXPECT-- +%00%00%00%02%17%04Test%14%02%11%04drop%00%11%04prop%25%1A%00%14%02%0E%01%00%0E%02%25%22%01 +object(Test)#3 (2) { + ["drop"]=> + NULL + ["prop"]=> + &object(Test)#4 (2) { + ["drop"]=> + NULL + ["prop"]=> + *RECURSION* + } +} +Caught TypeError diff --git a/tests/typed_property_refs.phpt b/tests/typed_property_refs.phpt new file mode 100644 index 00000000..b17633be --- /dev/null +++ b/tests/typed_property_refs.phpt @@ -0,0 +1,105 @@ +--TEST-- +unserialize with references to typed properties shall skip the references or fail +--SKIPIF-- + +--FILE-- +a = $value; + $v->b = &$v->a; + $ser = igbinary_serialize($v); + printf("for %s: %s\n", var_export($value, true), urlencode($ser)); + return $ser; +} +$serInt = create_ser(1); +$serNull = create_ser(null); +$serStr = create_ser('x'); +var_dump(igbinary_unserialize(str_replace('X', 'A', $serInt))); +var_dump(igbinary_unserialize(str_replace('X', 'B', $serInt))); +var_dump(igbinary_unserialize(str_replace('X', 'E', $serInt))); + +try { + var_dump(igbinary_unserialize(str_replace('X', 'A', $serNull))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'B', $serNull))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'C', $serInt))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'C', $serStr))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'D', $serInt))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +for 1: %00%00%00%02%17%01X%14%02%11%01a%25%06%01%11%01b%25%01%01 +for NULL: %00%00%00%02%17%01X%14%02%11%01a%25%00%11%01b%25%01%01 +for 'x': %00%00%00%02%17%01X%14%02%11%01a%25%11%01x%11%01b%25%01%01 +object(A)#1 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +object(B)#1 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +object(E)#1 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +Cannot assign null to property A::$a of type int +Cannot assign null to property B::$b of type int +Cannot assign int to property C::$b of type string +Cannot assign string to property C::$a of type int +Reference with value of type int held by property D::$a of type int is not compatible with property D::$b of type float diff --git a/tests/typed_property_refs_php74.phpt b/tests/typed_property_refs_php74.phpt new file mode 100644 index 00000000..4aae44c1 --- /dev/null +++ b/tests/typed_property_refs_php74.phpt @@ -0,0 +1,105 @@ +--TEST-- +unserialize with references to typed properties shall skip the references or fail +--SKIPIF-- += 80000) die("skip requires php 7.4 error message"); ?> +--FILE-- +a = $value; + $v->b = &$v->a; + $ser = igbinary_serialize($v); + printf("for %s: %s\n", var_export($value, true), urlencode($ser)); + return $ser; +} +$serInt = create_ser(1); +$serNull = create_ser(null); +$serStr = create_ser('x'); +var_dump(igbinary_unserialize(str_replace('X', 'A', $serInt))); +var_dump(igbinary_unserialize(str_replace('X', 'B', $serInt))); +var_dump(igbinary_unserialize(str_replace('X', 'E', $serInt))); + +try { + var_dump(igbinary_unserialize(str_replace('X', 'A', $serNull))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'B', $serNull))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'C', $serInt))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'C', $serStr))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(igbinary_unserialize(str_replace('X', 'D', $serInt))); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +for 1: %00%00%00%02%17%01X%14%02%11%01a%25%06%01%11%01b%25%01%01 +for NULL: %00%00%00%02%17%01X%14%02%11%01a%25%00%11%01b%25%01%01 +for 'x': %00%00%00%02%17%01X%14%02%11%01a%25%11%01x%11%01b%25%01%01 +object(A)#1 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +object(B)#1 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +object(E)#1 (2) { + ["a"]=> + &int(1) + ["b"]=> + &int(1) +} +Typed property A::$a must be int, null used +Typed property B::$b must be int, null used +Typed property C::$b must be string, int used +Typed property C::$a must be int, string used +Reference with value of type int held by property D::$a of type int is not compatible with property D::$b of type float