diff --git a/.github/actions/apt-x64/action.yml b/.github/actions/apt-x64/action.yml new file mode 100644 index 0000000000000..c21a34534aea8 --- /dev/null +++ b/.github/actions/apt-x64/action.yml @@ -0,0 +1,68 @@ +name: apt +runs: + using: composite + steps: + - shell: bash + run: | + set -x + sudo apt-get update + sudo apt-get install \ + bison \ + re2c \ + locales \ + ldap-utils \ + openssl \ + slapd \ + language-pack-de \ + libgmp-dev \ + libicu-dev \ + libtidy-dev \ + libenchant-dev \ + libaspell-dev \ + libpspell-dev \ + libsasl2-dev \ + libxpm-dev \ + libzip-dev \ + libsqlite3-dev \ + libwebp-dev \ + libonig-dev \ + libkrb5-dev \ + libgssapi-krb5-2 \ + libcurl4-openssl-dev \ + libxml2-dev \ + libxslt1-dev \ + libpq-dev \ + libreadline-dev \ + libldap2-dev \ + libsodium-dev \ + libargon2-0-dev \ + libmm-dev \ + libsnmp-dev \ + postgresql \ + postgresql-contrib \ + snmpd \ + snmp-mibs-downloader \ + freetds-dev \ + unixodbc-dev \ + llvm \ + libc-client-dev \ + dovecot-core \ + dovecot-pop3d \ + dovecot-imapd \ + sendmail \ + firebird-dev \ + liblmdb-dev \ + libtokyocabinet-dev \ + libdb-dev \ + libqdbm-dev \ + libjpeg-dev \ + libpng-dev \ + libfreetype6-dev + mkdir /opt/oracle + wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip + unzip instantclient-basiclite-linuxx64.zip + wget https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip + unzip instantclient-sdk-linuxx64.zip + mv instantclient_*_* /opt/oracle/instantclient + # Interferes with libldap2 headers. + rm /opt/oracle/instantclient/sdk/include/ldap.h diff --git a/.github/actions/brew/action.yml b/.github/actions/brew/action.yml new file mode 100644 index 0000000000000..36b76392048d4 --- /dev/null +++ b/.github/actions/brew/action.yml @@ -0,0 +1,34 @@ +name: brew +runs: + using: composite + steps: + - shell: bash + run: | + set -x + brew install \ + pkg-config \ + autoconf \ + bison \ + re2c + brew install \ + openssl@1.1 \ + krb5 \ + bzip2 \ + enchant \ + libffi \ + libpng \ + webp \ + freetype \ + intltool \ + icu4c \ + libiconv \ + zlib \ + t1lib \ + gd \ + libzip \ + gmp \ + tidyp \ + libxml2 \ + libxslt \ + postgresql + brew link icu4c gettext --force diff --git a/.github/actions/configure-macos/action.yml b/.github/actions/configure-macos/action.yml new file mode 100644 index 0000000000000..852d8f62b68e1 --- /dev/null +++ b/.github/actions/configure-macos/action.yml @@ -0,0 +1,71 @@ +name: ./configure +inputs: + configurationParameters: + default: '' + required: false +runs: + using: composite + steps: + - shell: bash + run: | + set -x + export PATH="/usr/local/opt/bison/bin:$PATH" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/openssl@1.1/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/krb5/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libffi/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxml2/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/libxslt/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/zlib/lib/pkgconfig" + export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/opt/icu4c/lib/pkgconfig" + ./buildconf --force + ./configure \ + --enable-option-checking=fatal \ + --prefix=/usr/local \ + --enable-fpm \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pgsql=/usr/local/opt/libpq \ + --with-pdo-pgsql=/usr/local/opt/libpq \ + --with-pdo-sqlite \ + --without-pear \ + --enable-gd \ + --with-jpeg \ + --with-webp \ + --with-freetype \ + --enable-exif \ + --with-zip \ + --with-zlib \ + --enable-soap \ + --enable-xmlreader \ + --with-xsl \ + --with-tidy=/usr/local/opt/tidyp \ + --with-libxml \ + --enable-sysvsem \ + --enable-sysvshm \ + --enable-shmop \ + --enable-pcntl \ + --with-readline=/usr/local/opt/readline \ + --enable-mbstring \ + --with-curl \ + --with-gettext=/usr/local/opt/gettext \ + --enable-sockets \ + --with-bz2=/usr/local/opt/bzip2 \ + --with-openssl \ + --with-gmp=/usr/local/opt/gmp \ + --with-iconv=/usr/local/opt/libiconv \ + --enable-bcmath \ + --enable-calendar \ + --enable-ftp \ + --with-pspell=/usr/local/opt/aspell \ + --with-kerberos \ + --enable-sysvmsg \ + --with-ffi \ + --enable-zend-test \ + --enable-intl \ + --with-mhash \ + --with-sodium \ + --enable-dba \ + --enable-werror \ + --with-config-file-path=/etc \ + --with-config-file-scan-dir=/etc/php.d \ + ${{ inputs.configurationParameters }} diff --git a/.github/actions/configure-x64/action.yml b/.github/actions/configure-x64/action.yml new file mode 100644 index 0000000000000..f9b774218f4ef --- /dev/null +++ b/.github/actions/configure-x64/action.yml @@ -0,0 +1,84 @@ +name: ./configure +inputs: + configurationParameters: + default: '' + required: false +runs: + using: composite + steps: + - shell: bash + run: | + set -x + ./buildconf --force + ./configure \ + --enable-option-checking=fatal \ + --prefix=/usr \ + --enable-phpdbg \ + --enable-fpm \ + --with-pdo-mysql=mysqlnd \ + --with-mysqli=mysqlnd \ + --with-pgsql \ + --with-pdo-pgsql \ + --with-pdo-sqlite \ + --enable-intl \ + --without-pear \ + --enable-gd \ + --with-jpeg \ + --with-webp \ + --with-freetype \ + --with-xpm \ + --enable-exif \ + --with-zip \ + --with-zlib \ + --with-zlib-dir=/usr \ + --enable-soap \ + --enable-xmlreader \ + --with-xsl \ + --with-tidy \ + --enable-sysvsem \ + --enable-sysvshm \ + --enable-shmop \ + --enable-pcntl \ + --with-readline \ + --enable-mbstring \ + --with-curl \ + --with-gettext \ + --enable-sockets \ + --with-bz2 \ + --with-openssl \ + --with-gmp \ + --enable-bcmath \ + --enable-calendar \ + --enable-ftp \ + --with-pspell=/usr \ + --with-enchant=/usr \ + --with-kerberos \ + --enable-sysvmsg \ + --with-ffi \ + --enable-zend-test \ + --with-ldap \ + --with-ldap-sasl \ + --with-password-argon2 \ + --with-mhash \ + --with-sodium \ + --enable-dba \ + --with-cdb \ + --enable-flatfile \ + --enable-inifile \ + --with-tcadb \ + --with-lmdb \ + --with-qdbm \ + --with-snmp \ + --with-unixODBC \ + --with-imap \ + --with-kerberos \ + --with-imap-ssl \ + --with-pdo-odbc=unixODBC,/usr \ + --with-pdo-oci=shared,instantclient,/opt/oracle/instantclient \ + --with-oci8=shared,instantclient,/opt/oracle/instantclient \ + --with-config-file-path=/etc \ + --with-config-file-scan-dir=/etc/php.d \ + --with-pdo-firebird \ + --with-pdo-dblib \ + --enable-werror \ + ${{ inputs.configurationParameters }} diff --git a/.github/actions/install-linux/action.yml b/.github/actions/install-linux/action.yml new file mode 100644 index 0000000000000..6db48f0ec3e1d --- /dev/null +++ b/.github/actions/install-linux/action.yml @@ -0,0 +1,14 @@ +name: Install +runs: + using: composite + steps: + - shell: bash + run: | + set -x + sudo make install + sudo mkdir /etc/php.d + sudo chmod 777 /etc/php.d + echo mysqli.default_socket=/var/run/mysqld/mysqld.sock > /etc/php.d/mysqli.ini + echo pdo_mysql.default_socket=/var/run/mysqld/mysqld.sock > /etc/php.d/pdo_mysql.ini + echo opcache.enable_cli=1 >> /etc/php.d/opcache.ini + echo opcache.protect_memory=1 >> /etc/php.d/opcache.ini diff --git a/.github/actions/mssql/action.yml b/.github/actions/mssql/action.yml new file mode 100644 index 0000000000000..894380644c1ea --- /dev/null +++ b/.github/actions/mssql/action.yml @@ -0,0 +1,14 @@ +name: Create mssql container +runs: + using: composite + steps: + - shell: bash + run: | + set -x + docker run \ + -e "ACCEPT_EULA=Y" \ + -e "SA_PASSWORD=" \ + -p 1433:1433 \ + --name sql1 \ + -h sql1 \ + -d mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-16.04 diff --git a/.github/actions/setup-x64/action.yml b/.github/actions/setup-x64/action.yml new file mode 100644 index 0000000000000..6cec51d4c8079 --- /dev/null +++ b/.github/actions/setup-x64/action.yml @@ -0,0 +1,30 @@ +name: Setup +runs: + using: composite + steps: + - shell: bash + run: | + set -x + + sudo service mysql start + sudo service postgresql start + sudo service slapd start + mysql -uroot -proot -e "CREATE DATABASE IF NOT EXISTS test" + # Ensure local_infile tests can run. + mysql -uroot -proot -e "SET GLOBAL local_infile = true" + sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'postgres';" + sudo -u postgres psql -c "CREATE DATABASE test;" + docker exec sql1 /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U SA -P "" -Q "create login pdo_test with password='password', check_policy=off; create user pdo_test for login pdo_test; grant alter, control to pdo_test;" + sudo locale-gen de_DE + + ./.github/scripts/setup-slapd.sh + + sudo cp ext/snmp/tests/snmpd.conf /etc/snmp + sudo cp ext/snmp/tests/bigtest /etc/snmp + sudo service snmpd restart + + sudo groupadd -g 5000 vmail + sudo useradd -m -d /var/vmail -s /bin/false -u 5000 -g vmail vmail + sudo cp ext/imap/tests/setup/dovecot.conf /etc/dovecot/dovecot.conf + sudo cp ext/imap/tests/setup/dovecotpass /etc/dovecot/dovecotpass + sudo service dovecot restart diff --git a/.github/actions/test-linux/action.yml b/.github/actions/test-linux/action.yml new file mode 100644 index 0000000000000..c7dab609820dd --- /dev/null +++ b/.github/actions/test-linux/action.yml @@ -0,0 +1,27 @@ +name: Test +inputs: + runTestsParameters: + default: '' + required: false +runs: + using: composite + steps: + - shell: bash + run: | + set -x + export MYSQL_TEST_USER=root + export MYSQL_TEST_PASSWD=root + export PDO_MYSQL_TEST_DSN="mysql:host=localhost;dbname=test" + export PDO_MYSQL_TEST_USER=root + export PDO_MYSQL_TEST_PASS=root + export PDO_DBLIB_TEST_DSN="dblib:host=127.0.0.1;dbname=master;version=7.0" + export PDO_DBLIB_TEST_USER="pdo_test" + export PDO_DBLIB_TEST_PASS="password" + export SKIP_IO_CAPTURE_TESTS=1 + sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ + -j$(/usr/bin/nproc) \ + -g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP \ + --offline \ + --show-diff \ + --show-slow 1000 \ + --set-timeout 120 diff --git a/.github/actions/test-macos/action.yml b/.github/actions/test-macos/action.yml new file mode 100644 index 0000000000000..99ac49b268c76 --- /dev/null +++ b/.github/actions/test-macos/action.yml @@ -0,0 +1,20 @@ +name: Test +inputs: + runTestsParameters: + default: '' + required: false +runs: + using: composite + steps: + - shell: bash + run: | + set -x + export SKIP_IO_CAPTURE_TESTS=1 + export CI_NO_IPV6=1 + sapi/cli/php run-tests.php -P -q ${{ inputs.runTestsParameters }} \ + -j$(sysctl -n hw.ncpu) \ + -g FAIL,XFAIL,BORK,WARN,LEAK,XLEAK,SKIP \ + --offline \ + --show-diff \ + --show-slow 1000 \ + --set-timeout 120 diff --git a/.github/scripts/setup-slapd.sh b/.github/scripts/setup-slapd.sh new file mode 100755 index 0000000000000..7ea3cb33b3d0e --- /dev/null +++ b/.github/scripts/setup-slapd.sh @@ -0,0 +1,185 @@ +#!/bin/sh +set -ev + +# Create TLS certificate +sudo mkdir -p /etc/ldap/ssl + +alt_names() { + ( + ( + (hostname && hostname -a && hostname -A && hostname -f) | + xargs -n 1 | + sort -u | + sed -e 's/\(\S\+\)/DNS:\1/g' + ) && ( + (hostname -i && hostname -I && echo "127.0.0.1 ::1") | + xargs -n 1 | + sort -u | + sed -e 's/\(\S\+\)/IP:\1/g' + ) + ) | paste -d, -s +} + +sudo openssl req -newkey rsa:4096 -x509 -nodes -days 3650 \ + -out /etc/ldap/ssl/server.crt -keyout /etc/ldap/ssl/server.key \ + -subj "/C=US/ST=Arizona/L=Localhost/O=localhost/CN=localhost" \ + -addext "subjectAltName = `alt_names`" + +sudo chown -R openldap:openldap /etc/ldap/ssl + +# Display the TLS certificate (should be world readable) +openssl x509 -noout -text -in /etc/ldap/ssl/server.crt + +# Point to the certificate generated +if ! grep -q 'TLS_CACERT \/etc\/ldap\/ssl\/server.crt' /etc/ldap/ldap.conf; then + sudo sed -e 's|^\s*TLS_CACERT|# TLS_CACERT|' -i /etc/ldap/ldap.conf + echo 'TLS_CACERT /etc/ldap/ssl/server.crt' | sudo tee -a /etc/ldap/ldap.conf +fi + +# Configure LDAP protocols to serve. +sudo sed -e 's|^\s*SLAPD_SERVICES\s*=.*$|SLAPD_SERVICES="ldap:/// ldaps:/// ldapi:///"|' -i /etc/default/slapd + +# Configure LDAP database. +DBDN=`sudo ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config '(&(olcRootDN=*)(olcSuffix=*))' dn | grep -i '^dn:' | sed -e 's/^dn:\s*//'`; + +sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/ppolicy.ldif + +sudo service slapd restart + +sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// << EOF +dn: $DBDN +changetype: modify +replace: olcSuffix +olcSuffix: dc=my-domain,dc=com +- +replace: olcRootDN +olcRootDN: cn=Manager,dc=my-domain,dc=com +- +replace: olcRootPW +olcRootPW: secret + +dn: cn=config +changetype: modify +add: olcTLSCACertificateFile +olcTLSCACertificateFile: /etc/ldap/ssl/server.crt +- +add: olcTLSCertificateFile +olcTLSCertificateFile: /etc/ldap/ssl/server.crt +- +add: olcTLSCertificateKeyFile +olcTLSCertificateKeyFile: /etc/ldap/ssl/server.key +- +add: olcTLSVerifyClient +olcTLSVerifyClient: never +- +add: olcAuthzRegexp +olcAuthzRegexp: uid=usera,cn=digest-md5,cn=auth cn=usera,dc=my-domain,dc=com +- +replace: olcLogLevel +olcLogLevel: -1 + +dn: cn=module{0},cn=config +changetype: modify +add: olcModuleLoad +olcModuleLoad: sssvlv +- +add: olcModuleLoad +olcModuleLoad: ppolicy +- +add: olcModuleLoad +olcModuleLoad: dds +EOF + +sudo service slapd restart + +sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// << EOF +dn: olcOverlay=sssvlv,$DBDN +objectClass: olcOverlayConfig +objectClass: olcSssVlvConfig +olcOverlay: sssvlv +olcSssVlvMax: 10 +olcSssVlvMaxKeys: 5 + +dn: olcOverlay=ppolicy,$DBDN +objectClass: olcOverlayConfig +objectClass: olcPPolicyConfig +olcOverlay: ppolicy +### This would clutter our DIT and make tests to fail, while ppolicy does not +### seem to work as we expect (it does not seem to provide expected controls) +## olcPPolicyDefault: cn=default,ou=pwpolicies,dc=my-domain,dc=com +## olcPPolicyHashCleartext: FALSE +## olcPPolicyUseLockout: TRUE + +dn: olcOverlay=dds,$DBDN +objectClass: olcOverlayConfig +objectClass: olcDdsConfig +olcOverlay: dds +EOF + +sudo service slapd restart + +sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// << EOF +dn: $DBDN +changetype: modify +add: olcDbIndex +olcDbIndex: entryExpireTimestamp eq +EOF + +sudo service slapd restart + +ldapadd -H ldapi:/// -D cn=Manager,dc=my-domain,dc=com -w secret <- + --${{ matrix.debug && 'enable' || 'disable' }}-debug + --${{ matrix.zts && 'enable' || 'disable' }}-zts + - name: make + run: make -j$(/usr/bin/nproc) >/dev/null + - name: make install + uses: ./.github/actions/install-linux + - name: Setup + uses: ./.github/actions/setup-x64 + - name: Test + uses: ./.github/actions/test-linux + - name: Test Tracing JIT + uses: ./.github/actions/test-linux + with: + runTestsParameters: -d zend_extension=opcache.so -d opcache.jit_buffer_size=16M + MACOS_DEBUG_NTS: + runs-on: macos-10.15 + steps: + - name: git checkout + uses: actions/checkout@v2 + - name: brew + uses: ./.github/actions/brew + - name: ./configure + uses: ./.github/actions/configure-macos + with: + configurationParameters: --enable-debug --disable-zts + - name: make + run: |- + export PATH="/usr/local/opt/bison/bin:$PATH" + make -j$(sysctl -n hw.logicalcpu) >/dev/null + - name: make install + run: sudo make install + - name: Test + uses: ./.github/actions/test-macos + - name: Test Tracing JIT + uses: ./.github/actions/test-macos + with: + runTestsParameters: >- + -d zend_extension=opcache.so + -d opcache.protect_memory=1 + -d opcache.jit_buffer_size=16M diff --git a/NEWS b/NEWS index 5fe565cb881f3..0c5927890fb08 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,43 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.1.3 +17 Mar 2022, PHP 8.1.4 + +- Core: + . Fixed Haiku ZTS build. (David Carlier) + . Fixed bug GH-8059 arginfo not regenerated for extension. (Remi) + . Fixed bug GH-8083 Segfault when dumping uncalled fake closure with static + variables. (ilutov) + . Fixed bug GH-7958 (Nested CallbackFilterIterator is leaking memory). (cmb) + . Fixed bug GH-8074 (Wrong type inference of range() result). (cmb) + . Fixed bug GH-8140 (Wrong first class callable by name optimization). (cmb) + . Fixed bug GH-8082 (op_arrays with temporary run_time_cache leak memory + when observed). (Bob) + +- GD: + . Fixed libpng warning when loading interlaced images. (Brett) + +- FPM: + . Fixed bug #76109 (Unsafe access to fpm scoreboard). + (Till Backhaus, Jakub Zelenka) + +- Iconv: + . Fixed bug GH-7953 (ob_clean() only does not set Content-Encoding). (cmb) + . Fixed bug GH-7980 (Unexpected result for iconv_mime_decode). (cmb) + +- MBString: + . Fixed bug GH-8128 (mb_check_encoding wrong result for 7bit). (alexdowad) + +- MySQLnd: + . Fixed bug GH-8058 (NULL pointer dereference in mysqlnd package). (Kamil Tekiela) + +- Reflection: + . Fixed bug GH-8080 (ReflectionClass::getConstants() depends on def. order). + (cmb) + +- Zlib: + . Fixed bug GH-7953 (ob_clean() only does not set Content-Encoding). (cmb) + +03 Feb 2022, PHP 8.1.3 - Core: . Fixed bug #81430 (Attribute instantiation leaves dangling pointer). diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h index 941d502226687..63b42dc1864a2 100644 --- a/TSRM/TSRM.h +++ b/TSRM/TSRM.h @@ -147,7 +147,7 @@ TSRM_API const char *tsrm_api_name(void); # define __has_attribute(x) 0 #endif -#if !__has_attribute(tls_model) || defined(__FreeBSD__) || defined(__MUSL__) +#if !__has_attribute(tls_model) || defined(__FreeBSD__) || defined(__MUSL__) || defined(__HAIKU__) # define TSRM_TLS_MODEL_ATTR #elif __PIC__ # define TSRM_TLS_MODEL_ATTR __attribute__((tls_model("initial-exec"))) diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c index 087add4cbed66..ac134e9727ec5 100644 --- a/Zend/Optimizer/dce.c +++ b/Zend/Optimizer/dce.c @@ -398,7 +398,8 @@ static inline bool is_free_of_live_var(context *ctx, zend_op *opline, zend_ssa_o switch (opline->opcode) { case ZEND_FREE: /* It is always safe to remove FREEs of non-refcounted values, even if they are live. */ - if (!may_be_refcounted(ctx->ssa->var_info[ssa_op->op1_use].type)) { + if ((ctx->ssa->var_info[ssa_op->op1_use].type & (MAY_BE_REF|MAY_BE_ANY|MAY_BE_UNDEF)) != 0 + && !may_be_refcounted(ctx->ssa->var_info[ssa_op->op1_use].type)) { return 0; } ZEND_FALLTHROUGH; diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index d0b52fbfcd1bb..5f7d027a73bf6 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -1356,6 +1356,7 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx if (src_var >= 0 && !(ssa->var_info[src_var].type & MAY_BE_REF) + && (ssa->var_info[src_var].type & (MAY_BE_UNDEF|MAY_BE_ANY)) && ssa->vars[src_var].definition >= 0 && ssa->ops[ssa->vars[src_var].definition].result_def == src_var && ssa->ops[ssa->vars[src_var].definition].result_use < 0 @@ -1513,6 +1514,7 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) && src_var >= 0 && !(ssa->var_info[src_var].type & MAY_BE_REF) + && (ssa->var_info[src_var].type & (MAY_BE_UNDEF|MAY_BE_ANY)) && ssa->vars[src_var].definition >= 0 && ssa->ops[ssa->vars[src_var].definition].result_def == src_var && ssa->ops[ssa->vars[src_var].definition].result_use < 0 diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index e2e16f90b8654..351010177f630 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -200,14 +200,18 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); literal_dtor(&ZEND_OP2_LITERAL(fcall)); fcall->op2.constant = fcall->op2.constant + 1; - opline->opcode = zend_get_call_op(fcall, call_stack[call].func); + if (opline->opcode != ZEND_CALLABLE_CONVERT) { + opline->opcode = zend_get_call_op(fcall, call_stack[call].func); + } } else if (fcall->opcode == ZEND_INIT_NS_FCALL_BY_NAME) { fcall->opcode = ZEND_INIT_FCALL; fcall->op1.num = zend_vm_calc_used_stack(fcall->extended_value, call_stack[call].func); literal_dtor(&op_array->literals[fcall->op2.constant]); literal_dtor(&op_array->literals[fcall->op2.constant + 2]); fcall->op2.constant = fcall->op2.constant + 1; - opline->opcode = zend_get_call_op(fcall, call_stack[call].func); + if (opline->opcode != ZEND_CALLABLE_CONVERT) { + opline->opcode = zend_get_call_op(fcall, call_stack[call].func); + } } else if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL || fcall->opcode == ZEND_INIT_METHOD_CALL || fcall->opcode == ZEND_NEW) { diff --git a/Zend/Optimizer/zend_func_info.c b/Zend/Optimizer/zend_func_info.c index 0218ae15940ac..16e09d8337d7b 100644 --- a/Zend/Optimizer/zend_func_info.c +++ b/Zend/Optimizer/zend_func_info.c @@ -75,8 +75,8 @@ static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa || (t3 & (MAY_BE_DOUBLE|MAY_BE_STRING))) { tmp |= MAY_BE_ARRAY_OF_DOUBLE; } - if ((t1 & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_STRING|MAY_BE_DOUBLE))) - && (t2 & ((MAY_BE_ANY|MAY_BE_UNDEF)-(MAY_BE_STRING|MAY_BE_DOUBLE)))) { + if ((t1 & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE)) + && (t2 & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) { if ((t3 & MAY_BE_ANY) != MAY_BE_DOUBLE) { tmp |= MAY_BE_ARRAY_OF_LONG; } diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index d4f0c19c8d8a5..6c5d96bd83158 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2661,6 +2661,7 @@ static zend_always_inline int _zend_update_type_info( case ZEND_ASSIGN_DIM: if (opline->op1_type == IS_CV) { tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type); + tmp |= ssa->var_info[ssa_op->op1_def].type & (MAY_BE_ARRAY_PACKED|MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); UPDATE_SSA_TYPE(tmp, ssa_op->op1_def); COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def); } @@ -2669,6 +2670,9 @@ static zend_always_inline int _zend_update_type_info( if (t1 & MAY_BE_STRING) { tmp |= MAY_BE_STRING | MAY_BE_NULL; } + if (t1 & MAY_BE_OBJECT) { + tmp |= (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF); + } if (t1 & (MAY_BE_ARRAY|MAY_BE_FALSE|MAY_BE_NULL|MAY_BE_UNDEF)) { tmp |= (OP1_DATA_INFO() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF)); diff --git a/Zend/tests/closure_063.phpt b/Zend/tests/closure_063.phpt new file mode 100644 index 0000000000000..b53f370eb4bfc --- /dev/null +++ b/Zend/tests/closure_063.phpt @@ -0,0 +1,12 @@ +--TEST-- +Closure::bindTo leaks with "fake" closure +--FILE-- +bindTo(new stdClass); +?> +DONE +--EXPECT-- +DONE \ No newline at end of file diff --git a/Zend/tests/generators/errors/resume_running_generator_error_002.phpt b/Zend/tests/generators/errors/resume_running_generator_error_002.phpt new file mode 100644 index 0000000000000..e71e32a886dcc --- /dev/null +++ b/Zend/tests/generators/errors/resume_running_generator_error_002.phpt @@ -0,0 +1,17 @@ +--TEST-- +Memory leak when resume an already running generator +--FILE-- +send($g); +} +$gen = gen(); +try { + $gen->send($gen); +} catch (Throwable $e) { + echo $e->getMessage() . "\n"; +} +?> +--EXPECT-- +Cannot resume an already running generator diff --git a/Zend/tests/generators/errors/resume_running_generator_error_003.phpt b/Zend/tests/generators/errors/resume_running_generator_error_003.phpt new file mode 100644 index 0000000000000..c72f9ba87258c --- /dev/null +++ b/Zend/tests/generators/errors/resume_running_generator_error_003.phpt @@ -0,0 +1,24 @@ +--TEST-- +Use-after-free when resume an already running generator +--FILE-- +send($y); +} +$gen=gen(); +try { + $gen->send($gen); +}catch(y) { +} +?> +--EXPECTF-- +Warning: Undefined variable $y in %sresume_running_generator_error_003.php on line 4 + +Fatal error: Uncaught Error: Cannot resume an already running generator in %sresume_running_generator_error_003.php:4 +Stack trace: +#0 %sresume_running_generator_error_003.php(4): Generator->send(NULL) +#1 [internal function]: gen() +#2 %sresume_running_generator_error_003.php(8): Generator->send(Object(Generator)) +#3 {main} + thrown in %sresume_running_generator_error_003.php on line 4 diff --git a/Zend/tests/gh7958.phpt b/Zend/tests/gh7958.phpt new file mode 100644 index 0000000000000..d9f3b8940a4a4 --- /dev/null +++ b/Zend/tests/gh7958.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-7958 (Nested CallbackFilterIterator is leaking memory) +--FILE-- +iterator = new ArrayIterator($data); + echo '-- c ' . spl_object_id($this) . "\n"; + } + + public function __destruct() + { + echo '-- d ' . spl_object_id($this) . "\n"; + } + + public function filter() + { + $this->iterator = new \CallbackFilterIterator($this->iterator, fn() => true); + $this->iterator->rewind(); + } +} + +$action = new Action(['a', 'b']); +$action->filter(); +$action->filter(); +print_r(iterator_to_array($action->iterator)); +$action = null; +gc_collect_cycles(); +echo "==DONE==\n"; +?> +--EXPECT-- +-- c 1 +Array +( + [0] => a + [1] => b +) +-- d 1 +==DONE== diff --git a/Zend/tests/gh8083.phpt b/Zend/tests/gh8083.phpt new file mode 100644 index 0000000000000..a98de47cbf375 --- /dev/null +++ b/Zend/tests/gh8083.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-8083 (var_dump() on closure with static variable segfaults) +--FILE-- + +--EXPECT-- +object(Closure)#1 (1) { + ["static"]=> + array(1) { + ["i"]=> + NULL + } +} diff --git a/Zend/zend.c b/Zend/zend.c index 6951465d1f956..f0b653a915d1d 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1249,8 +1249,6 @@ ZEND_API void zend_deactivate(void) /* {{{ */ /* we're no longer executing anything */ EG(current_execute_data) = NULL; - zend_observer_deactivate(); - zend_try { shutdown_scanner(); } zend_end_try(); diff --git a/Zend/zend.h b/Zend/zend.h index 071901d4c5725..7f152a8591e5b 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.1.3-dev" +#define ZEND_VERSION "4.1.4" #define ZEND_ENGINE_3 diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 04b1a4ff19d02..dfdc4e2bb4c14 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -2661,7 +2661,20 @@ ZEND_API char* ZEND_FASTCALL zend_strndup(const char *s, size_t length) ZEND_API zend_result zend_set_memory_limit(size_t memory_limit) { #if ZEND_MM_LIMIT - if (UNEXPECTED(memory_limit < AG(mm_heap)->real_size)) { + zend_mm_heap *heap = AG(mm_heap); + + if (UNEXPECTED(memory_limit < heap->real_size)) { + if (memory_limit >= heap->real_size - heap->cached_chunks_count * ZEND_MM_CHUNK_SIZE) { + /* free some cached chunks to fit into new memory limit */ + do { + zend_mm_chunk *p = heap->cached_chunks; + heap->cached_chunks = p->next; + zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + heap->cached_chunks_count--; + heap->real_size -= ZEND_MM_CHUNK_SIZE; + } while (memory_limit < heap->real_size); + return SUCCESS; + } return FAILURE; } AG(mm_heap)->limit = memory_limit; diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index a5ae8cde359e2..0dab5537a3752 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -697,6 +697,13 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en } ZEND_MAP_PTR_INIT(closure->func.op_array.static_variables_ptr, &closure->func.op_array.static_variables); + } else if (func->op_array.static_variables) { + HashTable *ht = ZEND_MAP_PTR_GET(func->op_array.static_variables_ptr); + + if (!ht) { + ht = zend_array_dup(func->op_array.static_variables); + ZEND_MAP_PTR_SET(closure->func.op_array.static_variables_ptr, ht); + } } /* Runtime cache is scope-dependent, so we cannot reuse it if the scope changed */ @@ -770,7 +777,8 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) { - zend_create_closure_ex(res, func, scope, called_scope, this_ptr, /* is_fake */ false); + zend_create_closure_ex(res, func, scope, called_scope, this_ptr, + /* is_fake */ (func->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0); } ZEND_API void zend_create_fake_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */ diff --git a/Zend/zend_extensions.c b/Zend/zend_extensions.c index 4d4b1ffe09b41..c3efc0719bc94 100644 --- a/Zend/zend_extensions.c +++ b/Zend/zend_extensions.c @@ -267,8 +267,17 @@ ZEND_API int zend_get_resource_handle(const char *module_name) ZEND_API int zend_get_op_array_extension_handle(const char *module_name) { + int handle = zend_op_array_extension_handles++; zend_add_system_entropy(module_name, "zend_get_op_array_extension_handle", &zend_op_array_extension_handles, sizeof(int)); - return zend_op_array_extension_handles++; + return handle; +} + +ZEND_API int zend_get_op_array_extension_handles(const char *module_name, int handles) +{ + int handle = zend_op_array_extension_handles; + zend_op_array_extension_handles += handles; + zend_add_system_entropy(module_name, "zend_get_op_array_extension_handle", &zend_op_array_extension_handles, sizeof(int)); + return handle; } ZEND_API zend_extension *zend_get_extension(const char *extension_name) diff --git a/Zend/zend_extensions.h b/Zend/zend_extensions.h index 6ee44d2072160..45538265b0907 100644 --- a/Zend/zend_extensions.h +++ b/Zend/zend_extensions.h @@ -115,6 +115,7 @@ extern ZEND_API int zend_op_array_extension_handles; ZEND_API int zend_get_resource_handle(const char *module_name); ZEND_API int zend_get_op_array_extension_handle(const char *module_name); +ZEND_API int zend_get_op_array_extension_handles(const char *module_name, int handles); ZEND_API void zend_extension_dispatch_message(int message, void *arg); END_EXTERN_C() diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index b7cc9aea8cdc5..24248d4f7b0b6 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -919,7 +919,7 @@ ZEND_METHOD(Generator, send) root = zend_generator_get_current(generator); /* Put sent value in the target VAR slot, if it is used */ - if (root->send_target) { + if (root->send_target && !(root->flags & ZEND_GENERATOR_CURRENTLY_RUNNING)) { ZVAL_COPY(root->send_target, value); } diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index 54a2d61c38c29..b032d1d4e0b4c 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -182,6 +182,14 @@ ZEND_API void zend_user_it_rewind(zend_object_iterator *_iter) } /* }}} */ +ZEND_API HashTable *zend_user_it_get_gc(zend_object_iterator *_iter, zval **table, int *n) +{ + zend_user_iterator *iter = (zend_user_iterator*)_iter; + *table = &iter->it.data; + *n = 1; + return NULL; +} + static const zend_object_iterator_funcs zend_interface_iterator_funcs_iterator = { zend_user_it_dtor, zend_user_it_valid, @@ -190,7 +198,7 @@ static const zend_object_iterator_funcs zend_interface_iterator_funcs_iterator = zend_user_it_move_forward, zend_user_it_rewind, zend_user_it_invalidate_current, - NULL, /* get_gc */ + zend_user_it_get_gc, }; /* {{{ zend_user_it_get_iterator */ diff --git a/Zend/zend_interfaces.h b/Zend/zend_interfaces.h index 2799e2c438154..a8351ee9a7823 100644 --- a/Zend/zend_interfaces.h +++ b/Zend/zend_interfaces.h @@ -55,6 +55,7 @@ ZEND_API void zend_user_it_get_current_key(zend_object_iterator *_iter, zval *ke ZEND_API zval *zend_user_it_get_current_data(zend_object_iterator *_iter); ZEND_API void zend_user_it_move_forward(zend_object_iterator *_iter); ZEND_API void zend_user_it_invalidate_current(zend_object_iterator *_iter); +ZEND_API HashTable *zend_user_it_get_gc(zend_object_iterator *_iter, zval **table, int *n); ZEND_API void zend_user_it_new_iterator(zend_class_entry *ce, zval *object, zval *iterator); ZEND_API zend_object_iterator *zend_user_it_get_new_iterator(zend_class_entry *ce, zval *object, int by_ref); diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c index 35958aeac5a6c..a8e35d23a6ac4 100644 --- a/Zend/zend_observer.c +++ b/Zend/zend_observer.c @@ -31,13 +31,6 @@ #define ZEND_OBSERVABLE_FN(fn_flags) \ (!(fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) -typedef struct _zend_observer_fcall_data { - // points after the last handler - zend_observer_fcall_handlers *end; - // a variadic array using "struct hack" - zend_observer_fcall_handlers handlers[1]; -} zend_observer_fcall_data; - zend_llist zend_observers_fcall_list; zend_llist zend_observer_error_callbacks; zend_llist zend_observer_fiber_init; @@ -46,33 +39,18 @@ zend_llist zend_observer_fiber_destroy; int zend_observer_fcall_op_array_extension; -ZEND_TLS zend_arena *fcall_handlers_arena; ZEND_TLS zend_execute_data *first_observed_frame; ZEND_TLS zend_execute_data *current_observed_frame; // Call during minit/startup ONLY -ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) { - if (!ZEND_OBSERVER_ENABLED) { - /* We don't want to get an extension handle unless an ext installs an observer */ - zend_observer_fcall_op_array_extension = - zend_get_op_array_extension_handle("Zend Observer"); - - /* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op() - * is called before any extensions have registered as an observer. So we - * adjust the offset to the observed handler when we know we need to observe. */ - ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op)); - - /* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions - * exist when zend_init_exception_op() is called. */ - ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)); - ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+1); - ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)+2); - } +ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init init) +{ zend_llist_add_element(&zend_observers_fcall_list, &init); } // Called by engine before MINITs -ZEND_API void zend_observer_startup(void) { +ZEND_API void zend_observer_startup(void) +{ zend_llist_init(&zend_observers_fcall_list, sizeof(zend_observer_fcall_init), NULL, 1); zend_llist_init(&zend_observer_error_callbacks, sizeof(zend_observer_error_cb), NULL, 1); zend_llist_init(&zend_observer_fiber_init, sizeof(zend_observer_fiber_init_handler), NULL, 1); @@ -82,110 +60,115 @@ ZEND_API void zend_observer_startup(void) { zend_observer_fcall_op_array_extension = -1; } -ZEND_API void zend_observer_activate(void) { - if (ZEND_OBSERVER_ENABLED) { - fcall_handlers_arena = zend_arena_create(4096); - } else { - fcall_handlers_arena = NULL; +ZEND_API void zend_observer_post_startup(void) +{ + if (zend_observers_fcall_list.count) { + /* We don't want to get an extension handle unless an ext installs an observer + * Allocate each a begin and an end pointer */ + zend_observer_fcall_op_array_extension = + zend_get_op_array_extension_handles("Zend Observer", (int) zend_observers_fcall_list.count * 2); + + /* ZEND_CALL_TRAMPOLINE has SPEC(OBSERVER) but zend_init_call_trampoline_op() + * is called before any extensions have registered as an observer. So we + * adjust the offset to the observed handler when we know we need to observe. */ + ZEND_VM_SET_OPCODE_HANDLER(&EG(call_trampoline_op)); + + /* ZEND_HANDLE_EXCEPTION also has SPEC(OBSERVER) and no observer extensions + * exist when zend_init_exception_op() is called. */ + ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op)); + ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 1); + ZEND_VM_SET_OPCODE_HANDLER(EG(exception_op) + 2); } +} + +ZEND_API void zend_observer_activate(void) +{ first_observed_frame = NULL; current_observed_frame = NULL; } -ZEND_API void zend_observer_deactivate(void) { - if (fcall_handlers_arena) { - zend_arena_destroy(fcall_handlers_arena); - } +ZEND_API void zend_observer_deactivate(void) +{ + // now empty and unused, but kept for ABI compatibility } -ZEND_API void zend_observer_shutdown(void) { +ZEND_API void zend_observer_shutdown(void) +{ zend_llist_destroy(&zend_observers_fcall_list); zend_llist_destroy(&zend_observer_error_callbacks); zend_llist_destroy(&zend_observer_fiber_switch); } -static void zend_observer_fcall_install(zend_execute_data *execute_data) { - zend_llist_element *element; +static void zend_observer_fcall_install(zend_execute_data *execute_data) +{ zend_llist *list = &zend_observers_fcall_list; zend_function *function = execute_data->func; zend_op_array *op_array = &function->op_array; - if (fcall_handlers_arena == NULL) { - return; - } - ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION); - zend_llist handlers_list; - zend_llist_init(&handlers_list, sizeof(zend_observer_fcall_handlers), NULL, 0); - for (element = list->head; element; element = element->next) { + ZEND_ASSERT(RUN_TIME_CACHE(op_array)); + zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array); + zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers; + + *begin_handlers = ZEND_OBSERVER_NOT_OBSERVED; + *end_handlers = ZEND_OBSERVER_NOT_OBSERVED; + + for (zend_llist_element *element = list->head; element; element = element->next) { zend_observer_fcall_init init; memcpy(&init, element->data, sizeof init); zend_observer_fcall_handlers handlers = init(execute_data); - if (handlers.begin || handlers.end) { - zend_llist_add_element(&handlers_list, &handlers); + if (handlers.begin) { + *(begin_handlers++) = handlers.begin; } - } - - ZEND_ASSERT(RUN_TIME_CACHE(op_array)); - void *ext; - if (handlers_list.count) { - size_t size = sizeof(zend_observer_fcall_data) + (handlers_list.count - 1) * sizeof(zend_observer_fcall_handlers); - zend_observer_fcall_data *fcall_data = zend_arena_alloc(&fcall_handlers_arena, size); - zend_observer_fcall_handlers *handlers = fcall_data->handlers; - for (element = handlers_list.head; element; element = element->next) { - memcpy(handlers++, element->data, sizeof *handlers); + if (handlers.end) { + *(end_handlers++) = handlers.end; } - fcall_data->end = handlers; - ext = fcall_data; - } else { - ext = ZEND_OBSERVER_NOT_OBSERVED; } - - ZEND_OBSERVER_DATA(op_array) = ext; - zend_llist_destroy(&handlers_list); + + // end handlers are executed in reverse order + for (--end_handlers; end_handlers_start < end_handlers; --end_handlers, ++end_handlers_start) { + zend_observer_fcall_end_handler tmp = *end_handlers; + *end_handlers = *end_handlers_start; + *end_handlers_start = tmp; + } } static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data) { - zend_op_array *op_array; - uint32_t fn_flags; - zend_observer_fcall_data *fcall_data; - zend_observer_fcall_handlers *handlers, *end; - if (!ZEND_OBSERVER_ENABLED) { return; } - op_array = &execute_data->func->op_array; - fn_flags = op_array->fn_flags; + zend_op_array *op_array = &execute_data->func->op_array; + uint32_t fn_flags = op_array->fn_flags; if (!ZEND_OBSERVABLE_FN(fn_flags)) { return; } - fcall_data = ZEND_OBSERVER_DATA(op_array); - if (!fcall_data) { + zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array); + if (!*handler) { zend_observer_fcall_install(execute_data); - fcall_data = ZEND_OBSERVER_DATA(op_array); } - ZEND_ASSERT(fcall_data); - if (fcall_data == ZEND_OBSERVER_NOT_OBSERVED) { - return; - } + zend_observer_fcall_begin_handler *possible_handlers_end = handler + zend_observers_fcall_list.count; - if (first_observed_frame == NULL) { - first_observed_frame = execute_data; + zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)possible_handlers_end; + if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) { + if (first_observed_frame == NULL) { + first_observed_frame = execute_data; + } + current_observed_frame = execute_data; } - current_observed_frame = execute_data; - end = fcall_data->end; - for (handlers = fcall_data->handlers; handlers != end; ++handlers) { - if (handlers->begin) { - handlers->begin(execute_data); - } + if (*handler == ZEND_OBSERVER_NOT_OBSERVED) { + return; } + + do { + (*handler)(execute_data); + } while (++handler != possible_handlers_end && *handler != NULL); } ZEND_API void ZEND_FASTCALL zend_observer_generator_resume(zend_execute_data *execute_data) @@ -201,43 +184,48 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute } } -ZEND_API void ZEND_FASTCALL zend_observer_fcall_end( - zend_execute_data *execute_data, - zval *return_value) +static inline bool zend_observer_is_skipped_frame(zend_execute_data *execute_data) { + zend_function *func = execute_data->func; + + if (!func || func->type == ZEND_INTERNAL_FUNCTION || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) { + return true; + } + + zend_observer_fcall_end_handler end_handler = (&ZEND_OBSERVER_DATA(&func->op_array))[zend_observers_fcall_list.count]; + if (end_handler == NULL || end_handler == ZEND_OBSERVER_NOT_OBSERVED) { + return true; + } + + return false; +} + +ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_data, zval *return_value) { zend_function *func = execute_data->func; - zend_observer_fcall_data *fcall_data; - zend_observer_fcall_handlers *handlers, *end; if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) { return; } - fcall_data = (zend_observer_fcall_data*)ZEND_OBSERVER_DATA(&func->op_array); + zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(&func->op_array) + zend_observers_fcall_list.count; // TODO: Fix exceptions from generators // ZEND_ASSERT(fcall_data); - if (!fcall_data || fcall_data == ZEND_OBSERVER_NOT_OBSERVED) { + if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) { return; } - handlers = fcall_data->end; - end = fcall_data->handlers; - while (handlers-- != end) { - if (handlers->end) { - handlers->end(execute_data, return_value); - } - } + zend_observer_fcall_end_handler *possible_handlers_end = handler + zend_observers_fcall_list.count; + do { + (*handler)(execute_data, return_value); + } while (++handler != possible_handlers_end && *handler != NULL); if (first_observed_frame == execute_data) { first_observed_frame = NULL; current_observed_frame = NULL; } else { zend_execute_data *ex = execute_data->prev_execute_data; - while (ex && (!ex->func || ex->func->type == ZEND_INTERNAL_FUNCTION - || !ZEND_OBSERVABLE_FN(ex->func->common.fn_flags) - || !ZEND_OBSERVER_DATA(&ex->func->op_array) - || ZEND_OBSERVER_DATA(&ex->func->op_array) == ZEND_OBSERVER_NOT_OBSERVED)) { + while (ex && zend_observer_is_skipped_frame(ex)) { ex = ex->prev_execute_data; } current_observed_frame = ex; @@ -253,7 +241,6 @@ ZEND_API void zend_observer_fcall_end_all(void) } ex = ex->prev_execute_data; } - current_observed_frame = NULL; } ZEND_API void zend_observer_error_register(zend_observer_error_cb cb) @@ -263,11 +250,8 @@ ZEND_API void zend_observer_error_register(zend_observer_error_cb cb) void zend_observer_error_notify(int type, zend_string *error_filename, uint32_t error_lineno, zend_string *message) { - zend_llist_element *element; - zend_observer_error_cb callback; - - for (element = zend_observer_error_callbacks.head; element; element = element->next) { - callback = *(zend_observer_error_cb *) (element->data); + for (zend_llist_element *element = zend_observer_error_callbacks.head; element; element = element->next) { + zend_observer_error_cb callback = *(zend_observer_error_cb *) (element->data); callback(type, error_filename, error_lineno, message); } } diff --git a/Zend/zend_observer.h b/Zend/zend_observer.h index 8df0ed3247526..b43ff1e4f6557 100644 --- a/Zend/zend_observer.h +++ b/Zend/zend_observer.h @@ -57,6 +57,7 @@ typedef zend_observer_fcall_handlers (*zend_observer_fcall_init)(zend_execute_da ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init); ZEND_API void zend_observer_startup(void); // Called by engine before MINITs +ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs ZEND_API void zend_observer_activate(void); ZEND_API void zend_observer_deactivate(void); ZEND_API void zend_observer_shutdown(void); diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 73802757b5e43..2489cd40072e3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -27,22 +27,11 @@ schedules: - master jobs: - - template: azure/job.yml - parameters: - configurationName: DEBUG_NTS - configurationParameters: '--enable-debug --disable-zts' - - template: azure/job.yml - parameters: - configurationName: RELEASE_ZTS - configurationParameters: '--disable-debug --enable-zts' - - template: azure/i386/job.yml - parameters: - configurationName: I386_DEBUG_ZTS - configurationParameters: '--enable-debug --enable-zts' - - template: azure/macos/job.yml - parameters: - configurationName: MACOS_DEBUG_NTS - configurationParameters: '--enable-debug --disable-zts' + # Azure pipelines don't work atm + # - template: azure/i386/job.yml + # parameters: + # configurationName: I386_DEBUG_ZTS + # configurationParameters: '--enable-debug --enable-zts' - ${{ if eq(variables['Build.Reason'], 'Schedule') }}: - template: azure/job.yml parameters: diff --git a/build/Makefile.global b/build/Makefile.global index 6941bab6ad412..f93a874ce1b57 100644 --- a/build/Makefile.global +++ b/build/Makefile.global @@ -148,6 +148,9 @@ prof-use: if test ! -z "$(PHP)"; then \ echo Parse $< to generate $@;\ $(PHP) $(top_srcdir)/build/gen_stub.php $<; \ + elif test ! -z "$(PHP_EXECUTABLE)" && test -x "$(PHP_EXECUTABLE)"; then \ + echo Parse $< to generate $@;\ + $(PHP_EXECUTABLE) $(top_srcdir)/build/gen_stub.php $<; \ fi; \ fi; diff --git a/build/gen_stub.php b/build/gen_stub.php index 486ff679499fc..348cc9e82d277 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1748,7 +1748,7 @@ function (Name $item) { } if ($this->alias) { - $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "_", $this->alias) . "\", class_entry);\n"; + $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n"; } foreach ($this->enumCaseInfos as $enumCase) { diff --git a/configure.ac b/configure.ac index 4c111b8a9b3dd..b54abe326b8c6 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.1.3-dev],[https://bugs.php.net],[php],[https://www.php.net]) +AC_INIT([PHP],[8.1.4],[https://bugs.php.net],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index 108e8e3259c78..91bf929a9d00e 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -444,10 +444,10 @@ void php_filter_float(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */ switch (is_numeric_string(num, p - num, &lval, &dval, 0)) { case IS_LONG: - zval_ptr_dtor(value); if ((min_range_set && (lval < min_range)) || (max_range_set && (lval > max_range))) { goto error; } + zval_ptr_dtor(value); ZVAL_DOUBLE(value, (double)lval); break; case IS_DOUBLE: diff --git a/ext/filter/tests/bug81708.phpt b/ext/filter/tests/bug81708.phpt new file mode 100644 index 0000000000000..5f99ae4afd19a --- /dev/null +++ b/ext/filter/tests/bug81708.phpt @@ -0,0 +1,18 @@ +--TEST-- +Bug #81708 (UAF due to php_filter_float() failing for ints) +--EXTENSIONS-- +filter +--INI-- +opcache.enable_cli=0 +--FILE-- + ['min_range' => -1, 'max_range' => 1]] +); +var_dump($input); +?> +--EXPECT-- +string(3) "+11" diff --git a/ext/gd/libgd/gd_png.c b/ext/gd/libgd/gd_png.c index b8a1fc3083b06..655eee77b9063 100644 --- a/ext/gd/libgd/gd_png.c +++ b/ext/gd/libgd/gd_png.c @@ -336,6 +336,11 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) break; } + /* enable the interlace transform if supported */ +#ifdef PNG_READ_INTERLACING_SUPPORTED + (void)png_set_interlace_handling(png_ptr); +#endif + png_read_update_info(png_ptr, info_ptr); /* allocate space for the PNG image data */ diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c index 4850c00624232..39de76a5e4243 100644 --- a/ext/iconv/iconv.c +++ b/ext/iconv/iconv.c @@ -311,7 +311,7 @@ static int php_iconv_output_handler(void **nothing, php_output_context *output_c mimetype = SG(default_mimetype) ? SG(default_mimetype) : SAPI_DEFAULT_MIMETYPE; } - if (mimetype != NULL && !(output_context->op & PHP_OUTPUT_HANDLER_CLEAN)) { + if (mimetype != NULL && (!(output_context->op & PHP_OUTPUT_HANDLER_CLEAN) || (output_context->op & PHP_OUTPUT_HANDLER_START))) { size_t len; char *p = strstr(get_output_encoding(), "//"); @@ -1578,6 +1578,9 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st } err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd); + if (err == PHP_ICONV_ERR_SUCCESS) { + err = _php_iconv_appendl(pretval, NULL, 0, cd); + } zend_string_release_ex(decoded_text, 0); if (err != PHP_ICONV_ERR_SUCCESS) { @@ -1718,13 +1721,6 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st *next_pos = p1; } - if (cd != (iconv_t)(-1)) { - _php_iconv_appendl(pretval, NULL, 0, cd); - } - if (cd_pl != (iconv_t)(-1)) { - _php_iconv_appendl(pretval, NULL, 0, cd_pl); - } - smart_str_0(pretval); out: if (cd != (iconv_t)(-1)) { diff --git a/ext/iconv/tests/gh7953.phpt b/ext/iconv/tests/gh7953.phpt new file mode 100644 index 0000000000000..10bab494ca675 --- /dev/null +++ b/ext/iconv/tests/gh7953.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-7953 (ob_clean() only may not set Content-* header) +--EXTENSIONS-- +iconv +--INI-- +input_encoding=UTF-8 +output_encoding=ISO-8859-1 +--CGI-- +--FILE-- + +--EXPECTF-- +%a +--EXPECTHEADERS-- +Content-Type: text/html; charset=ISO-8859-1 diff --git a/ext/iconv/tests/gh7980.phpt b/ext/iconv/tests/gh7980.phpt new file mode 100644 index 0000000000000..11be9e129e02b --- /dev/null +++ b/ext/iconv/tests/gh7980.phpt @@ -0,0 +1,11 @@ +--TEST-- +Bug GH-7980 (Unexpected result for iconv_mime_decode) +--EXTENSIONS-- +iconv +--FILE-- +'; +var_dump(iconv_mime_decode($subject, ICONV_MIME_DECODE_STRICT, 'UTF-8')); +?> +--EXPECT-- +string(57) "DSI Chargé de Formation Jean Dupont " diff --git a/ext/intl/uchar/uchar.c b/ext/intl/uchar/uchar.c index 149446706724e..43fe38314fbb2 100644 --- a/ext/intl/uchar/uchar.c +++ b/ext/intl/uchar/uchar.c @@ -38,7 +38,7 @@ static inline int convert_cp(UChar32* pcp, zend_string *string_codepoint, zend_l static zend_never_inline int parse_code_point_param(INTERNAL_FUNCTION_PARAMETERS, UChar32 *cp) { zend_string *string_codepoint; - zend_long int_codepoint; + zend_long int_codepoint = 0; ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR_OR_LONG(string_codepoint, int_codepoint) ZEND_PARSE_PARAMETERS_END_EX(return FAILURE); diff --git a/ext/mbstring/libmbfl/filters/mbfilter_7bit.c b/ext/mbstring/libmbfl/filters/mbfilter_7bit.c index dbf8993128472..a3a90c96f7a4a 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_7bit.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_7bit.c @@ -67,7 +67,7 @@ const struct mbfl_convert_vtbl vtbl_7bit_8bit = { int mbfl_filt_conv_7bit_any(int c, mbfl_convert_filter *filter) { - return (*filter->output_function)(c, filter->data); + return (*filter->output_function)(c < 0x80 ? c : MBFL_BAD_INPUT, filter->data); } diff --git a/ext/mbstring/tests/other_encodings.phpt b/ext/mbstring/tests/other_encodings.phpt index 321eccb247b6f..0cdaad43d8826 100644 --- a/ext/mbstring/tests/other_encodings.phpt +++ b/ext/mbstring/tests/other_encodings.phpt @@ -14,10 +14,11 @@ mb_substitute_character(0x25); var_dump(mb_convert_encoding("ABC", "7bit", "ASCII")); var_dump(mb_convert_encoding("\x80", "7bit", "ASCII")); var_dump(mb_convert_encoding("ABC", "8bit", "7bit")); +var_dump(mb_check_encoding(chr(255), '7bit')); echo "7bit done\n"; // "8bit" -var_dump(mb_convert_encoding("\x01\x00", "8bit", "UTF-16BE")); // codepoints over 0xFF are illegal or '8-bit' +var_dump(mb_convert_encoding("\x01\x00", "8bit", "UTF-16BE")); // codepoints over 0xFF are illegal for '8-bit' echo "8bit done\n"; // UCS-2 @@ -41,6 +42,7 @@ echo "UCS-4 done\n"; string(3) "ABC" string(1) "%" string(3) "ABC" +bool(false) 7bit done string(1) "%" 8bit done diff --git a/ext/mysqli/tests/gh8058.phpt b/ext/mysqli/tests/gh8058.phpt new file mode 100644 index 0000000000000..5480682e93fae --- /dev/null +++ b/ext/mysqli/tests/gh8058.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-8058 (NULL pointer dereference in mysqlnd package (#81706)) +--EXTENSIONS-- +mysqli +--SKIPIF-- + +--FILE-- +prepare("select 1,2,3"); +$stmt->bind_result($a,$a,$a); +$stmt->prepare(""); +$stmt->prepare("select ".str_repeat("'A',", 0x1201)."1"); +unset($stmt); // trigger dtor + +// There should be no memory leak +$stmt = $mysqli->prepare("select 1,2,3"); +$stmt->bind_result($a,$a,$a); +$stmt->prepare(""); +$stmt->prepare("select 1"); +unset($stmt); // trigger dtor + +mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); +$stmt = $mysqli->prepare("select 1,2,3"); +try { + // We expect an exception to be thrown + $stmt->prepare(""); +} catch (mysqli_sql_exception $e) { + var_dump($e->getMessage()); +} +?> +--EXPECT-- +string(15) "Query was empty" diff --git a/ext/mysqli/tests/mysqli_stmt_affected_rows.phpt b/ext/mysqli/tests/mysqli_stmt_affected_rows.phpt index d3baf3e66ec8d..0dc070ee5089d 100644 --- a/ext/mysqli/tests/mysqli_stmt_affected_rows.phpt +++ b/ext/mysqli/tests/mysqli_stmt_affected_rows.phpt @@ -197,18 +197,13 @@ require_once('skipifconnectfailure.inc'); /* stmt_affected_rows is not really meant for SELECT! */ if (mysqli_stmt_prepare($stmt, 'SELECT unknown_column FROM this_table_does_not_exist') || mysqli_stmt_execute($stmt)) - printf("[041] The invalid SELECT statement is issued on purpose\n"); + printf("[041] Expecting SELECT statement to fail on purpose\n"); if (-1 !== ($tmp = mysqli_stmt_affected_rows($stmt))) printf("[042] Expecting int/-1, got %s/%s\n", gettype($tmp), $tmp); - if ($IS_MYSQLND) { - if (false !== ($tmp = mysqli_stmt_store_result($stmt))) - printf("[043] Expecting boolean/false, got %s\%s\n", gettype($tmp), $tmp); - } else { - if (true !== ($tmp = mysqli_stmt_store_result($stmt))) - printf("[043] Libmysql does not care if the previous statement was bogus, expecting boolean/true, got %s\%s\n", gettype($tmp), $tmp); - } + if (true !== ($tmp = mysqli_stmt_store_result($stmt))) + printf("[043] Expecting boolean/true, got %s/%s\n", gettype($tmp), $tmp); if (0 !== ($tmp = mysqli_stmt_num_rows($stmt))) printf("[044] Expecting int/0, got %s/%s\n", gettype($tmp), $tmp); diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index 54426700c374b..a7bff8051f731 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -369,12 +369,10 @@ mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s) /* {{{ mysqlnd_stmt::prepare */ static enum_func_status -MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const query, const size_t query_len) +MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * s, const char * const query, const size_t query_len) { MYSQLND_STMT_DATA * stmt = s? s->data : NULL; MYSQLND_CONN_DATA * conn = stmt? stmt->conn : NULL; - MYSQLND_STMT * s_to_prepare = s; - MYSQLND_STMT_DATA * stmt_to_prepare = stmt; DBG_ENTER("mysqlnd_stmt::prepare"); if (!stmt || !conn) { @@ -390,25 +388,15 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const SET_EMPTY_ERROR(conn->error_info); if (stmt->state > MYSQLND_STMT_INITTED) { - /* See if we have to clean the wire */ - if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { - /* Do implicit use_result and then flush the result */ - stmt->default_rset_handler = s->m->use_result; - stmt->default_rset_handler(s); - } - /* No 'else' here please :) */ - if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE && stmt->result) { - stmt->result->m.skip_result(stmt->result); - } /* - Create a new test statement, which we will prepare, but if anything - fails, we will scrap it. + Create a new prepared statement and destroy the previous one. */ - s_to_prepare = conn->m->stmt_init(conn); - if (!s_to_prepare) { + s->m->dtor(s, TRUE); + s = conn->m->stmt_init(conn); + if (!s) { goto fail; } - stmt_to_prepare = s_to_prepare->data; + stmt = s->data; } { @@ -422,13 +410,13 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const } } - if (FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare)) { + if (FAIL == mysqlnd_stmt_read_prepare_response(s)) { goto fail; } - if (stmt_to_prepare->param_count) { - if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare) || - FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare)) + if (stmt->param_count) { + if (FAIL == mysqlnd_stmt_skip_metadata(s) || + FAIL == mysqlnd_stmt_prepare_read_eof(s)) { goto fail; } @@ -439,51 +427,31 @@ MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const Beware that SHOW statements bypass the PS framework and thus they send no metadata at prepare. */ - if (stmt_to_prepare->field_count) { - MYSQLND_RES * result = conn->m->result_init(stmt_to_prepare->field_count); + if (stmt->field_count) { + MYSQLND_RES * result = conn->m->result_init(stmt->field_count); if (!result) { SET_OOM_ERROR(conn->error_info); goto fail; } /* Allocate the result now as it is needed for the reading of metadata */ - stmt_to_prepare->result = result; + stmt->result = result; result->conn = conn->m->get_reference(conn); result->type = MYSQLND_RES_PS_BUF; if (FAIL == result->m.read_result_metadata(result, conn) || - FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare)) + FAIL == mysqlnd_stmt_prepare_read_eof(s)) { goto fail; } } - if (stmt_to_prepare != stmt) { - /* swap */ - size_t real_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *); - char * tmp_swap = mnd_emalloc(real_size); - memcpy(tmp_swap, s, real_size); - memcpy(s, s_to_prepare, real_size); - memcpy(s_to_prepare, tmp_swap, real_size); - mnd_efree(tmp_swap); - { - MYSQLND_STMT_DATA * tmp_swap_data = stmt_to_prepare; - stmt_to_prepare = stmt; - stmt = tmp_swap_data; - } - s_to_prepare->m->dtor(s_to_prepare, TRUE); - } stmt->state = MYSQLND_STMT_PREPARED; DBG_INF("PASS"); DBG_RETURN(PASS); fail: - if (stmt_to_prepare != stmt && s_to_prepare) { - s_to_prepare->m->dtor(s_to_prepare, TRUE); - } - stmt->state = MYSQLND_STMT_INITTED; - DBG_INF("FAIL"); DBG_RETURN(FAIL); } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 8f6a5aa42b920..c9ba5cf5551fe 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -2185,6 +2185,9 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) zend_hash_index_del(EG(zend_constants), new_const_num); } } + persistent_script->dynamic_members.last_used = ZCG(request_time); + SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); } else { #if !ZEND_WIN32 @@ -2221,15 +2224,14 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) } } } + persistent_script->dynamic_members.last_used = ZCG(request_time); + SHM_PROTECT(); + HANDLE_UNBLOCK_INTERRUPTIONS(); + replay_warnings(persistent_script->num_warnings, persistent_script->warnings); from_shared_memory = 1; } - persistent_script->dynamic_members.last_used = ZCG(request_time); - - SHM_PROTECT(); - HANDLE_UNBLOCK_INTERRUPTIONS(); - /* Fetch jit auto globals used in the script before execution */ if (persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask)) { zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask)); diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index fbc9c364484da..d9eaaf5794431 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -170,8 +170,8 @@ static bool zend_ssa_is_last_use(const zend_op_array *op_array, const zend_ssa * } while (phi); } - next_use = zend_ssa_next_use(ssa->ops, var, use); - if (next_use < 0) { + if (ssa->cfg.blocks[ssa->cfg.map[use]].loop_header > 0 + || (ssa->cfg.blocks[ssa->cfg.map[use]].flags & ZEND_BB_LOOP_HEADER)) { int b = ssa->cfg.map[use]; int prev_use = ssa->vars[var].use_chain; @@ -183,6 +183,10 @@ static bool zend_ssa_is_last_use(const zend_op_array *op_array, const zend_ssa * } prev_use = zend_ssa_next_use(ssa->ops, var, prev_use); } + } + + next_use = zend_ssa_next_use(ssa->ops, var, use); + if (next_use < 0) { return 1; } else if (zend_ssa_is_no_val_use(op_array->opcodes + next_use, ssa->ops + next_use, var)) { return 1; @@ -3306,6 +3310,9 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op op2_def_addr = op2_addr; } op1_info = OP1_INFO(); + if (ra && ssa->vars[ssa_op->op1_use].no_val) { + op1_info |= MAY_BE_UNDEF; // requres type assignment + } if (opline->result_type == IS_UNUSED) { res_addr = 0; res_info = -1; diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index 6d6bc471ec27e..9f3e358346dca 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -3560,6 +3560,14 @@ static int zend_jit_store_var(dasm_State **Dst, uint32_t info, int var, zend_reg return zend_jit_spill_store(Dst, src, dst, info, set_type); } +static int zend_jit_store_var_type(dasm_State **Dst, int var, uint32_t type) +{ + zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var)); + + | SET_ZVAL_TYPE_INFO dst, type, TMP1w, TMP2 + return 1; +} + static int zend_jit_store_var_if_necessary(dasm_State **Dst, int var, zend_jit_addr src, uint32_t info) { if (Z_MODE(src) == IS_REG && Z_STORE(src)) { @@ -3816,7 +3824,7 @@ static int zend_jit_inc_dec(dasm_State **Dst, const zend_op *opline, uint32_t op | LOAD_64BIT_VAL TMP1, val | fmov Rd(Z_REG(op1_def_addr)-ZREG_V0), TMP1 } else { - | SET_ZVAL_LVAL op1_def_addr, val, REG0, TMP1 + | SET_ZVAL_LVAL op1_def_addr, val, TMP2, TMP1 } } else { uint64_t val = 0xc3e0000000000000; @@ -3824,7 +3832,7 @@ static int zend_jit_inc_dec(dasm_State **Dst, const zend_op *opline, uint32_t op | LOAD_64BIT_VAL TMP1, val | fmov Rd(Z_REG(op1_def_addr)-ZREG_V0), TMP1 } else { - | SET_ZVAL_LVAL op1_def_addr, val, REG0, TMP1 + | SET_ZVAL_LVAL op1_def_addr, val, TMP2, TMP1 } } if (Z_MODE(op1_def_addr) == IS_MEM_ZVAL) { @@ -4155,7 +4163,7 @@ static int zend_jit_math_long_long(dasm_State **Dst, | LOAD_64BIT_VAL TMP1, val | fmov Rd(Z_REG(res_addr)-ZREG_V0), TMP1 } else { - | SET_ZVAL_LVAL res_addr, val, REG0, TMP1 + | SET_ZVAL_LVAL res_addr, val, TMP2, TMP1 } break; } else if (opcode == ZEND_SUB) { @@ -4164,7 +4172,7 @@ static int zend_jit_math_long_long(dasm_State **Dst, | LOAD_64BIT_VAL TMP1, val | fmov Rd(Z_REG(res_addr)-ZREG_V0), TMP1 } else { - | SET_ZVAL_LVAL res_addr, val, REG0, TMP1 + | SET_ZVAL_LVAL res_addr, val, TMP2, TMP1 } break; } @@ -6317,7 +6325,12 @@ static int zend_jit_assign_dim_op(dasm_State **Dst, const zend_op *opline, uint3 | LOAD_ZVAL_ADDR FCARG2x, op3_addr | LOAD_ADDR CARG3, binary_op | SET_EX_OPLINE opline, REG0 - | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR)) + && (op1_data_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, REG0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + } | b >9 |.code |1: @@ -6438,7 +6451,12 @@ static int zend_jit_assign_op(dasm_State **Dst, const zend_op *opline, uint32_t | LOAD_ZVAL_ADDR FCARG2x, op2_addr | LOAD_ADDR CARG3, binary_op | SET_EX_OPLINE opline, REG0 - | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) + && (op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, REG0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + } zend_jit_check_exception(Dst); | b >9 |.code @@ -12942,20 +12960,20 @@ static int zend_jit_incdec_obj(dasm_State **Dst, |3: if (opline->opcode == ZEND_PRE_INC_OBJ || opline->opcode == ZEND_POST_INC_OBJ) { uint64_t val = 0x43e0000000000000; - | LOAD_64BIT_VAL REG0, val - | SET_ZVAL_LVAL_FROM_REG var_addr, REG0, TMP1 + | LOAD_64BIT_VAL TMP2, val + | SET_ZVAL_LVAL_FROM_REG var_addr, TMP2, TMP1 | SET_ZVAL_TYPE_INFO var_addr, IS_DOUBLE, TMP1w, TMP2 if (opline->opcode == ZEND_PRE_INC_OBJ && opline->result_type != IS_UNUSED) { - | SET_ZVAL_LVAL_FROM_REG res_addr, REG0, TMP1 + | SET_ZVAL_LVAL_FROM_REG res_addr, TMP2, TMP1 | SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE, TMP1w, TMP2 } } else { uint64_t val = 0xc3e0000000000000; - | LOAD_64BIT_VAL REG0, val - | SET_ZVAL_LVAL_FROM_REG var_addr, REG0, TMP1 + | LOAD_64BIT_VAL TMP2, val + | SET_ZVAL_LVAL_FROM_REG var_addr, TMP2, TMP1 | SET_ZVAL_TYPE_INFO var_addr, IS_DOUBLE, TMP1w, TMP2 if (opline->opcode == ZEND_PRE_DEC_OBJ && opline->result_type != IS_UNUSED) { - | SET_ZVAL_LVAL_FROM_REG res_addr, REG0, TMP1 + | SET_ZVAL_LVAL_FROM_REG res_addr, TMP2, TMP1 | SET_ZVAL_TYPE_INFO res_addr, IS_DOUBLE, TMP1w, TMP2 } } @@ -13223,7 +13241,12 @@ static int zend_jit_assign_obj_op(dasm_State **Dst, | LOAD_ZVAL_ADDR FCARG2x, val_addr } | LOAD_ADDR CARG3, binary_op - | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) + && (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, REG0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + } | b >9 |.code @@ -13284,7 +13307,12 @@ static int zend_jit_assign_obj_op(dasm_State **Dst, | SET_EX_OPLINE opline, REG0 } | LOAD_ADDR CARG3, binary_op - | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR)) + && (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, REG0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, REG0 + } | b >9 |.code |2: diff --git a/ext/opcache/jit/zend_jit_disasm.c b/ext/opcache/jit/zend_jit_disasm.c index 9adc058ec8dee..408af448ee200 100644 --- a/ext/opcache/jit/zend_jit_disasm.c +++ b/ext/opcache/jit/zend_jit_disasm.c @@ -664,6 +664,7 @@ static int zend_jit_disasm_init(void) REGISTER_HELPER(zend_jit_post_inc_typed_ref); REGISTER_HELPER(zend_jit_post_dec_typed_ref); REGISTER_HELPER(zend_jit_assign_op_to_typed_ref); + REGISTER_HELPER(zend_jit_assign_op_to_typed_ref_tmp); REGISTER_HELPER(zend_jit_only_vars_by_reference); REGISTER_HELPER(zend_jit_invalid_array_access); REGISTER_HELPER(zend_jit_invalid_property_read); diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 5a9251d8650f2..3c9ae80e86e4d 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2321,6 +2321,20 @@ static void ZEND_FASTCALL zend_jit_assign_op_to_typed_ref(zend_reference *ref, z } } +static void ZEND_FASTCALL zend_jit_assign_op_to_typed_ref_tmp(zend_reference *ref, zval *val, binary_op_type binary_op) +{ + zval z_copy; + + binary_op(&z_copy, &ref->val, val); + if (EXPECTED(zend_verify_ref_assignable_zval(ref, &z_copy, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data))))) { + zval_ptr_dtor(&ref->val); + ZVAL_COPY_VALUE(&ref->val, &z_copy); + } else { + zval_ptr_dtor(&z_copy); + } + zval_ptr_dtor_nogc(val); +} + static void ZEND_FASTCALL zend_jit_only_vars_by_reference(zval *arg) { ZVAL_NEW_REF(arg, arg); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 177c7478b3dbe..892902a4e03a6 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -4157,7 +4157,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } info &= ~MAY_BE_GUARD; ssa->var_info[phi->ssa_var].type = info; - SET_STACK_TYPE(stack, i, concrete_type(info), 1); + SET_STACK_TYPE(stack, phi->var, concrete_type(info), 1); } SET_STACK_REG_EX(stack, phi->var, ival->reg, ZREG_LOAD); if (!zend_jit_load_var(&dasm_state, ssa->var_info[phi->ssa_var].type, ssa->vars[phi->ssa_var].var, ival->reg)) { @@ -6704,6 +6704,22 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par if (p->stop == ZEND_JIT_TRACE_STOP_LOOP || p->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL || p->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) { + if (ra) { + zend_ssa_phi *phi = ssa->blocks[1].phis; + + while (phi) { + if (ra[phi->ssa_var] + && ra[phi->sources[1]] + && STACK_MEM_TYPE(stack, phi->var) != STACK_TYPE(stack, phi->var) + && (ra[phi->ssa_var]->flags & (ZREG_LOAD|ZREG_STORE)) == 0 + && (ra[phi->sources[1]]->flags & (ZREG_LOAD|ZREG_STORE)) == 0) { + /* Store actual type to memory to avoid deoptimization mistakes */ + /* TODO: Alternatively, we may try to update alredy generated deoptimization info */ + zend_jit_store_var_type(&dasm_state, phi->var, STACK_TYPE(stack, phi->var)); + } + phi = phi->next; + } + } if (p->stop != ZEND_JIT_TRACE_STOP_RECURSIVE_RET) { if ((t->flags & ZEND_JIT_TRACE_USES_INITIAL_IP) && !zend_jit_set_ip(&dasm_state, p->opline)) { diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 2de49f11f24db..81b5f531621e3 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -3900,6 +3900,14 @@ static int zend_jit_store_var(dasm_State **Dst, uint32_t info, int var, zend_reg return zend_jit_spill_store(Dst, src, dst, info, set_type); } +static int zend_jit_store_var_type(dasm_State **Dst, int var, uint32_t type) +{ + zend_jit_addr dst = ZEND_ADDR_MEM_ZVAL(ZREG_FP, EX_NUM_TO_VAR(var)); + + | SET_ZVAL_TYPE_INFO dst, type + return 1; +} + static int zend_jit_store_var_if_necessary(dasm_State **Dst, int var, zend_jit_addr src, uint32_t info) { if (Z_MODE(src) == IS_REG && Z_STORE(src)) { @@ -4490,11 +4498,11 @@ static int zend_jit_math_long_long(dasm_State **Dst, (Z_MODE(op2_addr) == IS_CONST_ZVAL && Z_LVAL_P(Z_ZV(op2_addr)) == 1))) { if (opcode == ZEND_ADD) { |.if X64 - | mov64 rax, 0x43e0000000000000 + | mov64 Ra(tmp_reg), 0x43e0000000000000 if (Z_MODE(res_addr) == IS_REG) { - | movd xmm(Z_REG(res_addr)-ZREG_XMM0), rax + | movd xmm(Z_REG(res_addr)-ZREG_XMM0), Ra(tmp_reg) } else { - | SET_ZVAL_LVAL res_addr, rax + | SET_ZVAL_LVAL res_addr, Ra(tmp_reg) } |.else | SET_ZVAL_LVAL res_addr, 0 @@ -4503,11 +4511,11 @@ static int zend_jit_math_long_long(dasm_State **Dst, break; } else if (opcode == ZEND_SUB) { |.if X64 - | mov64 rax, 0xc3e0000000000000 + | mov64 Ra(tmp_reg), 0xc3e0000000000000 if (Z_MODE(res_addr) == IS_REG) { - | movd xmm(Z_REG(res_addr)-ZREG_XMM0), rax + | movd xmm(Z_REG(res_addr)-ZREG_XMM0), Ra(tmp_reg) } else { - | SET_ZVAL_LVAL res_addr, rax + | SET_ZVAL_LVAL res_addr, Ra(tmp_reg) } |.else | SET_ZVAL_LVAL res_addr, 0x00200000 @@ -6855,7 +6863,12 @@ static int zend_jit_assign_dim_op(dasm_State **Dst, const zend_op *opline, uint3 | PUSH_ADDR binary_op, r0 |.endif | SET_EX_OPLINE opline, r0 - | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR)) + && (op1_data_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + } |.if not(X64) | add r4, 12 |.endif @@ -6993,7 +7006,12 @@ static int zend_jit_assign_op(dasm_State **Dst, const zend_op *opline, uint32_t | PUSH_ADDR binary_op, r0 |.endif | SET_EX_OPLINE opline, r0 - | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + if ((opline->op2_type & (IS_TMP_VAR|IS_VAR)) + && (op2_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + } |.if not(X64) | add r4, 12 |.endif @@ -13998,7 +14016,12 @@ static int zend_jit_assign_obj_op(dasm_State **Dst, | sub r4, 12 | PUSH_ADDR binary_op, r0 |.endif - | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR)) + && (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + } |.if not(X64) | add r4, 12 |.endif @@ -14077,7 +14100,12 @@ static int zend_jit_assign_obj_op(dasm_State **Dst, | sub r4, 12 | PUSH_ADDR binary_op, r0 |.endif - | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + if (((opline+1)->op1_type & (IS_TMP_VAR|IS_VAR)) + && (val_info & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) { + | EXT_CALL zend_jit_assign_op_to_typed_ref_tmp, r0 + } else { + | EXT_CALL zend_jit_assign_op_to_typed_ref, r0 + } |.if not(X64) | add r4, 12 |.endif diff --git a/ext/opcache/tests/jit/assign_051.phpt b/ext/opcache/tests/jit/assign_051.phpt new file mode 100644 index 0000000000000..88264161d4af5 --- /dev/null +++ b/ext/opcache/tests/jit/assign_051.phpt @@ -0,0 +1,23 @@ +--TEST-- +JIT ASSIGN: incorrect assignment optimization +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +opcache.protect_memory=1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Undefined constant "y" in %sassign_051.php:3 +Stack trace: +#0 %sassign_051.php(7): foo(0) +#1 {main} + thrown in %sassign_051.php on line 3 diff --git a/ext/opcache/tests/jit/assign_dim_012.phpt b/ext/opcache/tests/jit/assign_dim_012.phpt new file mode 100644 index 0000000000000..0753d93618213 --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_012.phpt @@ -0,0 +1,18 @@ +--TEST-- +JIT ASSIGN_DIM: 012 +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- + +DONE +--EXPECTF-- +DONE diff --git a/ext/opcache/tests/jit/assign_dim_013.phpt b/ext/opcache/tests/jit/assign_dim_013.phpt new file mode 100644 index 0000000000000..49907b358a21a --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_013.phpt @@ -0,0 +1,26 @@ +--TEST-- +JIT ASSIGN_DIM: 013 +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- +y = set_error_handler(function(){}); + foreach($obj as $y) { + } + $arr = ['' => y]; + } +} +test(); +?> +--EXPECTF-- +Fatal error: Uncaught Error: Undefined constant "y" in %sassign_dim_013.php:8 +Stack trace: +#0 %sassign_dim_013.php(11): test() +#1 {main} + thrown in %sassign_dim_013.php on line 8 \ No newline at end of file diff --git a/ext/opcache/tests/jit/assign_dim_op_007.phpt b/ext/opcache/tests/jit/assign_dim_op_007.phpt new file mode 100644 index 0000000000000..e8708a1255131 --- /dev/null +++ b/ext/opcache/tests/jit/assign_dim_op_007.phpt @@ -0,0 +1,21 @@ +--TEST-- +JIT ASSIGN_DIM_OP: overflow +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- +member = 9223372036854775807; + $this->member += 1; + } +} +new test(); +?> +DONE +--EXPECT-- +DONE diff --git a/ext/opcache/tests/jit/assign_obj_op_002.phpt b/ext/opcache/tests/jit/assign_obj_op_002.phpt new file mode 100644 index 0000000000000..3d6ac3f897bff --- /dev/null +++ b/ext/opcache/tests/jit/assign_obj_op_002.phpt @@ -0,0 +1,31 @@ +--TEST-- +JIT ASSIGN_OBJ_OP: memory leak +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- +prop .= $a->prop . "leak"; + return "test"; + } +} + +$a = new A; +$prop = &$a->prop; +$a->prop = new B; +var_dump($a); +?> +--EXPECT-- +object(A)#1 (1) { + ["prop"]=> + &string(4) "test" +} diff --git a/ext/opcache/tests/jit/reg_alloc_008.phpt b/ext/opcache/tests/jit/reg_alloc_008.phpt new file mode 100644 index 0000000000000..f71578ab4307c --- /dev/null +++ b/ext/opcache/tests/jit/reg_alloc_008.phpt @@ -0,0 +1,17 @@ +--TEST-- +Register Alloction 008: Incorrect deoptimization code +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- + +--EXPECTF-- +Warning: Undefined variable $y in %sreg_alloc_008.php on line 4 diff --git a/ext/opcache/tests/jit/reg_alloc_009.phpt b/ext/opcache/tests/jit/reg_alloc_009.phpt new file mode 100644 index 0000000000000..dfcbfc086f85f --- /dev/null +++ b/ext/opcache/tests/jit/reg_alloc_009.phpt @@ -0,0 +1,24 @@ +--TEST-- +Register Alloction 009: Missing type store +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- +> - $j++; + } +} +test(); +?> +--EXPECTF-- +Warning: Undefined variable $j in %sreg_alloc_009.php on line 4 + +Fatal error: Uncaught ArithmeticError: Bit shift by negative number in %sreg_alloc_009.php:4 +Stack trace: +#0 %sreg_alloc_009.php(7): test() +#1 {main} + thrown in %sreg_alloc_009.php on line 4 diff --git a/ext/opcache/tests/jit/reg_alloc_010.phpt b/ext/opcache/tests/jit/reg_alloc_010.phpt new file mode 100644 index 0000000000000..d14b107295346 --- /dev/null +++ b/ext/opcache/tests/jit/reg_alloc_010.phpt @@ -0,0 +1,24 @@ +--TEST-- +Register Alloction 010: Missed store +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- + +DONE +--EXPECTF-- +Warning: Undefined variable $cnt in %sreg_alloc_010.php on line 3 + +Warning: Undefined variable $cnt in %sreg_alloc_010.php on line 3 +DONE \ No newline at end of file diff --git a/ext/opcache/tests/jit/reg_alloc_011.phpt b/ext/opcache/tests/jit/reg_alloc_011.phpt new file mode 100644 index 0000000000000..f628adf7795ae --- /dev/null +++ b/ext/opcache/tests/jit/reg_alloc_011.phpt @@ -0,0 +1,25 @@ +--TEST-- +Register Alloction 011: Missed type store +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +--FILE-- + +DONE +--EXPECTF-- +DONE \ No newline at end of file diff --git a/ext/opcache/tests/opt/dce_013.phpt b/ext/opcache/tests/opt/dce_013.phpt new file mode 100644 index 0000000000000..ac5211498f6d1 --- /dev/null +++ b/ext/opcache/tests/opt/dce_013.phpt @@ -0,0 +1,12 @@ +--TEST-- +Incorrect DCE of FREE +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/ext/opcache/tests/opt/gh8074.phpt b/ext/opcache/tests/opt/gh8074.phpt new file mode 100644 index 0000000000000..86ec3449c476c --- /dev/null +++ b/ext/opcache/tests/opt/gh8074.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-8074 (Wrong type inference of range() result) +--FILE-- + +--EXPECT-- +int(2) +int(3) diff --git a/ext/opcache/tests/opt/gh8140a.phpt b/ext/opcache/tests/opt/gh8140a.phpt new file mode 100644 index 0000000000000..03f097c77c5ce --- /dev/null +++ b/ext/opcache/tests/opt/gh8140a.phpt @@ -0,0 +1,16 @@ +--TEST-- +GH-8140 (Wrong first class callable by name optimization) +--FILE-- + +--EXPECT-- +Hello, world! diff --git a/ext/opcache/tests/opt/gh8140b.phpt b/ext/opcache/tests/opt/gh8140b.phpt new file mode 100644 index 0000000000000..60da3a79e1408 --- /dev/null +++ b/ext/opcache/tests/opt/gh8140b.phpt @@ -0,0 +1,14 @@ +--TEST-- +GH-8140 (Wrong first class callable by name optimization) +--FILE-- + +--EXPECT-- +Hello, world! diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index b344943bf2288..ece10de386be8 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4637,7 +4637,7 @@ ZEND_METHOD(ReflectionClass, getConstants) array_init(return_value); ZEND_HASH_FOREACH_STR_KEY_PTR(CE_CONSTANTS_TABLE(ce), key, constant) { - if (UNEXPECTED(zval_update_constant_ex(&constant->value, ce) != SUCCESS)) { + if (UNEXPECTED(zval_update_constant_ex(&constant->value, constant->ce) != SUCCESS)) { RETURN_THROWS(); } @@ -4696,7 +4696,7 @@ ZEND_METHOD(ReflectionClass, getConstant) GET_REFLECTION_OBJECT_PTR(ce); constants_table = CE_CONSTANTS_TABLE(ce); ZEND_HASH_FOREACH_PTR(constants_table, c) { - if (UNEXPECTED(zval_update_constant_ex(&c->value, ce) != SUCCESS)) { + if (UNEXPECTED(zval_update_constant_ex(&c->value, c->ce) != SUCCESS)) { RETURN_THROWS(); } } ZEND_HASH_FOREACH_END(); diff --git a/ext/reflection/tests/gh8080.phpt b/ext/reflection/tests/gh8080.phpt new file mode 100644 index 0000000000000..c9c861051ab85 --- /dev/null +++ b/ext/reflection/tests/gh8080.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-8080 (ReflectionClass::getConstants() depends on def. order) +--FILE-- + 'Test', + ]; + private const TEST = 'test'; +} + +class B extends A {} + +$r = new ReflectionClass(B::class); +var_dump( + $r->getConstants(), + $r->getConstant("LIST") +); +?> +--EXPECT-- +array(1) { + ["LIST"]=> + array(1) { + ["test"]=> + string(4) "Test" + } +} +array(1) { + ["test"]=> + string(4) "Test" +} diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index f5a60607c73e2..2cf5e2360adbf 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -44,6 +44,8 @@ typedef struct _spl_fixedarray { zend_long size; /* It is possible to resize this, so this can't be combined with the object */ zval *elements; + /* True if this was modified after the last call to get_properties or the hash table wasn't rebuilt. */ + bool should_rebuild_properties; } spl_fixedarray; typedef struct _spl_fixedarray_methods { @@ -110,6 +112,7 @@ static void spl_fixedarray_init(spl_fixedarray *array, zend_long size) array->size = 0; /* reset size in case ecalloc() fails */ array->elements = safe_emalloc(size, sizeof(zval), 0); array->size = size; + array->should_rebuild_properties = true; spl_fixedarray_init_elems(array, 0, size); } else { spl_fixedarray_default_ctor(array); @@ -173,6 +176,7 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size) /* nothing to do */ return; } + array->should_rebuild_properties = true; /* first initialization */ if (array->size == 0) { @@ -212,8 +216,28 @@ static HashTable* spl_fixedarray_object_get_properties(zend_object *obj) HashTable *ht = zend_std_get_properties(obj); if (!spl_fixedarray_empty(&intern->array)) { + /* + * Usually, the reference count of the hash table is 1, + * except during cyclic reference cycles. + * + * Maintain the DEBUG invariant that a hash table isn't modified during iteration, + * and avoid unnecessary work rebuilding a hash table for unmodified properties. + * + * See https://github.com/php/php-src/issues/8079 and ext/spl/tests/fixedarray_022.phpt + * Also see https://github.com/php/php-src/issues/8044 for alternate considered approaches. + */ + if (!intern->array.should_rebuild_properties) { + /* Return the same hash table so that recursion cycle detection works in internal functions. */ + return ht; + } + intern->array.should_rebuild_properties = false; + zend_long j = zend_hash_num_elements(ht); + if (GC_REFCOUNT(ht) > 1) { + intern->std.properties = zend_array_dup(ht); + GC_TRY_DELREF(ht); + } for (zend_long i = 0; i < intern->array.size; i++) { zend_hash_index_update(ht, i, &intern->array.elements[i]); Z_TRY_ADDREF(intern->array.elements[i]); @@ -394,6 +418,9 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off } return &EG(uninitialized_zval); } + if (type != BP_VAR_IS && type != BP_VAR_R) { + intern->array.should_rebuild_properties = true; + } return spl_fixedarray_object_read_dimension_helper(intern, offset); } @@ -418,6 +445,7 @@ static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object * zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0); return; } else { + intern->array.should_rebuild_properties = true; /* Fix #81429 */ zval *ptr = &(intern->array.elements[index]); zval tmp; @@ -460,6 +488,7 @@ static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object * zend_throw_exception(spl_ce_RuntimeException, "Index invalid or out of range", 0); return; } else { + intern->array.should_rebuild_properties = true; zval_ptr_dtor(&(intern->array.elements[index])); ZVAL_NULL(&intern->array.elements[index]); } diff --git a/ext/spl/tests/fixedarray_022.phpt b/ext/spl/tests/fixedarray_022.phpt new file mode 100644 index 0000000000000..c1cff1a926582 --- /dev/null +++ b/ext/spl/tests/fixedarray_022.phpt @@ -0,0 +1,20 @@ +--TEST-- +SPL: FixedArray: Bug GH-8044 (var_export/debug_zval_dump HT_ASSERT_RC1 debug failure for SplFixedArray) +--FILE-- + +--EXPECTF-- +Warning: var_export does not handle circular references in %s on line 5 +SplFixedArray::__set_state(array( + 0 => NULL, +)) +object(SplFixedArray)#2 (1) refcount(4){ + [0]=> + *RECURSION* +} diff --git a/ext/spl/tests/fixedarray_023.phpt b/ext/spl/tests/fixedarray_023.phpt new file mode 100644 index 0000000000000..1d60a2ce6d9f7 --- /dev/null +++ b/ext/spl/tests/fixedarray_023.phpt @@ -0,0 +1,36 @@ +--TEST-- +SPL: FixedArray: Infinite loop in var_export bugfix +--FILE-- + +--EXPECTF-- +Warning: var_export does not handle circular references in %s on line 8 + +Warning: var_export does not handle circular references in %s on line 8 +SplFixedArray::__set_state(array( + 0 => NAN, + 1 => 0.0, + 2 => NULL, + 3 => NULL, +)) +object(SplFixedArray)#2 (4) refcount(6){ + [0]=> + float(NAN) + [1]=> + float(-0) + [2]=> + *RECURSION* + [3]=> + *RECURSION* +} \ No newline at end of file diff --git a/ext/standard/tests/serialize/serialization_objects_016.phpt b/ext/standard/tests/serialize/serialization_objects_016.phpt new file mode 100644 index 0000000000000..92b83f5f3c9df --- /dev/null +++ b/ext/standard/tests/serialize/serialization_objects_016.phpt @@ -0,0 +1,32 @@ +--TEST-- +Object serialization / unserialization: circular object with rc=1 +--FILE-- +y=$t; +$y=(array)$t; +unset($t); +var_dump($y); +$s=serialize($y); +var_dump($s); +$x=unserialize($s); +var_dump($x); +vaR_dump(serialize($x)); +?> +--EXPECTF-- +array(1) { + ["y"]=> + object(stdClass)#%d (1) { + ["y"]=> + *RECURSION* + } +} +string(45) "a:1:{s:1:"y";O:8:"stdClass":1:{s:1:"y";r:2;}}" +array(1) { + ["y"]=> + object(stdClass)#%d (1) { + ["y"]=> + *RECURSION* + } +} +string(45) "a:1:{s:1:"y";O:8:"stdClass":1:{s:1:"y";r:2;}}" diff --git a/ext/standard/var.c b/ext/standard/var.c index ef4b019fb6e76..c429763eb9c86 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -662,7 +662,11 @@ static inline zend_long php_add_var_hash(php_serialize_data_t data, zval *var) / data->n += 1; - if (!is_ref && (Z_TYPE_P(var) != IS_OBJECT || Z_REFCOUNT_P(var) == 1)) { + if (is_ref) { + /* pass */ + } else if (Z_TYPE_P(var) != IS_OBJECT) { + return 0; + } else if (Z_REFCOUNT_P(var) == 1 && (Z_OBJ_P(var)->properties == NULL || GC_REFCOUNT(Z_OBJ_P(var)->properties) == 1)) { return 0; } diff --git a/ext/zend_test/tests/observer_bug81430_1.phpt b/ext/zend_test/tests/observer_bug81430_1.phpt index cac53ef70cbbb..1d1e3c4256e0e 100644 --- a/ext/zend_test/tests/observer_bug81430_1.phpt +++ b/ext/zend_test/tests/observer_bug81430_1.phpt @@ -1,7 +1,7 @@ --TEST-- Bug #81430 (Attribute instantiation frame accessing invalid frame pointer) --EXTENSIONS-- -zend_test +zend-test --INI-- memory_limit=20M zend_test.observer.enabled=1 diff --git a/ext/zend_test/tests/observer_bug81430_2.phpt b/ext/zend_test/tests/observer_bug81430_2.phpt index 4d56248a80f34..a575fb9d7a038 100644 --- a/ext/zend_test/tests/observer_bug81430_2.phpt +++ b/ext/zend_test/tests/observer_bug81430_2.phpt @@ -1,7 +1,7 @@ --TEST-- Bug #81430 (Attribute instantiation leaves dangling execute_data pointer) --EXTENSIONS-- -zend_test +zend-test --INI-- memory_limit=20M zend_test.observer.enabled=1 diff --git a/ext/zlib/tests/gh7953.phpt b/ext/zlib/tests/gh7953.phpt new file mode 100644 index 0000000000000..e5cc283e62fb5 --- /dev/null +++ b/ext/zlib/tests/gh7953.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-7953 (ob_clean() only may not set Content-* header) +--EXTENSIONS-- +zlib +--CGI-- +--ENV-- +HTTP_ACCEPT_ENCODING=gzip +--FILE-- + +--EXPECTF-- +%a +--EXPECTHEADERS-- +Content-Encoding: gzip +Vary: Accept-Encoding diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 81a23c48c8444..09188dc76941e 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -281,7 +281,7 @@ static int php_zlib_output_handler(void **handler_context, php_output_context *o return FAILURE; } - if (!(output_context->op & PHP_OUTPUT_HANDLER_CLEAN)) { + if (!(output_context->op & PHP_OUTPUT_HANDLER_CLEAN) || (output_context->op & PHP_OUTPUT_HANDLER_START)) { int flags; if (SUCCESS == php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_GET_FLAGS, &flags)) { diff --git a/main/main.c b/main/main.c index a0f1a658c661a..2a417987f3148 100644 --- a/main/main.c +++ b/main/main.c @@ -2278,6 +2278,9 @@ int php_module_startup(sapi_module_struct *sf, zend_module_entry *additional_mod module->version = PHP_VERSION; module->info_func = PHP_MINFO(php_core); } + + /* freeze the list of observer fcall_init handlers */ + zend_observer_post_startup(); /* Extensions that add engine hooks after this point do so at their own peril */ zend_finalize_system_id(); diff --git a/main/php_version.h b/main/php_version.h index e0e76bd6aaca1..d87c013026ff2 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 1 -#define PHP_RELEASE_VERSION 3 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.1.3-dev" -#define PHP_VERSION_ID 80103 +#define PHP_RELEASE_VERSION 4 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.1.4" +#define PHP_VERSION_ID 80104 diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c index 603ca7e24a80e..bcb5b4cef7a70 100644 --- a/sapi/fpm/fpm/fpm_children.c +++ b/sapi/fpm/fpm/fpm_children.c @@ -299,7 +299,7 @@ void fpm_children_bury() /* {{{ */ } } } else { - zlog(ZLOG_ALERT, "oops, unknown child (%d) exited %s. Please open a bug report (https://bugs.php.net).", pid, buf); + zlog(ZLOG_ALERT, "oops, unknown child (%d) exited %s. Please open a bug report (https://github.com/php/php-src/issues).", pid, buf); } } } diff --git a/sapi/fpm/fpm/fpm_scoreboard.c b/sapi/fpm/fpm/fpm_scoreboard.c index 5380d184901bf..ec28af49d7c4b 100644 --- a/sapi/fpm/fpm/fpm_scoreboard.c +++ b/sapi/fpm/fpm/fpm_scoreboard.c @@ -226,6 +226,63 @@ void fpm_scoreboard_release(struct fpm_scoreboard_s *scoreboard) { scoreboard->lock = 0; } +struct fpm_scoreboard_s *fpm_scoreboard_copy(struct fpm_scoreboard_s *scoreboard, int copy_procs) +{ + struct fpm_scoreboard_s *scoreboard_copy; + struct fpm_scoreboard_proc_s *scoreboard_proc_p; + size_t scoreboard_size, scoreboard_nprocs_size; + int i; + void *mem; + + if (!scoreboard) { + scoreboard = fpm_scoreboard_get(); + } + + if (copy_procs) { + scoreboard_size = sizeof(struct fpm_scoreboard_s); + scoreboard_nprocs_size = sizeof(struct fpm_scoreboard_proc_s) * scoreboard->nprocs; + + mem = malloc(scoreboard_size + scoreboard_nprocs_size); + } else { + mem = malloc(sizeof(struct fpm_scoreboard_s)); + } + + if (!mem) { + zlog(ZLOG_ERROR, "scoreboard: failed to allocate memory for copy"); + return NULL; + } + + scoreboard_copy = mem; + + scoreboard = fpm_scoreboard_acquire(scoreboard, FPM_SCOREBOARD_LOCK_NOHANG); + if (!scoreboard) { + free(mem); + zlog(ZLOG_ERROR, "scoreboard: failed to lock (already locked)"); + return NULL; + } + + *scoreboard_copy = *scoreboard; + + if (copy_procs) { + mem += scoreboard_size; + + for (i = 0; i < scoreboard->nprocs; i++, mem += sizeof(struct fpm_scoreboard_proc_s)) { + scoreboard_proc_p = fpm_scoreboard_proc_acquire(scoreboard, i, FPM_SCOREBOARD_LOCK_HANG); + scoreboard_copy->procs[i] = *scoreboard_proc_p; + fpm_scoreboard_proc_release(scoreboard_proc_p); + } + } + + fpm_scoreboard_release(scoreboard); + + return scoreboard_copy; +} + +void fpm_scoreboard_free_copy(struct fpm_scoreboard_s *scoreboard) +{ + free(scoreboard); +} + struct fpm_scoreboard_proc_s *fpm_scoreboard_proc_acquire(struct fpm_scoreboard_s *scoreboard, int child_index, int nohang) /* {{{ */ { struct fpm_scoreboard_proc_s *proc; diff --git a/sapi/fpm/fpm/fpm_scoreboard.h b/sapi/fpm/fpm/fpm_scoreboard.h index 17b73ae76066d..946b8262af44b 100644 --- a/sapi/fpm/fpm/fpm_scoreboard.h +++ b/sapi/fpm/fpm/fpm_scoreboard.h @@ -15,6 +15,9 @@ #define FPM_SCOREBOARD_ACTION_SET 0 #define FPM_SCOREBOARD_ACTION_INC 1 +#define FPM_SCOREBOARD_LOCK_HANG 0 +#define FPM_SCOREBOARD_LOCK_NOHANG 1 + struct fpm_scoreboard_proc_s { union { atomic_t lock; @@ -87,6 +90,9 @@ void fpm_scoreboard_child_use(struct fpm_child_s *child, pid_t pid); void fpm_scoreboard_proc_free(struct fpm_child_s *child); int fpm_scoreboard_proc_alloc(struct fpm_child_s *child); +struct fpm_scoreboard_s *fpm_scoreboard_copy(struct fpm_scoreboard_s *scoreboard, int copy_procs); +void fpm_scoreboard_free_copy(struct fpm_scoreboard_s *scoreboard); + #ifdef HAVE_TIMES float fpm_scoreboard_get_tick(void); #endif diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index 24ac8afdc94be..514d60d176e39 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -136,8 +136,8 @@ int fpm_status_export_to_zval(zval *status) int fpm_status_handle_request(void) /* {{{ */ { - struct fpm_scoreboard_s scoreboard, *scoreboard_p; - struct fpm_scoreboard_proc_s proc; + struct fpm_scoreboard_s *scoreboard_p; + struct fpm_scoreboard_proc_s *proc; char *buffer, *time_format, time_buffer[64]; time_t now_epoch; int full, encode, has_start_time; @@ -170,9 +170,17 @@ int fpm_status_handle_request(void) /* {{{ */ if (fpm_status_uri && !strcmp(fpm_status_uri, SG(request_info).request_uri)) { fpm_request_executing(); + /* full status ? */ + _GET_str = zend_string_init("_GET", sizeof("_GET")-1, 0); + full = (fpm_php_get_string_from_table(_GET_str, "full") != NULL); + short_syntax = short_post = NULL; + full_separator = full_pre = full_syntax = full_post = NULL; + encode = 0; + has_start_time = 1; + scoreboard_p = fpm_scoreboard_get(); - if (scoreboard_p->shared) { - scoreboard_p = scoreboard_p->shared; + if (scoreboard_p) { + scoreboard_p = fpm_scoreboard_copy(scoreboard_p->shared ? scoreboard_p->shared : scoreboard_p, full); } if (!scoreboard_p) { zlog(ZLOG_ERROR, "status: unable to find or access status shared memory"); @@ -184,21 +192,9 @@ int fpm_status_handle_request(void) /* {{{ */ return 1; } - if (!fpm_spinlock(&scoreboard_p->lock, 1)) { - zlog(ZLOG_NOTICE, "[pool %s] status: scoreboard already in used.", scoreboard_p->pool); - SG(sapi_headers).http_response_code = 503; - sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1); - sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1); - sapi_add_header_ex(ZEND_STRL("Cache-Control: no-cache, no-store, must-revalidate, max-age=0"), 1, 1); - PUTS("Server busy. Please try again later."); - return 1; - } - /* copy the scoreboard not to bother other processes */ - scoreboard = *scoreboard_p; - fpm_unlock(scoreboard_p->lock); - - if (scoreboard.idle < 0 || scoreboard.active < 0) { - zlog(ZLOG_ERROR, "[pool %s] invalid status values", scoreboard.pool); + if (scoreboard_p->idle < 0 || scoreboard_p->active < 0) { + fpm_scoreboard_free_copy(scoreboard_p); + zlog(ZLOG_ERROR, "[pool %s] invalid status values", scoreboard_p->pool); SG(sapi_headers).http_response_code = 500; sapi_add_header_ex(ZEND_STRL("Content-Type: text/plain"), 1, 1); sapi_add_header_ex(ZEND_STRL("Expires: Thu, 01 Jan 1970 00:00:00 GMT"), 1, 1); @@ -214,17 +210,10 @@ int fpm_status_handle_request(void) /* {{{ */ /* handle HEAD */ if (SG(request_info).headers_only) { + fpm_scoreboard_free_copy(scoreboard_p); return 1; } - /* full status ? */ - _GET_str = zend_string_init("_GET", sizeof("_GET")-1, 0); - full = (fpm_php_get_string_from_table(_GET_str, "full") != NULL); - short_syntax = short_post = NULL; - full_separator = full_pre = full_syntax = full_post = NULL; - encode = 0; - has_start_time = 1; - /* HTML */ if (fpm_php_get_string_from_table(_GET_str, "html")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1); @@ -486,37 +475,37 @@ int fpm_status_handle_request(void) /* {{{ */ now_epoch = time(NULL); if (has_start_time) { - strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&scoreboard.start_epoch)); + strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&scoreboard_p->start_epoch)); spprintf(&buffer, 0, short_syntax, - scoreboard.pool, - PM2STR(scoreboard.pm), + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), time_buffer, - (unsigned long) (now_epoch - scoreboard.start_epoch), - scoreboard.requests, - scoreboard.lq, - scoreboard.lq_max, - scoreboard.lq_len, - scoreboard.idle, - scoreboard.active, - scoreboard.idle + scoreboard.active, - scoreboard.active_max, - scoreboard.max_children_reached, - scoreboard.slow_rq); + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq); } else { spprintf(&buffer, 0, short_syntax, - scoreboard.pool, - PM2STR(scoreboard.pm), - (unsigned long) (now_epoch - scoreboard.start_epoch), - scoreboard.requests, - scoreboard.lq, - scoreboard.lq_max, - scoreboard.lq_len, - scoreboard.idle, - scoreboard.active, - scoreboard.idle + scoreboard.active, - scoreboard.active_max, - scoreboard.max_children_reached, - scoreboard.slow_rq); + scoreboard_p->pool, + PM2STR(scoreboard_p->pm), + (unsigned long) (now_epoch - scoreboard_p->start_epoch), + scoreboard_p->requests, + scoreboard_p->lq, + scoreboard_p->lq_max, + scoreboard_p->lq_len, + scoreboard_p->idle, + scoreboard_p->active, + scoreboard_p->idle + scoreboard_p->active, + scoreboard_p->active_max, + scoreboard_p->max_children_reached, + scoreboard_p->slow_rq); } PUTS(buffer); @@ -547,7 +536,7 @@ int fpm_status_handle_request(void) /* {{{ */ if (!scoreboard_p->procs[i].used) { continue; } - proc = scoreboard_p->procs[i]; + proc = &scoreboard_p->procs[i]; if (first) { first = 0; @@ -559,44 +548,44 @@ int fpm_status_handle_request(void) /* {{{ */ query_string = NULL; tmp_query_string = NULL; - if (proc.query_string[0] != '\0') { + if (proc->query_string[0] != '\0') { if (!encode) { - query_string = proc.query_string; + query_string = proc->query_string; } else { - tmp_query_string = php_escape_html_entities_ex((const unsigned char *) proc.query_string, strlen(proc.query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, /* double_encode */ 1, /* quiet */ 0); + tmp_query_string = php_escape_html_entities_ex((const unsigned char *) proc->query_string, strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, /* double_encode */ 1, /* quiet */ 0); query_string = ZSTR_VAL(tmp_query_string); } } /* prevent NaN */ - if (proc.cpu_duration.tv_sec == 0 && proc.cpu_duration.tv_usec == 0) { + if (proc->cpu_duration.tv_sec == 0 && proc->cpu_duration.tv_usec == 0) { cpu = 0.; } else { - cpu = (proc.last_request_cpu.tms_utime + proc.last_request_cpu.tms_stime + proc.last_request_cpu.tms_cutime + proc.last_request_cpu.tms_cstime) / fpm_scoreboard_get_tick() / (proc.cpu_duration.tv_sec + proc.cpu_duration.tv_usec / 1000000.) * 100.; + cpu = (proc->last_request_cpu.tms_utime + proc->last_request_cpu.tms_stime + proc->last_request_cpu.tms_cutime + proc->last_request_cpu.tms_cstime) / fpm_scoreboard_get_tick() / (proc->cpu_duration.tv_sec + proc->cpu_duration.tv_usec / 1000000.) * 100.; } - if (proc.request_stage == FPM_REQUEST_ACCEPTING) { - duration = proc.duration; + if (proc->request_stage == FPM_REQUEST_ACCEPTING) { + duration = proc->duration; } else { - timersub(&now, &proc.accepted, &duration); + timersub(&now, &proc->accepted, &duration); } - strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&proc.start_epoch)); + strftime(time_buffer, sizeof(time_buffer) - 1, time_format, localtime(&proc->start_epoch)); spprintf(&buffer, 0, full_syntax, - (int) proc.pid, - fpm_request_get_stage_name(proc.request_stage), + (int) proc->pid, + fpm_request_get_stage_name(proc->request_stage), time_buffer, - (unsigned long) (now_epoch - proc.start_epoch), - proc.requests, + (unsigned long) (now_epoch - proc->start_epoch), + proc->requests, duration.tv_sec * 1000000UL + duration.tv_usec, - proc.request_method[0] != '\0' ? proc.request_method : "-", - proc.request_uri[0] != '\0' ? proc.request_uri : "-", + proc->request_method[0] != '\0' ? proc->request_method : "-", + proc->request_uri[0] != '\0' ? proc->request_uri : "-", query_string ? "?" : "", query_string ? query_string : "", - proc.content_length, - proc.auth_user[0] != '\0' ? proc.auth_user : "-", - proc.script_filename[0] != '\0' ? proc.script_filename : "-", - proc.request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., - proc.request_stage == FPM_REQUEST_ACCEPTING ? proc.memory : 0); + proc->content_length, + proc->auth_user[0] != '\0' ? proc->auth_user : "-", + proc->script_filename[0] != '\0' ? proc->script_filename : "-", + proc->request_stage == FPM_REQUEST_ACCEPTING ? cpu : 0., + proc->request_stage == FPM_REQUEST_ACCEPTING ? proc->memory : 0); PUTS(buffer); efree(buffer); @@ -610,6 +599,7 @@ int fpm_status_handle_request(void) /* {{{ */ } } + fpm_scoreboard_free_copy(scoreboard_p); return 1; }