diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index c01cbfe0954f..11a16c3ec785 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -16,19 +16,19 @@
/.github @TimWolla
/build/gen_stub.php @kocsismate
-/ext/bcmath @ndossche @SakiTakamachi
+/ext/bcmath @SakiTakamachi
/ext/curl @adoy
/ext/date @derickr
/ext/dba @Girgias
-/ext/dom @ndossche
+/ext/dom @devnexen
/ext/ffi @dstogov
/ext/gd @devnexen
/ext/gettext @devnexen
/ext/gmp @Girgias
-/ext/intl @devnexen
+/ext/intl @devnexen @LamentXU123
+/ext/libxml @devnexen
/ext/json @bukka
-/ext/lexbor @kocsismate @ndossche
-/ext/libxml @ndossche
+/ext/lexbor @kocsismate
/ext/mbstring @alexdowad @youkidearitai
/ext/mysqli @bukka @kamil-tekiela
/ext/mysqlnd @bukka @kamil-tekiela @SakiTakamachi
@@ -44,20 +44,21 @@
/ext/pdo_pgsql @devnexen @SakiTakamachi
/ext/pdo_sqlite @SakiTakamachi
/ext/pgsql @devnexen
+/ext/phar @LamentXU123
/ext/random @TimWolla @zeriyoshi
/ext/reflection @DanielEScherzer
/ext/session @Girgias
-/ext/simplexml @ndossche
-/ext/soap @ndossche
+/ext/simplexml @devnexen
+/ext/soap @devnexen
/ext/sockets @devnexen
/ext/spl @Girgias
/ext/standard @bukka
-/ext/tidy @ndossche
/ext/uri @kocsismate @TimWolla
-/ext/xml @ndossche
-/ext/xmlreader @ndossche
-/ext/xmlwriter @ndossche
-/ext/xsl @ndossche
+/ext/xml @devnexen
+/ext/xmlreader @devnexen
+/ext/xmlwriter @devnexen
+/ext/xsl @devnexen
+/ext/zip @LamentXU123
/main @bukka
/sapi/fpm @bukka
/Zend/Optimizer @dstogov
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 53bd4df5a1ca..296835e02e0d 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -26,14 +26,15 @@ body:
attributes:
label: PHP Version
description: |
- Please run PHP with the `-v` flag (e.g. `php -v`, `php8.3 -v`, `php-fpm -v` or similar) and provide the full output of that command. If executing that command is not possible, please provide the full version number as given in PHPInfo.
+ Please run PHP with the `-v` flag (e.g. `php -v`, `php8.5 -v`, `php-fpm -v` or similar) and provide the full output of that command. If executing that command is not possible, please provide the full version number as given in PHPInfo.
Please make sure that the used PHP version [is a supported version](https://www.php.net/supported-versions.php).
placeholder: |
- PHP 8.3.19 (cli) (built: Mar 13 2025 17:44:40) (NTS)
- Copyright (c) The PHP Group
- Zend Engine v4.3.19, Copyright (c) Zend Technologies
- with Zend OPcache v8.3.19, Copyright (c), by Zend Technologies
+ PHP 8.5.2 (cli) (built: Jan 21 2026 17:35:28) (NTS)
+ Copyright © The PHP Group and Contributors
+ Built by Ubuntu
+ Zend Engine v4.5.2, Copyright © Zend by Perforce
+ with Zend OPcache v8.5.2, Copyright ©, Zend by Perforce
render: plain
validations:
required: true
diff --git a/.github/actions/apk/action.yml b/.github/actions/apk/action.yml
index 0cda4963a6a9..8d0fee161187 100644
--- a/.github/actions/apk/action.yml
+++ b/.github/actions/apk/action.yml
@@ -28,7 +28,6 @@ runs:
curl-dev \
freetype-dev \
gettext-dev \
- gnu-libiconv-dev \
gmp-dev \
icu-dev \
icu-data-full \
diff --git a/.github/actions/apt-x64/action.yml b/.github/actions/apt-x64/action.yml
index 7ae0a88ff755..3e654ef9fdc4 100644
--- a/.github/actions/apt-x64/action.yml
+++ b/.github/actions/apt-x64/action.yml
@@ -63,6 +63,7 @@ runs:
snmp-mibs-downloader \
freetds-dev \
unixodbc-dev \
+ libsqliteodbc \
llvm \
clang \
dovecot-core \
diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml
index f90a66f239de..db43010f396f 100644
--- a/.github/actions/brew/action.yml
+++ b/.github/actions/brew/action.yml
@@ -33,4 +33,6 @@ runs:
t1lib \
libxml2 \
libjpeg \
- libxslt
+ libxslt \
+ openssl@4
+ brew link --force --overwrite openssl@4
diff --git a/.github/actions/configure-alpine/action.yml b/.github/actions/configure-alpine/action.yml
index fe02dacfcdaf..d1b4cfea0412 100644
--- a/.github/actions/configure-alpine/action.yml
+++ b/.github/actions/configure-alpine/action.yml
@@ -43,7 +43,7 @@ runs:
--enable-pcntl \
--with-readline \
--enable-mbstring \
- --with-iconv=/usr \
+ --with-iconv \
--with-curl \
--with-gettext \
--enable-sockets \
diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml
index f0af959d2102..b9739a790530 100644
--- a/.github/actions/configure-macos/action.yml
+++ b/.github/actions/configure-macos/action.yml
@@ -11,7 +11,7 @@ runs:
set -x
BREW_OPT="$(brew --prefix)"/opt
export PATH="$BREW_OPT/bison/bin:$PATH"
- export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl/lib/pkgconfig"
+ export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/openssl@4/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/curl/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libffi/lib/pkgconfig"
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:$BREW_OPT/libxml2/lib/pkgconfig"
diff --git a/.github/actions/freebsd/action.yml b/.github/actions/freebsd/action.yml
index 3b6d0c4e8617..197362d9f52b 100644
--- a/.github/actions/freebsd/action.yml
+++ b/.github/actions/freebsd/action.yml
@@ -46,7 +46,7 @@ runs:
pkgconf \
webp \
libavif \
- `#sqlite3` \
+ sqlite3 \
curl \
$OPCACHE_TLS_TESTS_DEPS
@@ -57,9 +57,7 @@ runs:
--enable-debug \
--enable-option-checking=fatal \
--enable-fpm \
- `#--with-pdo-sqlite` \
- --without-sqlite3 \
- --without-pdo-sqlite \
+ --with-pdo-sqlite \
--without-pear \
--with-bz2 \
--with-avif \
diff --git a/.github/actions/setup-windows/action.yml b/.github/actions/setup-windows/action.yml
index e0a3fd4e7d31..a5fa4cdddcb1 100644
--- a/.github/actions/setup-windows/action.yml
+++ b/.github/actions/setup-windows/action.yml
@@ -16,5 +16,6 @@ runs:
- name: Setup PostgreSQL
shell: pwsh
run: |
- Set-Service -Name "postgresql-x64-14" -StartupType manual -Status Running
+ $postgresService = if ($env:PHP_BUILD_CRT -eq "vs18") { "postgresql-x64-17" } else { "postgresql-x64-14" }
+ Set-Service -Name $postgresService -StartupType manual -Status Running
pwsh -Command { $env:PGPASSWORD="root"; & "$env:PGBIN\psql" -U postgres -c "ALTER USER postgres WITH PASSWORD 'Password12!';" }
diff --git a/.github/labeler.yml b/.github/labeler.yml
index b9f0f36e147d..e9731352c8cc 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -20,6 +20,18 @@
- scripts/**/*
- win32/build/**/*
+"Category: CI":
+ - changed-files:
+ - any-glob-to-any-file:
+ - .circleci/*
+ - .github/actions/**/*
+ - .github/scripts/**/*
+ - .github/workflows/*
+ - .github/CODEOWNERS
+ - .github/labeler.yml
+ - .github/setup_hmailserver.php
+ - .github/matrix.php
+
"Extension: bcmath":
- changed-files:
- any-glob-to-any-file:
diff --git a/.github/matrix.php b/.github/matrix.php
index 2ce59cb657b5..7970442d7050 100644
--- a/.github/matrix.php
+++ b/.github/matrix.php
@@ -61,7 +61,6 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re
$test_macos = in_array('CI: macOS', $labels, true);
$test_msan = in_array('CI: MSAN', $labels, true);
$test_opcache_variation = in_array('CI: Opcache Variation', $labels, true);
- $test_pecl = in_array('CI: PECL', $labels, true);
$test_solaris = in_array('CI: Solaris', $labels, true);
$test_windows = in_array('CI: Windows', $labels, true);
@@ -80,7 +79,14 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re
$jobs['COMMUNITY']['matrix'] = version_compare($php_version, '8.4', '>=')
? ['type' => ['asan', 'verify_type_inference']]
: ['type' => ['asan']];
- $jobs['COMMUNITY']['config']['symfony_version'] = version_compare($php_version, '8.4', '>=') ? '8.1' : '7.4';
+ $jobs['COMMUNITY']['config']['symfony_version'] = match (true) {
+ version_compare($php_version, '8.3', '<=') => '7.4',
+ default => '',
+ };
+ $jobs['COMMUNITY']['config']['laravel_version'] = match (true) {
+ version_compare($php_version, '8.2', '<=') => '12.x',
+ default => '',
+ };
}
if (($all_jobs && $ref === 'master') || $test_coverage) {
$jobs['COVERAGE'] = true;
@@ -130,22 +136,24 @@ function select_jobs($repository, $trigger, $nightly, $labels, $php_version, $re
if ($all_jobs || $test_opcache_variation) {
$jobs['OPCACHE_VARIATION'] = true;
}
- if (($all_jobs && $ref === 'master') || $test_pecl) {
- $jobs['PECL'] = true;
- }
if (version_compare($php_version, '8.6', '>=') && ($all_jobs || $test_solaris)) {
$jobs['SOLARIS'] = true;
}
if ($all_jobs || !$no_jobs || $test_windows) {
- $jobs['WINDOWS']['matrix'] = $all_variations
- ? ['include' => [
- ['asan' => true, 'opcache' => true, 'x64' => true, 'zts' => true],
- ['asan' => false, 'opcache' => false, 'x64' => false, 'zts' => false],
- ]]
- : ['include' => [['asan' => false, 'opcache' => true, 'x64' => true, 'zts' => true]]];
- $jobs['WINDOWS']['config'] = version_compare($php_version, '8.4', '>=')
- ? ['vs_crt_version' => 'vs17']
- : ['vs_crt_version' => 'vs16'];
+ $matrix = [['asan' => false, 'opcache' => true, 'x64' => true, 'zts' => true]];
+ if ($all_variations) {
+ $matrix[] = ['asan' => true, 'opcache' => true, 'x64' => true, 'zts' => true];
+ $matrix[] = ['asan' => false, 'opcache' => false, 'x64' => false, 'zts' => false];
+ if (version_compare($php_version, '8.6', '>=')) {
+ $matrix[] = ['asan' => false, 'opcache' => true, 'x64' => true, 'zts' => true, 'clang' => true];
+ }
+ }
+ $jobs['WINDOWS']['matrix'] = ['include' => $matrix];
+ $jobs['WINDOWS']['config'] = match (true) {
+ version_compare($php_version, '8.6', '>=') => ['vs_crt_version' => 'vs18', 'runs_on' => 'windows-2025-vs2026'],
+ version_compare($php_version, '8.4', '>=') => ['vs_crt_version' => 'vs17', 'runs_on' => 'windows-2022'],
+ default => ['vs_crt_version' => 'vs16', 'runs_on' => 'windows-2022'],
+ };
}
if ($all_jobs || !$no_jobs || $test_freebsd) {
$jobs['FREEBSD']['matrix'] = $all_variations && version_compare($php_version, '8.3', '>=')
diff --git a/.github/scripts/download-bundled/boost-context.sh b/.github/scripts/download-bundled/boost-context.sh
index b1fd8708b85b..02febff73c3f 100755
--- a/.github/scripts/download-bundled/boost-context.sh
+++ b/.github/scripts/download-bundled/boost-context.sh
@@ -5,7 +5,7 @@ cd "$(dirname "$0")/../../.."
tmp_dir=/tmp/php-src-download-bundled/boost-context
rm -rf "$tmp_dir"
-revision=refs/tags/boost-1.86.0
+revision=refs/tags/boost-1.91.0
git clone --depth 1 --revision="$revision" https://github.com/boostorg/context.git "$tmp_dir"
@@ -16,12 +16,14 @@ cd Zend/asm
# remove unneeded files
rm jump_arm_aapcs_pe_armasm.asm
+rm jump_arm64_aapcs_pe_armclang.S
rm jump_i386_ms_pe_clang_gas.S
rm jump_i386_ms_pe_gas.asm
rm jump_i386_x86_64_sysv_macho_gas.S
rm jump_ppc32_ppc64_sysv_macho_gas.S
rm jump_x86_64_ms_pe_clang_gas.S
rm make_arm_aapcs_pe_armasm.asm
+rm make_arm64_aapcs_pe_armclang.S
rm make_i386_ms_pe_clang_gas.S
rm make_i386_ms_pe_gas.asm
rm make_i386_x86_64_sysv_macho_gas.S
diff --git a/.github/scripts/download-bundled/uriparser.sh b/.github/scripts/download-bundled/uriparser.sh
index 8820a67333f0..2b8d61a2d56f 100755
--- a/.github/scripts/download-bundled/uriparser.sh
+++ b/.github/scripts/download-bundled/uriparser.sh
@@ -5,7 +5,7 @@ cd "$(dirname "$0")/../../.."
tmp_dir=/tmp/php-src-download-bundled/uriparser
rm -rf "$tmp_dir"
-revision=refs/tags/uriparser-1.0.0
+revision=refs/tags/uriparser-1.0.2
git clone --depth 1 --revision="$revision" https://github.com/uriparser/uriparser.git "$tmp_dir"
diff --git a/.github/scripts/windows/build_task.bat b/.github/scripts/windows/build_task.bat
index b65479451849..1177cef3be4d 100644
--- a/.github/scripts/windows/build_task.bat
+++ b/.github/scripts/windows/build_task.bat
@@ -26,12 +26,18 @@ if %errorlevel% neq 0 exit /b 3
if "%THREAD_SAFE%" equ "0" set ADD_CONF=%ADD_CONF% --disable-zts
if "%INTRINSICS%" neq "" set ADD_CONF=%ADD_CONF% --enable-native-intrinsics=%INTRINSICS%
if "%ASAN%" equ "1" set ADD_CONF=%ADD_CONF% --enable-sanitizer --enable-debug-pack
+if "%CLANG_TOOLSET%" equ "1" set ADD_CONF=%ADD_CONF% --with-toolset=clang
rem C4018: comparison: signed/unsigned mismatch
rem C4146: unary minus operator applied to unsigned type
rem C4244: type conversion, possible loss of data
rem C4267: 'size_t' type conversion, possible loss of data
-set CFLAGS=/W3 /WX /wd4018 /wd4146 /wd4244 /wd4267
+if "%CLANG_TOOLSET%" equ "1" (
+ rem Clang is much stricter than MSVC, produces too many warnings that would fail the build with /WX
+ set CFLAGS=/W3 /wd4018 /wd4146 /wd4244 /wd4267
+) else (
+ set CFLAGS=/W3 /WX /wd4018 /wd4146 /wd4244 /wd4267
+)
cmd /c configure.bat ^
--enable-snapshot-build ^
diff --git a/.github/scripts/windows/find-vs-toolset.bat b/.github/scripts/windows/find-vs-toolset.bat
index 2d9e68e73031..ecaca6775b07 100644
--- a/.github/scripts/windows/find-vs-toolset.bat
+++ b/.github/scripts/windows/find-vs-toolset.bat
@@ -3,7 +3,7 @@
setlocal enabledelayedexpansion
if "%~1"=="" (
- echo ERROR: Usage: %~nx0 [vc14^|vc15^|vs16^|vs17]
+ echo ERROR: Usage: %~nx0 [vc14^|vc15^|vs16^|vs17^|vs18]
exit /b 1
)
@@ -11,6 +11,7 @@ set "toolsets_vc14=14.0"
set "toolsets_vc15="
set "toolsets_vs16="
set "toolsets_vs17="
+set "toolsets_vs18="
for /f "usebackq tokens=*" %%I in (`vswhere.exe -latest -find "VC\Tools\MSVC"`) do set "MSVCDIR=%%I"
@@ -30,8 +31,10 @@ for /f "delims=" %%D in ('dir /b /ad "%MSVCDIR%"') do (
set "toolsets_vc15=%%D"
) else if !min! LEQ 29 (
set "toolsets_vs16=%%D"
- ) else (
+ ) else if !min! LEQ 49 (
set "toolsets_vs17=%%D"
+ ) else (
+ set "toolsets_vs18=%%D"
)
)
)
diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml
index 83aae655a3c3..e269ed336408 100644
--- a/.github/workflows/test-suite.yml
+++ b/.github/workflows/test-suite.yml
@@ -16,6 +16,7 @@ jobs:
name: LINUX_PPC64_ASAN_DEBUG_ZTS
# This runs on a self-hosted runner; see https://wiki.php.net/systems/ci
runs-on: [self-hosted, gentoo, ppc64]
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -55,6 +56,7 @@ jobs:
if: ${{ fromJson(inputs.branch).jobs.ALPINE }}
name: ALPINE_X64_ASAN_DEBUG_ZTS
runs-on: ubuntu-24.04
+ timeout-minutes: 180
container:
image: 'alpine:3.22'
steps:
@@ -130,6 +132,7 @@ jobs:
matrix: ${{ fromJson(inputs.branch).jobs.LINUX_X64.matrix }}
name: "LINUX_X64${{ matrix.name }}_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}"
runs-on: ubuntu-${{ fromJson(inputs.branch).config.ubuntu_version }}
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -166,7 +169,7 @@ jobs:
${{ matrix.variation && 'CFLAGS="-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1 -DZEND_VERIFY_TYPE_INFERENCE"' || '' }}
${{ (matrix.variation && fromJson(inputs.branch).jobs.LINUX_X64.config.variation_enable_zend_max_execution_timers) && '--enable-zend-max-execution-timers' || '' }}
--${{ matrix.debug && 'enable' || 'disable' }}-debug
- ${{ matrix.debug && 'CXXFLAGS="-D_GLIBCXX_ASSERTIONS"' || '' }}
+ ${{ (matrix.variation || matrix.debug) && format('CXXFLAGS="{0} {1}"', matrix.variation && '-DZEND_VERIFY_TYPE_INFERENCE' || '', matrix.debug && '-D_GLIBCXX_ASSERTIONS' || '') || '' }}
--${{ matrix.zts && 'enable' || 'disable' }}-zts
asan: ${{ matrix.asan && 'true' || 'false' }}
skipSlow: ${{ (matrix.asan && !inputs.all_variations) && 'true' || 'false' }}
@@ -228,6 +231,7 @@ jobs:
matrix: ${{ fromJson(inputs.branch).jobs.LINUX_X32.matrix }}
name: "LINUX_X32_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}"
runs-on: ubuntu-latest
+ timeout-minutes: 180
container:
image: ubuntu:${{ fromJson(inputs.branch).config.ubuntu_version }}
env:
@@ -309,6 +313,7 @@ jobs:
matrix: ${{ fromJson(inputs.branch).jobs.MACOS.matrix }}
name: "MACOS_${{ matrix.arch }}_${{ matrix.debug && 'DEBUG' || 'RELEASE' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}"
runs-on: macos-${{ matrix.arch == 'X64' && '15-intel' || fromJson(inputs.branch).jobs.MACOS.config.arm64_version }}
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -386,6 +391,7 @@ jobs:
FIREBIRD_USER: test
FIREBIRD_PASSWORD: test
runs-on: ubuntu-24.04
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -413,10 +419,15 @@ jobs:
with:
enableOpcache: true
jitType: tracing
- - uses: codecov/codecov-action@v5
+ - name: Generate coverage report
+ if: ${{ !cancelled() }}
+ run: make gcovr-xml
+ - uses: codecov/codecov-action@v6
if: ${{ !cancelled() }}
with:
+ disable_search: true
fail_ci_if_error: true
+ files: gcovr.xml
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
COMMUNITY:
@@ -506,7 +517,8 @@ jobs:
- name: Test Laravel
if: ${{ !cancelled() }}
run: |
- git clone https://github.com/laravel/framework.git --depth=1
+ branch=${{ fromJson(inputs.branch).jobs.COMMUNITY.config.laravel_version }}
+ git clone https://github.com/laravel/framework.git --depth=1 ${branch:+--branch="$branch"}
cd framework
git rev-parse HEAD
php /usr/bin/composer install --no-progress --ignore-platform-req=php+
@@ -551,7 +563,8 @@ jobs:
- name: Test Symfony
if: ${{ !cancelled() }}
run: |
- git clone https://github.com/symfony/symfony.git --depth=1 --branch="${{ fromJson(inputs.branch).jobs.COMMUNITY.config.symfony_version }}"
+ branch=${{ fromJson(inputs.branch).jobs.COMMUNITY.config.symfony_version }}
+ git clone https://github.com/symfony/symfony.git --depth=1 ${branch:+--branch="$branch"}
cd symfony
git rev-parse HEAD
php /usr/bin/composer install --no-progress --ignore-platform-req=php+
@@ -560,6 +573,10 @@ jobs:
php -r '$c = file_get_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php"); $c = str_replace("public function testSanitizeDeepNestedString()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testSanitizeDeepNestedString()", $c); file_put_contents("src/Symfony/Component/HtmlSanitizer/Tests/HtmlSanitizerCustomTest.php", $c);'
# Buggy FFI test in Symfony, see https://github.com/symfony/symfony/issues/47668
php -r '$c = file_get_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php"); $c = str_replace("public function testCastNonTrailingCharPointer()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testCastNonTrailingCharPointer()", $c); file_put_contents("src/Symfony/Component/VarDumper/Tests/Caster/FFICasterTest.php", $c);'
+ # Causes massive amounts of system calls with USE_ZEND_ALLOC=0, exceeding the timeout
+ if [ -e 'src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php' ]; then
+ php -r '$c = file_get_contents("src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php"); $c = str_replace("public function testReadWithPasteDetectionAbortsBeyondMaxBytes()", "#[\\PHPUnit\\Framework\\Attributes\\Group('"'"'skip'"'"')]\n public function testReadWithPasteDetectionAbortsBeyondMaxBytes()", $c); file_put_contents("src/Symfony/Component/Console/Tests/Helper/FileInputHelperTest.php", $c);'
+ fi
export SYMFONY_DEPRECATIONS_HELPER=max[total]=999
X=0
for component in $(find src/Symfony -mindepth 2 -type f -name phpunit.xml.dist -printf '%h\n'); do
@@ -635,6 +652,7 @@ jobs:
FIREBIRD_PASSWORD: test
name: OPCACHE_VARIATION
runs-on: ubuntu-${{ fromJson(inputs.branch).config.ubuntu_version }}
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -698,6 +716,7 @@ jobs:
if: ${{ fromJson(inputs.branch).jobs.MSAN }}
name: MSAN
runs-on: ubuntu-${{ fromJson(inputs.branch).config.ubuntu_version }}
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -789,6 +808,7 @@ jobs:
if: ${{ fromJson(inputs.branch).jobs.LIBMYSQLCLIENT }}
name: LIBMYSQLCLIENT
runs-on: ubuntu-${{ fromJson(inputs.branch).config.ubuntu_version }}
+ timeout-minutes: 180
steps:
- name: git checkout
uses: actions/checkout@v6
@@ -820,126 +840,19 @@ jobs:
uses: ./.github/actions/test-libmysqlclient
- name: Verify generated files are up to date
uses: ./.github/actions/verify-generated-files
- PECL:
- if: ${{ fromJson(inputs.branch).jobs.PECL }}
- runs-on: ubuntu-24.04
- steps:
- - name: git checkout PHP
- uses: actions/checkout@v6
- with:
- path: php
- ref: ${{ fromJson(inputs.branch).ref }}
- # Used for ccache action
- - name: Move .github
- run: mv php/.github .
- - name: git checkout apcu
- uses: actions/checkout@v6
- with:
- repository: krakjoe/apcu
- path: apcu
- - name: git checkout imagick
- uses: actions/checkout@v6
- with:
- repository: Imagick/imagick
- path: imagick
- - name: git checkout memcached
- uses: actions/checkout@v6
- with:
- repository: php-memcached-dev/php-memcached
- path: memcached
- - name: git checkout redis
- if: ${{ false }}
- uses: actions/checkout@v6
- with:
- repository: phpredis/phpredis
- path: redis
- - name: git checkout xdebug
- uses: actions/checkout@v6
- with:
- repository: xdebug/xdebug
- path: xdebug
- - name: git checkout yaml
- uses: actions/checkout@v6
- with:
- repository: php/pecl-file_formats-yaml
- path: yaml
- - name: apt
- run: |
- sudo apt-get update
- sudo apt-get install -y --no-install-recommends \
- ccache \
- libmemcached-dev \
- imagemagick \
- libmagickwand-dev \
- bison \
- re2c
- - name: ccache
- uses: ./.github/actions/ccache
- with:
- name: "${{ github.job }}"
- php_directory: php
- - name: build PHP
- run: |
- cd php
- ./buildconf --force
- ./configure \
- --enable-option-checking=fatal \
- --prefix=/opt/php \
- --enable-cli \
- --disable-all \
- --enable-session \
- --enable-werror
- make -j$(/usr/bin/nproc)
- sudo make install
- - name: build apcu
- run: |
- cd apcu
- /opt/php/bin/phpize
- ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config
- make -j$(/usr/bin/nproc)
- - name: build imagick
- run: |
- cd imagick
- /opt/php/bin/phpize
- ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config
- make -j$(/usr/bin/nproc)
- - name: build memcached
- run: |
- cd memcached
- /opt/php/bin/phpize
- ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config
- make -j$(/usr/bin/nproc)
- - name: build redis
- if: ${{ false }}
- run: |
- cd redis
- /opt/php/bin/phpize
- ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config
- make -j$(/usr/bin/nproc)
- - name: build xdebug
- run: |
- cd xdebug
- /opt/php/bin/phpize
- ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config
- make -j$(/usr/bin/nproc)
- - name: build yaml
- run: |
- cd yaml
- /opt/php/bin/phpize
- ./configure --prefix=/opt/php --with-php-config=/opt/php/bin/php-config
- make -j$(/usr/bin/nproc)
WINDOWS:
if: ${{ fromJson(inputs.branch).jobs.WINDOWS }}
strategy:
fail-fast: false
matrix: ${{ fromJson(inputs.branch).jobs.WINDOWS.matrix }}
- name: "WINDOWS_${{ matrix.x64 && 'X64' || 'X86' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}${{ matrix.asan && '_ASAN' || ''}}"
- runs-on: windows-2022
+ name: "WINDOWS_${{ matrix.x64 && 'X64' || 'X86' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}${{ matrix.asan && '_ASAN' || ''}}${{ matrix.clang && '_CLANG' || ''}}"
+ runs-on: ${{ fromJson(inputs.branch).jobs.WINDOWS.config.runs_on }}
+ timeout-minutes: 180
env:
PHP_BUILD_CACHE_BASE_DIR: C:\build-cache
PHP_BUILD_OBJ_DIR: C:\obj
PHP_BUILD_CACHE_SDK_DIR: C:\build-cache\sdk
- PHP_BUILD_SDK_BRANCH: php-sdk-2.5.0
+ PHP_BUILD_SDK_BRANCH: php-sdk-2.7.1
PHP_BUILD_CRT: ${{ fromJson(inputs.branch).jobs.WINDOWS.config.vs_crt_version }}
PLATFORM: ${{ matrix.x64 && 'x64' || 'x86' }}
THREAD_SAFE: "${{ matrix.zts && '1' || '0' }}"
@@ -947,6 +860,7 @@ jobs:
PARALLEL: -j2
OPCACHE: "${{ matrix.opcache && '1' || '0' }}"
ASAN: "${{ matrix.asan && '1' || '0' }}"
+ CLANG_TOOLSET: "${{ matrix.clang && '1' || '0' }}"
steps:
- name: git config
run: git config --global core.autocrlf false && git config --global core.eol lf
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 69ba4c0e4865..eafedec5eafa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,15 +2,17 @@ name: Test
on:
push:
paths-ignore: &ignore_paths
+ - .circleci/**
+ - .github/CODEOWNERS
+ - .github/ISSUE_TEMPLATE/**
+ - '**/*.md'
+ - '**/*.rst'
- docs/**
+ - EXTENSIONS
+ - LICENSE
- NEWS
- UPGRADING
- UPGRADING.INTERNALS
- - '**/README.*'
- - CONTRIBUTING.md
- - CODING_STANDARDS.md
- - .cirrus.yml
- - .circleci/**
branches:
- PHP-8.2
- PHP-8.3
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index 6338a1cb945d..fa79580f42ec 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -3,6 +3,8 @@ on:
push:
paths:
- 'main/network.c'
+ - 'main/io/**'
+ - 'main/php_io.h'
- 'tests/unit/**'
- '.github/workflows/unit-tests.yml'
branches:
@@ -10,6 +12,8 @@ on:
pull_request:
paths:
- 'main/network.c'
+ - 'main/io/**'
+ - 'main/php_io.h'
- 'tests/unit/**'
- '.github/workflows/unit-tests.yml'
branches:
diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md
index 47b76717c839..3a53d0e258ca 100644
--- a/CODING_STANDARDS.md
+++ b/CODING_STANDARDS.md
@@ -79,6 +79,16 @@ rewritten to comply with these rules.
return value for functions that perform some operation that may
succeed or fail.
+1. When throwing a `ValueError` or emitting a warning, use consistent
+ phrasing for error messages. Common patterns are:
+
+ * Type errors: `must be of type int` (use the type name, not e.g. `must be an integer`)
+ * Range/boundary: `must be between X and Y` / `must be greater than [or equal to] X` / `must be less than X` / `must be finite`
+ * String constraints: `must not contain any null bytes` / `must not be empty` / `must be a single character`
+ * Valid value: `must be a valid X` (e.g. `must be a valid encoding`, `must be a valid calendar ID`)
+ * Enum-like: `must be one of X, Y, or Z`
+ * Structural: `must have X` / `must have key X` / `must have N elements`
+
## User functions/methods naming conventions
1. Function names for user-level functions should be enclosed with in the
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0f797df1e97a..aabd945ad385 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -27,6 +27,7 @@ had several contributions accepted, commit privileges are often quickly granted.
* [Git commit rules](#git-commit-rules)
* [Copyright and license headers](#copyright-and-license-headers)
* [NEWS file](#news)
+* [LLM usage in GitHub comments](#llm-usage-in-github-comments)
## Pull requests
@@ -36,8 +37,8 @@ implement RFCs. Please be sure to include tests as appropriate!
By submitting a pull request, you certify that you have the necessary rights
to submit the work, that the work does not violate any third-party rights
(including those of your employer, if applicable), and that you license your
-contribution under the PHP License or under another license if explicitly
-accepted by the PHP project maintainers.
+contribution under the [Modified BSD License](LICENSE) or under another license
+if explicitly accepted by the PHP project maintainers.
If you are fixing a bug, then please submit your PR against the lowest actively
supported branch of PHP that the bug affects (only green branches on
@@ -90,7 +91,7 @@ repository. Mailing list subscription is explained on the
[mailing lists page](https://www.php.net/mailing-lists.php).
You may also want to read
-[The Mysterious PHP RFC Process](https://blogs.oracle.com/opal/post/the-mysterious-php-rfc-process-and-how-you-can-change-the-web)
+[The Mysterious PHP RFC Process](https://web.archive.org/web/20210621140006/https://blogs.oracle.com/opal/the-mysterious-php-rfc-process-and-how-you-can-change-the-web)
for additional notes on the best way to approach submitting an RFC.
## Technical resources
@@ -151,9 +152,8 @@ about what you're working on, you can contact us via the
issues.
Although not a formal channel, you can also find a number of core developers on
-the #php.pecl channel on [EFnet](http://www.efnet.org/). Similarly, many
-documentation writers can be found on #php.doc. Windows development IRC channel
-is available at #winphp-dev on FreeNode.
+the [php community discord server](https://phpc.chat/) (`#php-internals` channel)
+where many people are eager to help.
## PHP source code directory structure
@@ -355,30 +355,21 @@ Having said that, here are the organizational rules:
`--enable-zts` switch to ensure your code handles TSRM correctly and doesn't
break for those who need that.
-Currently, we have the following branches in use:
-
-| Branch | |
-| --------- | --------- |
-| master | Active development branch for PHP 8.6, which is open for backwards incompatible changes and major internal API changes. |
-| PHP-8.5 | Is used to release the PHP 8.5.x series. This is a current stable version and is open for bugfixes only. |
-| PHP-8.4 | Is used to release the PHP 8.4.x series. This is a current stable version and is open for bugfixes only. |
-| PHP-8.3 | Is used to release the PHP 8.3.x series. This is a current stable version and is open for bugfixes only. |
-| PHP-8.2 | Is used to release the PHP 8.2.x series. This is an old stable version and is open for security fixes only. |
-| PHP-8.1 | Is used to release the PHP 8.1.x series. This is an old stable version and is open for security fixes only. |
-| PHP-8.0 | This branch is closed. |
-| PHP-7.4 | This branch is closed. |
-| PHP-7.3 | This branch is closed. |
-| PHP-7.2 | This branch is closed. |
-| PHP-7.1 | This branch is closed. |
-| PHP-7.0 | This branch is closed. |
-| PHP-5.6 | This branch is closed. |
-| PHP-5.5 | This branch is closed. |
-| PHP-5.4 | This branch is closed. |
-| PHP-5.3 | This branch is closed. |
-| PHP-5.2 | This branch is closed. |
-| PHP-5.1 | This branch is closed. |
-| PHP-4.4 | This branch is closed. |
-| PHP-X.Y.Z | These branches are used for the release managers for tagging the releases, hence they are closed to the general public. |
+The master branch is an active development branch for the newest version of PHP,
+which is open for backwards incompatible changes and major internal API changes.
+
+For PHP-X.Y branches, they are used to release the PHP X.Y.z series. Please see
+the [supported versions page](https://www.php.net/supported-versions.php) to get
+the status of each version.
+
+If a version is described as "Active support", the corresponding branch is a
+current stable version and is open for bugfixes only. If a version is described
+as "Security fixes only", the corresponding branch is an old stable version
+and is open for security fixes only. If a version is described as "End of life",
+the corresponding branch is closed.
+
+Note that PHP-X.Y.Z branches are used for the release managers for tagging the
+releases, hence they are closed to the general public.
The next few rules are more of a technical nature:
@@ -438,15 +429,13 @@ New source code files should include the following header block:
```c
/*
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Author: |
+----------------------------------------------------------------------+
@@ -537,6 +526,12 @@ If for some reason a feature is introduced in a branch lower than master,
although this is strictly prohibited by other policies, then the entry must
also be in master.
+## LLM usage in GitHub comments
+
+When using LLMs to generate comments to maintainers for any purpose other than
+direct translation, we would highly appreciate it if you disclosed the relevant
+paragraphs as such via markdown quote.
+
## Thanks
Thank you for contributing to PHP!
diff --git a/EXTENSIONS b/EXTENSIONS
index 8da09aed5392..01807655e3a5 100644
--- a/EXTENSIONS
+++ b/EXTENSIONS
@@ -171,28 +171,28 @@ PRIMARY MAINTAINER: Christian Stocker (2003 - 2011)
Rob Richards (2003 - 2012)
Marcus Börger (2003 - 2006)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
SINCE: 5.0
-------------------------------------------------------------------------------
EXTENSION: simplexml
PRIMARY MAINTAINER: Marcus Börger (2003 - 2008)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
SINCE: 5.0
-------------------------------------------------------------------------------
EXTENSION: soap
PRIMARY MAINTAINER: Dmitry Stogov (2004 - 2018)
Nora Dossche (2024 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
-------------------------------------------------------------------------------
EXTENSION: xml
PRIMARY MAINTAINER: Thies C. Arntzen (1999 - 2002)
Rob Richards (2003 - 2013)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
-------------------------------------------------------------------------------
EXTENSION: lexbor
@@ -206,28 +206,28 @@ EXTENSION: libxml
PRIMARY MAINTAINER: Rob Richards (2003 - 2009)
Christian Stocker (2004 - 2011)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
-------------------------------------------------------------------------------
EXTENSION: xmlreader
PRIMARY MAINTAINER: Rob Richards (2004 - 2010)
Christian Stocker (2004 - 2004)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
-------------------------------------------------------------------------------
EXTENSION: xmlwriter
PRIMARY MAINTAINER: Rob Richards (2004 - 2010)
Pierre-Alain Joye (2005-2009)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
-------------------------------------------------------------------------------
EXTENSION: xsl
PRIMARY MAINTAINER: Christian Stocker (2003 - 2011)
Rob Richards (2003 - 2010)
Nora Dossche (2023 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
SINCE: 5.0
-------------------------------------------------------------------------------
@@ -351,6 +351,7 @@ STATUS: Working
EXTENSION: intl
PRIMARY MAINTAINER: Stanislav Malyshev (2008 - 2019)
Anatol Belski (2017 - 2018)
+ Weilin Du (2026 - 2026)
MAINTENANCE: Maintained
STATUS: Working
SINCE: 5.3
@@ -409,6 +410,7 @@ PRIMARY MAINTAINER: Greg Beaver (2008 - 2009)
Marcus Börger (2008 - 2008)
Steph Fox (2008 - 2008)
Bishop Bettini (2018 - 2019)
+ Weilin Du (2026 - 2026)
MAINTENANCE: Maintained
STATUS: Working
SINCE: 5.3
@@ -496,7 +498,7 @@ PRIMARY MAINTAINER: John Coggeshall (2003 - 2006)
Ilia Alshanetsky (2003 - 2009)
Nuno Lopes (2006 - 2012)
Nora Dossche (2025 - 2026)
-MAINTENANCE: Odd fixes
+MAINTENANCE: Orphan
STATUS: Working
-------------------------------------------------------------------------------
EXTENSION: tokenizer
@@ -514,7 +516,8 @@ SINCE: 8.5.0
-------------------------------------------------------------------------------
EXTENSION: zip
PRIMARY MAINTAINER: Pierre-Alain Joye (2006 - 2011)
- Remi Collet (2013-2020)
+ Remi Collet (2013 - 2020)
+ Weilin Du (2026 - 2026)
MAINTENANCE: Maintained
STATUS: Working
-------------------------------------------------------------------------------
diff --git a/LICENSE b/LICENSE
index b155a18c2fb7..ee42a57fffbb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,68 +1,27 @@
---------------------------------------------------------------------
- The PHP License, version 3.01
-Copyright (c) 1999 - 2025 The PHP Group. All rights reserved.
---------------------------------------------------------------------
+Copyright © 1999–2026, The PHP Group and Contributors.
+Copyright © 1999–2026, Zend Technologies Ltd., a subsidiary company of Perforce Software, Inc.
Redistribution and use in source and binary forms, with or without
-modification, is permitted provided that the following conditions
-are met:
-
- 1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above copyright
- notice, this list of conditions and the following disclaimer in
- the documentation and/or other materials provided with the
- distribution.
-
- 3. The name "PHP" must not be used to endorse or promote products
- derived from this software without prior written permission. For
- written permission, please contact group@php.net.
-
- 4. Products derived from this software may not be called "PHP", nor
- may "PHP" appear in their name, without prior written permission
- from group@php.net. You may indicate that your software works in
- conjunction with PHP by saying "Foo for PHP" instead of calling
- it "PHP Foo" or "phpfoo"
-
- 5. The PHP Group may publish revised and/or new versions of the
- license from time to time. Each version will be given a
- distinguishing version number.
- Once covered code has been published under a particular version
- of the license, you may always continue to use it under the terms
- of that version. You may also choose to use such covered code
- under the terms of any subsequent version of the license
- published by the PHP Group. No one other than the PHP Group has
- the right to modify the terms applicable to covered code created
- under this License.
-
- 6. Redistributions of any form whatsoever must retain the following
- acknowledgment:
- "This product includes PHP software, freely available from
- ".
-
-THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND
-ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PHP
-DEVELOPMENT TEAM OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
-INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
-OF THE POSSIBILITY OF SUCH DAMAGE.
-
---------------------------------------------------------------------
-
-This software consists of voluntary contributions made by many
-individuals on behalf of the PHP Group.
-
-The PHP Group can be contacted via Email at group@php.net.
-
-For more information on the PHP Group and the PHP project,
-please see .
-
-PHP includes the Zend Engine, freely available at
-.
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/NEWS b/NEWS
index 4dcac8bb21e4..63e4fdf0c244 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,211 @@
PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
-?? ??? ????, PHP 8.6.0alpha1
+?? ??? ????, PHP 8.6.0alpha3
+
+- Core:
+ . Implemented partial function application RFC. (Arnaud)
+ . Fixed bug GH-22263 (reset typed property default on every unserialize
+ failure path). (David Carlier)
+
+- DOM:
+ . Fixed bug GH-22825 (DOMElement::setAttribute() fails silently when the DTD
+ declares a default value for the attribute). (iliaal)
+
+- GMP:
+ . Fixed GMP power and shift operators to reject GMP right operands outside
+ the unsigned long range instead of silently truncating them. (Weilin Du)
+ . Fixed GMP integer string parsing to reject strings containing NUL bytes
+ instead of silently truncating them. (Weilin Du)
+ . Fixed GMP error messages that referenced outdated parameter names.
+ (Weilin Du)
+
+- Intl:
+ . Fixed grammatical issues in Normalizer invalid form and IntlCalendar time
+ zone offset error messages. (Weilin Du)
+
+- ODBC:
+ . Fixed bug GH-22668 (Heap buffer over-read when a column value exceeds the
+ driver-reported display size). (iliaal)
+
+- Opcache:
+ . Re-enable JIT for ZTS builds on Apple Silicon. (realFlowControl)
+
+- PDO_ODBC:
+ . Fixed bug GH-22667 (Heap buffer over-read when a column value exceeds the
+ driver-reported display size). (iliaal)
+ . Fixed bug GH-22666 (Heap buffer overflow when an output parameter value is
+ longer than the declared maxlen). (iliaal)
+ . Fixed bug GH-22665 (Out-of-bounds write when the ODBC driver reports a
+ diagnostic message length beyond the error buffer). (iliaal)
+
+- Phar:
+ . Fixed grammatical issues and outdated terminology in Phar error messages.
+ (Weilin Du)
+
+- Reflection:
+ . Fixed bug GH-22681 (Reflection*::__toString() truncates on null bytes).
+ (DanielEScherzer)
+
+- SOAP:
+ . Fixed header injection through the Content-Type context option, the
+ soapaction and the cookie names and values. (David Carlier)
+
+- MBString:
+ . Fixed bug GH-22779 (mb_strrpos() returns the wrong position for a negative
+ offset in a non-UTF-8 encoding). (Eyüp Can Akman)
+
+- Sockets:
+ . Fixed socket_set_option() validation error messages for UDP_SEGMENT and
+ TCP_USER_TIMEOUT, and SO_LINGER options. (Weilin Du)
+ . Fixed various memory related issues in ext/sockets. (David Carlier)
+
+- Streams:
+ . Added a new IO copy API used by php_stream_copy_to_stream_ex() that
+ leverages platform primitives (sendfile, splice, copy_file_range,
+ TransmitFile) for faster stream copying. (Jakub Zelenka, David Carlier)
+
+16 Jul 2026, PHP 8.6.0alpha2
+
+- Core:
+ . Sync Boost.Context assembly with 1.91.0. (kn1g78)
+ . Fixed bug GH-22387 (AST pretty-printing drops meaningful parentheses around
+ RHS of instanceof). (timwolla)
+ . Fixed bug GH-15672 and GH-15911 (Stack overflow when an internal function
+ recurses through zend_call_function, such as a self-attached SPL
+ iterator). (iliaal)
+ . Lock unmodified readonly properties for modification after clone-with.
+ (NickSdot)
+ . abort() instead of exit() on hard OOM. (realFlowControl)
+ . perf: ZTS: move AG and SCNG into native __thread storage. (henderkes)
+
+- Calendar:
+ . Fixed bug GH-22602 (gregoriantojd() and juliantojd() integer overflow with
+ INT_MAX year). (arshidkv12)
+
+- Curl:
+ . Added CURLOPT_SEEKFUNCTION and the CURL_SEEKFUNC_OK, CURL_SEEKFUNC_FAIL
+ and CURL_SEEKFUNC_CANTSEEK constants, letting libcurl rewind a streamed
+ request body to resend it on a redirect, multi-pass authentication or a
+ retried reused connection. (GrahamCampbell)
+
+- Date:
+ . Update timelib to 2022.17. (Derick)
+ . Fixed bug GH-19803 (Parsing a string with a single white space does create
+ an error). (Derick)
+ . Fixed Unix timestamps in February of the year 0 are misparsed with
+ @-notation. (LukasGelbmann)
+ . Fixed bug GH-11368 (idate() doesn't work for the year -1). (Derick)
+ . Fixed bug GH-11310 (__debugInfo does nothing on userland classes extending
+ Date classes). (Derick)
+
+- DBA:
+ . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz)
+
+- DOM:
+ . Fixed bug GH-22570 (Stack overflow when serializing a deeply nested
+ Dom\XMLDocument). (iliaal)
+ . Fixed Dom\DtdNamedNodeMap integer dimension access so negative indexes
+ return NULL and indexes outside the int range throw ValueError instead of
+ returning the first entity or notation. (Weilin Du)
+ . Fixed bug GH-22623 (use after free with namespace nodes from
+ XSLTProcessor::registerFunctions())/ (David Carlier)
+ . Fixed bug GH-22554 (use-after-free with XPath callback returning a node
+ from a foreign document). (David Carlier)
+
+- Exif:
+ . Fixed bug GH-11020 (exif_read_data() emits a spurious "Illegal IFD size"
+ warning when an IFD is not followed by a next-IFD offset). (Eyüp Can Akman)
+
+- GMP:
+ . Fixed bug GH-22549 (Assertion failure / UB on a compound GMP power or shift
+ assignment with a negative exponent). (iliaal)
+
+- Intl:
+ . Fixed NumberFormatter::parse() and NumberFormatter::parseCurrency() to
+ reject offset values outside the 32-bit range instead of silently
+ truncating them. (Weilin Du)
+ . IntlDateFormatter::parse()/datefmt_parse() and
+ IntlDateFormatter::localtime()/datefmt_localtime() now raise TypeError
+ when the offset argument is not of type int. (Weilin Du)
+
+- JSON:
+ . Report unterminated JSON strings as syntax errors. (timwolla)
+ . Improve performance error position tracking during JSON decoding.
+ (henderkes)
+ . Fixed bug GH-22514 (Incorrect error column in PHP 8.6 JSON parser).
+ (henderkes, timwolla)
+
+- Opcache:
+ . Fixed bug GH-21770 (Infinite recursion in property hook getter in opcache
+ preloaded trait). (iliaal)
+
+- OpenSSL:
+ . Added $salt_length parameter to openssl_sign() and openssl_verify() with
+ new OPENSSL_RSA_PSS_SALTLEN_* constants. (Jakub Zelenka)
+ . Fixed timeout for supplemental read at end of a blocking stream in SSL
+ stream wrapper. (ilutov)
+ . Fixed stream_socket_get_crypto_status() after supplemental read. (ilutov)
+
+- PDO_ODBC:
+ . Fixed bug GH-20726 (Crash with ODBC connection pooling when the DSN
+ carries no credentials). (iliaal)
+
+- PHPDBG:
+ . Fixed fleaked lowercased lookup keys in phpdbg_resolve_opline_break.
+ (jorgsowa)
+ . Fixed off-by-one in phpdbg_safe_class_lookup() causing class lookups to
+ always fail during phpdbg's signal-safe interruption path. (jorgsowa)
+
+- Reflection:
+ . Fixed bug GH-22683 (Reflection(Class)Constant::__toString() should not warn
+ on NAN conversions). (Khaled Alam)
+ . Fixed bug GH-22681 (Reflection*::__toString() truncates on null bytes).
+ (DanielEScherzer)
+
+- Session:
+ . Fixed bug GH-21314 (Different session garbage collector behavior between
+ PHP 8.3 and PHP 8.5). (jorgsowa)
+
+- SOAP:
+ . Fixed bug GH-22585 (OOM on bailout with uninitialized
+ do_request() parameters). (David Carlier)
+ . Fixed xsd:hexBinary decoding to reject odd-length values instead of
+ silently truncating the last nibble. (Weilin Du)
+ . Made SOAP encoding errors report the affected type or failing operation
+ instead of the generic "Violation of encoding rules" message. (Weilin Du)
+
+- Standard:
+ . Fixed sleep() and usleep() to reject values that overflow the underlying
+ unsigned int timeout. (Weilin Du)
+ . Fixed bug GH-22671 (assert.bail aborts the process when the assert callback
+ throws an exception whose reporting re-throws). (iliaal)
+ . Fixed bug GH-22678 (Use-after-free in array_multisort() when the comparator
+ mutates the array being sorted). (azchin, iliaal)
+
+- Streams:
+ . Fixed bug GH-21468 (Segfault in file_get_contents w/ a https URL
+ and a proxy set). (CVE-2026-12184) (ndossche)
+
+- URI:
+ . Fixed bug GH-22628 (Percent-encoding of caret in WHATWG URL paths is not
+ performed). (kocsismate)
+ . Fixed bug GH-22629 (WHATWG Validation error incorrect with empty host and
+ non-empty userinfo). (kocsismate)
+
+- Zip:
+ . Fixed bug GH-22649 (ZipArchive::setCommentName() and setCommentIndex()
+ could crash after overwriting an entry and resetting its inherited
+ unchanged comment). (Weilin Du)
+ . Fixed bug GH-21705 (ZipArchive::getFromIndex() ignores
+ ZipArchive::FL_UNCHANGED for deleted entries). (Weilin Du)
+ . Fixed bug GH-22176 (memory leak with ZipArchive::registerCancelBack()
+ is used with reference returning function during shutdown).
+ (David Carlier)
+ . ZipArchive::addGlob() and ZipArchive::addPattern() now raise a TypeError
+ for invalid "remove_all_path", "comp_method", "comp_flags", and
+ "enc_method" options instead of emitting a warning. (David Carlier)
+
+02 Jul 2026, PHP 8.6.0alpha1
- Core:
. Added first-class callable cache to share instances for the duration of the
@@ -16,39 +221,118 @@ PHP NEWS
. Fixed bug GH-20174 (Assertion failure in
ReflectionProperty::skipLazyInitialization after failed LazyProxy
initialization). (Arnaud)
+ . Enabled the TAILCALL VM on Windows when compiling with Clang >= 19 x86_64.
+ (henderkes)
+ . Deprecate specifying a nullable return type for __debugInfo(). (timwolla)
+ . Fixed bug GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF).
+ (David Carlier)
+ . Fixed bug GH-22046 (The unserialize function can lead to segfault when
+ non-Serializable internal classes are serialized back with the C format).
+ (kocsismate)
+ . Fixed bug GH-22292 (AST pretty printing does not correctly handle invalid
+ variable names). (timwolla)
+ . Fixed bug GH-22291 (AST pretty printing does not correctly handle braces in
+ string interpolation). (timwolla)
+ . Fixed bug GH-22373 (AST pretty-printing drops meaningful parentheses
+ surrounding property access). (timwolla)
+ . Fixed GH-22422 (zend_arena layout mismatch leaked memory in separately
+ built extensions under AddressSanitizer). (iliaal)
+ . TSRM: use local-exec TLS in PIE executables. (henderkes)
+ . perf: make all static extensions use TSRMG_STATIC. (henderkes)
+ . Fixed bug GH-22257 (type confusion in Exception::getTraceAsString()).
+ (David Carlier)
+ . TSRM: make CG, EG, SCNG and AG compile-time offsets. (henderkes)
+ . Deprecate returning values from __construct() and __destruct(). (timwolla)
+
+- BCMath:
+ . Added NUL-byte validation to BCMath functions. (jorgsowa)
+
+- BZ2:
+ . Reject oversized input in bzdecompress(). (arshidkv12)
+
+- Curl:
+ . Add support for CURLINFO_SIZE_DELIVERED (libcurl >= 8.20.0). (Ayesh)
- Date:
. Update timelib to 2022.16. (Derick)
- DOM:
- . Removed LIBXML_XINCLUDE from valid options for XMLDocument,
- as it was a no-op. (ndossche)
+ . Removed LIBXML_XINCLUDE from valid options for XMLDocument, as it was a
+ no-op. (ndossche)
+ . Readonly DOM properties are now declared with asymmetric visibility
+ (public private(set)). ReflectionProperty::isWritable() reports them
+ correctly, and external writes raise "Cannot modify private(set)
+ property" instead of the previous readonly modification error.
+ (David Carlier)
+ . Fixed Dom\Notation nodes missing tree connection, so that ownerDocument,
+ parentNode, isConnected and baseURI now return correct values, and
+ textContent returns NULL per the DOM specification. (jordikroon)
+
+- EXIF:
+ . Added support for reading EXIF metadata from WebP images (GH-19904).
+ (iliaal)
- Fileinfo:
. Fixed bug GH-20679 (finfo_file() doesn't work on remote resources).
(ndossche)
+ . Fixed bug #66095 (Hide libmagic dynamic symbols). (orlitzky)
+
+- GD:
+ . imagesetstyle()/imagefilter()/imagecrop() check array argument entries
+ types. (David Carlier)
+
+- GMP:
+ . gmp_fact() reject values larger than unsigned long. (David Carlier)
+ . gmp_pow/binomial/root/rootrem and shift/pow operators reject values larger
+ than unsigned long. (David Carlier)
+ . GMP exponentiation and shift operators now emit a deprecation warning
+ when converting a float right operand to int loses precision. (Weilin Du)
- Hash:
. Upgrade xxHash to 0.8.2. (timwolla)
- Intl:
+ . Fixed malformed ResourceBundle::get() error message when fallback is
+ disabled. (Weilin Du)
+ . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(),
+ with the alias of locale_get_display_keyword() and
+ locale_get_display_keyword_value() respectively. (Weilin Du)
+ . Fix incorrect argument positions for invalid start/end arguments in
+ transliterator_transliterate(). (Weilin Du)
+ . Fixed IntlTimeZone::getDisplayName() to synchronize object error state
+ for invalid display types. (Weilin Du)
+ . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
+ fallback locale when a language tag cannot be canonicalized. (Weilin Du)
. Added IntlNumberRangeFormatter class to format an interval of two numbers
with a given skeleton, locale, collapse type and identity fallback.
(BogdanUngureanu)
. Fixed bug GH-20426 (Spoofchecker::setRestrictionLevel() error message
suggests missing constants). (DanielEScherzer)
. Added grapheme_strrev (Yuya Hamada)
+ . Passing a non-stringable object as a time zone to Intl time zone
+ argument handling now raises TypeError instead of Error. (Weilin Du)
+ . IntlBreakIterator::getLocale() now raises ValueError for invalid locale
+ types. (Weilin Du)
+ . Fixed MessageFormatter::parse() and parseMessage() returning PHP_INT_MIN
+ as float rather than int on 64-bit platforms. (Weilin Du)
+ . Fixed UConverter::transcode() silently truncating from_subst and to_subst
+ option lengths greater than 127 bytes. (Weilin Du)
+ . Fixed IntlIterator::current() to return NULL instead of an undefined value
+ when the iterator is not positioned on a valid element. (Weilin Du)
+
+- IO:
+ . Added new polling API. (Jakub Zelenka)
- JSON:
. Enriched JSON last error / exception message with error location.
(Juan Morales)
- Fibers:
- . Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI
- small value). (David Carlier)
+ . Fixed bug GH-20483 (ASAN stack overflow with fiber.stack_size INI small
+ value). (David Carlier)
- Mail:
- . Fixed bug GH-20862 (null pointer dereference in
+ . Fixed bug GH-20862 (null pointer dereference in
php_mail_detect_multiple_crlf via error_log (jordikroon)
- Mbstring:
@@ -59,32 +343,63 @@ PHP NEWS
recursive array references). (alexandre-daubois)
. Fixed bug GH-21223; mb_guess_encoding no longer crashes when passed huge
list of candidate encodings (with 200,000+ entries). (Jordi Kroon)
+ . mbregex has been deprecated. (youkidearitai)
+
+- Mysqli:
+ . Added mysqli_quote_string() and mysqli::quote_string(). (Kamil Tekiela)
- Opcache:
. Fixed bug GH-20051 (apache2 shutdowns when restart is requested during
preloading). (Arnaud, welcomycozyhom)
- OpenSSL:
+ . Added AES-SIV support. (jordikroon)
. Implemented GH-20310 (No critical extension indication in
openssl_x509_parse() output). (StephenWall)
+ . Added TLS session resumption support for streams with new context options
+ and Openssl\Session class. (Jakub Zelenka)
+ . Added TLS external PSK support for streams with new context options and
+ Openssl\Psk class. (Jakub Zelenka)
+ . Added stream crypto status for exposing OpenSSL WANT_READ / WANT_WRITE.
+ (Jakub Zelenka)
+
+- PCNTL:
+ . pcntl_exec() now throws a ValueError if the $args array is not a list
+ array. (Weilin Du)
+
+- PDO_DBLIB:
+ . Added dblib_handle_check_liveness handler. (freddy77)
- PDO_PGSQL:
. Clear session-local state disconnect-equivalent processing.
(KentarouTakeda)
- PGSQL:
- . Enabled 64 bits support for pg_lo_truncate()/pg_lo_tell()
- if the server supports it. (KentarouTakeda)
+ . Enabled 64 bits support for pg_lo_truncate()/pg_lo_tell() if the server
+ supports it. (KentarouTakeda)
+ . pg_fetch_object() now surfaces non-instantiable class errors before
+ fetching, resolves the constructor via the get_constructor handler, and
+ reports the empty-constructor ValueError on the $constructor_args argument.
+ (David Carlier)
- Phar:
. Support reference values in Phar::mungServer(). (ndossche)
. Invalid values now throw in Phar::mungServer() instead of being silently
ignored. (ndossche)
+ . Fixed a bypass of the magic ".phar" directory protection in
+ Phar::addEmptyDir() for paths starting with "/.phar". (Weilin Du)
+ . Fixed an integer underflow when parsing ZIP extra fields. (Weilin Du)
+ . Phar::addEmptyDir() now allows non-magic directory names that merely
+ share the ".phar" prefix. (Weilin Du)
. Support overridden methods in SplFileInfo for getMTime() and getPathname()
when building a phar. (ndossche)
. Mark Phar::buildFromIterator() base directory argument as a path.
(ndossche)
+- phpdbg:
+ . Fixed GH-22480 (Use-after-free when re-watching an already-watched
+ variable). (iliaal)
+
- Posix:
. Added validity check to the flags argument for posix_access(). (arshidkv12)
@@ -92,18 +407,37 @@ PHP NEWS
. Fixed bug GH-20217 (ReflectionClass::isIterable() incorrectly returns true
for classes with property hooks). (alexandre-daubois)
. Added ReflectionConstant::inNamespace(). (Khaled Alam)
- . Added ReflectionProperty::isReadable() and ReflectionProperty::isWritable().
- (ilutov)
+ . Added ReflectionProperty::isReadable() and ::isWritable(). (ilutov)
. Fixed bug GH-21362 (ReflectionMethod::invoke/invokeArgs() did not verify
Closure instance identity for Closure::__invoke()). (Ilia Alshanetsky)
+ . Added ReflectionParameter::getDocComment(). (chschneider)
- Session:
. Fixed bug 71162 (updateTimestamp never called when session data is empty).
(Girgias)
+ . Null bytes in session.cookie_path, session.cookie_domain, and
+ session.cache_limiter are now rejected with a warning. (jorgsowa)
+ . session.cookie_samesite now rejects invalid values with a warning; only
+ "Strict", "Lax", "None", or "" are accepted. (jorgsowa)
+ . session.cookie_lifetime now rejects non-integer and out-of-range values
+ with a warning. (jorgsowa)
+ . Session file GC now recursively cleans nested subdirectories when
+ session.save_path uses the dirdepth prefix. (jorgsowa)
+ . Changed defaults of session.use_strict_mode (now 1), session.cookie_httponly
+ (now 1) and session.cookie_samesite (now "Lax"). (jorgsowa)
+
+- Shmop:
+ . Fixed bug GH-9945 (shmop_open() silently truncates keys outside the key_t
+ range). (Weilin Du)
- Soap:
. Soap::__setCookie() when cookie name is a digit is now not stored and
represented as a string anymore but a int. (David Carlier)
+ . Fixed bug GH-21421 (SoapClient typemap property breaks engine assumptions).
+ (ndossche)
+ . WSDL/XML Schema parsing now rejects out-of-range integer values for
+ occurrence constraints and integer restriction facets. Negative minOccurs
+ and maxOccurs values are rejected as well. (Weilin Du)
- Sockets:
. Added the TCP_USER_TIMEOUT constant for Linux to set the maximum time in
@@ -112,13 +446,27 @@ PHP NEWS
AF_INET* family only. (David Carlier)
. Fixed GH-20532 (socket_addrinfo_lookup gives the error code with a new
optional parameter). (David Carlier)
+ . Added AF_PACKET support completion for socket_sendto()/socket_recvfrom().
+ (David Carlier)
- Sodium:
. Added support for libsodium 1.0.21 IPcrypt and XOF APIs. (jedisct1)
+ . pwhash argument-validation errors now throw ValueError instead of
+ SodiumException. (iliaal)
- SPL:
. DirectoryIterator key can now work better with filesystem supporting larger
directory indexing. (David Carlier)
+ . Fixed bug GH-21831 (SplObjectStorage::removeAllExcept() use-after-free with
+ re-entrant getHash()). (Pratik Bhujel)
+ . Fix bugs GH-8561, GH-8562, GH-8563, and GH-8564 (Fixing various
+ SplFileObject iterator desync bugs). (iliaal)
+ . Fix bug GH-22062 (SplDoublyLinkedList iterator UAF via destructor releasing
+ next node). (David Carlier)
+
+- Sysvshm:
+ . Fixed shm_attach() to throw ValueError for keys outside the key_t range.
+ (Weilin Du)
- Sqlite3:
. Fix NUL byte truncation in sqlite3 TEXT column handling. (ndossche)
@@ -134,19 +482,63 @@ PHP NEWS
null destination). (David Carlier)
. Fixed bug GH-13204 (glob() fails if square bracket is in current directory).
(ndossche)
+ . Add array size maximum to array_diff(). (ndossche)
+ . Add enum SortDirection. (timwolla)
+ . pathinfo() raises a ValueError with an invalid $flags argument.
+ (David Carlier)
+ . Passing an invalid flag value to the second argument of scandir() will now
+ throw a ValueError. (alexandre-daubois)
+ . array_change_key_case() now raises a ValueError when an invalid $case
+ argument value is passed. (Girgias)
+ . linkinfo() now raises a ValueError when the argument is an empty string.
+ (Weilin Du)
+ . getenv() and putenv() now raises a ValueError when the first argument
+ contains NUL bytes. (Weilin Du)
+ . dl() now raises a ValueError when the $extension_filename argument contains
+ NUL bytes. (Weilin Du)
+ . openlog() now raises a ValueError when the $prefix argument contains NUL
+ bytes. (Weilin Du)
+ . parse_str() now raises a ValueError when the $string argument contains NUL
+ bytes. (Weilin Du)
+ . proc_open() now raises a ValueError when the $cwd argument contains NUL
+ bytes. (Weilin Du)
+ . ini_get_all() now includes the built-in default value in the details.
+ (sebastian)
+ . Fixed bug GH-22171 (Invalid auth header generation in http(s) stream
+ wrapper). (David Carlier)
+ . Fixed bug GH-17384 (number_format() may exhaust memory with decimals
+ outside the range from -2147483648 to 2147483647). (Weilin Du)
- Streams:
+ . Added new stream errors API including new StreamException, StreamError
+ classes, StreamErrorStore, StreamErrorMode, StreamErrorCode enums,
+ stream_last_errors() and stream_clear_errors() functions, error_mode,
+ error_store and error_handler stream context options and extending some
+ stream functions with context param. (Jakub Zelenka)
. Added so_keepalive, tcp_keepidle, tcp_keepintvl and tcp_keepcnt stream
- socket context options.
+ socket context options. (Jakub Zelenka)
. Added so_reuseaddr streams context socket option that allows disabling
- address resuse.
+ address reuse. (Jakub Zelenka)
. Fixed bug GH-20370 (User stream filters could violate typed property
constraints). (alexandre-daubois)
. Allowed filtered streams to be casted as fd for select. (Jakub Zelenka)
. Fixed bug GH-21221 (Prevent closing of innerstream of php://temp stream).
(ilutov)
+ . Improved stream_socket_server() bind failure error reporting. (ilutov)
+ . Fixed bug #49874 (ftell() and fseek() inconsistency when using stream
+ filters). (Jakub Zelenka)
+
+- URI:
+ . Added Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme().
+ (kocsismate)
+ . Added Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType().
+ (kocsismate)
+ . Added Uri\Rfc3986\UriBuilder. (kocsismate)
- Zip:
+ . Fixed bug GH-21682 (ZipArchive instances should not be serializable).
+ serialize()/unserialize() now throw unless a subclass overrides
+ __serialize()/__unserialize(). (iliaal)
. Fixed ZipArchive callback being called after executor has shut down.
(ilutov)
. Support minimum version for libzip dependency updated to 1.0.0.
@@ -154,4 +546,11 @@ PHP NEWS
. Added ZipArchive::openString() method.
(Tim Starling, Soner Sayakci, Ghaith Olabi)
+- Zlib:
+ . deflate_init() now raises a TypeError when the value for option
+ "level", "memory", "window", or "strategy" is not of type int.
+ (Weilin Du)
+ . inflate_init() now raises a TypeError when the value for option
+ "window" is not of type int. (Weilin Du)
+
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
diff --git a/README.md b/README.md
index 9a058bb942e2..d83203d74c0b 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,12 @@
PHP is a popular general-purpose scripting language that is especially suited to
web development. Fast, flexible and pragmatic, PHP powers everything from your
-blog to the most popular websites in the world. PHP is distributed under the
-[PHP License v3.01](LICENSE).
+blog to the most popular websites in the world.
-[](https://github.com/php/php-src/actions/workflows/push.yml)
+PHP is distributed under the [Modified BSD License](LICENSE)
+(SPDX-License-Identifier: `BSD-3-Clause`).
+
+[](https://github.com/php/php-src/actions/workflows/test.yml)
[](https://issues.oss-fuzz.com/issues?q=project:php)
## Documentation
@@ -128,8 +130,10 @@ permissions.
## PHP extensions
Extensions provide additional functionality on top of PHP. PHP consists of many
-essential bundled extensions. Additional extensions can be found in the PHP
-Extension Community Library - [PECL](https://pecl.php.net).
+essential bundled extensions. Additional extensions can be found on the
+[PIE Extensions](https://packagist.org/extensions) list, and installed with
+[🥧 PIE, the PHP Installer for Extensions](https://github.com/php/pie) or the
+deprecated PHP Extension Community Library - [PECL](https://pecl.php.net).
## Contributing
diff --git a/SECURITY.md b/SECURITY.md
index deb5a7a950a4..24801b3b4e43 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -11,6 +11,31 @@ Vulnerability reports remain private until published. When published, you will
be credited as a contributor, and your contribution will reflect the MITRE
Credit System.
+# Classification
+
+Issues commonly reported that are _not_ considered security issues include (but
+are not limited to):
+
+- Invocation of specially crafted, malicious code intended to cause memory
+ violations. This commonly includes malicious error handlers, destructors or
+ `__toString()` functions. PHP does not offer sandboxing, and the execution of
+ untrusted code is always considered unsafe. Such issues are bugs, but not
+ security issues. They may still be reported, though please avoid reporting
+ the known issues.
+
+- Passing malicious arguments to functions clearly not intended to receive
+ unsanitized values, e.g. `mysqli_query()`. `escapeshellarg()` on the other
+ hand should clearly be hardened against unsafe inputs.
+
+- The use of legacy APIs or settings known to be insecure, particularly those
+ documented as such, or those with a secure alternative.
+
+- The use of FFI.
+
+- `open_basedir` or `disable_functions` bypasses.
+
+- Malicious `unserialize()` inputs.
+
# Vulnerability Policy
Our full policy is described at
diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c
index e99993204b6f..a5032e456aae 100644
--- a/TSRM/TSRM.c
+++ b/TSRM/TSRM.c
@@ -36,12 +36,13 @@ struct _tsrm_tls_entry {
tsrm_tls_entry *next;
};
-
typedef struct {
size_t size;
ts_allocate_ctor ctor;
ts_allocate_dtor dtor;
- size_t fast_offset;
+ ptrdiff_t fast_offset;
+ /* When set, storage comes from __thread memory instead of being allocated by TSRM. */
+ void *(*tls_addr)(void);
int done;
} tsrm_resource_type;
@@ -58,6 +59,7 @@ static int resource_types_table_size;
/* Reserved space for fast globals access */
static size_t tsrm_reserved_pos = 0;
static size_t tsrm_reserved_size = 0;
+static size_t tsrm_reserved_front = 0;
static MUTEX_T tsmm_mutex; /* thread-safe memory manager mutex */
static MUTEX_T tsrm_env_mutex; /* tsrm environ mutex */
@@ -155,6 +157,7 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb
tsrm_reserved_pos = 0;
tsrm_reserved_size = 0;
+ tsrm_reserved_front = 0;
tsrm_env_mutex = tsrm_mutex_alloc();
@@ -163,14 +166,20 @@ TSRM_API bool tsrm_startup(int expected_threads, int expected_resources, int deb
static void ts_free_resources(tsrm_tls_entry *thread_resources)
{
+ bool own_thread = thread_resources->thread_id == tsrm_thread_id();
+
/* Need to destroy in reverse order to respect dependencies. */
for (int i = thread_resources->count - 1; i >= 0; i--) {
if (!resource_types_table[i].done) {
+ /* A __thread block of a foreign thread is inaccessible. */
+ if (resource_types_table[i].tls_addr && !own_thread) {
+ continue;
+ }
if (resource_types_table[i].dtor) {
resource_types_table[i].dtor(thread_resources->storage[i]);
}
- if (!resource_types_table[i].fast_offset) {
+ if (!resource_types_table[i].fast_offset && !resource_types_table[i].tls_addr) {
free(thread_resources->storage[i]);
}
}
@@ -179,7 +188,9 @@ static void ts_free_resources(tsrm_tls_entry *thread_resources)
free(thread_resources->storage);
}
-/* Shutdown TSRM (call once for the entire process) */
+/* Shutdown TSRM (call once for the entire process). Tears down every thread left
+ * in the table. For resources allocated with ts_allocate_tls_id(), only the dtor
+ * of the calling thread is invoked. */
TSRM_API void tsrm_shutdown(void)
{/*{{{*/
if (is_thread_shutdown) {
@@ -205,7 +216,7 @@ TSRM_API void tsrm_shutdown(void)
} else {
free(p->storage);
}
- free(p);
+ free((char *) p - tsrm_reserved_front);
p = next_p;
}
}
@@ -232,6 +243,7 @@ TSRM_API void tsrm_shutdown(void)
tsrm_reserved_pos = 0;
tsrm_reserved_size = 0;
+ tsrm_reserved_front = 0;
}/*}}}*/
/* {{{ */
@@ -256,7 +268,10 @@ static void tsrm_update_active_threads(void)
p->storage = (void *) realloc(p->storage, sizeof(void *)*id_count);
for (j=p->count; jthread_id == tsrm_thread_id());
+ p->storage[j] = resource_types_table[j].tls_addr();
+ } else if (resource_types_table[j].fast_offset) {
p->storage[j] = (void *) (((char*)p) + resource_types_table[j].fast_offset);
} else {
p->storage[j] = (void *) malloc(resource_types_table[j].size);
@@ -301,6 +316,7 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor;
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor;
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = 0;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = NULL;
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0;
tsrm_update_active_threads();
@@ -319,17 +335,20 @@ TSRM_API void tsrm_reserve(size_t size)
}/*}}}*/
+/* Carve a fixed-offset front region out of the reserved space. It is placed
+ * before the TLS entry, so the hot globals get compile-time-constant negative
+ * offsets from the cache pointer. */
+TSRM_API void tsrm_reserve_fast_front(size_t size)
+{
+ tsrm_reserved_front = TSRM_ALIGNED_SIZE(size);
+ tsrm_reserved_size -= tsrm_reserved_front;
+}
+
+
/* allocates a new fast thread-safe-resource id */
TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
{/*{{{*/
- TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new fast resource id, %d bytes", size));
-
tsrm_mutex_lock(tsmm_mutex);
-
- /* obtain a resource id */
- *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);
- TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id));
-
size = TSRM_ALIGNED_SIZE(size);
if (tsrm_reserved_size - tsrm_reserved_pos < size) {
TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate space for fast resource"));
@@ -338,9 +357,26 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz
tsrm_mutex_unlock(tsmm_mutex);
return 0;
}
-
- *offset = TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_pos;
+ ptrdiff_t fixed_offset = TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_pos;
tsrm_reserved_pos += size;
+ tsrm_mutex_unlock(tsmm_mutex);
+
+ return ts_allocate_fast_id_at(rsrc_id, offset, fixed_offset, size, ctor, dtor);
+}/*}}}*/
+
+
+TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
+{
+ TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new fast resource id, %d bytes", size));
+
+ tsrm_mutex_lock(tsmm_mutex);
+
+ /* obtain a resource id */
+ *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);
+ TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtained resource id %d", *rsrc_id));
+
+ size = TSRM_ALIGNED_SIZE(size);
+ *offset = (size_t) fixed_offset;
/* store the new resource type in the resource sizes table */
if (resource_types_table_size < id_count) {
@@ -359,6 +395,7 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor;
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor;
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = *offset;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = NULL;
resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0;
tsrm_update_active_threads();
@@ -366,7 +403,42 @@ TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, siz
TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new resource id %d", *rsrc_id));
return *rsrc_id;
-}/*}}}*/
+}
+
+/* allocates a resource id whose per-thread storage is a native __thread block */
+TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor)
+{
+ TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Obtaining a new TLS resource id, %d bytes", size));
+
+ tsrm_mutex_lock(tsmm_mutex);
+
+ *rsrc_id = TSRM_SHUFFLE_RSRC_ID(id_count++);
+
+ if (resource_types_table_size < id_count) {
+ tsrm_resource_type *_tmp;
+ _tmp = (tsrm_resource_type *) realloc(resource_types_table, sizeof(tsrm_resource_type)*id_count);
+ if (!_tmp) {
+ TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Unable to allocate storage for resource"));
+ *rsrc_id = 0;
+ tsrm_mutex_unlock(tsmm_mutex);
+ return 0;
+ }
+ resource_types_table = _tmp;
+ resource_types_table_size = id_count;
+ }
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].size = size;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].ctor = ctor;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].dtor = dtor;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].fast_offset = 0;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].tls_addr = tls_addr;
+ resource_types_table[TSRM_UNSHUFFLE_RSRC_ID(*rsrc_id)].done = 0;
+
+ tsrm_update_active_threads();
+ tsrm_mutex_unlock(tsmm_mutex);
+
+ TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Successfully allocated new TLS resource id %d", *rsrc_id));
+ return *rsrc_id;
+}
static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource)
{
@@ -378,7 +450,10 @@ static void set_thread_local_storage_resource_to(tsrm_tls_entry *thread_resource
static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_T thread_id)
{/*{{{*/
TSRM_ERROR((TSRM_ERROR_LEVEL_CORE, "Creating data structures for thread %x", thread_id));
- (*thread_resources_ptr) = (tsrm_tls_entry *) malloc(TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_size);
+ /* The entry follows the fixed-offset front region.
+ * hot globals live at negative offsets from the TLS cache pointer. */
+ char *block = (char *) malloc(tsrm_reserved_front + TSRM_ALIGNED_SIZE(sizeof(tsrm_tls_entry)) + tsrm_reserved_size);
+ (*thread_resources_ptr) = (tsrm_tls_entry *) (block + tsrm_reserved_front);
(*thread_resources_ptr)->storage = NULL;
if (id_count > 0) {
(*thread_resources_ptr)->storage = (void **) malloc(sizeof(void *)*id_count);
@@ -397,7 +472,9 @@ static void allocate_new_resource(tsrm_tls_entry **thread_resources_ptr, THREAD_
if (resource_types_table[i].done) {
(*thread_resources_ptr)->storage[i] = NULL;
} else {
- if (resource_types_table[i].fast_offset) {
+ if (resource_types_table[i].tls_addr) {
+ (*thread_resources_ptr)->storage[i] = resource_types_table[i].tls_addr();
+ } else if (resource_types_table[i].fast_offset) {
(*thread_resources_ptr)->storage[i] = (void *) (((char*)(*thread_resources_ptr)) + resource_types_table[i].fast_offset);
} else {
(*thread_resources_ptr)->storage[i] = (void *) malloc(resource_types_table[i].size);
@@ -485,9 +562,11 @@ TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id)
/* In case that extensions don't use the pointer passed from the dtor, but incorrectly
* use the global pointer, we need to setup the global pointer temporarily here. */
set_thread_local_storage_resource_to(thread_resources);
- /* Free up the old resource from the old thread instance */
+ /* Dead thread with a recycled id: its __thread blocks are gone, and this
+ * thread's blocks were never constructed, so keep tls dtors from running. */
+ thread_resources->thread_id = 0;
ts_free_resources(thread_resources);
- free(thread_resources);
+ free((char *) thread_resources - tsrm_reserved_front);
/* Allocate a new resource at the same point in the linked list, and relink the next pointer */
allocate_new_resource(last_thread_resources, thread_id);
thread_resources = *last_thread_resources;
@@ -529,7 +608,7 @@ void ts_free_thread(void)
tsrm_tls_table[hash_value] = thread_resources->next;
}
tsrm_tls_set(0);
- free(thread_resources);
+ free((char *) thread_resources - tsrm_reserved_front);
break;
}
if (thread_resources->next) {
@@ -559,7 +638,7 @@ void ts_free_id(ts_rsrc_id id)
if (resource_types_table[rsrc_id].dtor) {
resource_types_table[rsrc_id].dtor(p->storage[rsrc_id]);
}
- if (!resource_types_table[rsrc_id].fast_offset) {
+ if (!resource_types_table[rsrc_id].fast_offset && !resource_types_table[rsrc_id].tls_addr) {
free(p->storage[rsrc_id]);
}
}
diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h
index 80d6cbad0443..752ea1803f9c 100644
--- a/TSRM/TSRM.h
+++ b/TSRM/TSRM.h
@@ -20,6 +20,7 @@
# include
#endif
+#include
#include
#include
@@ -94,6 +95,15 @@ TSRM_API ts_rsrc_id ts_allocate_id(ts_rsrc_id *rsrc_id, size_t size, ts_allocate
TSRM_API void tsrm_reserve(size_t size);
TSRM_API ts_rsrc_id ts_allocate_fast_id(ts_rsrc_id *rsrc_id, size_t *offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor);
+/* Fast resources at caller-chosen, compile-time-constant offsets. The fixed
+ * front region must be reserved after tsrm_reserve() and before any fast id. */
+TSRM_API void tsrm_reserve_fast_front(size_t size);
+TSRM_API ts_rsrc_id ts_allocate_fast_id_at(ts_rsrc_id *rsrc_id, size_t *offset, ptrdiff_t fixed_offset, size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor);
+
+/* Resource whose per-thread storage is a native __thread block.
+ * Must be called at startup before any other thread exists. */
+TSRM_API ts_rsrc_id ts_allocate_tls_id(ts_rsrc_id *rsrc_id, void *(*tls_addr)(void), size_t size, ts_allocate_ctor ctor, ts_allocate_dtor dtor);
+
/* fetches the requested resource for the current thread */
TSRM_API void *ts_resource_ex(ts_rsrc_id id, THREAD_T *th_id);
#define ts_resource(id) ts_resource_ex(id, NULL)
@@ -155,7 +165,7 @@ TSRM_API bool tsrm_is_managed_thread(void);
#if !__has_attribute(tls_model) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__MUSL__) || defined(__HAIKU__)
# define TSRM_TLS_MODEL_ATTR
# define TSRM_TLS_MODEL_DEFAULT
-#elif __PIC__
+#elif defined(__PIC__) && !defined(__PIE__)
# define TSRM_TLS_MODEL_ATTR __attribute__((tls_model("initial-exec")))
# define TSRM_TLS_MODEL_INITIAL_EXEC
#else
@@ -175,9 +185,14 @@ TSRM_API bool tsrm_is_managed_thread(void);
#define TSRMG_BULK_STATIC(id, type) ((type) (*((void ***) TSRMLS_CACHE))[TSRM_UNSHUFFLE_RSRC_ID(id)])
#define TSRMG_FAST_STATIC(offset, type, element) (TSRMG_FAST_BULK_STATIC(offset, type)->element)
#define TSRMG_FAST_BULK_STATIC(offset, type) ((type) (((char*) TSRMLS_CACHE)+(offset)))
+#ifdef __cplusplus
+#define TSRMLS_MAIN_CACHE_EXTERN() extern "C" { extern TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR; }
+#define TSRMLS_CACHE_EXTERN() extern "C" { extern TSRM_TLS void *TSRMLS_CACHE; }
+#else
#define TSRMLS_MAIN_CACHE_EXTERN() extern TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR;
-#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;
#define TSRMLS_CACHE_EXTERN() extern TSRM_TLS void *TSRMLS_CACHE;
+#endif
+#define TSRMLS_MAIN_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE TSRM_TLS_MODEL_ATTR = NULL;
#define TSRMLS_CACHE_DEFINE() TSRM_TLS void *TSRMLS_CACHE = NULL;
#define TSRMLS_CACHE_UPDATE() TSRMLS_CACHE = tsrm_get_ls_cache()
#define TSRMLS_CACHE _tsrm_ls_cache
diff --git a/TSRM/tsrm_win32.c b/TSRM/tsrm_win32.c
index 4c8fc9d19aa9..90317ab64b19 100644
--- a/TSRM/tsrm_win32.c
+++ b/TSRM/tsrm_win32.c
@@ -1,14 +1,12 @@
/*
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Daniel Beulshausen |
+----------------------------------------------------------------------+
diff --git a/TSRM/tsrm_win32.h b/TSRM/tsrm_win32.h
index c5bdc492be1b..9c16bd7c654b 100644
--- a/TSRM/tsrm_win32.h
+++ b/TSRM/tsrm_win32.h
@@ -1,14 +1,12 @@
/*
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Daniel Beulshausen |
+----------------------------------------------------------------------+
diff --git a/UPGRADING b/UPGRADING
index 113258ebbd3e..97670e09246d 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -19,17 +19,104 @@ PHP 8.6 UPGRADE NOTES
1. Backward Incompatible Changes
========================================
+- Core:
+ . ??/empty() on a magic property no longer call __get() when __isset()
+ has materialised the property by writing into the property table.
+ The freshly-written value is returned directly. isset() is unaffected.
+
+- COM
+ . It is no longer possible to clone variant objects, this is because
+ the cloning behaviour was ill defined.
+
+- DOM:
+ . Properties previously documented as @readonly (e.g. DOMNode::$nodeType,
+ DOMDocument::$xmlEncoding, DOMEntity::$actualEncoding, ::$encoding,
+ ::$version) are now declared with asymmetric visibility
+ (public private(set)). Attempts to write to them from outside the
+ class now raise "Cannot modify private(set) property ::$
+ from global scope" instead of the prior readonly modification error.
+ ReflectionProperty::isWritable() also reports these properties
+ accurately.
+ . Array access on Dom\DtdNamedNodeMap objects now returns null for negative
+ integer indexes instead of returning the first node.
+ . Array access on Dom\DtdNamedNodeMap objects now raises a ValueError when
+ the integer index is greater than INT_MAX instead of overflowing to a
+ smaller index.
+
+- GD:
+ . imagesetstyle(), imagefilter() and imagecrop() filter their array arguments
+ types / values and raise a TypeError / ValueError accordingly.
+
+- GMP:
+ . GMP power and shift operators now throw ValueError when GMP right operands
+ are outside the unsigned long range, instead of silently truncating them.
+ . GMP integer string parsing now throw ValueError strings containing NUL
+ bytes, instead of silently truncating them.
+
+- Intl:
+ . Passing a non-stringable object as a time zone to Intl APIs that accept
+ time zone objects or strings now raises a TypeError instead of an Error.
+ . IntlIterator::current() now returns null when called before the iterator is
+ positioned, or after the iterator becomes invalid, instead of exposing an
+ undefined value.
+ . IntlBreakIterator::getLocale() now raises a ValueError when the type is
+ neither Locale::ACTUAL_LOCALE nor Locale::VALID_LOCALE instead of
+ returning false.
+ . MessageFormatter::parse() and parseMessage() now return PHP_INT_MIN as
+ int, rather than float, on 64-bit platforms when parsing integer values.
+ . The $type parameter of IntlBreakIterator::getPartsIterator() has been
+ changed from string to int to match the underlying implementation.
+ . UConverter::transcode() now rejects from_subst and to_subst option values
+ longer than 127 bytes instead of silently truncating the length before
+ passing it to ICU.
+ . ResourceBundle::get() and resourcebundle_get() now report fallback-disabled
+ resource lookups with "without fallback to " instead of the
+ malformed "without fallback from to ".
+ . IntlDateFormatter::parse()/datefmt_parse() and
+ IntlDateFormatter::localtime()/datefmt_localtime() now raise a TypeError
+ when the offset argument is not of type int instead of silently converting
+ the value.
+
+- PCNTL:
+ . pcntl_alarm() now raises a ValueError if the seconds argument is
+ lower than zero or greater than platform's UINT_MAX.
+ . pcntl_exec() now raises a ValueError if the $args argument is not a list
+ array.
+
- PCRE:
. preg_grep() now returns false instead of a partial array when a PCRE
execution error occurs (e.g. malformed UTF-8 input with the /u modifier).
This is consistent with other preg_* functions.
- Phar:
- . Invalid values now throw in Phar::mungServer() instead of being silently
- ignored.
+ . Phar::mungServer() now raises a ValueError when an invalid argument value
+ is passed instead of being silently ignored.
+ . Phar::addEmptyDir() now rejects "/.phar" paths in addition to ".phar"
+ paths, and raises the same BadMethodCallException for attempts to create
+ the reserved magic ".phar" directory through that form.
+ . Phar::addEmptyDir() now treats non-magic names that merely share the
+ ".phar" prefix as ordinary directories.
+
+- PGSQL:
+ . pg_fetch_object() now reports the ValueError for a non-empty
+ $constructor_args on a class without a constructor on the
+ $constructor_args argument instead of $class. Errors raised when
+ the requested class is not instantiable (abstract, interface, enum)
+ now surface before the row is fetched.
+
+- Posix:
+ . posix_access() now raises a ValueError when an invalid $flags
+ argument value is passed.
+ . posix_mkfifo() now raises a ValueError when an invalid $permissions
+ argument value is passed.
- Session:
- . A ValueError is not thrown if $name is a string containing null bytes in
+ . Setting session.cookie_path, session.cookie_domain, or session.cache_limiter
+ to a value containing NUL bytes now emits a warning and leaves the setting
+ unchanged. Previously, NUL bytes were silently accepted: for cookie_path
+ and cookie_domain this caused the SAPI to drop the Set-Cookie header; for
+ cache_limiter the value was silently truncated at the NUL byte.
+ . A ValueError is not thrown if $name is a string containing NUL bytes in
session_module_name().
. session_encode() now returns an empty string instead of false for empty
sessions. It only returns false now when the session data could not be
@@ -43,12 +130,110 @@ PHP 8.6 UPGRADE NOTES
comparison. Custom session handlers that rely on write() being called
with empty data (e.g. to destroy the session) should implement the same
logic in their updateTimestamp() method.
+ . The defaults of three session INI settings have changed to provide secure
+ behavior out of the box:
+ - session.use_strict_mode is now 1 (was 0). Strict mode rejects
+ uninitialized session IDs, mitigating session fixation. Custom session
+ handlers that previously relied on accepting externally supplied IDs
+ without a corresponding storage entry must either implement
+ validateId() / create_sid() or explicitly set this to 0.
+ - session.cookie_httponly is now 1 (was 0). Session cookies are no
+ longer accessible to JavaScript via document.cookie. Applications
+ that read the session cookie from JavaScript must explicitly set
+ this to 0.
+ - session.cookie_samesite is now "Lax" (was unset). Session cookies
+ are no longer sent on cross-site requests other than top-level
+ navigations using safe HTTP methods. Applications that depend on
+ session cookies being sent on cross-site POST submissions must
+ explicitly set this to "None" (and also set session.cookie_secure
+ to 1).
+ RFC: https://wiki.php.net/rfc/session_security_defaults
+
+- Shmop:
+ . shmop_open() now raises a ValueError when the $key argument is outside the
+ platform's key_t range instead of passing a truncated key to the operating
+ system.
+
+- SOAP:
+ . WSDL/XML Schema parsing now rejects out-of-range integer values for
+ occurrence constraints and integer restriction facets. Negative minOccurs
+ and maxOccurs values are rejected as well.
+ . SOAP encoding errors now report the affected type or failing operation in
+ the error message instead of the generic "Encoding: Violation of encoding
+ rules" message. Code that compares the exact message may need to be
+ updated.
+
+- Sodium:
+ . The password-hashing functions sodium_crypto_pwhash(),
+ sodium_crypto_pwhash_str(),
+ sodium_crypto_pwhash_scryptsalsa208sha256() and
+ sodium_crypto_pwhash_scryptsalsa208sha256_str() now throw ValueError
+ instead of SodiumException when an argument is out of range, such as an
+ opslimit or memlimit below the documented minimum. SodiumException is
+ still thrown for internal libsodium failures.
+
+- SPL:
+ . SplObjectStorage::getHash() implementations may no longer mutate any
+ SplObjectStorage instance. Attempting to do so now throws an Error.
+ . SplFileObject::next() now advances the stream when no prior current()
+ call has cached a line. A subsequent current() call returns the new line
+ rather than the previous one.
+ . SplFileObject::fgets() no longer caches the returned line for subsequent
+ current() calls. current() now re-reads from the current stream position
+ instead of returning the line fgets() just returned.
+ . SplFileObject::next() past EOF no longer increments key() without bound.
+ SplFileObject::seek() past EOF now produces the same key() value as
+ SplTempFileObject; the two previously returned different values.
- Standard:
- . Invalid mode values now throw in array_filter() instead of being silently
- defaulted to 0.
. Form feed (\f) is now added in the default trimmed characters of trim(),
- rtrim() and ltrim(). RFC: https://wiki.php.net/rfc/trim_form_feed
+ rtrim() and ltrim().
+ RFC: https://wiki.php.net/rfc/trim_form_feed
+ . array_filter() now raises a ValueError when an invalid $mode argument value
+ is passed.
+ . array_change_key_case() now raises a ValueError when an invalid $case
+ argument value is passed.
+ . getenv() and putenv() now raises a ValueError when the first argument
+ contains NUL bytes.
+ . dl() now raises a ValueError when the $extension_filename argument contains
+ NUL bytes.
+ . openlog() now raises a ValueError when the $prefix argument contains NUL
+ bytes.
+ . parse_str() now raises a ValueError when the $string argument contains NUL
+ bytes.
+ . linkinfo() now raises a ValueError when the $path argument is empty.
+ . pathinfo() now raises a ValueError when an invalid $flag argument value is
+ passed.
+ . scandir() now raises a ValueError when an invalid $sorting_order argument
+ value is passed.
+ . number_format() now raises a ValueError when $decimals is outside the
+ integer range instead of silently clamping very large positive values.
+ . sleep() now raises a ValueError when $seconds is greater than the platform
+ limit (UINT_MAX seconds, or UINT_MAX / 1000 seconds on Windows) instead of
+ allowing the value to overflow.
+ . usleep() now raises a ValueError when $microseconds is greater than
+ UINT_MAX instead of allowing the value to overflow.
+ . proc_open() now raises a ValueError when the $cwd argument contains NUL
+ bytes.
+
+- Sysvshm:
+ . shm_attach() now raises a ValueError when the $key argument is outside the
+ platform's key_t range instead of passing a truncated key to the operating
+ system.
+
+- Zip:
+ . ZipArchive::extractTo now raises a TypeError for the files argument if one
+ or more of the entries is not a string.
+ . ZipArchive::addGlob() and ZipArchive::addPattern() now raise a TypeError
+ when the "remove_all_path" option is not of type bool, or when the
+ "comp_method", "comp_flags", or "enc_method" options are not of type int
+ (numeric strings are coerced), instead of emitting a warning.
+
+- Zlib:
+ . deflate_init() now raises a TypeError when the value for option "level",
+ "memory", "window", or "strategy" is not of type int.
+ . inflate_init() now raises a TypeError when the value for option "window" is
+ not of type int.
========================================
2. New Features
@@ -57,11 +242,34 @@ PHP 8.6 UPGRADE NOTES
- Core:
. It is now possible to use reference assign on WeakMap without the key
needing to be present beforehand.
+ . It is now possible to define the __debugInfo() magic method on enums.
+ RFC: https://wiki.php.net/rfc/debugable-enums
+ . #[\Override] can now be applied to class constants, including enum cases.
+ RFC: https://wiki.php.net/rfc/override_constants
+ . Implemented partial function application
+ RFC: https://wiki.php.net/rfc/partial_function_application_v2
+
+- Curl:
+ . curl_getinfo() return array now includes a new size_delivered key, which
+ indicates the total number of bytes passed to the download write callback.
+ This value can also be obtained by passing CURLINFO_SIZE_DELIVERED as the
+ $option parameter.
+ Requires libcurl 8.20.0 or later.
+ . Added CURLOPT_SEEKFUNCTION to register a callback that repositions a
+ streamed request body so libcurl can rewind and resend it on a redirect,
+ multi-pass authentication, or a retried reused connection instead of
+ failing with CURLE_SEND_FAIL_REWIND. The callback receives the CurlHandle,
+ offset and origin, and must return one of CURL_SEEKFUNC_OK,
+ CURL_SEEKFUNC_FAIL or CURL_SEEKFUNC_CANTSEEK.
- Fileinfo:
. finfo_file() now works with remote streams.
- Intl:
+ . Added Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue(),
+ with the alias of locale_get_display_keyword() and
+ locale_get_display_keyword_value() respectively.
+ RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue
. Added IntlNumberRangeFormatter class to format an interval of two numbers
with a given skeleton, locale, IntlNumberRangeFormatter::COLLAPSE_AUTO,
IntlNumberRangeFormatter::COLLAPSE_NONE,
@@ -73,16 +281,35 @@ PHP 8.6 UPGRADE NOTES
IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE identity fallbacks.
It is supported from icu 63.
+- IO:
+ . Added new polling API.
+ RFC: https://wiki.php.net/rfc/poll_api
+
- JSON:
. Added extra info about error location to the JSON error messages returned
from json_last_error_msg() and JsonException message.
+- OpenSSL:
+ . Added TLS session resumption support for streams with new stream context
+ options: session_data, session_new_cb, session_cache, session_cache_size,
+ session_timeout, session_id_context, session_get_cb, session_remove_cb,
+ and num_tickets. This allows saving and restoring client sessions across
+ requests, implementing custom server-side session storage, and controlling
+ session cache behavior.
+ RFC: https://wiki.php.net/rfc/tls_session_resumption
+ . Added TLS external PSK support for streams with new strem context options:
+ psk_client_cb and psk_server_cb. This allows setting and receiving PSK.
+
- Phar:
. Overriding the getMTime() and getPathname() methods of SplFileInfo now
influences the result of the phar buildFrom family of functions.
This makes it possible to override the timestamp and names of files.
- Streams:
+ . Added new stream errors API including new classes, enums, functions and
+ internal API. It is controlled using error_mode, error_store and
+ error_handler stream context options.
+ RFC: https://wiki.php.net/rfc/stream_errors
. Added stream socket context option so_reuseaddr that allows disabling
address reuse (SO_REUSEADDR) and explicitly uses SO_EXCLUSIVEADDRUSE on
Windows.
@@ -90,77 +317,178 @@ PHP 8.6 UPGRADE NOTES
tcp_keepintvl and tcp_keepcnt that allow setting socket keepalive
options.
. Allowed casting casting filtered streams as file descriptor for select.
+ . Added the "write_seek_mode stream" filter parameter for the bz2, iconv,
+ zlib, and string stream filters. This parameter must be set via an
+ associative array where the key is "write_seek_mode stream" and the
+ value is one of the following strings "preserve", "reset", or "strict".
+
+- URI:
+ . Added Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme().
+ RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection
+ . Added Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType().
+ RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection
+ . Added Uri\Rfc3986\UriBuilder.
+ RFC: https://wiki.php.net/rfc/uri_followup#uri_building
========================================
3. Changes in SAPI modules
========================================
+- CLI:
+ . The built-in development server now accepts requests using the QUERY HTTP
+ method instead of returning 501 Not Implemented.
+
========================================
4. Deprecated Functionality
========================================
+- Core:
+ . Specifying a return type of array|null / ?array for __debugInfo() is now
+ deprecated. Specify array instead.
+ . Returning values from __construct() and __destruct() is now deprecated.
+ RFC: https://wiki.php.net/rfc/deprecate-return-value-from-construct
+ . Making __construct() and __destruct() a Generator is now deprecated.
+ RFC: https://wiki.php.net/rfc/deprecate-return-value-from-construct
+
+- GMP
+ . The shift (<<, >>) and exponentiation (**) operators on GMP objects now
+ emit a deprecation warning when converting a float right operand to int
+ loses precision.
+
+- Mbstring:
+ . Mbregex has been deprecated, because the underlying Oniguruma library
+ is no longer maintained.
+ RFC: https://wiki.php.net/rfc/eol-oniguruma
+
========================================
5. Changed Functions
========================================
+- GMP:
+ . gmp_fact() now throws a ValueError() if $num does not fit into a unsigned
+ long.
+ . gmp_pow(), gmp_binomial(), gmp_root() and gmp_rootrem() now throw a
+ ValueError if their second argument does not fit into an unsigned long.
+ . The shift (<<, >>) and exponentiation (**) operators on GMP objects now
+ throw a ValueError if the right operand does not fit into an unsigned long.
+ . gmp_powm() modulo-by-zero now raises a DivisionByZeroError whose message
+ includes the function name and argument index ($modulus).
+
- mysqli:
- . The return structure of mysqli_get_charset() no longer contains
- the undocumented "comment" element. The value of "charsetnr" is
- now set to a constant 0 as this number was an implementation detail
- that should not have been exposed to the public.
+ . The return structure of mysqli_get_charset() no longer contains the
+ undocumented "comment" element. The value of "charsetnr" is now set to a
+ constant 0 as this number was an implementation detail that should not have
+ been exposed to the public.
- OpenSSL:
. Output of openssl_x509_parse() contains criticalExtensions listing all
critical certificate extensions.
+ . openssl_sign() and openssl_verify() now have an additional optional
+ argument $salt_length that allows controlling the RSA-PSS salt length
+ when OPENSSL_PKCS1_PSS_PADDING is used. It accepts an explicit length or
+ one of the new OPENSSL_RSA_PSS_SALTLEN_* constants.
-- PCNTL:
- . pcntl_alarm() now throws a ValueError if the seconds argument is
- lower than zero or greater than platform's UINT_MAX.
+- PDO_DBLIB:
+ . When using persistent connections, there is now a liveness check in the
+ constructor.
- Phar:
. Phar::mungServer() now supports reference values.
-- Posix:
- . posix_access() now throws a ValueError exception if the flags
- argument is invalid.
- . posix_mkfifo() now throws a ValueError exception if the permissions
- argument is invalid.
-
- Sockets:
. socket_addrinfo_lookup() now has an additional optional argument $error
when not null, and on failure, gives the error code (one of the EAI_*
constants).
-- Zip:
- . ZipArchive::extractTo now raises a TypeError for the
- files argument if one or more of the entries is not
- a string.
+- Standard:
+ . ini_get_all() now includes a "builtin_default_value" element for each
+ directive when $details is true. It holds the built-in default value of the
+ directive (or null if it has none), independent of values set in php.ini,
+ on the command line, or at runtime.
========================================
6. New Functions
========================================
+- Intl:
+ . grapheme_strrev()
+ RFC: https://wiki.php.net/rfc/grapheme_strrev
+ . Locale::getDisplayKeyword() and Locale::getDisplayKeywordValue()
+ RFC: https://wiki.php.net/rfc/getdisplaykeyword_and_getdisplaykeywordvalue
+
+- mysqli:
+ . Added mysqli::quote_string() and mysqli_quote_string().
+ RFC: https://wiki.php.net/rfc/mysqli_quote_string
+
- Reflection:
. ReflectionConstant::inNamespace()
- . Added ReflectionProperty::isReadable() and ReflectionProperty::isWritable().
+ . ReflectionProperty::isReadable() and ReflectionProperty::isWritable()
RFC: https://wiki.php.net/rfc/isreadable-iswriteable
-
-- Intl:
- . `grapheme_strrev()` returns strrev for grapheme cluster unit.
- RFC: https://wiki.php.net/rfc/grapheme_strrev
+ . ReflectionParameter::getDocComment()
+ RFC: https://wiki.php.net/rfc/parameter-doccomments
- Standard:
- . `clamp()` returns the given value if in range, else return the nearest
+ . clamp() returns the given value if in range, else return the nearest
bound.
RFC: https://wiki.php.net/rfc/clamp_v2
+ . stream_last_errors() and stream_clear_errors()
+ RFC: https://wiki.php.net/rfc/stream_errors
+ . stream_socket_get_crypto_status()
+
+- URI:
+ . Uri\Rfc3986\Uri::getUriType() and Uri\WhatWg\Url::isSpecialScheme()
+ RFC: https://wiki.php.net/rfc/uri_followup#uri_type_detection
+ . Uri\Rfc3986\Uri::getHostType() and Uri\WhatWg\Url::getHostType()
+ RFC: https://wiki.php.net/rfc/uri_followup#host_type_detection
- Zip:
- . Added ZipArchive::openString() method.
+ . ZipArchive::openString()
+ . ZipArchive::closeString()
========================================
7. New Classes and Interfaces
========================================
+- Intl:
+ . IntlNumberRangeFormatter
+
+- OpenSSL:
+ . Openssl\OpensslException
+ . Openssl\Session
+ RFC: https://wiki.php.net/rfc/tls_session_resumption
+ . Openssl\Psk
+
+- Standard:
+ . enum SortDirection
+ RFC: https://wiki.php.net/rfc/sort_direction_enum
+ . StreamError
+ . StreamException
+ . enum StreamErrorStore
+ . enum StreamErrorMode
+ . enum StreamErrorCode
+ RFC: https://wiki.php.net/rfc/stream_errors
+ . Io\Poll\Context
+ . Io\Poll\Watcher
+ . enum Io\Poll\Backend
+ . enum Io\Poll\Event
+ . interface Io\Poll\Handle
+ . Io\IoException
+ . Io\Poll\PollException
+ . Io\Poll\FailedPollOperationException
+ . Io\Poll\FailedContextInitializationException
+ . Io\Poll\FailedHandleAddException
+ . Io\Poll\FailedWatcherModificationException
+ . Io\Poll\FailedPollWaitException
+ . Io\Poll\BackendUnavailableException
+ . Io\Poll\InactiveWatcherException
+ . Io\Poll\HandleAlreadyWatchedException
+ . Io\Poll\InvalidHandleException
+ . StreamPollHandle
+
+- URI:
+ . Uri\Rfc3986\UriBuilder
+ RFC: https://wiki.php.net/rfc/uri_followup#uri_building
+
========================================
8. Removed Extensions and SAPIs
========================================
@@ -175,10 +503,25 @@ PHP 8.6 UPGRADE NOTES
- mysqli
. Added new constant MYSQLI_OPT_COMPRESS.
+- Opcache
+ . JIT is now supported for ZTS builds on Apple Silicon.
+
========================================
10. New Global Constants
========================================
+- Curl:
+ . CURLINFO_SIZE_DELIVERED (libcurl >= 8.20.0).
+ . CURLOPT_SEEKFUNCTION.
+ . CURL_SEEKFUNC_OK.
+ . CURL_SEEKFUNC_FAIL.
+ . CURL_SEEKFUNC_CANTSEEK.
+
+- OpenSSL:
+ . OPENSSL_RSA_PSS_SALTLEN_DIGEST.
+ . OPENSSL_RSA_PSS_SALTLEN_AUTO.
+ . OPENSSL_RSA_PSS_SALTLEN_MAX.
+
- Sockets:
. TCP_USER_TIMEOUT (Linux only).
. AF_UNSPEC.
@@ -201,12 +544,22 @@ PHP 8.6 UPGRADE NOTES
. EAI_IDN_ENCODE.
- Standard
- . ARRAY_FILTER_USE_KEY.
+ . ARRAY_FILTER_USE_VALUE.
+ . STREAM_CRYPTO_STATUS_NONE
+ . STREAM_CRYPTO_STATUS_WANT_READ
+ . STREAM_CRYPTO_STATUS_WANT_WRITE
========================================
11. Changes to INI File Handling
========================================
+- Mbstring:
+ . The mbstring.detect_order INI directive now updates the internal detection
+ order when changed at runtime via ini_set(). Previously, runtime changes
+ using ini_set() did not take effect for mb_detect_order(). Setting the
+ directive to NULL or an empty string at runtime now leaves the previously
+ configured detection order unchanged.
+
- Mysqli:
. mysqli.default_port now checks the validity of the value which should be
between 0 and 65535 included.
@@ -216,13 +569,6 @@ PHP 8.6 UPGRADE NOTES
When used along with ZEND_JIT_DEBUG_TRACE_EXIT_INFO, the source of exit
points is printed in exit info output, in debug builds.
-- Mbstring:
- . The mbstring.detect_order INI directive now updates the internal detection
- order when changed at runtime via ini_set(). Previously, runtime changes
- using ini_set() did not take effect for mb_detect_order(). Setting the
- directive to NULL or an empty string at runtime now leaves the previously
- configured detection order unchanged.
-
========================================
12. Windows Support
========================================
@@ -231,12 +577,18 @@ PHP 8.6 UPGRADE NOTES
13. Other Changes
========================================
+- Core:
+ . In case of a hard OOM PHP now calls abort() instead of exit(1), changing
+ the exit code to 134 and possibly creating a core dump.
+ . The PHP_OS_FAMILY constant has an AIX value for when running on AIX or
+ IBM i via PASE.
+
========================================
14. Performance Improvements
========================================
- Core:
- . `printf()` using only `%s` and `%d` will be compiled into the equivalent
+ . printf() using only "%s" and "%d" will be compiled into the equivalent
string interpolation, avoiding the overhead of a function call and
repeatedly parsing the format string.
. Arguments are now passed more efficiently to known constructors (e.g. when
@@ -246,20 +598,48 @@ PHP 8.6 UPGRADE NOTES
creation of intermediate Closures, the overhead of calling userland
callbacks from internal functions and providing for better insight for the
JIT.
+ . The performance of the TAILCALL VM has been improved.
+ . The TAILCALL VM is now enabled on Windows when compiling with Clang >= 19
+ x86_64.
+ . The performance of ZTS builds has been improved.
- DOM:
. Made splitText() faster and consume less memory.
- JSON:
. Improve performance of encoding arrays and objects.
+ . Improved performance of indentation generation in json_encode()
+ when using PHP_JSON_PRETTY_PRINT.
+
+- Intl:
+ . Improved performance of IntlCalendar::getAvailableLocales() and
+ IntlDateFormatter::localtime() / datefmt_localtime() by pre-allocating
+ their returned arrays.
+ . Improved performance of transliterator_list_ids() and
+ resourcebundle_locales() by pre-allocating their returned arrays.
+
+- Phar:
+ . Reduced temporary allocations when iterating Phar directories.
- Standard:
. Improved performance of array_fill_keys().
. Improved performance of array_map() with multiple arrays passed.
+ . Improved performance of array_sum() and array_product() for
+ integer-only arrays.
. Improved performance of array_unshift().
. Improved performance of array_walk().
. Improved performance of intval('+0b...', 2) and intval('0b...', 2).
. Improved performance of str_split().
+- URI:
+ . Improved performance of Uri\WhatWg\Url::parse() when collecting
+ validation errors by pre-allocating the error array.
+ . Reduced allocations when reading IPv6/IPFuture hosts and paths with
+ Uri\Rfc3986\Uri.
+ . Improved performance and memory consumption when using normalizing
+ (non-raw) getters on already-normalized URIs with Uri\Rfc3986\Uri.
+
- Zip:
+ . Improved performance of ZipArchive::addGlob() and
+ ZipArchive::addPattern() by pre-allocating their returned arrays.
. Avoid string copies in ZipArchive::addFromString().
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 57bef65c25ee..49f9e1317143 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -14,12 +14,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES
1. Internal API changes
========================
- . ZSTR_INIT_LITERAL(), zend_string_starts_with_literal(), and
- zend_string_starts_with_literal_ci() now support strings containing NUL
- bytes. Passing non-literal char* is no longer supported.
+- Removed:
. The misnamed ZVAL_IS_NULL() has been removed. Use Z_ISNULL() instead.
- . New zend_class_entry.ce_flags2 and zend_function.fn_flags2 fields were
- added, given the primary flags were running out of bits.
. The zval_is_true() alias of zend_is_true() has been removed. Call
zend_is_true() directly instead.
. The _zval_get_*() compatibility macros for PHP 7.2 have been removed
@@ -35,35 +31,79 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. The zval_dtor() alias of zval_ptr_dtor_nogc() has been removed.
Call zval_ptr_dtor_nogc() directly instead.
. The internal zend_copy_parameters_array() function is no longer exposed.
+ . The internal zend_hash_minmax() function is no longer exposed. Scan the
+ HashTable directly and use zend_compare() for value comparisons instead.
. The zend_make_callable() function has been removed, if a callable zval
needs to be obtained use the zend_get_callable_zval_from_fcc() function
instead. If this was used to store a callable, then an FCC should be
stored instead.
- . The zend_active_function{_ex}() functions now return a const zend_function
- pointer.
- . The zend_get_call_trampoline_func() API now takes the __call or
- __callStatic zend_function* instead of a CE and a boolean argument.
+ . The zend_exception_save() and zend_exception_restore() functions were
+ removed.
. The zend_set_hash_symbol() API has been removed.
- . Added zend_hash_str_lookup().
. The WRONG_PARAM_COUNT and ZEND_WRONG_PARAM_COUNT() macros have been
removed. Call zend_wrong_param_count(); followed by RETURN_THROWS();
instead.
. PHP_HAVE_STREAMS macro removed from .
- . zend_function.arg_info is now always a zend_arg_info*. Before, it was a
- zend_internal_arg_info on internal functions, unless the
- ZEND_ACC_USER_ARG_INFO flag was set.
- . Added zend_ast_call_get_args() to fetch the argument node from any call
- node.
- . The zend_exception_save() and zend_exception_restore() functions were
- removed.
+ . The INI_STR(), INI_INT(), INI_FLT(), and INI_BOOL() macros have been
+ removed. Instead new zend_ini_{bool|long|double|str|string}_literal()
+ macros have been added. This fixes an internal naming inconsistency as
+ "str" usually means zend_string*, and "string" means char*.
+ However INI_STR() returned a char*
+ . The INI_ORIG_{INT|STR|FLT|BOOL}() macros have been removed as they are
+ unused. If this behaviour is required fall back to the zend_ini_*
+ functions.
+ . The unused ZEND_AST_PARENT_PROPERTY_HOOK_CALL has been removed.
+ . The EMPTY_SWITCH_DEFAULT_CASE() macro has been removed. Use
+ default: ZEND_UNREACHABLE(); instead.
+ . The ZEND_RESULT_CODE type has been removed. Use zend_result directly.
+ . The zend_parse_parameters_none_throw(), zend_parse_parameters_throw(),
+ and ZEND_PARSE_PARAMS_THROW have been removed due to being misleading,
+ since ZPP always throws, unless ZEND_PARSE_PARAMS_QUIET is given. Use
+ the non-throw versions.
+ . The XtOffsetOf() alias of C’s offsetof() macro has been removed. Use
+ offsetof() directly.
+ . The deprecated Z_COPYABLE(), Z_COPYABLE_P(), Z_OPT_COPYABLE(), and
+ Z_OPT_COPYABLE_P() macros have been removed. Check for IS_ARRAY directly.
+ . The deprecated Z_IMMUTABLE(), Z_IMMUTABLE_P(), Z_OPT_IMMUTABLE(), and
+ Z_OPT_IMMUTABLE_P() macros have been removed. Check for
+ IS_ARRAY && !REFCOUNTED directly.
+ . The unused Z_GC_*() macros have been removed. Use the corresponding
+ GC_*() macro on the result of Z_COUNTED().
+ . The zend_binary_zval_strcmp() and zend_binary_zval_strncmp() functions
+ have been removed, because they are unsafe by relying on the zvals
+ having a specific type. Use zend_binary_strcmp() / zend_binary_strncmp(),
+ string_compare_function() or similar instead.
+ . The OPENBASEDIR_CHECKPATH() compatibility macro has been removed, instead
+ use php_check_open_basedir() directly.
+ . The Z_CONSTANT(), Z_CONSTANT_P(), Z_OPT_CONSTANT(), and
+ Z_OPT_CONSTANT_P() macros have been removed. Check for IS_CONSTANT_AST
+ directly.
+ . The {_}php_stream_fopen_with_path() functions have been removed as they are
+ unused.
+ . The php_error_docref1() and php_error_docref2() functions have been
+ removed, instead rely on the error_include_args INI option to show the
+ arguments to functions in a consistent manner.
+
+- Changed:
. Internal functions that return by reference are now expected to
automatically unwrap references when the result of the call is stored in an
IS_TMP_VAR variable. This may be achieved by calling the
zend_return_unwrap_ref() function.
- . The php_math_round_mode_from_enum() function now takes a
- zend_enum_RoundingMode parameter.
- . Added Z_PARAM_ENUM().
- . Added zend_enum_fetch_case_id().
+ . ZEND_AST_METHOD_REFERENCE has been renamed to
+ ZEND_AST_TRAIT_METHOD_REFERENCE.
+ . Functions using zend_forbid_dynamic_call() *must* be flagged with
+ ZEND_ACC2_FORBID_DYN_CALLS (@forbid-dynamic-calls in stubs). In debug
+ builds, failing to include that flag will lead to assertion failures.
+ . The zend_get_call_trampoline_func() API now takes the __call or
+ __callStatic zend_function* instead of a CE and a boolean argument.
+ . ZSTR_INIT_LITERAL(), zend_string_starts_with_literal(), and
+ zend_string_starts_with_literal_ci() now support strings containing NUL
+ bytes. Passing non-literal char* is no longer supported.
+ . The zend_active_function{_ex}() functions now return a const zend_function
+ pointer.
+ . zend_function.arg_info is now always a zend_arg_info*. Before, it was a
+ zend_internal_arg_info on internal functions, unless the
+ ZEND_ACC_USER_ARG_INFO flag was set.
. ZEND_INI_GET_ADDR() is now a void* pointer instead of a char* pointer. This
more correctly represents the generic nature of the returned pointer and
allows to remove explicit casts, but possibly breaks pointer arithmetic
@@ -72,8 +112,48 @@ PHP 8.6 INTERNALS UPGRADE NOTES
zend_string* parameter.
. EG(in_autoload) was renamed to EG(autoload_current_classnames) and no
longer is a pointer, but a directly embedded HashTable struct.
+ . Extended php_stream_filter_ops with seek method.
+ . zend_argument_error_variadic() now takes a new 'function' parameter.
+ . The param argument in the php_verror() function has been removed.
+
+- Added:
+ . New zend_class_entry.ce_flags2 and zend_function.fn_flags2 fields were
+ added, given the primary flags were running out of bits.
+ . Added zend_hash_str_lookup().
+ . Added zend_ast_call_get_args() to fetch the argument node from any call
+ node.
+ . Added Z_PARAM_ENUM().
+ . Added zend_enum_fetch_case_id().
+ . Added zend_enum_get_case_by_id().
+ . Added zend_bin2hex() and zend_bin2hex_str() as helper functions to remove
+ dependencies on /ext/hash in various extensions.
. Added a C23_ENUM() helper macro to define forward-compatible fixed-size
enums.
+ . Added zend_fcall_info.consumed_args together with
+ zend_fci_consumed_arg(), which allows moving a selected callback argument
+ instead of copying it in zend_call_function(). Currently only a single
+ consumed argument is supported.
+ . Added ZEND_CONTAINER_OF().
+ . Added zend_reflection_property_set_raw_value_without_lazy_initialization(),
+ zend_reflection_property_set_raw_value() to expose the functionality of
+ ReflectionProperty::setRawValueWithoutLazyInitialization() and
+ ReflectionProperty::setRawValue() to C extensions.
+ . Added zend_argument_error_ex(), zend_argument_type_error_ex(),
+ zend_argument_value_error_ex().
+ . Added zend_ast_dup().
+ . Added zend_compile_ast().
+ . Added zend_check_type_ex().
+ . Added zend_create_partial_closure().
+ . Added a new IO copy API in . php_io_copy() copies bytes between
+ file descriptors using the most efficient platform primitive available
+ (sendfile, splice, copy_file_range, TransmitFile), and is now used by
+ php_stream_copy_to_stream_ex(). The mmap-based copy fallback was removed.
+ . Added zend_string_equals_cstr_ci().
+ . Added zend_cstr_append_char(), zend_cstr_concat(), and
+ zend_cstr_concat3() as helper functions to allocate NUL-terminated raw C
+ strings from one or more buffers.
+ . Added zend_string_ends_with() and related variants.
+ . Added trait support for internal classes.
========================
2. Build system changes
@@ -99,6 +179,8 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. --with-pic is now --enable-pic. The old flag will result in an error.
. Symbol HAVE_ST_BLOCKS has been removed from php_config.h (use
HAVE_STRUCT_STAT_ST_BLOCKS).
+ . Added a new configure option --disable-apache2-conf to prevent apxs from
+ editing httpd.conf during installation.
- Windows build system changes:
. Function SETUP_OPENSSL() doesn't accept 6th argument anymore and doesn't
@@ -112,6 +194,18 @@ PHP 8.6 INTERNALS UPGRADE NOTES
3. Module changes
========================
+- ext/date:
+ . php_idate() now returns the result state, and moves the return value into an
+ out parameter.
+
+- ext/intl:
+ . Added intl_icu_compat.h with helpers and feature macros for ICU
+ version-specific API differences. Code in ext/intl should use the
+ intl_icu_compat_* helpers and INTL_ICU_HAS_* macros instead of adding
+ direct U_ICU_VERSION_* guards for supported ICU API variants.
+ . The internal grapheme_get_break_iterator() helper no longer accepts a
+ stack buffer argument; pass only the UErrorCode* status argument.
+
- ext/mbstring:
. Added GB18030-2022 to default encoding list for zh-CN.
@@ -119,6 +213,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. Dropped session_options parameter from all methods in mysqlnd_auth.
The same information is present in conn->options and should be used
instead.
+ . Removed charsets plugin.
- ext/session:
. php_session_flush() now returns a bool rather than a zend_result.
@@ -142,6 +237,13 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. _php_error_log() now accepts zend_string* values instead of char*.
. _php_error_log_ex() has been removed.
. php_mail()'s extra_cmd parameter is now a zend_string*.
+ . The php_math_round_mode_from_enum() function now takes a
+ zend_enum_RoundingMode parameter.
+
+- ext/uri:
+ . The value parameter of the php_uri_property_handler_write callback is now
+ const zval * instead of zval *, reflecting that write handlers must
+ not modify the input zval.
- ext/xml:
. Removed the XML_ExpatVersion() libxml compatibility wrapper,
@@ -160,3 +262,11 @@ PHP 8.6 INTERNALS UPGRADE NOTES
========================
5. SAPI changes
========================
+
+- SAPIs should explicitly release a thread's resources by calling
+ ts_free_thread() before terminating it. tsrm_shutdown() can only release the
+ resources of the calling thread, for resources allocated with
+ ts_allocate_tls_id().
+
+- AG and SCNG are now allocated with ts_allocate_tls_id() and live in native
+ __thread storage on ZTS builds.
diff --git a/Zend/LICENSE b/Zend/LICENSE
deleted file mode 100644
index 51f5cccde950..000000000000
--- a/Zend/LICENSE
+++ /dev/null
@@ -1,56 +0,0 @@
---------------------------------------------------------------------
- The Zend Engine License, Version 2.00
-Copyright (c) 1999-2006 Zend Technologies Ltd. All rights reserved.
---------------------------------------------------------------------
-
-Redistribution and use in source and binary forms, with or without
-modification, is permitted provided that the following conditions
-are met:
-
- 1. Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
-
- 2. Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
-
- 3. The names "Zend" and "Zend Engine" must not be used to endorse
- or promote products derived from this software without prior
- permission from Zend Technologies Ltd. For written permission,
- please contact license@zend.com.
-
- 4. Zend Technologies Ltd. may publish revised and/or new versions
- of the license from time to time. Each version will be given a
- distinguishing version number.
- Once covered code has been published under a particular version
- of the license, you may always continue to use it under the
- terms of that version. You may also choose to use such covered
- code under the terms of any subsequent version of the license
- published by Zend Technologies Ltd. No one other than Zend
- Technologies Ltd. has the right to modify the terms applicable
- to covered code created under this License.
-
- 5. Redistributions of any form whatsoever must retain the following
- acknowledgment:
- "This product includes the Zend Engine, freely available at
- http://www.zend.com"
-
- 6. All advertising materials mentioning features or use of this
- software must display the following acknowledgment:
- "The Zend Engine is freely available at http://www.zend.com"
-
-THIS SOFTWARE IS PROVIDED BY ZEND TECHNOLOGIES LTD. ``AS IS'' AND
-ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
-PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ZEND
-TECHNOLOGIES LTD. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-SUCH DAMAGE.
-
---------------------------------------------------------------------
diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c
index 5a0af2630391..3775f8165c3e 100644
--- a/Zend/Optimizer/block_pass.c
+++ b/Zend/Optimizer/block_pass.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -346,59 +344,6 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
}
break;
-#if 0
- /* pre-evaluate functions:
- constant(x)
- function_exists(x)
- extension_loaded(x)
- BAD: interacts badly with Accelerator
- */
- if((opline->op1_type & IS_VAR) &&
- VAR_SOURCE(opline->op1) && VAR_SOURCE(opline->op1)->opcode == ZEND_DO_CF_FCALL &&
- VAR_SOURCE(opline->op1)->extended_value == 1) {
- zend_op *fcall = VAR_SOURCE(opline->op1);
- zend_op *sv = fcall-1;
- if(sv >= block->start_opline && sv->opcode == ZEND_SEND_VAL &&
- sv->op1_type == IS_CONST && Z_TYPE(OPLINE_OP1_LITERAL(sv)) == IS_STRING &&
- Z_LVAL(OPLINE_OP2_LITERAL(sv)) == 1
- ) {
- zval *arg = &OPLINE_OP1_LITERAL(sv);
- char *fname = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].function_name;
- size_t flen = FUNCTION_CACHE->funcs[Z_LVAL(ZEND_OP1_LITERAL(fcall))].name_len;
- if((flen == sizeof("function_exists")-1 && zend_binary_strcasecmp(fname, flen, "function_exists", sizeof("function_exists")-1) == 0) ||
- (flen == sizeof("is_callable")-1 && zend_binary_strcasecmp(fname, flen, "is_callable", sizeof("is_callable")-1) == 0)
- ) {
- zend_function *function;
- if((function = zend_hash_find_ptr(EG(function_table), Z_STR_P(arg))) != NULL) {
- literal_dtor(arg);
- MAKE_NOP(sv);
- MAKE_NOP(fcall);
- LITERAL_BOOL(opline->op1, 1);
- opline->op1_type = IS_CONST;
- }
- } else if(flen == sizeof("constant")-1 && zend_binary_strcasecmp(fname, flen, "constant", sizeof("constant")-1) == 0) {
- zval c;
- if (zend_optimizer_get_persistent_constant(Z_STR_P(arg), &c, true ELS_CC)) {
- literal_dtor(arg);
- MAKE_NOP(sv);
- MAKE_NOP(fcall);
- ZEND_OP1_LITERAL(opline) = zend_optimizer_add_literal(op_array, &c);
- /* no copy ctor - get already copied it */
- opline->op1_type = IS_CONST;
- }
- } else if(flen == sizeof("extension_loaded")-1 && zend_binary_strcasecmp(fname, flen, "extension_loaded", sizeof("extension_loaded")-1) == 0) {
- if(zend_hash_exists(&module_registry, Z_STR_P(arg))) {
- literal_dtor(arg);
- MAKE_NOP(sv);
- MAKE_NOP(fcall);
- LITERAL_BOOL(opline->op1, 1);
- opline->op1_type = IS_CONST;
- }
- }
- }
- }
-#endif
-
case ZEND_FETCH_LIST_R:
case ZEND_FETCH_LIST_W:
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
@@ -703,7 +648,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
} else if (opline->op1_type == IS_TMP_VAR &&
!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var))) {
src = VAR_SOURCE(opline->op1);
- if (src) {
+ if (src && (src->op1_type != IS_VAR)) {
if (src->opcode == ZEND_BOOL_NOT) {
VAR_SOURCE(opline->op1) = NULL;
COPY_NODE(opline->op1, src->op1);
@@ -747,7 +692,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
(!zend_bitset_in(used_ext, VAR_NUM(opline->op1.var)) ||
opline->result.var == opline->op1.var)) {
src = VAR_SOURCE(opline->op1);
- if (src) {
+ if (src && (src->op1_type != IS_VAR)) {
if (src->opcode == ZEND_BOOL ||
src->opcode == ZEND_QM_ASSIGN) {
VAR_SOURCE(opline->op1) = NULL;
@@ -1185,7 +1130,7 @@ static void assemble_code_blocks(const zend_cfg *cfg, zend_op_array *op_array, z
/* rebuild map (just for printing) */
memset(cfg->map, -1, sizeof(int) * op_array->last);
- for (int n = 0; n < cfg->blocks_count; n++) {
+ for (uint32_t n = 0; n < cfg->blocks_count; n++) {
if (cfg->blocks[n].flags & (ZEND_BB_REACHABLE|ZEND_BB_UNREACHABLE_FREE)) {
cfg->map[cfg->blocks[n].start] = n;
}
@@ -1493,7 +1438,7 @@ static void zend_jmp_optimization(zend_basic_block *block, zend_op_array *op_arr
* defined. We won't apply some optimization patterns for such variables. */
static void zend_t_usage(const zend_cfg *cfg, const zend_op_array *op_array, zend_bitset used_ext, zend_optimizer_ctx *ctx)
{
- int n;
+ uint32_t n;
zend_basic_block *block, *next_block;
uint32_t var_num;
uint32_t bitset_len;
@@ -1697,11 +1642,10 @@ static void zend_t_usage(const zend_cfg *cfg, const zend_op_array *op_array, zen
static void zend_merge_blocks(const zend_op_array *op_array, const zend_cfg *cfg, uint32_t *opt_count)
{
- int i;
zend_basic_block *b, *bb;
zend_basic_block *prev = NULL;
- for (i = 0; i < cfg->blocks_count; i++) {
+ for (uint32_t i = 0; i < cfg->blocks_count; i++) {
b = cfg->blocks + i;
if (b->flags & ZEND_BB_REACHABLE) {
if ((b->flags & ZEND_BB_FOLLOW) &&
diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c
index 447a034530e1..a4ecb19c85ef 100644
--- a/Zend/Optimizer/compact_literals.c
+++ b/Zend/Optimizer/compact_literals.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
| Xinchen Hui |
@@ -735,6 +733,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_REF:
case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_PLACEHOLDER:
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
opline->result.num = cache_size;
@@ -747,6 +746,10 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
cache_size += sizeof(void *);
}
break;
+ case ZEND_CALLABLE_CONVERT_PARTIAL:
+ opline->op1.num = cache_size;
+ cache_size += 2 * sizeof(void *);
+ break;
}
opline++;
}
diff --git a/Zend/Optimizer/compact_vars.c b/Zend/Optimizer/compact_vars.c
index 9898714a17c5..b4a861d3595c 100644
--- a/Zend/Optimizer/compact_vars.c
+++ b/Zend/Optimizer/compact_vars.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Removing unused variables |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Nikita Popov |
+----------------------------------------------------------------------+
diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c
index 6c5a885fba40..0780ac190cdd 100644
--- a/Zend/Optimizer/dce.c
+++ b/Zend/Optimizer/dce.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, DCE - Dead Code Elimination |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Nikita Popov |
| Dmitry Stogov |
@@ -571,7 +569,7 @@ int dce_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *optimizer
} FOREACH_PHI_END();
/* Mark reachable instruction without side effects as dead */
- int b = ssa->cfg.blocks_count;
+ uint32_t b = ssa->cfg.blocks_count;
while (b > 0) {
int op_data = -1;
diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c
index 796e998493d7..77dc322fbdec 100644
--- a/Zend/Optimizer/dfa_pass.c
+++ b/Zend/Optimizer/dfa_pass.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -398,13 +396,13 @@ static bool variable_defined_or_used_in_range(zend_ssa *ssa, int var, int start,
return false;
}
-int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
+static uint32_t zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
{
- zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
- int removed_ops = 0;
+ const zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ uint32_t removed_ops = 0;
if (func_info->callee_info) {
- zend_call_info *call_info = func_info->callee_info;
+ const zend_call_info *call_info = func_info->callee_info;
do {
zend_op *op = call_info->caller_init_opline;
@@ -413,7 +411,6 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
|| (op->opcode == ZEND_FRAMELESS_ICALL_3 && (op + 1)->op1_type == IS_CONST))
&& call_info->callee_func
&& zend_string_equals_literal_ci(call_info->callee_func->common.function_name, "in_array")) {
-
bool strict = false;
bool has_opdata = op->opcode == ZEND_FRAMELESS_ICALL_3;
ZEND_ASSERT(!call_info->is_prototype);
@@ -428,7 +425,7 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
&& Z_TYPE_P(CT_CONSTANT_EX(op_array, op->op2.constant)) == IS_ARRAY) {
bool ok = true;
- HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, op->op2.constant));
+ const HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, op->op2.constant));
HashTable *dst;
zval *val, tmp;
zend_ulong idx;
@@ -479,7 +476,7 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa)
return removed_ops;
}
-static zend_always_inline void take_successor_0(zend_ssa *ssa, int block_num, zend_basic_block *block)
+static zend_always_inline void take_successor_0(zend_ssa *ssa, uint32_t block_num, zend_basic_block *block)
{
if (block->successors_count == 2) {
if (block->successors[1] != block->successors[0]) {
@@ -489,7 +486,7 @@ static zend_always_inline void take_successor_0(zend_ssa *ssa, int block_num, ze
}
}
-static zend_always_inline void take_successor_1(zend_ssa *ssa, int block_num, zend_basic_block *block)
+static zend_always_inline void take_successor_1(zend_ssa *ssa, uint32_t block_num, zend_basic_block *block)
{
if (block->successors_count == 2) {
if (block->successors[1] != block->successors[0]) {
@@ -500,11 +497,9 @@ static zend_always_inline void take_successor_1(zend_ssa *ssa, int block_num, ze
}
}
-static zend_always_inline void take_successor_ex(zend_ssa *ssa, int block_num, zend_basic_block *block, int target_block)
+static zend_always_inline void take_successor_ex(zend_ssa *ssa, uint32_t block_num, zend_basic_block *block, int target_block)
{
- int i;
-
- for (i = 0; i < block->successors_count; i++) {
+ for (uint32_t i = 0; i < block->successors_count; i++) {
if (block->successors[i] != target_block) {
zend_ssa_remove_predecessor(ssa, block_num, block->successors[i]);
}
@@ -531,10 +526,9 @@ static void replace_predecessor(zend_ssa *ssa, int block_id, int old_pred, int n
int *predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
zend_ssa_phi *phi;
- int i;
int old_pred_idx = -1;
int new_pred_idx = -1;
- for (i = 0; i < block->predecessors_count; i++) {
+ for (uint32_t i = 0; i < block->predecessors_count; i++) {
if (predecessors[i] == old_pred) {
old_pred_idx = i;
}
@@ -582,10 +576,9 @@ static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa
zend_basic_block *src = &ssa->cfg.blocks[from];
zend_basic_block *old = &ssa->cfg.blocks[to];
zend_basic_block *dst = &ssa->cfg.blocks[new_to];
- int i;
zend_op *opline;
- for (i = 0; i < src->successors_count; i++) {
+ for (uint32_t i = 0; i < src->successors_count; i++) {
if (src->successors[i] == to) {
src->successors[i] = new_to;
}
@@ -650,10 +643,10 @@ static void zend_ssa_replace_control_link(zend_op_array *op_array, zend_ssa *ssa
replace_predecessor(ssa, new_to, to, from);
}
-static void zend_ssa_unlink_block(zend_op_array *op_array, zend_ssa *ssa, zend_basic_block *block, int block_num)
+static void zend_ssa_unlink_block(zend_op_array *op_array, zend_ssa *ssa, zend_basic_block *block, uint32_t block_num)
{
if (block->predecessors_count == 1 && ssa->blocks[block_num].phis == NULL) {
- int *predecessors, i;
+ int *predecessors;
zend_basic_block *fe_fetch_block = NULL;
ZEND_ASSERT(block->successors_count == 1);
@@ -669,7 +662,7 @@ static void zend_ssa_unlink_block(zend_op_array *op_array, zend_ssa *ssa, zend_b
}
}
}
- for (i = 0; i < block->predecessors_count; i++) {
+ for (uint32_t i = 0; i < block->predecessors_count; i++) {
zend_ssa_replace_control_link(op_array, ssa, predecessors[i], block_num, block->successors[0]);
}
zend_ssa_remove_block(op_array, ssa, block_num);
@@ -686,7 +679,7 @@ static void zend_ssa_unlink_block(zend_op_array *op_array, zend_ssa *ssa, zend_b
static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa)
{
int removed_ops = 0;
- int block_num = 0;
+ uint32_t block_num = 0;
for (block_num = 1; block_num < ssa->cfg.blocks_count; block_num++) {
zend_basic_block *block = &ssa->cfg.blocks[block_num];
@@ -706,7 +699,7 @@ static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa)
block_num++;
}
while (block_num < ssa->cfg.blocks_count) {
- int next_block_num = block_num + 1;
+ uint32_t next_block_num = block_num + 1;
zend_basic_block *block = &ssa->cfg.blocks[block_num];
uint32_t op_num;
zend_op *opline;
@@ -941,11 +934,13 @@ static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa)
if (block_num > 0) {
zend_ssa_unlink_block(op_array, ssa, block, block_num);
/* backtrack to previous basic block */
+ int backtracking_block_num = block_num;
do {
- block_num--;
- } while (block_num >= 0
- && !(ssa->cfg.blocks[block_num].flags & ZEND_BB_REACHABLE));
- if (block_num >= 0) {
+ backtracking_block_num--;
+ } while (backtracking_block_num >= 0
+ && !(ssa->cfg.blocks[backtracking_block_num].flags & ZEND_BB_REACHABLE));
+ if (backtracking_block_num >= 0) {
+ block_num = backtracking_block_num;
continue;
}
}
diff --git a/Zend/Optimizer/escape_analysis.c b/Zend/Optimizer/escape_analysis.c
index 00ee32984502..8dbd6855d68d 100644
--- a/Zend/Optimizer/escape_analysis.c
+++ b/Zend/Optimizer/escape_analysis.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache, Escape Analysis |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -78,7 +76,7 @@ static zend_result zend_build_equi_escape_sets(int *parent, zend_op_array *op_ar
zend_ssa_var *ssa_vars = ssa->vars;
int ssa_vars_count = ssa->vars_count;
zend_ssa_phi *p;
- int i, j;
+ int i;
int *size;
ALLOCA_FLAG(use_heap)
@@ -94,7 +92,7 @@ static zend_result zend_build_equi_escape_sets(int *parent, zend_op_array *op_ar
if (p->pi >= 0) {
union_find_unite(parent, size, i, p->sources[0]);
} else {
- for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ for (uint32_t j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
union_find_unite(parent, size, i, p->sources[j]);
}
}
diff --git a/Zend/Optimizer/nop_removal.c b/Zend/Optimizer/nop_removal.c
index 7de3919ee8cb..0a6a04fc4a9b 100644
--- a/Zend/Optimizer/nop_removal.c
+++ b/Zend/Optimizer/nop_removal.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c
index 62b50464e87b..05cdce4fc4cf 100644
--- a/Zend/Optimizer/optimize_func_calls.c
+++ b/Zend/Optimizer/optimize_func_calls.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
| Xinchen Hui |
@@ -193,6 +191,7 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
+ case ZEND_CALLABLE_CONVERT_PARTIAL:
call--;
if (call_stack[call].func && call_stack[call].opline) {
zend_op *fcall = call_stack[call].opline;
@@ -225,13 +224,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
* At this point we also know whether or not the result of
* the DO opcode is used, allowing to optimize calls to
* ZEND_ACC_NODISCARD functions. */
- if (opline->opcode != ZEND_CALLABLE_CONVERT) {
+ if (opline->opcode != ZEND_CALLABLE_CONVERT && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
opline->opcode = zend_get_call_op(fcall, call_stack[call].func, !RESULT_UNUSED(opline));
}
if ((ZEND_OPTIMIZER_PASS_16 & ctx->optimization_level)
&& call_stack[call].try_inline
- && opline->opcode != ZEND_CALLABLE_CONVERT) {
+ && opline->opcode != ZEND_CALLABLE_CONVERT
+ && opline->opcode != ZEND_CALLABLE_CONVERT_PARTIAL) {
zend_try_inline_call(op_array, fcall, opline, call_stack[call].func);
}
}
diff --git a/Zend/Optimizer/optimize_temp_vars_5.c b/Zend/Optimizer/optimize_temp_vars_5.c
index de0b189d5dca..6a5c99f4755b 100644
--- a/Zend/Optimizer/optimize_temp_vars_5.c
+++ b/Zend/Optimizer/optimize_temp_vars_5.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
diff --git a/Zend/Optimizer/pass1.c b/Zend/Optimizer/pass1.c
index 4be966c25d89..962bdb6e4be3 100644
--- a/Zend/Optimizer/pass1.c
+++ b/Zend/Optimizer/pass1.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
diff --git a/Zend/Optimizer/pass3.c b/Zend/Optimizer/pass3.c
index 5c31de7bc49c..2d2a44685226 100644
--- a/Zend/Optimizer/pass3.c
+++ b/Zend/Optimizer/pass3.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c
index 60afe3165f77..ba94e9b7b91f 100644
--- a/Zend/Optimizer/sccp.c
+++ b/Zend/Optimizer/sccp.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, SCCP - Sparse Conditional Constant Propagation |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Nikita Popov |
| Dmitry Stogov |
@@ -1596,7 +1594,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
case ZEND_SHORT_CIRCUITING_CHAIN_EMPTY:
ZVAL_TRUE(&zv);
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
SET_RESULT(result, &zv);
break;
@@ -1832,7 +1830,7 @@ static void sccp_mark_feasible_successors(
zend_op *opline, zend_ssa_op *ssa_op) {
sccp_ctx *ctx = (sccp_ctx *) scdf;
zval *op1, zv;
- int s;
+ uint32_t s;
/* We can't determine the branch target at compile-time for these */
switch (opline->opcode) {
@@ -2049,7 +2047,6 @@ static void sccp_visit_phi(scdf_ctx *scdf, const zend_ssa_phi *phi) {
zend_basic_block *block = &ssa->cfg.blocks[phi->block];
int *predecessors = &ssa->cfg.predecessors[block->predecessor_offset];
- int i;
zval result;
MAKE_TOP(&result);
#if SCP_DEBUG
@@ -2061,7 +2058,7 @@ static void sccp_visit_phi(scdf_ctx *scdf, const zend_ssa_phi *phi) {
join_phi_values(&result, &ctx->values[phi->sources[0]], ssa->vars[phi->ssa_var].escape_state != ESCAPE_STATE_NO_ESCAPE);
}
} else {
- for (i = 0; i < block->predecessors_count; i++) {
+ for (uint32_t i = 0; i < block->predecessors_count; i++) {
ZEND_ASSERT(phi->sources[i] >= 0);
if (scdf_is_edge_feasible(scdf, predecessors[i], phi->block)) {
#if SCP_DEBUG
diff --git a/Zend/Optimizer/scdf.c b/Zend/Optimizer/scdf.c
index e5c40b8c90cf..31f40a7ed9f9 100644
--- a/Zend/Optimizer/scdf.c
+++ b/Zend/Optimizer/scdf.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Sparse Conditional Data Flow Propagation Framework |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Nikita Popov |
+----------------------------------------------------------------------+
@@ -254,9 +252,8 @@ static uint32_t cleanup_loop_var_free_block(const scdf_ctx *scdf, const zend_bas
* unreachable. Blocks already marked unreachable are not removed. */
uint32_t scdf_remove_unreachable_blocks(const scdf_ctx *scdf) {
zend_ssa *ssa = scdf->ssa;
- int i;
uint32_t removed_ops = 0;
- for (i = 0; i < ssa->cfg.blocks_count; i++) {
+ for (uint32_t i = 0; i < ssa->cfg.blocks_count; i++) {
const zend_basic_block *block = &ssa->cfg.blocks[i];
if (!zend_bitset_in(scdf->executable_blocks, i) && (block->flags & ZEND_BB_REACHABLE)) {
if (!kept_alive_by_loop_var_free(scdf, block)) {
diff --git a/Zend/Optimizer/scdf.h b/Zend/Optimizer/scdf.h
index 49222880f22b..d7bee4b73816 100644
--- a/Zend/Optimizer/scdf.h
+++ b/Zend/Optimizer/scdf.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Call Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Nikita Popov |
+----------------------------------------------------------------------+
@@ -75,11 +73,10 @@ static inline void scdf_add_def_to_worklist(const scdf_ctx *scdf, int var_num) {
}
}
-static inline uint32_t scdf_edge(const zend_cfg *cfg, int from, int to) {
+static inline uint32_t scdf_edge(const zend_cfg *cfg, int from, uint32_t to) {
const zend_basic_block *to_block = cfg->blocks + to;
- int i;
- for (i = 0; i < to_block->predecessors_count; i++) {
+ for (uint32_t i = 0; i < to_block->predecessors_count; i++) {
uint32_t edge = to_block->predecessor_offset + i;
if (cfg->predecessors[edge] == from) {
@@ -89,7 +86,7 @@ static inline uint32_t scdf_edge(const zend_cfg *cfg, int from, int to) {
ZEND_UNREACHABLE();
}
-static inline bool scdf_is_edge_feasible(const scdf_ctx *scdf, int from, int to) {
+static inline bool scdf_is_edge_feasible(const scdf_ctx *scdf, int from, uint32_t to) {
uint32_t edge = scdf_edge(&scdf->ssa->cfg, from, to);
return zend_bitset_in(scdf->feasible_edges, edge);
}
diff --git a/Zend/Optimizer/ssa_integrity.c b/Zend/Optimizer/ssa_integrity.c
index 793fb1c06c8b..34a2b3a9ef64 100644
--- a/Zend/Optimizer/ssa_integrity.c
+++ b/Zend/Optimizer/ssa_integrity.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, SSA validation |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Nikita Popov |
+----------------------------------------------------------------------+
@@ -66,8 +64,8 @@ static inline bool is_in_phi_sources(zend_ssa *ssa, zend_ssa_phi *phi, int check
}
static inline bool is_in_predecessors(zend_cfg *cfg, zend_basic_block *block, int check) {
- int i, *predecessors = &cfg->predecessors[block->predecessor_offset];
- for (i = 0; i < block->predecessors_count; i++) {
+ int *predecessors = &cfg->predecessors[block->predecessor_offset];
+ for (uint32_t i = 0; i < block->predecessors_count; i++) {
if (predecessors[i] == check) {
return true;
}
@@ -76,8 +74,7 @@ static inline bool is_in_predecessors(zend_cfg *cfg, zend_basic_block *block, in
}
static inline bool is_in_successors(zend_basic_block *block, int check) {
- int s;
- for (s = 0; s < block->successors_count; s++) {
+ for (uint32_t s = 0; s < block->successors_count; s++) {
if (block->successors[s] == check) {
return true;
}
@@ -329,7 +326,7 @@ void ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *ex
/* Phis */
FOREACH_PHI(phi) {
- unsigned num_sources = NUM_PHI_SOURCES(phi);
+ uint32_t num_sources = NUM_PHI_SOURCES(phi);
for (i = 0; i < num_sources; i++) {
int source = phi->sources[i];
if (source < 0) {
@@ -360,7 +357,7 @@ void ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *ex
for (i = 0; i < cfg->blocks_count; i++) {
zend_basic_block *block = &cfg->blocks[i];
int *predecessors = &cfg->predecessors[block->predecessor_offset];
- int s, j;
+ uint32_t j;
if (i != 0 && block->start < (block-1)->start + (block-1)->len) {
FAIL("Block %d start %d smaller previous end %d\n",
@@ -384,7 +381,7 @@ void ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *ex
continue;
}
- for (s = 0; s < block->successors_count; s++) {
+ for (uint32_t s = 0; s < block->successors_count; s++) {
zend_basic_block *next_block;
if (block->successors[s] < 0) {
FAIL("Successor number %d of %d negative", s, i);
@@ -400,7 +397,6 @@ void ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *ex
for (j = 0; j < block->predecessors_count; j++) {
if (predecessors[j] >= 0) {
- int k;
zend_basic_block *prev_block = &cfg->blocks[predecessors[j]];
if (!(prev_block->flags & ZEND_BB_REACHABLE)) {
FAIL("Predecessor %d of %d not reachable\n", predecessors[j], i);
@@ -408,7 +404,7 @@ void ssa_verify_integrity(zend_op_array *op_array, zend_ssa *ssa, const char *ex
if (!is_in_successors(prev_block, i)) {
FAIL("Block %d successors missing %d\n", predecessors[j], i);
}
- for (k = 0; k < block->predecessors_count; k++) {
+ for (uint32_t k = 0; k < block->predecessors_count; k++) {
if (k != j && predecessors[k] == predecessors[j]) {
FAIL("Block %d has duplicate predecessor %d\n", i, predecessors[j]);
}
diff --git a/Zend/Optimizer/zend_call_graph.c b/Zend/Optimizer/zend_call_graph.c
index 884b481aceb8..b67f57cf041d 100644
--- a/Zend/Optimizer/zend_call_graph.c
+++ b/Zend/Optimizer/zend_call_graph.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Call Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -126,6 +124,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
case ZEND_DO_UCALL:
case ZEND_DO_FCALL_BY_NAME:
case ZEND_CALLABLE_CONVERT:
+ case ZEND_CALLABLE_CONVERT_PARTIAL:
func_info->flags |= ZEND_FUNC_HAS_CALLS;
if (call_info) {
call_info->caller_call_opline = opline;
@@ -142,6 +141,7 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_USER:
+ case ZEND_SEND_PLACEHOLDER:
if (call_info) {
if (opline->op2_type == IS_CONST) {
call_info->named_args = true;
diff --git a/Zend/Optimizer/zend_call_graph.h b/Zend/Optimizer/zend_call_graph.h
index 8810dc1a560e..57c6bdffe998 100644
--- a/Zend/Optimizer/zend_call_graph.h
+++ b/Zend/Optimizer/zend_call_graph.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Call Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
diff --git a/Zend/Optimizer/zend_cfg.c b/Zend/Optimizer/zend_cfg.c
index 32d5f49ef7df..837515169d70 100644
--- a/Zend/Optimizer/zend_cfg.c
+++ b/Zend/Optimizer/zend_cfg.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, CFG - Control Flow Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -35,7 +33,6 @@ static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_bloc
zend_worklist_push(&work, b - cfg->blocks);
while (zend_worklist_len(&work)) {
- int i;
b = cfg->blocks + zend_worklist_pop(&work);
b->flags |= ZEND_BB_REACHABLE;
@@ -44,7 +41,7 @@ static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_bloc
continue;
}
- for (i = 0; i < b->successors_count; i++) {
+ for (uint32_t i = 0; i < b->successors_count; i++) {
zend_basic_block *succ = blocks + b->successors[i];
if (b->len != 0) {
@@ -104,7 +101,7 @@ static void zend_mark_reachable(zend_op *opcodes, zend_cfg *cfg, zend_basic_bloc
}
/* }}} */
-static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg, int start) /* {{{ */
+static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg, uint32_t start) /* {{{ */
{
zend_basic_block *blocks = cfg->blocks;
@@ -113,14 +110,14 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
if (op_array->last_try_catch) {
zend_basic_block *b;
- int j, changed;
+ int changed;
uint32_t *block_map = cfg->map;
do {
changed = 0;
/* Add exception paths */
- for (j = 0; j < op_array->last_try_catch; j++) {
+ for (uint32_t j = 0; j < op_array->last_try_catch; j++) {
/* check for jumps into the middle of try block */
b = blocks + block_map[op_array->try_catch_array[j].try_op];
@@ -202,7 +199,6 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
if (cfg->flags & ZEND_FUNC_FREE_LOOP_VAR) {
zend_basic_block *b;
- int j;
uint32_t *block_map = cfg->map;
/* Mark blocks that are unreachable, but free a loop var created in a reachable block. */
@@ -211,7 +207,7 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
continue;
}
- for (j = b->start; j < b->start + b->len; j++) {
+ for (uint32_t j = b->start; j < b->start + b->len; j++) {
zend_op *opline = &op_array->opcodes[j];
if (zend_optimizer_is_loop_var_free(opline)) {
zend_op *def_opline = zend_optimizer_get_loop_var_def(op_array, opline);
@@ -232,8 +228,8 @@ static void zend_mark_reachable_blocks(const zend_op_array *op_array, zend_cfg *
void zend_cfg_remark_reachable_blocks(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
{
zend_basic_block *blocks = cfg->blocks;
- int i;
- int start = 0;
+ uint32_t i;
+ uint32_t start = 0;
for (i = 0; i < cfg->blocks_count; i++) {
if (blocks[i].flags & ZEND_BB_REACHABLE) {
@@ -593,13 +589,12 @@ ZEND_API void zend_build_cfg(zend_arena **arena, const zend_op_array *op_array,
ZEND_API void zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /* {{{ */
{
- int j, s, edges;
zend_basic_block *b;
zend_basic_block *blocks = cfg->blocks;
zend_basic_block *end = blocks + cfg->blocks_count;
+ uint32_t edges = 0;
int *predecessors;
- edges = 0;
for (b = blocks; b < end; b++) {
b->predecessors_count = 0;
}
@@ -608,7 +603,7 @@ ZEND_API void zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /*
b->successors_count = 0;
b->predecessors_count = 0;
} else {
- for (s = 0; s < b->successors_count; s++) {
+ for (uint32_t s = 0; s < b->successors_count; s++) {
edges++;
blocks[b->successors[s]].predecessors_count++;
}
@@ -627,14 +622,13 @@ ZEND_API void zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /*
}
}
- for (j = 0; j < cfg->blocks_count; j++) {
+ for (uint32_t j = 0; j < cfg->blocks_count; j++) {
if (blocks[j].flags & ZEND_BB_REACHABLE) {
/* SWITCH_STRING/LONG may have few identical successors */
- for (s = 0; s < blocks[j].successors_count; s++) {
+ for (uint32_t s = 0; s < blocks[j].successors_count; s++) {
int duplicate = 0;
- int p;
- for (p = 0; p < s; p++) {
+ for (uint32_t p = 0; p < s; p++) {
if (blocks[j].successors[p] == blocks[j].successors[s]) {
duplicate = 1;
break;
@@ -654,16 +648,15 @@ ZEND_API void zend_cfg_build_predecessors(zend_arena **arena, zend_cfg *cfg) /*
/* Computes a postorder numbering of the CFG */
static void compute_postnum_recursive(
- int *postnum, int *cur, const zend_cfg *cfg, int block_num) /* {{{ */
+ int *postnum, uint32_t *cur, const zend_cfg *cfg, int block_num) /* {{{ */
{
- int s;
zend_basic_block *block = &cfg->blocks[block_num];
if (postnum[block_num] != -1) {
return;
}
postnum[block_num] = -2; /* Marker for "currently visiting" */
- for (s = 0; s < block->successors_count; s++) {
+ for (uint32_t s = 0; s < block->successors_count; s++) {
compute_postnum_recursive(postnum, cur, cfg, block->successors[s]);
}
postnum[block_num] = (*cur)++;
@@ -675,8 +668,9 @@ static void compute_postnum_recursive(
ZEND_API void zend_cfg_compute_dominators_tree(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
{
zend_basic_block *blocks = cfg->blocks;
- int blocks_count = cfg->blocks_count;
- int j, k, changed;
+ uint32_t blocks_count = cfg->blocks_count;
+ uint32_t j;
+ int changed;
if (cfg->blocks_count == 1) {
blocks[0].level = 0;
@@ -700,7 +694,7 @@ ZEND_API void zend_cfg_compute_dominators_tree(const zend_op_array *op_array, ze
if ((blocks[j].flags & ZEND_BB_REACHABLE) == 0) {
continue;
}
- for (k = 0; k < blocks[j].predecessors_count; k++) {
+ for (uint32_t k = 0; k < blocks[j].predecessors_count; k++) {
int pred = cfg->predecessors[blocks[j].predecessor_offset + k];
if (blocks[pred].idom >= 0) {
@@ -776,7 +770,7 @@ static bool dominates(zend_basic_block *blocks, int a, int b) /* {{{ */
ZEND_API void zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *cfg) /* {{{ */
{
- int i, j, k, n;
+ int i, j, n;
int time;
zend_basic_block *blocks = cfg->blocks;
int *entry_times, *exit_times;
@@ -890,7 +884,7 @@ ZEND_API void zend_cfg_identify_loops(const zend_op_array *op_array, zend_cfg *c
continue;
}
blocks[j].loop_header = i;
- for (k = 0; k < blocks[j].predecessors_count; k++) {
+ for (uint32_t k = 0; k < blocks[j].predecessors_count; k++) {
zend_worklist_push(&work, cfg->predecessors[blocks[j].predecessor_offset + k]);
}
}
diff --git a/Zend/Optimizer/zend_cfg.h b/Zend/Optimizer/zend_cfg.h
index 1f3885f511f5..3ed635ecdedc 100644
--- a/Zend/Optimizer/zend_cfg.h
+++ b/Zend/Optimizer/zend_cfg.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, CFG - Control Flow Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -44,8 +42,8 @@ typedef struct _zend_basic_block {
uint32_t flags;
uint32_t start; /* first opcode number */
uint32_t len; /* number of opcodes */
- int successors_count; /* number of successors */
- int predecessors_count; /* number of predecessors */
+ uint32_t successors_count; /* number of successors */
+ uint32_t predecessors_count; /* number of predecessors */
int predecessor_offset; /* offset of 1-st predecessor, or -1 */
int idom; /* immediate dominator block, or -1 */
int loop_header; /* closest loop header, or -1 */
@@ -82,8 +80,8 @@ typedef struct _zend_basic_block {
*/
typedef struct _zend_cfg {
- int blocks_count; /* number of basic blocks */
- int edges_count; /* number of edges */
+ uint32_t blocks_count; /* number of basic blocks */
+ uint32_t edges_count; /* number of edges */
zend_basic_block *blocks; /* array of basic blocks */
int *predecessors;
uint32_t *map;
diff --git a/Zend/Optimizer/zend_dfg.c b/Zend/Optimizer/zend_dfg.c
index c14d67e873a2..72e15693a844 100644
--- a/Zend/Optimizer/zend_dfg.c
+++ b/Zend/Optimizer/zend_dfg.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, DFG - Data Flow Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -253,10 +251,9 @@ void zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const ze
{
uint32_t set_size = dfg->size;
zend_basic_block *blocks = cfg->blocks;
- int blocks_count = cfg->blocks_count;
+ uint32_t blocks_count = cfg->blocks_count;
zend_bitset tmp, def, use, in, out;
- int k;
- int j;
+ uint32_t j;
tmp = dfg->tmp;
def = dfg->def;
@@ -305,7 +302,7 @@ void zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const ze
}
if (blocks[j].successors_count != 0) {
zend_bitset_copy(DFG_BITSET(out, set_size, j), DFG_BITSET(in, set_size, blocks[j].successors[0]), set_size);
- for (k = 1; k < blocks[j].successors_count; k++) {
+ for (uint32_t k = 1; k < blocks[j].successors_count; k++) {
zend_bitset_union(DFG_BITSET(out, set_size, j), DFG_BITSET(in, set_size, blocks[j].successors[k]), set_size);
}
} else {
@@ -318,7 +315,7 @@ void zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const ze
/* Add predecessors of changed block to worklist */
{
const int *predecessors = &cfg->predecessors[blocks[j].predecessor_offset];
- for (k = 0; k < blocks[j].predecessors_count; k++) {
+ for (uint32_t k = 0; k < blocks[j].predecessors_count; k++) {
zend_bitset_incl(worklist, predecessors[k]);
}
}
diff --git a/Zend/Optimizer/zend_dfg.h b/Zend/Optimizer/zend_dfg.h
index af3d761ba29f..464ea6bb2ebe 100644
--- a/Zend/Optimizer/zend_dfg.h
+++ b/Zend/Optimizer/zend_dfg.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, DFG - Data Flow Graph |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
diff --git a/Zend/Optimizer/zend_dump.c b/Zend/Optimizer/zend_dump.c
index 16cb75188242..c6cc5193b2dc 100644
--- a/Zend/Optimizer/zend_dump.c
+++ b/Zend/Optimizer/zend_dump.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Bytecode Visualisation |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -25,7 +23,7 @@
#include "zend_dump.h"
#include "zend_smart_str.h"
-void zend_dump_ht(HashTable *ht)
+void zend_dump_ht(const HashTable *ht)
{
zend_ulong index;
zend_string *key;
@@ -190,7 +188,7 @@ static void zend_dump_range(const zend_ssa_range *r)
}
}
-static void zend_dump_type_info(uint32_t info, zend_class_entry *ce, int is_instanceof, uint32_t dump_flags)
+static void zend_dump_type_info(uint32_t info, const zend_class_entry *ce, bool is_instanceof, uint32_t dump_flags)
{
bool first = true;
@@ -482,7 +480,7 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
}
if (ZEND_OP_IS_FRAMELESS_ICALL(opline->opcode)) {
- zend_function *func = ZEND_FLF_FUNC(opline);
+ const zend_function *func = ZEND_FLF_FUNC(opline);
fprintf(stderr, "(%s)", ZSTR_VAL(func->common.function_name));
}
@@ -674,13 +672,13 @@ ZEND_API void zend_dump_op(const zend_op_array *op_array, const zend_basic_block
}
if (opline->op2_type == IS_CONST) {
- zval *op = CRT_CONSTANT(opline->op2);
+ const zval *op = CRT_CONSTANT(opline->op2);
if (
opline->opcode == ZEND_SWITCH_LONG
|| opline->opcode == ZEND_SWITCH_STRING
|| opline->opcode == ZEND_MATCH
) {
- HashTable *jumptable = Z_ARRVAL_P(op);
+ const HashTable *jumptable = Z_ARRVAL_P(op);
zend_string *key;
zend_ulong num_key;
zval *zv;
@@ -778,7 +776,7 @@ ZEND_API void zend_dump_op_line(const zend_op_array *op_array, const zend_basic_
{
int len = 0;
const zend_ssa *ssa = NULL;
- zend_ssa_op *ssa_op = NULL;
+ const zend_ssa_op *ssa_op = NULL;
if (dump_flags & ZEND_DUMP_LINE_NUMBERS) {
fprintf(stderr, "L%04u ", opline->lineno);
@@ -800,7 +798,7 @@ ZEND_API void zend_dump_op_line(const zend_op_array *op_array, const zend_basic_
static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags)
{
- zend_basic_block *b = cfg->blocks + n;
+ const zend_basic_block *b = cfg->blocks + n;
if (n > 0) {
fprintf(stderr, "\n");
@@ -856,8 +854,8 @@ static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags
fprintf(stderr, "\n");
if (b->predecessors_count) {
- int *p = cfg->predecessors + b->predecessor_offset;
- int *end = p + b->predecessors_count;
+ const int *p = cfg->predecessors + b->predecessor_offset;
+ const int *end = p + b->predecessors_count;
fprintf(stderr, " ; from=(BB%d", *p);
for (p++; p < end; p++) {
@@ -867,9 +865,8 @@ static void zend_dump_block_info(const zend_cfg *cfg, int n, uint32_t dump_flags
}
if (b->successors_count > 0) {
- int s;
fprintf(stderr, " ; to=(BB%d", b->successors[0]);
- for (s = 1; s < b->successors_count; s++) {
+ for (uint32_t s = 1; s < b->successors_count; s++) {
fprintf(stderr, ", BB%d", b->successors[s]);
}
fprintf(stderr, ")\n");
@@ -900,16 +897,14 @@ static void zend_dump_block_header(const zend_cfg *cfg, const zend_op_array *op_
{
zend_dump_block_info(cfg, n, dump_flags);
if (ssa && ssa->blocks && ssa->blocks[n].phis) {
- zend_ssa_phi *p = ssa->blocks[n].phis;
+ const zend_ssa_phi *p = ssa->blocks[n].phis;
do {
- int j;
-
fprintf(stderr, " ");
zend_dump_ssa_var(op_array, ssa, p->ssa_var, 0, p->var, dump_flags);
if (p->pi < 0) {
fprintf(stderr, " = Phi(");
- for (j = 0; j < cfg->blocks[n].predecessors_count; j++) {
+ for (uint32_t j = 0; j < cfg->blocks[n].predecessors_count; j++) {
if (j > 0) {
fprintf(stderr, ", ");
}
@@ -949,7 +944,7 @@ ZEND_API void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_fl
{
const zend_cfg *cfg = NULL;
const zend_ssa *ssa = NULL;
- zend_func_info *func_info = NULL;
+ const zend_func_info *func_info = NULL;
uint32_t func_flags = 0;
if (dump_flags & (ZEND_DUMP_CFG|ZEND_DUMP_SSA)) {
@@ -1040,11 +1035,8 @@ ZEND_API void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_fl
}
if (cfg) {
- int n;
- zend_basic_block *b;
-
- for (n = 0; n < cfg->blocks_count; n++) {
- b = cfg->blocks + n;
+ for (uint32_t n = 0; n < cfg->blocks_count; n++) {
+ const zend_basic_block *b = cfg->blocks + n;
if (!(dump_flags & ZEND_DUMP_HIDE_UNREACHABLE) || (b->flags & ZEND_BB_REACHABLE)) {
const zend_op *opline;
const zend_op *end;
@@ -1180,13 +1172,11 @@ ZEND_API void zend_dump_op_array(const zend_op_array *op_array, uint32_t dump_fl
void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg)
{
- int j;
-
fprintf(stderr, "\nDOMINATORS-TREE for \"");
zend_dump_op_array_name(op_array);
fprintf(stderr, "\"\n");
- for (j = 0; j < cfg->blocks_count; j++) {
- zend_basic_block *b = cfg->blocks + j;
+ for (uint32_t j = 0; j < cfg->blocks_count; j++) {
+ const zend_basic_block *b = cfg->blocks + j;
if (b->flags & ZEND_BB_REACHABLE) {
zend_dump_block_info(cfg, j, 0);
}
@@ -1195,14 +1185,12 @@ void zend_dump_dominators(const zend_op_array *op_array, const zend_cfg *cfg)
void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa, uint32_t dump_flags)
{
- int j;
-
if (ssa->vars) {
fprintf(stderr, "\nSSA Variable for \"");
zend_dump_op_array_name(op_array);
fprintf(stderr, "\"\n");
- for (j = 0; j < ssa->vars_count; j++) {
+ for (int j = 0; j < ssa->vars_count; j++) {
fprintf(stderr, " ");
zend_dump_ssa_var(op_array, ssa, j, IS_CV, ssa->vars[j].var, dump_flags);
if (ssa->vars[j].scc >= 0) {
@@ -1221,10 +1209,9 @@ void zend_dump_ssa_variables(const zend_op_array *op_array, const zend_ssa *ssa,
static void zend_dump_var_set(const zend_op_array *op_array, const char *name, zend_bitset set)
{
bool first = true;
- uint32_t i;
fprintf(stderr, " ; %s = {", name);
- for (i = 0; i < op_array->last_var + op_array->T; i++) {
+ for (uint32_t i = 0; i < op_array->last_var + op_array->T; i++) {
if (zend_bitset_in(set, i)) {
if (first) {
first = false;
@@ -1239,12 +1226,11 @@ static void zend_dump_var_set(const zend_op_array *op_array, const char *name, z
void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zend_dfg *dfg)
{
- int j;
fprintf(stderr, "\nVariable Liveness for \"");
zend_dump_op_array_name(op_array);
fprintf(stderr, "\"\n");
- for (j = 0; j < cfg->blocks_count; j++) {
+ for (uint32_t j = 0; j < cfg->blocks_count; j++) {
fprintf(stderr, " BB%d:\n", j);
zend_dump_var_set(op_array, "def", DFG_BITSET(dfg->def, dfg->size, j));
zend_dump_var_set(op_array, "use", DFG_BITSET(dfg->use, dfg->size, j));
@@ -1255,17 +1241,16 @@ void zend_dump_dfg(const zend_op_array *op_array, const zend_cfg *cfg, const zen
void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa)
{
- int j;
- zend_ssa_block *ssa_blocks = ssa->blocks;
- int blocks_count = ssa->cfg.blocks_count;
+ const zend_ssa_block *ssa_blocks = ssa->blocks;
+ uint32_t blocks_count = ssa->cfg.blocks_count;
fprintf(stderr, "\nSSA Phi() Placement for \"");
zend_dump_op_array_name(op_array);
fprintf(stderr, "\"\n");
- for (j = 0; j < blocks_count; j++) {
+ for (uint32_t j = 0; j < blocks_count; j++) {
if (ssa_blocks && ssa_blocks[j].phis) {
- zend_ssa_phi *p = ssa_blocks[j].phis;
- int first = 1;
+ const zend_ssa_phi *p = ssa_blocks[j].phis;
+ bool first = true;
fprintf(stderr, " BB%d:\n", j);
if (p->pi >= 0) {
@@ -1275,7 +1260,7 @@ void zend_dump_phi_placement(const zend_op_array *op_array, const zend_ssa *ssa)
}
do {
if (first) {
- first = 0;
+ first = false;
} else {
fprintf(stderr, ", ");
}
diff --git a/Zend/Optimizer/zend_dump.h b/Zend/Optimizer/zend_dump.h
index db8f762bfc49..45740d9609cb 100644
--- a/Zend/Optimizer/zend_dump.h
+++ b/Zend/Optimizer/zend_dump.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Bytecode Visualisation |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -44,7 +42,7 @@ ZEND_API void zend_dump_ssa_var(const zend_op_array *op_array, const zend_ssa *s
ZEND_API void zend_dump_var(const zend_op_array *op_array, uint8_t var_type, uint32_t var_num);
void zend_dump_op_array_name(const zend_op_array *op_array);
void zend_dump_const(const zval *zv);
-void zend_dump_ht(HashTable *ht);
+void zend_dump_ht(const HashTable *ht);
END_EXTERN_C()
diff --git a/Zend/Optimizer/zend_func_info.c b/Zend/Optimizer/zend_func_info.c
index cec52f7e9860..185fcc40be67 100644
--- a/Zend/Optimizer/zend_func_info.c
+++ b/Zend/Optimizer/zend_func_info.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Func Info |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
| Xinchen Hui |
diff --git a/Zend/Optimizer/zend_func_info.h b/Zend/Optimizer/zend_func_info.h
index db00d843ee10..6b5b51cac58b 100644
--- a/Zend/Optimizer/zend_func_info.h
+++ b/Zend/Optimizer/zend_func_info.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, Func Info |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
diff --git a/Zend/Optimizer/zend_func_infos.h b/Zend/Optimizer/zend_func_infos.h
index b7b118c710c5..2a9353c9b86e 100644
--- a/Zend/Optimizer/zend_func_infos.h
+++ b/Zend/Optimizer/zend_func_infos.h
@@ -596,6 +596,7 @@ static const func_info_t func_infos[] = {
F1("stream_get_line", MAY_BE_STRING|MAY_BE_FALSE),
F1("stream_resolve_include_path", MAY_BE_STRING|MAY_BE_FALSE),
F1("stream_get_wrappers", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
+ FN("stream_last_errors", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_OBJECT),
F1("stream_get_transports", MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING),
#if defined(HAVE_GETTIMEOFDAY)
F1("uniqid", MAY_BE_STRING),
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
index 6963fc63dd15..2e6cc70ec254 100644
--- a/Zend/Optimizer/zend_inference.c
+++ b/Zend/Optimizer/zend_inference.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, e-SSA based Type & Range Inference |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -493,7 +491,7 @@ ZEND_API void zend_ssa_find_false_dependencies(const zend_op_array *op_array, co
const zend_ssa_op *ssa_ops = ssa->ops;
int ssa_vars_count = ssa->vars_count;
zend_bitset worklist;
- int i, j, use;
+ int i, use;
const zend_ssa_phi *p;
ALLOCA_FLAG(use_heap);
@@ -527,7 +525,7 @@ ZEND_API void zend_ssa_find_false_dependencies(const zend_op_array *op_array, co
zend_bitset_incl(worklist, p->sources[0]);
}
} else {
- for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ for (uint32_t j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
ZEND_ASSERT(p->sources[j] >= 0);
if (ssa->vars[p->sources[j]].no_val) {
ssa_vars[p->sources[j]].no_val = 0; /* used indirectly */
@@ -1063,7 +1061,7 @@ static bool zend_inference_calc_binary_op_range(
case ZEND_BW_XOR:
// TODO
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
return 0;
}
@@ -1076,7 +1074,6 @@ static bool zend_inference_calc_range(const zend_op_array *op_array, const zend_
if (ssa->vars[var].definition_phi) {
const zend_ssa_phi *p = ssa->vars[var].definition_phi;
- int i;
tmp->underflow = 0;
tmp->min = ZEND_LONG_MAX;
@@ -1222,7 +1219,7 @@ static bool zend_inference_calc_range(const zend_op_array *op_array, const zend_
}
}
} else {
- for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
+ for (uint32_t i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
ZEND_ASSERT(p->sources[i] >= 0);
if (ssa->var_info[p->sources[i]].has_range) {
/* union */
@@ -2351,7 +2348,7 @@ static uint32_t binary_op_result_type(
/* TODO: +MAY_BE_OBJECT ??? */
tmp = MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN;
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
return tmp;
}
@@ -3709,7 +3706,7 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_FREE:
/* This may happen if the using opcode is DCEd. */
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
j = zend_ssa_next_use(ssa->ops, ssa_op->result_def, j);
if (j >= 0) {
@@ -3906,6 +3903,7 @@ static zend_always_inline zend_result _zend_update_type_info(
}
break;
case ZEND_CALLABLE_CONVERT:
+ case ZEND_CALLABLE_CONVERT_PARTIAL:
UPDATE_SSA_TYPE(MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN, ssa_op->result_def);
UPDATE_SSA_OBJ_TYPE(zend_ce_closure, /* is_instanceof */ false, ssa_op->result_def);
break;
@@ -4192,7 +4190,7 @@ static zend_result zend_infer_types_ex(const zend_op_array *op_array, const zend
zend_ssa_var *ssa_vars = ssa->vars;
zend_ssa_var_info *ssa_var_info = ssa->var_info;
int ssa_vars_count = ssa->vars_count;
- int i, j;
+ int j;
uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
bool update_worklist = 1;
const zend_op **ssa_opcodes = NULL;
@@ -4236,6 +4234,7 @@ static zend_result zend_infer_types_ex(const zend_op_array *op_array, const zend
bool first = true;
bool is_instanceof = false;
zend_class_entry *ce = NULL;
+ uint32_t i;
tmp = 0;
for (i = 0; i < blocks[p->block].predecessors_count; i++) {
@@ -4261,7 +4260,7 @@ static zend_result zend_infer_types_ex(const zend_op_array *op_array, const zend
UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j);
}
} else if (ssa_vars[j].definition >= 0) {
- i = ssa_vars[j].definition;
+ int i = ssa_vars[j].definition;
if (_zend_update_type_info(op_array, ssa, script, worklist, op_array->opcodes + i, ssa->ops + i, NULL, optimization_level, true) == FAILURE) {
return FAILURE;
}
@@ -4562,9 +4561,8 @@ static void zend_func_return_info(const zend_op_array *op_array,
{
const zend_func_info *info = ZEND_FUNC_INFO(op_array);
const zend_ssa *ssa = &info->ssa;
- int blocks_count = info->ssa.cfg.blocks_count;
+ uint32_t blocks_count = info->ssa.cfg.blocks_count;
const zend_basic_block *blocks = info->ssa.cfg.blocks;
- int j;
uint32_t t1;
uint32_t tmp = 0;
zend_class_entry *tmp_ce = NULL;
@@ -4589,7 +4587,7 @@ static void zend_func_return_info(const zend_op_array *op_array,
| MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF;
}
- for (j = 0; j < blocks_count; j++) {
+ for (uint32_t j = 0; j < blocks_count; j++) {
if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
@@ -5295,10 +5293,10 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
return (t1 & MAY_BE_OBJECT);
case IS_OBJECT:
return 0;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
/* GCC is getting confused here for the Wimplicit-fallthrough warning with
- * EMPTY_SWITCH_DEFAULT_CASE() macro */
+ * default: ZEND_UNREACHABLE(); macro */
return 0;
case ZEND_ARRAY_KEY_EXISTS:
if ((t2 & MAY_BE_ANY) != MAY_BE_ARRAY) {
diff --git a/Zend/Optimizer/zend_inference.h b/Zend/Optimizer/zend_inference.h
index 1b626fa2ee22..e6b4207c7494 100644
--- a/Zend/Optimizer/zend_inference.h
+++ b/Zend/Optimizer/zend_inference.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, e-SSA based Type & Range Inference |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c
index f8cbefdaaf2b..0173d2ef47a2 100644
--- a/Zend/Optimizer/zend_optimizer.c
+++ b/Zend/Optimizer/zend_optimizer.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -106,9 +104,7 @@ zend_result zend_optimizer_eval_special_func_call(
zval *result, const zend_string *name, zend_string *arg) {
if (zend_string_equals_literal(name, "function_exists") ||
zend_string_equals_literal(name, "is_callable")) {
- zend_string *lc_name = zend_string_tolower(arg);
- const zend_internal_function *func = zend_hash_find_ptr(EG(function_table), lc_name);
- zend_string_release_ex(lc_name, 0);
+ const zend_internal_function *func = zend_hash_find_ptr_lc(EG(function_table), arg);
if (func && func->type == ZEND_INTERNAL_FUNCTION
&& func->module->type == MODULE_PERSISTENT
@@ -122,9 +118,7 @@ zend_result zend_optimizer_eval_special_func_call(
return FAILURE;
}
if (zend_string_equals_literal(name, "extension_loaded")) {
- zend_string *lc_name = zend_string_tolower(arg);
- zend_module_entry *m = zend_hash_find_ptr(&module_registry, lc_name);
- zend_string_release_ex(lc_name, 0);
+ zend_module_entry *m = zend_hash_find_ptr_lc(&module_registry, arg);
if (!m) {
if (PG(enable_dl)) {
@@ -782,7 +776,7 @@ static bool zend_optimizer_ignore_class(zval *ce_zv, const zend_string *filename
if (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE) {
return true;
}
- const Bucket *ce_bucket = (const Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val));
+ const Bucket *ce_bucket = ZEND_CONTAINER_OF(ce_zv, Bucket, val);
size_t offset = ce_bucket - EG(class_table)->arData;
if (offset < EG(persistent_classes_count)) {
return false;
@@ -803,7 +797,7 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, const zend_string *file
if (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE) {
return true;
}
- const Bucket *fbc_bucket = (const Bucket*)((uintptr_t)fbc_zv - XtOffsetOf(Bucket, val));
+ const Bucket *fbc_bucket = ZEND_CONTAINER_OF(fbc_zv, Bucket, val);
size_t offset = fbc_bucket - EG(function_table)->arData;
if (offset < EG(persistent_functions_count)) {
return false;
@@ -945,7 +939,7 @@ zend_function *zend_optimizer_get_called_func(
if (ce) {
zend_string *func_name = Z_STR_P(CRT_CONSTANT(opline->op2) + 1);
zend_function *fbc = zend_hash_find_ptr(&ce->function_table, func_name);
- if (fbc) {
+ if (fbc && !(fbc->common.fn_flags & ZEND_ACC_ABSTRACT)) {
bool is_public = (fbc->common.fn_flags & ZEND_ACC_PUBLIC) != 0;
bool same_scope = fbc->common.scope == op_array->scope;
if (is_public || same_scope) {
diff --git a/Zend/Optimizer/zend_optimizer.h b/Zend/Optimizer/zend_optimizer.h
index 43a0f60a2321..d2847c92869c 100644
--- a/Zend/Optimizer/zend_optimizer.h
+++ b/Zend/Optimizer/zend_optimizer.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
diff --git a/Zend/Optimizer/zend_optimizer_internal.h b/Zend/Optimizer/zend_optimizer_internal.h
index 869275811e3d..d01df56260bc 100644
--- a/Zend/Optimizer/zend_optimizer_internal.h
+++ b/Zend/Optimizer/zend_optimizer_internal.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend OPcache |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
diff --git a/Zend/Optimizer/zend_ssa.c b/Zend/Optimizer/zend_ssa.c
index 99978da9d7e8..fa2901b89421 100644
--- a/Zend/Optimizer/zend_ssa.c
+++ b/Zend/Optimizer/zend_ssa.c
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, SSA - Static Single Assignment Form |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
| Nikita Popov |
@@ -35,8 +33,7 @@ static bool dominates(const zend_basic_block *blocks, int a, int b) {
static bool will_rejoin(
const zend_cfg *cfg, const zend_dfg *dfg, const zend_basic_block *block,
int other_successor, int exclude, int var) {
- int i;
- for (i = 0; i < block->predecessors_count; i++) {
+ for (uint32_t i = 0; i < block->predecessors_count; i++) {
int predecessor = cfg->predecessors[block->predecessor_offset + i];
if (predecessor == exclude) {
continue;
@@ -243,8 +240,8 @@ static void place_essa_pis(
zend_arena **arena, const zend_script *script, const zend_op_array *op_array,
uint32_t build_flags, const zend_ssa *ssa, const zend_dfg *dfg) /* {{{ */ {
const zend_basic_block *blocks = ssa->cfg.blocks;
- int j, blocks_count = ssa->cfg.blocks_count;
- for (j = 0; j < blocks_count; j++) {
+ uint32_t blocks_count = ssa->cfg.blocks_count;
+ for (uint32_t j = 0; j < blocks_count; j++) {
zend_ssa_phi *pi;
zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
int bt; /* successor block number if a condition is true */
@@ -823,7 +820,6 @@ static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t bui
const zend_ssa_block *ssa_blocks = ssa->blocks;
zend_ssa_op *ssa_ops = ssa->ops;
int ssa_vars_count = ssa->vars_count;
- int i, j;
const zend_op *opline, *end;
if (ssa_blocks[n].phis) {
@@ -853,7 +849,7 @@ static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t bui
&& ((end-1)->opcode == ZEND_FE_FETCH_R || (end-1)->opcode == ZEND_FE_FETCH_RW)
&& (end-1)->op2_type == IS_CV
? &ssa_ops[blocks[n].start + blocks[n].len - 1] : NULL;
- for (i = 0; i < blocks[n].successors_count; i++) {
+ for (uint32_t i = 0; i < blocks[n].successors_count; i++) {
int succ = blocks[n].successors[i];
zend_ssa_phi *p;
for (p = ssa_blocks[succ].phis; p; p = p->next) {
@@ -867,7 +863,7 @@ static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t bui
p->constraint.range.max_ssa_var = var[p->constraint.range.max_var];
}
}
- for (j = 0; j < blocks[succ].predecessors_count; j++) {
+ for (uint32_t j = 0; j < blocks[succ].predecessors_count; j++) {
p->sources[j] = var[p->var];
}
if (p->ssa_var < 0) {
@@ -876,6 +872,7 @@ static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t bui
}
} else if (p->pi < 0) {
/* Normal Phi */
+ uint32_t j;
for (j = 0; j < blocks[succ].predecessors_count; j++)
if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) {
break;
@@ -893,6 +890,7 @@ static void zend_ssa_rename_in_block(const zend_op_array *op_array, uint32_t bui
zend_ssa_phi *q = p->next;
while (q) {
if (q->pi < 0 && q->var == p->var) {
+ uint32_t j;
for (j = 0; j < blocks[succ].predecessors_count; j++) {
if (ssa->cfg.predecessors[blocks[succ].predecessor_offset + j] == n) {
break;
@@ -997,11 +995,11 @@ ZEND_API zend_result zend_build_ssa(zend_arena **arena, const zend_script *scrip
{
const zend_basic_block *blocks = ssa->cfg.blocks;
zend_ssa_block *ssa_blocks;
- int blocks_count = ssa->cfg.blocks_count;
+ uint32_t blocks_count = ssa->cfg.blocks_count;
uint32_t set_size;
zend_bitset def, in, phi;
int *var = NULL;
- int i, j, k, changed;
+ int i, j, changed;
zend_dfg dfg;
ALLOCA_FLAG(dfg_use_heap)
ALLOCA_FLAG(var_use_heap)
@@ -1056,7 +1054,7 @@ ZEND_API zend_result zend_build_ssa(zend_arena **arena, const zend_script *scrip
register allocator depends on this property. */
zend_bitset_union(phi_j, in + (j * set_size), set_size);
} else {
- for (k = 0; k < blocks[j].predecessors_count; k++) {
+ for (uint32_t k = 0; k < blocks[j].predecessors_count; k++) {
i = ssa->cfg.predecessors[blocks[j].predecessor_offset + k];
while (i != -1 && i != blocks[j].idom) {
zend_bitset_union_with_intersection(
@@ -1223,9 +1221,7 @@ ZEND_API void zend_ssa_compute_use_def_chains(zend_arena **arena, const zend_op_
}
}
} else {
- int j;
-
- for (j = 0; j < ssa->cfg.blocks[i].predecessors_count; j++) {
+ for (uint32_t j = 0; j < ssa->cfg.blocks[i].predecessors_count; j++) {
zend_ssa_phi *p;
ZEND_ASSERT(phi->sources[j] >= 0);
@@ -1374,8 +1370,7 @@ static inline zend_ssa_phi **zend_ssa_next_use_phi_ptr(const zend_ssa *ssa, int
if (p->pi >= 0) {
return &p->use_chains[0];
} else {
- int j;
- for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ for (uint32_t j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
if (p->sources[j] == var) {
return &p->use_chains[j];
}
@@ -1438,9 +1433,9 @@ void zend_ssa_remove_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op) /* {{{ */
}
/* }}} */
-static inline void zend_ssa_remove_phi_source(const zend_ssa *ssa, const zend_ssa_phi *phi, int pred_offset, int predecessors_count) /* {{{ */
+static inline void zend_ssa_remove_phi_source(const zend_ssa *ssa, const zend_ssa_phi *phi, int pred_offset, uint32_t predecessors_count) /* {{{ */
{
- int j, var_num = phi->sources[pred_offset];
+ int var_num = phi->sources[pred_offset];
zend_ssa_phi *next_phi = phi->use_chains[pred_offset];
predecessors_count--;
@@ -1451,7 +1446,7 @@ static inline void zend_ssa_remove_phi_source(const zend_ssa *ssa, const zend_ss
/* Check if they same var is used in a different phi operand as well, in this case we don't
* need to adjust the use chain (but may have to move the next pointer). */
- for (j = 0; j < predecessors_count; j++) {
+ for (uint32_t j = 0; j < predecessors_count; j++) {
if (phi->sources[j] == var_num) {
if (j < pred_offset) {
ZEND_ASSERT(next_phi == NULL);
@@ -1485,8 +1480,8 @@ void zend_ssa_remove_uses_of_var(const zend_ssa *ssa, int var_num) /* {{{ */
zend_ssa_phi *phi;
int use;
FOREACH_PHI_USE(var, phi) {
- int i, end = NUM_PHI_SOURCES(phi);
- for (i = 0; i < end; i++) {
+ uint32_t end = NUM_PHI_SOURCES(phi);
+ for (uint32_t i = 0; i < end; i++) {
if (phi->sources[i] == var_num) {
phi->use_chains[i] = NULL;
}
@@ -1517,13 +1512,12 @@ void zend_ssa_remove_predecessor(zend_ssa *ssa, int from, int to) /* {{{ */
zend_basic_block *next_block = &ssa->cfg.blocks[to];
const zend_ssa_block *next_ssa_block = &ssa->blocks[to];
zend_ssa_phi *phi;
- int j;
/* Find at which predecessor offset this block is referenced */
int pred_offset = -1;
int *predecessors = &ssa->cfg.predecessors[next_block->predecessor_offset];
- for (j = 0; j < next_block->predecessors_count; j++) {
+ for (uint32_t j = 0; j < next_block->predecessors_count; j++) {
if (predecessors[j] == from) {
pred_offset = j;
break;
@@ -1558,7 +1552,7 @@ void zend_ssa_remove_predecessor(zend_ssa *ssa, int from, int to) /* {{{ */
}
/* }}} */
-void zend_ssa_remove_block(const zend_op_array *op_array, zend_ssa *ssa, int i) /* {{{ */
+void zend_ssa_remove_block(const zend_op_array *op_array, zend_ssa *ssa, uint32_t i) /* {{{ */
{
zend_basic_block *block = &ssa->cfg.blocks[i];
const zend_ssa_block *ssa_block = &ssa->blocks[i];
@@ -1591,7 +1585,8 @@ void zend_ssa_remove_block_from_cfg(zend_ssa *ssa, int i) /* {{{ */
{
zend_basic_block *block = &ssa->cfg.blocks[i];
int *predecessors;
- int j, s;
+ int j;
+ uint32_t s;
for (s = 0; s < block->successors_count; s++) {
zend_ssa_remove_predecessor(ssa, i, block->successors[s]);
@@ -1722,7 +1717,7 @@ void zend_ssa_rename_var_uses(zend_ssa *ssa, int old, int new, bool update_types
/* Update phi use chains */
FOREACH_PHI_USE(old_var, phi) {
- int j;
+ uint32_t j;
bool after_first_new_source = false;
/* If the phi already uses the new var, find its use chain, as we may
diff --git a/Zend/Optimizer/zend_ssa.h b/Zend/Optimizer/zend_ssa.h
index 0696d2bba867..ee9faf9bfd23 100644
--- a/Zend/Optimizer/zend_ssa.h
+++ b/Zend/Optimizer/zend_ssa.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine, SSA - Static Single Assignment Form |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
@@ -66,7 +64,7 @@ struct _zend_ssa_phi {
zend_ssa_pi_constraint constraint; /* e-SSA Pi constraint */
int var; /* Original CV, VAR or TMP variable index */
int ssa_var; /* SSA variable index */
- int block; /* current BB index */
+ uint32_t block; /* current BB index */
bool has_range_constraint;
zend_ssa_phi **use_chains;
zend_ssa_phi *sym_use_chain;
@@ -155,7 +153,7 @@ void zend_ssa_remove_defs_of_instr(zend_ssa *ssa, zend_ssa_op *ssa_op);
void zend_ssa_remove_instr(const zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op);
void zend_ssa_remove_phi(const zend_ssa *ssa, zend_ssa_phi *phi);
void zend_ssa_remove_uses_of_var(const zend_ssa *ssa, int var_num);
-void zend_ssa_remove_block(const zend_op_array *op_array, zend_ssa *ssa, int b);
+void zend_ssa_remove_block(const zend_op_array *op_array, zend_ssa *ssa, uint32_t b);
void zend_ssa_rename_var_uses(zend_ssa *ssa, int old_var, int new_var, bool update_types);
void zend_ssa_remove_block_from_cfg(zend_ssa *ssa, int b);
@@ -207,8 +205,7 @@ static zend_always_inline zend_ssa_phi* zend_ssa_next_use_phi(const zend_ssa *ss
if (p->pi >= 0) {
return p->use_chains[0];
} else {
- int j;
- for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ for (uint32_t j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
if (p->sources[j] == var) {
return p->use_chains[j];
}
@@ -285,7 +282,7 @@ static zend_always_inline void zend_ssa_rename_defs_of_instr(zend_ssa *ssa, zend
#define FOREACH_PHI_SOURCE(phi, source) do { \
zend_ssa_phi *_phi = (phi); \
- int _i, _end = NUM_PHI_SOURCES(phi); \
+ uint32_t _i, _end = NUM_PHI_SOURCES(phi); \
for (_i = 0; _i < _end; _i++) { \
ZEND_ASSERT(_phi->sources[_i] >= 0); \
source = _phi->sources[_i];
@@ -294,8 +291,7 @@ static zend_always_inline void zend_ssa_rename_defs_of_instr(zend_ssa *ssa, zend
} while (0)
#define FOREACH_PHI(phi) do { \
- int _i; \
- for (_i = 0; _i < ssa->cfg.blocks_count; _i++) { \
+ for (uint32_t _i = 0; _i < ssa->cfg.blocks_count; _i++) { \
phi = ssa->blocks[_i].phis; \
for (; phi; phi = phi->next) {
#define FOREACH_PHI_END() \
@@ -304,8 +300,7 @@ static zend_always_inline void zend_ssa_rename_defs_of_instr(zend_ssa *ssa, zend
} while (0)
#define FOREACH_BLOCK(block) do { \
- int _i; \
- for (_i = 0; _i < ssa->cfg.blocks_count; _i++) { \
+ for (uint32_t _i = 0; _i < ssa->cfg.blocks_count; _i++) { \
(block) = &ssa->cfg.blocks[_i]; \
if (!((block)->flags & ZEND_BB_REACHABLE)) { \
continue; \
diff --git a/Zend/Optimizer/zend_worklist.h b/Zend/Optimizer/zend_worklist.h
index 85e7b111d5c9..ea03048e9756 100644
--- a/Zend/Optimizer/zend_worklist.h
+++ b/Zend/Optimizer/zend_worklist.h
@@ -2,15 +2,13 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) The PHP Group |
+ | Copyright © The PHP Group and Contributors. |
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andy Wingo |
+----------------------------------------------------------------------+
diff --git a/Zend/Zend.m4 b/Zend/Zend.m4
index d0c682e8e659..5e69a97db197 100644
--- a/Zend/Zend.m4
+++ b/Zend/Zend.m4
@@ -317,6 +317,7 @@ int emu(const opcode_handler_t *ip, void *fp) {
while ((*ip)());
FP = orig_fp;
IP = orig_ip;
+ return 0;
}], [])],
[php_cv_have_global_register_vars=yes],
[php_cv_have_global_register_vars=no])
diff --git a/Zend/asm/jump_arm64_aapcs_elf_gas.S b/Zend/asm/jump_arm64_aapcs_elf_gas.S
index cefd1830d718..b8abb2ef6bc4 100644
--- a/Zend/asm/jump_arm64_aapcs_elf_gas.S
+++ b/Zend/asm/jump_arm64_aapcs_elf_gas.S
@@ -53,10 +53,29 @@
.file "jump_arm64_aapcs_elf_gas.S"
.text
+#if defined(__ARM_FEATURE_BTI_DEFAULT) && (__ARM_FEATURE_BTI_DEFAULT == 1)
+/* Mark this object as requiring BTI, enabling the linker to set BTI on the output */
+ .pushsection .note.gnu.property, "a", %note
+ .p2align 3
+ .long 4 /* namesz */
+ .long 16 /* descsz (16 bytes) */
+ .long 5 /* type = NT_GNU_PROPERTY_TYPE_0 */
+ .asciz "GNU"
+ .p2align 3
+ .long 0xc0000000 /* GNU_PROPERTY_AARCH64_FEATURE_1_AND */
+ .long 4 /* datasz = 4 */
+ .long 1 /* GNU_PROPERTY_AARCH64_FEATURE_1_BTI */
+ .long 0 /* pad to 8-byte alignment */
+ .popsection
+#endif
.align 2
.global jump_fcontext
+.hidden jump_fcontext
.type jump_fcontext, %function
jump_fcontext:
+#if defined(__ARM_FEATURE_BTI_DEFAULT) && (__ARM_FEATURE_BTI_DEFAULT == 1)
+ hint #34 /* bti c: valid indirect-entry target */
+#endif
# prepare stack for GP + FPU
sub sp, sp, #0xb0
diff --git a/Zend/asm/jump_arm64_aapcs_macho_gas.S b/Zend/asm/jump_arm64_aapcs_macho_gas.S
index 31738f745314..12cc02158685 100644
--- a/Zend/asm/jump_arm64_aapcs_macho_gas.S
+++ b/Zend/asm/jump_arm64_aapcs_macho_gas.S
@@ -52,6 +52,7 @@
*******************************************************/
.text
+.private_extern _jump_fcontext
.globl _jump_fcontext
.balign 16
_jump_fcontext:
diff --git a/Zend/asm/jump_arm64_aapcs_pe_armasm.asm b/Zend/asm/jump_arm64_aapcs_pe_armasm.asm
index 3100243d68de..afb6d2b2e703 100644
--- a/Zend/asm/jump_arm64_aapcs_pe_armasm.asm
+++ b/Zend/asm/jump_arm64_aapcs_pe_armasm.asm
@@ -45,7 +45,7 @@
;* ------------------------------------------------- *
;* | 0xa0| 0xa4| 0xa8| 0xac| 0xb0| 0xb4| 0xb8| 0xbc| *
;* ------------------------------------------------- *
-;* | fiber data| base | limit | dealloc | *
+;* | base | limit | dealloc | fiber data| *
;* ------------------------------------------------- *
;* ------------------------------------------------- *
;* | 48 | 49 | 50 | 51 | | | *
@@ -130,4 +130,4 @@ jump_fcontext proc
ret x4
ENDP
- END
\ No newline at end of file
+ END
diff --git a/Zend/asm/jump_arm_aapcs_elf_gas.S b/Zend/asm/jump_arm_aapcs_elf_gas.S
index 86efe9d82146..4415a541fd3e 100644
--- a/Zend/asm/jump_arm_aapcs_elf_gas.S
+++ b/Zend/asm/jump_arm_aapcs_elf_gas.S
@@ -41,6 +41,7 @@
.file "jump_arm_aapcs_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.align 2
.type jump_fcontext,%function
.syntax unified
@@ -48,8 +49,38 @@ jump_fcontext:
@ save LR as PC
push {lr}
@ save hidden,V1-V8,LR
+
+ #if __ARM_ARCH >= 7
+
push {a1,v1-v8,lr}
+ #else
+
+ sub sp, #40
+
+ str a1, [sp, #0]
+ str v1, [sp, #4]
+ str v2, [sp, #8]
+ str v3, [sp, #12]
+ str v4, [sp, #16]
+
+ mov v4, v5
+ str v4, [sp, #20]
+
+ mov v4, v6
+ str v4, [sp, #24]
+
+ mov v4, v7
+ str v4, [sp, #28]
+
+ mov v4, v8
+ str v4, [sp, #32]
+
+ mov v4, lr
+ str v4, [sp, #36]
+
+ #endif
+
@ prepare stack for FPU
sub sp, sp, #64
#if (defined(__VFP_FP__) && !defined(__SOFTFP__))
@@ -71,7 +102,34 @@ jump_fcontext:
add sp, sp, #64
@ restore hidden,V1-V8,LR
+ #if __ARM_ARCH >= 7
pop {a4,v1-v8,lr}
+ #else
+
+ ldr v4, [sp, #20]
+ mov v5, v4
+
+ ldr v4, [sp, #24]
+ mov v6, v4
+
+ ldr v4, [sp, #28]
+ mov v7, v4
+
+ ldr v4, [sp, #32]
+ mov v8, v4
+
+ ldr v4, [sp, #36]
+ mov lr, v4
+
+ ldr a4, [sp, #0]
+ ldr v1, [sp, #4]
+ ldr v2, [sp, #8]
+ ldr v3, [sp, #12]
+ ldr v4, [sp, #16]
+
+ add sp, #40
+
+ #endif
@ return transfer_t from jump
str a1, [a4, #0]
diff --git a/Zend/asm/jump_arm_aapcs_macho_gas.S b/Zend/asm/jump_arm_aapcs_macho_gas.S
index 077c36409e83..44e7f2a42274 100644
--- a/Zend/asm/jump_arm_aapcs_macho_gas.S
+++ b/Zend/asm/jump_arm_aapcs_macho_gas.S
@@ -39,6 +39,7 @@
*******************************************************/
.text
+.private_extern _jump_fcontext
.globl _jump_fcontext
.align 2
_jump_fcontext:
diff --git a/Zend/asm/jump_i386_ms_pe_masm.asm b/Zend/asm/jump_i386_ms_pe_masm.asm
index 7a9e848f1c3f..a0450d0532f8 100644
--- a/Zend/asm/jump_i386_ms_pe_masm.asm
+++ b/Zend/asm/jump_i386_ms_pe_masm.asm
@@ -55,17 +55,17 @@ ENDIF
mov eax, [edx]
mov [esp+018h], eax
- mov [esp+01ch], edi ; save EDI
- mov [esp+020h], esi ; save ESI
- mov [esp+024h], ebx ; save EBX
- mov [esp+028h], ebp ; save EBP
+ mov [esp+01ch], edi ; save EDI
+ mov [esp+020h], esi ; save ESI
+ mov [esp+024h], ebx ; save EBX
+ mov [esp+028h], ebp ; save EBP
; store ESP (pointing to context-data) in EAX
mov eax, esp
; firstarg of jump_fcontext() == fcontext to jump to
mov ecx, [esp+030h]
-
+
; restore ESP (pointing to context-data) from ECX
mov esp, ecx
@@ -98,10 +98,10 @@ ENDIF
mov ecx, [esp+02ch] ; restore EIP
- mov edi, [esp+01ch] ; restore EDI
- mov esi, [esp+020h] ; restore ESI
- mov ebx, [esp+024h] ; restore EBX
- mov ebp, [esp+028h] ; restore EBP
+ mov edi, [esp+01ch] ; restore EDI
+ mov esi, [esp+020h] ; restore ESI
+ mov ebx, [esp+024h] ; restore EBX
+ mov ebp, [esp+028h] ; restore EBP
; prepare stack
lea esp, [esp+030h]
diff --git a/Zend/asm/jump_i386_sysv_elf_gas.S b/Zend/asm/jump_i386_sysv_elf_gas.S
index ed83717ce2b8..d00ac4fffd42 100644
--- a/Zend/asm/jump_i386_sysv_elf_gas.S
+++ b/Zend/asm/jump_i386_sysv_elf_gas.S
@@ -31,6 +31,7 @@
.file "jump_i386_sysv_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.align 2
.type jump_fcontext,@function
jump_fcontext:
diff --git a/Zend/asm/jump_i386_sysv_macho_gas.S b/Zend/asm/jump_i386_sysv_macho_gas.S
index 8ab7c6f29c66..4cae3252d171 100644
--- a/Zend/asm/jump_i386_sysv_macho_gas.S
+++ b/Zend/asm/jump_i386_sysv_macho_gas.S
@@ -25,6 +25,7 @@
****************************************************************************************/
.text
+.private_extern _jump_fcontext
.globl _jump_fcontext
.align 2
_jump_fcontext:
@@ -54,8 +55,8 @@ _jump_fcontext:
/* return parent fcontext_t */
movl %ecx, %eax
- /* returned data is stored in EDX */
-
+ /* returned data is stored in EDX */
+
movl 0x18(%esp), %ecx /* restore EIP */
#if !defined(BOOST_USE_TSX)
diff --git a/Zend/asm/jump_loongarch64_sysv_elf_gas.S b/Zend/asm/jump_loongarch64_sysv_elf_gas.S
index 74c081e07faa..6f99e719ec00 100644
--- a/Zend/asm/jump_loongarch64_sysv_elf_gas.S
+++ b/Zend/asm/jump_loongarch64_sysv_elf_gas.S
@@ -41,6 +41,7 @@
.file "jump_loongarch64_sysv_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.align 2
.type jump_fcontext,@function
jump_fcontext:
diff --git a/Zend/asm/jump_mips32_o32_elf_gas.S b/Zend/asm/jump_mips32_o32_elf_gas.S
index f2b8034d8c5b..38d22d26f6d7 100644
--- a/Zend/asm/jump_mips32_o32_elf_gas.S
+++ b/Zend/asm/jump_mips32_o32_elf_gas.S
@@ -41,6 +41,7 @@
.file "jump_mips32_o32_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.align 2
.type jump_fcontext,@function
.ent jump_fcontext
@@ -102,13 +103,13 @@ jump_fcontext:
# adjust stack
addiu $sp, $sp, 96
-
+
# return transfer_t from jump
sw $a0, ($v0) # fctx of transfer_t
sw $a2, 4($v0) # data of transfer_t
# pass transfer_t as first arg in context function
# A0 == fctx, A1 == data
- move $a1, $a2
+ move $a1, $a2
# jump to context
jr $t9
diff --git a/Zend/asm/jump_mips64_n64_elf_gas.S b/Zend/asm/jump_mips64_n64_elf_gas.S
index e338912bb6e8..60027fe61734 100644
--- a/Zend/asm/jump_mips64_n64_elf_gas.S
+++ b/Zend/asm/jump_mips64_n64_elf_gas.S
@@ -48,6 +48,7 @@
.file "jump_mips64_n64_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.align 3
.type jump_fcontext,@function
.ent jump_fcontext
diff --git a/Zend/asm/jump_ppc32_sysv_elf_gas.S b/Zend/asm/jump_ppc32_sysv_elf_gas.S
index 48e09c935e09..e31336640065 100644
--- a/Zend/asm/jump_ppc32_sysv_elf_gas.S
+++ b/Zend/asm/jump_ppc32_sysv_elf_gas.S
@@ -53,6 +53,7 @@
.file "jump_ppc32_sysv_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.align 2
.type jump_fcontext,@function
jump_fcontext:
diff --git a/Zend/asm/jump_ppc32_sysv_macho_gas.S b/Zend/asm/jump_ppc32_sysv_macho_gas.S
index fef90c295f8c..c2a2061e8774 100644
--- a/Zend/asm/jump_ppc32_sysv_macho_gas.S
+++ b/Zend/asm/jump_ppc32_sysv_macho_gas.S
@@ -68,12 +68,13 @@
* ------------------------------------------------- *
* | 256 | | *
* ------------------------------------------------- *
- * | DATA| | *
+ * | DATA| | *
* ------------------------------------------------- *
* *
*******************************************************/
.text
+.private_extern _jump_fcontext
.globl _jump_fcontext
.align 2
_jump_fcontext:
@@ -193,7 +194,7 @@ _jump_fcontext:
; adjust stack
addi r1, r1, 244
- ; return transfer_t
+ ; return transfer_t
stw r6, 0(r3)
stw r5, 4(r3)
diff --git a/Zend/asm/jump_ppc64_sysv_elf_gas.S b/Zend/asm/jump_ppc64_sysv_elf_gas.S
index 28907db32b8c..debeb3466404 100644
--- a/Zend/asm/jump_ppc64_sysv_elf_gas.S
+++ b/Zend/asm/jump_ppc64_sysv_elf_gas.S
@@ -68,6 +68,7 @@
.file "jump_ppc64_sysv_elf_gas.S"
.globl jump_fcontext
+.hidden jump_fcontext
#if _CALL_ELF == 2
.text
.align 2
@@ -97,7 +98,7 @@ jump_fcontext:
# endif
#endif
# reserve space on stack
- subi %r1, %r1, 184
+ subi %r1, %r1, 200
#if _CALL_ELF != 2
std %r2, 0(%r1) # save TOC
@@ -133,6 +134,10 @@ jump_fcontext:
# save LR as PC
std %r0, 176(%r1)
+ # Save VS63
+ li %r31, 184
+ stvx %v31, %r1, %r31
+
# store RSP (pointing to context-data) in R6
mr %r6, %r1
@@ -145,6 +150,11 @@ jump_fcontext:
ld %r2, 0(%r1) # restore TOC
#endif
+
+ # Restore VS63
+ li %r31, 184
+ lvx %v31, %r1, %r31
+
ld %r14, 8(%r1) # restore R14
ld %r15, 16(%r1) # restore R15
ld %r16, 24(%r1) # restore R16
@@ -180,7 +190,7 @@ jump_fcontext:
mtctr %r12
# adjust stack
- addi %r1, %r1, 184
+ addi %r1, %r1, 200
#if _CALL_ELF == 2
# copy transfer_t into transfer_fn arg registers
diff --git a/Zend/asm/jump_ppc64_sysv_macho_gas.S b/Zend/asm/jump_ppc64_sysv_macho_gas.S
index dcc6c645db61..e4d8944b563d 100644
--- a/Zend/asm/jump_ppc64_sysv_macho_gas.S
+++ b/Zend/asm/jump_ppc64_sysv_macho_gas.S
@@ -68,6 +68,7 @@
.text
.align 2
+.private_extern _jump_fcontext
.globl _jump_fcontext
_jump_fcontext:
diff --git a/Zend/asm/jump_riscv64_sysv_elf_gas.S b/Zend/asm/jump_riscv64_sysv_elf_gas.S
index 5417e5d5e349..879eb7efee62 100644
--- a/Zend/asm/jump_riscv64_sysv_elf_gas.S
+++ b/Zend/asm/jump_riscv64_sysv_elf_gas.S
@@ -61,6 +61,7 @@
.text
.align 1
.global jump_fcontext
+.hidden jump_fcontext
.type jump_fcontext, %function
jump_fcontext:
# prepare stack for GP + FPU
diff --git a/Zend/asm/jump_s390x_sysv_elf_gas.S b/Zend/asm/jump_s390x_sysv_elf_gas.S
index fa71467756d2..59bc58bf370b 100644
--- a/Zend/asm/jump_s390x_sysv_elf_gas.S
+++ b/Zend/asm/jump_s390x_sysv_elf_gas.S
@@ -46,6 +46,7 @@
.text
.align 8
.global jump_fcontext
+.hidden jump_fcontext
.type jump_fcontext, @function
#define ARG_OFFSET 0
diff --git a/Zend/asm/jump_x86_64_sysv_elf_gas.S b/Zend/asm/jump_x86_64_sysv_elf_gas.S
index be264bdc2e2b..2eff59a380cb 100644
--- a/Zend/asm/jump_x86_64_sysv_elf_gas.S
+++ b/Zend/asm/jump_x86_64_sysv_elf_gas.S
@@ -44,6 +44,7 @@
.file "jump_x86_64_sysv_elf_gas.S"
.text
.globl jump_fcontext
+.hidden jump_fcontext
.type jump_fcontext,@function
.align 16
jump_fcontext:
@@ -67,14 +68,6 @@ jump_fcontext:
movq %rbx, 0x30(%rsp) /* save RBX */
movq %rbp, 0x38(%rsp) /* save RBP */
-#if BOOST_CONTEXT_SHADOW_STACK
- /* grow the stack to reserve space for shadow stack pointer(SSP) */
- leaq -0x8(%rsp), %rsp
- /* read the current SSP and store it */
- rdsspq %rcx
- movq %rcx, (%rsp)
-#endif
-
#if BOOST_CONTEXT_SHADOW_STACK
/* grow the stack to reserve space for shadow stack pointer(SSP) */
leaq -0x8(%rsp), %rsp
diff --git a/Zend/asm/jump_x86_64_sysv_macho_gas.S b/Zend/asm/jump_x86_64_sysv_macho_gas.S
index afc3e5c126f9..673daa61dfaa 100644
--- a/Zend/asm/jump_x86_64_sysv_macho_gas.S
+++ b/Zend/asm/jump_x86_64_sysv_macho_gas.S
@@ -25,6 +25,7 @@
****************************************************************************************/
.text
+.private_extern _jump_fcontext
.globl _jump_fcontext
.align 8
_jump_fcontext:
diff --git a/Zend/asm/make_arm64_aapcs_elf_gas.S b/Zend/asm/make_arm64_aapcs_elf_gas.S
index 66cfb2da17ea..246525402f5c 100644
--- a/Zend/asm/make_arm64_aapcs_elf_gas.S
+++ b/Zend/asm/make_arm64_aapcs_elf_gas.S
@@ -53,10 +53,29 @@
.file "make_arm64_aapcs_elf_gas.S"
.text
+#if defined(__ARM_FEATURE_BTI_DEFAULT) && (__ARM_FEATURE_BTI_DEFAULT == 1)
+/* Mark this object as requiring BTI, enabling the linker to set BTI on the output */
+ .pushsection .note.gnu.property, "a", %note
+ .p2align 3
+ .long 4 /* namesz */
+ .long 16 /* descsz (16 bytes) */
+ .long 5 /* type = NT_GNU_PROPERTY_TYPE_0 */
+ .asciz "GNU"
+ .p2align 3
+ .long 0xc0000000 /* GNU_PROPERTY_AARCH64_FEATURE_1_AND */
+ .long 4 /* datasz = 4 */
+ .long 1 /* GNU_PROPERTY_AARCH64_FEATURE_1_BTI */
+ .long 0 /* pad to 8-byte alignment */
+ .popsection
+#endif
.align 2
.global make_fcontext
+.hidden make_fcontext
.type make_fcontext, %function
make_fcontext:
+#if defined(__ARM_FEATURE_BTI_DEFAULT) && (__ARM_FEATURE_BTI_DEFAULT == 1)
+ hint #34 /* bti c */
+#endif
# shift address in x0 (allocated stack) to lower 16 byte boundary
and x0, x0, ~0xF
diff --git a/Zend/asm/make_arm64_aapcs_macho_gas.S b/Zend/asm/make_arm64_aapcs_macho_gas.S
index b30b1e3e5bbb..a6a1314c0dcd 100644
--- a/Zend/asm/make_arm64_aapcs_macho_gas.S
+++ b/Zend/asm/make_arm64_aapcs_macho_gas.S
@@ -52,6 +52,7 @@
*******************************************************/
.text
+.private_extern _make_fcontext
.globl _make_fcontext
.balign 16
diff --git a/Zend/asm/make_arm64_aapcs_pe_armasm.asm b/Zend/asm/make_arm64_aapcs_pe_armasm.asm
index 50f9b69876be..c384af039fc4 100644
--- a/Zend/asm/make_arm64_aapcs_pe_armasm.asm
+++ b/Zend/asm/make_arm64_aapcs_pe_armasm.asm
@@ -73,7 +73,7 @@ make_fcontext proc
; save top address of context_stack as 'base'
str x3, [x0, #0xa0]
- ; save bottom address of context-stack as 'limit' and 'dealloction stack'
+ ; save bottom address of context-stack as 'limit' and 'deallocation stack'
sub x3, x3, x1
stp x3, x3, [x0, #0xa8]
; save 0 as 'fiber data'
diff --git a/Zend/asm/make_arm_aapcs_elf_gas.S b/Zend/asm/make_arm_aapcs_elf_gas.S
index 98ae64b43f9d..2367c75a6a77 100644
--- a/Zend/asm/make_arm_aapcs_elf_gas.S
+++ b/Zend/asm/make_arm_aapcs_elf_gas.S
@@ -41,26 +41,47 @@
.file "make_arm_aapcs_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.align 2
.type make_fcontext,%function
.syntax unified
make_fcontext:
@ shift address in A1 to lower 16 byte boundary
+
+ #if __ARM_ARCH >= 7
bic a1, a1, #15
+ #else
+ lsrs a1, a1, #4
+ lsls a1, a1, #4
+ #endif
@ reserve space for context-data on context-stack
+ #if __ARM_ARCH >= 7
sub a1, a1, #124
+ #else
+ subs a1, #124
+ #endif
@ third arg of make_fcontext() == address of context-function
str a3, [a1, #104]
@ compute address of returned transfer_t
+ #if __ARM_ARCH >= 7
add a2, a1, #108
+ #else
+ mov a2, a1
+ adds a2, #108
+ #endif
mov a3, a2
str a3, [a1, #64]
+
@ compute abs address of label finish
+ #if __ARM_ARCH >= 7
adr a2, finish
+ #else
+ ldr a2, =finish
+ #endif
@ save address of finish as return-address for context-function
@ will be entered after context-function returns
str a2, [a1, #100]
@@ -72,7 +93,13 @@ make_fcontext:
finish:
@ exit code is zero
+ #if __ARM_ARCH >=7
mov a1, #0
+ #else
+ movs r3, #0
+ movs r0, r3
+ #endif
+
@ exit application
bl _exit@PLT
.size make_fcontext,.-make_fcontext
diff --git a/Zend/asm/make_arm_aapcs_macho_gas.S b/Zend/asm/make_arm_aapcs_macho_gas.S
index c909ae9d43a2..de934075d247 100644
--- a/Zend/asm/make_arm_aapcs_macho_gas.S
+++ b/Zend/asm/make_arm_aapcs_macho_gas.S
@@ -39,6 +39,7 @@
*******************************************************/
.text
+.private_extern _make_fcontext
.globl _make_fcontext
.align 2
_make_fcontext:
diff --git a/Zend/asm/make_i386_ms_pe_masm.asm b/Zend/asm/make_i386_ms_pe_masm.asm
index 5246465cb975..3879396390f2 100644
--- a/Zend/asm/make_i386_ms_pe_masm.asm
+++ b/Zend/asm/make_i386_ms_pe_masm.asm
@@ -53,7 +53,7 @@ make_fcontext PROC BOOST_CONTEXT_EXPORT
mov [eax+014h], ecx
; second arg of make_fcontext() == size of context-stack
mov edx, [esp+08h]
- ; negate stack size for LEA instruction (== substraction)
+ ; negate stack size for LEA instruction (== subtraction)
neg edx
; compute bottom address of context stack (limit)
lea ecx, [ecx+edx]
diff --git a/Zend/asm/make_i386_sysv_elf_gas.S b/Zend/asm/make_i386_sysv_elf_gas.S
index c6e0b36558a6..992bae019af2 100644
--- a/Zend/asm/make_i386_sysv_elf_gas.S
+++ b/Zend/asm/make_i386_sysv_elf_gas.S
@@ -31,6 +31,7 @@
.file "make_i386_sysv_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.align 2
.type make_fcontext,@function
make_fcontext:
diff --git a/Zend/asm/make_i386_sysv_macho_gas.S b/Zend/asm/make_i386_sysv_macho_gas.S
index 519e406248bb..01076129bcf5 100644
--- a/Zend/asm/make_i386_sysv_macho_gas.S
+++ b/Zend/asm/make_i386_sysv_macho_gas.S
@@ -25,6 +25,7 @@
****************************************************************************************/
.text
+.private_extern _make_fcontext
.globl _make_fcontext
.align 2
_make_fcontext:
@@ -69,7 +70,7 @@ _make_fcontext:
addl $finish-2b, %ecx
/* save address of finish as return-address for context-function */
/* will be entered after context-function returns */
- movl %ecx, 0x14(%eax)
+ movl %ecx, 0x14(%eax)
ret /* return pointer to context-data */
diff --git a/Zend/asm/make_loongarch64_sysv_elf_gas.S b/Zend/asm/make_loongarch64_sysv_elf_gas.S
index 55062702f18d..a067baded56f 100644
--- a/Zend/asm/make_loongarch64_sysv_elf_gas.S
+++ b/Zend/asm/make_loongarch64_sysv_elf_gas.S
@@ -41,6 +41,7 @@
.file "make_loongarch64_sysv_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.align 2
.type make_fcontext,@function
make_fcontext:
diff --git a/Zend/asm/make_mips32_o32_elf_gas.S b/Zend/asm/make_mips32_o32_elf_gas.S
index 4e11e3d05825..54742f3a8be2 100644
--- a/Zend/asm/make_mips32_o32_elf_gas.S
+++ b/Zend/asm/make_mips32_o32_elf_gas.S
@@ -41,6 +41,7 @@
.file "make_mips32_o32_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.align 2
.type make_fcontext,@function
.ent make_fcontext
diff --git a/Zend/asm/make_mips64_n64_elf_gas.S b/Zend/asm/make_mips64_n64_elf_gas.S
index 7bb30b14dee7..c43f2ff368f9 100644
--- a/Zend/asm/make_mips64_n64_elf_gas.S
+++ b/Zend/asm/make_mips64_n64_elf_gas.S
@@ -48,6 +48,7 @@
.file "make_mips64_n64_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.align 3
.type make_fcontext,@function
.ent make_fcontext
@@ -69,7 +70,7 @@ make_fcontext:
# save global pointer in context-data
sd $gp, 136($v0)
- # psudo instruction compute abs address of label finish based on GP
+ # pseudo instruction compute abs address of label finish based on GP
dla $t9, finish
# save address of finish as return-address for context-function
diff --git a/Zend/asm/make_ppc32_sysv_elf_gas.S b/Zend/asm/make_ppc32_sysv_elf_gas.S
index 9616c4ca9b46..0dd77719354b 100644
--- a/Zend/asm/make_ppc32_sysv_elf_gas.S
+++ b/Zend/asm/make_ppc32_sysv_elf_gas.S
@@ -53,6 +53,7 @@
.file "make_ppc32_sysv_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.align 2
.type make_fcontext,@function
make_fcontext:
diff --git a/Zend/asm/make_ppc32_sysv_macho_gas.S b/Zend/asm/make_ppc32_sysv_macho_gas.S
index 1102ee90ef07..75d1c80e5263 100644
--- a/Zend/asm/make_ppc32_sysv_macho_gas.S
+++ b/Zend/asm/make_ppc32_sysv_macho_gas.S
@@ -68,12 +68,13 @@
* ------------------------------------------------- *
* | 256 | | *
* ------------------------------------------------- *
- * | DATA| | *
+ * | DATA| | *
* ------------------------------------------------- *
* *
*******************************************************/
.text
+.private_extern _make_fcontext
.globl _make_fcontext
.align 2
_make_fcontext:
@@ -101,8 +102,8 @@ _make_fcontext:
; compute address of returned transfer_t
addi r0, r3, 252
- mr r4, r0
- stw r4, 228(r3)
+ mr r4, r0
+ stw r4, 228(r3)
; load LR
mflr r0
diff --git a/Zend/asm/make_ppc64_sysv_elf_gas.S b/Zend/asm/make_ppc64_sysv_elf_gas.S
index c4d7ee59826a..f513d3639ea9 100644
--- a/Zend/asm/make_ppc64_sysv_elf_gas.S
+++ b/Zend/asm/make_ppc64_sysv_elf_gas.S
@@ -68,6 +68,7 @@
.file "make_ppc64_sysv_elf_gas.S"
.globl make_fcontext
+.hidden make_fcontext
#if _CALL_ELF == 2
.text
.align 2
diff --git a/Zend/asm/make_ppc64_sysv_macho_gas.S b/Zend/asm/make_ppc64_sysv_macho_gas.S
index dd7bf2c5c0c0..d8cdd2d080db 100644
--- a/Zend/asm/make_ppc64_sysv_macho_gas.S
+++ b/Zend/asm/make_ppc64_sysv_macho_gas.S
@@ -67,6 +67,7 @@
*******************************************************/
.text
+.private_extern _make_fcontext
.globl _make_fcontext
_make_fcontext:
; save return address into R6
diff --git a/Zend/asm/make_riscv64_sysv_elf_gas.S b/Zend/asm/make_riscv64_sysv_elf_gas.S
index 5322e0fdbdec..6d97c59e877e 100644
--- a/Zend/asm/make_riscv64_sysv_elf_gas.S
+++ b/Zend/asm/make_riscv64_sysv_elf_gas.S
@@ -61,6 +61,7 @@
.text
.align 1
.global make_fcontext
+.hidden make_fcontext
.type make_fcontext, %function
make_fcontext:
# shift address in a0 (allocated stack) to lower 16 byte boundary
diff --git a/Zend/asm/make_s390x_sysv_elf_gas.S b/Zend/asm/make_s390x_sysv_elf_gas.S
index 4dd423e2a44c..4d6aa62c0ae1 100644
--- a/Zend/asm/make_s390x_sysv_elf_gas.S
+++ b/Zend/asm/make_s390x_sysv_elf_gas.S
@@ -46,6 +46,7 @@
.text
.align 8
.global make_fcontext
+.hidden make_fcontext
.type make_fcontext, @function
#define ARG_OFFSET 0
diff --git a/Zend/asm/make_sparc64_sysv_elf_gas.S b/Zend/asm/make_sparc64_sysv_elf_gas.S
index 3e7ee809c918..3d504471afd5 100644
--- a/Zend/asm/make_sparc64_sysv_elf_gas.S
+++ b/Zend/asm/make_sparc64_sysv_elf_gas.S
@@ -45,7 +45,7 @@ make_fcontext:
add %l0, (trampoline - 1b - 8), %l0
stx %l0, [%i0 + I7]
- # Save framepointer to first stack frame but first substract the BIAS
+ # Save framepointer to first stack frame but first subtract the BIAS
add %i0, CC64FSZ - BIAS, %l0
stx %l0, [%i0 + SP]
diff --git a/Zend/asm/make_x86_64_ms_pe_gas.S b/Zend/asm/make_x86_64_ms_pe_gas.S
index 958a2a7b6d0e..7146637c0319 100644
--- a/Zend/asm/make_x86_64_ms_pe_gas.S
+++ b/Zend/asm/make_x86_64_ms_pe_gas.S
@@ -113,7 +113,7 @@ make_fcontext:
/* save top address of context stack as 'base' */
movq %rcx, 0xc8(%rax)
/* second arg of make_fcontext() == size of context-stack */
- /* negate stack size for LEA instruction (== substraction) */
+ /* negate stack size for LEA instruction (== subtraction) */
negq %rdx
/* compute bottom address of context stack (limit) */
leaq (%rcx,%rdx), %rcx
@@ -151,7 +151,7 @@ make_fcontext:
trampoline:
/* store return address on stack */
- /* fix stack alignment */
+ /* fix stack alignment */
pushq %rbp
/* jump to context-function */
jmp *%rbx
diff --git a/Zend/asm/make_x86_64_ms_pe_masm.asm b/Zend/asm/make_x86_64_ms_pe_masm.asm
index 8f6c959a8376..a6c2b147e446 100644
--- a/Zend/asm/make_x86_64_ms_pe_masm.asm
+++ b/Zend/asm/make_x86_64_ms_pe_masm.asm
@@ -110,7 +110,7 @@ make_fcontext PROC BOOST_CONTEXT_EXPORT FRAME
; save top address of context stack as 'base'
mov [rax+0c8h], rcx
; second arg of make_fcontext() == size of context-stack
- ; negate stack size for LEA instruction (== substraction)
+ ; negate stack size for LEA instruction (== subtraction)
neg rdx
; compute bottom address of context stack (limit)
lea rcx, [rcx+rdx]
@@ -141,7 +141,7 @@ make_fcontext PROC BOOST_CONTEXT_EXPORT FRAME
; compute abs address of label finish
lea rcx, finish
; save address of finish as return-address for context-function in RBP
- ; will be entered after context-function returns
+ ; will be entered after context-function returns
mov [rax+0108h], rcx
ret ; return pointer to context-data
diff --git a/Zend/asm/make_x86_64_sysv_elf_gas.S b/Zend/asm/make_x86_64_sysv_elf_gas.S
index b0d0c0341ef3..cabebff6e626 100644
--- a/Zend/asm/make_x86_64_sysv_elf_gas.S
+++ b/Zend/asm/make_x86_64_sysv_elf_gas.S
@@ -44,6 +44,7 @@
.file "make_x86_64_sysv_elf_gas.S"
.text
.globl make_fcontext
+.hidden make_fcontext
.type make_fcontext,@function
.align 16
make_fcontext:
@@ -76,7 +77,7 @@ make_fcontext:
#if defined(BOOST_CONTEXT_TLS_STACK_PROTECTOR)
/* save stack guard */
movq %fs:0x28, %rcx /* read stack guard from TLS record */
- movq %rcx, 0x8(%rsp) /* save stack guard */
+ movq %rcx, 0x8(%rax) /* save stack guard */
#endif
/* compute abs address of label trampoline */
@@ -91,35 +92,6 @@ make_fcontext:
/* will be entered after context-function returns */
movq %rcx, 0x38(%rax)
-#if BOOST_CONTEXT_SHADOW_STACK
- /* Populate the shadow stack and normal stack */
- /* get original SSP */
- rdsspq %r8
- /* restore new shadow stack */
- rstorssp -0x8(%r9)
- /* save the restore token on the original shadow stack */
- saveprevssp
- /* push the address of "jmp trampoline" to the new shadow stack */
- /* as well as the stack */
- call 1f
- jmp trampoline
-1:
- /* save address of "jmp trampoline" as return-address */
- /* for context-function */
- pop 0x38(%rax)
- /* Get the new SSP. */
- rdsspq %r9
- /* restore original shadow stack */
- rstorssp -0x8(%r8)
- /* save the restore token on the new shadow stack. */
- saveprevssp
-
- /* reserve space for the new SSP */
- leaq -0x8(%rax), %rax
- /* save the new SSP to this fcontext */
- movq %r9, (%rax)
-#endif
-
#if BOOST_CONTEXT_SHADOW_STACK
/* Populate the shadow stack */
@@ -161,6 +133,8 @@ make_fcontext:
ret /* return pointer to context-data */
trampoline:
+ .cfi_startproc
+ .cfi_undefined rip
/* store return address on stack */
/* fix stack alignment */
_CET_ENDBR
@@ -175,6 +149,7 @@ trampoline:
#endif
/* jump to context-function */
jmp *%rbx
+ .cfi_endproc
finish:
_CET_ENDBR
diff --git a/Zend/asm/make_x86_64_sysv_macho_gas.S b/Zend/asm/make_x86_64_sysv_macho_gas.S
index 5d6c5431c598..06357f678ac9 100644
--- a/Zend/asm/make_x86_64_sysv_macho_gas.S
+++ b/Zend/asm/make_x86_64_sysv_macho_gas.S
@@ -25,6 +25,7 @@
****************************************************************************************/
.text
+.private_extern _make_fcontext
.globl _make_fcontext
.align 8
_make_fcontext:
diff --git a/Zend/asm/save_xmm_x86_64_ms_masm.asm b/Zend/asm/save_xmm_x86_64_ms_masm.asm
index 1569d6bdb0e8..0af7c346095d 100644
--- a/Zend/asm/save_xmm_x86_64_ms_masm.asm
+++ b/Zend/asm/save_xmm_x86_64_ms_masm.asm
@@ -9,34 +9,34 @@ EXTERN execute_ex_real:PROC
; save the preserved registers when re-entering the VM from JIT code.
; See GH-18136.
execute_ex PROC EXPORT FRAME
- ; 10 floating points numbers
+ ; 10 XMM registers
; 32 bytes shadow space
; 8 bytes to align after the return address
- sub rsp, 8*10 + 32 + 8
- .allocstack 8*10 + 32 + 8
+ sub rsp, 16*10 + 32 + 8
+ .allocstack 16*10 + 32 + 8
.endprolog
- movsd qword ptr [rsp + 32 + 8*0], xmm6
- movsd qword ptr [rsp + 32 + 8*1], xmm7
- movsd qword ptr [rsp + 32 + 8*2], xmm8
- movsd qword ptr [rsp + 32 + 8*3], xmm9
- movsd qword ptr [rsp + 32 + 8*4], xmm10
- movsd qword ptr [rsp + 32 + 8*5], xmm11
- movsd qword ptr [rsp + 32 + 8*6], xmm12
- movsd qword ptr [rsp + 32 + 8*7], xmm13
- movsd qword ptr [rsp + 32 + 8*8], xmm14
- movsd qword ptr [rsp + 32 + 8*9], xmm15
+ movaps xmmword ptr [rsp + 32 + 16*0], xmm6
+ movaps xmmword ptr [rsp + 32 + 16*1], xmm7
+ movaps xmmword ptr [rsp + 32 + 16*2], xmm8
+ movaps xmmword ptr [rsp + 32 + 16*3], xmm9
+ movaps xmmword ptr [rsp + 32 + 16*4], xmm10
+ movaps xmmword ptr [rsp + 32 + 16*5], xmm11
+ movaps xmmword ptr [rsp + 32 + 16*6], xmm12
+ movaps xmmword ptr [rsp + 32 + 16*7], xmm13
+ movaps xmmword ptr [rsp + 32 + 16*8], xmm14
+ movaps xmmword ptr [rsp + 32 + 16*9], xmm15
call execute_ex_real
- movsd xmm6, qword ptr [rsp + 32 + 8*0]
- movsd xmm7, qword ptr [rsp + 32 + 8*1]
- movsd xmm8, qword ptr [rsp + 32 + 8*2]
- movsd xmm9, qword ptr [rsp + 32 + 8*3]
- movsd xmm10, qword ptr [rsp + 32 + 8*4]
- movsd xmm11, qword ptr [rsp + 32 + 8*5]
- movsd xmm12, qword ptr [rsp + 32 + 8*6]
- movsd xmm13, qword ptr [rsp + 32 + 8*7]
- movsd xmm14, qword ptr [rsp + 32 + 8*8]
- movsd xmm15, qword ptr [rsp + 32 + 8*9]
- add rsp, 8*10 + 32 + 8
+ movaps xmm6, xmmword ptr [rsp + 32 + 16*0]
+ movaps xmm7, xmmword ptr [rsp + 32 + 16*1]
+ movaps xmm8, xmmword ptr [rsp + 32 + 16*2]
+ movaps xmm9, xmmword ptr [rsp + 32 + 16*3]
+ movaps xmm10, xmmword ptr [rsp + 32 + 16*4]
+ movaps xmm11, xmmword ptr [rsp + 32 + 16*5]
+ movaps xmm12, xmmword ptr [rsp + 32 + 16*6]
+ movaps xmm13, xmmword ptr [rsp + 32 + 16*7]
+ movaps xmm14, xmmword ptr [rsp + 32 + 16*8]
+ movaps xmm15, xmmword ptr [rsp + 32 + 16*9]
+ add rsp, 16*10 + 32 + 8
ret
execute_ex ENDP
diff --git a/Zend/tests/GHSA-wm6j-2649-pv75.phpt b/Zend/tests/GHSA-wm6j-2649-pv75.phpt
new file mode 100644
index 000000000000..c1035938bd7b
--- /dev/null
+++ b/Zend/tests/GHSA-wm6j-2649-pv75.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GHSA-wm6j-2649-pv75: Null pointer dereference in php_mb_check_encoding() via mb_ereg_search_init()
+--CREDITS--
+vi3tL0u1s
+--EXTENSIONS--
+mbstring
+--SKIPIF--
+
+--FILE--
+
+--EXPECTF--
+Deprecated: Function mb_regex_encoding() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d
+
+Fatal error: Uncaught ValueError: mb_regex_encoding(): Argument #1 ($encoding) must be a valid encoding, "iso-8859-11" given in %s:%d
+Stack trace:
+#0 %s(%d): mb_regex_encoding('iso-8859-11')
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt
index 9f8e9b77003b..3bb08504f581 100644
--- a/Zend/tests/assert/expect_015.phpt
+++ b/Zend/tests/assert/expect_015.phpt
@@ -201,7 +201,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use($c, &$d): ?X {
$s[$i] = $a[$j];
}
foreach ($a as $key => &$val) {
- print "$key => $val\n";
+ print "{$key} => {$val}\n";
}
while ($s[$i]) {
$i++;
@@ -299,12 +299,12 @@ assert(0 && ($a = function (): ?static {
echo 1;
}
$x = '\'"`$a';
- $x = "'\"`$a";
- $x = `'"\`$a`;
+ $x = "'\"`{$a}";
+ $x = `'"\`{$a}`;
$x = "{$a}b";
$x = "{$a}b";
$x = " {$foo->bar} {${$foo->bar}} ";
- $x = " ${---} ";
+ $x = " {${'---'}} ";
foo();
\foo();
namespace\foo();
diff --git a/Zend/tests/assert/expect_020.phpt b/Zend/tests/assert/expect_020.phpt
index d305667a28d7..2146abc96045 100644
--- a/Zend/tests/assert/expect_020.phpt
+++ b/Zend/tests/assert/expect_020.phpt
@@ -16,5 +16,5 @@ assert(0 && ($a = function () {
--EXPECT--
assert(): assert(0 && ($a = function () {
$var = 'test';
- $str = "$var, {$var[1]}, {$var}[], {$var[1]}[], {$var}[], {$var[1]}[]";
+ $str = "{$var}, {$var[1]}, {$var}[], {$var[1]}[], {$var}[], {$var[1]}[]";
})) failed
diff --git a/Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt b/Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt
new file mode 100644
index 000000000000..9ebb8e4c0202
--- /dev/null
+++ b/Zend/tests/attributes/delayed_target_validation/with_Override_error_constant.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (class constant)
+--FILE--
+
+--EXPECTF--
+Fatal error: DemoClass::CLASS_CONSTANT has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/delayed_target_validation/with_Override_error_enum_case.phpt b/Zend/tests/attributes/delayed_target_validation/with_Override_error_enum_case.phpt
new file mode 100644
index 000000000000..53d75412a849
--- /dev/null
+++ b/Zend/tests/attributes/delayed_target_validation/with_Override_error_enum_case.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\DelayedTargetValidation] with #[\Override]: non-overrides still error (enum case)
+--FILE--
+
+--EXPECTF--
+Fatal error: DemoEnum::MyCase has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt b/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt
index dd077f4b9cbd..494d85eaea9c 100644
--- a/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt
+++ b/Zend/tests/attributes/delayed_target_validation/with_Override_okay.phpt
@@ -12,6 +12,8 @@ class Base {
set => $value;
}
+ public const CLASS_CONST = '';
+
public function printVal() {
echo __METHOD__ . "\n";
}
@@ -34,7 +36,7 @@ class DemoClass extends Base {
}
#[DelayedTargetValidation]
- #[Override] // Does nothing here
+ #[Override] // Does something here
public const CLASS_CONST = 'FOO';
public function __construct(
diff --git a/Zend/tests/attributes/override/constants/anon_failure.phpt b/Zend/tests/attributes/override/constants/anon_failure.phpt
new file mode 100644
index 000000000000..c43cedda81d7
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/anon_failure.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\Override]: Constants - anonymous class, no interface or parent class
+--FILE--
+
+--EXPECTF--
+Fatal error: class@anonymous::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/anon_interface.phpt b/Zend/tests/attributes/override/constants/anon_interface.phpt
new file mode 100644
index 000000000000..b19abfbf4d3a
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/anon_interface.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - anonymous class overrides interface
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/anon_parent.phpt b/Zend/tests/attributes/override/constants/anon_parent.phpt
new file mode 100644
index 000000000000..b430705c3c9d
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/anon_parent.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - anonymous class overrides parent class
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/basic.phpt b/Zend/tests/attributes/override/constants/basic.phpt
new file mode 100644
index 000000000000..c89007022051
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/basic.phpt
@@ -0,0 +1,57 @@
+--TEST--
+#[\Override]: Constants - basic
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/enum_failure.phpt b/Zend/tests/attributes/override/constants/enum_failure.phpt
new file mode 100644
index 000000000000..77ba225e9b9e
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/enum_failure.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\Override]: Constants - no interface for enum constant
+--FILE--
+
+--EXPECTF--
+Fatal error: Demo::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/enum_failure_case.phpt b/Zend/tests/attributes/override/constants/enum_failure_case.phpt
new file mode 100644
index 000000000000..2f2c6fc84a08
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/enum_failure_case.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\Override]: Constants - no interface for enum case
+--FILE--
+
+--EXPECTF--
+Fatal error: Demo::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/failure.phpt b/Zend/tests/attributes/override/constants/failure.phpt
new file mode 100644
index 000000000000..be621915d927
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/failure.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\Override]: Constants - no interface or parent class
+--FILE--
+
+--EXPECTF--
+Fatal error: Demo::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/interface_failure.phpt b/Zend/tests/attributes/override/constants/interface_failure.phpt
new file mode 100644
index 000000000000..0aa4d33d1301
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/interface_failure.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\Override]: Constants - no parent interface
+--FILE--
+
+--EXPECTF--
+Fatal error: IFace::I has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/trait_failure.phpt b/Zend/tests/attributes/override/constants/trait_failure.phpt
new file mode 100644
index 000000000000..8d4af0e57977
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_failure.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - on a trait, no interface or parent class
+--FILE--
+
+--EXPECTF--
+Fatal error: UsesTrait::T has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/trait_interface.phpt b/Zend/tests/attributes/override/constants/trait_interface.phpt
new file mode 100644
index 000000000000..3a2ba799ae4e
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_interface.phpt
@@ -0,0 +1,23 @@
+--TEST--
+#[\Override]: Constants - on a trait, overrides interface
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/trait_parent.phpt b/Zend/tests/attributes/override/constants/trait_parent.phpt
new file mode 100644
index 000000000000..2753aaf546a2
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_parent.phpt
@@ -0,0 +1,23 @@
+--TEST--
+#[\Override]: Constants - on a trait, overrides parent class
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/trait_redeclared.phpt b/Zend/tests/attributes/override/constants/trait_redeclared.phpt
new file mode 100644
index 000000000000..984f9eedb668
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_redeclared.phpt
@@ -0,0 +1,21 @@
+--TEST--
+#[\Override]: Constants - trait constant redeclared, not overridden
+--FILE--
+
+--EXPECTF--
+Fatal error: UsesTrait::T has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt b/Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt
new file mode 100644
index 000000000000..21a1aa532249
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_redeclared_interface.phpt
@@ -0,0 +1,25 @@
+--TEST--
+#[\Override]: Constants - trait constant redeclared, overrides interface
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt b/Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt
new file mode 100644
index 000000000000..c3d1e43d2534
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_redeclared_parent.phpt
@@ -0,0 +1,25 @@
+--TEST--
+#[\Override]: Constants - trait constant redeclared, overrides parent class
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/trait_unused.phpt b/Zend/tests/attributes/override/constants/trait_unused.phpt
new file mode 100644
index 000000000000..785f2888e90b
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/trait_unused.phpt
@@ -0,0 +1,15 @@
+--TEST--
+#[\Override]: Constants - on a trait, unused
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/visibility_01.phpt b/Zend/tests/attributes/override/constants/visibility_01.phpt
new file mode 100644
index 000000000000..49671471f758
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/visibility_01.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - private constant not overridden (by public constant)
+--FILE--
+
+--EXPECTF--
+Fatal error: Child::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/visibility_02.phpt b/Zend/tests/attributes/override/constants/visibility_02.phpt
new file mode 100644
index 000000000000..c160a29a6180
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/visibility_02.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - private constant not overridden (by private constant)
+--FILE--
+
+--EXPECTF--
+Fatal error: Child::C has #[\Override] attribute, but no matching parent constant exists in %s on line %d
diff --git a/Zend/tests/attributes/override/constants/visibility_03.phpt b/Zend/tests/attributes/override/constants/visibility_03.phpt
new file mode 100644
index 000000000000..db460f24375e
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/visibility_03.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - protected constant is overridden (by public constant)
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/attributes/override/constants/visibility_04.phpt b/Zend/tests/attributes/override/constants/visibility_04.phpt
new file mode 100644
index 000000000000..0ad46b022f43
--- /dev/null
+++ b/Zend/tests/attributes/override/constants/visibility_04.phpt
@@ -0,0 +1,19 @@
+--TEST--
+#[\Override]: Constants - protected constant is overridden (by protected constant)
+--FILE--
+
+--EXPECT--
+Done
diff --git a/Zend/tests/backtrace/debug_backtrace_with_include_and_this.phpt b/Zend/tests/backtrace/debug_backtrace_with_include_and_this.phpt
index 49796a9b9103..18f155611f42 100644
--- a/Zend/tests/backtrace/debug_backtrace_with_include_and_this.phpt
+++ b/Zend/tests/backtrace/debug_backtrace_with_include_and_this.phpt
@@ -30,7 +30,7 @@ try {
}
?>
--EXPECTF--
-ERR#2: include(class://non.existent.Class): Failed to open stream: "CLWrapper::stream_open" call failed @ include
+ERR#2: include(): Failed to open stream: "CLWrapper::stream_open" call failed @ include
ERR#2: include(): Failed opening 'class://non.existent.Class' for inclusion (include_path='%s') @ include
Fatal error: Uncaught Exception: Failed loading class://non.existent.Class in %s
diff --git a/Zend/tests/bug30998.phpt b/Zend/tests/bug30998.phpt
index 9d8435121671..a4cd6de8f50e 100644
--- a/Zend/tests/bug30998.phpt
+++ b/Zend/tests/bug30998.phpt
@@ -14,6 +14,6 @@ set_error_handler('my_error');
$f = fopen("/tmp/blah", "r");
?>
--EXPECTF--
-fopen(/tmp/blah): Failed to open stream: %s (2) in %s:%d
+fopen(): Failed to open stream: %s (2) in %s:%d
-Warning: fopen(/tmp/blah): Failed to open stream: %s in %s on line %d
+Warning: fopen(): Failed to open stream: %s in %s on line %d
diff --git a/Zend/tests/bug60909_1.phpt b/Zend/tests/bug60909_1.phpt
index 4404adfd54a3..bde5a381ba47 100644
--- a/Zend/tests/bug60909_1.phpt
+++ b/Zend/tests/bug60909_1.phpt
@@ -11,10 +11,10 @@ set_error_handler(function($errno, $errstr, $errfile, $errline){
require 'notfound.php';
?>
--EXPECTF--
-error(require(notfound.php): Failed to open stream: %s)
+error(require(): Failed to open stream: %s)
Fatal error: Uncaught Exception: Foo in %sbug60909_1.php:5
Stack trace:
-#0 %s(%d): {closure:%s:%d}(2, 'require(notfoun...', '%s', 8)
+#0 %s(%d): {closure:%s:%d}(2, 'require(): Fail...', '%s', 8)
#1 %sbug60909_1.php(8): require()
#2 {main}
thrown in %sbug60909_1.php on line 5
diff --git a/Zend/tests/bug74093.phpt b/Zend/tests/bug74093.phpt
index d9adfeefa50e..d38d6e5109c4 100644
--- a/Zend/tests/bug74093.phpt
+++ b/Zend/tests/bug74093.phpt
@@ -1,5 +1,6 @@
--TEST--
Bug #74093 (Maximum execution time of n+2 seconds exceed not written in error_log)
+--FLAKY--
--SKIPIF--
--EXPECTF--
diff --git a/Zend/tests/clone/clone_with_014.phpt b/Zend/tests/clone/clone_with_014.phpt
new file mode 100644
index 000000000000..bfbee40e163c
--- /dev/null
+++ b/Zend/tests/clone/clone_with_014.phpt
@@ -0,0 +1,41 @@
+--TEST--
+Properties are still readonly after clone-with
+--FILE--
+a = 1;
+ $this->b = 2;
+ }
+}
+
+$test = clone(new Test(), ['a' => 3]);
+var_dump($test);
+
+try {
+ $test->b = 4;
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), PHP_EOL;
+}
+
+var_dump($test);
+
+?>
+--EXPECT--
+object(Test)#2 (2) {
+ ["a"]=>
+ int(3)
+ ["b"]=>
+ int(2)
+}
+Error: Cannot modify readonly property Test::$b
+object(Test)#2 (2) {
+ ["a"]=>
+ int(3)
+ ["b"]=>
+ int(2)
+}
diff --git a/Zend/tests/comparison/spaceship_type_compare.phpt b/Zend/tests/comparison/spaceship_type_compare.phpt
new file mode 100644
index 000000000000..7382a593cc84
--- /dev/null
+++ b/Zend/tests/comparison/spaceship_type_compare.phpt
@@ -0,0 +1,536 @@
+--TEST--
+Compare types via <=> operator
+--FILE--
+ $r) {
+ 0 => "EQUAL",
+ 1 => match ($r <=> $l) {
+ -1 => "GREATER THAN",
+ 1 => "UNCOMPARABLE",
+ },
+ -1 => "LESS THAN",
+ };
+}
+
+class test {
+}
+
+$values = [
+ 'null' => null,
+ 'false' => false,
+ 'true' => true,
+ 'i-5' => -5,
+ 'i0' => 0,
+ 'i10' => 10,
+ 'f-5' => -5.0,
+ 'f0' => 0.0,
+ 'f2.5' => 2.5,
+ 'f10' => 10.0,
+ 's""' => "",
+ 's"str"' => "str",
+ 's"-5"' => "-5",
+ 's"0"' => "0",
+ 's"2.5"' => "2.5",
+ 's"10"' => "10",
+ 'a[]' => [],
+ 'a[1, 2, 3]' => [1, 2, 3],
+ 'c"stdClass"' => new stdClass(),
+ 'c"test"' => new test(),
+ 'r"STDOUT"' => STDOUT,
+ 'r"STDERR"' => STDERR,
+];
+
+foreach($values as $l_name => $l) {
+ foreach($values as $r_name => $r) {
+ echo $l_name, ' ', cmp($l, $r), ' ', $r_name, "\n";
+ }
+}
+
+?>
+--EXPECT--
+null EQUAL null
+null EQUAL false
+null LESS THAN true
+null LESS THAN i-5
+null EQUAL i0
+null LESS THAN i10
+null LESS THAN f-5
+null EQUAL f0
+null LESS THAN f2.5
+null LESS THAN f10
+null EQUAL s""
+null LESS THAN s"str"
+null LESS THAN s"-5"
+null LESS THAN s"0"
+null LESS THAN s"2.5"
+null LESS THAN s"10"
+null EQUAL a[]
+null LESS THAN a[1, 2, 3]
+null LESS THAN c"stdClass"
+null LESS THAN c"test"
+null LESS THAN r"STDOUT"
+null LESS THAN r"STDERR"
+false EQUAL null
+false EQUAL false
+false LESS THAN true
+false LESS THAN i-5
+false EQUAL i0
+false LESS THAN i10
+false LESS THAN f-5
+false EQUAL f0
+false LESS THAN f2.5
+false LESS THAN f10
+false EQUAL s""
+false LESS THAN s"str"
+false LESS THAN s"-5"
+false EQUAL s"0"
+false LESS THAN s"2.5"
+false LESS THAN s"10"
+false EQUAL a[]
+false LESS THAN a[1, 2, 3]
+false LESS THAN c"stdClass"
+false LESS THAN c"test"
+false LESS THAN r"STDOUT"
+false LESS THAN r"STDERR"
+true GREATER THAN null
+true GREATER THAN false
+true EQUAL true
+true EQUAL i-5
+true GREATER THAN i0
+true EQUAL i10
+true EQUAL f-5
+true GREATER THAN f0
+true EQUAL f2.5
+true EQUAL f10
+true GREATER THAN s""
+true EQUAL s"str"
+true EQUAL s"-5"
+true GREATER THAN s"0"
+true EQUAL s"2.5"
+true EQUAL s"10"
+true GREATER THAN a[]
+true EQUAL a[1, 2, 3]
+true EQUAL c"stdClass"
+true EQUAL c"test"
+true EQUAL r"STDOUT"
+true EQUAL r"STDERR"
+i-5 GREATER THAN null
+i-5 GREATER THAN false
+i-5 EQUAL true
+i-5 EQUAL i-5
+i-5 LESS THAN i0
+i-5 LESS THAN i10
+i-5 EQUAL f-5
+i-5 LESS THAN f0
+i-5 LESS THAN f2.5
+i-5 LESS THAN f10
+i-5 GREATER THAN s""
+i-5 LESS THAN s"str"
+i-5 EQUAL s"-5"
+i-5 LESS THAN s"0"
+i-5 LESS THAN s"2.5"
+i-5 LESS THAN s"10"
+i-5 LESS THAN a[]
+i-5 LESS THAN a[1, 2, 3]
+i-5 LESS THAN c"stdClass"
+i-5 LESS THAN c"test"
+i-5 LESS THAN r"STDOUT"
+i-5 LESS THAN r"STDERR"
+i0 EQUAL null
+i0 EQUAL false
+i0 LESS THAN true
+i0 GREATER THAN i-5
+i0 EQUAL i0
+i0 LESS THAN i10
+i0 GREATER THAN f-5
+i0 EQUAL f0
+i0 LESS THAN f2.5
+i0 LESS THAN f10
+i0 GREATER THAN s""
+i0 LESS THAN s"str"
+i0 GREATER THAN s"-5"
+i0 EQUAL s"0"
+i0 LESS THAN s"2.5"
+i0 LESS THAN s"10"
+i0 LESS THAN a[]
+i0 LESS THAN a[1, 2, 3]
+i0 LESS THAN c"stdClass"
+i0 LESS THAN c"test"
+i0 LESS THAN r"STDOUT"
+i0 LESS THAN r"STDERR"
+i10 GREATER THAN null
+i10 GREATER THAN false
+i10 EQUAL true
+i10 GREATER THAN i-5
+i10 GREATER THAN i0
+i10 EQUAL i10
+i10 GREATER THAN f-5
+i10 GREATER THAN f0
+i10 GREATER THAN f2.5
+i10 EQUAL f10
+i10 GREATER THAN s""
+i10 LESS THAN s"str"
+i10 GREATER THAN s"-5"
+i10 GREATER THAN s"0"
+i10 GREATER THAN s"2.5"
+i10 EQUAL s"10"
+i10 LESS THAN a[]
+i10 LESS THAN a[1, 2, 3]
+i10 GREATER THAN c"stdClass"
+i10 GREATER THAN c"test"
+i10 GREATER THAN r"STDOUT"
+i10 GREATER THAN r"STDERR"
+f-5 GREATER THAN null
+f-5 GREATER THAN false
+f-5 EQUAL true
+f-5 EQUAL i-5
+f-5 LESS THAN i0
+f-5 LESS THAN i10
+f-5 EQUAL f-5
+f-5 LESS THAN f0
+f-5 LESS THAN f2.5
+f-5 LESS THAN f10
+f-5 GREATER THAN s""
+f-5 LESS THAN s"str"
+f-5 EQUAL s"-5"
+f-5 LESS THAN s"0"
+f-5 LESS THAN s"2.5"
+f-5 LESS THAN s"10"
+f-5 LESS THAN a[]
+f-5 LESS THAN a[1, 2, 3]
+f-5 LESS THAN c"stdClass"
+f-5 LESS THAN c"test"
+f-5 LESS THAN r"STDOUT"
+f-5 LESS THAN r"STDERR"
+f0 EQUAL null
+f0 EQUAL false
+f0 LESS THAN true
+f0 GREATER THAN i-5
+f0 EQUAL i0
+f0 LESS THAN i10
+f0 GREATER THAN f-5
+f0 EQUAL f0
+f0 LESS THAN f2.5
+f0 LESS THAN f10
+f0 GREATER THAN s""
+f0 LESS THAN s"str"
+f0 GREATER THAN s"-5"
+f0 EQUAL s"0"
+f0 LESS THAN s"2.5"
+f0 LESS THAN s"10"
+f0 LESS THAN a[]
+f0 LESS THAN a[1, 2, 3]
+f0 LESS THAN c"stdClass"
+f0 LESS THAN c"test"
+f0 LESS THAN r"STDOUT"
+f0 LESS THAN r"STDERR"
+f2.5 GREATER THAN null
+f2.5 GREATER THAN false
+f2.5 EQUAL true
+f2.5 GREATER THAN i-5
+f2.5 GREATER THAN i0
+f2.5 LESS THAN i10
+f2.5 GREATER THAN f-5
+f2.5 GREATER THAN f0
+f2.5 EQUAL f2.5
+f2.5 LESS THAN f10
+f2.5 GREATER THAN s""
+f2.5 LESS THAN s"str"
+f2.5 GREATER THAN s"-5"
+f2.5 GREATER THAN s"0"
+f2.5 EQUAL s"2.5"
+f2.5 LESS THAN s"10"
+f2.5 LESS THAN a[]
+f2.5 LESS THAN a[1, 2, 3]
+f2.5 GREATER THAN c"stdClass"
+f2.5 GREATER THAN c"test"
+f2.5 GREATER THAN r"STDOUT"
+f2.5 LESS THAN r"STDERR"
+f10 GREATER THAN null
+f10 GREATER THAN false
+f10 EQUAL true
+f10 GREATER THAN i-5
+f10 GREATER THAN i0
+f10 EQUAL i10
+f10 GREATER THAN f-5
+f10 GREATER THAN f0
+f10 GREATER THAN f2.5
+f10 EQUAL f10
+f10 GREATER THAN s""
+f10 LESS THAN s"str"
+f10 GREATER THAN s"-5"
+f10 GREATER THAN s"0"
+f10 GREATER THAN s"2.5"
+f10 EQUAL s"10"
+f10 LESS THAN a[]
+f10 LESS THAN a[1, 2, 3]
+f10 GREATER THAN c"stdClass"
+f10 GREATER THAN c"test"
+f10 GREATER THAN r"STDOUT"
+f10 GREATER THAN r"STDERR"
+s"" EQUAL null
+s"" EQUAL false
+s"" LESS THAN true
+s"" LESS THAN i-5
+s"" LESS THAN i0
+s"" LESS THAN i10
+s"" LESS THAN f-5
+s"" LESS THAN f0
+s"" LESS THAN f2.5
+s"" LESS THAN f10
+s"" EQUAL s""
+s"" LESS THAN s"str"
+s"" LESS THAN s"-5"
+s"" LESS THAN s"0"
+s"" LESS THAN s"2.5"
+s"" LESS THAN s"10"
+s"" LESS THAN a[]
+s"" LESS THAN a[1, 2, 3]
+s"" LESS THAN c"stdClass"
+s"" LESS THAN c"test"
+s"" LESS THAN r"STDOUT"
+s"" LESS THAN r"STDERR"
+s"str" GREATER THAN null
+s"str" GREATER THAN false
+s"str" EQUAL true
+s"str" GREATER THAN i-5
+s"str" GREATER THAN i0
+s"str" GREATER THAN i10
+s"str" GREATER THAN f-5
+s"str" GREATER THAN f0
+s"str" GREATER THAN f2.5
+s"str" GREATER THAN f10
+s"str" GREATER THAN s""
+s"str" EQUAL s"str"
+s"str" GREATER THAN s"-5"
+s"str" GREATER THAN s"0"
+s"str" GREATER THAN s"2.5"
+s"str" GREATER THAN s"10"
+s"str" LESS THAN a[]
+s"str" LESS THAN a[1, 2, 3]
+s"str" LESS THAN c"stdClass"
+s"str" LESS THAN c"test"
+s"str" LESS THAN r"STDOUT"
+s"str" LESS THAN r"STDERR"
+s"-5" GREATER THAN null
+s"-5" GREATER THAN false
+s"-5" EQUAL true
+s"-5" EQUAL i-5
+s"-5" LESS THAN i0
+s"-5" LESS THAN i10
+s"-5" EQUAL f-5
+s"-5" LESS THAN f0
+s"-5" LESS THAN f2.5
+s"-5" LESS THAN f10
+s"-5" GREATER THAN s""
+s"-5" LESS THAN s"str"
+s"-5" EQUAL s"-5"
+s"-5" LESS THAN s"0"
+s"-5" LESS THAN s"2.5"
+s"-5" LESS THAN s"10"
+s"-5" LESS THAN a[]
+s"-5" LESS THAN a[1, 2, 3]
+s"-5" LESS THAN c"stdClass"
+s"-5" LESS THAN c"test"
+s"-5" LESS THAN r"STDOUT"
+s"-5" LESS THAN r"STDERR"
+s"0" GREATER THAN null
+s"0" EQUAL false
+s"0" LESS THAN true
+s"0" GREATER THAN i-5
+s"0" EQUAL i0
+s"0" LESS THAN i10
+s"0" GREATER THAN f-5
+s"0" EQUAL f0
+s"0" LESS THAN f2.5
+s"0" LESS THAN f10
+s"0" GREATER THAN s""
+s"0" LESS THAN s"str"
+s"0" GREATER THAN s"-5"
+s"0" EQUAL s"0"
+s"0" LESS THAN s"2.5"
+s"0" LESS THAN s"10"
+s"0" LESS THAN a[]
+s"0" LESS THAN a[1, 2, 3]
+s"0" LESS THAN c"stdClass"
+s"0" LESS THAN c"test"
+s"0" LESS THAN r"STDOUT"
+s"0" LESS THAN r"STDERR"
+s"2.5" GREATER THAN null
+s"2.5" GREATER THAN false
+s"2.5" EQUAL true
+s"2.5" GREATER THAN i-5
+s"2.5" GREATER THAN i0
+s"2.5" LESS THAN i10
+s"2.5" GREATER THAN f-5
+s"2.5" GREATER THAN f0
+s"2.5" EQUAL f2.5
+s"2.5" LESS THAN f10
+s"2.5" GREATER THAN s""
+s"2.5" LESS THAN s"str"
+s"2.5" GREATER THAN s"-5"
+s"2.5" GREATER THAN s"0"
+s"2.5" EQUAL s"2.5"
+s"2.5" LESS THAN s"10"
+s"2.5" LESS THAN a[]
+s"2.5" LESS THAN a[1, 2, 3]
+s"2.5" LESS THAN c"stdClass"
+s"2.5" LESS THAN c"test"
+s"2.5" GREATER THAN r"STDOUT"
+s"2.5" LESS THAN r"STDERR"
+s"10" GREATER THAN null
+s"10" GREATER THAN false
+s"10" EQUAL true
+s"10" GREATER THAN i-5
+s"10" GREATER THAN i0
+s"10" EQUAL i10
+s"10" GREATER THAN f-5
+s"10" GREATER THAN f0
+s"10" GREATER THAN f2.5
+s"10" EQUAL f10
+s"10" GREATER THAN s""
+s"10" LESS THAN s"str"
+s"10" GREATER THAN s"-5"
+s"10" GREATER THAN s"0"
+s"10" GREATER THAN s"2.5"
+s"10" EQUAL s"10"
+s"10" LESS THAN a[]
+s"10" LESS THAN a[1, 2, 3]
+s"10" LESS THAN c"stdClass"
+s"10" LESS THAN c"test"
+s"10" GREATER THAN r"STDOUT"
+s"10" GREATER THAN r"STDERR"
+a[] EQUAL null
+a[] EQUAL false
+a[] LESS THAN true
+a[] GREATER THAN i-5
+a[] GREATER THAN i0
+a[] GREATER THAN i10
+a[] GREATER THAN f-5
+a[] GREATER THAN f0
+a[] GREATER THAN f2.5
+a[] GREATER THAN f10
+a[] GREATER THAN s""
+a[] GREATER THAN s"str"
+a[] GREATER THAN s"-5"
+a[] GREATER THAN s"0"
+a[] GREATER THAN s"2.5"
+a[] GREATER THAN s"10"
+a[] EQUAL a[]
+a[] LESS THAN a[1, 2, 3]
+a[] LESS THAN c"stdClass"
+a[] LESS THAN c"test"
+a[] GREATER THAN r"STDOUT"
+a[] GREATER THAN r"STDERR"
+a[1, 2, 3] GREATER THAN null
+a[1, 2, 3] GREATER THAN false
+a[1, 2, 3] EQUAL true
+a[1, 2, 3] GREATER THAN i-5
+a[1, 2, 3] GREATER THAN i0
+a[1, 2, 3] GREATER THAN i10
+a[1, 2, 3] GREATER THAN f-5
+a[1, 2, 3] GREATER THAN f0
+a[1, 2, 3] GREATER THAN f2.5
+a[1, 2, 3] GREATER THAN f10
+a[1, 2, 3] GREATER THAN s""
+a[1, 2, 3] GREATER THAN s"str"
+a[1, 2, 3] GREATER THAN s"-5"
+a[1, 2, 3] GREATER THAN s"0"
+a[1, 2, 3] GREATER THAN s"2.5"
+a[1, 2, 3] GREATER THAN s"10"
+a[1, 2, 3] GREATER THAN a[]
+a[1, 2, 3] EQUAL a[1, 2, 3]
+a[1, 2, 3] LESS THAN c"stdClass"
+a[1, 2, 3] LESS THAN c"test"
+a[1, 2, 3] GREATER THAN r"STDOUT"
+a[1, 2, 3] GREATER THAN r"STDERR"
+c"stdClass" GREATER THAN null
+c"stdClass" GREATER THAN false
+c"stdClass" EQUAL true
+c"stdClass" GREATER THAN i-5
+c"stdClass" GREATER THAN i0
+c"stdClass" LESS THAN i10
+c"stdClass" GREATER THAN f-5
+c"stdClass" GREATER THAN f0
+c"stdClass" LESS THAN f2.5
+c"stdClass" LESS THAN f10
+c"stdClass" GREATER THAN s""
+c"stdClass" GREATER THAN s"str"
+c"stdClass" GREATER THAN s"-5"
+c"stdClass" GREATER THAN s"0"
+c"stdClass" GREATER THAN s"2.5"
+c"stdClass" GREATER THAN s"10"
+c"stdClass" GREATER THAN a[]
+c"stdClass" GREATER THAN a[1, 2, 3]
+c"stdClass" EQUAL c"stdClass"
+c"stdClass" UNCOMPARABLE c"test"
+c"stdClass" GREATER THAN r"STDOUT"
+c"stdClass" GREATER THAN r"STDERR"
+c"test" GREATER THAN null
+c"test" GREATER THAN false
+c"test" EQUAL true
+c"test" GREATER THAN i-5
+c"test" GREATER THAN i0
+c"test" LESS THAN i10
+c"test" GREATER THAN f-5
+c"test" GREATER THAN f0
+c"test" LESS THAN f2.5
+c"test" LESS THAN f10
+c"test" GREATER THAN s""
+c"test" GREATER THAN s"str"
+c"test" GREATER THAN s"-5"
+c"test" GREATER THAN s"0"
+c"test" GREATER THAN s"2.5"
+c"test" GREATER THAN s"10"
+c"test" GREATER THAN a[]
+c"test" GREATER THAN a[1, 2, 3]
+c"test" UNCOMPARABLE c"stdClass"
+c"test" EQUAL c"test"
+c"test" GREATER THAN r"STDOUT"
+c"test" GREATER THAN r"STDERR"
+r"STDOUT" GREATER THAN null
+r"STDOUT" GREATER THAN false
+r"STDOUT" EQUAL true
+r"STDOUT" GREATER THAN i-5
+r"STDOUT" GREATER THAN i0
+r"STDOUT" LESS THAN i10
+r"STDOUT" GREATER THAN f-5
+r"STDOUT" GREATER THAN f0
+r"STDOUT" LESS THAN f2.5
+r"STDOUT" LESS THAN f10
+r"STDOUT" GREATER THAN s""
+r"STDOUT" GREATER THAN s"str"
+r"STDOUT" GREATER THAN s"-5"
+r"STDOUT" GREATER THAN s"0"
+r"STDOUT" LESS THAN s"2.5"
+r"STDOUT" LESS THAN s"10"
+r"STDOUT" LESS THAN a[]
+r"STDOUT" LESS THAN a[1, 2, 3]
+r"STDOUT" LESS THAN c"stdClass"
+r"STDOUT" LESS THAN c"test"
+r"STDOUT" EQUAL r"STDOUT"
+r"STDOUT" LESS THAN r"STDERR"
+r"STDERR" GREATER THAN null
+r"STDERR" GREATER THAN false
+r"STDERR" EQUAL true
+r"STDERR" GREATER THAN i-5
+r"STDERR" GREATER THAN i0
+r"STDERR" LESS THAN i10
+r"STDERR" GREATER THAN f-5
+r"STDERR" GREATER THAN f0
+r"STDERR" GREATER THAN f2.5
+r"STDERR" LESS THAN f10
+r"STDERR" GREATER THAN s""
+r"STDERR" GREATER THAN s"str"
+r"STDERR" GREATER THAN s"-5"
+r"STDERR" GREATER THAN s"0"
+r"STDERR" GREATER THAN s"2.5"
+r"STDERR" LESS THAN s"10"
+r"STDERR" LESS THAN a[]
+r"STDERR" LESS THAN a[1, 2, 3]
+r"STDERR" LESS THAN c"stdClass"
+r"STDERR" LESS THAN c"test"
+r"STDERR" GREATER THAN r"STDOUT"
+r"STDERR" EQUAL r"STDERR"
diff --git a/Zend/tests/debug_info/debug_info-error-0.0.phpt b/Zend/tests/debug_info/debug_info-error-0.0.phpt
index ab41b440fc88..4df25c4c95af 100644
--- a/Zend/tests/debug_info/debug_info-error-0.0.phpt
+++ b/Zend/tests/debug_info/debug_info-error-0.0.phpt
@@ -17,4 +17,4 @@ $c = new C(0.0);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-0.phpt b/Zend/tests/debug_info/debug_info-error-0.phpt
index 37289c6468ff..c0cedf3b88e5 100644
--- a/Zend/tests/debug_info/debug_info-error-0.phpt
+++ b/Zend/tests/debug_info/debug_info-error-0.phpt
@@ -17,4 +17,4 @@ $c = new C(0);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-1.0.phpt b/Zend/tests/debug_info/debug_info-error-1.0.phpt
index 9b168621b5ef..6af945cb0e37 100644
--- a/Zend/tests/debug_info/debug_info-error-1.0.phpt
+++ b/Zend/tests/debug_info/debug_info-error-1.0.phpt
@@ -17,4 +17,4 @@ $c = new C(1.0);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-1.phpt b/Zend/tests/debug_info/debug_info-error-1.phpt
index ae01a6055c00..7cadd485118d 100644
--- a/Zend/tests/debug_info/debug_info-error-1.phpt
+++ b/Zend/tests/debug_info/debug_info-error-1.phpt
@@ -17,4 +17,4 @@ $c = new C(1);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt b/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt
new file mode 100644
index 000000000000..c2a6f4357a5c
--- /dev/null
+++ b/Zend/tests/debug_info/debug_info-error-case-insensitive.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Testing __debugInfo() magic method declared with non-canonical case
+--FILE--
+val;
+ }
+ public function __construct($val) {
+ $this->val = $val;
+ }
+}
+
+$c = new C(1);
+var_dump($c);
+?>
+--EXPECTF--
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-empty_str.phpt b/Zend/tests/debug_info/debug_info-error-empty_str.phpt
index bbab78cd8202..21611cc9bde5 100644
--- a/Zend/tests/debug_info/debug_info-error-empty_str.phpt
+++ b/Zend/tests/debug_info/debug_info-error-empty_str.phpt
@@ -17,4 +17,4 @@ $c = new C("");
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-false.phpt b/Zend/tests/debug_info/debug_info-error-false.phpt
index 3e48372c420c..fb5a35c2d1e3 100644
--- a/Zend/tests/debug_info/debug_info-error-false.phpt
+++ b/Zend/tests/debug_info/debug_info-error-false.phpt
@@ -17,4 +17,4 @@ $c = new C(false);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-object.phpt b/Zend/tests/debug_info/debug_info-error-object.phpt
index 42e073999908..5cdc6b289e62 100644
--- a/Zend/tests/debug_info/debug_info-error-object.phpt
+++ b/Zend/tests/debug_info/debug_info-error-object.phpt
@@ -17,4 +17,4 @@ $c = new C(new stdClass);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-resource.phpt b/Zend/tests/debug_info/debug_info-error-resource.phpt
index ccacce7a74b4..1fc84fe83d87 100644
--- a/Zend/tests/debug_info/debug_info-error-resource.phpt
+++ b/Zend/tests/debug_info/debug_info-error-resource.phpt
@@ -19,4 +19,4 @@ $c = new C(fopen("data:text/plain,Foo", 'r'));
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-str.phpt b/Zend/tests/debug_info/debug_info-error-str.phpt
index 85d3f63b97e0..fe4201de1c9f 100644
--- a/Zend/tests/debug_info/debug_info-error-str.phpt
+++ b/Zend/tests/debug_info/debug_info-error-str.phpt
@@ -17,4 +17,4 @@ $c = new C("foo");
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info-error-true.phpt b/Zend/tests/debug_info/debug_info-error-true.phpt
index 3843c6a7a14a..12bb7329a856 100644
--- a/Zend/tests/debug_info/debug_info-error-true.phpt
+++ b/Zend/tests/debug_info/debug_info-error-true.phpt
@@ -17,4 +17,4 @@ $c = new C(true);
var_dump($c);
?>
--EXPECTF--
-Fatal error: __debuginfo() must return an array in %s on line %d
+Fatal error: __debugInfo() must return an array in %s on line %d
diff --git a/Zend/tests/debug_info/debug_info.phpt b/Zend/tests/debug_info/debug_info.phpt
index bff5876777ba..e7bb4d48aec6 100644
--- a/Zend/tests/debug_info/debug_info.phpt
+++ b/Zend/tests/debug_info/debug_info.phpt
@@ -21,6 +21,12 @@ class Bar {
}
}
+class Baz {
+ public function __debugInfo(): ?array {
+ return null;
+ }
+}
+
$f = new Foo;
var_dump($f);
@@ -28,6 +34,7 @@ $b = new Bar;
var_dump($b);
?>
--EXPECTF--
+Deprecated: Returning null from Baz::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead in %s on line %d
object(Foo)#%d (3) {
["a"]=>
int(1)
diff --git a/Zend/tests/display_error_function_args.phpt b/Zend/tests/display_error_function_args.phpt
new file mode 100644
index 000000000000..03530eb98189
--- /dev/null
+++ b/Zend/tests/display_error_function_args.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Displaying function arguments in errors
+--INI--
+error_include_args=On
+--FILE--
+ "123456789012345678901" . chr(0), "cost" => 4];
+password_hash("test", PASSWORD_BCRYPT, $flags);
+
+ini_set("error_include_args", "Off");
+
+unlink('/');
+password_hash("test", PASSWORD_BCRYPT, $flags);
+
+?>
+--EXPECTF--
+Warning: unlink('/'): %s in %s on line %d
+
+Warning: password_hash(Object(SensitiveParameterValue), '2y', Array): The "salt" option has been ignored, since providing a custom salt is no longer supported in %s on line %d
+
+Warning: unlink(): %s in %s on line %d
+
+Warning: password_hash(): The "salt" option has been ignored, since providing a custom salt is no longer supported in %s on line %d
diff --git a/Zend/tests/enum/__debugInfo.phpt b/Zend/tests/enum/__debugInfo.phpt
deleted file mode 100644
index 33e0f49851f9..000000000000
--- a/Zend/tests/enum/__debugInfo.phpt
+++ /dev/null
@@ -1,16 +0,0 @@
---TEST--
-Enum __debugInfo
---FILE--
-cases();
- }
-}
-
-?>
---EXPECTF--
-Fatal error: Enum Foo cannot include magic method __debugInfo in %s on line %d
diff --git a/Zend/tests/enum/debugInfo/backed_enum_value.phpt b/Zend/tests/enum/debugInfo/backed_enum_value.phpt
new file mode 100644
index 000000000000..2bf56eb3a59e
--- /dev/null
+++ b/Zend/tests/enum/debugInfo/backed_enum_value.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum with __debugInfo() magic method - backed enum value accessible
+--FILE--
+name . ' = ' . $this->value];
+ }
+}
+
+var_dump(Foo::Bar);
+
+?>
+--EXPECT--
+enum(Foo::Bar) (1) {
+ [0]=>
+ string(14) "Foo::Bar = Baz"
+}
diff --git a/Zend/tests/enum/debugInfo/magic_method.phpt b/Zend/tests/enum/debugInfo/magic_method.phpt
new file mode 100644
index 000000000000..4c5963f73b26
--- /dev/null
+++ b/Zend/tests/enum/debugInfo/magic_method.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Enum supports __debugInfo() magic method
+--FILE--
+name . ' is a case of the ' . __CLASS__ . ' enum'];
+ }
+}
+
+var_dump(Foo::Bar);
+
+?>
+--EXPECT--
+enum(Foo::Bar) (1) {
+ [0]=>
+ string(29) "Bar is a case of the Foo enum"
+}
diff --git a/Zend/tests/enum/debugInfo/missing_magic.phpt b/Zend/tests/enum/debugInfo/missing_magic.phpt
new file mode 100644
index 000000000000..9cb5b0c69e52
--- /dev/null
+++ b/Zend/tests/enum/debugInfo/missing_magic.phpt
@@ -0,0 +1,14 @@
+--TEST--
+When an enum does not have __debugInfo() it is printed nicely
+--FILE--
+
+--EXPECT--
+enum(Foo::Bar)
diff --git a/Zend/tests/enum/debugInfo/param_validation.phpt b/Zend/tests/enum/debugInfo/param_validation.phpt
new file mode 100644
index 000000000000..84c60730822e
--- /dev/null
+++ b/Zend/tests/enum/debugInfo/param_validation.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Enum with __debugInfo() magic method - param validation
+--FILE--
+
+--EXPECTF--
+Fatal error: Method Foo::__debugInfo() cannot take arguments in %s on line %d
diff --git a/Zend/tests/enum/debugInfo/return_validation.phpt b/Zend/tests/enum/debugInfo/return_validation.phpt
new file mode 100644
index 000000000000..5583dcadf409
--- /dev/null
+++ b/Zend/tests/enum/debugInfo/return_validation.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Enum with __debugInfo() magic method - return validation
+--FILE--
+
+--EXPECTF--
+Fatal error: Foo::__debugInfo(): Return type must be ?array when declared in %s on line %d
diff --git a/Zend/tests/enum/debugInfo/visibility_validation.phpt b/Zend/tests/enum/debugInfo/visibility_validation.phpt
new file mode 100644
index 000000000000..97e2f2c7456b
--- /dev/null
+++ b/Zend/tests/enum/debugInfo/visibility_validation.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Enum with __debugInfo() magic method - visibility validation
+--FILE--
+
+--EXPECTF--
+Warning: The magic method Foo::__debugInfo() must have public visibility in %s on line %d
+enum(Foo::Bar) (0) {
+}
diff --git a/Zend/tests/enum/gh21760.phpt b/Zend/tests/enum/gh21760.phpt
new file mode 100644
index 000000000000..0835e3ca937d
--- /dev/null
+++ b/Zend/tests/enum/gh21760.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-21760 (Trait with class constant name conflict against enum case causes SEGV)
+--FILE--
+
+--EXPECTF--
+Fatal error: Cannot use trait X, because X::Up conflicts with enum case Direction::Up in %s on line %d
diff --git a/Zend/tests/fibers/gh19983.phpt b/Zend/tests/fibers/gh19983.phpt
new file mode 100644
index 000000000000..554a7c8bf8ef
--- /dev/null
+++ b/Zend/tests/fibers/gh19983.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-19983 (GC Assertion Failure with fibers, generators and destructors)
+--SKIPIF--
+
+--INI--
+memory_limit=16M
+--FILE--
+current();
+ });
+ $fiber->start();
+ }
+}
+new a;
+?>
+--EXPECTF--
+Fatal error: Allowed memory size of %d bytes exhausted%s
diff --git a/Zend/tests/first_class_callable/constexpr/gh22782.inc b/Zend/tests/first_class_callable/constexpr/gh22782.inc
new file mode 100644
index 000000000000..d67ba4eab14e
--- /dev/null
+++ b/Zend/tests/first_class_callable/constexpr/gh22782.inc
@@ -0,0 +1,18 @@
+
+--FILE--
+getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method attr\n";
+var_dump((new ReflectionMethod(C::class, 'f')->getAttributes(A::class)[0]->newInstance()->fn)('hello'));
+echo "# Method default value\n";
+var_dump((new C()->f())('hello'));
+
+?>
+--EXPECT--
+# Class const
+int(5)
+# Class attr
+int(5)
+# Method attr
+int(5)
+# Method default value
+int(5)
diff --git a/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt b/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt
deleted file mode 100644
index 74e36a9ad0df..000000000000
--- a/Zend/tests/first_class_callable/first_class_callable_non_unary_error.phpt
+++ /dev/null
@@ -1,10 +0,0 @@
---TEST--
-First class callable error: more than one argument
---FILE--
-
---EXPECTF--
-Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d
diff --git a/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt b/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt
deleted file mode 100644
index efbd13b7593b..000000000000
--- a/Zend/tests/first_class_callable/first_class_callable_non_variadic_error.phpt
+++ /dev/null
@@ -1,10 +0,0 @@
---TEST--
-First class callable error: non-variadic placeholder
---FILE--
-
---EXPECTF--
-Fatal error: Cannot create a Closure for call expression with more than one argument, or non-variadic placeholders in %s on line %d
diff --git a/Zend/tests/gh11735_2.phpt b/Zend/tests/gh11735_2.phpt
index b568b6f6df37..521862b48237 100644
--- a/Zend/tests/gh11735_2.phpt
+++ b/Zend/tests/gh11735_2.phpt
@@ -13,5 +13,5 @@ stream_wrapper_register('foo', 'FooWrapper');
var_dump(fopen('foo://bar', 'r'));
?>
--EXPECTF--
-Warning: fopen(foo://bar): Failed to open stream: "FooWrapper::stream_open" call failed in %s on line %d
+Warning: fopen(): Failed to open stream: "FooWrapper::stream_open" call failed in %s on line %d
bool(false)
diff --git a/Zend/tests/gh16799.phpt b/Zend/tests/gh16799.phpt
index ce1dbd5b1655..d31d1a5705c6 100644
--- a/Zend/tests/gh16799.phpt
+++ b/Zend/tests/gh16799.phpt
@@ -15,7 +15,12 @@ Test::test();
--EXPECTF--
Fatal error: Uncaught Exception: Use of "static" in callables is deprecated in %s:%d
Stack trace:
-#0 %s(%d): {closure:%s:%d}(8192, 'Use of "static"...', %s, %d)
+#0 %s(%d): {closure:%s}(8192, 'Use of "static"%s', '%s', %d)
#1 %s(%d): Test::test()
#2 {main}
+
+Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d
+Stack trace:
+#0 %s(%d): Test::test()
+#1 {main}
thrown in %s on line %d
diff --git a/Zend/tests/gh21504.inc b/Zend/tests/gh21504.inc
new file mode 100644
index 000000000000..71dc5c2e61f6
--- /dev/null
+++ b/Zend/tests/gh21504.inc
@@ -0,0 +1,3 @@
+ var_dump(...);
diff --git a/Zend/tests/gh21504.phpt b/Zend/tests/gh21504.phpt
new file mode 100644
index 000000000000..5fd9eaff91ab
--- /dev/null
+++ b/Zend/tests/gh21504.phpt
@@ -0,0 +1,12 @@
+--TEST--
+GH-21504: Incorrect RC-handling for ZEND_EXT_STMT op1
+--FILE--
+
+--EXPECT--
+string(4) "1234"
diff --git a/Zend/tests/gh21603.phpt b/Zend/tests/gh21603.phpt
new file mode 100644
index 000000000000..ab2fbc44e797
--- /dev/null
+++ b/Zend/tests/gh21603.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-21603: Missing addref for __unset
+--CREDITS--
+cnwangjihe
+--FILE--
+prop);
+
+?>
+--EXPECTF--
+object(C)#%d (0) {
+}
diff --git a/Zend/tests/gh21605.phpt b/Zend/tests/gh21605.phpt
new file mode 100644
index 000000000000..973339ad3bb9
--- /dev/null
+++ b/Zend/tests/gh21605.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-21605: Missing addref for Countable::count()
+--CREDITS--
+cnwangjihe
+--FILE--
+
+--EXPECTF--
+object(C)#%d (0) {
+}
+int(42)
diff --git a/Zend/tests/gh22257.phpt b/Zend/tests/gh22257.phpt
new file mode 100644
index 000000000000..9bbf3b1f8d35
--- /dev/null
+++ b/Zend/tests/gh22257.phpt
@@ -0,0 +1,38 @@
+--TEST--
+GH-22257 (Type confusion / OOB read unserializing an Exception with a non-array trace)
+--CREDITS--
+Igor Sak-Sakovskiy (Positive Technologies)
+--FILE--
+getPrevious()) {
+ printf("%s: %s\n", $e::class, $e->getMessage());
+ }
+}
+
+/* By-ref type violation: the slot is reset to the property default. */
+class Test { public int $i; public array $a; }
+try {
+ var_dump(unserialize('O:4:"Test":2:{s:1:"i";N;s:1:"a";R:2;}'));
+} catch (\Throwable $e) {
+ printf("%s: %s\n", $e::class, $e->getMessage());
+}
+echo "OK\n";
+?>
+--EXPECTF--
+Warning: unserialize(): Error at offset %d of %d bytes in %s on line %d
+TypeError: Cannot assign null to property Test::$i of type int
+OK
diff --git a/Zend/tests/gh22849.phpt b/Zend/tests/gh22849.phpt
new file mode 100644
index 000000000000..a38c64baf542
--- /dev/null
+++ b/Zend/tests/gh22849.phpt
@@ -0,0 +1,31 @@
+--TEST--
+PHP 8.6 error_include_args=1 mishandled for include()
+--INI--
+error_include_args=On
+--FILE--
+
+--EXPECTF--
+Warning: include('does not exist'): Failed to open stream: No such file or directory in %s on line %d
+
+Warning: include('does not exist'): Failed opening 'does not exist' for inclusion (include_path='%s') in %s on line %d
+
+Warning: include_once('does not exist'): Failed to open stream: No such file or directory in %s on line %d
+
+Warning: include_once('does not exist'): Failed opening 'does not exist' for inclusion (include_path='%s') in %s on line %d
+
+Warning: require('does not exist'): Failed to open stream: No such file or directory in %s on line %d
+
+Fatal error: Uncaught Error: Failed opening required 'does not exist' (include_path='%s') in %s:%d
+Stack trace:
+#0 %s(%d): foo(1, 2)
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/gh_21699.phpt b/Zend/tests/gh_21699.phpt
new file mode 100644
index 000000000000..49b58365dabf
--- /dev/null
+++ b/Zend/tests/gh_21699.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-21699: Assertion failure in shutdown_executor when error handler throws during self:: callable resolution
+--FILE--
+test();
+?>
+--EXPECTF--
+Fatal error: Uncaught Exception in %s:%d
+Stack trace:
+#0 %s(%d): {closure:%s}(%d, 'Use of "self" i%s', '%s', %d)
+#1 %s(%d): bar->test()
+#2 {main}
+
+Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d
+Stack trace:
+#0 %s(%d): bar->test()
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/gh_21699_parent.phpt b/Zend/tests/gh_21699_parent.phpt
new file mode 100644
index 000000000000..73cae41f3f5b
--- /dev/null
+++ b/Zend/tests/gh_21699_parent.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-21699 (parent::): no shutdown_executor trampoline assertion when error handler throws during parent:: callable resolution
+--FILE--
+test();
+?>
+--EXPECTF--
+Fatal error: Uncaught Exception in %s:%d
+Stack trace:
+#0 %s(%d): {closure:%s}(%d, 'Use of "parent"%s', '%s', %d)
+#1 %s(%d): Child->test()
+#2 {main}
+
+Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d
+Stack trace:
+#0 %s(%d): Child->test()
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/gh_21699_static.phpt b/Zend/tests/gh_21699_static.phpt
new file mode 100644
index 000000000000..4d9604ebe77b
--- /dev/null
+++ b/Zend/tests/gh_21699_static.phpt
@@ -0,0 +1,31 @@
+--TEST--
+GH-21699 (static::): no shutdown_executor trampoline assertion when error handler throws during static:: callable resolution
+--FILE--
+test();
+?>
+--EXPECTF--
+Fatal error: Uncaught Exception in %s:%d
+Stack trace:
+#0 %s(%d): {closure:%s}(%d, 'Use of "static"%s', '%s', %d)
+#1 %s(%d): bar->test()
+#2 {main}
+
+Next TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, (null) in %s:%d
+Stack trace:
+#0 %s(%d): bar->test()
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/include_stat_is_quiet.phpt b/Zend/tests/include_stat_is_quiet.phpt
index 75ba8f3d6273..39eb3053fbe4 100644
--- a/Zend/tests/include_stat_is_quiet.phpt
+++ b/Zend/tests/include_stat_is_quiet.phpt
@@ -26,7 +26,7 @@ try {
?>
--EXPECTF--
-Warning: require_once(doesnt_exist.php): Failed to open stream: No such file or directory in %s on line %d
+Warning: require_once(): Failed to open stream: No such file or directory in %s on line %d
Fatal error: Uncaught Error: Failed opening required 'doesnt_exist.php' (include_path='test://foo:test://bar') in %s:%d
Stack trace:
diff --git a/Zend/tests/lazy_objects/gh18038-002.phpt b/Zend/tests/lazy_objects/gh18038-002.phpt
index 4c12f21de811..d363731c62a0 100644
--- a/Zend/tests/lazy_objects/gh18038-002.phpt
+++ b/Zend/tests/lazy_objects/gh18038-002.phpt
@@ -34,5 +34,4 @@ var_dump($obj->prop);
--EXPECT--
init
string(19) "RealInstance::__set"
-string(12) "Proxy::__set"
int(2)
diff --git a/Zend/tests/lazy_objects/gh18038-004.phpt b/Zend/tests/lazy_objects/gh18038-004.phpt
index 8810efb6bec2..c1495c5a6d8d 100644
--- a/Zend/tests/lazy_objects/gh18038-004.phpt
+++ b/Zend/tests/lazy_objects/gh18038-004.phpt
@@ -36,7 +36,6 @@ var_dump($real->prop);
--EXPECTF--
init
string(19) "RealInstance::__get"
-string(12) "Proxy::__get"
Warning: Undefined property: RealInstance::$prop in %s on line %d
NULL
diff --git a/Zend/tests/lazy_objects/gh18038-007.phpt b/Zend/tests/lazy_objects/gh18038-007.phpt
index 9925190a1980..4c7c0d0b4b0a 100644
--- a/Zend/tests/lazy_objects/gh18038-007.phpt
+++ b/Zend/tests/lazy_objects/gh18038-007.phpt
@@ -36,6 +36,5 @@ var_dump(isset($real->prop['']));
--EXPECT--
init
string(21) "RealInstance::__isset"
-string(14) "Proxy::__isset"
bool(false)
bool(false)
diff --git a/Zend/tests/lazy_objects/gh18038-009.phpt b/Zend/tests/lazy_objects/gh18038-009.phpt
index 3c165a71ccff..11067cdb970b 100644
--- a/Zend/tests/lazy_objects/gh18038-009.phpt
+++ b/Zend/tests/lazy_objects/gh18038-009.phpt
@@ -36,6 +36,5 @@ var_dump(isset($real->prop));
--EXPECT--
init
string(21) "RealInstance::__isset"
-string(14) "Proxy::__isset"
bool(false)
bool(false)
diff --git a/Zend/tests/lazy_objects/gh20854.phpt b/Zend/tests/lazy_objects/gh20854.phpt
new file mode 100644
index 000000000000..26a69cfcbbf5
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20854.phpt
@@ -0,0 +1,22 @@
+--TEST--
+GH-20854 (Assertion in ZEND_RETURN_BY_REF with lazy proxy and return-by-ref __get)
+--FILE--
+x;
+ }
+}
+
+$rc = new ReflectionClass(C::class);
+$obj = $rc->newLazyProxy(function () {
+ return new C;
+});
+$obj->x;
+echo "Done\n";
+?>
+--EXPECTF--
+Deprecated: Creation of dynamic property C::$x is deprecated in %s on line %d
+Done
diff --git a/Zend/tests/lazy_objects/gh20873.phpt b/Zend/tests/lazy_objects/gh20873.phpt
new file mode 100644
index 000000000000..e5b8c31e9130
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20873.phpt
@@ -0,0 +1,30 @@
+--TEST--
+GH-20873 (Assertion failure in _zendi_try_convert_scalar_to_number with lazy proxy)
+--FILE--
+x =& $this->_;
+ static $a = $a;
+ $e =& $this->_ - $a;
+ }
+}
+$rc = new ReflectionClass(A::class);
+$obj = $rc->newLazyProxy(fn() => new A);
+$rc->initializeLazyObject($obj);
+var_dump($obj->p);
+?>
+--EXPECTF--
+Deprecated: Creation of dynamic property A::$x is deprecated in %s on line %d
+
+Warning: Undefined variable $a in %s on line %d
+
+Notice: Indirect modification of overloaded property A::$x has no effect in %s on line %d
+
+Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d
+Stack trace:
+#0 %s(%d): A->__get('p')
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/lazy_objects/gh20875.phpt b/Zend/tests/lazy_objects/gh20875.phpt
new file mode 100644
index 000000000000..ff036edabd59
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20875.phpt
@@ -0,0 +1,41 @@
+--TEST--
+GH-20875 (Assertion failure in _get_zval_ptr_tmp with lazy proxy)
+--FILE--
+f =& $this->b - $x > $y = new StdClass;
+ static $a = $a;
+ $t = 'x';
+ foreach (get_defined_vars() as $key => $e) {}
+ if ($v ==!1) $x = $a ?: $t = "ok";
+ }
+}
+$rc = new ReflectionClass(A::class);
+$obj = $rc->newLazyProxy(function () { return new A; });
+$real = $rc->initializeLazyObject($obj);
+var_dump($real->prop);
+?>
+--EXPECTF--
+Deprecated: Creation of dynamic property A::$b is deprecated in %s on line %d
+
+Deprecated: Creation of dynamic property A::$f is deprecated in %s on line %d
+
+Warning: Undefined variable $x in %s on line %d
+
+Notice: Object of class stdClass could not be converted to int in %s on line %d
+
+Warning: Undefined variable $a in %s on line %d
+
+Warning: Undefined variable $v in %s on line %d
+
+Notice: Indirect modification of overloaded property A::$f has no effect in %s on line %d
+
+Fatal error: Uncaught Error: Cannot assign by reference to overloaded object in %s:%d
+Stack trace:
+#0 %s(%d): A->__get('b')
+#1 %s(%d): A->__get('prop')
+#2 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/lazy_objects/gh20875_proxy_get_no_init.phpt b/Zend/tests/lazy_objects/gh20875_proxy_get_no_init.phpt
new file mode 100644
index 000000000000..d5d9754864b4
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh20875_proxy_get_no_init.phpt
@@ -0,0 +1,23 @@
+--TEST--
+GH-20875 (Lazy proxy should not initialize when __get handles dynamic property access)
+--FILE--
+newLazyProxy(function () {
+ echo "init\n";
+ return new Foo();
+});
+$x = &$proxy->x;
+
+?>
+--EXPECT--
+__get
diff --git a/Zend/tests/lazy_objects/gh21478-isset.phpt b/Zend/tests/lazy_objects/gh21478-isset.phpt
new file mode 100644
index 000000000000..9138984af01b
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh21478-isset.phpt
@@ -0,0 +1,30 @@
+--TEST--
+GH-21478: __isset on lazy proxy should not double-invoke when real instance guard is set
+--FILE--
+{$name});
+ }
+}
+
+class Bar extends Foo {}
+
+$rc = new ReflectionClass(Bar::class);
+$proxy = $rc->newLazyProxy(function () {
+ echo "Init\n";
+ return new Foo();
+});
+
+$real = $rc->initializeLazyObject($proxy);
+isset($real->x);
+
+?>
+--EXPECT--
+Init
+__isset($x) on Foo
diff --git a/Zend/tests/lazy_objects/gh21478-proxy-get-override.phpt b/Zend/tests/lazy_objects/gh21478-proxy-get-override.phpt
new file mode 100644
index 000000000000..520c8f662353
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh21478-proxy-get-override.phpt
@@ -0,0 +1,30 @@
+--TEST--
+GH-21478: Proxy's own __get runs when accessed directly (not from real instance)
+--FILE--
+newLazyProxy(function () {
+ return new Foo();
+});
+$rc->initializeLazyObject($proxy);
+
+$proxy->x;
+
+?>
+--EXPECT--
+Bar x
diff --git a/Zend/tests/lazy_objects/gh21478-proxy-get-ref-forward.phpt b/Zend/tests/lazy_objects/gh21478-proxy-get-ref-forward.phpt
new file mode 100644
index 000000000000..fa737cf18f2e
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh21478-proxy-get-ref-forward.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-21478: No assertion failure when &__get forwards through initialized lazy proxy
+--FILE--
+{$name};
+ }
+}
+
+class Bar extends Foo {}
+
+$rc = new ReflectionClass(Bar::class);
+$proxy = $rc->newLazyProxy(function () {
+ echo "Init\n";
+ return new Foo();
+});
+
+$real = $rc->initializeLazyObject($proxy);
+$a = &$real->x;
+var_dump($a);
+?>
+--EXPECTF--
+Init
+Foo::__get($x) on Foo
+
+Warning: Undefined property: Foo::$x in %s on line %d
+NULL
diff --git a/Zend/tests/lazy_objects/gh21478-set.phpt b/Zend/tests/lazy_objects/gh21478-set.phpt
new file mode 100644
index 000000000000..0b2f872de11a
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh21478-set.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-21478: __set on lazy proxy should not double-invoke when real instance guard is set
+--FILE--
+{$name} = $value;
+ }
+}
+
+#[AllowDynamicProperties]
+class Bar extends Foo {}
+
+$rc = new ReflectionClass(Bar::class);
+$proxy = $rc->newLazyProxy(function () {
+ echo "Init\n";
+ return new Foo();
+});
+
+$real = $rc->initializeLazyObject($proxy);
+$real->x = 1;
+
+?>
+--EXPECT--
+Init
+__set($x) on Foo
diff --git a/Zend/tests/lazy_objects/gh21478-unset.phpt b/Zend/tests/lazy_objects/gh21478-unset.phpt
new file mode 100644
index 000000000000..5febbd235d82
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh21478-unset.phpt
@@ -0,0 +1,30 @@
+--TEST--
+GH-21478: __unset on lazy proxy should not double-invoke when real instance guard is set
+--FILE--
+{$name});
+ }
+}
+
+class Bar extends Foo {}
+
+$rc = new ReflectionClass(Bar::class);
+$proxy = $rc->newLazyProxy(function () {
+ echo "Init\n";
+ return new Foo();
+});
+
+$real = $rc->initializeLazyObject($proxy);
+unset($real->x);
+
+?>
+--EXPECT--
+Init
+__unset($x) on Foo
diff --git a/Zend/tests/lazy_objects/gh21478.phpt b/Zend/tests/lazy_objects/gh21478.phpt
new file mode 100644
index 000000000000..aaa226a9a09a
--- /dev/null
+++ b/Zend/tests/lazy_objects/gh21478.phpt
@@ -0,0 +1,32 @@
+--TEST--
+GH-21478 (Property access on lazy proxy may invoke magic method despite real instance guards)
+--FILE--
+{$name};
+ }
+}
+
+class Bar extends Foo {}
+
+$rc = new ReflectionClass(Bar::class);
+$proxy = $rc->newLazyProxy(function () {
+ echo "Init\n";
+ return new Foo();
+});
+
+$real = $rc->initializeLazyObject($proxy);
+$real->x;
+
+?>
+--EXPECTF--
+Init
+__get($x) on Foo
+
+Warning: Undefined property: Foo::$x in %s on line %d
diff --git a/Zend/tests/magic_methods/constructor_destructor_return.phpt b/Zend/tests/magic_methods/constructor_destructor_return.phpt
new file mode 100644
index 000000000000..635548bb6f47
--- /dev/null
+++ b/Zend/tests/magic_methods/constructor_destructor_return.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Returning values from constructors and destructors is deprecated
+--FILE--
+voidMethod(); }
+ public function __destruct() { return $this->voidMethod(); }
+
+ public function voidMethod(): void { }
+}
+
+class Gen {
+ public function __construct() { yield ''; }
+ public function __destruct() { yield ''; }
+}
+
+?>
+--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a destructor is deprecated in %s on line %d
+
+Deprecated: Making a constructor a Generator is deprecated in %s on line %d
+
+Deprecated: Making a destructor a Generator is deprecated in %s on line %d
diff --git a/Zend/tests/magic_methods/gh12695.phpt b/Zend/tests/magic_methods/gh12695.phpt
new file mode 100644
index 000000000000..957f474fb6a1
--- /dev/null
+++ b/Zend/tests/magic_methods/gh12695.phpt
@@ -0,0 +1,65 @@
+--TEST--
+GH-12695: ?? on unset property does not call __get() when __isset() materialized the property
+--FILE--
+$n = 123;
+ return true;
+ }
+}
+
+echo "Dynamic property materialised in __isset, then `??`:\n";
+$a = new A;
+var_dump($a->foo ?? 'fallback');
+
+echo "\nSame on a declared (unset) property:\n";
+class B {
+ public int $x = 99;
+ public function __get($n) {
+ throw new Exception("__get must not be called when __isset materialised the property");
+ }
+ public function __isset($n) {
+ echo " __isset($n)\n";
+ $this->$n = 7;
+ return true;
+ }
+}
+$b = new B;
+unset($b->x);
+var_dump($b->x ?? 'fallback');
+
+echo "\nWhen __isset() materialises the property to null, `??` falls back:\n";
+#[AllowDynamicProperties]
+class D {
+ public function __get($n) {
+ throw new Exception("__get must not be called when __isset materialised the property");
+ }
+ public function __isset($n) {
+ echo " __isset($n)\n";
+ $this->$n = null;
+ return true;
+ }
+}
+$d = new D;
+var_dump($d->foo ?? 'fallback');
+
+?>
+--EXPECT--
+Dynamic property materialised in __isset, then `??`:
+ __isset(foo)
+int(123)
+
+Same on a declared (unset) property:
+ __isset(x)
+int(7)
+
+When __isset() materialises the property to null, `??` falls back:
+ __isset(foo)
+string(8) "fallback"
diff --git a/Zend/tests/magic_methods/gh12695_empty.phpt b/Zend/tests/magic_methods/gh12695_empty.phpt
new file mode 100644
index 000000000000..99806820cc6f
--- /dev/null
+++ b/Zend/tests/magic_methods/gh12695_empty.phpt
@@ -0,0 +1,55 @@
+--TEST--
+GH-12695: empty() on unset property does not call __get() when __isset() materialized the property
+--FILE--
+$n = $GLOBALS['next_value'];
+ return true;
+ }
+}
+
+echo "empty() when __isset materialised a truthy value: __get is not called, empty=false:\n";
+$GLOBALS['next_value'] = 7;
+$a = new A;
+var_dump(empty($a->foo));
+
+echo "\nempty() when __isset materialised a falsy value: __get is not called, empty=true:\n";
+$GLOBALS['next_value'] = 0;
+$a = new A;
+var_dump(empty($a->bar));
+
+echo "\nempty() with no materialization: __get is still called (legacy path preserved):\n";
+class B {
+ public function __get($n) {
+ echo " __get($n)\n";
+ return 'value';
+ }
+ public function __isset($n) {
+ echo " __isset($n)\n";
+ return true;
+ }
+}
+$b = new B;
+var_dump(empty($b->any));
+
+?>
+--EXPECT--
+empty() when __isset materialised a truthy value: __get is not called, empty=false:
+ __isset(foo)
+bool(false)
+
+empty() when __isset materialised a falsy value: __get is not called, empty=true:
+ __isset(bar)
+bool(true)
+
+empty() with no materialization: __get is still called (legacy path preserved):
+ __isset(any)
+ __get(any)
+bool(false)
diff --git a/Zend/tests/magic_methods/gh12695_no_materialization.phpt b/Zend/tests/magic_methods/gh12695_no_materialization.phpt
new file mode 100644
index 000000000000..700b605e2984
--- /dev/null
+++ b/Zend/tests/magic_methods/gh12695_no_materialization.phpt
@@ -0,0 +1,65 @@
+--TEST--
+GH-12695: __get() invocation is based on __isset()'s return value
+--FILE--
+any ?? 'fallback');
+
+echo "\n`??` when __isset=true and __get returns null: __get is called and fallback is used:\n";
+class D {
+ public function __get($n) {
+ echo " __get($n)\n";
+ return null;
+ }
+ public function __isset($n) {
+ echo " __isset($n)\n";
+ return true;
+ }
+}
+$d = new D;
+var_dump($d->any ?? 'fallback');
+
+echo "\n`??` when __isset returns false: __get is not called:\n";
+class E {
+ public function __get($n) {
+ throw new Exception("__get must not be called when __isset returned false");
+ }
+ public function __isset($n) {
+ echo " __isset($n)\n";
+ return false;
+ }
+}
+$e = new E;
+var_dump($e->any ?? 'fallback');
+
+?>
+--EXPECT--
+`??` when __isset=true and __get returns a value: __get is called:
+ __isset(any)
+ __get(any)
+string(8) "from-get"
+
+`??` when __isset=true and __get returns null: __get is called and fallback is used:
+ __isset(any)
+ __get(any)
+string(8) "fallback"
+
+`??` when __isset returns false: __get is not called:
+ __isset(any)
+string(8) "fallback"
diff --git a/Zend/tests/magic_methods/gh12695_object_released_in_isset.phpt b/Zend/tests/magic_methods/gh12695_object_released_in_isset.phpt
new file mode 100644
index 000000000000..911ffde637a9
--- /dev/null
+++ b/Zend/tests/magic_methods/gh12695_object_released_in_isset.phpt
@@ -0,0 +1,28 @@
+--TEST--
+GH-12695: Object freed by __isset() during materialization
+--FILE--
+prop = 'materialised';
+ return true;
+ }
+}
+
+$obj = new C();
+unset($obj->prop);
+var_dump($obj->prop ?? 'fb');
+
+?>
+--EXPECT--
+string(12) "materialised"
diff --git a/Zend/tests/magic_methods/magic_methods_inheritance_rules.phpt b/Zend/tests/magic_methods/magic_methods_inheritance_rules.phpt
index 6bdcafb1bfb5..a4076ee00ce2 100644
--- a/Zend/tests/magic_methods/magic_methods_inheritance_rules.phpt
+++ b/Zend/tests/magic_methods/magic_methods_inheritance_rules.phpt
@@ -66,5 +66,6 @@ class WidenedArgumentType extends NarrowedReturnType {
echo 'No problems!';
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning null from ValidMagicMethods::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead in %s on line %d
No problems!
diff --git a/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt b/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt
new file mode 100644
index 000000000000..7f4564794452
--- /dev/null
+++ b/Zend/tests/magic_methods/stringable_automatic_implementation_case_insensitive.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Stringable is automatically implemented for __toString() declared with non-canonical case
+--FILE--
+getInterfaceNames());
+var_dump((string) new Test);
+
+?>
+--EXPECT--
+bool(true)
+array(1) {
+ [0]=>
+ string(10) "Stringable"
+}
+string(3) "foo"
diff --git a/Zend/tests/oss_fuzz_64209.phpt b/Zend/tests/oss_fuzz_64209.phpt
index 599ae258e5b2..56b9dcfc7a24 100644
--- a/Zend/tests/oss_fuzz_64209.phpt
+++ b/Zend/tests/oss_fuzz_64209.phpt
@@ -5,7 +5,7 @@ oss-fuzz #64209: Fix in-place modification of filename in php_message_handler_fo
require '://@';
?>
--EXPECTF--
-Warning: require(://@): Failed to open stream: No such file or directory in %s on line %d
+Warning: require(): Failed to open stream: No such file or directory in %s on line %d
Fatal error: Uncaught Error: Failed opening required '://@' (include_path='%s') in %s:%d
Stack trace:
diff --git a/Zend/tests/partial_application/assert.phpt b/Zend/tests/partial_application/assert.phpt
new file mode 100644
index 000000000000..166cc5022eb2
--- /dev/null
+++ b/Zend/tests/partial_application/assert.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA of assert() behaves like a dynamic call to assert()
+--FILE--
+getMessage(), "\n";
+}
+
+try {
+ echo "# Dynamic call:\n";
+ (function ($f) { $f(false); })('assert');
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
+}
+
+try {
+ echo "# PFA call:\n";
+ $f = assert(?);
+ $f(false);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
+}
+
+try {
+ echo "# Upper-case assert():\n";
+ $f = ASSERT(?);
+ $f(false);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage() ?: '(no message)', "\n";
+}
+
+?>
+--EXPECT--
+# Static call:
+AssertionError: assert(false)
+# Dynamic call:
+AssertionError: (no message)
+# PFA call:
+AssertionError: (no message)
+# Upper-case assert():
+AssertionError: (no message)
diff --git a/Zend/tests/partial_application/attributes_001.phpt b/Zend/tests/partial_application/attributes_001.phpt
new file mode 100644
index 000000000000..827ad41321ec
--- /dev/null
+++ b/Zend/tests/partial_application/attributes_001.phpt
@@ -0,0 +1,88 @@
+--TEST--
+PFA inherits NoDiscard and SensitiveParameter attributes
+--FILE--
+getAttributes());
+
+ foreach ($r->getParameters() as $i => $p) {
+ echo "Parameter $i:\n";
+ var_dump($p->getAttributes());
+ }
+}
+
+echo "# Orig attributes:\n";
+
+dump_attributes('f');
+
+$f = f(1, ?, ?, ...);
+
+echo "# PFA attributes:\n";
+
+dump_attributes($f);
+
+?>
+--EXPECTF--
+# Orig attributes:
+array(2) {
+ [0]=>
+ object(ReflectionAttribute)#%d (1) {
+ ["name"]=>
+ string(9) "NoDiscard"
+ }
+ [1]=>
+ object(ReflectionAttribute)#%d (1) {
+ ["name"]=>
+ string(4) "Test"
+ }
+}
+Parameter 0:
+array(0) {
+}
+Parameter 1:
+array(1) {
+ [0]=>
+ object(ReflectionAttribute)#%d (1) {
+ ["name"]=>
+ string(18) "SensitiveParameter"
+ }
+}
+Parameter 2:
+array(1) {
+ [0]=>
+ object(ReflectionAttribute)#%d (1) {
+ ["name"]=>
+ string(4) "Test"
+ }
+}
+# PFA attributes:
+array(1) {
+ [0]=>
+ object(ReflectionAttribute)#%d (1) {
+ ["name"]=>
+ string(9) "NoDiscard"
+ }
+}
+Parameter 0:
+array(1) {
+ [0]=>
+ object(ReflectionAttribute)#%d (1) {
+ ["name"]=>
+ string(18) "SensitiveParameter"
+ }
+}
+Parameter 1:
+array(0) {
+}
+Parameter 2:
+array(0) {
+}
diff --git a/Zend/tests/partial_application/attributes_002.phpt b/Zend/tests/partial_application/attributes_002.phpt
new file mode 100644
index 000000000000..be1f1612f938
--- /dev/null
+++ b/Zend/tests/partial_application/attributes_002.phpt
@@ -0,0 +1,19 @@
+--TEST--
+PFA preserves #[SensitiveParameter]
+--FILE--
+
+--EXPECTF--
+Fatal error: Uncaught Exception in %s:%d
+Stack trace:
+#0 %s(%d): f(1, Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
+#1 %s(%d): {closure:pfa:%s:7}(Object(SensitiveParameterValue), 3, Object(SensitiveParameterValue), Object(SensitiveParameterValue))
+#2 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/partial_application/attributes_003.phpt b/Zend/tests/partial_application/attributes_003.phpt
new file mode 100644
index 000000000000..d113d02eb8f7
--- /dev/null
+++ b/Zend/tests/partial_application/attributes_003.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA preserves #[NoDiscard]
+--FILE--
+
+--EXPECTF--
+Warning: The return value of function {closure:%s}() should either be used or intentionally ignored by casting it as (void) in %s on line 7
diff --git a/Zend/tests/partial_application/bound_arg_strict_types.phpt b/Zend/tests/partial_application/bound_arg_strict_types.phpt
new file mode 100644
index 000000000000..d7e2ddcae5c4
--- /dev/null
+++ b/Zend/tests/partial_application/bound_arg_strict_types.phpt
@@ -0,0 +1,26 @@
+--TEST--
+Partial application checks bound arguments with the creating scope's strict_types
+--FILE--
+getMessage(), "\n";
+}
+
+function make() {
+ try {
+ f("3", ?);
+ } catch (\TypeError $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+ }
+}
+make();
+?>
+--EXPECT--
+TypeError: f(): Argument #1 ($a) must be of type int, string given
+TypeError: f(): Argument #1 ($a) must be of type int, string given
diff --git a/Zend/tests/partial_application/clone.phpt b/Zend/tests/partial_application/clone.phpt
new file mode 100644
index 000000000000..f778d776b717
--- /dev/null
+++ b/Zend/tests/partial_application/clone.phpt
@@ -0,0 +1,50 @@
+--TEST--
+clone() can be partially applied
+--FILE--
+ 7]));
+
+$clone = clone(?, ['a' => 8]);
+var_dump($clone(new C(9, 10)));
+
+?>
+--EXPECTF--
+object(C)#%d (2) {
+ ["a"]=>
+ int(1)
+ ["b"]=>
+ int(2)
+}
+object(C)#%d (2) {
+ ["a"]=>
+ int(3)
+ ["b"]=>
+ int(4)
+}
+object(C)#%d (2) {
+ ["a"]=>
+ int(7)
+ ["b"]=>
+ int(6)
+}
+object(C)#%d (2) {
+ ["a"]=>
+ int(8)
+ ["b"]=>
+ int(10)
+}
diff --git a/Zend/tests/partial_application/closure_rt_cache_lifetime.phpt b/Zend/tests/partial_application/closure_rt_cache_lifetime.phpt
new file mode 100644
index 000000000000..7eb4810cf00a
--- /dev/null
+++ b/Zend/tests/partial_application/closure_rt_cache_lifetime.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Partial application over a Closure::fromCallable() must not double-free the run_time_cache
+--FILE--
+
+--EXPECT--
+int(15)
+OK
diff --git a/Zend/tests/partial_application/compile_errors_001.phpt b/Zend/tests/partial_application/compile_errors_001.phpt
new file mode 100644
index 000000000000..f08495a1f1e5
--- /dev/null
+++ b/Zend/tests/partial_application/compile_errors_001.phpt
@@ -0,0 +1,8 @@
+--TEST--
+PFA compile errors: multiple variadic placeholders
+--FILE--
+
+--EXPECTF--
+Fatal error: Variadic placeholder may only appear once in %s on line %d
diff --git a/Zend/tests/partial_application/compile_errors_002.phpt b/Zend/tests/partial_application/compile_errors_002.phpt
new file mode 100644
index 000000000000..b6a2073a8363
--- /dev/null
+++ b/Zend/tests/partial_application/compile_errors_002.phpt
@@ -0,0 +1,8 @@
+--TEST--
+PFA compile errors: variadic placeholder must be last
+--FILE--
+
+--EXPECTF--
+Fatal error: Variadic placeholder must be last in %s on line %d
diff --git a/Zend/tests/partial_application/compile_errors_003.phpt b/Zend/tests/partial_application/compile_errors_003.phpt
new file mode 100644
index 000000000000..26ff8435111b
--- /dev/null
+++ b/Zend/tests/partial_application/compile_errors_003.phpt
@@ -0,0 +1,8 @@
+--TEST--
+PFA compile errors: placeholders can not appear after named args
+--FILE--
+
+--EXPECTF--
+Fatal error: Cannot use positional argument after named argument in %s on line %d
diff --git a/Zend/tests/partial_application/compile_errors_004.phpt b/Zend/tests/partial_application/compile_errors_004.phpt
new file mode 100644
index 000000000000..ac7ec163c5da
--- /dev/null
+++ b/Zend/tests/partial_application/compile_errors_004.phpt
@@ -0,0 +1,8 @@
+--TEST--
+PFA compile errors: variadic placeholder must be last, including after named args
+--FILE--
+
+--EXPECTF--
+Fatal error: Variadic placeholder must be last in %s on line %d
diff --git a/Zend/tests/partial_application/compile_errors_005.phpt b/Zend/tests/partial_application/compile_errors_005.phpt
new file mode 100644
index 000000000000..30e4aa12b488
--- /dev/null
+++ b/Zend/tests/partial_application/compile_errors_005.phpt
@@ -0,0 +1,8 @@
+--TEST--
+PFA compile errors: variadic placeholder must be last, including after positional args
+--FILE--
+
+--EXPECTF--
+Fatal error: Variadic placeholder must be last in %s on line %d
diff --git a/Zend/tests/partial_application/compile_errors_006.phpt b/Zend/tests/partial_application/compile_errors_006.phpt
new file mode 100644
index 000000000000..8549c671e906
--- /dev/null
+++ b/Zend/tests/partial_application/compile_errors_006.phpt
@@ -0,0 +1,8 @@
+--TEST--
+PFA compile errors: can not use unpacking in PFA, including with variadic placeholders
+--FILE--
+ "bar"], ...);
+?>
+--EXPECTF--
+Fatal error: Cannot combine partial application and unpacking in %s on line %d
diff --git a/Zend/tests/partial_application/construct_throw_arg_leak.phpt b/Zend/tests/partial_application/construct_throw_arg_leak.phpt
new file mode 100644
index 000000000000..1291901f0cc8
--- /dev/null
+++ b/Zend/tests/partial_application/construct_throw_arg_leak.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Partial application must not leak bound arguments when construction throws
+--FILE--
+
+--EXPECT--
+TypeError
+TypeError
+ArgumentCountError
+OK
diff --git a/Zend/tests/partial_application/default_const_expr.phpt b/Zend/tests/partial_application/default_const_expr.phpt
new file mode 100644
index 000000000000..12d25adedeff
--- /dev/null
+++ b/Zend/tests/partial_application/default_const_expr.phpt
@@ -0,0 +1,116 @@
+--TEST--
+PFA: constant-expression default for a skipped optional parameter
+--FILE--
+name, $c];
+}
+
+$p = f(a: 10, c: ?);
+var_dump($p(99));
+
+$v = variadic(a: 10, c: ?, foo: 1);
+var_dump($v(99));
+
+$q = C::m(a: 1, c: ?);
+var_dump($q(9));
+
+$r = g(a: 1, c: ?);
+var_dump($r(9));
+
+$l = late(a: 1, c: ?);
+define('App\LATE', 'defined after the partial was created');
+var_dump($l(9));
+
+define('GLOBAL_ONLY', 'global fallback');
+$s = fallback(a: 1, c: ?);
+var_dump($s(9));
+
+?>
+--EXPECT--
+array(3) {
+ [0]=>
+ int(10)
+ [1]=>
+ int(7)
+ [2]=>
+ int(99)
+}
+array(4) {
+ [0]=>
+ int(10)
+ [1]=>
+ int(7)
+ [2]=>
+ int(99)
+ [3]=>
+ array(1) {
+ ["foo"]=>
+ int(1)
+ }
+}
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(42)
+ [2]=>
+ int(9)
+}
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(1) "A"
+ [2]=>
+ int(9)
+}
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(37) "defined after the partial was created"
+ [2]=>
+ int(9)
+}
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ string(15) "global fallback"
+ [2]=>
+ int(9)
+}
diff --git a/Zend/tests/partial_application/deprecated.phpt b/Zend/tests/partial_application/deprecated.phpt
new file mode 100644
index 000000000000..5a6280347cfc
--- /dev/null
+++ b/Zend/tests/partial_application/deprecated.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFAs may emit deprecation warnings
+--FILE--
+
+--EXPECTF--
+Deprecated: Function f() is deprecated in %s on line %d
diff --git a/Zend/tests/partial_application/errors_001.phpt b/Zend/tests/partial_application/errors_001.phpt
new file mode 100644
index 000000000000..2d5348cb28af
--- /dev/null
+++ b/Zend/tests/partial_application/errors_001.phpt
@@ -0,0 +1,62 @@
+--TEST--
+PFA errors: PFA instantiation follows the usual argument count validation
+--FILE--
+getMessage());
+}
+
+try {
+ foo(?, ?, ?, ?);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+try {
+ $c = new C();
+ $c->f(?);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+try {
+ property_exists(?);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+try {
+ usleep(?, ?);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+try {
+ foo(?, ?, ?, ?, ...);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+/* It is allowed to specify less than the number of required params, when there
+ * is a variadic placeholder */
+foo(?, ...);
+
+?>
+--EXPECT--
+ArgumentCountError: Partial application of foo() expects exactly 3 arguments, 1 given
+ArgumentCountError: Partial application of foo() expects at most 3 arguments, 4 given
+ArgumentCountError: Partial application of C::f() expects exactly 3 arguments, 1 given
+ArgumentCountError: Partial application of property_exists() expects exactly 2 arguments, 1 given
+ArgumentCountError: Partial application of usleep() expects at most 1 arguments, 2 given
+ArgumentCountError: Partial application of foo() expects at most 3 arguments, 4 given
diff --git a/Zend/tests/partial_application/errors_002.phpt b/Zend/tests/partial_application/errors_002.phpt
new file mode 100644
index 000000000000..0132d8873e2a
--- /dev/null
+++ b/Zend/tests/partial_application/errors_002.phpt
@@ -0,0 +1,15 @@
+--TEST--
+PFA errors: named parameter that resolve to the position of a placeholder is an error
+--FILE--
+getMessage());
+}
+?>
+--EXPECT--
+Error: Named parameter $a overwrites previous placeholder
diff --git a/Zend/tests/partial_application/errors_003.phpt b/Zend/tests/partial_application/errors_003.phpt
new file mode 100644
index 000000000000..85ecf398fa1b
--- /dev/null
+++ b/Zend/tests/partial_application/errors_003.phpt
@@ -0,0 +1,74 @@
+--TEST--
+PFA errors: PFA call follows the usual argument count validation
+--FILE--
+getMessage());
+}
+
+$foo = foo(?, ?);
+
+try {
+ $foo(1);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+$bar = bar(?, ?, ...);
+
+try {
+ $bar(1);
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+class Foo {
+ public function bar($a, ...$b) {}
+}
+
+$foo = new Foo;
+
+$bar = $foo->bar(?);
+
+try {
+ $bar();
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+$repeat = str_repeat('a', ...);
+
+try {
+ $repeat();
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+$usleep = usleep(?);
+
+try {
+ $usleep();
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+$usleep(1, 2);
+
+?>
+--EXPECTF--
+ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
+ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected
+ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 1 passed in %s on line %d and exactly 3 expected
+ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
+ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
+ArgumentCountError: Too few arguments to function {closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
diff --git a/Zend/tests/partial_application/errors_004.phpt b/Zend/tests/partial_application/errors_004.phpt
new file mode 100644
index 000000000000..e5e1432753b2
--- /dev/null
+++ b/Zend/tests/partial_application/errors_004.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA errors: not specifying a required param is an error
+--FILE--
+
+--EXPECTF--
+Fatal error: Uncaught ArgumentCountError: f(): Argument #2 ($b) not passed in %s:6
+Stack trace:
+#0 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/partial_application/errors_005.phpt b/Zend/tests/partial_application/errors_005.phpt
new file mode 100644
index 000000000000..23fc2a75dff3
--- /dev/null
+++ b/Zend/tests/partial_application/errors_005.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA errors: Can not fetch default parameter value for Closure::__invoke()
+--FILE--
+__invoke(0, 0, ?);
+echo "No error\n";
+
+try {
+ $g = $f->__invoke(0, 0, ...);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+No error
+ArgumentCountError: Closure::__invoke(): Argument #3 ($c) must be passed explicitly, because the default value is not known
diff --git a/Zend/tests/partial_application/errors_006.phpt b/Zend/tests/partial_application/errors_006.phpt
new file mode 100644
index 000000000000..9534bb40ee54
--- /dev/null
+++ b/Zend/tests/partial_application/errors_006.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA errors: Some internal function parameters have UNKNOWN default value
+--FILE--
+ array_keys($array, ???, true)
+ $f = array_keys(?, strict: true);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+ // fn (array $array, mixed $filter_value = ???) => array_keys($array, $filter_value, true)
+ $f = array_keys(?, strict: true, ...);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ int(1)
+}
+array(0) {
+}
+array(1) {
+ [0]=>
+ int(1)
+}
+array(0) {
+}
+ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known
+ArgumentCountError: array_keys(): Argument #2 ($filter_value) must be passed explicitly, because the default value is not known
diff --git a/Zend/tests/partial_application/export_001.phpt b/Zend/tests/partial_application/export_001.phpt
new file mode 100644
index 000000000000..b48146bcc65a
--- /dev/null
+++ b/Zend/tests/partial_application/export_001.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFA AST export
+--INI--
+assert.exception=1
+--FILE--
+getMessage());
+}
+?>
+--EXPECT--
+AssertionError: assert(0 && foo(?) && foo(new stdClass(), bar: 1, ...))
diff --git a/Zend/tests/partial_application/extra_named.phpt b/Zend/tests/partial_application/extra_named.phpt
new file mode 100644
index 000000000000..4dd80cfa0127
--- /dev/null
+++ b/Zend/tests/partial_application/extra_named.phpt
@@ -0,0 +1,49 @@
+--TEST--
+PFA extra named parameters are forwarded to the actual function
+--FILE--
+
+--EXPECT--
+array(3) {
+ ["foo"]=>
+ string(3) "foo"
+ ["bar"]=>
+ string(3) "bar"
+ ["baz"]=>
+ string(3) "baz"
+}
+array(2) {
+ ["bar"]=>
+ string(3) "bar"
+ ["baz"]=>
+ string(3) "baz"
+}
+array(2) {
+ ["foo"]=>
+ string(3) "foo"
+ ["bar"]=>
+ string(3) "bar"
+}
diff --git a/Zend/tests/partial_application/function_name.phpt b/Zend/tests/partial_application/function_name.phpt
new file mode 100644
index 000000000000..68d2ccf84871
--- /dev/null
+++ b/Zend/tests/partial_application/function_name.phpt
@@ -0,0 +1,40 @@
+--TEST--
+Partial application function name
+--FILE--
+getName());
+ }
+}
+
+function f() {
+ echo "# From a function:\n";
+ var_dump((new ReflectionFunction(g(?)))->getName());
+
+ echo "# Declared on same line:\n";
+ [$a, $b] = [g(?), g(?)];
+ var_dump((new ReflectionFunction($a))->getName(), (new ReflectionFunction($b))->getName());
+}
+
+f();
+C::m();
+
+echo "# From global scope:\n";
+var_dump((new ReflectionFunction(g(?)))->getName());
+
+?>
+--EXPECTF--
+# From a function:
+string(20) "{closure:pfa:f():14}"
+# Declared on same line:
+string(20) "{closure:pfa:f():17}"
+string(20) "{closure:pfa:f():17}"
+# From a method:
+string(22) "{closure:pfa:C::m():8}"
+# From global scope:
+string(%d) "{closure:pfa:%sfunction_name.php:25}"
diff --git a/Zend/tests/partial_application/fuzz_001.phpt b/Zend/tests/partial_application/fuzz_001.phpt
new file mode 100644
index 000000000000..790162ba6a36
--- /dev/null
+++ b/Zend/tests/partial_application/fuzz_001.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Closure application fuzz 001
+--FILE--
+
+--EXPECTF--
+Closure [ function {closure:%s:%d} ] {
+ @@ %s 4 - 4
+
+ - Bound Variables [2] {
+ Variable #0 [ $fn ]
+ Variable #1 [ $a ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $b ]
+ }
+}
diff --git a/Zend/tests/partial_application/fuzz_003.phpt b/Zend/tests/partial_application/fuzz_003.phpt
new file mode 100644
index 000000000000..6e9d583fda99
--- /dev/null
+++ b/Zend/tests/partial_application/fuzz_003.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Closure application fuzz 003
+--FILE--
+method(1, ...);
+$bar(2);
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+}
diff --git a/Zend/tests/partial_application/fuzz_004.phpt b/Zend/tests/partial_application/fuzz_004.phpt
new file mode 100644
index 000000000000..ea005304a3af
--- /dev/null
+++ b/Zend/tests/partial_application/fuzz_004.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Closure application fuzz 004
+--FILE--
+__invoke(UNDEFINED);
+} catch (\Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Error: Undefined constant "UNDEFINED"
diff --git a/Zend/tests/partial_application/fuzz_005.phpt b/Zend/tests/partial_application/fuzz_005.phpt
new file mode 100644
index 000000000000..ea04862d8399
--- /dev/null
+++ b/Zend/tests/partial_application/fuzz_005.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA fuzz 005
+--FILE--
+
+==DONE==
+--EXPECT--
+==DONE==
diff --git a/Zend/tests/partial_application/fuzz_006.phpt b/Zend/tests/partial_application/fuzz_006.phpt
new file mode 100644
index 000000000000..26ec6e3e4dd1
--- /dev/null
+++ b/Zend/tests/partial_application/fuzz_006.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA fuzz 006
+--FILE--
+
+--EXPECT--
+int(1)
diff --git a/Zend/tests/partial_application/fuzz_007.phpt b/Zend/tests/partial_application/fuzz_007.phpt
new file mode 100644
index 000000000000..123ce29d8b18
--- /dev/null
+++ b/Zend/tests/partial_application/fuzz_007.phpt
@@ -0,0 +1,19 @@
+--TEST--
+PFA fuzz 007
+--FILE--
+getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Undefined constant "UNDEFINED"
diff --git a/Zend/tests/partial_application/hook.phpt b/Zend/tests/partial_application/hook.phpt
new file mode 100644
index 000000000000..6402c5d01e74
--- /dev/null
+++ b/Zend/tests/partial_application/hook.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Parent property hook call can not be partially applied
+--FILE--
+a = 1;
+
+?>
+--EXPECTF--
+Fatal error: Cannot create Closure for parent property hook call in %s on line %d
diff --git a/Zend/tests/partial_application/instance_polymorphism.phpt b/Zend/tests/partial_application/instance_polymorphism.phpt
new file mode 100644
index 000000000000..59fb5795d60f
--- /dev/null
+++ b/Zend/tests/partial_application/instance_polymorphism.phpt
@@ -0,0 +1,43 @@
+--TEST--
+PFA: instance polymorphism
+--FILE--
+m(?);
+ }
+}
+
+class C extends P {
+ public function m(string|array $b): void {
+ echo __METHOD__, PHP_EOL;
+ var_dump($b);
+ }
+}
+
+for ($i = 0; $i < 2; $i++) {
+ (new P())->get()(a: 'a');
+ (new C())->get()(b: []);
+}
+
+?>
+--EXPECT--
+P::m
+string(1) "a"
+C::m
+array(0) {
+}
+P::m
+string(1) "a"
+C::m
+array(0) {
+}
diff --git a/Zend/tests/partial_application/invokable.phpt b/Zend/tests/partial_application/invokable.phpt
new file mode 100644
index 000000000000..c21030e7733a
--- /dev/null
+++ b/Zend/tests/partial_application/invokable.phpt
@@ -0,0 +1,51 @@
+--TEST--
+__invoke() can be partially applied
+--FILE--
+
+--EXPECTF--
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s.php 11 - 11
+
+ - Parameters [2] {
+ Parameter #0 [ int $a ]
+ Parameter #1 [ object $b ]
+ }
+ - Return [ C ]
+}
+
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s.php 15 - 15
+
+ - Bound Variables [1] {
+ Variable #0 [ $b ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ int $a ]
+ }
+ - Return [ C ]
+}
+
+int(1)
+object(stdClass)#%d (0) {
+}
diff --git a/Zend/tests/partial_application/jit_001.phpt b/Zend/tests/partial_application/jit_001.phpt
new file mode 100644
index 000000000000..84aefa05ab28
--- /dev/null
+++ b/Zend/tests/partial_application/jit_001.phpt
@@ -0,0 +1,9 @@
+--TEST--
+PFA JIT 001
+--FILE--
+
+--EXPECT--
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/magic_001.phpt b/Zend/tests/partial_application/magic_001.phpt
new file mode 100644
index 000000000000..bdcc10675785
--- /dev/null
+++ b/Zend/tests/partial_application/magic_001.phpt
@@ -0,0 +1,81 @@
+--TEST--
+__call() can be partially applied
+--FILE--
+method(?);
+
+echo (string) new ReflectionFunction($bar);
+
+try {
+ $bar();
+} catch (Error $ex) {
+ printf("%s: %s\n", $ex::class, $ex->getMessage());
+}
+
+$bar(1, 2);
+$bar(1);
+
+$bar = $foo->method(?, ...);
+
+echo (string) new ReflectionFunction($bar);
+
+$bar(10);
+$bar(10, 20);
+
+$bar = $foo->method(new Foo, ...);
+
+echo (string) new ReflectionFunction($bar);
+
+$bar(100);
+?>
+--EXPECTF--
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 12 - 12
+
+ - Parameters [1] {
+ Parameter #0 [ mixed $arguments0 ]
+ }
+}
+ArgumentCountError: Too few arguments to function Foo::{closure:%s:%d}(), 0 passed in %s on line %d and exactly 1 expected
+Foo::method
+int(1)
+Foo::method
+int(1)
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 25 - 25
+
+ - Parameters [2] {
+ Parameter #0 [ mixed $arguments0 ]
+ Parameter #1 [ mixed ...$arguments ]
+ }
+}
+Foo::method
+int(10)
+Foo::method
+int(10)
+int(20)
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 32 - 32
+
+ - Bound Variables [1] {
+ Variable #0 [ $arguments2 ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ mixed ...$arguments ]
+ }
+}
+Foo::method
+object(Foo)#%d (0) {
+}
+int(100)
diff --git a/Zend/tests/partial_application/magic_002.phpt b/Zend/tests/partial_application/magic_002.phpt
new file mode 100644
index 000000000000..1d5efaea7c63
--- /dev/null
+++ b/Zend/tests/partial_application/magic_002.phpt
@@ -0,0 +1,72 @@
+--TEST--
+__callStatic() can be partially applied
+--FILE--
+
+--EXPECTF--
+Closure [ static public method {closure:%s:%d} ] {
+ @@ %s 10 - 10
+
+ - Parameters [1] {
+ Parameter #0 [ mixed $arguments0 ]
+ }
+}
+Foo::method
+int(1)
+Foo::method
+int(1)
+Closure [ static public method {closure:%s:%d} ] {
+ @@ %s 17 - 17
+
+ - Parameters [2] {
+ Parameter #0 [ mixed $arguments0 ]
+ Parameter #1 [ mixed ...$arguments ]
+ }
+}
+Foo::method
+int(10)
+Foo::method
+int(10)
+int(20)
+Closure [ static public method {closure:%s:%d} ] {
+ @@ %s 24 - 24
+
+ - Bound Variables [1] {
+ Variable #0 [ $arguments2 ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ mixed ...$arguments ]
+ }
+}
+Foo::method
+object(Foo)#%d (0) {
+}
+int(100)
diff --git a/Zend/tests/partial_application/magic_003.phpt b/Zend/tests/partial_application/magic_003.phpt
new file mode 100644
index 000000000000..242403ea38fc
--- /dev/null
+++ b/Zend/tests/partial_application/magic_003.phpt
@@ -0,0 +1,13 @@
+--TEST--
+PFA magic trampoline release (result unused)
+--FILE--
+
+--EXPECT--
+OK
diff --git a/Zend/tests/partial_application/magic_004.phpt b/Zend/tests/partial_application/magic_004.phpt
new file mode 100644
index 000000000000..8ef93a91ce83
--- /dev/null
+++ b/Zend/tests/partial_application/magic_004.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFA magic trampoline release (result used)
+--FILE--
+
+--EXPECT--
+OK
diff --git a/Zend/tests/partial_application/magic_005.phpt b/Zend/tests/partial_application/magic_005.phpt
new file mode 100644
index 000000000000..e70ed90b1767
--- /dev/null
+++ b/Zend/tests/partial_application/magic_005.phpt
@@ -0,0 +1,29 @@
+--TEST--
+PFA magic null ptr deref in arginfo
+--FILE--
+method(?);
+var_dump($bar);
+?>
+--EXPECTF--
+object(Closure)#%d (%d) {
+ ["name"]=>
+ string(%d) "{closure:%s}"
+ ["file"]=>
+ string(%d) "%smagic_005.php"
+ ["line"]=>
+ int(7)
+ ["this"]=>
+ object(Foo)#%d (0) {
+ }
+ ["parameter"]=>
+ array(1) {
+ ["$arguments0"]=>
+ string(10) ""
+ }
+}
diff --git a/Zend/tests/partial_application/magic_006.phpt b/Zend/tests/partial_application/magic_006.phpt
new file mode 100644
index 000000000000..29564d8b615a
--- /dev/null
+++ b/Zend/tests/partial_application/magic_006.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA magic varargs
+--FILE--
+method(1,...);
+$bar(2);
+?>
+--EXPECT--
+array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+}
diff --git a/Zend/tests/partial_application/named_placeholders.phpt b/Zend/tests/partial_application/named_placeholders.phpt
new file mode 100644
index 000000000000..19ce8cc03860
--- /dev/null
+++ b/Zend/tests/partial_application/named_placeholders.phpt
@@ -0,0 +1,121 @@
+--TEST--
+PFA supports named placeholders
+--FILE--
+getMessage(), "\n";
+}
+
+echo "# Case 4\n";
+
+function bar($a = 1, $b = 2, ...$c) {
+ var_dump($a, $b, $c);
+}
+
+$d = bar(b: ?, ...);
+
+echo (string) new ReflectionFunction($d);
+
+$d(new B, new A, new C);
+
+echo "# Case 5\n";
+
+try {
+ $d = bar(?, a: ?);
+} catch (\Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# Case 6\n";
+
+try {
+ $d = bar(c: ?, ...);
+} catch (\Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+# Case 1
+Closure [ static function {closure:%s:%d} ] {
+ @@ %snamed_placeholders.php 13 - 13
+
+ - Parameters [1] {
+ Parameter #0 [ $b ]
+ }
+}
+int(1)
+object(B)#%d (0) {
+}
+int(3)
+# Case 2
+Closure [ static function {closure:%s:%d} ] {
+ @@ %snamed_placeholders.php 21 - 21
+
+ - Bound Variables [1] {
+ Variable #0 [ $fn ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $b ]
+ }
+}
+int(1)
+object(B)#%d (0) {
+}
+int(3)
+# Case 3
+ArgumentCountError: {closure:pfa:%s:29}(): Argument #1 ($a) not passed
+# Case 4
+Closure [ static function {closure:%s:%d} ] {
+ @@ %snamed_placeholders.php 42 - 42
+
+ - Parameters [3] {
+ Parameter #0 [ $b ]
+ Parameter #1 [ $a = 1 ]
+ Parameter #2 [ ...$c ]
+ }
+}
+object(A)#%d (0) {
+}
+object(B)#%d (0) {
+}
+array(1) {
+ [0]=>
+ object(C)#%d (0) {
+ }
+}
+# Case 5
+Error: Named parameter $a overwrites previous placeholder
+# Case 6
+Error: Cannot use named placeholder for unknown or variadic parameter $c
diff --git a/Zend/tests/partial_application/never_cache_001.phpt b/Zend/tests/partial_application/never_cache_001.phpt
new file mode 100644
index 000000000000..3c9e8a173e51
--- /dev/null
+++ b/Zend/tests/partial_application/never_cache_001.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Inline cache can not be used for ZEND_ACC_NEVER_CACHE functions
+--FILE--
+__invoke(?));
+}
+
+?>
+--EXPECT--
+string(1) "A"
+string(1) "B"
+string(1) "A"
+string(1) "B"
diff --git a/Zend/tests/partial_application/non_dynamic_call_funcs.phpt b/Zend/tests/partial_application/non_dynamic_call_funcs.phpt
new file mode 100644
index 000000000000..db25ef3dcc93
--- /dev/null
+++ b/Zend/tests/partial_application/non_dynamic_call_funcs.phpt
@@ -0,0 +1,46 @@
+--TEST--
+Functions that can not be called dynamically, can not be partially applied
+--FILE--
+getMessage(), "\n";
+ }
+}
+
+?>
+--EXPECT--
+Error: Cannot call func_get_arg() dynamically
+Error: Cannot call compact() dynamically
+Error: Cannot call extract() dynamically
+Error: Cannot call func_get_args() dynamically
+Error: Cannot call func_num_args() dynamically
+Error: Cannot call get_defined_vars() dynamically
diff --git a/Zend/tests/partial_application/observers.phpt b/Zend/tests/partial_application/observers.phpt
new file mode 100644
index 000000000000..8717b38ca326
--- /dev/null
+++ b/Zend/tests/partial_application/observers.phpt
@@ -0,0 +1,34 @@
+--TEST--
+PFA support observers
+--EXTENSIONS--
+zend_test
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.show_output=1
+zend_test.observer.observe_all=1
+--FILE--
+
+--EXPECTF--
+
+
+
+ <{closure:%s}>
+
+
+
+
+int(1)
+int(2)
+
+
+ {closure:%s:%d}>
+
diff --git a/Zend/tests/partial_application/optional_placeholder.phpt b/Zend/tests/partial_application/optional_placeholder.phpt
new file mode 100644
index 000000000000..d6230241d906
--- /dev/null
+++ b/Zend/tests/partial_application/optional_placeholder.phpt
@@ -0,0 +1,77 @@
+--TEST--
+PFA optional placeholder: ? always generates required params; ... inherits optionality
+--FILE--
+getParameters()[0]->isOptional());
+
+$f = foo(?, ?);
+$params = (new ReflectionFunction($f))->getParameters();
+var_dump($params[0]->isOptional(), $params[1]->isOptional());
+
+$f = foo(?, ?, ?);
+$params = (new ReflectionFunction($f))->getParameters();
+var_dump($params[0]->isOptional(), $params[1]->isOptional(), $params[2]->isOptional());
+
+echo "# ... inherits optionality from the original function\n";
+$f = foo(?, ...);
+$params = (new ReflectionFunction($f))->getParameters();
+var_dump($params[0]->isOptional(), $params[1]->isOptional(), $params[2]->isOptional());
+
+$f = foo(?, ?, ...);
+$params = (new ReflectionFunction($f))->getParameters();
+var_dump($params[0]->isOptional(), $params[1]->isOptional(), $params[2]->isOptional());
+
+echo "# Calling works: ? params are required, unspecified optional params use their defaults\n";
+$f = foo(?, ?);
+var_dump($f(10, 'hello'));
+
+echo "# Named ? placeholders are also required\n";
+
+function bar(int $x = 0, int $y = 0) {
+ return [$x, $y];
+}
+
+$f = bar(y: ?);
+var_dump((new ReflectionFunction($f))->getParameters()[0]->isOptional());
+var_dump($f(42));
+
+?>
+--EXPECT--
+# ? makes targeted params required regardless of original optionality
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+bool(false)
+# ... inherits optionality from the original function
+bool(false)
+bool(true)
+bool(true)
+bool(false)
+bool(false)
+bool(true)
+# Calling works: ? params are required, unspecified optional params use their defaults
+array(3) {
+ [0]=>
+ int(10)
+ [1]=>
+ string(5) "hello"
+ [2]=>
+ float(3.14)
+}
+# Named ? placeholders are also required
+bool(false)
+array(2) {
+ [0]=>
+ int(0)
+ [1]=>
+ int(42)
+}
diff --git a/Zend/tests/partial_application/param_reorder.phpt b/Zend/tests/partial_application/param_reorder.phpt
new file mode 100644
index 000000000000..3ade1beb0af7
--- /dev/null
+++ b/Zend/tests/partial_application/param_reorder.phpt
@@ -0,0 +1,202 @@
+--TEST--
+Named parameters define the order of parameters in a PFA
+--FILE--
+getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+# All named
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sparam_reorder.php 13 - 13
+
+ - Parameters [4] {
+ Parameter #0 [ $d ]
+ Parameter #1 [ $c ]
+ Parameter #2 [ $b ]
+ Parameter #3 [ $a ]
+ }
+}
+
+array(4) {
+ [0]=>
+ int(4)
+ [1]=>
+ int(3)
+ [2]=>
+ int(2)
+ [3]=>
+ int(1)
+}
+# Some named: Positional first, then named in specified order
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sparam_reorder.php 19 - 19
+
+ - Parameters [4] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $b ]
+ Parameter #2 [ $d ]
+ Parameter #3 [ $c ]
+ }
+}
+
+array(4) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ [2]=>
+ int(4)
+ [3]=>
+ int(3)
+}
+# Some named, one unspecified
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sparam_reorder.php 25 - 25
+
+ - Parameters [3] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $c ]
+ Parameter #2 [ $b ]
+ }
+}
+
+array(4) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(3)
+ [2]=>
+ int(2)
+ [3]=>
+ NULL
+}
+# Some named, some implicit added by '...'
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sparam_reorder.php 31 - 31
+
+ - Parameters [4] {
+ Parameter #0 [ $c ]
+ Parameter #1 [ $b ]
+ Parameter #2 [ $a ]
+ Parameter #3 [ $d = NULL ]
+ }
+}
+
+array(4) {
+ [0]=>
+ int(3)
+ [1]=>
+ int(2)
+ [2]=>
+ int(1)
+ [3]=>
+ int(4)
+}
+# Some named, some implicit added by '...' on variadic function
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sparam_reorder.php 37 - 37
+
+ - Parameters [4] {
+ Parameter #0 [ $c ]
+ Parameter #1 [ $b ]
+ Parameter #2 [ $a ]
+ Parameter #3 [ ...$d ]
+ }
+}
+
+array(4) {
+ [0]=>
+ int(3)
+ [1]=>
+ int(2)
+ [2]=>
+ int(1)
+ [3]=>
+ array(3) {
+ [0]=>
+ int(4)
+ [1]=>
+ int(5)
+ [2]=>
+ int(6)
+ }
+}
+# Some prebound, some named
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sparam_reorder.php 43 - 43
+
+ - Bound Variables [2] {
+ Variable #0 [ $a ]
+ Variable #1 [ $d ]
+ }
+
+ - Parameters [2] {
+ Parameter #0 [ $c ]
+ Parameter #1 [ $b ]
+ }
+}
+
+array(4) {
+ [0]=>
+ int(-1)
+ [1]=>
+ int(2)
+ [2]=>
+ int(1)
+ [3]=>
+ int(-2)
+}
+# Some named, some required missing
+ArgumentCountError: f(): Argument #1 ($a) not passed
diff --git a/Zend/tests/partial_application/pipe_optimization_001.phpt b/Zend/tests/partial_application/pipe_optimization_001.phpt
new file mode 100644
index 000000000000..9610f37b76ac
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_001.phpt
@@ -0,0 +1,47 @@
+--TEST--
+PFA optimization: PFA with single placeholder arg can be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a) {
+ var_dump($a);
+ }
+}
+
+1 |> foo(?);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=9, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_001.php:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 1 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 DO_FCALL_BY_NAME
+0008 RETURN int(1)
+
+foo:
+ ; (lines=5, args=1, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_001.php:4-6
+0000 CV0($a) = RECV 1
+0001 INIT_FCALL 1 %d string("var_dump")
+0002 SEND_VAR CV0($a) 1
+0003 DO_ICALL
+0004 RETURN null
+int(1)
diff --git a/Zend/tests/partial_application/pipe_optimization_002.phpt b/Zend/tests/partial_application/pipe_optimization_002.phpt
new file mode 100644
index 000000000000..729e70d30e6b
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_002.phpt
@@ -0,0 +1,51 @@
+--TEST--
+PFA pipe optimization: PFA with only one placeholder can be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+2 |> foo(1, ?);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=10, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_002.php:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 2 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 SEND_VAL_EX int(2) 2
+0008 DO_FCALL_BY_NAME
+0009 RETURN int(1)
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_002.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_003.phpt b/Zend/tests/partial_application/pipe_optimization_003.phpt
new file mode 100644
index 000000000000..da112f8f3cea
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_003.phpt
@@ -0,0 +1,51 @@
+--TEST--
+PFA pipe optimization: PFA with only one placeholder can be optimized (placeholder first)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+2 |> foo(?, 1);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=10, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_003.php:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 2 string("foo")
+0006 SEND_VAL_EX int(2) 1
+0007 SEND_VAL_EX int(1) 2
+0008 DO_FCALL_BY_NAME
+0009 RETURN int(1)
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_003.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+int(2)
+int(1)
diff --git a/Zend/tests/partial_application/pipe_optimization_004.phpt b/Zend/tests/partial_application/pipe_optimization_004.phpt
new file mode 100644
index 000000000000..addee498d810
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_004.phpt
@@ -0,0 +1,88 @@
+--TEST--
+PFA pipe optimization: PFA with multiple placeholders can not be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+try {
+ 2 |> foo(?, ?);
+} catch (\Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=22, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_004.php:1-16
+0000 INIT_FCALL 0 %d string("time")
+0001 T2 = DO_ICALL
+0002 T1 = IS_SMALLER int(0) T2
+0003 JMPZ T1 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 2 string("foo")
+0006 SEND_PLACEHOLDER 1
+0007 SEND_PLACEHOLDER 2
+0008 T1 = CALLABLE_CONVERT_PARTIAL %d
+0009 INIT_DYNAMIC_CALL 1 T1
+0010 SEND_VAL_EX int(2) 1
+0011 DO_FCALL
+0012 RETURN int(1)
+0013 CV0($e) = CATCH string("Throwable")
+0014 T1 = FETCH_CLASS_NAME CV0($e)
+0015 ECHO T1
+0016 ECHO string(": ")
+0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
+0018 T1 = DO_FCALL
+0019 ECHO T1
+0020 ECHO string("\n")
+0021 RETURN int(1)
+EXCEPTION TABLE:
+ 0005, 0013, -, -
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_004.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+
+$_main:
+ ; (lines=3, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-10
+0000 T0 = DECLARE_LAMBDA_FUNCTION 0
+0001 FREE T0
+0002 RETURN int(1)
+
+{closure:%s:%d}:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %s:10-10
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("foo")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 T2 = DO_UCALL
+0006 RETURN T2
+ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected
diff --git a/Zend/tests/partial_application/pipe_optimization_005.phpt b/Zend/tests/partial_application/pipe_optimization_005.phpt
new file mode 100644
index 000000000000..3ccfec836609
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_005.phpt
@@ -0,0 +1,51 @@
+--TEST--
+PFA pipe optimization: PFA with only one placeholder can be optimized (variadic)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+2 |> foo(1, ...);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=10, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_005.php:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 2 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 SEND_VAL_EX int(2) 2
+0008 DO_FCALL_BY_NAME
+0009 RETURN int(1)
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_005.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_006.phpt b/Zend/tests/partial_application/pipe_optimization_006.phpt
new file mode 100644
index 000000000000..6e06477427a3
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_006.phpt
@@ -0,0 +1,55 @@
+--TEST--
+PFA pipe optimization: PFA with only one placeholder can be optimized (named)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b = null, $c = null) {
+ var_dump($a, $b, $c);
+ }
+}
+
+2 |> foo(1, c: ?);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=11, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_006.php:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 1 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 SEND_VAL_EX int(2) string("c")
+0008 CHECK_UNDEF_ARGS
+0009 DO_FCALL_BY_NAME
+0010 RETURN int(1)
+
+foo:
+ ; (lines=9, args=3, vars=3, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_006.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV_INIT 2 null
+0002 CV2($c) = RECV_INIT 3 null
+0003 INIT_FCALL 3 %d string("var_dump")
+0004 SEND_VAR CV0($a) 1
+0005 SEND_VAR CV1($b) 2
+0006 SEND_VAR CV2($c) 3
+0007 DO_ICALL
+0008 RETURN null
+int(1)
+NULL
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_007.phpt b/Zend/tests/partial_application/pipe_optimization_007.phpt
new file mode 100644
index 000000000000..09c3c765d237
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_007.phpt
@@ -0,0 +1,88 @@
+--TEST--
+PFA pipe optimization: PFA with multiple placeholders can not be optimized (named)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+try {
+ 2 |> foo(a: ?, b: ?);
+} catch (\Throwable $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=22, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_007.php:1-16
+0000 INIT_FCALL 0 %d string("time")
+0001 T2 = DO_ICALL
+0002 T1 = IS_SMALLER int(0) T2
+0003 JMPZ T1 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 0 string("foo")
+0006 SEND_PLACEHOLDER string("a")
+0007 SEND_PLACEHOLDER string("b")
+0008 T1 = CALLABLE_CONVERT_PARTIAL %d array(...)
+0009 INIT_DYNAMIC_CALL 1 T1
+0010 SEND_VAL_EX int(2) 1
+0011 DO_FCALL
+0012 RETURN int(1)
+0013 CV0($e) = CATCH string("Throwable")
+0014 T1 = FETCH_CLASS_NAME CV0($e)
+0015 ECHO T1
+0016 ECHO string(": ")
+0017 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
+0018 T1 = DO_FCALL
+0019 ECHO T1
+0020 ECHO string("\n")
+0021 RETURN int(1)
+EXCEPTION TABLE:
+ 0005, 0013, -, -
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_007.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+
+$_main:
+ ; (lines=3, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-10
+0000 T0 = DECLARE_LAMBDA_FUNCTION 0
+0001 FREE T0
+0002 RETURN int(1)
+
+{closure:%s:%d}:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %s:10-10
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("foo")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 T2 = DO_UCALL
+0006 RETURN T2
+ArgumentCountError: Too few arguments to function {closure:pfa:%s:%d}(), 1 passed in %s on line %d and exactly 2 expected
diff --git a/Zend/tests/partial_application/pipe_optimization_008.phpt b/Zend/tests/partial_application/pipe_optimization_008.phpt
new file mode 100644
index 000000000000..070074632c77
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_008.phpt
@@ -0,0 +1,99 @@
+--TEST--
+PFA pipe optimization: PFA with both a variadic placeholder and named arg can not be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+try {
+ 2 |> foo(a: 1, ...);
+} catch (\Throwable $e) {
+ echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=18, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_008.php:1-16
+0000 INIT_FCALL 0 %d string("time")
+0001 T2 = DO_ICALL
+0002 T1 = IS_SMALLER int(0) T2
+0003 JMPZ T1 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 0 string("foo")
+0006 SEND_VAL_EX int(1) string("a")
+0007 T1 = CALLABLE_CONVERT_PARTIAL %d
+0008 INIT_DYNAMIC_CALL 1 T1
+0009 SEND_VAL_EX int(2) 1
+0010 DO_FCALL
+0011 RETURN int(1)
+0012 CV0($e) = CATCH string("Throwable")
+0013 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
+0014 T1 = DO_FCALL
+0015 ECHO T1
+0016 ECHO string("\n")
+0017 RETURN int(1)
+EXCEPTION TABLE:
+ 0005, 0012, -, -
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_008.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+
+$_main:
+ ; (lines=4, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-10
+0000 T1 = DECLARE_LAMBDA_FUNCTION 0
+0001 BIND_LEXICAL T1 CV0($a)
+0002 FREE T1
+0003 RETURN int(1)
+LIVE RANGES:
+ 1: 0001 - 0002 (tmp/var)
+
+{closure:%s:%d}:
+ ; (lines=18, args=1, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %s:10-10
+0000 CV0($b) = RECV 1
+0001 BIND_STATIC CV1($a)
+0002 T3 = FUNC_NUM_ARGS
+0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1)
+0004 JMPZ T2 0010
+0005 INIT_FCALL 2 %d string("foo")
+0006 SEND_VAR CV1($a) 1
+0007 SEND_VAR CV0($b) 2
+0008 T2 = DO_UCALL
+0009 RETURN T2
+0010 INIT_FCALL 2 %d string("foo")
+0011 SEND_VAR CV1($a) 1
+0012 SEND_VAR CV0($b) 2
+0013 T2 = FUNC_GET_ARGS int(1)
+0014 SEND_UNPACK T2
+0015 CHECK_UNDEF_ARGS
+0016 T2 = DO_UCALL
+0017 RETURN T2
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_009.phpt b/Zend/tests/partial_application/pipe_optimization_009.phpt
new file mode 100644
index 000000000000..118482a3860b
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_009.phpt
@@ -0,0 +1,102 @@
+--TEST--
+PFA pipe optimization: Evaluation order
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b, $c) {
+ var_dump($a, $b, $c);
+ }
+ function lhs() {
+ echo __FUNCTION__, "\n";
+ return 0;
+ }
+ function arg1() {
+ echo __FUNCTION__, "\n";
+ return 1;
+ }
+ function arg2() {
+ echo __FUNCTION__, "\n";
+ return 2;
+ }
+}
+
+lhs() |> foo(arg1(), ?, arg2());
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=20, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_009.php:1-24
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0008
+0004 DECLARE_FUNCTION string("foo") 0
+0005 DECLARE_FUNCTION string("lhs") 1
+0006 DECLARE_FUNCTION string("arg1") 2
+0007 DECLARE_FUNCTION string("arg2") 3
+0008 INIT_FCALL_BY_NAME 0 string("lhs")
+0009 T0 = DO_FCALL_BY_NAME
+0010 INIT_FCALL_BY_NAME 3 string("foo")
+0011 INIT_FCALL_BY_NAME 0 string("arg1")
+0012 V1 = DO_FCALL_BY_NAME
+0013 SEND_VAR_NO_REF_EX V1 1
+0014 SEND_VAL_EX T0 2
+0015 INIT_FCALL_BY_NAME 0 string("arg2")
+0016 V0 = DO_FCALL_BY_NAME
+0017 SEND_VAR_NO_REF_EX V0 3
+0018 DO_FCALL_BY_NAME
+0019 RETURN int(1)
+LIVE RANGES:
+ 0: 0010 - 0014 (tmp/var)
+
+foo:
+ ; (lines=9, args=3, vars=3, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_009.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 CV2($c) = RECV 3
+0003 INIT_FCALL 3 %d string("var_dump")
+0004 SEND_VAR CV0($a) 1
+0005 SEND_VAR CV1($b) 2
+0006 SEND_VAR CV2($c) 3
+0007 DO_ICALL
+0008 RETURN null
+
+lhs:
+ ; (lines=2, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_009.php:7-10
+0000 ECHO string("lhs\n")
+0001 RETURN int(0)
+
+arg1:
+ ; (lines=2, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_009.php:11-14
+0000 ECHO string("arg1\n")
+0001 RETURN int(1)
+
+arg2:
+ ; (lines=2, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_009.php:15-18
+0000 ECHO string("arg2\n")
+0001 RETURN int(2)
+lhs
+arg1
+arg2
+int(1)
+int(0)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_010.phpt b/Zend/tests/partial_application/pipe_optimization_010.phpt
new file mode 100644
index 000000000000..f77c2a97732e
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_010.phpt
@@ -0,0 +1,58 @@
+--TEST--
+PFA pipe optimization: References
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo(&$a, $b) {
+ var_dump($a, $b);
+ $a = 2;
+ }
+}
+
+1 |> foo($a, ?);
+var_dump($a);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=13, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_010.php:1-14
+0000 INIT_FCALL 0 %d string("time")
+0001 T2 = DO_ICALL
+0002 T1 = IS_SMALLER int(0) T2
+0003 JMPZ T1 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 2 string("foo")
+0006 SEND_VAR_EX CV0($a) 1
+0007 SEND_VAL_EX int(1) 2
+0008 DO_FCALL_BY_NAME
+0009 INIT_FCALL 1 %d string("var_dump")
+0010 SEND_VAR CV0($a) 1
+0011 DO_ICALL
+0012 RETURN int(1)
+
+foo:
+ ; (lines=8, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_010.php:4-7
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 ASSIGN CV0($a) int(2)
+0007 RETURN null
+NULL
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_011.phpt b/Zend/tests/partial_application/pipe_optimization_011.phpt
new file mode 100644
index 000000000000..cd1c986d99e7
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_011.phpt
@@ -0,0 +1,98 @@
+--TEST--
+PFA pipe optimization: Evaluation order
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b, $c) {
+ var_dump($a, $b, $c);
+ }
+ function arg1() {
+ global $a;
+ $a = 2;
+ echo __FUNCTION__, "\n";
+ return 1;
+ }
+ function arg2() {
+ global $a;
+ $a = 3;
+ echo __FUNCTION__, "\n";
+ return 2;
+ }
+}
+
+$a = 0;
+$a |> foo(arg1(), ?, arg2());
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=19, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_011.php:1-25
+0000 INIT_FCALL 0 %d string("time")
+0001 T2 = DO_ICALL
+0002 T1 = IS_SMALLER int(0) T2
+0003 JMPZ T1 0007
+0004 DECLARE_FUNCTION string("foo") 0
+0005 DECLARE_FUNCTION string("arg1") %d
+0006 DECLARE_FUNCTION string("arg2") %d
+0007 ASSIGN CV0($a) int(0)
+0008 T1 = QM_ASSIGN CV0($a)
+0009 INIT_FCALL_BY_NAME 3 string("foo")
+0010 INIT_FCALL_BY_NAME 0 string("arg1")
+0011 V2 = DO_FCALL_BY_NAME
+0012 SEND_VAR_NO_REF_EX V2 1
+0013 SEND_VAL_EX T1 2
+0014 INIT_FCALL_BY_NAME 0 string("arg2")
+0015 V1 = DO_FCALL_BY_NAME
+0016 SEND_VAR_NO_REF_EX V1 3
+0017 DO_FCALL_BY_NAME
+0018 RETURN int(1)
+LIVE RANGES:
+ 1: 0009 - 0013 (tmp/var)
+
+foo:
+ ; (lines=9, args=3, vars=3, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_011.php:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 CV2($c) = RECV 3
+0003 INIT_FCALL 3 %d string("var_dump")
+0004 SEND_VAR CV0($a) 1
+0005 SEND_VAR CV1($b) 2
+0006 SEND_VAR CV2($c) 3
+0007 DO_ICALL
+0008 RETURN null
+
+arg1:
+ ; (lines=4, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_011.php:7-12
+0000 BIND_GLOBAL CV0($a) string("a")
+0001 ASSIGN CV0($a) int(2)
+0002 ECHO string("arg1\n")
+0003 RETURN int(1)
+
+arg2:
+ ; (lines=4, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %spipe_optimization_011.php:13-18
+0000 BIND_GLOBAL CV0($a) string("a")
+0001 ASSIGN CV0($a) int(3)
+0002 ECHO string("arg2\n")
+0003 RETURN int(2)
+arg1
+arg2
+int(1)
+int(0)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_012.phpt b/Zend/tests/partial_application/pipe_optimization_012.phpt
new file mode 100644
index 000000000000..da172874adce
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_012.phpt
@@ -0,0 +1,52 @@
+--TEST--
+PFA optimization: PFA with named args and placeholders can be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+1 |> foo(?, b: 2);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=11, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 1 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 SEND_VAL_EX int(2) string("b")
+0008 CHECK_UNDEF_ARGS
+0009 DO_FCALL_BY_NAME
+0010 RETURN int(1)
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %s:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_013.phpt b/Zend/tests/partial_application/pipe_optimization_013.phpt
new file mode 100644
index 000000000000..7d1a48b2f2ed
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_013.phpt
@@ -0,0 +1,87 @@
+--TEST--
+PFA optimization: PFA with named args and a variadic placeholder can not be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b) {
+ var_dump($a, $b);
+ }
+}
+
+1 |> foo(b: 2, ...);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=12, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 0 string("foo")
+0006 SEND_VAL_EX int(2) string("b")
+0007 T0 = CALLABLE_CONVERT_PARTIAL 3
+0008 INIT_DYNAMIC_CALL 1 T0
+0009 SEND_VAL_EX int(1) 1
+0010 DO_FCALL
+0011 RETURN int(1)
+
+foo:
+ ; (lines=7, args=2, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %s:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV 2
+0002 INIT_FCALL 2 %d string("var_dump")
+0003 SEND_VAR CV0($a) 1
+0004 SEND_VAR CV1($b) 2
+0005 DO_ICALL
+0006 RETURN null
+
+$_main:
+ ; (lines=4, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-9
+0000 T1 = DECLARE_LAMBDA_FUNCTION 0
+0001 BIND_LEXICAL T1 CV0($b)
+0002 FREE T1
+0003 RETURN int(1)
+LIVE RANGES:
+ 1: 0001 - 0002 (tmp/var)
+
+{closure:pfa:%s:9}:
+ ; (lines=18, args=1, vars=2, tmps=%d)
+ ; (after optimizer)
+ ; %s:9-9
+0000 CV0($a) = RECV 1
+0001 BIND_STATIC CV1($b)
+0002 T3 = FUNC_NUM_ARGS
+0003 T2 = IS_SMALLER_OR_EQUAL T3 int(1)
+0004 JMPZ T2 0010
+0005 INIT_FCALL 2 %d string("foo")
+0006 SEND_VAR CV0($a) 1
+0007 SEND_VAR CV1($b) 2
+0008 T2 = DO_UCALL
+0009 RETURN T2
+0010 INIT_FCALL 2 %d string("foo")
+0011 SEND_VAR CV0($a) 1
+0012 SEND_VAR CV1($b) 2
+0013 T2 = FUNC_GET_ARGS int(1)
+0014 SEND_UNPACK T2
+0015 CHECK_UNDEF_ARGS
+0016 T2 = DO_UCALL
+0017 RETURN T2
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/pipe_optimization_014.phpt b/Zend/tests/partial_application/pipe_optimization_014.phpt
new file mode 100644
index 000000000000..6e66d7e0e99d
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_014.phpt
@@ -0,0 +1,68 @@
+--TEST--
+PFA pipe optimization: PFA with unknown named parameter can be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b = null, $c = null) {
+ var_dump($a, $b, $c);
+ }
+}
+
+try {
+ 2 |> foo(1, unknown: ?);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=20, args=0, vars=1, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-16
+0000 INIT_FCALL 0 %d string("time")
+0001 T2 = DO_ICALL
+0002 T1 = IS_SMALLER int(0) T2
+0003 JMPZ T1 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 1 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 SEND_VAL_EX int(2) string("unknown")
+0008 CHECK_UNDEF_ARGS
+0009 DO_FCALL_BY_NAME
+0010 RETURN int(1)
+0011 CV0($e) = CATCH string("Error")
+0012 T1 = FETCH_CLASS_NAME CV0($e)
+0013 ECHO T1
+0014 ECHO string(": ")
+0015 INIT_METHOD_CALL 0 CV0($e) string("getMessage")
+0016 T1 = DO_FCALL
+0017 ECHO T1
+0018 ECHO string("\n")
+0019 RETURN int(1)
+EXCEPTION TABLE:
+ 0005, 0011, -, -
+
+foo:
+ ; (lines=9, args=3, vars=3, tmps=%d)
+ ; (after optimizer)
+ ; %s:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV_INIT 2 null
+0002 CV2($c) = RECV_INIT 3 null
+0003 INIT_FCALL 3 %d string("var_dump")
+0004 SEND_VAR CV0($a) 1
+0005 SEND_VAR CV1($b) 2
+0006 SEND_VAR CV2($c) 3
+0007 DO_ICALL
+0008 RETURN null
+Error: Unknown named parameter $unknown
diff --git a/Zend/tests/partial_application/pipe_optimization_015.phpt b/Zend/tests/partial_application/pipe_optimization_015.phpt
new file mode 100644
index 000000000000..ce293c7a300c
--- /dev/null
+++ b/Zend/tests/partial_application/pipe_optimization_015.phpt
@@ -0,0 +1,55 @@
+--TEST--
+PFA pipe optimization: PFA with skipped optional parameter can be optimized
+--EXTENSIONS--
+opcache
+--INI--
+opcache.opt_debug_level=0x20000
+opcache.enable=1
+opcache.enable_cli=1
+opcache.file_cache=
+opcache.file_cache_only=0
+--FILE--
+ 0) {
+ function foo($a, $b = null, $c = null) {
+ var_dump($a, $b, $c);
+ }
+}
+
+3 |> foo(1, c: ?);
+
+?>
+--EXPECTF--
+$_main:
+ ; (lines=11, args=0, vars=0, tmps=%d)
+ ; (after optimizer)
+ ; %s:1-12
+0000 INIT_FCALL 0 %d string("time")
+0001 T1 = DO_ICALL
+0002 T0 = IS_SMALLER int(0) T1
+0003 JMPZ T0 0005
+0004 DECLARE_FUNCTION string("foo") 0
+0005 INIT_FCALL_BY_NAME 1 string("foo")
+0006 SEND_VAL_EX int(1) 1
+0007 SEND_VAL_EX int(3) string("c")
+0008 CHECK_UNDEF_ARGS
+0009 DO_FCALL_BY_NAME
+0010 RETURN int(1)
+
+foo:
+ ; (lines=9, args=3, vars=3, tmps=%d)
+ ; (after optimizer)
+ ; %s:4-6
+0000 CV0($a) = RECV 1
+0001 CV1($b) = RECV_INIT 2 null
+0002 CV2($c) = RECV_INIT 3 null
+0003 INIT_FCALL 3 %d string("var_dump")
+0004 SEND_VAR CV0($a) 1
+0005 SEND_VAR CV1($b) 2
+0006 SEND_VAR CV2($c) 3
+0007 DO_ICALL
+0008 RETURN null
+int(1)
+NULL
+int(3)
diff --git a/Zend/tests/partial_application/preloading.inc b/Zend/tests/partial_application/preloading.inc
new file mode 100644
index 000000000000..885ed0c5b0f1
--- /dev/null
+++ b/Zend/tests/partial_application/preloading.inc
@@ -0,0 +1,13 @@
+
diff --git a/Zend/tests/partial_application/preloading.phpt b/Zend/tests/partial_application/preloading.phpt
new file mode 100644
index 000000000000..23ad6edf2c4c
--- /dev/null
+++ b/Zend/tests/partial_application/preloading.phpt
@@ -0,0 +1,19 @@
+--TEST--
+PFA preloading
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.preload={PWD}/preloading.inc
+--SKIPIF--
+
+--FILE--
+
+--EXPECT--
+int(2)
+int(2)
diff --git a/Zend/tests/partial_application/rebinding_001.phpt b/Zend/tests/partial_application/rebinding_001.phpt
new file mode 100644
index 000000000000..d1913957a8c9
--- /dev/null
+++ b/Zend/tests/partial_application/rebinding_001.phpt
@@ -0,0 +1,59 @@
+--TEST--
+PFA can only be rebound to an instanceof $this
+--FILE--
+f(?);
+$g = $c->g(?);
+
+echo "# Can be rebound to \$this of the same class:\n";
+$f->bindTo(new C)(1);
+
+echo "# Can be rebound to \$this of a sub-class:\n";
+$f->bindTo(new SubClass)(1);
+
+echo "# Cannot be rebound to an unrelated class:\n";
+try {
+ $f->bindTo(new Unrelated)(1);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# Cannot unbind \$this on instance method:\n";
+try {
+ $f->bindTo(null)(1);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# Can unbind \$this on static method:\n";
+$g->bindTo(null)(1);
+
+?>
+--EXPECTF--
+# Can be rebound to $this of the same class:
+object(C)#%d (0) {
+}
+# Can be rebound to $this of a sub-class:
+object(SubClass)#%d (0) {
+}
+# Cannot be rebound to an unrelated class:
+
+Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d
+Error: Value of type null is not callable
+# Cannot unbind $this on instance method:
+
+Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d
+Error: Value of type null is not callable
+# Can unbind $this on static method:
+string(1) "C"
diff --git a/Zend/tests/partial_application/rebinding_002.phpt b/Zend/tests/partial_application/rebinding_002.phpt
new file mode 100644
index 000000000000..cfead54bbd81
--- /dev/null
+++ b/Zend/tests/partial_application/rebinding_002.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA scope cannot be rebound
+--FILE--
+f(?);
+$g = g(?);
+
+echo "# Can be rebound to the same scope:\n";
+$f->bindTo($c, C::class)(1);
+
+echo "# Method cannot be rebound to a different scope:\n";
+try {
+ $f->bindTo($c, SubClass::class)(1);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# Function cannot be rebound to a different scope:\n";
+try {
+ $g->bindTo($c, SubClass::class)(1);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+?>
+--EXPECTF--
+# Can be rebound to the same scope:
+string(1) "C"
+# Method cannot be rebound to a different scope:
+
+Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d
+Error: Value of type null is not callable
+# Function cannot be rebound to a different scope:
+
+Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
+Error: Value of type null is not callable
diff --git a/Zend/tests/partial_application/rebinding_003.phpt b/Zend/tests/partial_application/rebinding_003.phpt
new file mode 100644
index 000000000000..9417bbc2d320
--- /dev/null
+++ b/Zend/tests/partial_application/rebinding_003.phpt
@@ -0,0 +1,69 @@
+--TEST--
+Rebinding PFA of Closure rebinds inner Closure
+--FILE--
+getF()(?);
+$g = $c->getG()(?);
+
+echo "# Can be rebound to \$this of the same class:\n";
+$d = new C();
+$f->bindTo($d)($d);
+
+echo "# Can be rebound to \$this of a sub-class:\n";
+$d = new SubClass();
+$f->bindTo($d)($d);
+
+echo "# Cannot be rebound to an unrelated class:\n";
+try {
+ $f->bindTo(new Unrelated)($c);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# Cannot unbind \$this on non-static closure:\n";
+try {
+ $f->bindTo(null)($c);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# Can unbind \$this on static closure:\n";
+$g->bindTo(null)($c);
+
+?>
+--EXPECTF--
+# Can be rebound to $this of the same class:
+object(C)#%d (0) {
+}
+bool(true)
+# Can be rebound to $this of a sub-class:
+object(SubClass)#%d (0) {
+}
+bool(true)
+# Cannot be rebound to an unrelated class:
+
+Warning: Cannot bind method C::{closure:%s:%d}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d
+Error: Value of type null is not callable
+# Cannot unbind $this on non-static closure:
+
+Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d
+Error: Value of type null is not callable
+# Can unbind $this on static closure:
+string(1) "C"
+object(C)#%d (0) {
+}
diff --git a/Zend/tests/partial_application/recorded_warnings.phpt b/Zend/tests/partial_application/recorded_warnings.phpt
new file mode 100644
index 000000000000..f25be826f525
--- /dev/null
+++ b/Zend/tests/partial_application/recorded_warnings.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFA compilation warnings are recorded and replayed
+--FILE--
+
+--EXPECTF--
+Deprecated: Using "_" as a type name is deprecated since 8.4 in %s on line 3
+
+Deprecated: Using "_" as a type name is deprecated since 8.4 in %s on line 5
diff --git a/Zend/tests/partial_application/references_001.phpt b/Zend/tests/partial_application/references_001.phpt
new file mode 100644
index 000000000000..080b3085acc0
--- /dev/null
+++ b/Zend/tests/partial_application/references_001.phpt
@@ -0,0 +1,24 @@
+--TEST--
+PFA receives by val if the actual function does
+--FILE--
+
+--EXPECT--
+array(1) {
+ [0]=>
+ &string(49) "unchanged because foo() doesn't take by reference"
+}
+string(49) "unchanged because foo() doesn't take by reference"
diff --git a/Zend/tests/partial_application/references_002.phpt b/Zend/tests/partial_application/references_002.phpt
new file mode 100644
index 000000000000..42a90648c1ae
--- /dev/null
+++ b/Zend/tests/partial_application/references_002.phpt
@@ -0,0 +1,24 @@
+--TEST--
+PFA receives by ref if the actual function does
+--FILE--
+
+--EXPECT--
+array(1) {
+ [0]=>
+ &int(2)
+}
+int(2)
diff --git a/Zend/tests/partial_application/references_003.phpt b/Zend/tests/partial_application/references_003.phpt
new file mode 100644
index 000000000000..be116b06c79f
--- /dev/null
+++ b/Zend/tests/partial_application/references_003.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA receives by ref if the actual function does
+--FILE--
+getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+{closure:%s}(): Argument #1 ($b) could not be passed by reference
diff --git a/Zend/tests/partial_application/references_004.phpt b/Zend/tests/partial_application/references_004.phpt
new file mode 100644
index 000000000000..e5163c710da2
--- /dev/null
+++ b/Zend/tests/partial_application/references_004.phpt
@@ -0,0 +1,42 @@
+--TEST--
+PFA receives variadic param by ref if the actual function does
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreferences_004.php 13 - 13
+
+ - Bound Variables [2] {
+ Variable #0 [ $a ]
+ Variable #1 [ $args2 ]
+ }
+
+ - Parameters [2] {
+ Parameter #0 [ &$args0 ]
+ Parameter #1 [ &...$args ]
+ }
+}
+
+int(-2)
+int(-3)
+int(-4)
diff --git a/Zend/tests/partial_application/references_005.phpt b/Zend/tests/partial_application/references_005.phpt
new file mode 100644
index 000000000000..e8c7c27a07be
--- /dev/null
+++ b/Zend/tests/partial_application/references_005.phpt
@@ -0,0 +1,26 @@
+--TEST--
+PFA inherits return by ref
+--FILE--
+
+--EXPECT--
+array(1) {
+ [0]=>
+ int(1)
+}
+array(1) {
+ [0]=>
+ int(1)
+}
diff --git a/Zend/tests/partial_application/reflection_001.phpt b/Zend/tests/partial_application/reflection_001.phpt
new file mode 100644
index 000000000000..ac079515d0a0
--- /dev/null
+++ b/Zend/tests/partial_application/reflection_001.phpt
@@ -0,0 +1,48 @@
+--TEST--
+PFA reflection: optional parameters
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreflection_001.php 6 - 6
+
+ - Parameters [3] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $b = 5 ]
+ Parameter #2 [ $c = 10 ]
+ }
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreflection_001.php 10 - 10
+
+ - Parameters [3] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $b ]
+ Parameter #2 [ $c = 10 ]
+ }
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreflection_001.php 14 - 14
+
+ - Parameters [3] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $b ]
+ Parameter #2 [ $c ]
+ }
+}
diff --git a/Zend/tests/partial_application/reflection_002.phpt b/Zend/tests/partial_application/reflection_002.phpt
new file mode 100644
index 000000000000..da91a7af50cf
--- /dev/null
+++ b/Zend/tests/partial_application/reflection_002.phpt
@@ -0,0 +1,57 @@
+--TEST--
+PFA reflection: variadics
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %s 6 - 6
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %s 10 - 10
+
+ - Parameters [2] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ ...$b ]
+ }
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %s 14 - 14
+
+ - Parameters [2] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $b0 ]
+ }
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %s 18 - 18
+
+ - Parameters [3] {
+ Parameter #0 [ $a ]
+ Parameter #1 [ $b0 ]
+ Parameter #2 [ $b1 ]
+ }
+}
diff --git a/Zend/tests/partial_application/reflection_003.phpt b/Zend/tests/partial_application/reflection_003.phpt
new file mode 100644
index 000000000000..90506d38a778
--- /dev/null
+++ b/Zend/tests/partial_application/reflection_003.phpt
@@ -0,0 +1,43 @@
+--TEST--
+PFA reflection: internal with variadics
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreflection_003.php 2 - 2
+
+ - Parameters [1] {
+ Parameter #0 [ string $format ]
+ }
+ - Return [ string ]
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreflection_003.php 6 - 6
+
+ - Parameters [2] {
+ Parameter #0 [ string $format ]
+ Parameter #1 [ mixed ...$values ]
+ }
+ - Return [ string ]
+}
+Closure [ static function {closure:%s:%d} ] {
+ @@ %sreflection_003.php 10 - 10
+
+ - Parameters [2] {
+ Parameter #0 [ string $format ]
+ Parameter #1 [ mixed $values0 ]
+ }
+ - Return [ string ]
+}
diff --git a/Zend/tests/partial_application/reflection_004.phpt b/Zend/tests/partial_application/reflection_004.phpt
new file mode 100644
index 000000000000..7226383f5afa
--- /dev/null
+++ b/Zend/tests/partial_application/reflection_004.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA reflection: ReflectionFunction::isAnonymous() is true for partials
+--FILE--
+isAnonymous());
+
+var_dump((new ReflectionFunction(function () {}))->isAnonymous());
+
+var_dump((new ReflectionFunction(sprintf(?)))->isAnonymous());
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
+bool(true)
diff --git a/Zend/tests/partial_application/reflection_005.phpt b/Zend/tests/partial_application/reflection_005.phpt
new file mode 100644
index 000000000000..be86270c004c
--- /dev/null
+++ b/Zend/tests/partial_application/reflection_005.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA reflection: ReflectionFunction::isClosure() is true for partials
+--FILE--
+isClosure());
+
+var_dump((new ReflectionFunction(function () {}))->isClosure());
+
+var_dump((new ReflectionFunction(sprintf(?)))->isClosure());
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
+bool(true)
diff --git a/Zend/tests/partial_application/relative_return_types.phpt b/Zend/tests/partial_application/relative_return_types.phpt
new file mode 100644
index 000000000000..5729f7edfd02
--- /dev/null
+++ b/Zend/tests/partial_application/relative_return_types.phpt
@@ -0,0 +1,148 @@
+--TEST--
+PFA supports relative return types
+--FILE--
+ 0) {
+ trait T {
+ public function getSelf(object $o): self {
+ return $o;
+ }
+ public function getStatic(object $o): static {
+ return $o;
+ }
+ }
+}
+
+class C {
+ use T;
+}
+
+class D extends C {
+}
+
+$c = new C;
+
+echo "# C::getSelf():\n";
+
+$self = $c->getSelf(?);
+
+echo (string) new ReflectionFunction($self), "\n";
+
+var_dump($self($c));
+var_dump($self(new D));
+try {
+ $self(new stdClass);
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "# C::getStatic():\n";
+
+$static = $c->getStatic(?);
+
+echo (string) new ReflectionFunction($static), "\n";
+
+var_dump($static($c));
+var_dump($static(new D));
+try {
+ $static(new stdClass);
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+$d = new D;
+
+echo "# D::getSelf():\n";
+
+$self = $d->getSelf(?);
+
+echo (string) new ReflectionFunction($self), "\n";
+
+var_dump($self($d));
+var_dump($self(new C));
+try {
+ $self(new stdClass);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+echo "# D::getStatic():\n";
+
+$static = $d->getStatic(?);
+
+echo (string) new ReflectionFunction($static), "\n";
+
+var_dump($static($d));
+try {
+ var_dump($static(new C));
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+try {
+ $static(new stdClass);
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+?>
+--EXPECTF--
+# C::getSelf():
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 25 - 25
+
+ - Parameters [1] {
+ Parameter #0 [ object $o ]
+ }
+ - Return [ self ]
+}
+
+object(C)#%d (0) {
+}
+object(D)#%d (0) {
+}
+C::getSelf(): Return value must be of type C, stdClass returned
+# C::getStatic():
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 39 - 39
+
+ - Parameters [1] {
+ Parameter #0 [ object $o ]
+ }
+ - Return [ static ]
+}
+
+object(C)#%d (0) {
+}
+object(D)#%d (0) {
+}
+C::getStatic(): Return value must be of type C, stdClass returned
+# D::getSelf():
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 55 - 55
+
+ - Parameters [1] {
+ Parameter #0 [ object $o ]
+ }
+ - Return [ self ]
+}
+
+object(D)#%d (0) {
+}
+object(C)#%d (0) {
+}
+TypeError: C::getSelf(): Return value must be of type C, stdClass returned
+# D::getStatic():
+Closure [ public method {closure:%s:%d} ] {
+ @@ %s 69 - 69
+
+ - Parameters [1] {
+ Parameter #0 [ object $o ]
+ }
+ - Return [ static ]
+}
+
+object(D)#%d (0) {
+}
+TypeError: C::getStatic(): Return value must be of type D, C returned
+C::getStatic(): Return value must be of type D, stdClass returned
diff --git a/Zend/tests/partial_application/return_type.phpt b/Zend/tests/partial_application/return_type.phpt
new file mode 100644
index 000000000000..ae3738e2c660
--- /dev/null
+++ b/Zend/tests/partial_application/return_type.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA inherits return type
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %s 4 - 4
+
+ - Bound Variables [1] {
+ Variable #0 [ $a ]
+ }
+
+ - Parameters [0] {
+ }
+ - Return [ array ]
+}
diff --git a/Zend/tests/partial_application/rfc_examples.inc b/Zend/tests/partial_application/rfc_examples.inc
new file mode 100644
index 000000000000..53babc07888b
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples.inc
@@ -0,0 +1,76 @@
+ [$pfa, $closure]) {
+ echo "# ", $test, ": ";
+ $pfaReflector = new ReflectionFunction($pfa);
+ $closureReflector = new ReflectionFunction($closure);
+
+ try {
+ if (count($pfaReflector->getParameters()) !== count($closureReflector->getParameters())) {
+ throw new Exception(sprintf(
+ "Arity does not match: expected %d, got %d",
+ count($closureReflector->getParameters()),
+ count($pfaReflector->getParameters()),
+ ));
+ }
+
+ $it = new MultipleIterator();
+ $it->attachIterator(new ArrayIterator($pfaReflector->getParameters()));
+ $it->attachIterator(new ArrayIterator($closureReflector->getParameters()));
+ foreach ($it as $i => [$pfaParam, $closureParam]) {
+ [$i] = $i;
+ if ($pfaParam->getName() !== $closureParam->getName()) {
+ throw new Exception(sprintf("Name of param %d does not match: %s vs %s",
+ $i,
+ $pfaParam->getName(),
+ $closureParam->getName(),
+ ));
+ }
+ if ((string)$pfaParam->getType() !== (string)$closureParam->getType()) {
+ throw new Exception(sprintf("Type of param %d does not match: %s vs %s",
+ $i,
+ $pfaParam->getType(),
+ $closureParam->getType(),
+ ));
+ }
+ if ($pfaParam->isOptional() !== $closureParam->isOptional()) {
+ throw new Exception(sprintf("Optionalness of param %d does not match: %d vs %d",
+ $i,
+ $pfaParam->isOptional(),
+ $closureParam->isOptional(),
+ ));
+ }
+ }
+
+ $args = [];
+ foreach ($pfaReflector->getParameters() as $i => $p) {
+ $args[] = match ((string) $p->getType()) {
+ 'int' => 100 + $i,
+ 'float' => 100.5 + $i,
+ '?float' => 100.5 + $i,
+ 'string' => (string) (100 + $i),
+ 'Point' => new Point,
+ 'mixed' => "mixed($i)",
+ '' => "mixed($i)",
+ };
+ }
+
+ if ($pfaReflector->getClosureThis() !== $closureReflector->getClosureThis()) {
+ throw new Exception("\$this differs");
+ }
+
+ if ($pfa(...$args) !== $closure(...$args)) {
+ throw new Exception("PFA is not equivalent to closure");
+ }
+ } catch (Exception $e) {
+ echo $e->getMessage(), "\n";
+ echo $pfaReflector;
+ echo $closureReflector;
+ return;
+ }
+
+ echo "Ok\n";
+ }
+}
diff --git a/Zend/tests/partial_application/rfc_examples_const_expr.phpt b/Zend/tests/partial_application/rfc_examples_const_expr.phpt
new file mode 100644
index 000000000000..e2fc57440256
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_const_expr.phpt
@@ -0,0 +1,25 @@
+--TEST--
+PFA RFC examples: "Constant expressions" section
+--XFAIL--
+PFA in constant expressions not implemented yet
+--FILE--
+
+==DONE==
+--EXPECTF--
+==DONE==
diff --git a/Zend/tests/partial_application/rfc_examples_debug.phpt b/Zend/tests/partial_application/rfc_examples_debug.phpt
new file mode 100644
index 000000000000..9c5501090d4e
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_debug.phpt
@@ -0,0 +1,25 @@
+--TEST--
+PFA RFC examples: "Debug output" section
+--FILE--
+
+--EXPECTF--
+Fatal error: Uncaught Exception in %s:%d
+Stack trace:
+#0 %s(%d): g()
+#1 %s(%d): f(1, Object(SensitiveParameterValue), 3)
+#2 %s(%d): {closure:pfa:%s}(1, Object(SensitiveParameterValue))
+#3 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/partial_application/rfc_examples_errors.phpt b/Zend/tests/partial_application/rfc_examples_errors.phpt
new file mode 100644
index 000000000000..21818d7446e3
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_errors.phpt
@@ -0,0 +1,34 @@
+--TEST--
+PFA RFC examples: "Error examples" section
+--FILE--
+getMessage(), "\n";
+}
+
+try {
+ stuff(?, ?, ?, ?, ?, ?);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+try {
+ stuff(?, ?, 3.5, $point, i: 5);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+ArgumentCountError: Partial application of stuff() expects at least 4 arguments, 1 given
+ArgumentCountError: Partial application of stuff() expects at most 5 arguments, 6 given
+Error: Named parameter $i overwrites previous placeholder
diff --git a/Zend/tests/partial_application/rfc_examples_eval_order.phpt b/Zend/tests/partial_application/rfc_examples_eval_order.phpt
new file mode 100644
index 000000000000..2fd6c9422e92
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_eval_order.phpt
@@ -0,0 +1,30 @@
+--TEST--
+PFA RFC examples: "Evaluation order" section
+--FILE--
+ speak($who, getArg());
+print "Arnaud\n";
+$arrow('Larry');
+
+$partial = speak(?, getArg());
+print "Arnaud\n";
+$partial('Larry');
+
+?>
+--EXPECT--
+Arnaud
+getArg
+Larry: hi
+getArg
+Arnaud
+Larry: hi
diff --git a/Zend/tests/partial_application/rfc_examples_extra_args.phpt b/Zend/tests/partial_application/rfc_examples_extra_args.phpt
new file mode 100644
index 000000000000..f6a24df8cc71
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_extra_args.phpt
@@ -0,0 +1,54 @@
+--TEST--
+PFA RFC examples: "Variadics, func_get_args(), and extraneous arguments" section
+--FILE--
+ [
+ foo(1, ?),
+ static function (int $j) { return foo(1, $j); },
+ ],
+ 'If a PFA call has a ... placeholder, then any extraneous arguments will be passed through to the underlying function' => [
+ foo(1, ?, ...),
+ static function (int $j) { return foo(1, $j, ...array_slice(func_get_args(), 1)); },
+ ],
+ 'If a PFA call has a ... placeholder and the underlying function is variadic, then the trailing arguments will be forwarded directly but will get “collected” by the variadic parameter as normal' => [
+ foo2(1, ?, ...),
+ static function (int $j, ...$extra) { return foo2(1, $j, ...$extra); },
+ ],
+];
+
+check_equivalence($tests);
+
+echo "# The extra parameter here will be passed to the closure object, which will simply ignore it:\n";
+var_dump(foo(1, ?)(4, 'ignore me'));
+
+echo "# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will be accessible only via ''func_get_args()'' et al:\n";
+var_dump(foo(1, ?, ...)(4, 'ignore me'));
+
+echo "# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will show up as part of the \$extra array:\n";
+var_dump(foo2(1, ?, ...)(4, 'ignore me'));
+
+?>
+==DONE==
+--EXPECT--
+# If a PFA call has no ... placeholder, then any extraneous arguments to the resulting closure will be ignored. That is consistent with how manually writing the equivalent closure would behave, and is the same regardless of whether the underlying function is variadic: Ok
+# If a PFA call has a ... placeholder, then any extraneous arguments will be passed through to the underlying function: Ok
+# If a PFA call has a ... placeholder and the underlying function is variadic, then the trailing arguments will be forwarded directly but will get “collected” by the variadic parameter as normal: Ok
+# The extra parameter here will be passed to the closure object, which will simply ignore it:
+int(2)
+# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will be accessible only via ''func_get_args()'' et al:
+int(3)
+# The extra parameter here will be passed to the closure object, which will forward it directly to the underlying function. It will show up as part of the $extra array:
+int(3)
+==DONE==
diff --git a/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt b/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt
new file mode 100644
index 000000000000..dd99cb229ae8
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_incompatible_functions.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFA RFC examples: "Incompatible functions" section
+--FILE--
+getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+Error: Cannot call func_get_arg() dynamically
diff --git a/Zend/tests/partial_application/rfc_examples_magic_methods.phpt b/Zend/tests/partial_application/rfc_examples_magic_methods.phpt
new file mode 100644
index 000000000000..7162a790c57c
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_magic_methods.phpt
@@ -0,0 +1,47 @@
+--TEST--
+PFA RFC examples: "Magic methods" section
+--FILE--
+ [
+ $f->method(?, ?),
+ (function ($f) {
+ return fn(mixed $arguments0, mixed $arguments1) => $f->method($arguments0, $arguments1);
+ })($f)->bindTo($f),
+ ],
+]);
+
+try {
+ $f->method(?, ?)(a: 1, b: 2);
+} catch (Error $e) {
+ echo $e::class, ": ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+# Test 1: Foo::method
+Array
+(
+ [0] => mixed(0)
+ [1] => mixed(1)
+)
+Foo::method
+Array
+(
+ [0] => mixed(0)
+ [1] => mixed(1)
+)
+Ok
+Error: Unknown named parameter $a
diff --git a/Zend/tests/partial_application/rfc_examples_overview.phpt b/Zend/tests/partial_application/rfc_examples_overview.phpt
new file mode 100644
index 000000000000..b6fc8c0586c2
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_overview.phpt
@@ -0,0 +1,44 @@
+--TEST--
+PFA RFC examples: "Overview" section
+--FILE--
+ [
+ foo(1, ?, 3, 4),
+ static fn(int $b): int => foo(1, $b, 3, 4),
+ ],
+ 'Test 2' => [
+ foo(1, ?, 3, ?),
+ static fn(int $b, int $d): int => foo(1, $b, 3, $d),
+ ],
+ 'Test 3' => [
+ foo(1, ...),
+ static fn(int $b, int $c, int $d): int => foo(1, $b, $c, $d),
+ ],
+ 'Test 4' => [
+ foo(1, 2, ...),
+ static fn(int $c, int $d): int => foo(1, 2, $c, $d),
+ ],
+ 'Test 5' => [
+ foo(1, ?, 3, ...),
+ static fn(int $b, int $d): int => foo(1, $b, 3, $d),
+ ],
+];
+
+check_equivalence($tests);
+
+?>
+--EXPECT--
+# Test 1: Ok
+# Test 2: Ok
+# Test 3: Ok
+# Test 4: Ok
+# Test 5: Ok
diff --git a/Zend/tests/partial_application/rfc_examples_scoping.phpt b/Zend/tests/partial_application/rfc_examples_scoping.phpt
new file mode 100644
index 000000000000..232d225dca6a
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_scoping.phpt
@@ -0,0 +1,113 @@
+--TEST--
+PFA RFC examples: "Scoping" section
+--FILE--
+ [
+ foo(?, ?),
+ static fn(int $i, int $j): string => foo($i, $j),
+ ],
+ 'Static closure 2' => [
+ Foo::bar(?, ?),
+ static fn(int $i, int $j): string => Foo::bar($i, $j),
+ ],
+ 'Static closure 3' => [
+ foo(?, ?)(1, ?),
+ static fn(int $j): string => foo(1, $j),
+ ],
+ 'Static closure 4' => [
+ foo(...)(?),
+ static fn(int $i): string => foo($i, 0),
+ ],
+];
+
+check_equivalence($tests);
+
+$c = new C();
+$f = $c->f(?);
+
+echo "# Cannot unbind \$this:\n";
+var_dump($f->bindTo(null, C::class)); // Warning: Cannot unbind $this of method, this will be an error in PHP 9 (returns null)
+
+echo "# Cannot rebind scope:\n";
+var_dump($f->bindTo($c, CSubClass::class)); // Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 (returns null)
+
+echo "# Can rebind \$this with subclass:\n";
+var_dump($f->bindTo(new CSubClass, C::class)); // Allowed
+
+echo "# Cannot rebind \$this with unrelated class:\n";
+$f = $f->bindTo(new Unrelated, C::class); // Warning: Cannot bind method C::{closure:/path/to/test.php:11}() to object of class Unrelated, this will be an error in PHP 9 (returns null)
+
+echo "# self resolution:\n";
+$c = new CSubClass();
+var_dump($c->f(?)(1)); // string(1) "C"
+var_dump($c->g(?)(1)); // string(9) "CSubClass"
+var_dump($c->h(1)(1)); // string(1) "C"
+
+?>
+--EXPECTF--
+# Static closure 1: Ok
+# Static closure 2: Ok
+# Static closure 3: Ok
+# Static closure 4: Ok
+# Cannot unbind $this:
+
+Warning: Cannot unbind $this of method, this will be an error in PHP 9 in %s on line %d
+NULL
+# Cannot rebind scope:
+
+Warning: Cannot rebind scope of closure created from method, this will be an error in PHP 9 in %s on line %d
+NULL
+# Can rebind $this with subclass:
+object(Closure)#%d (5) {
+ ["name"]=>
+ string(%d) "{closure:pfa:%s}"
+ ["file"]=>
+ string(%d) "%s"
+ ["line"]=>
+ int(53)
+ ["this"]=>
+ object(CSubClass)#%d (0) {
+ }
+ ["parameter"]=>
+ array(1) {
+ ["$a"]=>
+ string(10) ""
+ }
+}
+# Cannot rebind $this with unrelated class:
+
+Warning: Cannot bind method C::{closure:pfa:%s}() to object of class Unrelated, this will be an error in PHP 9 in %s on line %d
+# self resolution:
+string(1) "C"
+string(9) "CSubClass"
+string(1) "C"
diff --git a/Zend/tests/partial_application/rfc_examples_semantics.phpt b/Zend/tests/partial_application/rfc_examples_semantics.phpt
new file mode 100644
index 000000000000..a3ea25c30d6d
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_semantics.phpt
@@ -0,0 +1,30 @@
+--TEST--
+PFA RFC examples: "Placeholder Semantics" section
+--FILE--
+ [
+ foo(?, ?, ?, ?),
+ static fn(int $a, int $b, string $c0, string $c1) => foo($a, $b, $c0, $c1),
+ ],
+ 'Test 2' => [
+ stuff(1, ?, p: ?, f: 3.14, ...),
+ static fn(string $s, Point $p, int $m = 0) => stuff(1, $s, 3.14, $p, $m),
+ ],
+];
+
+check_equivalence($tests);
+
+?>
+--EXPECT--
+# Test 1: Ok
+# Test 2: Ok
diff --git a/Zend/tests/partial_application/rfc_examples_semantics_examples.phpt b/Zend/tests/partial_application/rfc_examples_semantics_examples.phpt
new file mode 100644
index 000000000000..d8c4a83ca074
--- /dev/null
+++ b/Zend/tests/partial_application/rfc_examples_semantics_examples.phpt
@@ -0,0 +1,217 @@
+--TEST--
+PFA RFC examples: "Placeholder Semantics: Examples" section
+--FILE--
+ [
+ stuff(?, ?, ...),
+ static fn(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array
+ => stuff($i1, $s2, $f3, $p4, $m5),
+ ],
+ 'The degenerate "first class callables" case. (Supported since 8.1)' => [
+ stuff(...),
+ static fn(int $i1, string $s2, float $f3, Point $p4, int $m5 = 0): array
+ => stuff($i1, $s2, $f3, $p4, $m5),
+ ],
+ 'Provide some values, require the rest to be provided later (1)' => [
+ stuff(1, 'hi', ?, ?, ?),
+ static fn(float $f3, Point $p4, int $m5): array => stuff(1, 'hi', $f3, $p4, $m5),
+ ],
+ 'Provide some values, require the rest to be provided later (2)' => [
+ stuff(1, 'hi', ...),
+ static fn(float $f3, Point $p4, int $m5 = 0): array => stuff(1, 'hi', $f3, $p4, $m5),
+ ],
+ 'Provide some values, but not just from the left (1)' => [
+ stuff(1, ?, 3.5, ?, ?),
+ static fn(string $s2, Point $p4, int $m5): array => stuff(1, $s2, 3.5, $p4, $m5),
+ ],
+ 'Provide some values, but not just from the left (2)' => [
+ stuff(1, ?, 3.5, ...),
+ static fn(string $s2, Point $p4, int $m5 = 0): array => stuff(1, $s2, 3.5, $p4, $m5),
+ ],
+ 'Provide just the last value' => [
+ stuff(?, ?, ?, ?, 5),
+ static fn(int $i1, string $s2, float $f3, Point $p4): array
+ => stuff($i1, $s2, $f3, $p4, 5),
+ ],
+ 'Not accounting for an optional argument means it will always get its default value' => [
+ stuff(?, ?, ?, ?),
+ static fn(int $i1, string $s2, float $f3, Point $p4): array
+ => stuff($i1, $s2, $f3, $p4),
+ ],
+ 'Named arguments can be pulled "out of order", and still work (1)' => [
+ stuff(?, ?, f3: 3.5, p4: $point),
+ static fn(int $i1, string $s2): array => stuff($i1, $s2, 3.5, $point),
+ ],
+ 'Named arguments can be pulled "out of order", and still work (2)' => [
+ stuff(?, ?, p4: $point, f3: 3.5),
+ static fn(int $i1, string $s2): array => stuff($i1, $s2, 3.5, $point),
+ ],
+
+ 'But named placeholders adopt the order listed' => [
+ stuff(s2: ?, i1: ?, p4: ?, f3: 3.5),
+ static fn(string $s2, int $i1, Point $p4): array => stuff($i1, $s2, 3.5, $p4),
+ ],
+ 'The ... "everything else" placeholder respects named arguments' => [
+ stuff(?, ?, f3: 3.5, p4: $point, ...),
+ static fn(int $i1, string $s2, int $m5 = 0): array => stuff($i1, $s2, 3.5, $point, $m5),
+ ],
+ 'Prefill all parameters, making a "delayed call" or "thunk"' => [
+ stuff(1, 'hi', 3.4, $point, 5, ...),
+ static fn(): array => stuff(1, 'hi', 3.4, $point, 5),
+ ],
+
+ // Variadics
+
+ 'FCC equivalent. The signature is unchanged' => [
+ things(...),
+ static fn(int $i1, ?float $f3 = null, Point ...$points): array => things(...[$i1, $f3, ...$points]),
+ ],
+ 'Provide some values, but allow the variadic to remain variadic' => [
+ things(1, 3.14, ...),
+ static fn(Point ...$points): array => things(1, 3.14, ...$points),
+ ],
+ 'In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case' => [
+ things(?, ?, ?, ?),
+ static fn(int $i1, ?float $f3, Point $points0, Point $points1): array => things($i1, $f3, $points0, $points1),
+ ],
+
+ // Esoteric examples
+
+ 'Esoteric 1' => [
+ four(...),
+ static fn(int $a, int $b, int $c, int $d): string => four($a, $b, $c, $d),
+ ],
+ 'Esoteric 2' => [
+ four(1, 2, ...),
+ static fn(int $c, int $d): string => four(1, 2, $c, $d),
+ ],
+ 'Esoteric 3' => [
+ four(1, 2, 3, ?),
+ static fn(int $d): string => four(1, 2, 3, $d),
+ ],
+ 'Esoteric 4' => [
+ four(1, ?, ?, 4),
+ static fn(int $b, int $c): string => four(1, $b, $c, 4),
+ ],
+ 'Esoteric 5' => [
+ four(1, 2, 3, 4, ...),
+ static fn(): string => four(1, 2, 3, 4, ...array_slice(func_get_args(), 4)),
+ ],
+ 'Esoteric 6' => [
+ four(d: 4, a: 1, ...),
+ static fn(int $b, int $c): string => four(1, $b, $c, 4, ...array_slice(func_get_args(), 4)),
+ ],
+ 'Esoteric 7' => [
+ four(c: ?, d: 4, b: ?, a: 1),
+ static fn(int $c, int $b): string => four(1, $b, $c, 4, ...array_slice(func_get_args(), 4)),
+ ],
+
+ // Other callable styles
+
+ 'This is allowed. Note the method is static, thus the partial closure is static' => [
+ E::make(1, ?),
+ static fn(int $y): E => E::make(1, $y),
+ ],
+ 'Note the method is non-static, so the partial closure is non-static' => (function () {
+ $eMaker = E::make(1, ?);
+ $e = $eMaker(2);
+ return [
+ $e->foo(?, ?, 3),
+ (function ($e) {
+ return fn(int $a, int $b): array => $e->foo($a, $b, 3);
+ })($e)->bindTo($e),
+ ];
+ })(),
+ '$c can then be further refined' => (function () {
+ $eMaker = E::make(1, ?);
+ $e = $eMaker(2);
+ $c = $e->foo(?, ?, 3);
+ return [
+ $c(1, ?),
+ (function ($e) {
+ return fn(int $b): array => $e->foo(1, $b, 3);
+ })($e)->bindTo($e),
+ ];
+ })(),
+ 'RunMe' => (function () {
+ $r = new RunMe();
+ return [
+ $r(?, 3),
+ (function ($r) {
+ return fn(int $a): string => $r($a, 3);
+ })($r)->bindTo($r),
+ ];
+ })(),
+];
+
+check_equivalence($tests);
+
+?>
+--EXPECT--
+# Manually specify the first two values, and pull the rest "as is": Ok
+# The degenerate "first class callables" case. (Supported since 8.1): Ok
+# Provide some values, require the rest to be provided later (1): Ok
+# Provide some values, require the rest to be provided later (2): Ok
+# Provide some values, but not just from the left (1): Ok
+# Provide some values, but not just from the left (2): Ok
+# Provide just the last value: Ok
+# Not accounting for an optional argument means it will always get its default value: Ok
+# Named arguments can be pulled "out of order", and still work (1): Ok
+# Named arguments can be pulled "out of order", and still work (2): Ok
+# But named placeholders adopt the order listed: Ok
+# The ... "everything else" placeholder respects named arguments: Ok
+# Prefill all parameters, making a "delayed call" or "thunk": Ok
+# FCC equivalent. The signature is unchanged: Ok
+# Provide some values, but allow the variadic to remain variadic: Ok
+# In this version, the partial requires precisely four arguments, the last two of which will get received by things() in the variadic parameter. Note too that $f becomes required in this case: Ok
+# Esoteric 1: Ok
+# Esoteric 2: Ok
+# Esoteric 3: Ok
+# Esoteric 4: Ok
+# Esoteric 5: Ok
+# Esoteric 6: Ok
+# Esoteric 7: Ok
+# This is allowed. Note the method is static, thus the partial closure is static: Ok
+# Note the method is non-static, so the partial closure is non-static: Ok
+# $c can then be further refined: Ok
+# RunMe: Ok
diff --git a/Zend/tests/partial_application/static_method_001.phpt b/Zend/tests/partial_application/static_method_001.phpt
new file mode 100644
index 000000000000..ce4151441c33
--- /dev/null
+++ b/Zend/tests/partial_application/static_method_001.phpt
@@ -0,0 +1,18 @@
+--TEST--
+PFA supports static methods
+--FILE--
+
+--EXPECTF--
+Foo::method
diff --git a/Zend/tests/partial_application/static_pfa_001.phpt b/Zend/tests/partial_application/static_pfa_001.phpt
new file mode 100644
index 000000000000..bc0f65b1ffd8
--- /dev/null
+++ b/Zend/tests/partial_application/static_pfa_001.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA of static method is static
+--FILE--
+f(?);
+echo new ReflectionFunction($f), "\n";
+$f(1);
+
+// Warns
+var_dump($f->bindTo(new C));
+
+?>
+--EXPECTF--
+Closure [ static public method {closure:pfa:%s:9} ] {
+ @@ %s 9 - 9
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+
+int(1)
+Closure [ static public method {closure:pfa:%s:13} ] {
+ @@ %s 13 - 13
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+
+int(1)
+
+Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
+NULL
diff --git a/Zend/tests/partial_application/static_pfa_002.phpt b/Zend/tests/partial_application/static_pfa_002.phpt
new file mode 100644
index 000000000000..5222109958b8
--- /dev/null
+++ b/Zend/tests/partial_application/static_pfa_002.phpt
@@ -0,0 +1,35 @@
+--TEST--
+PFA of instance method is not static
+--FILE--
+f(?);
+echo new ReflectionFunction($f), "\n";
+$f($c);
+
+$c2 = new C();
+$f->bindTo($c2)($c2);
+
+?>
+--EXPECTF--
+Closure [ public method {closure:pfa:%s:10} ] {
+ @@ %s 10 - 10
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+
+object(C)#%d (0) {
+}
+bool(true)
+object(C)#%d (0) {
+}
+bool(true)
diff --git a/Zend/tests/partial_application/static_pfa_003.phpt b/Zend/tests/partial_application/static_pfa_003.phpt
new file mode 100644
index 000000000000..e7b3c187e187
--- /dev/null
+++ b/Zend/tests/partial_application/static_pfa_003.phpt
@@ -0,0 +1,30 @@
+--TEST--
+PFA of standalone function is static
+--FILE--
+bindTo(new class {}));
+
+?>
+--EXPECTF--
+Closure [ static function {closure:pfa:%s:7} ] {
+ @@ %s 7 - 7
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+
+int(1)
+
+Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
+NULL
diff --git a/Zend/tests/partial_application/static_pfa_004.phpt b/Zend/tests/partial_application/static_pfa_004.phpt
new file mode 100644
index 000000000000..093430232d43
--- /dev/null
+++ b/Zend/tests/partial_application/static_pfa_004.phpt
@@ -0,0 +1,44 @@
+--TEST--
+PFA of non-static closure is not static
+--FILE--
+getMessage(), "\n";
+ }
+};
+
+echo "# Original PFA\n";
+
+$f = $c(?);
+echo new ReflectionFunction($f), "\n";
+$f($c);
+
+echo "# Re-bound PFA\n";
+
+$c2 = new class {};
+$f->bindTo($c2)($c2);
+
+?>
+--EXPECTF--
+# Original PFA
+Closure [ function {closure:pfa:%s:13} ] {
+ @@ %s 13 - 13
+
+ - Bound Variables [1] {
+ Variable #0 [ $fn ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+
+Error: Using $this when not in object context
+# Re-bound PFA
+object(class@anonymous)#3 (0) {
+}
+bool(true)
diff --git a/Zend/tests/partial_application/static_pfa_005.phpt b/Zend/tests/partial_application/static_pfa_005.phpt
new file mode 100644
index 000000000000..2783485b2f49
--- /dev/null
+++ b/Zend/tests/partial_application/static_pfa_005.phpt
@@ -0,0 +1,34 @@
+--TEST--
+PFA of static closure is static
+--FILE--
+bindTo(new class {}));
+
+?>
+--EXPECTF--
+Closure [ static function {closure:pfa:%s:7} ] {
+ @@ %s 7 - 7
+
+ - Bound Variables [1] {
+ Variable #0 [ $fn ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $a ]
+ }
+}
+
+int(1)
+
+Warning: Cannot bind an instance to a static closure, this will be an error in PHP 9 in %s on line %d
+NULL
diff --git a/Zend/tests/partial_application/static_polymorphism.phpt b/Zend/tests/partial_application/static_polymorphism.phpt
new file mode 100644
index 000000000000..5f121c65c0e8
--- /dev/null
+++ b/Zend/tests/partial_application/static_polymorphism.phpt
@@ -0,0 +1,43 @@
+--TEST--
+PFA: static polymorphism
+--FILE--
+
+--EXPECT--
+P::m
+string(1) "a"
+C::m
+array(0) {
+}
+P::m
+string(1) "a"
+C::m
+array(0) {
+}
diff --git a/Zend/tests/partial_application/static_vars_001.phpt b/Zend/tests/partial_application/static_vars_001.phpt
new file mode 100644
index 000000000000..610cddf37b54
--- /dev/null
+++ b/Zend/tests/partial_application/static_vars_001.phpt
@@ -0,0 +1,21 @@
+--TEST--
+PFA static variables are shared (001)
+--FILE--
+
+--EXPECT--
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/static_vars_002.phpt b/Zend/tests/partial_application/static_vars_002.phpt
new file mode 100644
index 000000000000..4e6cc82b720d
--- /dev/null
+++ b/Zend/tests/partial_application/static_vars_002.phpt
@@ -0,0 +1,22 @@
+--TEST--
+PFA static variables are shared (002)
+--FILE--
+
+--EXPECT--
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/static_vars_003.phpt b/Zend/tests/partial_application/static_vars_003.phpt
new file mode 100644
index 000000000000..04328bf717b8
--- /dev/null
+++ b/Zend/tests/partial_application/static_vars_003.phpt
@@ -0,0 +1,25 @@
+--TEST--
+PFA static variables are shared (003)
+--FILE--
+
+--EXPECT--
+int(1)
+int(2)
diff --git a/Zend/tests/partial_application/superfluous_args_are_forwarded.phpt b/Zend/tests/partial_application/superfluous_args_are_forwarded.phpt
new file mode 100644
index 000000000000..506092655217
--- /dev/null
+++ b/Zend/tests/partial_application/superfluous_args_are_forwarded.phpt
@@ -0,0 +1,44 @@
+--TEST--
+PFAs forwards superfluous args iff a variadic placeholder is specified
+--FILE--
+
+--EXPECT--
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ [2]=>
+ int(3)
+}
+array(1) {
+ [0]=>
+ int(1)
+}
+array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ [2]=>
+ int(3)
+}
diff --git a/Zend/tests/partial_application/this.phpt b/Zend/tests/partial_application/this.phpt
new file mode 100644
index 000000000000..bda6900bae39
--- /dev/null
+++ b/Zend/tests/partial_application/this.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA $this
+--FILE--
+method(new stdClass, ...);
+
+$baz = $bar(new stdClass, ...);
+
+var_dump($foo === $baz());
+?>
+--EXPECT--
+bool(true)
diff --git a/Zend/tests/partial_application/variation_call_001.phpt b/Zend/tests/partial_application/variation_call_001.phpt
new file mode 100644
index 000000000000..b9c1c19ec9c6
--- /dev/null
+++ b/Zend/tests/partial_application/variation_call_001.phpt
@@ -0,0 +1,31 @@
+--TEST--
+PFA variation: call
+--FILE--
+method(?, new Param);
+
+$closure(1);
+
+$closure->call(/* newThis: */ new Foo(), 10);
+?>
+--EXPECT--
+Bar: 1, Param
+Foo: 10, Param
diff --git a/Zend/tests/partial_application/variation_closure_001.phpt b/Zend/tests/partial_application/variation_closure_001.phpt
new file mode 100644
index 000000000000..1d31f22c7f8a
--- /dev/null
+++ b/Zend/tests/partial_application/variation_closure_001.phpt
@@ -0,0 +1,40 @@
+--TEST--
+PFA variation: Closure
+--FILE--
+
+--EXPECTF--
+Closure [ function {closure:%s:%d} ] {
+ @@ %s 5 - 5
+
+ - Bound Variables [2] {
+ Variable #0 [ $fn ]
+ Variable #1 [ $a ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $b ]
+ }
+}
+Closure [ function {closure:pfa:%s:%d} ] {
+ @@ %s 10 - 10
+
+ - Bound Variables [2] {
+ Variable #0 [ $fn2 ]
+ Variable #1 [ $a ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $fn ]
+ }
+}
diff --git a/Zend/tests/partial_application/variation_closure_002.phpt b/Zend/tests/partial_application/variation_closure_002.phpt
new file mode 100644
index 000000000000..41abe0aaab0e
--- /dev/null
+++ b/Zend/tests/partial_application/variation_closure_002.phpt
@@ -0,0 +1,28 @@
+--TEST--
+PFA variation: Closure::__invoke()
+--FILE--
+__invoke(1, ?);
+
+echo (string) new ReflectionFunction($function);
+
+$function(10);
+?>
+--EXPECTF--
+Closure [ public method {closure:%s:%d} ] {
+ @@ %svariation_closure_002.php 6 - 6
+
+ - Bound Variables [1] {
+ Variable #0 [ $a ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $b ]
+ }
+}
+int(1)
+int(10)
diff --git a/Zend/tests/partial_application/variation_closure_003.phpt b/Zend/tests/partial_application/variation_closure_003.phpt
new file mode 100644
index 000000000000..da567e179853
--- /dev/null
+++ b/Zend/tests/partial_application/variation_closure_003.phpt
@@ -0,0 +1,46 @@
+--TEST--
+PFA variation: Closure::__invoke() with $this
+--FILE--
+bar();
+
+$function = $closure->__invoke(1, ?);
+
+echo (string) new ReflectionFunction($function);
+
+var_dump($function(10));
+?>
+--EXPECTF--
+Closure [ public method {closure:%s:%d} ] {
+ @@ %svariation_closure_003.php 14 - 14
+
+ - Bound Variables [1] {
+ Variable #0 [ $a ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ $b ]
+ }
+}
+array(2) {
+ [0]=>
+ object(Foo)#1 (0) {
+ }
+ [1]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(10)
+ }
+}
diff --git a/Zend/tests/partial_application/variation_debug_001.phpt b/Zend/tests/partial_application/variation_debug_001.phpt
new file mode 100644
index 000000000000..04b63f3c4010
--- /dev/null
+++ b/Zend/tests/partial_application/variation_debug_001.phpt
@@ -0,0 +1,40 @@
+--TEST--
+PFA variation: var_dump(), user function
+--FILE--
+
+--EXPECTF--
+object(Closure)#%d (5) {
+ ["name"]=>
+ string(%d) "{closure:%s}"
+ ["file"]=>
+ string(%d) "%svariation_debug_001.php"
+ ["line"]=>
+ int(6)
+ ["static"]=>
+ array(4) {
+ ["b"]=>
+ object(stdClass)#%d (0) {
+ }
+ ["c"]=>
+ int(20)
+ ["c2"]=>
+ object(stdClass)#%d (0) {
+ }
+ ["extra_named_params"]=>
+ array(1) {
+ ["four"]=>
+ int(4)
+ }
+ }
+ ["parameter"]=>
+ array(1) {
+ ["$a"]=>
+ string(10) ""
+ }
+}
diff --git a/Zend/tests/partial_application/variation_debug_002.phpt b/Zend/tests/partial_application/variation_debug_002.phpt
new file mode 100644
index 000000000000..a7c4c2d76e49
--- /dev/null
+++ b/Zend/tests/partial_application/variation_debug_002.phpt
@@ -0,0 +1,49 @@
+--TEST--
+PFA variation: var_dump(), internal function
+--FILE--
+
+--EXPECTF--
+object(Closure)#%d (5) {
+ ["name"]=>
+ string(%d) "{closure:%s}"
+ ["file"]=>
+ string(%d) "%svariation_debug_002.php"
+ ["line"]=>
+ int(2)
+ ["static"]=>
+ array(3) {
+ ["array"]=>
+ array(3) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ [2]=>
+ int(3)
+ }
+ ["arrays2"]=>
+ array(3) {
+ [0]=>
+ int(4)
+ [1]=>
+ int(5)
+ [2]=>
+ int(6)
+ }
+ ["extra_named_params"]=>
+ array(1) {
+ ["four"]=>
+ object(stdClass)#%d (0) {
+ }
+ }
+ }
+ ["parameter"]=>
+ array(2) {
+ ["$callback"]=>
+ string(10) ""
+ ["$arrays"]=>
+ string(10) ""
+ }
+}
diff --git a/Zend/tests/partial_application/variation_ex_001.phpt b/Zend/tests/partial_application/variation_ex_001.phpt
new file mode 100644
index 000000000000..0f36e90f616d
--- /dev/null
+++ b/Zend/tests/partial_application/variation_ex_001.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFA variation: UAF in cleanup unfinished calls
+--FILE--
+getMessage(), "\n";
+}
+?>
+--EXPECTF--
+ArgumentCountError: Partial application of {closure:%s:%d}() expects at most 0 arguments, 1 given
diff --git a/Zend/tests/partial_application/variation_gc_001.phpt b/Zend/tests/partial_application/variation_gc_001.phpt
new file mode 100644
index 000000000000..2db49cc74363
--- /dev/null
+++ b/Zend/tests/partial_application/variation_gc_001.phpt
@@ -0,0 +1,19 @@
+--TEST--
+PFA variation: GC (001)
+--FILE--
+method = self::__construct(new stdClass, ...);
+ }
+}
+
+$foo = new Foo(new stdClass);
+$foo->bar = $foo;
+
+echo "OK";
+?>
+--EXPECT--
+OK
diff --git a/Zend/tests/partial_application/variation_gc_002.phpt b/Zend/tests/partial_application/variation_gc_002.phpt
new file mode 100644
index 000000000000..31c721e8e681
--- /dev/null
+++ b/Zend/tests/partial_application/variation_gc_002.phpt
@@ -0,0 +1,11 @@
+--TEST--
+PFA variation: GC (002)
+--FILE--
+prop = var_dump($obj, ?);
+
+echo "OK";
+?>
+--EXPECT--
+OK
diff --git a/Zend/tests/partial_application/variation_gc_003.phpt b/Zend/tests/partial_application/variation_gc_003.phpt
new file mode 100644
index 000000000000..23cdaa1c666f
--- /dev/null
+++ b/Zend/tests/partial_application/variation_gc_003.phpt
@@ -0,0 +1,15 @@
+--TEST--
+PFA variation: GC (003)
+--FILE--
+prop = test(?, x: $obj);
+
+echo "OK";
+?>
+--EXPECT--
+OK
diff --git a/Zend/tests/partial_application/variation_invoke_001.phpt b/Zend/tests/partial_application/variation_invoke_001.phpt
new file mode 100644
index 000000000000..9285c08da19b
--- /dev/null
+++ b/Zend/tests/partial_application/variation_invoke_001.phpt
@@ -0,0 +1,21 @@
+--TEST--
+PFA variation: __invoke()
+--FILE--
+__invoke(32));
+
+try {
+ $foo->nothing();
+} catch (Error $ex) {
+ echo $ex::class, ": ", $ex->getMessage(), "\n";
+}
+?>
+--EXPECT--
+int(42)
+Error: Call to undefined method Closure::nothing()
diff --git a/Zend/tests/partial_application/variation_nocall_001.phpt b/Zend/tests/partial_application/variation_nocall_001.phpt
new file mode 100644
index 000000000000..3fbb3ec8d8c3
--- /dev/null
+++ b/Zend/tests/partial_application/variation_nocall_001.phpt
@@ -0,0 +1,12 @@
+--TEST--
+PFA variation: no call args leak
+--FILE--
+
+--EXPECT--
+OK
diff --git a/Zend/tests/partial_application/variation_nocall_002.phpt b/Zend/tests/partial_application/variation_nocall_002.phpt
new file mode 100644
index 000000000000..cd4823f1bd05
--- /dev/null
+++ b/Zend/tests/partial_application/variation_nocall_002.phpt
@@ -0,0 +1,30 @@
+--TEST--
+PFA variation: no call, order of destruction
+--FILE--
+id, "\n";
+ }
+}
+$foo = new Foo;
+$f = $foo->method(?);
+$g = $f(?);
+
+$map = new WeakMap();
+$map[$f] = new Dtor(1);
+$map[$g] = new Dtor(2);
+
+unset($f);
+unset($g);
+
+echo "OK";
+?>
+--EXPECT--
+Dtor::__destruct 2
+Dtor::__destruct 1
+OK
diff --git a/Zend/tests/partial_application/variation_parent_001.phpt b/Zend/tests/partial_application/variation_parent_001.phpt
new file mode 100644
index 000000000000..98095f45009a
--- /dev/null
+++ b/Zend/tests/partial_application/variation_parent_001.phpt
@@ -0,0 +1,76 @@
+--TEST--
+PFA variation: parent
+--FILE--
+method(10, ...);
+$baz = $bar(20, ...);
+
+var_dump($baz, $c = $baz(), $c() === $foo);
+?>
+--EXPECTF--
+object(Closure)#%d (6) {
+ ["name"]=>
+ string(%d) "{closure:%s}"
+ ["file"]=>
+ string(%d) "%svariation_parent_001.php"
+ ["line"]=>
+ int(12)
+ ["static"]=>
+ array(2) {
+ ["fn"]=>
+ object(Closure)#%d (6) {
+ ["name"]=>
+ string(%d) "{closure:%s:%d}"
+ ["file"]=>
+ string(%d) "%s"
+ ["line"]=>
+ int(11)
+ ["static"]=>
+ array(1) {
+ ["a"]=>
+ int(10)
+ }
+ ["this"]=>
+ object(Foo)#%d (0) {
+ }
+ ["parameter"]=>
+ array(2) {
+ ["$b"]=>
+ string(10) ""
+ ["$c"]=>
+ string(10) ""
+ }
+ }
+ ["b"]=>
+ int(20)
+ }
+ ["this"]=>
+ object(Foo)#%d (0) {
+ }
+ ["parameter"]=>
+ array(1) {
+ ["$c"]=>
+ string(10) ""
+ }
+}
+object(Closure)#%d (4) {
+ ["name"]=>
+ string(25) "{closure:Foo::method():4}"
+ ["file"]=>
+ string(%d) "%s"
+ ["line"]=>
+ int(4)
+ ["this"]=>
+ object(Foo)#%d (0) {
+ }
+}
+bool(true)
diff --git a/Zend/tests/partial_application/variation_scope_001.phpt b/Zend/tests/partial_application/variation_scope_001.phpt
new file mode 100644
index 000000000000..0dcea0921c43
--- /dev/null
+++ b/Zend/tests/partial_application/variation_scope_001.phpt
@@ -0,0 +1,18 @@
+--TEST--
+PFA variation: called scope
+--FILE--
+method(new stdClass, ...);
+
+$bar();
+?>
+--EXPECT--
+Foo::method
diff --git a/Zend/tests/partial_application/variation_strict_001.phpt b/Zend/tests/partial_application/variation_strict_001.phpt
new file mode 100644
index 000000000000..835f6e138055
--- /dev/null
+++ b/Zend/tests/partial_application/variation_strict_001.phpt
@@ -0,0 +1,20 @@
+--TEST--
+PFA variation: strict_types declared
+--FILE--
+getMessage());
+}
+?>
+--EXPECTF--
+TypeError: {closure:%s:%d}(): Argument #1 ($int) must be of type int, string given, called in %s on line %d
diff --git a/Zend/tests/partial_application/variation_variadics_001.phpt b/Zend/tests/partial_application/variation_variadics_001.phpt
new file mode 100644
index 000000000000..850f0eda149d
--- /dev/null
+++ b/Zend/tests/partial_application/variation_variadics_001.phpt
@@ -0,0 +1,31 @@
+--TEST--
+PFA variation: variadics, user function
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %s 6 - 6
+
+ - Bound Variables [2] {
+ Variable #0 [ $a ]
+ Variable #1 [ $b2 ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ ...$b ]
+ }
+}
+int(10)
+int(100)
+int(1000)
+int(10000)
diff --git a/Zend/tests/partial_application/variation_variadics_002.phpt b/Zend/tests/partial_application/variation_variadics_002.phpt
new file mode 100644
index 000000000000..21d8169fc42c
--- /dev/null
+++ b/Zend/tests/partial_application/variation_variadics_002.phpt
@@ -0,0 +1,25 @@
+--TEST--
+PFA variation: variadics, internal function
+--FILE--
+
+--EXPECTF--
+Closure [ static function {closure:%s:%d} ] {
+ @@ %svariation_variadics_002.php 2 - 2
+
+ - Bound Variables [2] {
+ Variable #0 [ $format ]
+ Variable #1 [ $values2 ]
+ }
+
+ - Parameters [1] {
+ Parameter #0 [ mixed ...$values ]
+ }
+ - Return [ string ]
+}
+100 1000 10000
diff --git a/Zend/tests/partial_application/variation_variadics_004.phpt b/Zend/tests/partial_application/variation_variadics_004.phpt
new file mode 100644
index 000000000000..228404dc8272
--- /dev/null
+++ b/Zend/tests/partial_application/variation_variadics_004.phpt
@@ -0,0 +1,45 @@
+--TEST--
+PFA variation: variadics and optional args
+--FILE--
+
+--EXPECT--
+# Bound year, pass day:
+2006-01-02
+# Bound month, pass day:
+2005-12-02
+# Bound month, bound year, pass day:
+2016-12-02
+# Bound month, no args:
+2005-12-01
+# Bound month, bound year, no args:
+2016-12-01
diff --git a/Zend/tests/partial_application/variation_variadics_006.phpt b/Zend/tests/partial_application/variation_variadics_006.phpt
new file mode 100644
index 000000000000..cda62a2f3bfb
--- /dev/null
+++ b/Zend/tests/partial_application/variation_variadics_006.phpt
@@ -0,0 +1,17 @@
+--TEST--
+PFA variation: named may overwrite variadic placeholder
+--FILE--
+
+--EXPECTF--
+array(1) {
+ [0]=>
+ string(1) "a"
+}
diff --git a/Zend/tests/partial_application/variation_variadics_007.phpt b/Zend/tests/partial_application/variation_variadics_007.phpt
new file mode 100644
index 000000000000..9624e05b449b
--- /dev/null
+++ b/Zend/tests/partial_application/variation_variadics_007.phpt
@@ -0,0 +1,14 @@
+--TEST--
+PFA variation: extra through variadic
+--FILE--
+ $a + $b));
+?>
+--EXPECT--
+int(3)
diff --git a/Zend/tests/partial_application/variation_variadics_008.phpt b/Zend/tests/partial_application/variation_variadics_008.phpt
new file mode 100644
index 000000000000..4190bb759e68
--- /dev/null
+++ b/Zend/tests/partial_application/variation_variadics_008.phpt
@@ -0,0 +1,16 @@
+--TEST--
+PFA variation: variadics wrong signature checked
+--FILE--
+getMessage() . PHP_EOL;
+}
+?>
+--EXPECTF--
+Partial application of {closure:%s:%d}() expects at most 1 arguments, 2 given
diff --git a/Zend/tests/pipe_operator/gh22490.phpt b/Zend/tests/pipe_operator/gh22490.phpt
new file mode 100644
index 000000000000..c801d938c275
--- /dev/null
+++ b/Zend/tests/pipe_operator/gh22490.phpt
@@ -0,0 +1,60 @@
+--TEST--
+GH-22490: Null pointer dereference compiling a pipe on the lhs of ??=
+--FILE--
+p = 7;
+ return $o;
+}
+
+echo "property unset, assigns and returns default: ";
+var_dump((1 |> unset_prop(...))->p ??= 99);
+echo "pipe evaluated once: ";
+var_dump($calls);
+
+$calls = 0;
+echo "property set, returns existing value: ";
+var_dump((1 |> set_prop(...))->p ??= 99);
+echo "pipe evaluated once: ";
+var_dump($calls);
+
+function new_array($x) {
+ global $calls;
+ $calls++;
+ return [];
+}
+
+$calls = 0;
+echo "dim target on a pipe result: ";
+var_dump((1 |> new_array(...))[0] ??= 42);
+echo "pipe evaluated once: ";
+var_dump($calls);
+
+function identity($x) {
+ return $x;
+}
+
+$calls = 0;
+echo "nested pipe on the lhs: ";
+var_dump((1 |> identity(...) |> unset_prop(...))->p ??= 5);
+echo "pipe evaluated once: ";
+var_dump($calls);
+?>
+--EXPECT--
+property unset, assigns and returns default: int(99)
+pipe evaluated once: int(1)
+property set, returns existing value: int(7)
+pipe evaluated once: int(1)
+dim target on a pipe result: int(42)
+pipe evaluated once: int(1)
+nested pipe on the lhs: int(5)
+pipe evaluated once: int(1)
diff --git a/Zend/tests/prop_const_expr/non_enums_catchable.phpt b/Zend/tests/prop_const_expr/non_enums_catchable.phpt
index 6f410ac7acbe..702bc024122d 100644
--- a/Zend/tests/prop_const_expr/non_enums_catchable.phpt
+++ b/Zend/tests/prop_const_expr/non_enums_catchable.phpt
@@ -18,6 +18,7 @@ const A_prop = (new A)->{new Printer ? 'printer' : null};
?>
--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
Printer
Fatal error: Uncaught Error: Fetching properties on non-enums in constant expressions is not allowed in %s:%d
diff --git a/Zend/tests/require_once_warning_to_exception.phpt b/Zend/tests/require_once_warning_to_exception.phpt
index d115ac68ccde..bc706622438f 100644
--- a/Zend/tests/require_once_warning_to_exception.phpt
+++ b/Zend/tests/require_once_warning_to_exception.phpt
@@ -16,4 +16,4 @@ try {
?>
--EXPECT--
-require_once(does-not-exist.php): Failed to open stream: No such file or directory
+require_once(): Failed to open stream: No such file or directory
diff --git a/Zend/tests/return_types/042.phpt b/Zend/tests/return_types/042.phpt
index 069e0228ff99..21483047d4f2 100644
--- a/Zend/tests/return_types/042.phpt
+++ b/Zend/tests/return_types/042.phpt
@@ -20,5 +20,10 @@ class JustAnArray {
echo 'No problems!';
?>
---EXPECT--
+--EXPECTF--
+Deprecated: Returning null from UnionType::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead in %s on line %d
+
+Deprecated: Returning null from UnionType2::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead in %s on line %d
+
+Deprecated: Returning null from UnionTypeOldStyle::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead in %s on line %d
No problems!
diff --git a/Zend/tests/traits/bug60536_001.phpt b/Zend/tests/traits/bug60536_001.phpt
index a58098ca3112..712600a4b1d1 100644
--- a/Zend/tests/traits/bug60536_001.phpt
+++ b/Zend/tests/traits/bug60536_001.phpt
@@ -23,5 +23,9 @@ $a->__construct();
echo "DONE";
?>
--EXPECTF--
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
+Deprecated: Returning a value from a constructor is deprecated in %s on line %d
+
Warning: Undefined property: Z::$x in %s on line %d
DONE
diff --git a/Zend/tests/try/gh22280.phpt b/Zend/tests/try/gh22280.phpt
new file mode 100644
index 000000000000..30944aff59a6
--- /dev/null
+++ b/Zend/tests/try/gh22280.phpt
@@ -0,0 +1,19 @@
+--TEST--
+GH-22280: goto to label before try/finally after try/catch
+--FILE--
+
+--EXPECT--
+try
+finally
+done
diff --git a/Zend/tests/type_coercion/gh22112.phpt b/Zend/tests/type_coercion/gh22112.phpt
new file mode 100644
index 000000000000..84fdc393a828
--- /dev/null
+++ b/Zend/tests/type_coercion/gh22112.phpt
@@ -0,0 +1,35 @@
+--TEST--
+GH-22112 (Assertion failure when error handler throws during NaN to bool/string coercion at function entry)
+--FILE--
+getMessage(), "\n";
+}
+
+try {
+ take_string($nan);
+} catch (Exception $e) {
+ echo "string: ", $e->getMessage(), "\n";
+}
+
+?>
+--EXPECT--
+bool: unexpected NAN value was coerced to bool
+string: unexpected NAN value was coerced to string
diff --git a/Zend/tests/unserialize_typed_prop_reset_on_failure.phpt b/Zend/tests/unserialize_typed_prop_reset_on_failure.phpt
new file mode 100644
index 000000000000..2380c8e5550e
--- /dev/null
+++ b/Zend/tests/unserialize_typed_prop_reset_on_failure.phpt
@@ -0,0 +1,37 @@
+--TEST--
+unserialize() resets a typed property to its default on every failure path
+--FILE--
+getPrevious()) {
+ printf("%s: %s\n", $e::class, $e->getMessage());
+ }
+}
+
+/* By-ref type violation: the slot is reset to its default. */
+class C { public array $a; }
+try {
+ var_dump(unserialize('O:1:"C":1:{s:1:"a";R:1;}'));
+} catch (\Throwable $e) {
+ printf("%s: %s\n", $e::class, $e->getMessage());
+}
+echo "OK\n";
+?>
+--EXPECTF--
+Warning: unserialize(): Error at offset %d of %d bytes in %s on line %d
+TypeError: %s
+OK
diff --git a/Zend/tests/vm_kind_tailcall_clang_windows.phpt b/Zend/tests/vm_kind_tailcall_clang_windows.phpt
new file mode 100644
index 000000000000..f66a17ae0daf
--- /dev/null
+++ b/Zend/tests/vm_kind_tailcall_clang_windows.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Tailcall VM is selected when compiled with Clang >= 19 on Windows x64
+--SKIPIF--
+ clang version (\d+)/', $info, $m)) {
+ die('skip not compiled with clang');
+}
+
+if ((int)$m[1] < 19) {
+ die('skip requires clang >= 19');
+}
+?>
+--FILE--
+
+--EXPECT--
+string(21) "ZEND_VM_KIND_TAILCALL"
diff --git a/Zend/tests/weakrefs/gh17442_1.phpt b/Zend/tests/weakrefs/gh17442_1.phpt
index fc7f60174ed9..d535c307e3be 100644
--- a/Zend/tests/weakrefs/gh17442_1.phpt
+++ b/Zend/tests/weakrefs/gh17442_1.phpt
@@ -17,6 +17,6 @@ headers_sent($obj,$generator);
Fatal error: Uncaught Exception: Test in %s:%d
Stack trace:
#0 [internal function]: class@anonymous->__destruct()
-#1 %s(%d): headers_sent(NULL, 0)
+#1 %s(%d): headers_sent(NULL, NULL)
#2 {main}
thrown in %s on line %d
diff --git a/Zend/zend.c b/Zend/zend.c
index bce1faede8fb..07692db85196 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -55,6 +54,9 @@ ZEND_API int compiler_globals_id;
ZEND_API int executor_globals_id;
ZEND_API size_t compiler_globals_offset;
ZEND_API size_t executor_globals_offset;
+/* ts_allocate_tls_id takes a callback so each thread resolves its own block.
+ * A plain &language_scanner_globals would capture only the registering thread's address. */
+static void *language_scanner_globals_tls_addr(void) { return &language_scanner_globals; }
static HashTable *global_function_table = NULL;
static HashTable *global_class_table = NULL;
static HashTable *global_constants_table = NULL;
@@ -78,7 +80,7 @@ ZEND_API bool zend_dtrace_enabled;
/* version information */
static char *zend_version_info;
static uint32_t zend_version_info_length;
-#define ZEND_CORE_VERSION_INFO "Zend Engine v" ZEND_VERSION ", Copyright (c) Zend Technologies\n"
+#define ZEND_CORE_VERSION_INFO "Zend Engine v" ZEND_VERSION ", Copyright © Zend by Perforce\n"
#define PRINT_ZVAL_INDENT 4
/* true multithread-shared globals */
@@ -565,6 +567,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
zend_object *zobj = Z_OBJ_P(expr);
uint32_t *guard = zend_get_recursion_guard(zobj);
zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(zobj);
+ /* cut off on NULL byte ... class@anonymous */
smart_str_appends(buf, ZSTR_VAL(class_name));
zend_string_release_ex(class_name, 0);
@@ -811,6 +814,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
executor_globals->user_error_handler_error_reporting = 0;
ZVAL_UNDEF(&executor_globals->user_error_handler);
ZVAL_UNDEF(&executor_globals->user_exception_handler);
+ ZVAL_UNDEF(&executor_globals->last_fatal_error_backtrace);
executor_globals->current_execute_data = NULL;
executor_globals->current_module = NULL;
executor_globals->exit_status = 0;
@@ -832,8 +836,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{
#endif
executor_globals->flags = EG_FLAGS_INITIAL;
executor_globals->record_errors = false;
- executor_globals->num_errors = 0;
- executor_globals->errors = NULL;
+ memset(&executor_globals->errors, 0, sizeof(executor_globals->errors));
executor_globals->filename_override = NULL;
executor_globals->lineno_override = -1;
#ifdef ZEND_CHECK_STACK_LIMIT
@@ -1019,9 +1022,11 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */
zend_init_rsrc_list_dtors();
#ifdef ZTS
- ts_allocate_fast_id(&compiler_globals_id, &compiler_globals_offset, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor);
- ts_allocate_fast_id(&executor_globals_id, &executor_globals_offset, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
- ts_allocate_fast_id(&language_scanner_globals_id, &language_scanner_globals_offset, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL);
+ ts_allocate_fast_id_at(&compiler_globals_id, &compiler_globals_offset, ZEND_CG_OFFSET, sizeof(zend_compiler_globals), (ts_allocate_ctor) compiler_globals_ctor, (ts_allocate_dtor) compiler_globals_dtor);
+ ts_allocate_fast_id_at(&executor_globals_id, &executor_globals_offset, ZEND_EG_OFFSET, sizeof(zend_executor_globals), (ts_allocate_ctor) executor_globals_ctor, (ts_allocate_dtor) executor_globals_dtor);
+ ts_allocate_tls_id(&language_scanner_globals_id, language_scanner_globals_tls_addr, sizeof(zend_php_scanner_globals), (ts_allocate_ctor) php_scanner_globals_ctor, NULL);
+ ZEND_ASSERT(compiler_globals_offset == ZEND_CG_OFFSET);
+ ZEND_ASSERT(executor_globals_offset == ZEND_EG_OFFSET);
ts_allocate_fast_id(&ini_scanner_globals_id, &ini_scanner_globals_offset, sizeof(zend_ini_scanner_globals), (ts_allocate_ctor) ini_scanner_globals_ctor, NULL);
compiler_globals = ts_resource(compiler_globals_id);
executor_globals = ts_resource(executor_globals_id);
@@ -1304,7 +1309,7 @@ ZEND_API void zend_append_version_info(const zend_extension *extension) /* {{{ *
snprintf(new_info, new_info_length, " with %s v%s, %s, by %s\n", extension->name, extension->version, extension->copyright, extension->author);
- zend_version_info = (char *) realloc(zend_version_info, zend_version_info_length+new_info_length + 1);
+ zend_version_info = (char *) perealloc(zend_version_info, zend_version_info_length+new_info_length + 1, true);
strncat(zend_version_info, new_info, new_info_length);
zend_version_info_length += new_info_length;
free(new_info);
@@ -1445,8 +1450,7 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
zend_stack delayed_oplines_stack;
int type = orig_type & E_ALL;
bool orig_record_errors;
- uint32_t orig_num_errors;
- zend_error_info **orig_errors;
+ zend_err_buf orig_errors_buf;
zend_result res;
/* If we're executing a function during SCCP, count any warnings that may be emitted,
@@ -1458,11 +1462,9 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
}
/* Emit any delayed error before handling fatal error */
- if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(num_errors)) {
- uint32_t num_errors = EG(num_errors);
- zend_error_info **errors = EG(errors);
- EG(num_errors) = 0;
- EG(errors) = NULL;
+ if ((type & E_FATAL_ERRORS) && !(type & E_DONT_BAIL) && EG(errors).size) {
+ zend_err_buf errors_buf = EG(errors);
+ EG(errors).size = 0;
bool orig_record_errors = EG(record_errors);
EG(record_errors) = false;
@@ -1472,12 +1474,11 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
int orig_user_error_handler_error_reporting = EG(user_error_handler_error_reporting);
EG(user_error_handler_error_reporting) = 0;
- zend_emit_recorded_errors_ex(num_errors, errors);
+ zend_emit_recorded_errors_ex(errors_buf.size, errors_buf.errors);
EG(user_error_handler_error_reporting) = orig_user_error_handler_error_reporting;
EG(record_errors) = orig_record_errors;
- EG(num_errors) = num_errors;
- EG(errors) = errors;
+ EG(errors) = errors_buf;
}
if (EG(record_errors)) {
@@ -1486,12 +1487,13 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
info->lineno = error_lineno;
info->filename = zend_string_copy(error_filename);
info->message = zend_string_copy(message);
-
- /* This is very inefficient for a large number of errors.
- * Use pow2 realloc if it becomes a problem. */
- EG(num_errors)++;
- EG(errors) = erealloc(EG(errors), sizeof(zend_error_info*) * EG(num_errors));
- EG(errors)[EG(num_errors)-1] = info;
+ EG(errors).size++;
+ if (EG(errors).size > EG(errors).capacity) {
+ uint32_t capacity = EG(errors).capacity ? EG(errors).capacity + (EG(errors).capacity >> 1) : 2;
+ EG(errors).errors = erealloc(EG(errors).errors, sizeof(zend_error_info *) * capacity);
+ EG(errors).capacity = capacity;
+ }
+ EG(errors).errors[EG(errors).size - 1] = info;
/* Do not process non-fatal recorded error */
if (!(type & E_FATAL_ERRORS) || (type & E_DONT_BAIL)) {
@@ -1574,17 +1576,15 @@ ZEND_API ZEND_COLD void zend_error_zstr_at(
}
orig_record_errors = EG(record_errors);
- orig_num_errors = EG(num_errors);
- orig_errors = EG(errors);
EG(record_errors) = false;
- EG(num_errors) = 0;
- EG(errors) = NULL;
+
+ orig_errors_buf = EG(errors);
+ memset(&EG(errors), 0, sizeof(EG(errors)));
res = call_user_function(CG(function_table), NULL, &orig_user_error_handler, &retval, 4, params);
EG(record_errors) = orig_record_errors;
- EG(num_errors) = orig_num_errors;
- EG(errors) = orig_errors;
+ EG(errors) = orig_errors_buf;
if (res == SUCCESS) {
if (Z_TYPE(retval) != IS_UNDEF) {
@@ -1779,8 +1779,7 @@ ZEND_API void zend_begin_record_errors(void)
{
ZEND_ASSERT(!EG(record_errors) && "Error recording already enabled");
EG(record_errors) = true;
- EG(num_errors) = 0;
- EG(errors) = NULL;
+ EG(errors).size = 0;
}
ZEND_API void zend_emit_recorded_errors_ex(uint32_t num_errors, zend_error_info **errors)
@@ -1794,24 +1793,23 @@ ZEND_API void zend_emit_recorded_errors_ex(uint32_t num_errors, zend_error_info
ZEND_API void zend_emit_recorded_errors(void)
{
EG(record_errors) = false;
- zend_emit_recorded_errors_ex(EG(num_errors), EG(errors));
+ zend_emit_recorded_errors_ex(EG(errors).size, EG(errors).errors);
}
ZEND_API void zend_free_recorded_errors(void)
{
- if (!EG(num_errors)) {
+ if (!EG(errors).size) {
return;
}
- for (uint32_t i = 0; i < EG(num_errors); i++) {
- zend_error_info *info = EG(errors)[i];
+ for (uint32_t i = 0; i < EG(errors).size; i++) {
+ zend_error_info *info = EG(errors).errors[i];
zend_string_release(info->filename);
zend_string_release(info->message);
- efree(info);
+ efree_size(info, sizeof(zend_error_info));
}
- efree(EG(errors));
- EG(errors) = NULL;
- EG(num_errors) = 0;
+ efree(EG(errors).errors);
+ memset(&EG(errors), 0, sizeof(EG(errors)));
}
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */
diff --git a/Zend/zend.h b/Zend/zend.h
index ebb7d2391955..0d5303192b57 100644
--- a/Zend/zend.h
+++ b/Zend/zend.h
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -281,7 +280,9 @@ typedef size_t (*zend_write_func_t)(const char *str, size_t str_length);
EG(bailout) = &__bailout; \
if (SETJMP(__bailout)==0) {
#define zend_catch \
+ ZEND_ASSERT(EG(bailout) == &__bailout); \
} else { \
+ ZEND_ASSERT(EG(bailout) == &__bailout); \
EG(bailout) = __orig_bailout;
#define zend_end_try() \
} \
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 211a5d14e6c3..72f4727eeddf 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -126,7 +125,7 @@ ZEND_API const char *zend_get_type_by_const(int type) /* {{{ */
return "mixed";
case _IS_NUMBER:
return "int|float";
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
}
/* }}} */
@@ -262,7 +261,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(int error_code,
case ZPP_ERROR_FAILURE:
ZEND_ASSERT(EG(exception) && "Should have produced an error already");
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
}
/* }}} */
@@ -375,7 +374,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_unexpected_extra_named_error(void)
);
}
-ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, uint32_t arg_num, const char *format, va_list va) /* {{{ */
+ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(
+ zend_class_entry *error_ce, const zend_function *function, uint32_t arg_num, const char *format, va_list va)
{
zend_string *func_name;
const char *arg_name;
@@ -384,8 +384,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_en
return;
}
- func_name = get_active_function_or_method_name();
- arg_name = get_active_function_arg_name(arg_num);
+ func_name = get_function_or_method_name(function);
+ arg_name = get_function_arg_name(function, arg_num);
zend_vspprintf(&message, 0, format, va);
zend_throw_error(error_ce, "%s(): Argument #%d%s%s%s %s",
@@ -395,34 +395,65 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_en
efree(message);
zend_string_release(func_name);
}
-/* }}} */
+
+ZEND_API ZEND_COLD void zend_argument_error_ex(zend_class_entry *error_ce, const zend_function *function, uint32_t arg_num, const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ zend_argument_error_variadic(error_ce, function, arg_num, format, va);
+ va_end(va);
+}
ZEND_API ZEND_COLD void zend_argument_error(zend_class_entry *error_ce, uint32_t arg_num, const char *format, ...) /* {{{ */
{
va_list va;
+ const zend_function *function = zend_active_function();
va_start(va, format);
- zend_argument_error_variadic(error_ce, arg_num, format, va);
+ zend_argument_error_variadic(error_ce, function, arg_num, format, va);
va_end(va);
}
/* }}} */
+ZEND_API ZEND_COLD void zend_argument_type_error_ex(
+ const zend_function *function, uint32_t arg_num, const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ zend_argument_error_variadic(zend_ce_type_error, function, arg_num, format, va);
+ va_end(va);
+}
+
ZEND_API ZEND_COLD void zend_argument_type_error(uint32_t arg_num, const char *format, ...) /* {{{ */
{
va_list va;
+ const zend_function *function = zend_active_function();
va_start(va, format);
- zend_argument_error_variadic(zend_ce_type_error, arg_num, format, va);
+ zend_argument_error_variadic(zend_ce_type_error, function, arg_num, format, va);
va_end(va);
}
/* }}} */
+ZEND_API ZEND_COLD void zend_argument_value_error_ex(
+ const zend_function *function, uint32_t arg_num, const char *format, ...)
+{
+ va_list va;
+
+ va_start(va, format);
+ zend_argument_error_variadic(zend_ce_value_error, function, arg_num, format, va);
+ va_end(va);
+}
+
ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format, ...) /* {{{ */
{
va_list va;
+ const zend_function *function = zend_active_function();
va_start(va, format);
- zend_argument_error_variadic(zend_ce_value_error, arg_num, format, va);
+ zend_argument_error_variadic(zend_ce_value_error, function, arg_num, format, va);
va_end(va);
}
/* }}} */
@@ -510,35 +541,37 @@ static ZEND_COLD bool zend_null_arg_deprecated(const char *fallback_type, uint32
return !EG(exception);
}
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, bool *dest, uint32_t arg_num) /* {{{ */
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) <= IS_STRING)) {
if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) && !zend_null_arg_deprecated("bool", arg_num)) {
- return 0;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
- *dest = zend_is_true(arg);
- } else {
- return 0;
+ bool result = zend_is_true(arg);
+ if (UNEXPECTED(EG(exception))) {
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
+ }
+ return result;
}
- return 1;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num) /* {{{ */
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
- return zend_parse_arg_bool_weak(arg, dest, arg_num);
+ return zend_parse_arg_bool_weak(arg, arg_num);
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num)
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, uint32_t arg_num)
{
if (UNEXPECTED(ZEND_FLF_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return ZPP_PARSE_BOOL_STATUS_ERROR;
}
- return zend_parse_arg_bool_weak(arg, dest, arg_num);
+ return zend_parse_arg_bool_weak(arg, arg_num);
}
ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_weak(const zval *arg, zend_long *dest, uint32_t arg_num) /* {{{ */
@@ -625,44 +658,46 @@ ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_long_slow(const zval *arg, zend_l
return zend_parse_arg_long_weak(arg, dest, arg_num);
}
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, double *dest, uint32_t arg_num) /* {{{ */
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) {
- *dest = (double)Z_LVAL_P(arg);
+ return (double)Z_LVAL_P(arg);
} else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) {
zend_long l;
+ double dval;
uint8_t type;
- if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), &l, dest)) != IS_DOUBLE)) {
+ if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), &l, &dval)) != IS_DOUBLE)) {
if (EXPECTED(type != 0)) {
- *dest = (double)(l);
+ return (double)(l);
} else {
- return 0;
+ return NAN;
}
+ } else {
+ return dval;
}
} else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) {
if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) && !zend_null_arg_deprecated("float", arg_num)) {
- return 0;
+ return NAN;
}
- *dest = 0.0;
+ return 0.0;
} else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) {
- *dest = 1.0;
+ return 1.0;
} else {
- return 0;
+ return NAN;
}
- return 1;
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, double *dest, uint32_t arg_num) /* {{{ */
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) {
/* SSTH Exception: IS_LONG may be accepted instead as IS_DOUBLE */
- *dest = (double)Z_LVAL_P(arg);
+ return (double)Z_LVAL_P(arg);
} else if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return NAN;
}
- return zend_parse_arg_double_weak(arg, dest, arg_num);
+ return zend_parse_arg_double_weak(arg, arg_num);
}
/* }}} */
@@ -729,58 +764,61 @@ ZEND_API bool ZEND_FASTCALL zend_parse_arg_number_or_str_slow(zval *arg, zval **
return true;
}
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **dest, uint32_t arg_num) /* {{{ */
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, uint32_t arg_num) /* {{{ */
{
if (EXPECTED(Z_TYPE_P(arg) < IS_STRING)) {
if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL) && !zend_null_arg_deprecated("string", arg_num)) {
- return 0;
+ return NULL;
}
convert_to_string(arg);
- *dest = Z_STR_P(arg);
+ if (UNEXPECTED(EG(exception))) {
+ return NULL;
+ }
+ return Z_STR_P(arg);
} else if (UNEXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
zend_object *zobj = Z_OBJ_P(arg);
zval obj;
if (zobj->handlers->cast_object(zobj, &obj, IS_STRING) == SUCCESS) {
OBJ_RELEASE(zobj);
ZVAL_COPY_VALUE(arg, &obj);
- *dest = Z_STR_P(arg);
- return 1;
+ return Z_STR_P(arg);
}
- return 0;
+ return NULL;
} else {
- return 0;
+ return NULL;
}
- return 1;
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num) /* {{{ */
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, uint32_t arg_num) /* {{{ */
{
if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return NULL;
}
- return zend_parse_arg_str_weak(arg, dest, arg_num);
+ return zend_parse_arg_str_weak(arg, arg_num);
}
/* }}} */
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num)
+ZEND_API zend_string* ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, uint32_t arg_num)
{
if (UNEXPECTED(ZEND_FLF_ARG_USES_STRICT_TYPES())) {
- return 0;
+ return NULL;
}
- return zend_parse_arg_str_weak(arg, dest, arg_num);
+ return zend_parse_arg_str_weak(arg, arg_num);
}
ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_or_long_slow(zval *arg, zend_string **dest_str, zend_long *dest_long, uint32_t arg_num) /* {{{ */
{
+ zend_string *str;
if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) {
return 0;
}
if (zend_parse_arg_long_weak(arg, dest_long, arg_num)) {
*dest_str = NULL;
return 1;
- } else if (zend_parse_arg_str_weak(arg, dest_str, arg_num)) {
+ } else if ((str = zend_parse_arg_str_weak(arg, arg_num)) != NULL) {
*dest_long = 0;
+ *dest_str = str;
return 1;
} else {
return 0;
@@ -2505,19 +2543,19 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */
dl_loaded_count++;
}
} ZEND_HASH_FOREACH_END();
- module_request_startup_handlers = (zend_module_entry**)realloc(
+ module_request_startup_handlers = (zend_module_entry**)perealloc(
module_request_startup_handlers,
sizeof(zend_module_entry*) *
(startup_count + 1 +
shutdown_count + 1 +
- post_deactivate_count + 1));
+ post_deactivate_count + 1), true);
module_request_startup_handlers[startup_count] = NULL;
module_request_shutdown_handlers = module_request_startup_handlers + startup_count + 1;
module_request_shutdown_handlers[shutdown_count] = NULL;
module_post_deactivate_handlers = module_request_shutdown_handlers + shutdown_count + 1;
module_post_deactivate_handlers[post_deactivate_count] = NULL;
/* Cannot reuse module_request_startup_handlers because it is freed in zend_destroy_modules, which happens before zend_unload_modules. */
- modules_dl_loaded = realloc(modules_dl_loaded, sizeof(zend_module_entry*) * (dl_loaded_count + 1));
+ modules_dl_loaded = perealloc(modules_dl_loaded, sizeof(zend_module_entry*) * (dl_loaded_count + 1), true);
modules_dl_loaded[dl_loaded_count] = NULL;
startup_count = 0;
@@ -2544,10 +2582,10 @@ ZEND_API void zend_collect_module_handlers(void) /* {{{ */
}
} ZEND_HASH_FOREACH_END();
- class_cleanup_handlers = (zend_class_entry**)realloc(
+ class_cleanup_handlers = (zend_class_entry**)perealloc(
class_cleanup_handlers,
sizeof(zend_class_entry*) *
- (class_count + 1));
+ (class_count + 1), true);
class_cleanup_handlers[class_count] = NULL;
if (class_count) {
@@ -2810,34 +2848,38 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce,
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING);
zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY);
- } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
+ } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) {
zend_check_magic_method_args(2, ce, fptr, error_type);
zend_check_magic_method_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING);
zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY);
- } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) {
+ } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) {
zend_check_magic_method_args(0, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_STRING);
- } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
+ } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) {
zend_check_magic_method_args(0, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_return_type(ce, fptr, error_type, (MAY_BE_ARRAY | MAY_BE_NULL));
- } else if (zend_string_equals_literal(lcname, "__serialize")) {
+ if ((fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) && ZEND_TYPE_PURE_MASK(fptr->common.arg_info[-1].type) & MAY_BE_NULL) {
+ zend_error(E_DEPRECATED, "Returning null from %s::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead",
+ ZSTR_VAL(ce->name));
+ }
+ } else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) {
zend_check_magic_method_args(0, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_ARRAY);
- } else if (zend_string_equals_literal(lcname, "__unserialize")) {
+ } else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) {
zend_check_magic_method_args(1, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_ARRAY);
zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_VOID);
- } else if (zend_string_equals_literal(lcname, "__set_state")) {
+ } else if (zend_string_equals_literal(lcname, ZEND_SET_STATE_FUNC_NAME)) {
zend_check_magic_method_args(1, ce, fptr, error_type);
zend_check_magic_method_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
@@ -2885,16 +2927,16 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, c
} else if (zend_string_equals_literal(lcname, ZEND_ISSET_FUNC_NAME)) {
ce->__isset = fptr;
ce->ce_flags |= ZEND_ACC_USE_GUARDS;
- } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
+ } else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) {
ce->__callstatic = fptr;
- } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) {
+ } else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) {
ce->__tostring = fptr;
- } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
+ } else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) {
ce->__debugInfo = fptr;
ce->ce_flags |= ZEND_ACC_USE_GUARDS;
- } else if (zend_string_equals_literal(lcname, "__serialize")) {
+ } else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) {
ce->__serialize = fptr;
- } else if (zend_string_equals_literal(lcname, "__unserialize")) {
+ } else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) {
ce->__unserialize = fptr;
}
}
@@ -2997,6 +3039,7 @@ ZEND_API void zend_convert_internal_arg_info(zend_arg_info *new_arg_info, const
new_arg_info->name = NULL;
new_arg_info->default_value = NULL;
}
+ new_arg_info->doc_comment = NULL;
new_arg_info->type = arg_info->type;
zend_convert_internal_arg_info_type(&new_arg_info->type, persistent);
}
@@ -3045,7 +3088,6 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
internal_function->prop_info = NULL;
internal_function->attributes = NULL;
internal_function->frameless_function_infos = ptr->frameless_function_infos;
- internal_function->fn_flags2 = 0;
if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size()));
} else {
@@ -3055,7 +3097,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, NULL);
#endif
}
- if (ptr->flags) {
+ if (ptr->flags & UINT32_MAX) {
if (!(ptr->flags & ZEND_ACC_PPP_MASK)) {
if (ptr->flags != ZEND_ACC_DEPRECATED && scope) {
zend_error(error_type, "Invalid access level for %s::%s() - access must be exactly one of public, protected or private", ZSTR_VAL(scope->name), ptr->fname);
@@ -3067,6 +3109,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
} else {
internal_function->fn_flags = ZEND_ACC_PUBLIC;
}
+ internal_function->fn_flags2 = ptr->flags >> 32;
if (ptr->arg_info) {
zend_internal_function_info *info = (zend_internal_function_info*)ptr->arg_info;
@@ -3107,7 +3150,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
/* If not specified, add __toString() return type for compatibility with Stringable
* interface. */
- if (scope && zend_string_equals_literal_ci(internal_function->function_name, "__tostring") &&
+ if (scope && zend_string_equals_literal_ci(internal_function->function_name, ZEND_TOSTRING_FUNC_LCNAME) &&
!(internal_function->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
zend_error(E_CORE_WARNING, "%s::__toString() implemented without string return type",
ZSTR_VAL(scope->name));
@@ -3143,7 +3186,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
}
lowercase_name = zend_string_tolower_ex(internal_function->function_name, type == MODULE_PERSISTENT);
lowercase_name = zend_new_interned_string(lowercase_name);
- reg_function = malloc(sizeof(zend_internal_function));
+ reg_function = pemalloc(sizeof(zend_internal_function), true);
memcpy(reg_function, &function, sizeof(zend_internal_function));
if (zend_hash_add_ptr(target_function_table, lowercase_name, reg_function) == NULL) {
unload=1;
@@ -3161,8 +3204,8 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
zend_flf_capacity *= 2;
}
/* +1 for NULL terminator */
- zend_flf_handlers = realloc(zend_flf_handlers, (zend_flf_capacity + 1) * sizeof(void *));
- zend_flf_functions = realloc(zend_flf_functions, (zend_flf_capacity + 1) * sizeof(zend_function *));
+ zend_flf_handlers = perealloc(zend_flf_handlers, (zend_flf_capacity + 1) * sizeof(void *), true);
+ zend_flf_functions = perealloc(zend_flf_functions, (zend_flf_capacity + 1) * sizeof(zend_function *), true);
}
zend_flf_handlers[zend_flf_count] = flf_info->handler;
zend_flf_functions[zend_flf_count] = (zend_function *)reg_function;
@@ -3208,7 +3251,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
/* Treat return type as an extra argument */
num_args++;
- new_arg_info = malloc(sizeof(zend_arg_info) * num_args);
+ new_arg_info = pemalloc(sizeof(zend_arg_info) * num_args, true);
reg_function->arg_info = new_arg_info + 1;
for (i = 0; i < num_args; i++) {
zend_convert_internal_arg_info(&new_arg_info[i], &arg_info[i],
@@ -3493,7 +3536,7 @@ ZEND_API int zend_next_free_module(void) /* {{{ */
static zend_class_entry *do_register_internal_class(const zend_class_entry *orig_class_entry, uint32_t ce_flags) /* {{{ */
{
- zend_class_entry *class_entry = malloc(sizeof(zend_class_entry));
+ zend_class_entry *class_entry = pemalloc(sizeof(zend_class_entry), true);
zend_string *lowercase_name;
*class_entry = *orig_class_entry;
@@ -3769,6 +3812,10 @@ static bool zend_is_callable_check_class(zend_string *name, zend_class_entry *sc
if (error) zend_spprintf(error, 0, "class \"%.*s\" not found", (int)name_len, ZSTR_VAL(name));
}
ZSTR_ALLOCA_FREE(lcname, use_heap);
+ /* User error handlers may throw from deprecations above; do not report callable as valid. */
+ if (UNEXPECTED(EG(exception))) {
+ return false;
+ }
return ret;
}
/* }}} */
@@ -4030,7 +4077,7 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable,
}
/* }}} */
-ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, const zend_object *object) /* {{{ */
+ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend_object *object) /* {{{ */
{
try_again:
switch (Z_TYPE_P(callable)) {
@@ -4088,7 +4135,53 @@ ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, const zend_objec
}
/* }}} */
-ZEND_API zend_string *zend_get_callable_name(zval *callable) /* {{{ */
+static bool zend_fcc_function_handler_equals(const zend_function *func1, const zend_function *func2) /* {{{ */
+{
+ if (func1 == func2) {
+ return true;
+ }
+
+ const bool fake_closure1 = (func1->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;
+ const bool fake_closure2 = (func2->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;
+
+ if (!fake_closure1 && !fake_closure2) {
+ return false;
+ }
+ if (((func1->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure1) ||
+ ((func2->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure2)) {
+ return false;
+ }
+ if (func1->type != func2->type ||
+ func1->common.scope != func2->common.scope ||
+ !zend_string_equals(func1->common.function_name, func2->common.function_name)) {
+ return false;
+ }
+
+ if (func1->type == ZEND_USER_FUNCTION) {
+ return func1->op_array.opcodes == func2->op_array.opcodes;
+ }
+
+ return func1->internal_function.handler == func2->internal_function.handler;
+}
+/* }}} */
+
+ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b) /* {{{ */
+{
+ const zend_function *func1 = a->function_handler;
+ const zend_function *func2 = b->function_handler;
+
+ if (a->closure && a->closure->ce == zend_ce_closure) {
+ func1 = zend_get_closure_method_def(a->closure);
+ }
+ if (b->closure && b->closure->ce == zend_ce_closure) {
+ func2 = zend_get_closure_method_def(b->closure);
+ }
+
+ return zend_fcc_function_handler_equals(func1, func2);
+}
+/* }}} */
+
+ZEND_API zend_string *zend_get_callable_name(const zval *callable) /* {{{ */
{
return zend_get_callable_name_ex(callable, NULL);
}
@@ -4205,7 +4298,7 @@ ZEND_API bool zend_is_callable_at_frame(
}
/* }}} */
-ZEND_API bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error) /* {{{ */
+ZEND_API bool zend_is_callable_ex(const zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error) /* {{{ */
{
/* Determine callability at the first parent user frame. */
const zend_execute_data *frame = EG(current_execute_data);
@@ -4220,13 +4313,13 @@ ZEND_API bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t
return ret;
}
-ZEND_API bool zend_is_callable(zval *callable, uint32_t check_flags, zend_string **callable_name) /* {{{ */
+ZEND_API bool zend_is_callable(const zval *callable, uint32_t check_flags, zend_string **callable_name) /* {{{ */
{
return zend_is_callable_ex(callable, NULL, check_flags, callable_name, NULL, NULL);
}
/* }}} */
-ZEND_API zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error) /* {{{ */
+ZEND_API zend_result zend_fcall_info_init(const zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error) /* {{{ */
{
if (!zend_is_callable_ex(callable, NULL, check_flags, callable_name, fcc, error)) {
return FAILURE;
@@ -4239,6 +4332,7 @@ ZEND_API zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags,
fci->param_count = 0;
fci->params = NULL;
fci->named_params = NULL;
+ fci->consumed_args = 0;
return SUCCESS;
}
diff --git a/Zend/zend_API.h b/Zend/zend_API.h
index d78ee6604e34..2c0b892d1941 100644
--- a/Zend/zend_API.h
+++ b/Zend/zend_API.h
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -37,7 +36,7 @@ typedef struct _zend_function_entry {
zif_handler handler;
const struct _zend_internal_arg_info *arg_info;
uint32_t num_args;
- uint32_t flags;
+ uint64_t flags;
const zend_frameless_function_info *frameless_function_infos;
const char *doc_comment;
} zend_function_entry;
@@ -49,6 +48,7 @@ typedef struct _zend_fcall_info {
zval *params;
zend_object *object;
uint32_t param_count;
+ uint32_t consumed_args;
/* This hashtable can also contain positional arguments (with integer keys),
* which will be appended to the normal params[]. This makes it easier to
* integrate APIs like call_user_func_array(). The usual restriction that
@@ -75,6 +75,8 @@ typedef struct _zend_fcall_info_cache {
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(zif_##name)
#define ZEND_METHOD(classname, name) ZEND_NAMED_FUNCTION(zim_##classname##_##name)
+#define ZEND_FENTRY_FLAGS(flags, flags2) (((uint64_t)flags) | ((uint64_t)flags2 << 32))
+
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags, NULL, NULL },
#define ZEND_RAW_FENTRY(zend_name, name, arg_info, flags, frameless_function_infos, doc_comment) { zend_name, name, arg_info, (uint32_t) (sizeof(arg_info)/sizeof(struct _zend_internal_arg_info)-1), flags, frameless_function_infos, doc_comment },
@@ -340,6 +342,13 @@ typedef struct _zend_fcall_info_cache {
#define ZEND_FCI_INITIALIZED(fci) ((fci).size != 0)
#define ZEND_FCC_INITIALIZED(fcc) ((fcc).function_handler != NULL)
+static zend_always_inline uint32_t zend_fci_consumed_arg(uint32_t arg_index) {
+ return arg_index < 32 ? (UINT32_C(1) << arg_index) : UINT32_C(0);
+}
+static zend_always_inline bool zend_fci_is_consumed_arg(uint32_t consumed_args, uint32_t arg_index) {
+ return arg_index < 32 && (consumed_args & (UINT32_C(1) << arg_index));
+}
+
ZEND_API int zend_next_free_module(void);
BEGIN_EXTERN_C()
@@ -351,18 +360,12 @@ ZEND_API zend_result zend_get_parameters_array_ex(uint32_t param_count, zval *ar
zend_get_parameters_array_ex(param_count, argument_array)
#define zend_parse_parameters_none() \
(EXPECTED(ZEND_NUM_ARGS() == 0) ? SUCCESS : (zend_wrong_parameters_none_error(), FAILURE))
-#define zend_parse_parameters_none_throw() \
- zend_parse_parameters_none()
/* Parameter parsing API -- andrei */
-#define ZEND_PARSE_PARAMS_THROW 0 /* No longer used, zpp always uses exceptions */
#define ZEND_PARSE_PARAMS_QUIET (1<<1)
ZEND_API zend_result zend_parse_parameters(uint32_t num_args, const char *type_spec, ...);
ZEND_API zend_result zend_parse_parameters_ex(int flags, uint32_t num_args, const char *type_spec, ...);
-/* NOTE: This must have at least one value in __VA_ARGS__ for the expression to be valid */
-#define zend_parse_parameters_throw(num_args, ...) \
- zend_parse_parameters(num_args, __VA_ARGS__)
ZEND_API const char *zend_zval_type_name(const zval *arg);
ZEND_API const char *zend_zval_value_name(const zval *arg);
ZEND_API zend_string *zend_zval_get_legacy_type(const zval *arg);
@@ -392,6 +395,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(const zend_class_entr
ZEND_API zend_class_entry *zend_register_internal_class_with_flags(const zend_class_entry *class_entry, zend_class_entry *parent_ce, uint32_t flags);
ZEND_API zend_class_entry *zend_register_internal_interface(const zend_class_entry *orig_class_entry);
ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_interfaces, ...);
+ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...);
ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_len, zend_class_entry *ce, bool persistent);
@@ -410,13 +414,13 @@ ZEND_API ZEND_COLD void zend_wrong_property_read(const zval *object, zval *prope
#define IS_CALLABLE_SUPPRESS_DEPRECATIONS (1<<1)
ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc);
-ZEND_API zend_string *zend_get_callable_name_ex(zval *callable, const zend_object *object);
-ZEND_API zend_string *zend_get_callable_name(zval *callable);
+ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend_object *object);
+ZEND_API zend_string *zend_get_callable_name(const zval *callable);
ZEND_API bool zend_is_callable_at_frame(
const zval *callable, zend_object *object, const zend_execute_data *frame,
uint32_t check_flags, zend_fcall_info_cache *fcc, char **error);
-ZEND_API bool zend_is_callable_ex(zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error);
-ZEND_API bool zend_is_callable(zval *callable, uint32_t check_flags, zend_string **callable_name);
+ZEND_API bool zend_is_callable_ex(const zval *callable, zend_object *object, uint32_t check_flags, zend_string **callable_name, zend_fcall_info_cache *fcc, char **error);
+ZEND_API bool zend_is_callable(const zval *callable, uint32_t check_flags, zend_string **callable_name);
ZEND_API const char *zend_get_module_version(const char *module_name);
ZEND_API zend_result zend_get_module_started(const char *module_name);
@@ -445,10 +449,10 @@ ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const ze
ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
ZEND_API HashTable *zend_separate_class_constants_table(const zend_class_entry *class_type);
-static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) {
+static zend_always_inline const HashTable *zend_class_constants_table(const zend_class_entry *ce) {
if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) {
- zend_class_mutable_data *mutable_data =
- (zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
+ const zend_class_mutable_data *mutable_data =
+ (const zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
if (mutable_data && mutable_data->constants_table) {
return mutable_data->constants_table;
} else {
@@ -459,10 +463,10 @@ static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry
}
}
-static zend_always_inline zval *zend_class_default_properties_table(zend_class_entry *ce) {
+static zend_always_inline zval *zend_class_default_properties_table(const zend_class_entry *ce) {
if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES) && ZEND_MAP_PTR(ce->mutable_data)) {
- zend_class_mutable_data *mutable_data =
- (zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
+ const zend_class_mutable_data *mutable_data =
+ (const zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
return mutable_data->default_properties_table;
} else {
return ce->default_properties_table;
@@ -479,10 +483,10 @@ static zend_always_inline void zend_class_set_backed_enum_table(zend_class_entry
}
}
-static zend_always_inline HashTable *zend_class_backed_enum_table(zend_class_entry *ce)
+static zend_always_inline HashTable *zend_class_backed_enum_table(const zend_class_entry *ce)
{
if (ZEND_MAP_PTR(ce->mutable_data) && ce->type == ZEND_USER_CLASS) {
- zend_class_mutable_data *mutable_data = (zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
+ const zend_class_mutable_data *mutable_data = (const zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data);
return mutable_data->backed_enum_table;
} else {
return ce->backed_enum_table;
@@ -529,8 +533,16 @@ ZEND_API const char *zend_get_type_by_const(int type);
#define DLEXPORT
#endif
-#define array_init(arg) ZVAL_ARR((arg), zend_new_array(0))
-#define array_init_size(arg, size) ZVAL_ARR((arg), zend_new_array(size))
+static zend_always_inline void array_init_size(zval *arg, uint32_t size)
+{
+ ZVAL_ARR(arg, zend_new_array(size));
+}
+
+static zend_always_inline void array_init(zval *arg)
+{
+ array_init_size(arg, 0);
+}
+
ZEND_API void object_init(zval *arg);
ZEND_API zend_result object_init_ex(zval *arg, zend_class_entry *ce);
ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *class_type, uint32_t param_count, zval *params, HashTable *named_params);
@@ -701,7 +713,7 @@ ZEND_API zend_result _call_user_function_impl(zval *object, zval *function_name,
* fci->params = NULL;
* The callable_name argument may be NULL.
*/
-ZEND_API zend_result zend_fcall_info_init(zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error);
+ZEND_API zend_result zend_fcall_info_init(const zval *callable, uint32_t check_flags, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string **callable_name, char **error);
/** Clear arguments connected with zend_fcall_info *fci
* If free_mem is not zero then the params array gets free'd as well
@@ -747,20 +759,27 @@ ZEND_API void zend_fcall_info_argn(zend_fcall_info *fci, uint32_t argc, ...);
ZEND_API zend_result zend_fcall_info_call(zend_fcall_info *fci, zend_fcall_info_cache *fcc, zval *retval, zval *args);
/* Zend FCC API to store and handle PHP userland functions */
+ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b);
+
static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b)
{
+ if (a->closure || b->closure) {
+ return a->object == b->object
+ && a->calling_scope == b->calling_scope
+ && a->called_scope == b->called_scope
+ && (a->closure == b->closure || zend_fcc_closure_equals_ex(a, b))
+ ;
+ }
if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&
(b->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) {
return a->object == b->object
&& a->calling_scope == b->calling_scope
- && a->closure == b->closure
&& zend_string_equals(a->function_handler->common.function_name, b->function_handler->common.function_name)
;
}
return a->function_handler == b->function_handler
&& a->object == b->object
&& a->calling_scope == b->calling_scope
- && a->closure == b->closure
;
}
@@ -895,6 +914,8 @@ static zend_always_inline zend_result zend_forbid_dynamic_call(void)
const zend_execute_data *ex = EG(current_execute_data);
ZEND_ASSERT(ex != NULL && ex->func != NULL);
+ ZEND_ASSERT(ex->func->common.fn_flags2 & ZEND_ACC2_FORBID_DYN_CALLS);
+
if (ZEND_CALL_INFO(ex) & ZEND_CALL_DYNAMIC) {
zend_string *function_or_method_name = get_active_function_or_method_name();
zend_throw_error(NULL, "Cannot call %.*s() dynamically",
@@ -1559,10 +1580,13 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_class_or_string_or_nu
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_error(uint32_t num, char *error);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_or_null_error(uint32_t num, char *error);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_unexpected_extra_named_error(void);
-ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, uint32_t arg_num, const char *format, va_list va);
+ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, const zend_function *function, uint32_t arg_num, const char *format, va_list va);
ZEND_API ZEND_COLD void zend_argument_error(zend_class_entry *error_ce, uint32_t arg_num, const char *format, ...);
+ZEND_API ZEND_COLD void zend_argument_error_ex(zend_class_entry *error_ce, const zend_function *function, uint32_t arg_num, const char *format, ...);
ZEND_API ZEND_COLD void zend_argument_type_error(uint32_t arg_num, const char *format, ...);
+ZEND_API ZEND_COLD void zend_argument_type_error_ex(const zend_function *function, uint32_t arg_num, const char *format, ...);
ZEND_API ZEND_COLD void zend_argument_value_error(uint32_t arg_num, const char *format, ...);
+ZEND_API ZEND_COLD void zend_argument_value_error_ex(const zend_function *function, uint32_t arg_num, const char *format, ...);
ZEND_API ZEND_COLD void zend_argument_must_not_be_empty_error(uint32_t arg_num);
ZEND_API ZEND_COLD void zend_class_redeclaration_error(int type, const zend_class_entry *old_ce);
ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string *new_name, const zend_class_entry *old_ce);
@@ -2174,21 +2198,27 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string
/* Inlined implementations shared by new and old parameter parsing APIs */
+typedef enum zpp_parse_bool_status {
+ ZPP_PARSE_BOOL_STATUS_FALSE = 0,
+ ZPP_PARSE_BOOL_STATUS_TRUE = 1,
+ ZPP_PARSE_BOOL_STATUS_ERROR = 2,
+} zpp_parse_bool_status;
+
ZEND_API bool ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **pce, uint32_t num, bool check_null);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, bool *dest, uint32_t arg_num);
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_slow(const zval *arg, uint32_t arg_num);
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_parse_arg_bool_weak(const zval *arg, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_slow(const zval *arg, zend_long *dest, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_long_weak(const zval *arg, zend_long *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, double *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, double *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **dest, uint32_t arg_num);
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_slow(const zval *arg, uint32_t arg_num);
+ZEND_API double ZEND_FASTCALL zend_parse_arg_double_weak(const zval *arg, uint32_t arg_num);
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, uint32_t arg_num);
+ZEND_API zend_string* ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_number_slow(zval *arg, zval **dest, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_number_or_str_slow(zval *arg, zval **dest, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_parse_arg_str_or_long_slow(zval *arg, zend_string **dest_str, zend_long *dest_long, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, bool *dest, uint32_t arg_num);
-ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, zend_string **dest, uint32_t arg_num);
+ZEND_API zpp_parse_bool_status ZEND_FASTCALL zend_flf_parse_arg_bool_slow(const zval *arg, uint32_t arg_num);
+ZEND_API zend_string* ZEND_FASTCALL zend_flf_parse_arg_str_slow(zval *arg, uint32_t arg_num);
ZEND_API bool ZEND_FASTCALL zend_flf_parse_arg_long_slow(const zval *arg, zend_long *dest, uint32_t arg_num);
static zend_always_inline bool zend_parse_arg_bool_ex(const zval *arg, bool *dest, bool *is_null, bool check_null, uint32_t arg_num, bool frameless)
@@ -2204,11 +2234,16 @@ static zend_always_inline bool zend_parse_arg_bool_ex(const zval *arg, bool *des
*is_null = 1;
*dest = 0;
} else {
+ zpp_parse_bool_status result;
if (frameless) {
- return zend_flf_parse_arg_bool_slow(arg, dest, arg_num);
+ result = zend_flf_parse_arg_bool_slow(arg, arg_num);
} else {
- return zend_parse_arg_bool_slow(arg, dest, arg_num);
+ result = zend_parse_arg_bool_slow(arg, arg_num);
+ }
+ if (UNEXPECTED(result == ZPP_PARSE_BOOL_STATUS_ERROR)) {
+ return false;
}
+ *dest = result;
}
return 1;
}
@@ -2254,7 +2289,8 @@ static zend_always_inline bool zend_parse_arg_double(const zval *arg, double *de
*is_null = 1;
*dest = 0.0;
} else {
- return zend_parse_arg_double_slow(arg, dest, arg_num);
+ *dest = zend_parse_arg_double_slow(arg, arg_num);
+ return !zend_isnan(*dest);
}
return 1;
}
@@ -2291,12 +2327,13 @@ static zend_always_inline bool zend_parse_arg_str_ex(zval *arg, zend_string **de
*dest = NULL;
} else {
if (frameless) {
- return zend_flf_parse_arg_str_slow(arg, dest, arg_num);
+ *dest = zend_flf_parse_arg_str_slow(arg, arg_num);
} else {
- return zend_parse_arg_str_slow(arg, dest, arg_num);
+ *dest = zend_parse_arg_str_slow(arg, arg_num);
}
+ return *dest != NULL;
}
- return 1;
+ return true;
}
static zend_always_inline bool zend_parse_arg_str(zval *arg, zend_string **dest, bool check_null, uint32_t arg_num)
@@ -2482,7 +2519,7 @@ static zend_always_inline bool zend_parse_arg_resource(zval *arg, zval **dest, b
return 1;
}
-static zend_always_inline bool zend_parse_arg_func(zval *arg, zend_fcall_info *dest_fci, zend_fcall_info_cache *dest_fcc, bool check_null, char **error, bool free_trampoline)
+static zend_always_inline bool zend_parse_arg_func(const zval *arg, zend_fcall_info *dest_fci, zend_fcall_info_cache *dest_fcc, bool check_null, char **error, bool free_trampoline)
{
if (check_null && UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) {
dest_fci->size = 0;
@@ -2527,7 +2564,8 @@ static zend_always_inline bool zend_parse_arg_array_ht_or_str(
*dest_str = NULL;
} else {
*dest_ht = NULL;
- return zend_parse_arg_str_slow(arg, dest_str, arg_num);
+ *dest_str = zend_parse_arg_str_slow(arg, arg_num);
+ return *dest_str != NULL;
}
return 1;
}
diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c
index bca2190976c7..fc7bc1f4d9d4 100644
--- a/Zend/zend_alloc.c
+++ b/Zend/zend_alloc.c
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -198,7 +197,7 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */
#define ZEND_MM_SRUN_BIN_NUM_MASK 0x0000001f
#define ZEND_MM_SRUN_BIN_NUM_OFFSET 0
-#define ZEND_MM_SRUN_FREE_COUNTER_MASK 0x01ff0000
+#define ZEND_MM_SRUN_FREE_COUNTER_MASK 0x03ff0000
#define ZEND_MM_SRUN_FREE_COUNTER_OFFSET 16
#define ZEND_MM_NRUN_OFFSET_MASK 0x01ff0000
@@ -2614,12 +2613,12 @@ typedef struct _zend_alloc_globals {
#ifdef ZTS
static int alloc_globals_id;
-static size_t alloc_globals_offset;
-# define AG(v) ZEND_TSRMG_FAST(alloc_globals_offset, zend_alloc_globals *, v)
+static TSRM_TLS TSRM_TLS_MODEL_ATTR zend_alloc_globals alloc_globals;
+static void *alloc_globals_tls_addr(void) { return &alloc_globals; }
#else
-# define AG(v) (alloc_globals.v)
static zend_alloc_globals alloc_globals;
#endif
+#define AG(v) (alloc_globals.v)
ZEND_API bool is_zend_mm(void)
{
@@ -2974,7 +2973,7 @@ ZEND_API void refresh_memory_manager(void)
static ZEND_COLD ZEND_NORETURN void zend_out_of_memory(void)
{
fprintf(stderr, "Out of memory\n");
- exit(1);
+ abort();
}
#if ZEND_MM_CUSTOM
@@ -3336,7 +3335,7 @@ ZEND_API void start_memory_manager(void)
# endif
#endif
#ifdef ZTS
- ts_allocate_fast_id(&alloc_globals_id, &alloc_globals_offset, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor);
+ ts_allocate_tls_id(&alloc_globals_id, alloc_globals_tls_addr, sizeof(zend_alloc_globals), (ts_allocate_ctor) alloc_globals_ctor, (ts_allocate_dtor) alloc_globals_dtor);
#else
alloc_globals_ctor(&alloc_globals);
#endif
@@ -3582,9 +3581,3 @@ ZEND_API char * __zend_strdup(const char *s)
zend_out_of_memory();
}
-#ifdef ZTS
-size_t zend_mm_globals_size(void)
-{
- return sizeof(zend_alloc_globals);
-}
-#endif
diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h
index 264e13848d1b..7e3ea031dc72 100644
--- a/Zend/zend_alloc.h
+++ b/Zend/zend_alloc.h
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Andi Gutmans |
| Zeev Suraski |
@@ -404,10 +403,6 @@ static void apc_init_heap(void)
*/
-#ifdef ZTS
-size_t zend_mm_globals_size(void);
-#endif
-
END_EXTERN_C()
#endif
diff --git a/Zend/zend_alloc_sizes.h b/Zend/zend_alloc_sizes.h
index 502b982a5052..f806056ad9d3 100644
--- a/Zend/zend_alloc_sizes.h
+++ b/Zend/zend_alloc_sizes.h
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
diff --git a/Zend/zend_arena.h b/Zend/zend_arena.h
index 37cda28f29db..9b917a47e927 100644
--- a/Zend/zend_arena.h
+++ b/Zend/zend_arena.h
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Dmitry Stogov |
+----------------------------------------------------------------------+
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 9d93c4d22251..1b293c832c91 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Bob Weinand |
| Dmitry Stogov |
@@ -814,7 +813,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
case IS_OBJECT:
zend_cast_zval_to_object(result, &op1, IS_VAR);
break;
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
zval_ptr_dtor_nogc(&op1);
if (UNEXPECTED(EG(exception))) {
@@ -1140,6 +1139,12 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
case ZEND_AST_CALL:
case ZEND_AST_STATIC_CALL:
{
+ // Preloading will attempt to resolve constants but objects can't be stored in shm
+ // Aborting here to store the const AST instead
+ if (CG(in_compilation)) {
+ return FAILURE;
+ }
+
zend_function *fptr;
zend_class_entry *called_scope = NULL;
@@ -1240,7 +1245,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
break;
}
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL);
@@ -1460,6 +1465,16 @@ ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast)
return ref;
}
+ZEND_API zend_ast * ZEND_FASTCALL zend_ast_dup(zend_ast *ast)
+{
+ ZEND_ASSERT(ast != NULL);
+
+ zend_ast *buf = zend_ast_alloc(zend_ast_tree_size(ast));
+ zend_ast_tree_copy(ast, buf);
+
+ return buf;
+}
+
ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast)
{
tail_call:
@@ -1579,9 +1594,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
static ZEND_COLD void zend_ast_export_str(smart_str *str, const zend_string *s)
{
- size_t i;
-
- for (i = 0; i < ZSTR_LEN(s); i++) {
+ for (size_t i = 0; i < ZSTR_LEN(s); i++) {
unsigned char c = ZSTR_VAL(s)[i];
if (c == '\'' || c == '\\') {
smart_str_appendc(str, '\\');
@@ -1594,9 +1607,7 @@ static ZEND_COLD void zend_ast_export_str(smart_str *str, const zend_string *s)
static ZEND_COLD void zend_ast_export_qstr(smart_str *str, char quote, const zend_string *s)
{
- size_t i;
-
- for (i = 0; i < ZSTR_LEN(s); i++) {
+ for (size_t i = 0; i < ZSTR_LEN(s); i++) {
unsigned char c = ZSTR_VAL(s)[i];
if (c < ' ') {
switch (c) {
@@ -1637,6 +1648,22 @@ static ZEND_COLD void zend_ast_export_qstr(smart_str *str, char quote, const zen
}
}
+static ZEND_COLD void zend_ast_export_quoted_str(smart_str *str, const zend_string *s)
+{
+ for (size_t i = 0; i < ZSTR_LEN(s); i++) {
+ unsigned char c = ZSTR_VAL(s)[i];
+ if (c < ' ') {
+ smart_str_appendc(str, '"');
+ zend_ast_export_qstr(str, '"', s);
+ smart_str_appendc(str, '"');
+ return;
+ }
+ }
+ smart_str_appendc(str, '\'');
+ zend_ast_export_str(str, s);
+ smart_str_appendc(str, '\'');
+}
+
static ZEND_COLD void zend_ast_export_indent(smart_str *str, int indent)
{
while (indent > 0) {
@@ -1675,18 +1702,18 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int
}
zend_ast_export_ex(str, ast, priority, indent);
}
-
-static ZEND_COLD bool zend_ast_valid_var_char(char ch)
+static ZEND_COLD void zend_ast_export_ns_name_or_expression(smart_str *str, zend_ast *ast, int priority, int indent)
{
- unsigned char c = (unsigned char)ch;
-
- if (c != '_' && c < 127 &&
- (c < '0' || c > '9') &&
- (c < 'A' || c > 'Z') &&
- (c < 'a' || c > 'z')) {
- return false;
+ switch (ast->kind) {
+ case ZEND_AST_ZVAL:
+ case ZEND_AST_VAR:
+ zend_ast_export_ns_name(str, ast, priority, indent);
+ break;
+ default:
+ smart_str_appendc(str, '(');
+ zend_ast_export_ex(str, ast, priority, indent);
+ smart_str_appendc(str, ')');
}
- return true;
}
static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len)
@@ -1715,18 +1742,18 @@ static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len)
return true;
}
-static ZEND_COLD bool zend_ast_var_needs_braces(char ch)
-{
- return ch == '[' || zend_ast_valid_var_char(ch);
-}
-
static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int indent)
{
if (ast->kind == ZEND_AST_ZVAL) {
zval *zv = zend_ast_get_zval(ast);
- if (Z_TYPE_P(zv) == IS_STRING &&
- zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) {
- smart_str_append(str, Z_STR_P(zv));
+ if (Z_TYPE_P(zv) == IS_STRING) {
+ if (zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) {
+ smart_str_append(str, Z_STR_P(zv));
+ } else {
+ smart_str_appends(str, "{'");
+ zend_ast_export_str(str, Z_STR_P(zv));
+ smart_str_appends(str, "'}");
+ }
return;
}
} else if (ast->kind == ZEND_AST_VAR) {
@@ -1771,14 +1798,6 @@ static ZEND_COLD void zend_ast_export_encaps_list(smart_str *str, char quote, co
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
zend_ast_export_qstr(str, quote, Z_STR_P(zv));
- } else if (ast->kind == ZEND_AST_VAR &&
- ast->child[0]->kind == ZEND_AST_ZVAL &&
- (i + 1 == list->children ||
- list->child[i + 1]->kind != ZEND_AST_ZVAL ||
- !zend_ast_var_needs_braces(
- *Z_STRVAL_P(
- zend_ast_get_zval(list->child[i + 1]))))) {
- zend_ast_export_ex(str, ast, 0, indent);
} else {
smart_str_appendc(str, '{');
zend_ast_export_ex(str, ast, 0, indent);
@@ -1929,9 +1948,7 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, const zval *zv, int p
str, Z_DVAL_P(zv), (int) EG(precision), /* zero_fraction */ true);
break;
case IS_STRING:
- smart_str_appendc(str, '\'');
- zend_ast_export_str(str, Z_STR_P(zv));
- smart_str_appendc(str, '\'');
+ zend_ast_export_quoted_str(str, Z_STR_P(zv));
break;
case IS_ARRAY: {
zend_long idx;
@@ -1946,13 +1963,11 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, const zval *zv, int p
smart_str_appends(str, ", ");
}
if (key) {
- smart_str_appendc(str, '\'');
- zend_ast_export_str(str, key);
- smart_str_appends(str, "' => ");
+ zend_ast_export_quoted_str(str, key);
} else {
smart_str_append_long(str, idx);
- smart_str_appends(str, " => ");
}
+ smart_str_appends(str, " => ");
zend_ast_export_zval(str, val, 0, indent);
} ZEND_HASH_FOREACH_END();
smart_str_appendc(str, ']');
@@ -1961,7 +1976,7 @@ static ZEND_COLD void zend_ast_export_zval(smart_str *str, const zval *zv, int p
case IS_CONSTANT_AST:
zend_ast_export_ex(str, Z_ASTVAL_P(zv), priority, indent);
break;
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
}
@@ -2160,7 +2175,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
break;
case ZEND_AST_CONSTANT: {
zend_string *name = zend_ast_get_constant_name(ast);
- smart_str_appendl(str, ZSTR_VAL(name), ZSTR_LEN(name));
+ smart_str_append(str, name);
break;
}
case ZEND_AST_OP_ARRAY:
@@ -2210,7 +2225,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appendc(str, '&');
}
if (ast->kind != ZEND_AST_CLOSURE && ast->kind != ZEND_AST_ARROW_FUNC) {
- smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
+ smart_str_append(str, decl->name);
}
smart_str_appendc(str, '(');
zend_ast_export_ex(str, decl->child[0], 0, indent);
@@ -2268,7 +2283,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
}
smart_str_appends(str, "class ");
}
- smart_str_appendl(str, ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
+ smart_str_append(str, decl->name);
if (decl->flags & ZEND_ACC_ENUM && decl->child[4]) {
smart_str_appends(str, ": ");
zend_ast_export_type(str, decl->child[4], indent);
@@ -2396,7 +2411,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case T_PROPERTY_C: APPEND_STR("__PROPERTY__");
case T_NS_C: APPEND_STR("__NAMESPACE__");
case T_CLASS_C: APPEND_STR("__CLASS__");
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_TYPE:
@@ -2405,7 +2420,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case IS_CALLABLE: APPEND_STR("callable");
case IS_STATIC: APPEND_STR("static");
case IS_MIXED: APPEND_STR("mixed");
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_PLACEHOLDER_ARG:
@@ -2439,7 +2454,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case IS_STRING: PREFIX_OP("(string)", 240, 241);
case IS_ARRAY: PREFIX_OP("(array)", 240, 241);
case IS_OBJECT: PREFIX_OP("(object)", 240, 241);
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_CAST_VOID:
@@ -2473,14 +2488,14 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case ZEND_REQUIRE_ONCE: FUNC_OP("require_once");
case ZEND_REQUIRE: FUNC_OP("require");
case ZEND_EVAL: FUNC_OP("eval");
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_UNARY_OP:
switch (ast->attr) {
case ZEND_BW_NOT: PREFIX_OP("~", 240, 241);
case ZEND_BOOL_NOT: PREFIX_OP("!", 240, 241);
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_PRE_INC:
@@ -2537,37 +2552,24 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
zend_ast_export_var(str, ast->child[1], indent);
break;
case ZEND_AST_STATIC_PROP:
- zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+ zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
smart_str_appends(str, "::$");
zend_ast_export_var(str, ast->child[1], indent);
break;
case ZEND_AST_CALL: {
- zend_ast *left = ast->child[0];
- if (left->kind == ZEND_AST_ARROW_FUNC || left->kind == ZEND_AST_CLOSURE) {
- smart_str_appendc(str, '(');
- zend_ast_export_ns_name(str, left, 0, indent);
- smart_str_appendc(str, ')');
- } else {
- zend_ast_export_ns_name(str, left, 0, indent);
- }
+ zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
smart_str_appendc(str, '(');
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appendc(str, ')');
break;
}
- case ZEND_AST_PARENT_PROPERTY_HOOK_CALL:
- smart_str_append(str, Z_STR_P(zend_ast_get_zval(ast->child[0])));
- smart_str_appendc(str, '(');
- zend_ast_export_ex(str, ast->child[1], 0, indent);
- smart_str_appendc(str, ')');
- break;
case ZEND_AST_CALLABLE_CONVERT: {
zend_ast_fcc *fcc_ast = (zend_ast_fcc*)ast;
ast = fcc_ast->args;
goto simple_list;
}
case ZEND_AST_CLASS_CONST:
- zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+ zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
smart_str_appends(str, "::");
zend_ast_export_name(str, ast->child[1], 0, indent);
break;
@@ -2581,10 +2583,10 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case ZEND_FETCH_CLASS_PARENT:
smart_str_append(str, ZSTR_KNOWN(ZEND_STR_PARENT));
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
} else {
- zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+ zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
}
smart_str_appends(str, "::class");
break;
@@ -2604,7 +2606,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case ZEND_BW_AND: BINARY_OP(" &= ", 90, 91, 90);
case ZEND_BW_XOR: BINARY_OP(" ^= ", 90, 91, 90);
case ZEND_POW: BINARY_OP(" **= ", 90, 91, 90);
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_ASSIGN_COALESCE: BINARY_OP(" \?\?= ", 90, 91, 90);
@@ -2630,7 +2632,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case ZEND_POW: BINARY_OP(" ** ", 250, 251, 250);
case ZEND_BOOL_XOR: BINARY_OP(" xor ", 40, 40, 41);
case ZEND_SPACESHIP: BINARY_OP(" <=> ", 180, 181, 181);
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
break;
case ZEND_AST_GREATER: BINARY_OP(" > ", 180, 181, 181);
@@ -2663,7 +2665,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
}
zend_ast_export_class_no_header(str, decl, indent);
} else {
- zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+ zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
smart_str_appendc(str, '(');
zend_ast_export_ex(str, ast->child[1], 0, indent);
smart_str_appendc(str, ')');
@@ -2672,7 +2674,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
case ZEND_AST_INSTANCEOF:
zend_ast_export_ex(str, ast->child[0], 0, indent);
smart_str_appends(str, " instanceof ");
- zend_ast_export_ns_name(str, ast->child[1], 0, indent);
+ zend_ast_export_ns_name_or_expression(str, ast->child[1], 0, indent);
break;
case ZEND_AST_YIELD:
if (priority > 70) smart_str_appendc(str, '(');
@@ -2809,7 +2811,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appends(str, " insteadof ");
zend_ast_export_ex(str, ast->child[1], 0, indent);
break;
- case ZEND_AST_METHOD_REFERENCE:
+ case ZEND_AST_TRAIT_METHOD_REFERENCE:
if (ast->child[0]) {
zend_ast_export_name(str, ast->child[0], 0, indent);
smart_str_appends(str, "::");
@@ -2865,7 +2867,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
smart_str_appendc(str, ')');
break;
case ZEND_AST_STATIC_CALL:
- zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+ if (zend_ast_is_parent_hook_call(ast)) {
+ zend_ast_export_ns_name(str, ast->child[0], 0, indent);
+ } else {
+ zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent);
+ }
+
smart_str_appends(str, "::");
zend_ast_export_var(str, ast->child[1], indent);
smart_str_appendc(str, '(');
@@ -2982,7 +2989,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio
zend_ast_export_indent(str, indent);
smart_str_appendc(str, '}');
break;
- EMPTY_SWITCH_DEFAULT_CASE();
+ default: ZEND_UNREACHABLE();
}
return;
@@ -3079,7 +3086,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
* zend_compile_const_decl() checks the kind of the list elements. */
ast = zend_ast_list_add(ast, attr);
break;
- EMPTY_SWITCH_DEFAULT_CASE()
+ default: ZEND_UNREACHABLE();
}
return ast;
@@ -3096,3 +3103,22 @@ zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast)
ZEND_UNREACHABLE();
return NULL;
}
+
+bool zend_ast_is_parent_hook_call(const zend_ast *ast)
+{
+ ZEND_ASSERT(ast->kind == ZEND_AST_STATIC_CALL);
+
+ const zend_ast *class_ast = ast->child[0];
+ zend_ast *method_ast = ast->child[1];
+
+ return class_ast->kind == ZEND_AST_STATIC_PROP
+ && !(class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP)
+ && class_ast->child[0]->kind == ZEND_AST_ZVAL
+ && Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) == IS_STRING
+ && zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) == ZEND_FETCH_CLASS_PARENT
+ && class_ast->child[1]->kind == ZEND_AST_ZVAL
+ && method_ast->kind == ZEND_AST_ZVAL
+ && Z_TYPE_P(zend_ast_get_zval(method_ast)) == IS_STRING
+ && (zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get")
+ || zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"));
+}
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index a88efefd85b2..26f54102a143 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Bob Weinand |
| Dmitry Stogov |
@@ -144,7 +143,7 @@ enum _zend_ast_kind {
ZEND_AST_DECLARE,
ZEND_AST_USE_TRAIT,
ZEND_AST_TRAIT_PRECEDENCE,
- ZEND_AST_METHOD_REFERENCE,
+ ZEND_AST_TRAIT_METHOD_REFERENCE,
ZEND_AST_NAMESPACE,
ZEND_AST_USE_ELEM,
ZEND_AST_TRAIT_ALIAS,
@@ -153,7 +152,6 @@ enum _zend_ast_kind {
ZEND_AST_MATCH,
ZEND_AST_MATCH_ARM,
ZEND_AST_NAMED_ARG,
- ZEND_AST_PARENT_PROPERTY_HOOK_CALL,
ZEND_AST_PIPE,
/* 3 child nodes */
@@ -352,7 +350,10 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate_ex(zval *result, zend_ast *ast, zend_class_entry *scope, bool *short_circuited_ptr, zend_ast_evaluate_ctx *ctx);
ZEND_API zend_string *zend_ast_export(const char *prefix, zend_ast *ast, const char *suffix);
+/* Copies 'ast' to the heap, returns a refcounted AST reference */
ZEND_API zend_ast_ref * ZEND_FASTCALL zend_ast_copy(zend_ast *ast);
+/* Duplicates 'ast' on the arena */
+ZEND_API zend_ast * ZEND_FASTCALL zend_ast_dup(zend_ast *ast);
ZEND_API void ZEND_FASTCALL zend_ast_destroy(zend_ast *ast);
ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast);
@@ -360,7 +361,7 @@ typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr, void *context);
ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context);
static zend_always_inline size_t zend_ast_size(uint32_t children) {
- return XtOffsetOf(zend_ast, child) + (sizeof(zend_ast *) * children);
+ return offsetof(zend_ast, child) + (sizeof(zend_ast *) * children);
}
static zend_always_inline bool zend_ast_is_special(const zend_ast *ast) {
@@ -442,4 +443,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr)
zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast);
+/* Recognize parent::$prop::get() pattern. */
+bool zend_ast_is_parent_hook_call(const zend_ast *ast);
+
#endif
diff --git a/Zend/zend_atomic.c b/Zend/zend_atomic.c
index 5f6a7793beea..98a39c583437 100644
--- a/Zend/zend_atomic.c
+++ b/Zend/zend_atomic.c
@@ -1,12 +1,12 @@
/*
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Levi Morrison |
+----------------------------------------------------------------------+
@@ -57,7 +57,7 @@ ZEND_API void zend_atomic_int_store(zend_atomic_int *obj, int desired) {
zend_atomic_int_store_ex(obj, desired);
}
-#if defined(ZEND_WIN32) || defined(HAVE_SYNC_ATOMICS)
+#if (defined(ZEND_WIN32) || defined(HAVE_SYNC_ATOMICS)) && !defined(HAVE_C11_ATOMICS)
/* On these platforms it is non-const due to underlying APIs. */
ZEND_API bool zend_atomic_bool_load(zend_atomic_bool *obj) {
return zend_atomic_bool_load_ex(obj);
diff --git a/Zend/zend_atomic.h b/Zend/zend_atomic.h
index 56422a721fdb..31558996c3c3 100644
--- a/Zend/zend_atomic.h
+++ b/Zend/zend_atomic.h
@@ -1,12 +1,12 @@
/*
+----------------------------------------------------------------------+
- | 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: |
- | https://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. |
+ | Copyright © The PHP Group and Contributors. |
+ +----------------------------------------------------------------------+
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Levi Morrison |
+----------------------------------------------------------------------+
@@ -39,7 +39,7 @@
* and alignment purposes.
*/
-#if defined(ZEND_WIN32) || defined(HAVE_SYNC_ATOMICS)
+#if (defined(ZEND_WIN32) || defined(HAVE_SYNC_ATOMICS)) && !defined(HAVE_C11_ATOMICS)
typedef struct zend_atomic_bool_s {
volatile char value;
} zend_atomic_bool;
@@ -68,7 +68,7 @@ typedef struct zend_atomic_int_s {
BEGIN_EXTERN_C()
-#ifdef ZEND_WIN32
+#if defined(ZEND_WIN32) && !defined(HAVE_C11_ATOMICS)
#ifndef InterlockedExchange8
#define InterlockedExchange8 _InterlockedExchange8
@@ -123,7 +123,7 @@ static zend_always_inline bool zend_atomic_int_compare_exchange_ex(zend_atomic_i
}
}
-/* On this platform it is non-const due to Iterlocked API*/
+/* On this platform it is non-const due to Interlocked API */
static zend_always_inline bool zend_atomic_bool_load_ex(zend_atomic_bool *obj) {
/* Or'ing with false won't change the value. */
return InterlockedOr8(&obj->value, false);
@@ -376,7 +376,7 @@ ZEND_API bool zend_atomic_int_compare_exchange(zend_atomic_int *obj, int *expect
ZEND_API void zend_atomic_bool_store(zend_atomic_bool *obj, bool desired);
ZEND_API void zend_atomic_int_store(zend_atomic_int *obj, int desired);
-#if defined(ZEND_WIN32) || defined(HAVE_SYNC_ATOMICS)
+#if (defined(ZEND_WIN32) && !defined(HAVE_C11_ATOMICS)) || defined(HAVE_SYNC_ATOMICS)
/* On these platforms it is non-const due to underlying APIs. */
ZEND_API bool zend_atomic_bool_load(zend_atomic_bool *obj);
ZEND_API int zend_atomic_int_load(zend_atomic_int *obj);
diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c
index cba95810ba49..71f0d788e921 100644
--- a/Zend/zend_attributes.c
+++ b/Zend/zend_attributes.c
@@ -2,15 +2,14 @@
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
- | Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+ | Copyright © Zend Technologies Ltd., a subsidiary company of |
+ | Perforce Software, Inc., and Contributors. |
+----------------------------------------------------------------------+
- | 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. |
+ | This source file is subject to the Modified BSD License that is |
+ | bundled with this package in the file LICENSE, and is available |
+ | through the World Wide Web at . |
+ | |
+ | SPDX-License-Identifier: BSD-3-Clause |
+----------------------------------------------------------------------+
| Authors: Benjamin Eberlei |
| Martin Schröder