diff --git a/.github/actions/apt-x64/action.yml b/.github/actions/apt-x64/action.yml index 621b18532e05f..4e1a03dd58cc5 100644 --- a/.github/actions/apt-x64/action.yml +++ b/.github/actions/apt-x64/action.yml @@ -59,15 +59,3 @@ runs: libjpeg-dev \ libpng-dev \ libfreetype6-dev - - mkdir /opt/oracle - wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip - unzip instantclient-basiclite-linuxx64.zip && rm instantclient-basiclite-linuxx64.zip - wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip - unzip instantclient-sdk-linuxx64.zip && rm instantclient-sdk-linuxx64.zip - mv instantclient_*_* /opt/oracle/instantclient - # interferes with libldap2 headers - rm /opt/oracle/instantclient/sdk/include/ldap.h - # fix debug build warning: zend_signal: handler was replaced for signal (2) after startup - echo DISABLE_INTERRUPT=on > /opt/oracle/instantclient/network/admin/sqlnet.ora - sudo sh -c 'echo /opt/oracle/instantclient >/etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig' diff --git a/.github/actions/configure-x64/action.yml b/.github/actions/configure-x64/action.yml index ee802334490ae..3c3264ae07f5e 100644 --- a/.github/actions/configure-x64/action.yml +++ b/.github/actions/configure-x64/action.yml @@ -74,8 +74,8 @@ runs: --with-imap \ --with-imap-ssl \ --with-pdo-odbc=unixODBC,/usr \ - --with-pdo-oci=shared,instantclient,/opt/oracle/instantclient \ - --with-oci8=shared,instantclient,/opt/oracle/instantclient \ + $([ -d "/opt/oracle/instantclient" ] && echo '--with-pdo-oci=shared,instantclient,/opt/oracle/instantclient') \ + $([ -d "/opt/oracle/instantclient" ] && echo '--with-oci8=shared,instantclient,/opt/oracle/instantclient') \ --with-config-file-path=/etc \ --with-config-file-scan-dir=/etc/php.d \ --with-pdo-firebird \ diff --git a/.github/actions/install-linux-x32/action.yml b/.github/actions/install-linux-x32/action.yml index bf5f09cd779d4..4ef87ee03fd3f 100644 --- a/.github/actions/install-linux-x32/action.yml +++ b/.github/actions/install-linux-x32/action.yml @@ -6,7 +6,7 @@ runs: run: | set -x make install - mkdir /etc/php.d + mkdir -p /etc/php.d 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 diff --git a/.github/actions/install-linux/action.yml b/.github/actions/install-linux/action.yml index 7ac2ae4c4fcb1..576357b94f1c5 100644 --- a/.github/actions/install-linux/action.yml +++ b/.github/actions/install-linux/action.yml @@ -6,9 +6,7 @@ runs: run: | set -x sudo make install - sudo mkdir /etc/php.d + sudo mkdir -p /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 extension=oci8.so > /etc/php.d/oci8.ini - echo extension=pdo_oci.so > /etc/php.d/pdo_oci.ini diff --git a/.github/actions/setup-oracle/action.yml b/.github/actions/setup-oracle/action.yml index 11c16fe93d525..1208e93a24893 100644 --- a/.github/actions/setup-oracle/action.yml +++ b/.github/actions/setup-oracle/action.yml @@ -11,3 +11,20 @@ runs: --name oracle \ -h oracle \ -d gvenzl/oracle-xe:slim + + mkdir /opt/oracle + wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-basiclite-linuxx64.zip + unzip instantclient-basiclite-linuxx64.zip && rm instantclient-basiclite-linuxx64.zip + wget -nv https://download.oracle.com/otn_software/linux/instantclient/instantclient-sdk-linuxx64.zip + unzip instantclient-sdk-linuxx64.zip && rm instantclient-sdk-linuxx64.zip + mv instantclient_*_* /opt/oracle/instantclient + # interferes with libldap2 headers + rm /opt/oracle/instantclient/sdk/include/ldap.h + # fix debug build warning: zend_signal: handler was replaced for signal (2) after startup + echo DISABLE_INTERRUPT=on > /opt/oracle/instantclient/network/admin/sqlnet.ora + sudo sh -c 'echo /opt/oracle/instantclient >/etc/ld.so.conf.d/oracle-instantclient.conf && ldconfig' + + sudo mkdir -p /etc/php.d + sudo chmod 777 /etc/php.d + echo extension=oci8.so > /etc/php.d/oci8.ini + echo extension=pdo_oci.so > /etc/php.d/pdo_oci.ini diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 83fe292591608..61371fa3d0817 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -564,7 +564,7 @@ jobs: - name: make install run: | sudo make install - sudo mkdir /etc/php.d + sudo mkdir -p /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 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index a29b9890564bc..07e3a684cc5e5 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -6,9 +6,12 @@ on: - NEWS - UPGRADING - UPGRADING.INTERNALS - - README.md + - '**/README.*' - CONTRIBUTING.md - CODING_STANDARDS.md + - .cirrus.yml + - .travis.yml + - travis/* branches: - PHP-7.4 - PHP-8.0 @@ -16,6 +19,17 @@ on: - PHP-8.2 - master pull_request: + paths-ignore: + - docs/* + - NEWS + - UPGRADING + - UPGRADING.INTERNALS + - '**/README.*' + - CONTRIBUTING.md + - CODING_STANDARDS.md + - .cirrus.yml + - .travis.yml + - travis/* branches: - '**' concurrency: diff --git a/NEWS b/NEWS index 8d8884ef7757b..a42c12273558e 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,53 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.10 +28 Sep 2023, PHP 8.2.11 + +- Core: + . Fixed bug GH-11937 (Constant ASTs containing objects). (ilutov) + . Fixed bug GH-11790 (On riscv64 require libatomic if actually needed). + (Jeremie Courreges-Anglas) + . Fixed bug GH-11876: ini_parse_quantity() accepts invalid quantities. + (Girgias) + . Fixed bug GH-12073 (Segfault when freeing incompletely initialized + closures). (ilutov) + . Fixed bug GH-12060 (Internal iterator rewind handler is called twice). + (ju1ius) + . Fixed bug GH-12102 (Incorrect compile error when using array access on TMP + value in function call). (ilutov) + +- DOM: + . Fix memory leak when setting an invalid DOMDocument encoding. (nielsdos) + +- Iconv: + . Fixed build for NetBSD which still uses the old iconv signature. + (David Carlier) + +- Intl: + . Fixed bug GH-12020 (intl_get_error_message() broken after + MessageFormatter::formatMessage() fails). (Girgias) + +- MySQLnd: + . Fixed bug GH-10270 (Invalid error message when connection via SSL fails: + "trying to connect via (null)"). (Kamil Tekiela) + +- ODBC: + . Fixed memory leak with failed SQLPrepare. (NattyNarwhal) + . Fixed persistent procedural ODBC connections not getting closed. + (NattyNarwhal) + +- SimpleXML: + . Fixed bug #52751 (XPath processing-instruction() function is not + supported). (nielsdos) + +- SPL: + . Fixed bug GH-11972 (RecursiveCallbackFilterIterator regression in 8.1.18). + (nielsdos) + +- SQLite3: + . Fixed bug GH-11878 (SQLite3 callback functions cause a memory leak with + a callable array). (nielsdos, arnaud-lb) + +31 Aug 2023, PHP 8.2.10 - CLI: . Fixed bug GH-11716 (cli server crashes on SIGINT when compiled with diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index 200b5a6ff83f4..bcddacc4e43fc 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -31,6 +31,7 @@ typedef struct _optimizer_call_info { zend_function *func; zend_op *opline; + zend_op *last_check_func_arg_opline; bool is_prototype; bool try_inline; uint32_t func_arg_num; @@ -235,6 +236,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (call_stack[call - 1].func_arg_num != (uint32_t)-1 && has_known_send_mode(&call_stack[call - 1], call_stack[call - 1].func_arg_num)) { if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) { + /* There's no TMP specialization for FETCH_OBJ_W/FETCH_DIM_W. Avoid + * converting it and error at runtime in the FUNC_ARG variant. */ + if ((opline->opcode == ZEND_FETCH_OBJ_FUNC_ARG || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) + && (opline->op1_type == IS_TMP_VAR || call_stack[call - 1].last_check_func_arg_opline == NULL)) { + /* Don't remove the associated CHECK_FUNC_ARG opcode. */ + call_stack[call - 1].last_check_func_arg_opline = NULL; + break; + } if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) { opline->opcode -= 9; } else { @@ -278,11 +287,21 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx) if (has_known_send_mode(&call_stack[call - 1], opline->op2.num)) { call_stack[call - 1].func_arg_num = opline->op2.num; - MAKE_NOP(opline); + call_stack[call - 1].last_check_func_arg_opline = opline; } break; - case ZEND_SEND_VAR_EX: case ZEND_SEND_FUNC_ARG: + /* Don't transform SEND_FUNC_ARG if any FETCH opcodes weren't transformed. */ + if (call_stack[call - 1].last_check_func_arg_opline == NULL) { + if (opline->op2_type == IS_CONST) { + call_stack[call - 1].try_inline = 0; + } + break; + } + MAKE_NOP(call_stack[call - 1].last_check_func_arg_opline); + call_stack[call - 1].last_check_func_arg_opline = NULL; + ZEND_FALLTHROUGH; + case ZEND_SEND_VAR_EX: if (opline->op2_type == IS_CONST) { call_stack[call - 1].try_inline = 0; break; diff --git a/Zend/tests/arginfo_zpp_mismatch.phpt b/Zend/tests/arginfo_zpp_mismatch.phpt index f5df299430090..d7aefc6f374f6 100644 --- a/Zend/tests/arginfo_zpp_mismatch.phpt +++ b/Zend/tests/arginfo_zpp_mismatch.phpt @@ -2,6 +2,7 @@ Test that there is no arginfo/zpp mismatch --SKIPIF-- --FILE-- diff --git a/Zend/tests/arginfo_zpp_mismatch_strict.phpt b/Zend/tests/arginfo_zpp_mismatch_strict.phpt index 8e77af9fb4d01..ddee0bd29cfe4 100644 --- a/Zend/tests/arginfo_zpp_mismatch_strict.phpt +++ b/Zend/tests/arginfo_zpp_mismatch_strict.phpt @@ -2,6 +2,7 @@ Test that there is no arginfo/zpp mismatch in strict mode --SKIPIF-- --FILE-- diff --git a/Zend/tests/gh12073.phpt b/Zend/tests/gh12073.phpt new file mode 100644 index 0000000000000..ef115685ce7d4 --- /dev/null +++ b/Zend/tests/gh12073.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-12073: Freeing of non-ZMM pointer of incompletely allocated closure +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/Zend/tests/gh12102_1.phpt b/Zend/tests/gh12102_1.phpt new file mode 100644 index 0000000000000..1d548d6b28f3c --- /dev/null +++ b/Zend/tests/gh12102_1.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-12102: Incorrect "Cannot use temporary expression in write context" error for BP_VAR_FUNC_ARG +--FILE-- +getMessage(), "\n"; + } +} + +/* Intentionally declared after test() to avoid compile-time checking of ref args. */ + +function byVal($arg) { + var_dump($arg); +} + +function byRef(&$arg) { + var_dump($arg); +} + +test('y'); + +?> +--EXPECT-- +string(1) "y" +Cannot use temporary expression in write context diff --git a/Zend/tests/gh12102_2.phpt b/Zend/tests/gh12102_2.phpt new file mode 100644 index 0000000000000..dacc11c03081a --- /dev/null +++ b/Zend/tests/gh12102_2.phpt @@ -0,0 +1,43 @@ +--TEST-- +GH-12102: Incorrect "Cannot use temporary expression in write context" error for BP_VAR_FUNC_ARG +--FILE-- + +--EXPECTF-- +Warning: Undefined array key 0 in %s on line %d +array(0) { +} +array(1) { + [0]=> + array(1) { + [0]=> + int(42) + } +} diff --git a/Zend/tests/gh12102_3.phpt b/Zend/tests/gh12102_3.phpt new file mode 100644 index 0000000000000..741bce5ab1ba1 --- /dev/null +++ b/Zend/tests/gh12102_3.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-12102: Incorrect "Cannot use temporary expression in write context" error for BP_VAR_FUNC_ARG +--FILE-- +getMessage(), "\n"; + } +} + +/* Intentionally declared after test() to avoid compile-time checking of ref args. */ + +const C = ['foo']; + +function byVal($arg) { + var_dump($arg); +} + +function byRef(&$arg) { + var_dump($arg); +} + +test('y'); + +?> +--EXPECT-- +string(3) "foo" +Cannot use temporary expression in write context diff --git a/Zend/tests/zend_ini/gh11876.phpt b/Zend/tests/zend_ini/gh11876.phpt new file mode 100644 index 0000000000000..b83061bf161e3 --- /dev/null +++ b/Zend/tests/zend_ini/gh11876.phpt @@ -0,0 +1,51 @@ +--TEST-- +Invalid INI quantities, base prefix followed by stuff eaten by strtoull() +--EXTENSIONS-- +zend_test +--FILE-- +func.common.fn_flags |= ZEND_ACC_CLOSURE; closure->func.common.fn_flags &= ~ZEND_ACC_IMMUTABLE; + zend_string_addref(closure->func.op_array.function_name); + if (closure->func.op_array.refcount) { + (*closure->func.op_array.refcount)++; + } + /* For fake closures, we want to reuse the static variables of the original function. */ if (!is_fake) { if (closure->func.op_array.static_variables) { @@ -758,22 +763,17 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en if (func->common.scope != scope) { func->common.scope = scope; } - closure->func.op_array.fn_flags &= ~ZEND_ACC_HEAP_RT_CACHE; ptr = zend_arena_alloc(&CG(arena), func->op_array.cache_size); ZEND_MAP_PTR_SET(func->op_array.run_time_cache, ptr); + closure->func.op_array.fn_flags &= ~ZEND_ACC_HEAP_RT_CACHE; } else { /* Otherwise, we use a non-shared runtime cache */ - closure->func.op_array.fn_flags |= ZEND_ACC_HEAP_RT_CACHE; ptr = emalloc(func->op_array.cache_size); + closure->func.op_array.fn_flags |= ZEND_ACC_HEAP_RT_CACHE; } memset(ptr, 0, func->op_array.cache_size); } ZEND_MAP_PTR_INIT(closure->func.op_array.run_time_cache, ptr); - - zend_string_addref(closure->func.op_array.function_name); - if (closure->func.op_array.refcount) { - (*closure->func.op_array.refcount)++; - } } else { memcpy(&closure->func, func, sizeof(zend_internal_function)); closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 63893728f1ee4..cba90112cfc18 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1423,6 +1423,35 @@ ZEND_API zend_result zend_unmangle_property_name_ex(const zend_string *name, con } /* }}} */ +static bool array_is_const_ex(zend_array *array, uint32_t *max_checks) +{ + if (zend_hash_num_elements(array) > *max_checks) { + return false; + } + *max_checks -= zend_hash_num_elements(array); + + zval *element; + ZEND_HASH_FOREACH_VAL(array, element) { + if (Z_TYPE_P(element) < IS_ARRAY) { + continue; + } else if (Z_TYPE_P(element) == IS_ARRAY) { + if (!array_is_const_ex(array, max_checks)) { + return false; + } + } else { + return false; + } + } ZEND_HASH_FOREACH_END(); + + return true; +} + +static bool array_is_const(zend_array *array) +{ + uint32_t max_checks = 50; + return array_is_const_ex(array, &max_checks); +} + static bool can_ct_eval_const(zend_constant *c) { if (ZEND_CONSTANT_FLAGS(c) & CONST_DEPRECATED) { return 0; @@ -1433,9 +1462,13 @@ static bool can_ct_eval_const(zend_constant *c) { && (CG(compiler_options) & ZEND_COMPILE_WITH_FILE_CACHE))) { return 1; } - if (Z_TYPE(c->value) < IS_OBJECT + if (Z_TYPE(c->value) < IS_ARRAY && !(CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION)) { return 1; + } else if (Z_TYPE(c->value) == IS_ARRAY + && !(CG(compiler_options) & ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION) + && array_is_const(Z_ARR(c->value))) { + return 1; } return 0; } @@ -1660,7 +1693,10 @@ static bool zend_try_ct_eval_class_const(zval *zv, zend_string *class_name, zend c = &cc->value; /* Substitute case-sensitive (or lowercase) persistent class constants */ - if (Z_TYPE_P(c) < IS_OBJECT) { + if (Z_TYPE_P(c) < IS_ARRAY) { + ZVAL_COPY_OR_DUP(zv, c); + return 1; + } else if (Z_TYPE_P(c) == IS_ARRAY && array_is_const(Z_ARR_P(c))) { ZVAL_COPY_OR_DUP(zv, c); return 1; } @@ -2771,7 +2807,11 @@ static zend_op *zend_compile_simple_var(znode *result, zend_ast *ast, uint32_t t static void zend_separate_if_call_and_write(znode *node, zend_ast *ast, uint32_t type) /* {{{ */ { - if (type != BP_VAR_R && type != BP_VAR_IS && zend_is_call(ast)) { + if (type != BP_VAR_R + && type != BP_VAR_IS + /* Whether a FUNC_ARG is R may only be determined at runtime. */ + && type != BP_VAR_FUNC_ARG + && zend_is_call(ast)) { if (node->op_type == IS_VAR) { zend_op *opline = zend_emit_op(NULL, ZEND_SEPARATE, node, NULL); opline->result_type = IS_VAR; diff --git a/Zend/zend_ini.c b/Zend/zend_ini.c index 86aa959f99017..1aaac0ba348a5 100644 --- a/Zend/zend_ini.c +++ b/Zend/zend_ini.c @@ -547,6 +547,34 @@ typedef enum { ZEND_INI_PARSE_QUANTITY_UNSIGNED, } zend_ini_parse_quantity_signed_result_t; +static const char *zend_ini_consume_quantity_prefix(const char *const digits, const char *const str_end) { + const char *digits_consumed = digits; + /* Ignore leading whitespace. */ + while (digits_consumed < str_end && zend_is_whitespace(*digits_consumed)) {++digits_consumed;} + if (digits_consumed[0] == '+' || digits_consumed[0] == '-') { + ++digits_consumed; + } + + if (digits_consumed[0] == '0' && !isdigit(digits_consumed[1])) { + /* Value is just 0 */ + if ((digits_consumed+1) == str_end) { + return digits; + } + + switch (digits_consumed[1]) { + case 'x': + case 'X': + case 'o': + case 'O': + case 'b': + case 'B': + digits_consumed += 2; + break; + } + } + return digits_consumed; +} + static zend_ulong zend_ini_parse_quantity_internal(zend_string *value, zend_ini_parse_quantity_signed_result_t signed_result, zend_string **errstr) /* {{{ */ { char *digits_end = NULL; @@ -634,6 +662,18 @@ static zend_ulong zend_ini_parse_quantity_internal(zend_string *value, zend_ini_ smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value)); smart_str_0(&invalid); + *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": no digits after base prefix, interpreting as \"0\" for backwards compatibility", + ZSTR_VAL(invalid.s)); + + smart_str_free(&invalid); + return 0; + } + if (UNEXPECTED(digits != zend_ini_consume_quantity_prefix(digits, str_end))) { + /* Escape the string to avoid null bytes and to make non-printable chars + * visible */ + smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value)); + smart_str_0(&invalid); + *errstr = zend_strpprintf(0, "Invalid quantity \"%s\": no digits after base prefix, interpreting as \"0\" for backwards compatibility", ZSTR_VAL(invalid.s)); diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index b4e43b78efaf3..42fbccd4a746f 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -623,6 +623,7 @@ ZEND_METHOD(InternalIterator, rewind) { RETURN_THROWS(); } + intern->rewind_called = 1; if (!intern->iter->funcs->rewind) { /* Allow calling rewind() if no iteration has happened yet, * even if the iterator does not support rewinding. */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 07d24170ae71c..8ad2c6116d898 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8064,6 +8064,7 @@ ZEND_VM_HANDLER(142, ZEND_DECLARE_LAMBDA_FUNCTION, CONST, NUM) called_scope = Z_CE(EX(This)); object = NULL; } + SAVE_OPLINE(); zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index b2ab2013a2401..22e9f5b62a8ae 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -5440,6 +5440,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_C called_scope = Z_CE(EX(This)); object = NULL; } + SAVE_OPLINE(); zend_create_closure(EX_VAR(opline->result.var), func, EX(func)->op_array.scope, called_scope, object); diff --git a/configure.ac b/configure.ac index 23827974270d6..e4d1ec79b6313 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.2.10-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.11],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER @@ -375,11 +375,7 @@ AC_CHECK_LIB(m, sin) case $host_alias in riscv64*) - AC_CHECK_LIB(atomic, __atomic_exchange_1, [ - PHP_ADD_LIBRARY(atomic) - ], [ - AC_MSG_ERROR([Problem with enabling atomic. Please check config.log for details.]) - ]) + PHP_CHECK_FUNC(__atomic_exchange_1, atomic) ;; esac diff --git a/ext/dl_test/tests/skip.inc b/ext/dl_test/tests/skip.inc index e7558c7635082..5b1f0f3d0098b 100644 --- a/ext/dl_test/tests/skip.inc +++ b/ext/dl_test/tests/skip.inc @@ -12,3 +12,7 @@ if (PHP_OS_FAMILY === 'Windows') { if (!file_exists($path)) { die(sprintf('skip dl_test extension is not built (tried %s)', $path)); } + +if (getenv('SKIP_ASAN')) { + die('skip dl() crashes LSan'); +} diff --git a/ext/dom/document.c b/ext/dom/document.c index ea6daafeb30fb..64da4f051be2c 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -139,7 +139,6 @@ int dom_document_encoding_read(dom_object *obj, zval *retval) zend_result dom_document_encoding_write(dom_object *obj, zval *newval) { xmlDoc *docp = (xmlDocPtr) dom_object_get_node(obj); - zend_string *str; xmlCharEncodingHandlerPtr handler; if (docp == NULL) { @@ -147,11 +146,15 @@ zend_result dom_document_encoding_write(dom_object *obj, zval *newval) return FAILURE; } - str = zval_try_get_string(newval); - if (UNEXPECTED(!str)) { - return FAILURE; + /* Typed property, can only be IS_STRING or IS_NULL. */ + ZEND_ASSERT(Z_TYPE_P(newval) == IS_STRING || Z_TYPE_P(newval) == IS_NULL); + + if (Z_TYPE_P(newval) == IS_NULL) { + goto invalid_encoding; } + zend_string *str = Z_STR_P(newval); + handler = xmlFindCharEncodingHandler(ZSTR_VAL(str)); if (handler != NULL) { @@ -161,12 +164,14 @@ zend_result dom_document_encoding_write(dom_object *obj, zval *newval) } docp->encoding = xmlStrdup((const xmlChar *) ZSTR_VAL(str)); } else { - zend_value_error("Invalid document encoding"); - return FAILURE; + goto invalid_encoding; } - zend_string_release_ex(str, 0); return SUCCESS; + +invalid_encoding: + zend_value_error("Invalid document encoding"); + return FAILURE; } /* }}} */ diff --git a/ext/dom/tests/bug80602.phpt b/ext/dom/tests/bug80602.phpt index 844d829cb08d0..9f6334ed36be0 100644 --- a/ext/dom/tests/bug80602.phpt +++ b/ext/dom/tests/bug80602.phpt @@ -1,5 +1,7 @@ --TEST-- Bug #80602 (Segfault when using DOMChildNode::before()) +--EXTENSIONS-- +dom --FILE-- encoding = make_nonconst('utf-8'); +var_dump($dom->encoding); +try { + $dom->encoding = make_nonconst('foobar'); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->encoding); +$dom->encoding = make_nonconst('utf-16le'); +var_dump($dom->encoding); +try { + $dom->encoding = NULL; +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->encoding); + +?> +--EXPECT-- +string(5) "utf-8" +Invalid document encoding +string(5) "utf-8" +string(8) "utf-16le" +Invalid document encoding +string(8) "utf-16le" diff --git a/ext/dom/tests/gh9142.phpt b/ext/dom/tests/gh9142.phpt index f72dfa823f38c..ba4cf10c355fd 100644 --- a/ext/dom/tests/gh9142.phpt +++ b/ext/dom/tests/gh9142.phpt @@ -1,5 +1,7 @@ --TEST-- GH-9142 (DOMChildNode replaceWith() double-free error when replacing elements not separated by any whitespace) +--EXTENSIONS-- +dom --FILE-- s) + ZSTR_LEN((d)->s); - if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { switch (errno) { case EINVAL: return PHP_ICONV_ERR_ILLEGAL_CHAR; @@ -456,7 +464,7 @@ PHP_ICONV_API php_iconv_err_t php_iconv_string(const char *in_p, size_t in_len, out_p = ZSTR_VAL(out_buf); while (in_left > 0) { - result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left); + result = iconv(cd, (ICONV_CONST char **) &in_p, &in_left, (char **) &out_p, &out_left); out_size = bsz - out_left; if (result == (size_t)(-1)) { if (ignore_ilseq && errno == EILSEQ) { @@ -576,7 +584,7 @@ static php_iconv_err_t _php_iconv_strlen(size_t *pretval, const char *str, size_ more = in_left > 0; - iconv(cd, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + iconv(cd, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); if (out_left == sizeof(buf)) { break; } else { @@ -683,7 +691,7 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval, more = in_left > 0 && len > 0; - iconv(cd1, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + iconv(cd1, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); if (out_left == sizeof(buf)) { break; } @@ -805,7 +813,7 @@ static php_iconv_err_t _php_iconv_strpos(size_t *pretval, more = in_left > 0; - iconv_ret = iconv(cd, more ? (char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + iconv_ret = iconv(cd, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); if (out_left == sizeof(buf)) { break; } @@ -1012,7 +1020,7 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn out_left = out_size - out_reserved; - if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { switch (errno) { case EINVAL: err = PHP_ICONV_ERR_ILLEGAL_CHAR; @@ -1096,7 +1104,7 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn out_p = buf; out_left = out_size; - if (iconv(cd, (char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { switch (errno) { case EINVAL: err = PHP_ICONV_ERR_ILLEGAL_CHAR; @@ -2365,7 +2373,7 @@ static int php_iconv_stream_filter_append_bucket( tcnt = self->stub_len; while (tcnt > 0) { - if (iconv(self->cd, &pt, &tcnt, &pd, &ocnt) == (size_t)-1) { + if (iconv(self->cd, (ICONV_CONST char **)&pt, &tcnt, &pd, &ocnt) == (size_t)-1) { switch (errno) { case EILSEQ: php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); @@ -2431,7 +2439,7 @@ static int php_iconv_stream_filter_append_bucket( while (icnt > 0) { if ((ps == NULL ? iconv(self->cd, NULL, NULL, &pd, &ocnt): - iconv(self->cd, (char **)&ps, &icnt, &pd, &ocnt)) == (size_t)-1) { + iconv(self->cd, (ICONV_CONST char **)&ps, &icnt, &pd, &ocnt)) == (size_t)-1) { switch (errno) { case EILSEQ: php_error_docref(NULL, E_WARNING, "iconv stream filter (\"%s\"=>\"%s\"): invalid multibyte sequence", self->from_charset, self->to_charset); diff --git a/ext/intl/msgformat/msgformat_format.c b/ext/intl/msgformat/msgformat_format.c index 098c6a2b92286..f6ec60fe19942 100644 --- a/ext/intl/msgformat/msgformat_format.c +++ b/ext/intl/msgformat/msgformat_format.c @@ -98,7 +98,7 @@ PHP_FUNCTION( msgfmt_format_message ) intl_convert_utf8_to_utf16(&spattern, &spattern_len, pattern, pattern_len, &INTL_DATA_ERROR_CODE(mfo)); if( U_FAILURE(INTL_DATA_ERROR_CODE((mfo))) ) { - intl_error_set( NULL, U_ILLEGAL_ARGUMENT_ERROR, + intl_error_set(/* intl_error* */ NULL, U_ILLEGAL_ARGUMENT_ERROR, "msgfmt_format_message: error converting pattern to UTF-16", 0 ); RETURN_FALSE; } @@ -113,7 +113,7 @@ PHP_FUNCTION( msgfmt_format_message ) #ifdef MSG_FORMAT_QUOTE_APOS if(msgformat_fix_quotes(&spattern, &spattern_len, &INTL_DATA_ERROR_CODE(mfo)) != SUCCESS) { - intl_error_set( NULL, U_INVALID_FORMAT_ERROR, + intl_error_set(/* intl_error* */ NULL, U_INVALID_FORMAT_ERROR, "msgfmt_format_message: error converting pattern to quote-friendly format", 0 ); RETURN_FALSE; } @@ -134,15 +134,14 @@ PHP_FUNCTION( msgfmt_format_message ) spprintf( &msg, 0, "pattern syntax error (%s)", parse_error_str.s? ZSTR_VAL(parse_error_str.s) : "unknown parser error" ); smart_str_free( &parse_error_str ); - intl_error_set_code( NULL, INTL_DATA_ERROR_CODE( mfo ) ); - intl_errors_set_custom_msg( INTL_DATA_ERROR_P( mfo ), msg, 1 ); + /* Pass NULL to intl_error* parameter to store message in global Intl error msg stack */ + intl_error_set_code(/* intl_error* */ NULL, INTL_DATA_ERROR_CODE( mfo ) ); + intl_errors_set_custom_msg(/* intl_error* */ NULL, msg, 1 ); efree( msg ); } else { - intl_errors_set_custom_msg( INTL_DATA_ERROR_P(mfo), "Creating message formatter failed", 0 ); + intl_errors_set_custom_msg(/* intl_error* */ NULL, "Creating message formatter failed", 0 ); } - /* Reset custom error message as this is a static method that has no object */ - intl_errors_reset(INTL_DATA_ERROR_P(mfo)); umsg_close(MSG_FORMAT_OBJECT(mfo)); RETURN_FALSE; } diff --git a/ext/intl/tests/gh11658.phpt b/ext/intl/tests/gh11658.phpt index b29786255cacb..f0cfab9280ef6 100644 --- a/ext/intl/tests/gh11658.phpt +++ b/ext/intl/tests/gh11658.phpt @@ -9,7 +9,13 @@ ini_set("intl.error_level", E_WARNING); $s = MessageFormatter::formatMessage('en', 'some {wrong.format}', []); var_dump($s); + +$s = msgfmt_format_message('en', 'some {wrong.format}', []); +var_dump($s); ?> --EXPECTF-- Warning: MessageFormatter::formatMessage(): pattern syntax error (parse error at offset 6, after "some {", before or at "wrong.format}") in %s on line %d bool(false) + +Warning: msgfmt_format_message(): pattern syntax error (parse error at offset 6, after "some {", before or at "wrong.format}") in %s on line %d +bool(false) diff --git a/ext/intl/tests/gh12020.phpt b/ext/intl/tests/gh12020.phpt new file mode 100644 index 0000000000000..e4102606ca54e --- /dev/null +++ b/ext/intl/tests/gh12020.phpt @@ -0,0 +1,22 @@ +--TEST-- +GitHub #12020 intl_get_error_message() broken after MessageFormatter::formatMessage() fails +--EXTENSIONS-- +intl +--FILE-- + +--EXPECT-- +bool(false) +string(128) "pattern syntax error (parse error at offset 19, after " message with {", before or at "invalid format}"): U_PATTERN_SYNTAX_ERROR" +bool(false) +string(116) "pattern syntax error (parse error at offset 6, after "some {", before or at "wrong.format}"): U_PATTERN_SYNTAX_ERROR" +bool(false) +string(128) "pattern syntax error (parse error at offset 19, after " message with {", before or at "invalid format}"): U_PATTERN_SYNTAX_ERROR" +bool(false) +string(116) "pattern syntax error (parse error at offset 6, after "some {", before or at "wrong.format}"): U_PATTERN_SYNTAX_ERROR" diff --git a/ext/mysqli/tests/gh8978.phpt b/ext/mysqli/tests/gh8978.phpt new file mode 100644 index 0000000000000..92de63b381ca8 --- /dev/null +++ b/ext/mysqli/tests/gh8978.phpt @@ -0,0 +1,29 @@ +--TEST-- +Bug GH-8267 (Invalid error message when connection via SSL fails) +--EXTENSIONS-- +mysqli +--SKIPIF-- + +--FILE-- +getMessage()."\n"; +} + +echo 'done!'; +?> +--EXPECTF-- +Warning: failed loading cafile stream: `x509.ca' in %s +Cannot connect to MySQL using SSL +done! diff --git a/ext/mysqlnd/mysqlnd_commands.c b/ext/mysqlnd/mysqlnd_commands.c index 40821bb1efedd..ae64560850531 100644 --- a/ext/mysqlnd/mysqlnd_commands.c +++ b/ext/mysqlnd/mysqlnd_commands.c @@ -571,6 +571,8 @@ MYSQLND_METHOD(mysqlnd_command, enable_ssl)(MYSQLND_CONN_DATA * const conn, cons conn->vio->data->m.set_client_option(conn->vio, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (const char *) &verify); if (FAIL == conn->vio->data->m.enable_ssl(conn->vio)) { + SET_CONNECTION_STATE(&conn->state, CONN_QUIT_SENT); + SET_CLIENT_ERROR(conn->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Cannot connect to MySQL using SSL"); goto end; } } diff --git a/ext/mysqlnd/mysqlnd_connection.c b/ext/mysqlnd/mysqlnd_connection.c index ed4d1af277ddc..fa50c23c9d0bf 100644 --- a/ext/mysqlnd/mysqlnd_connection.c +++ b/ext/mysqlnd/mysqlnd_connection.c @@ -289,7 +289,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, free_contents)(MYSQLND_CONN_DATA * conn) mysqlnd_set_persistent_string(&conn->unix_socket, NULL, 0, pers); DBG_INF_FMT("scheme=%s", conn->scheme.s); mysqlnd_set_persistent_string(&conn->scheme, NULL, 0, pers); - + if (conn->server_version) { mnd_pefree(conn->server_version, pers); conn->server_version = NULL; @@ -725,19 +725,20 @@ MYSQLND_METHOD(mysqlnd_conn_data, connect)(MYSQLND_CONN_DATA * conn, DBG_RETURN(PASS); } err: - if (transport.s) { - mnd_sprintf_free(transport.s); - transport.s = NULL; - } - - DBG_ERR_FMT("[%u] %.128s (trying to connect via %s)", conn->error_info->error_no, conn->error_info->error, conn->scheme.s); + DBG_ERR_FMT("[%u] %.128s (trying to connect via %s)", conn->error_info->error_no, conn->error_info->error, transport.s ? transport.s : conn->scheme.s); if (!conn->error_info->error_no) { + /* There was an unknown error if the connection failed but we have no error number */ char * msg; - mnd_sprintf(&msg, 0, "%s (trying to connect via %s)",conn->error_info->error, conn->scheme.s); + mnd_sprintf(&msg, 0, "Unknown error while trying to connect via %s", transport.s ? transport.s : conn->scheme.s); SET_CLIENT_ERROR(conn->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, msg); mnd_sprintf_free(msg); } + if (transport.s) { + mnd_sprintf_free(transport.s); + transport.s = NULL; + } + conn->m->free_contents(conn); MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CONNECT_FAILURE); DBG_RETURN(FAIL); diff --git a/ext/mysqlnd/mysqlnd_vio.c b/ext/mysqlnd/mysqlnd_vio.c index 79fc92d500a25..312bf7e2782ff 100644 --- a/ext/mysqlnd/mysqlnd_vio.c +++ b/ext/mysqlnd/mysqlnd_vio.c @@ -569,7 +569,6 @@ MYSQLND_METHOD(mysqlnd_vio, enable_ssl)(MYSQLND_VIO * const net) php_stream_xport_crypto_enable(net_stream, 1) < 0) { DBG_ERR("Cannot connect to MySQL by using SSL"); - php_error_docref(NULL, E_WARNING, "Cannot connect to MySQL by using SSL"); DBG_RETURN(FAIL); } net->data->ssl = TRUE; diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c index cf25ef7cdee4e..fa9d8330fe605 100644 --- a/ext/odbc/php_odbc.c +++ b/ext/odbc/php_odbc.c @@ -675,12 +675,14 @@ void odbc_transact(INTERNAL_FUNCTION_PARAMETERS, int type) /* }}} */ /* {{{ _close_pconn_with_res */ -static int _close_pconn_with_res(zend_resource *le, zend_resource *res) +static int _close_pconn_with_res(zval *zv, void *p) { - if (le->type == le_pconn && (((odbc_connection *)(le->ptr))->res == res)){ - return 1; - }else{ - return 0; + zend_resource *le = Z_RES_P(zv); + zend_resource *res = (zend_resource*)p; + if (le->type == le_pconn && (((odbc_connection *)(le->ptr))->res == res)) { + return ZEND_HASH_APPLY_REMOVE; + } else { + return ZEND_HASH_APPLY_KEEP; } } /* }}} */ @@ -759,7 +761,7 @@ PHP_FUNCTION(odbc_close_all) zend_list_close(p); /* Delete the persistent connection */ zend_hash_apply_with_argument(&EG(persistent_list), - (apply_func_arg_t) _close_pconn_with_res, (void *)p); + _close_pconn_with_res, (void *)p); } } } ZEND_HASH_FOREACH_END(); @@ -845,6 +847,7 @@ PHP_FUNCTION(odbc_prepare) break; default: odbc_sql_error(conn, result->stmt, "SQLPrepare"); + efree(result); RETURN_FALSE; } @@ -2329,7 +2332,7 @@ PHP_FUNCTION(odbc_close) zend_list_close(Z_RES_P(pv_conn)); if(is_pconn){ - zend_hash_apply_with_argument(&EG(persistent_list), (apply_func_arg_t) _close_pconn_with_res, (void *) Z_RES_P(pv_conn)); + zend_hash_apply_with_argument(&EG(persistent_list), _close_pconn_with_res, (void *) Z_RES_P(pv_conn)); } } /* }}} */ diff --git a/ext/odbc/tests/config.inc b/ext/odbc/tests/config.inc index 78be4b2d99adb..8d74feeb6ffb7 100644 --- a/ext/odbc/tests/config.inc +++ b/ext/odbc/tests/config.inc @@ -1,8 +1,5 @@ +--FILE-- + +--EXPECT-- +string(22) "PHP odbc_pconnect test" +string(22) "PHP odbc_pconnect test" +NULL diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index a4d7864332723..a61c46523181d 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1062,6 +1062,7 @@ static const zend_op *zend_jit_trace_find_init_fcall_op(zend_jit_trace_rec *p, c case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: + case ZEND_CALLABLE_CONVERT: call_level++; break; } @@ -6202,6 +6203,7 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par case ZEND_DO_ICALL: case ZEND_DO_UCALL: case ZEND_DO_FCALL_BY_NAME: + case ZEND_CALLABLE_CONVERT: frame->call_level--; } diff --git a/ext/opcache/tests/bug68104.phpt b/ext/opcache/tests/bug68104.phpt index 0726d96a1cd08..b4b4edfd34415 100644 --- a/ext/opcache/tests/bug68104.phpt +++ b/ext/opcache/tests/bug68104.phpt @@ -8,6 +8,10 @@ opcache.enable_cli=1 disable_functions=dl --EXTENSIONS-- opcache +--SKIPIF-- + --FILE-- ce; ZEND_ASSERT(class->ce_flags & ZEND_ACC_ENUM); diff --git a/ext/reflection/tests/gh11937_1.inc b/ext/reflection/tests/gh11937_1.inc new file mode 100644 index 0000000000000..4d55213f2f831 --- /dev/null +++ b/ext/reflection/tests/gh11937_1.inc @@ -0,0 +1,13 @@ +getAttributes('Attr')[0]; + +?> +--EXPECT-- +array(2) { + [0]=> + enum(TestEnum::One) + [1]=> + enum(TestEnum::Two) +} +Attribute [ Attr ] { + - Arguments [1] { + Argument #0 [ new \Foo(TestEnum::CASES) ] + } +} diff --git a/ext/reflection/tests/gh11937_2.inc b/ext/reflection/tests/gh11937_2.inc new file mode 100644 index 0000000000000..d6e21e6ba5c55 --- /dev/null +++ b/ext/reflection/tests/gh11937_2.inc @@ -0,0 +1,4 @@ +getAttributes('Attr')[0]; + +?> +--EXPECT-- +array(1) { + [0]=> + object(Foo)#1 (0) { + } +} +Attribute [ Attr ] { + - Arguments [1] { + Argument #0 [ FOOS ] + } +} diff --git a/ext/simplexml/simplexml.c b/ext/simplexml/simplexml.c index 1e09fa355c73b..7a6719a5737ee 100644 --- a/ext/simplexml/simplexml.c +++ b/ext/simplexml/simplexml.c @@ -1322,7 +1322,7 @@ PHP_METHOD(SimpleXMLElement, xpath) for (i = 0; i < result->nodeNr; ++i) { nodeptr = result->nodeTab[i]; - if (nodeptr->type == XML_TEXT_NODE || nodeptr->type == XML_ELEMENT_NODE || nodeptr->type == XML_ATTRIBUTE_NODE) { + if (nodeptr->type == XML_TEXT_NODE || nodeptr->type == XML_ELEMENT_NODE || nodeptr->type == XML_ATTRIBUTE_NODE || nodeptr->type == XML_PI_NODE) { /** * Detect the case where the last selector is text(), simplexml * always accesses the text() child by default, therefore we assign diff --git a/ext/simplexml/tests/bug52751.phpt b/ext/simplexml/tests/bug52751.phpt new file mode 100644 index 0000000000000..de5d58e9e632a --- /dev/null +++ b/ext/simplexml/tests/bug52751.phpt @@ -0,0 +1,58 @@ +--TEST-- +Bug #52751 (XPath processing-instruction() function is not supported) +--EXTENSIONS-- +simplexml +--FILE-- + + + text node + + + +XML; + +$sxe = simplexml_load_string($xml); + +var_dump( + $sxe->xpath('//bar') +); + +var_dump( + $sxe->xpath('//processing-instruction(\'baz\')') +); + +foreach ($sxe->xpath('//processing-instruction()') as $pi) { + var_dump($pi->getName()); +} + +?> +--EXPECT-- +array(3) { + [0]=> + object(SimpleXMLElement)#2 (1) { + [0]=> + string(9) "text node" + } + [1]=> + object(SimpleXMLElement)#3 (1) { + ["baz"]=> + object(SimpleXMLElement)#5 (0) { + } + } + [2]=> + object(SimpleXMLElement)#4 (1) { + ["foo"]=> + object(SimpleXMLElement)#5 (0) { + } + } +} +array(1) { + [0]=> + object(SimpleXMLElement)#4 (0) { + } +} +string(3) "baz" +string(3) "foo" diff --git a/ext/soap/tests/bug71610.phpt b/ext/soap/tests/bug71610.phpt index 92491d0576996..ef0803134fb42 100644 --- a/ext/soap/tests/bug71610.phpt +++ b/ext/soap/tests/bug71610.phpt @@ -8,7 +8,7 @@ if (getenv("SKIP_ONLINE_TESTS")) die("skip online test"); ?> --FILE-- blahblah(); } catch(SoapFault $e) { diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 669f6f7b463ac..dbe110f7c40da 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -1061,13 +1061,12 @@ static void spl_array_set_array(zval *object, spl_array_object *intern, zval *ar ZVAL_ARR(&intern->array, zend_array_dup(Z_ARR_P(array))); if (intern->is_child) { - Z_TRY_DELREF_P(&intern->bucket->val); + Z_TRY_DELREF(intern->bucket->val); /* * replace bucket->val with copied array, so the changes between * parent and child object can affect each other. */ - intern->bucket->val = intern->array; - Z_TRY_ADDREF_P(&intern->array); + ZVAL_COPY(&intern->bucket->val, &intern->array); } } } else { diff --git a/ext/spl/tests/gh11972.phpt b/ext/spl/tests/gh11972.phpt new file mode 100644 index 0000000000000..d88d7c5ecae40 --- /dev/null +++ b/ext/spl/tests/gh11972.phpt @@ -0,0 +1,196 @@ +--TEST-- +GH-11972 (RecursiveCallbackFilterIterator regression in 8.1.18) +--EXTENSIONS-- +spl +--FILE-- +setMaxDepth(20); + foreach ($recursive_iterator as $value) { + // Avoid recursion by marking where we've been. + $value['#override_mode_breadcrumb'] = true; + } + return \iterator_to_array($recursive_iterator); + } + + public function isCyclic($current, string $key, \RecursiveArrayIterator $iterator): bool { + var_dump($current); + if (!is_array($current)) { + return false; + } + // Avoid infinite loops by checking if we've been here before. + // e.g. View > query > view > query ... + if (isset($current['#override_mode_breadcrumb'])) { + return false; + } + return true; + } +} + +$test_array['e']['p'][] = ['a', 'a']; +$test_array['e']['p'][] = ['b', 'b']; +$test_array['e']['p'][] = ['c', 'c']; +$serialized = serialize($test_array); +$unserialized = unserialize($serialized); + +$test_class = new RecursiveFilterTest(); +$test_class->traverse($unserialized); + +echo "Done\n"; + +?> +--EXPECT-- +array(1) { + ["p"]=> + array(3) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "a" + } + [1]=> + array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "b" + } + [2]=> + array(2) { + [0]=> + string(1) "c" + [1]=> + string(1) "c" + } + } +} +array(3) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "a" + } + [1]=> + array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "b" + } + [2]=> + array(2) { + [0]=> + string(1) "c" + [1]=> + string(1) "c" + } +} +array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "a" +} +string(1) "a" +string(1) "a" +array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "b" +} +string(1) "b" +string(1) "b" +array(2) { + [0]=> + string(1) "c" + [1]=> + string(1) "c" +} +string(1) "c" +string(1) "c" +array(1) { + ["p"]=> + array(3) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "a" + } + [1]=> + array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "b" + } + [2]=> + array(2) { + [0]=> + string(1) "c" + [1]=> + string(1) "c" + } + } +} +array(3) { + [0]=> + array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "a" + } + [1]=> + array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "b" + } + [2]=> + array(2) { + [0]=> + string(1) "c" + [1]=> + string(1) "c" + } +} +array(2) { + [0]=> + string(1) "a" + [1]=> + string(1) "a" +} +string(1) "a" +string(1) "a" +array(2) { + [0]=> + string(1) "b" + [1]=> + string(1) "b" +} +string(1) "b" +string(1) "b" +array(2) { + [0]=> + string(1) "c" + [1]=> + string(1) "c" +} +string(1) "c" +string(1) "c" +Done diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 65367982f7f6e..3bb8a3f87b546 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -2229,6 +2229,42 @@ static void php_sqlite3_object_free_storage(zend_object *object) /* {{{ */ } /* }}} */ +static HashTable *php_sqlite3_get_gc(zend_object *object, zval **table, int *n) +{ + php_sqlite3_db_object *intern = php_sqlite3_db_from_obj(object); + + if (intern->funcs == NULL && intern->collations == NULL) { + /* Fast path without allocations */ + *table = NULL; + *n = 0; + return zend_std_get_gc(object, table, n); + } else { + zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + + php_sqlite3_func *func = intern->funcs; + while (func != NULL) { + zend_get_gc_buffer_add_zval(gc_buffer, &func->func); + zend_get_gc_buffer_add_zval(gc_buffer, &func->step); + zend_get_gc_buffer_add_zval(gc_buffer, &func->fini); + func = func->next; + } + + php_sqlite3_collation *collation = intern->collations; + while (collation != NULL) { + zend_get_gc_buffer_add_zval(gc_buffer, &collation->cmp_func); + collation = collation->next; + } + + zend_get_gc_buffer_use(gc_buffer, table, n); + + if (object->properties == NULL && object->ce->default_properties_count == 0) { + return NULL; + } else { + return zend_std_get_properties(object); + } + } +} + static void php_sqlite3_stmt_object_free_storage(zend_object *object) /* {{{ */ { php_sqlite3_stmt *intern = php_sqlite3_stmt_from_obj(object); @@ -2364,6 +2400,7 @@ PHP_MINIT_FUNCTION(sqlite3) sqlite3_object_handlers.offset = XtOffsetOf(php_sqlite3_db_object, zo); sqlite3_object_handlers.clone_obj = NULL; sqlite3_object_handlers.free_obj = php_sqlite3_object_free_storage; + sqlite3_object_handlers.get_gc = php_sqlite3_get_gc; php_sqlite3_sc_entry = register_class_SQLite3(); php_sqlite3_sc_entry->create_object = php_sqlite3_object_new; diff --git a/ext/sqlite3/tests/gh11878.phpt b/ext/sqlite3/tests/gh11878.phpt new file mode 100644 index 0000000000000..4a8a408a2679b --- /dev/null +++ b/ext/sqlite3/tests/gh11878.phpt @@ -0,0 +1,31 @@ +--TEST-- +GH-11878 (SQLite3 callback functions cause a memory leak with a callable array) +--EXTENSIONS-- +sqlite3 +--FILE-- +sqlite = new SQLite3(":memory:"); + if ($aggregates) { + $this->sqlite->createAggregate("indexes", array($this, "SQLiteIndex"), array($this, "SQLiteFinal"), 0); + } + if ($normalFunctions) { + $this->sqlite->createFunction("func", array($this, "SQLiteIndex"), 0); + $this->sqlite->createCollation("collation", array($this, "SQLiteIndex")); + } + } + public function SQLiteIndex() {} + public function SQLiteFinal() {} +} + +// Test different combinations to check for null pointer derefs +$x = new Foo(true, true); +$y = new Foo(false, true); +$z = new Foo(true, false); +$w = new Foo(false, false); +?> +Done +--EXPECT-- +Done diff --git a/ext/standard/string.c b/ext/standard/string.c index c103c9b654d43..e8c682a4cb77b 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1201,7 +1201,7 @@ PHP_FUNCTION(implode) if (pieces == NULL) { if (arg1_array == NULL) { - zend_type_error("%s(): Argument #1 ($pieces) must be of type array, string given", get_active_function_name()); + zend_type_error("%s(): Argument #1 ($array) must be of type array, string given", get_active_function_name()); RETURN_THROWS(); } diff --git a/ext/standard/tests/file/file.inc b/ext/standard/tests/file/file.inc index 0292bea118281..d4ad02a363bd5 100644 --- a/ext/standard/tests/file/file.inc +++ b/ext/standard/tests/file/file.inc @@ -590,6 +590,8 @@ $all_stat_keys = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, "rdev", "size", "atime", "mtime", "ctime", "blksize", "blocks"); +$stat_time_diff_keys = array(8, 'atime'); + function compare_stats($stat1, $stat2, $fields, $op = "==", $flag = false ) { // dump the stat if requested if ( $flag == true ) { @@ -606,8 +608,13 @@ function compare_stats($stat1, $stat2, $fields, $op = "==", $flag = false ) { { case "==": if ( $stat1[ $fields[$index] ] != $stat2[ $fields[$index] ] ) { - $result = false; - echo "Error: stat1 do not match with stat2 at key value: $fields[$index]\n"; + if ( ! in_array( $index, $stat_time_diff_keys ) ) { + $result = false; + echo "Error: stat1 do not match with stat2 at key value: $fields[$index]\n"; + } elseif (abs($stat1[ $fields[$index] ] - $stat2[ $fields[$index] ]) > 1) { + $result = false; + echo "Error: stat1 differs too much from stat2 at key value: $fields[$index]\n"; + } } break; diff --git a/ext/standard/tests/general_functions/dl-check-enabled.phpt b/ext/standard/tests/general_functions/dl-check-enabled.phpt index c5a6eb50cc75f..a0c4489547cda 100644 --- a/ext/standard/tests/general_functions/dl-check-enabled.phpt +++ b/ext/standard/tests/general_functions/dl-check-enabled.phpt @@ -9,6 +9,7 @@ $enabled_sapi = array('cgi-fcgi', 'cli', 'embed', 'fpm'); if (!in_array(php_sapi_name(), $enabled_sapi)) { die('skip dl() is not enabled for ' . php_sapi_name()); } +if (getenv('SKIP_ASAN')) die('skip dl() crashes LSan'); ?> --INI-- enable_dl=0 diff --git a/ext/standard/tests/general_functions/dl-cve-2007-4887.phpt b/ext/standard/tests/general_functions/dl-cve-2007-4887.phpt index effae83464ed5..d4920365574d8 100644 --- a/ext/standard/tests/general_functions/dl-cve-2007-4887.phpt +++ b/ext/standard/tests/general_functions/dl-cve-2007-4887.phpt @@ -6,6 +6,7 @@ $enabled_sapi = array('cgi-fcgi', 'cli', 'embed', 'fpm'); if (!in_array(php_sapi_name(), $enabled_sapi)) { die('skip dl() is not enabled for ' . php_sapi_name()); } +if (getenv('SKIP_ASAN')) die('skip dl() crashes LSan'); ?> --INI-- enable_dl=1 diff --git a/ext/standard/tests/general_functions/dl-full-path-not-supported.phpt b/ext/standard/tests/general_functions/dl-full-path-not-supported.phpt index aaf7d042e536a..ac317df429b02 100644 --- a/ext/standard/tests/general_functions/dl-full-path-not-supported.phpt +++ b/ext/standard/tests/general_functions/dl-full-path-not-supported.phpt @@ -9,6 +9,7 @@ $enabled_sapi = array('cgi-fcgi', 'cli', 'embed', 'fpm'); if (!in_array(php_sapi_name(), $enabled_sapi)) { die('skip dl() is not enabled for ' . php_sapi_name()); } +if (getenv('SKIP_ASAN')) die('skip dl() crashes LSan'); ?> --INI-- enable_dl=1 diff --git a/ext/standard/tests/general_functions/gh9589.phpt b/ext/standard/tests/general_functions/gh9589.phpt index a26f052debeb8..e6f0fb20ecf7a 100644 --- a/ext/standard/tests/general_functions/gh9589.phpt +++ b/ext/standard/tests/general_functions/gh9589.phpt @@ -2,6 +2,10 @@ dl() segfaults when module is already loaded --EXTENSIONS-- dl_test +--SKIPIF-- + --FILE-- +#include "php.h" + +#define DUMP(s) php_output_write((s), sizeof((s)) - 1) + +static zend_class_entry *traversable_test_ce; + +// Dummy iterator that yields numbers from 0..4, +// while printing operations to the output buffer +typedef struct { + zend_object_iterator intern; + zval current; +} test_traversable_it; + +static test_traversable_it *test_traversable_it_fetch(zend_object_iterator *iter) { + return (test_traversable_it *)iter; +} + +static void test_traversable_it_dtor(zend_object_iterator *iter) { + DUMP("TraversableTest::drop\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + zval_ptr_dtor(&iterator->intern.data); +} + +static void test_traversable_it_rewind(zend_object_iterator *iter) { + DUMP("TraversableTest::rewind\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + ZVAL_LONG(&iterator->current, 0); +} + +static void test_traversable_it_next(zend_object_iterator *iter) { + DUMP("TraversableTest::next\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + ZVAL_LONG(&iterator->current, Z_LVAL(iterator->current) + 1); +} + +static int test_traversable_it_valid(zend_object_iterator *iter) { + DUMP("TraversableTest::valid\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + if (Z_LVAL(iterator->current) < 4) { + return SUCCESS; + } + return FAILURE; +} + +static void test_traversable_it_key(zend_object_iterator *iter, zval *return_value) { + DUMP("TraversableTest::key\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + ZVAL_LONG(return_value, Z_LVAL(iterator->current)); +} + +static zval *test_traversable_it_current(zend_object_iterator *iter) { + DUMP("TraversableTest::current\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + return &iterator->current; +} + +static const zend_object_iterator_funcs test_traversable_it_vtable = { + test_traversable_it_dtor, + test_traversable_it_valid, + test_traversable_it_current, + test_traversable_it_key, + test_traversable_it_next, + test_traversable_it_rewind, + NULL, // invalidate_current + NULL, // get_gc +}; + +static zend_object_iterator *test_traversable_get_iterator( + zend_class_entry *ce, + zval *object, + int by_ref +) { + test_traversable_it *iterator; + + if (by_ref) { + zend_throw_error(NULL, "An iterator cannot be used with foreach by reference"); + return NULL; + } + + iterator = emalloc(sizeof(test_traversable_it)); + zend_iterator_init((zend_object_iterator*)iterator); + + ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); + iterator->intern.funcs = &test_traversable_it_vtable; + ZVAL_LONG(&iterator->current, 0); + + return (zend_object_iterator*)iterator; +} + +ZEND_METHOD(ZendTest_Iterators_TraversableTest, __construct) { + ZEND_PARSE_PARAMETERS_NONE(); +} + +ZEND_METHOD(ZendTest_Iterators_TraversableTest, getIterator) { + ZEND_PARSE_PARAMETERS_NONE(); + zend_create_internal_iterator_zval(return_value, ZEND_THIS); +} + +void zend_test_iterators_init(void) { + traversable_test_ce = register_class_ZendTest_Iterators_TraversableTest(zend_ce_aggregate); + traversable_test_ce->get_iterator = test_traversable_get_iterator; +} diff --git a/ext/zend_test/iterators.h b/ext/zend_test/iterators.h new file mode 100644 index 0000000000000..cef09109e239a --- /dev/null +++ b/ext/zend_test/iterators.h @@ -0,0 +1,21 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_TEST_ITERATORS_H +#define ZEND_TEST_ITERATORS_H + +void zend_test_iterators_init(void); + +#endif + diff --git a/ext/zend_test/iterators.stub.php b/ext/zend_test/iterators.stub.php new file mode 100644 index 0000000000000..9b681e36b949e --- /dev/null +++ b/ext/zend_test/iterators.stub.php @@ -0,0 +1,14 @@ +ce_flags |= ZEND_ACC_FINAL; + zend_class_implements(class_entry, 1, class_entry_IteratorAggregate); + + return class_entry; +} diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 8b2ca68e4e72a..88b7d5d904eef 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -24,6 +24,7 @@ #include "php_test.h" #include "observer.h" #include "fiber.h" +#include "iterators.h" #include "zend_attributes.h" #include "zend_enum.h" #include "zend_interfaces.h" @@ -845,6 +846,7 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU); zend_test_fiber_init(); + zend_test_iterators_init(); return SUCCESS; } diff --git a/ext/zend_test/tests/iterators/double-rewind.phpt b/ext/zend_test/tests/iterators/double-rewind.phpt new file mode 100644 index 0000000000000..179dd1de10775 --- /dev/null +++ b/ext/zend_test/tests/iterators/double-rewind.phpt @@ -0,0 +1,40 @@ +--TEST-- +Tests that internal iterator's rewind function is called once +--EXTENSIONS-- +zend_test +--FILE-- +getIterator(); +var_dump($it); +foreach ($it as $key => $value) { + echo "{$key} => {$value}\n"; +} +?> +--EXPECT-- +object(InternalIterator)#3 (0) { +} +TraversableTest::rewind +TraversableTest::valid +TraversableTest::current +TraversableTest::key +0 => 0 +TraversableTest::next +TraversableTest::valid +TraversableTest::current +TraversableTest::key +1 => 1 +TraversableTest::next +TraversableTest::valid +TraversableTest::current +TraversableTest::key +2 => 2 +TraversableTest::next +TraversableTest::valid +TraversableTest::current +TraversableTest::key +3 => 3 +TraversableTest::next +TraversableTest::valid +TraversableTest::drop diff --git a/main/php_version.h b/main/php_version.h index 7e4f058576ce5..edfd8085aa707 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 2 -#define PHP_RELEASE_VERSION 10 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.10-dev" -#define PHP_VERSION_ID 80210 +#define PHP_RELEASE_VERSION 11 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.2.11" +#define PHP_VERSION_ID 80211 diff --git a/php.ini-development b/php.ini-development index 62320e36e066d..61cd33e89504c 100644 --- a/php.ini-development +++ b/php.ini-development @@ -1201,9 +1201,6 @@ mysqli.default_user = ; https://php.net/mysqli.default-pw mysqli.default_pw = -; Allow or prevent reconnect -mysqli.reconnect = Off - ; If this option is enabled, closing a persistent connection will rollback ; any pending transactions of this connection, before it is put back ; into the persistent connection pool. diff --git a/php.ini-production b/php.ini-production index cb36654a72672..2189660c02c48 100644 --- a/php.ini-production +++ b/php.ini-production @@ -1203,9 +1203,6 @@ mysqli.default_user = ; https://php.net/mysqli.default-pw mysqli.default_pw = -; Allow or prevent reconnect -mysqli.reconnect = Off - ; If this option is enabled, closing a persistent connection will rollback ; any pending transactions of this connection, before it is put back ; into the persistent connection pool. diff --git a/run-tests.php b/run-tests.php index 96859037c8def..19335cbf697a3 100755 --- a/run-tests.php +++ b/run-tests.php @@ -1204,6 +1204,10 @@ function system_with_timeout( } $timeout = $valgrind ? 300 : ($env['TEST_TIMEOUT'] ?? 60); + /* ASAN can cause a ~2-3x slowdown. */ + if (isset($env['SKIP_ASAN'])) { + $timeout *= 3; + } while (true) { /* hide errors from interrupted syscalls */ @@ -1807,7 +1811,6 @@ function run_test(string $php, $file, array $env): string $skipCache = new SkipCache($enableSkipCache, $cfg['keep']['skip']); } - $retriable = true; $retried = false; retry: @@ -1851,7 +1854,6 @@ function run_test(string $php, $file, array $env): string $tested = $test->getName(); if ($test->hasSection('FILE_EXTERNAL')) { - $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); } @@ -1880,7 +1882,6 @@ function run_test(string $php, $file, array $env): string } $php = $php_cgi . ' -C '; $uses_cgi = true; - $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat'); } @@ -1898,7 +1899,6 @@ function run_test(string $php, $file, array $env): string } else { return skip_test($tested, $tested_file, $shortname, 'phpdbg not available'); } - $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'phpdbg does not support --repeat'); } @@ -1906,7 +1906,6 @@ function run_test(string $php, $file, array $env): string foreach (['CLEAN', 'STDIN', 'CAPTURE_STDIO'] as $section) { if ($test->hasSection($section)) { - $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, "Test with $section might not be repeatable"); } @@ -2092,7 +2091,6 @@ function run_test(string $php, $file, array $env): string settings2array(preg_split("/[\n\r]+/", $ini), $ini_settings); if (isset($ini_settings['opcache.opt_debug_level'])) { - $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); } @@ -2626,7 +2624,7 @@ function run_test(string $php, $file, array $env): string $wanted_re = null; } - if (!$passed && !$retried && $retriable && error_may_be_retried($test, $output)) { + if (!$passed && !$retried && error_may_be_retried($test, $output)) { $retried = true; goto retry; }