diff --git a/NEWS b/NEWS index b6ae41f72386d..661be47ada6e3 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,66 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.1 +02 Feb 2023, PHP 8.2.2 + +- Core: + . Fixed bug GH-10200 (zif_get_object_vars: + Assertion `!(((__ht)->u.flags & (1<<2)) != 0)' failed). (nielsdos) + . Fix GH-10251 (Assertion `(flag & (1<<3)) == 0' failed). (nielsdos) + . Fix GH-10240 (Assertion failure when adding more than 2**30 elements to an + unpacked array). (Arnaud) + . Fix GH-9735 (Fiber stack variables do not participate in cycle collector). + (Arnaud) + . Fix GH-9675 (Broken run_time_cache init for internal enum methods). + (Petar Obradović, Bob) + +- FPM: + . Fixed bug #77106 (Missing separator in FPM FastCGI errors). (Jakub Zelenka) + . Fixed bug GH-9981 (FPM does not reset fastcgi.error_header). + (Jakub Zelenka) + . Fixed bug #68591 (Configuration test does not perform UID lookups). + (Jakub Zelenka) + . Fixed memory leak when running FPM config test. (Jakub Zelenka) + . Fixed bug #67244 (Wrong owner:group for listening unix socket). + (Jakub Zelenka) + +- Hash: + . Handle exceptions from __toString in XXH3's initialization (nielsdos) + +- LDAP: + . Fixed bug GH-10112 (LDAP\Connection::__construct() refers to ldap_create()). + (cmb) + +- Opcache: + . Fix inverted bailout value in zend_runtime_jit() (Max Kellermann). + . Fix access to uninitialized variable in accel_preload(). (nielsdos) + . Fix zend_jit_find_trace() crashes. (Max Kellermann) + . Added missing lock for EXIT_INVALIDATE in zend_jit_trace_exit. (Max Kellermann) + +- Phar: + . Fix wrong flags check for compression method in phar_object.c (nielsdos) + +- PHPDBG: + . Fix undefined behaviour in phpdbg_load_module_or_extension(). (nielsdos) + . Fix NULL pointer dereference in phpdbg_create_conditional_breal(). (nielsdos) + . Fix GH-9710: phpdbg memory leaks by option "-h" (nielsdos) + . Fix phpdbg segmentation fault in case of malformed input (nielsdos) + +- Posix: + . Fix memory leak in posix_ttyname() (girgias) + +- Random: + . Fixed bug GH-10247 (Theoretical file descriptor leak for /dev/urandom). (timwolla) + +- Standard: + . Fix GH-10187 (Segfault in stripslashes() with arm64). (nielsdos) + . Fixed bug GH-10214 (Incomplete validation of object syntax during + unserialize()). (timwolla) + . Fix substr_replace with slots in repl_ht being UNDEF. (nielsdos) + +- XMLWriter + . Fix missing check for xmlTextWriterEndElement (nielsdos) + +05 Jan 2023, PHP 8.2.1 - Core: . Fixed bug GH-9905 (constant() behaves inconsistent when class is undefined). @@ -51,6 +111,10 @@ PHP NEWS . Fixed bug GH-9971 (Incorrect NUMERIC value returned from PDO_Firebird). (cmb) +- PDO/SQLite: + . Fixed bug #81740 (PDO::quote() may return unquoted string). (CVE-2022-31631) + (cmb) + - Session: . Fixed GH-9932 (session name silently fails with . and [). (David Carlier) diff --git a/UPGRADING b/UPGRADING index 5f69712a4b02b..bc92affa46e80 100644 --- a/UPGRADING +++ b/UPGRADING @@ -223,6 +223,10 @@ PHP 8.2 UPGRADE NOTES widened to iterable from Iterator, allowing arrays to be passed. RFC: https://wiki.php.net/rfc/iterator_xyz_accept_array +- Standard + . unserialize() now performs a stricter validation of the structure of serialized + objects. + - XML . xml_parser_set_option() now actually returns false when attempting to set a negative tag start. Previously it returned true while emitting an E_WARNING. diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index b176ba2704e26..b3f2099fb1955 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -256,6 +256,13 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array } break; + case ZEND_MATCH_ERROR: + if (opline->op1_type == IS_TMP_VAR) { + src = VAR_SOURCE(opline->op1); + VAR_SOURCE(opline->op1) = NULL; + } + break; + case ZEND_FREE: if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); @@ -624,13 +631,13 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_JMPNZ_EX: while (1) { if (opline->op1_type == IS_CONST) { - if (zend_is_true(&ZEND_OP1_LITERAL(opline)) == - (opline->opcode == ZEND_JMPZ_EX)) { + bool is_jmpz_ex = opline->opcode == ZEND_JMPZ_EX; + if (zend_is_true(&ZEND_OP1_LITERAL(opline)) == is_jmpz_ex) { ++(*opt_count); opline->opcode = ZEND_QM_ASSIGN; zval_ptr_dtor_nogc(&ZEND_OP1_LITERAL(opline)); - ZVAL_BOOL(&ZEND_OP1_LITERAL(opline), opline->opcode == ZEND_JMPZ_EX); + ZVAL_BOOL(&ZEND_OP1_LITERAL(opline), is_jmpz_ex); opline->op2.num = 0; block->successors_count = 1; block->successors[0] = block->successors[1]; diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 970557daf9a22..39c1bc12af902 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -1647,7 +1647,7 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx && Z_TYPE_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == IS_LONG && Z_LVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)) == 1 && ssa->ops[op_1].op1_use >= 0 - && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) { + && !(ssa->var_info[ssa->ops[op_1].op1_use].type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF))) { // op_1: ASSIGN_SUB #?.CV [undef,null,int,foat] -> #v.CV, int(1) => PRE_DEC #?.CV ->#v.CV diff --git a/Zend/tests/class_constants_007.phpt b/Zend/tests/class_constants_007.phpt new file mode 100644 index 0000000000000..d09a12e8c17b8 --- /dev/null +++ b/Zend/tests/class_constants_007.phpt @@ -0,0 +1,13 @@ +--TEST-- +Ownership of constant expression inhereted from immutable class should be transfered to class +--FILE-- + +--EXPECT-- +string(2) " " diff --git a/Zend/tests/fibers/gh10249.phpt b/Zend/tests/fibers/gh10249.phpt new file mode 100644 index 0000000000000..efbef9c42a2ed --- /dev/null +++ b/Zend/tests/fibers/gh10249.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-10249 (Assertion `size >= page_size + 1 * page_size' failed.) +--FILE-- +start(); +} catch (Exception $e) { + echo $e->getMessage() . "\n"; +} + +?> +--EXPECTF-- +Fiber stack size is too small, it needs to be at least %d bytes diff --git a/Zend/tests/fibers/gh10340-001.phpt b/Zend/tests/fibers/gh10340-001.phpt new file mode 100644 index 0000000000000..0c34b4a787bce --- /dev/null +++ b/Zend/tests/fibers/gh10340-001.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug GH-10340 001 (Assertion in zend_fiber_object_gc()) +--FILE-- +start(); +gc_collect_cycles(); +?> +==DONE== +--EXPECTF-- +Warning: Undefined variable $y in %s on line %d +==DONE== diff --git a/Zend/tests/fibers/gh10340-002.phpt b/Zend/tests/fibers/gh10340-002.phpt new file mode 100644 index 0000000000000..6c8f8016cbf12 --- /dev/null +++ b/Zend/tests/fibers/gh10340-002.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug GH-10340 002 (Assertion in zend_fiber_object_gc()) +--FILE-- +start(); +gc_collect_cycles(); +?> +==DONE== +--EXPECT-- +==DONE== diff --git a/Zend/tests/fibers/gh10340-003.phpt b/Zend/tests/fibers/gh10340-003.phpt new file mode 100644 index 0000000000000..6e59223a2a9c9 --- /dev/null +++ b/Zend/tests/fibers/gh10340-003.phpt @@ -0,0 +1,38 @@ +--TEST-- +Bug GH-10340 003 (Assertion in zend_fiber_object_gc()) +--FILE-- +start(); + +print "1\n"; + +$fiber = null; +gc_collect_cycles(); + +print "2\n"; +?> +==DONE== +--EXPECT-- +1 +C::__destruct +2 +==DONE== diff --git a/Zend/tests/fibers/gh9735-001.phpt b/Zend/tests/fibers/gh9735-001.phpt new file mode 100644 index 0000000000000..327e74323924a --- /dev/null +++ b/Zend/tests/fibers/gh9735-001.phpt @@ -0,0 +1,37 @@ +--TEST-- +Bug GH-9735 001 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECT-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-002.phpt b/Zend/tests/fibers/gh9735-002.phpt new file mode 100644 index 0000000000000..9a56c65f0abd9 --- /dev/null +++ b/Zend/tests/fibers/gh9735-002.phpt @@ -0,0 +1,41 @@ +--TEST-- +Bug GH-9735 002 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECT-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-003.phpt b/Zend/tests/fibers/gh9735-003.phpt new file mode 100644 index 0000000000000..03ea973b61dd4 --- /dev/null +++ b/Zend/tests/fibers/gh9735-003.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug GH-9735 003 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECT-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-004.phpt b/Zend/tests/fibers/gh9735-004.phpt new file mode 100644 index 0000000000000..cfcf381d83d08 --- /dev/null +++ b/Zend/tests/fibers/gh9735-004.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug GH-9735 004 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECT-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-005.phpt b/Zend/tests/fibers/gh9735-005.phpt new file mode 100644 index 0000000000000..c18a42fc37082 --- /dev/null +++ b/Zend/tests/fibers/gh9735-005.phpt @@ -0,0 +1,44 @@ +--TEST-- +Bug GH-9735 005 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECTF-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-006.phpt b/Zend/tests/fibers/gh9735-006.phpt new file mode 100644 index 0000000000000..59bb79d2404bc --- /dev/null +++ b/Zend/tests/fibers/gh9735-006.phpt @@ -0,0 +1,47 @@ +--TEST-- +Bug GH-9735 006 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECTF-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-007.phpt b/Zend/tests/fibers/gh9735-007.phpt new file mode 100644 index 0000000000000..dbb6a33821119 --- /dev/null +++ b/Zend/tests/fibers/gh9735-007.phpt @@ -0,0 +1,47 @@ +--TEST-- +Bug GH-9735 007 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECTF-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-008.phpt b/Zend/tests/fibers/gh9735-008.phpt new file mode 100644 index 0000000000000..ec6f29fb79de4 --- /dev/null +++ b/Zend/tests/fibers/gh9735-008.phpt @@ -0,0 +1,42 @@ +--TEST-- +Bug GH-9735 008 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECTF-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/fibers/gh9735-009.phpt b/Zend/tests/fibers/gh9735-009.phpt new file mode 100644 index 0000000000000..c471499443bd4 --- /dev/null +++ b/Zend/tests/fibers/gh9735-009.phpt @@ -0,0 +1,42 @@ +--TEST-- +Bug GH-9735 009 (Fiber stack variables do not participate in cycle collector) +--FILE-- +start(); +gc_collect_cycles(); + +print "2\n"; + +$fiber = null; +gc_collect_cycles(); + +print "3\n"; + +?> +--EXPECTF-- +1 +2 +C::__destruct +3 diff --git a/Zend/tests/gh10072-2.phpt b/Zend/tests/gh10072-2.phpt new file mode 100644 index 0000000000000..3de75a2b6c09f --- /dev/null +++ b/Zend/tests/gh10072-2.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-10072 (PHP crashes when execute_ex is overridden and a trampoline is used from internal code during shutdown) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.replace_zend_execute_ex=1 +opcache.jit=disable +--FILE-- + +--EXPECT-- +Trampoline for shutdown diff --git a/Zend/tests/gh10072.phpt b/Zend/tests/gh10072.phpt new file mode 100644 index 0000000000000..95a0d43450525 --- /dev/null +++ b/Zend/tests/gh10072.phpt @@ -0,0 +1,106 @@ +--TEST-- +GH-10072 (PHP crashes when execute_ex is overridden and a trampoline is used from internal code) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.replace_zend_execute_ex=1 +opcache.jit=disable +--FILE-- +handle; + } + + + public function stream_close(): void + { + } + + public function stream_open(string $path, string $mode, int $options = 0, ?string &$openedPath = null): bool + { + return true; + } + + + public function stream_read(int $count) + { + return 0; + } + + + public function stream_seek(int $offset, int $whence = SEEK_SET): bool + { + return true; + } + + + public function stream_set_option(int $option, int $arg1, ?int $arg2): bool + { + return false; + } + + + public function stream_stat() + { + return []; + } + + + public function stream_tell() + { + return []; + } + + + public function stream_truncate(int $newSize): bool + { + return true; + } + + + public function stream_write(string $data) + { + } + + + public function unlink(string $path): bool + { + return false; + } +} + +class TrampolineTest { + /** @var resource|null */ + public $context; + + /** @var object|null */ + private $wrapper; + + public function __call(string $name, array $arguments) { + if (!$this->wrapper) { + $this->wrapper = new DummyStreamWrapper(); + } + echo 'Trampoline for ', $name, PHP_EOL; + return $this->wrapper->$name(...$arguments); + } + +} + +stream_wrapper_register('custom', TrampolineTest::class); + + +$fp = fopen("custom://myvar", "r+"); +?> +--EXPECT-- +Trampoline for stream_open +Trampoline for stream_close diff --git a/Zend/tests/gh10251.phpt b/Zend/tests/gh10251.phpt new file mode 100644 index 0000000000000..da8d8767a3656 --- /dev/null +++ b/Zend/tests/gh10251.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-10251 (Assertion `(flag & (1<<3)) == 0' failed.) +--FILE-- +$p = $v; + } +} +$a = new A(); +$pp = ""; +$op = $pp & ""; +// Bitwise operators on strings don't compute the hash. +// The code below previously assumed a hash was actually computed, leading to a crash. +$a->$op = 0; +echo "Done\n"; +?> +--EXPECTF-- +Warning: Undefined variable $v in %s on line %d + +Warning: Undefined variable $p in %s on line %d +Done diff --git a/Zend/tests/gh10346.phpt b/Zend/tests/gh10346.phpt new file mode 100644 index 0000000000000..74fce28e2307c --- /dev/null +++ b/Zend/tests/gh10346.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-10346 (Observer: enum tryFrom() run_time_cache properly assigned) +--CREDITS-- +Florian Sowade +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.observe_all=1 +--FILE-- + +--EXPECTF-- + + + + + + + +enum(Card::HEART) + + diff --git a/Zend/tests/traits/constant_016.phpt b/Zend/tests/traits/constant_016.phpt index 9cb2b29bcc095..0754c15716377 100644 --- a/Zend/tests/traits/constant_016.phpt +++ b/Zend/tests/traits/constant_016.phpt @@ -2,6 +2,8 @@ Compatibility of values of same name trait constants is checked after their constant expressions are evaluated --ENV-- ENSURE_CONSTANT_IS_DEFINED_AT_RUNTIME=1 +--INI-- +variables_order=EGPCS --FILE-- prototype = NULL; internal_function->attributes = NULL; if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime - ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size())); + ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size())); } else { ZEND_MAP_PTR_NEW(internal_function->run_time_cache); } diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 8813fa9788a97..5cebbbc560894 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -760,7 +760,7 @@ ZEND_FUNCTION(get_object_vars) } else { array_init_size(return_value, zend_hash_num_elements(properties)); - ZEND_HASH_MAP_FOREACH_KEY_VAL(properties, num_key, key, value) { + ZEND_HASH_FOREACH_KEY_VAL(properties, num_key, key, value) { bool is_dynamic = 1; if (Z_TYPE_P(value) == IS_INDIRECT) { value = Z_INDIRECT_P(value); diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 59befde732194..25261cf53f880 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -409,8 +409,11 @@ static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id n zif->module = EG(current_module); zif->scope = ce; zif->T = ZEND_OBSERVER_ENABLED; - ZEND_MAP_PTR_NEW(zif->run_time_cache); - ZEND_MAP_PTR_SET(zif->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size())); + if (EG(active)) { // at run-time + ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size())); + } else { + ZEND_MAP_PTR_NEW(zif->run_time_cache); + } if (!zend_hash_add_ptr(&ce->function_table, name, zif)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name)); diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 487dcfc6bc881..de32aab8af94b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -4414,6 +4414,71 @@ ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, cleanup_live_vars(execute_data, op_num, catch_op_num); } +ZEND_API HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer) +{ + if (!EX(func) || !ZEND_USER_CODE(EX(func)->common.type)) { + return NULL; + } + + zend_op_array *op_array = &EX(func)->op_array; + + if (!(EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE)) { + uint32_t i, num_cvs = EX(func)->op_array.last_var; + for (i = 0; i < num_cvs; i++) { + zend_get_gc_buffer_add_zval(gc_buffer, EX_VAR_NUM(i)); + } + } + + if (EX_CALL_INFO() & ZEND_CALL_FREE_EXTRA_ARGS) { + zval *zv = EX_VAR_NUM(op_array->last_var + op_array->T); + zval *end = zv + (EX_NUM_ARGS() - op_array->num_args); + while (zv != end) { + zend_get_gc_buffer_add_zval(gc_buffer, zv++); + } + } + + if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) { + zend_get_gc_buffer_add_obj(gc_buffer, Z_OBJ(execute_data->This)); + } + if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) { + zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func))); + } + if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { + zval extra_named_params; + ZVAL_ARR(&extra_named_params, EX(extra_named_params)); + zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params); + } + + if (call) { + /* -1 required because we want the last run opcode, not the next to-be-run one. */ + uint32_t op_num = execute_data->opline - op_array->opcodes - 1; + zend_unfinished_calls_gc(execute_data, call, op_num, gc_buffer); + } + + if (execute_data->opline != op_array->opcodes) { + uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1; + for (i = 0; i < op_array->last_live_range; i++) { + const zend_live_range *range = &op_array->live_range[i]; + if (range->start > op_num) { + break; + } else if (op_num < range->end) { + uint32_t kind = range->var & ZEND_LIVE_MASK; + uint32_t var_num = range->var & ~ZEND_LIVE_MASK; + zval *var = EX_VAR(var_num); + if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) { + zend_get_gc_buffer_add_zval(gc_buffer, var); + } + } + } + } + + if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) { + return execute_data->symbol_table; + } else { + return NULL; + } +} + #if ZEND_VM_SPEC static void zend_swap_operands(zend_op *op) /* {{{ */ { diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index a9b316b8bdbed..7a5837f4a0f7c 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -380,6 +380,7 @@ ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table); ZEND_API void ZEND_FASTCALL zend_free_compiled_variables(zend_execute_data *execute_data); ZEND_API void zend_unfinished_calls_gc(zend_execute_data *execute_data, zend_execute_data *call, uint32_t op_num, zend_get_gc_buffer *buf); ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num); +ZEND_API HashTable *zend_unfinished_execution_gc(zend_execute_data *execute_data, zend_execute_data *call, zend_get_gc_buffer *gc_buffer); zval * ZEND_FASTCALL zend_handle_named_arg( zend_execute_data **call_ptr, zend_string *arg_name, diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 0bb45d1c3dc9c..93502fa00c3a0 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -25,6 +25,8 @@ #include "zend_builtin_functions.h" #include "zend_observer.h" #include "zend_mmap.h" +#include "zend_compile.h" +#include "zend_closures.h" #include "zend_fibers.h" #include "zend_fibers_arginfo.h" @@ -175,8 +177,12 @@ static zend_fiber_stack *zend_fiber_stack_allocate(size_t size) { void *pointer; const size_t page_size = zend_fiber_get_page_size(); + const size_t minimum_stack_size = page_size + ZEND_FIBER_GUARD_PAGES * page_size; - ZEND_ASSERT(size >= page_size + ZEND_FIBER_GUARD_PAGES * page_size); + if (size < minimum_stack_size) { + zend_throw_exception_ex(NULL, 0, "Fiber stack size is too small, it needs to be at least %zu bytes", minimum_stack_size); + return NULL; + } const size_t stack_size = (size + page_size - 1) / page_size * page_size; const size_t alloc_size = stack_size + ZEND_FIBER_GUARD_PAGES * page_size; @@ -655,9 +661,32 @@ static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *n zend_get_gc_buffer_add_zval(buf, &fiber->fci.function_name); zend_get_gc_buffer_add_zval(buf, &fiber->result); + if (fiber->context.status != ZEND_FIBER_STATUS_SUSPENDED) { + zend_get_gc_buffer_use(buf, table, num); + return NULL; + } + + HashTable *lastSymTable = NULL; + zend_execute_data *ex = fiber->execute_data; + for (; ex; ex = ex->prev_execute_data) { + HashTable *symTable = zend_unfinished_execution_gc(ex, ex->call, buf); + if (symTable) { + if (lastSymTable) { + zval *val; + ZEND_HASH_FOREACH_VAL(lastSymTable, val) { + if (EXPECTED(Z_TYPE_P(val) == IS_INDIRECT)) { + val = Z_INDIRECT_P(val); + } + zend_get_gc_buffer_add_zval(buf, val); + } ZEND_HASH_FOREACH_END(); + } + lastSymTable = symTable; + } + } + zend_get_gc_buffer_use(buf, table, num); - return NULL; + return lastSymTable; } ZEND_METHOD(Fiber, __construct) diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 48f228b956e9e..f47f008bfabff 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -332,7 +332,7 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int * { zend_generator *generator = (zend_generator*)object; zend_execute_data *execute_data = generator->execute_data; - zend_op_array *op_array; + zend_execute_data *call = NULL; if (!execute_data) { /* If the generator has been closed, it can only hold on to three values: The value, key @@ -352,7 +352,6 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int * return NULL; } - op_array = &EX(func)->op_array; zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); zend_get_gc_buffer_add_zval(gc_buffer, &generator->value); @@ -360,59 +359,17 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int * zend_get_gc_buffer_add_zval(gc_buffer, &generator->retval); zend_get_gc_buffer_add_zval(gc_buffer, &generator->values); - if (!(EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE)) { - uint32_t i, num_cvs = EX(func)->op_array.last_var; - for (i = 0; i < num_cvs; i++) { - zend_get_gc_buffer_add_zval(gc_buffer, EX_VAR_NUM(i)); - } - } - - if (EX_CALL_INFO() & ZEND_CALL_FREE_EXTRA_ARGS) { - zval *zv = EX_VAR_NUM(op_array->last_var + op_array->T); - zval *end = zv + (EX_NUM_ARGS() - op_array->num_args); - while (zv != end) { - zend_get_gc_buffer_add_zval(gc_buffer, zv++); - } + if (UNEXPECTED(generator->frozen_call_stack)) { + /* The frozen stack is linked in reverse order */ + call = zend_generator_revert_call_stack(generator->frozen_call_stack); } - if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) { - zend_get_gc_buffer_add_obj(gc_buffer, Z_OBJ(execute_data->This)); - } - if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) { - zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func))); - } - if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { - zval extra_named_params; - ZVAL_ARR(&extra_named_params, EX(extra_named_params)); - zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params); - } + zend_unfinished_execution_gc(execute_data, call, gc_buffer); if (UNEXPECTED(generator->frozen_call_stack)) { - /* The frozen stack is linked in reverse order */ - zend_execute_data *call = zend_generator_revert_call_stack(generator->frozen_call_stack); - /* -1 required because we want the last run opcode, not the next to-be-run one. */ - uint32_t op_num = execute_data->opline - op_array->opcodes - 1; - zend_unfinished_calls_gc(execute_data, call, op_num, gc_buffer); zend_generator_revert_call_stack(call); } - if (execute_data->opline != op_array->opcodes) { - uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1; - for (i = 0; i < op_array->last_live_range; i++) { - const zend_live_range *range = &op_array->live_range[i]; - if (range->start > op_num) { - break; - } else if (op_num < range->end) { - uint32_t kind = range->var & ZEND_LIVE_MASK; - uint32_t var_num = range->var & ~ZEND_LIVE_MASK; - zval *var = EX_VAR(var_num); - if (kind == ZEND_LIVE_TMPVAR || kind == ZEND_LIVE_LOOP) { - zend_get_gc_buffer_add_zval(gc_buffer, var); - } - } - } - } - if (generator->node.parent) { zend_get_gc_buffer_add_obj(gc_buffer, &generator->node.parent->std); } diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index 9498bd3718c86..eae03e4491fc6 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -166,6 +166,8 @@ static zend_always_inline void zend_hash_real_init_mixed_ex(HashTable *ht) void *data; uint32_t nSize = ht->nTableSize; + ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); + if (UNEXPECTED(GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)) { data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), 1); } else if (EXPECTED(nSize == HT_MIN_SIZE)) { @@ -338,6 +340,8 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht) uint32_t i; uint32_t nSize = ht->nTableSize; + ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); + HT_ASSERT_RC1(ht); HT_FLAGS(ht) &= ~HASH_FLAG_PACKED; new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); @@ -380,7 +384,11 @@ ZEND_API void ZEND_FASTCALL zend_hash_to_packed(HashTable *ht) ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool packed) { HT_ASSERT_RC1(ht); + if (nSize == 0) return; + + ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); + if (UNEXPECTED(HT_FLAGS(ht) & HASH_FLAG_UNINITIALIZED)) { if (nSize > ht->nTableSize) { ht->nTableSize = zend_hash_check_size(nSize); @@ -1227,6 +1235,8 @@ static void ZEND_FASTCALL zend_hash_do_resize(HashTable *ht) uint32_t nSize = ht->nTableSize + ht->nTableSize; Bucket *old_buckets = ht->arData; + ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); + ht->nTableSize = nSize; new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 2d9970975785f..6b168a8b41106 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1385,6 +1385,7 @@ static void do_inherit_class_constant(zend_string *name, zend_class_constant *pa c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant)); memcpy(c, parent_const, sizeof(zend_class_constant)); parent_const = c; + Z_CONSTANT_FLAGS(c->value) |= CONST_OWNED; } } if (ce->type & ZEND_INTERNAL_CLASS) { diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index d0677f0fe4e96..e44a3822dd22f 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -553,9 +553,8 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe if (EXPECTED(Z_TYPE_P(zv) == IS_STRING)) { zend_string *str = Z_STR_P(zv); if (EXPECTED(str == member) || - /* "str" always has a pre-calculated hash value here */ - (EXPECTED(ZSTR_H(str) == zend_string_hash_val(member)) && - EXPECTED(zend_string_equal_content(str, member)))) { + /* str and member don't necessarily have a pre-calculated hash value here */ + EXPECTED(zend_string_equal_content(str, member))) { return &Z_PROPERTY_GUARD_P(zv); } else if (EXPECTED(Z_PROPERTY_GUARD_P(zv) == 0)) { zval_ptr_dtor_str(zv); diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index d0b272686e339..d59fc6d07c1db 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -692,7 +692,7 @@ extern "C++" { # define ZEND_VOIDP(ptr) (ptr) #endif -#if defined(__GNUC__) && ZEND_GCC_VERSION >= 9000 +#if __has_attribute(__indirect_return__) # define ZEND_INDIRECT_RETURN __attribute__((__indirect_return__)) #else # define ZEND_INDIRECT_RETURN diff --git a/Zend/zend_types.h b/Zend/zend_types.h index df64541749d4c..e6505d8b62ad1 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -411,8 +411,15 @@ struct _zend_array { #define HT_MIN_MASK ((uint32_t) -2) #define HT_MIN_SIZE 8 +/* HT_MAX_SIZE is chosen to satisfy the following constraints: + * - HT_SIZE_TO_MASK(HT_MAX_SIZE) != 0 + * - HT_SIZE_EX(HT_MAX_SIZE, HT_SIZE_TO_MASK(HT_MAX_SIZE)) does not overflow or + * wrapparound, and is <= the addressable space size + * - HT_MAX_SIZE must be a power of two: + * (nTableSizetype) || (call_info & ZEND_CALL_TOP)) { + if (!execute_data || !EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { ZEND_VM_RETURN(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9303953e4e85e..02842be27f6ee 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3409,7 +3409,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_HANDLER(Z SAVE_OPLINE_EX(); execute_data = EX(prev_execute_data); - LOAD_OPLINE(); + if (execute_data) { + LOAD_OPLINE(); + } ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP); zend_execute_ex(call); } @@ -3460,7 +3462,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_HANDLER(Z execute_data = EG(current_execute_data); - if (!EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { + if (!execute_data || !EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { ZEND_VM_RETURN(); } @@ -3547,7 +3549,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER_ SAVE_OPLINE_EX(); zend_observer_fcall_begin(execute_data); execute_data = EX(prev_execute_data); - LOAD_OPLINE(); + if (execute_data) { + LOAD_OPLINE(); + } ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP); zend_execute_ex(call); } @@ -3599,7 +3603,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER_ execute_data = EG(current_execute_data); - if (!EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { + if (!execute_data || !EX(func) || !ZEND_USER_CODE(EX(func)->type) || (call_info & ZEND_CALL_TOP)) { ZEND_VM_RETURN(); } diff --git a/build/gen_stub.php b/build/gen_stub.php index 8cf319ceaea39..9e6a945a5c4e7 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1487,14 +1487,10 @@ public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDoc $methodSynopsis = $doc->createElement($synopsisType); - $aliasedFunc = $this->aliasType === "alias" && isset($funcMap[$this->alias->__toString()]) ? $funcMap[$this->alias->__toString()] : null; - $aliasFunc = $aliasMap[$this->name->__toString()] ?? null; - - if (($this->aliasType === "alias" && $aliasedFunc !== null && $aliasedFunc->isMethod() !== $this->isMethod()) || - ($aliasFunc !== null && $aliasFunc->isMethod() !== $this->isMethod()) - ) { + if ($this->isMethod()) { + assert($this->name instanceof MethodName); $role = $doc->createAttribute("role"); - $role->value = $this->isMethod() ? "oop" : "procedural"; + $role->value = addslashes($this->name->className->__toString()); $methodSynopsis->appendChild($role); } @@ -2704,17 +2700,18 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, itera } $classSynopsisInfo->appendChild(new DOMText("\n ")); - /** @var Name[] $parentsWithInheritedConstants */ + /** @var array $parentsWithInheritedConstants */ $parentsWithInheritedConstants = []; - /** @var Name[] $parentsWithInheritedProperties */ + /** @var array $parentsWithInheritedProperties */ $parentsWithInheritedProperties = []; - /** @var Name[] $parentsWithInheritedMethods */ + /** @var array $parentsWithInheritedMethods */ $parentsWithInheritedMethods = []; $this->collectInheritedMembers( $parentsWithInheritedConstants, $parentsWithInheritedProperties, $parentsWithInheritedMethods, + $this->hasConstructor(), $classMap ); @@ -2767,12 +2764,13 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, itera $classSynopsis->appendChild($classSynopsisInfo); $classReference = self::getClassSynopsisReference($this->name); + $escapedName = addslashes($this->name->__toString()); if ($this->hasConstructor()) { $classSynopsis->appendChild(new DOMText("\n ")); $includeElement = $this->createIncludeElement( $doc, - "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[not(@role='procedural')])" + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[@role='$escapedName'])" ); $classSynopsis->appendChild($includeElement); } @@ -2781,7 +2779,7 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, itera $classSynopsis->appendChild(new DOMText("\n ")); $includeElement = $this->createIncludeElement( $doc, - "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[not(@role='procedural')])" + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[@role='$escapedName'])" ); $classSynopsis->appendChild($includeElement); } @@ -2790,7 +2788,7 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, itera $classSynopsis->appendChild(new DOMText("\n ")); $includeElement = $this->createIncludeElement( $doc, - "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:destructorsynopsis[not(@role='procedural')])" + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:destructorsynopsis[@role='$escapedName'])" ); $classSynopsis->appendChild($includeElement); } @@ -2803,13 +2801,21 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, itera $classSynopsis->appendChild($classSynopsisInfo); foreach ($parentsWithInheritedMethods as $parent) { - $classSynopsis->appendChild(new DOMText("\n ")); - $parentReference = self::getClassSynopsisReference($parent); - $includeElement = $this->createIncludeElement( - $doc, - "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$parentReference')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[not(@role='procedural')])" - ); - $classSynopsis->appendChild($includeElement); + $parentName = $parent["name"]; + $parentMethodsynopsisTypes = $parent["types"]; + + $parentReference = self::getClassSynopsisReference($parentName); + $escapedParentName = addslashes($parentName->__toString()); + + foreach ($parentMethodsynopsisTypes as $parentMethodsynopsisType) { + $classSynopsis->appendChild(new DOMText("\n ")); + $includeElement = $this->createIncludeElement( + $doc, + "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$parentReference')/db:refentry/db:refsect1[@role='description']/descendant::db:{$parentMethodsynopsisType}[@role='$escapedParentName'])" + ); + + $classSynopsis->appendChild($includeElement); + } } } @@ -2867,39 +2873,54 @@ public static function getClassSynopsisReference(Name $name): string { } /** - * @param Name[] $parentsWithInheritedConstants - * @param Name[] $parentsWithInheritedProperties - * @param Name[] $parentsWithInheritedMethods + * @param array $parentsWithInheritedConstants + * @param array $parentsWithInheritedProperties + * @param array $parentsWithInheritedMethods * @param array $classMap */ private function collectInheritedMembers( array &$parentsWithInheritedConstants, array &$parentsWithInheritedProperties, array &$parentsWithInheritedMethods, + bool $hasConstructor, array $classMap ): void { foreach ($this->extends as $parent) { $parentInfo = $classMap[$parent->toString()] ?? null; + $parentName = $parent->toString(); + if (!$parentInfo) { - throw new Exception("Missing parent class " . $parent->toString()); + throw new Exception("Missing parent class $parentName"); } - if (!empty($parentInfo->constInfos) && !isset($parentsWithInheritedConstants[$parent->toString()])) { - $parentsWithInheritedConstants[$parent->toString()] = $parent; + if (!empty($parentInfo->constInfos) && !isset($parentsWithInheritedConstants[$parentName])) { + $parentsWithInheritedConstants[] = $parent; + } + + if (!empty($parentInfo->propertyInfos) && !isset($parentsWithInheritedProperties[$parentName])) { + $parentsWithInheritedProperties[$parentName] = $parent; + } + + if (!$hasConstructor && $parentInfo->hasNonPrivateConstructor()) { + $parentsWithInheritedMethods[$parentName]["name"] = $parent; + $parentsWithInheritedMethods[$parentName]["types"][] = "constructorsynopsis"; } - if (!empty($parentInfo->propertyInfos) && !isset($parentsWithInheritedProperties[$parent->toString()])) { - $parentsWithInheritedProperties[$parent->toString()] = $parent; + if ($parentInfo->hasMethods()) { + $parentsWithInheritedMethods[$parentName]["name"] = $parent; + $parentsWithInheritedMethods[$parentName]["types"][] = "methodsynopsis"; } - if (!isset($parentsWithInheritedMethods[$parent->toString()]) && $parentInfo->hasMethods()) { - $parentsWithInheritedMethods[$parent->toString()] = $parent; + if ($parentInfo->hasDestructor()) { + $parentsWithInheritedMethods[$parentName]["name"] = $parent; + $parentsWithInheritedMethods[$parentName]["types"][] = "destructorsynopsis"; } $parentInfo->collectInheritedMembers( $parentsWithInheritedConstants, $parentsWithInheritedProperties, $parentsWithInheritedMethods, + $hasConstructor, $classMap ); } @@ -2921,6 +2942,7 @@ private function collectInheritedMembers( $parentsWithInheritedConstants, $unusedParentsWithInheritedProperties, $unusedParentsWithInheritedMethods, + $hasConstructor, $classMap ); } @@ -2937,6 +2959,17 @@ private function hasConstructor(): bool return false; } + private function hasNonPrivateConstructor(): bool + { + foreach ($this->funcInfos as $funcInfo) { + if ($funcInfo->name->isConstructor() && !($funcInfo->flags & Class_::MODIFIER_PRIVATE)) { + return true; + } + } + + return false; + } + private function hasDestructor(): bool { foreach ($this->funcInfos as $funcInfo) { @@ -4058,14 +4091,14 @@ static function (FuncInfo $funcInfo) use ($allConstInfos) { ); } -/** @param FuncInfo $funcInfos */ -function generateOptimizerInfo(array $funcInfos): string { +/** @param array $funcMap */ +function generateOptimizerInfo(array $funcMap): string { $code = "/* This is a generated file, edit the .stub.php files instead. */\n\n"; $code .= "static const func_info_t func_infos[] = {\n"; - $code .= generateCodeWithConditions($funcInfos, "", static function (FuncInfo $funcInfo) { + $code .= generateCodeWithConditions($funcMap, "", static function (FuncInfo $funcInfo) { return $funcInfo->getOptimizerInfo(); }); @@ -4261,16 +4294,18 @@ function replaceClassSynopses(string $targetDirectory, array $classMap, iterable $replacedXml = preg_replace( [ "/REPLACED-ENTITY-([A-Za-z0-9._{}%-]+?;)/", - "//i", - "//i", - "//i", - "//i", + '//i', + '//i', + '//i', + '//i', + '//i', ], [ "&$1", "", "", "", + "", "", ], $replacedXml @@ -4494,8 +4529,8 @@ function replaceMethodSynopses(string $targetDirectory, array $funcMap, array $a $replacedXml = preg_replace( [ "/REPLACED-ENTITY-([A-Za-z0-9._{}%-]+?;)/", - "//i", - "//i", + '//i', + '//i', ], [ "&$1", @@ -4686,7 +4721,6 @@ function initPhpParser() { foreach ($fileInfos as $fileInfo) { foreach ($fileInfo->getAllFuncInfos() as $funcInfo) { - /** @var FuncInfo $funcInfo */ $funcMap[$funcInfo->name->__toString()] = $funcInfo; // TODO: Don't use aliasMap for methodsynopsis? diff --git a/configure.ac b/configure.ac index 3c257b6bdfac0..5696e562d2110 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.1-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.2],[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 diff --git a/ext/date/lib/parse_date.c b/ext/date/lib/parse_date.c index fde143646992f..63411f5123ae0 100644 --- a/ext/date/lib/parse_date.c +++ b/ext/date/lib/parse_date.c @@ -1,4 +1,4 @@ -/* Generated by re2c 0.15.3 on Thu Dec 1 10:59:46 2022 */ +/* Generated by re2c 0.15.3 on Tue Jan 10 15:16:26 2023 */ #line 1 "ext/date/lib/parse_date.re" /* * The MIT License (MIT) @@ -787,7 +787,7 @@ static timelib_long timelib_lookup_abbr(const char **ptr, int *dst, char **tz_ab (**ptr >= 'A' && **ptr <= 'Z') || (**ptr >= 'a' && **ptr <= 'z') || (**ptr >= '0' && **ptr <= '9') || - **ptr == '/' || **ptr == '_' || **ptr == '-' + **ptr == '/' || **ptr == '_' || **ptr == '-' || **ptr == '+' ) { ++*ptr; } diff --git a/ext/date/lib/parse_date.re b/ext/date/lib/parse_date.re index 800cd830fbd68..81dfcec8511af 100644 --- a/ext/date/lib/parse_date.re +++ b/ext/date/lib/parse_date.re @@ -785,7 +785,7 @@ static timelib_long timelib_lookup_abbr(const char **ptr, int *dst, char **tz_ab (**ptr >= 'A' && **ptr <= 'Z') || (**ptr >= 'a' && **ptr <= 'z') || (**ptr >= '0' && **ptr <= '9') || - **ptr == '/' || **ptr == '_' || **ptr == '-' + **ptr == '/' || **ptr == '_' || **ptr == '-' || **ptr == '+' ) { ++*ptr; } diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 32ef3771ad230..fd13e98100848 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -3052,6 +3052,10 @@ static bool php_date_modify(zval *object, char *modify, size_t modify_len) /* {{ dateobj->time->us = tmp_time->us; } + if (tmp_time->have_zone && tmp_time->zone_type == TIMELIB_ZONETYPE_OFFSET) { + timelib_set_timezone_from_offset(dateobj->time, tmp_time->z); + } + timelib_time_dtor(tmp_time); timelib_update_ts(dateobj->time, NULL); diff --git a/ext/date/tests/gh10218.phpt b/ext/date/tests/gh10218.phpt new file mode 100644 index 0000000000000..ff1b493ce7f01 --- /dev/null +++ b/ext/date/tests/gh10218.phpt @@ -0,0 +1,15 @@ +--TEST-- +Bug GH-10218 (DateTimeZone fails to parse time zones that contain the "+" character) +--FILE-- + +--EXPECTF-- +object(DateTime)#%d (%d) { + ["date"]=> + string(%d) "%s" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(9) "Etc/GMT+1" +} diff --git a/ext/date/tests/gh9891.phpt b/ext/date/tests/gh9891.phpt new file mode 100644 index 0000000000000..6d8832b0d9237 --- /dev/null +++ b/ext/date/tests/gh9891.phpt @@ -0,0 +1,30 @@ +--TEST-- +Bug GH-9891 (DateTime modify with unixtimestamp (@) must work like setTimestamp) +--FILE-- +modify('@1234567890'); +var_dump($m->getTimeStamp()); + +echo "=======\n"; + +$a = new DateTime('2022-11-01 13:30:00', new DateTimezone('America/Lima')); +$b = clone $a; +echo '$a: ', $a->format(DateTime::ATOM), "\n"; +echo '$b: ', $b->format(DateTime::ATOM), "\n"; +echo '$a: @', $a->getTimestamp(), "\n"; +echo '$b: setTimestamp(', $b->getTimestamp(), ")\n"; +$a->modify('@' . $a->getTimestamp()); +$b->setTimestamp($b->getTimestamp()); +echo '$a: ', $a->format(DateTime::ATOM), "\n"; +echo '$b: ', $b->format(DateTime::ATOM), "\n"; +?> +--EXPECT-- +int(1234567890) +======= +$a: 2022-11-01T13:30:00-05:00 +$b: 2022-11-01T13:30:00-05:00 +$a: @1667327400 +$b: setTimestamp(1667327400) +$a: 2022-11-01T18:30:00+00:00 +$b: 2022-11-01T13:30:00-05:00 diff --git a/ext/gmp/tests/bug74670.phpt b/ext/gmp/tests/bug74670.phpt index 5f9de9330e591..1d09509a91316 100644 --- a/ext/gmp/tests/bug74670.phpt +++ b/ext/gmp/tests/bug74670.phpt @@ -8,5 +8,5 @@ $str = 'C:3:"GMP":4:{s:6666666666:""}'; var_dump(unserialize($str)); ?> --EXPECTF-- -Notice: unserialize(): Error at offset 13 of 29 bytes in %s on line %d +Notice: unserialize(): Error at offset 17 of 29 bytes in %s on line %d bool(false) diff --git a/ext/gmp/tests/gmp_dynamic_property.phpt b/ext/gmp/tests/gmp_dynamic_property.phpt index 547fe51a7f6a3..ff3d4ac94e012 100644 --- a/ext/gmp/tests/gmp_dynamic_property.phpt +++ b/ext/gmp/tests/gmp_dynamic_property.phpt @@ -5,7 +5,7 @@ gmp --FILE-- {1} = 123; $serialized = serialize($g); diff --git a/ext/hash/hash_xxhash.c b/ext/hash/hash_xxhash.c index 7ecedd81287ce..8a155c9862271 100644 --- a/ext/hash/hash_xxhash.c +++ b/ext/hash/hash_xxhash.c @@ -174,7 +174,9 @@ zend_always_inline static void _PHP_XXH3_Init(PHP_XXH3_64_CTX *ctx, HashTable *a func_init_seed(&ctx->s, (XXH64_hash_t)Z_LVAL_P(_seed)); return; } else if (_secret) { - convert_to_string(_secret); + if (!try_convert_to_string(_secret)) { + return; + } size_t len = Z_STRLEN_P(_secret); if (len < PHP_XXH3_SECRET_SIZE_MIN) { zend_throw_error(NULL, "%s: Secret length must be >= %u bytes, %zu bytes passed", algo_name, XXH3_SECRET_SIZE_MIN, len); diff --git a/ext/hash/tests/xxhash_secret.phpt b/ext/hash/tests/xxhash_secret.phpt index 91e7d929d323d..6f1efbe6c90a6 100644 --- a/ext/hash/tests/xxhash_secret.phpt +++ b/ext/hash/tests/xxhash_secret.phpt @@ -3,6 +3,13 @@ Hash: xxHash secret --FILE-- getMessage()); } + try { + $ctx = hash_init($a, options: ["secret" => new StringableThrowingClass()]); + } catch (Throwable $e) { + var_dump($e->getMessage()); + } + try { $ctx = hash_init($a, options: ["secret" => str_repeat('a', 17)]); } catch (Throwable $e) { @@ -35,8 +48,10 @@ foreach (["xxh3", "xxh128"] as $a) { ?> --EXPECT-- string(67) "xxh3: Only one of seed or secret is to be passed for initialization" +string(23) "exception in __toString" string(57) "xxh3: Secret length must be >= 136 bytes, 17 bytes passed" 8028aa834c03557a == 8028aa834c03557a == true string(69) "xxh128: Only one of seed or secret is to be passed for initialization" +string(23) "exception in __toString" string(59) "xxh128: Secret length must be >= 136 bytes, 17 bytes passed" 54279097795e7218093a05d4d781cbb9 == 54279097795e7218093a05d4d781cbb9 == true diff --git a/ext/intl/tests/dateformat_calendars_variant3.phpt b/ext/intl/tests/dateformat_calendars_variant3.phpt index d189361bcffb6..1ad970d07d0e6 100644 --- a/ext/intl/tests/dateformat_calendars_variant3.phpt +++ b/ext/intl/tests/dateformat_calendars_variant3.phpt @@ -6,6 +6,7 @@ date.timezone=Atlantic/Azores intl --SKIPIF-- = 54.1'); ?> += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +format(strtotime('2012-01-01 00:00:00 +0000'))); +var_dump($fmt2->format(strtotime('2012-01-01 00:00:00 +0000'))); +var_dump($fmt3->format(strtotime('2012-01-01 00:00:00 +0000'))); + +new IntlDateFormatter('en_US@calendar=hebrew', + IntlDateFormatter::FULL, + IntlDateFormatter::FULL, + 'GMT+05:12', + -1); +?> +==DONE== +--EXPECTF-- +string(49) "Sunday, January 1, 2012 at 5:12:00 AM GMT+05:12" +string(49) "Sunday, January 1, 2012 at 5:12:00 AM GMT+05:12" +string(46) "Sunday, 6 Tevet 5772 at 5:12:00 AM GMT+05:12" + +Fatal error: Uncaught IntlException: IntlDateFormatter::__construct(): datefmt_create: Invalid value for calendar type; it must be one of IntlDateFormatter::TRADITIONAL (locale's default calendar) or IntlDateFormatter::GREGORIAN. Alternatively, it can be an IntlCalendar object in %s:%d +Stack trace: +#0 %s(%d): IntlDateFormatter->__construct('en_US@calendar=...', 0, 0, 'GMT+05:12', -1) +#1 {main} + thrown in %s on line %d diff --git a/ext/intl/tests/dateformat_create_default.phpt b/ext/intl/tests/dateformat_create_default.phpt index eddac36f5ea39..1479ee9a98476 100644 --- a/ext/intl/tests/dateformat_create_default.phpt +++ b/ext/intl/tests/dateformat_create_default.phpt @@ -4,6 +4,8 @@ IntlDateFormatter::create() with default date and time types intl --INI-- date.timezone=UTC +--SKIPIF-- += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +format($ts), "\n"; + +$fmt = new IntlDateFormatter('en_US'); +echo $fmt->format($ts), "\n"; + +$fmt = datefmt_create('en_US'); +echo $fmt->format($ts), "\n"; + +?> +--EXPECT-- +Sunday, January 1, 2012 at 12:00:00 AM Coordinated Universal Time +Sunday, January 1, 2012 at 12:00:00 AM Coordinated Universal Time +Sunday, January 1, 2012 at 12:00:00 AM Coordinated Universal Time diff --git a/ext/intl/tests/dateformat_formatObject_calendar_variant5.phpt b/ext/intl/tests/dateformat_formatObject_calendar_variant5.phpt index 494b66768906f..9eb9f39a917a7 100644 --- a/ext/intl/tests/dateformat_formatObject_calendar_variant5.phpt +++ b/ext/intl/tests/dateformat_formatObject_calendar_variant5.phpt @@ -4,6 +4,7 @@ IntlDateFormatter::formatObject(): IntlCalendar tests intl --SKIPIF-- = 55.1'); ?> += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +setTime(strtotime('2012-01-01 00:00:00')*1000.); +echo IntlDateFormatter::formatObject($cal), "\n"; +echo IntlDateFormatter::formatObject($cal, IntlDateFormatter::FULL, "en-US"), "\n"; + +?> +--EXPECTF-- +01/01/2012, 00:00:00 +domingo, 1 de janeiro de 2012 às 00:00:00 Hora padrão %Sda Europa Ocidental +Jan 1, 2012, 12:00:00 AM +1/1/12, 12:00:00 AM Western European Standard Time +Sun 2012-01-1 00,00,00.000 Portugal Time +domingo, 1 de janeiro de 2012 às 05:00:00 GMT+03:00 +06/02/1433, 00:00:00 +Sunday, Safar 6, 1433 at 12:00:00 AM Western European Standard Time diff --git a/ext/intl/tests/dateformat_formatObject_datetime_variant5.phpt b/ext/intl/tests/dateformat_formatObject_datetime_variant5.phpt index b51a46d7f3976..b3c2ecc8e7932 100644 --- a/ext/intl/tests/dateformat_formatObject_datetime_variant5.phpt +++ b/ext/intl/tests/dateformat_formatObject_datetime_variant5.phpt @@ -4,6 +4,7 @@ IntlDateFormatter::formatObject(): DateTime tests intl --SKIPIF-- = 55.1'); ?> += 0) die('skip for ICU < 72.1'); ?> --FILE-- +--FILE-- + +--EXPECTF-- +01/01/2012, 00:00:00 +domingo, 1 de janeiro de 2012 às 00:00:00 Hora padrão %Sda Europa Ocidental +Jan 1, 2012, 12:00:00 AM +1/1/12, 12:00:00 AM Western European Standard Time +Sun 2012-01-1 00,00,00.000 Portugal Time +domingo, 1 de janeiro de 2012 às 05:00:00 GMT+03:00 diff --git a/ext/intl/tests/dateformat_format_parse_version2.phpt b/ext/intl/tests/dateformat_format_parse_version2.phpt index 19e7d914adcfd..1293d29c383bf 100644 --- a/ext/intl/tests/dateformat_format_parse_version2.phpt +++ b/ext/intl/tests/dateformat_format_parse_version2.phpt @@ -4,6 +4,7 @@ datefmt_format_code() and datefmt_parse_code() intl --SKIPIF-- = 51.2'); ?> += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- + 24 , + 'tm_min' => 3, + 'tm_hour' => 19, + 'tm_mday' => 3, + 'tm_mon' => 3, + 'tm_year' => 105, + ); + $localtime_arr2 = array ( + 'tm_sec' => 21, + 'tm_min' => 5, + 'tm_hour' => 7, + 'tm_mday' => 13, + 'tm_mon' => 7, + 'tm_year' => 205, + ); + $localtime_arr3 = array ( + 'tm_sec' => 11, + 'tm_min' => 13, + 'tm_hour' => 0, + 'tm_mday' => 17, + 'tm_mon' => 11, + 'tm_year' => -5 + ); + + $localtime_arr = array ( + $localtime_arr1, + $localtime_arr2, + $localtime_arr3 + ); + + //Test format and parse with a timestamp : long + foreach( $time_arr as $timestamp_entry){ + $res_str .= "\n------------\n"; + $res_str .= "\nInput timestamp is : $timestamp_entry"; + $res_str .= "\n------------\n"; + foreach( $locale_arr as $locale_entry ){ + foreach( $datetype_arr as $datetype_entry ) { + $res_str .= "\nIntlDateFormatter locale= $locale_entry ,datetype = $datetype_entry ,timetype =$datetype_entry "; + $fmt = ut_datefmt_create( $locale_entry , $datetype_entry ,$datetype_entry,$timezone); + $formatted = ut_datefmt_format( $fmt , $timestamp_entry); + $res_str .= "\nFormatted timestamp is : $formatted"; + $parsed = ut_datefmt_parse( $fmt , $formatted); + if( intl_get_error_code() == U_ZERO_ERROR){ + $res_str .= "\nParsed timestamp is : $parsed"; + }else{ + $res_str .= "\nError while parsing as: '".intl_get_error_message()."'"; + } + } + } + } + + //Test format and parse with a localtime :array + foreach( $localtime_arr as $localtime_entry){ + $res_str .= "\n------------\n"; + $res_str .= "\nInput localtime is : "; + foreach( $localtime_entry as $key => $value){ + $res_str .= "$key : '$value' , "; + } + + $res_str .= "\n------------\n"; + foreach( $locale_arr as $locale_entry ){ + foreach( $datetype_arr as $datetype_entry ) { + $res_str .= "\nIntlDateFormatter locale= $locale_entry ,datetype = $datetype_entry ,timetype =$datetype_entry "; + $fmt = ut_datefmt_create( $locale_entry , $datetype_entry ,$datetype_entry,$timezone); + $formatted1 = ut_datefmt_format( $fmt , $localtime_entry); + if( intl_get_error_code() == U_ZERO_ERROR){ + $res_str .= "\nFormatted localtime_array is : $formatted1"; + }else{ + $res_str .= "\nError while formatting as: '".intl_get_error_message()."'"; + } + //Parsing + $parsed_arr = ut_datefmt_localtime( $fmt, $formatted1 ); + + if( $parsed_arr){ + $res_str .= "\nParsed array is: "; + foreach( $parsed_arr as $key => $value){ + $res_str .= "$key : '$value' , "; + } + } +/* + else{ + //$res_str .= "No values found from LocaleTime parsing."; + $res_str .= "\tError : '".intl_get_error_message()."'"; + } +*/ + } + } + } + + return $res_str; + +} + +include_once( 'ut_common.inc' ); + +// Run the test +ut_run(); +?> +--EXPECT-- +------------ + +Input timestamp is : 0 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Thursday, January 1, 1970 at 5:00:00 AM GMT+05:00 +Parsed timestamp is : 0 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : January 1, 1970 at 5:00:00 AM GMT+5 +Parsed timestamp is : 0 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Jan 1, 1970, 5:00:00 AM +Parsed timestamp is : 0 +------------ + +Input timestamp is : -1200000 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Thursday, December 18, 1969 at 7:40:00 AM GMT+05:00 +Parsed timestamp is : -1200000 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : December 18, 1969 at 7:40:00 AM GMT+5 +Parsed timestamp is : -1200000 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Dec 18, 1969, 7:40:00 AM +Parsed timestamp is : -1200000 +------------ + +Input timestamp is : 1200000 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Thursday, January 15, 1970 at 2:20:00 AM GMT+05:00 +Parsed timestamp is : 1200000 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : January 15, 1970 at 2:20:00 AM GMT+5 +Parsed timestamp is : 1200000 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Jan 15, 1970, 2:20:00 AM +Parsed timestamp is : 1200000 +------------ + +Input timestamp is : 2200000000 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Monday, September 19, 2039 at 4:06:40 AM GMT+05:00 +Parsed timestamp is : 2200000000 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : September 19, 2039 at 4:06:40 AM GMT+5 +Parsed timestamp is : 2200000000 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Sep 19, 2039, 4:06:40 AM +Parsed timestamp is : 2200000000 +------------ + +Input timestamp is : -2200000000 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Sunday, April 15, 1900 at 5:53:20 AM GMT+05:00 +Parsed timestamp is : -2200000000 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : April 15, 1900 at 5:53:20 AM GMT+5 +Parsed timestamp is : -2200000000 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Apr 15, 1900, 5:53:20 AM +Parsed timestamp is : -2200000000 +------------ + +Input timestamp is : 90099999 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Thursday, November 9, 1972 at 12:46:39 AM GMT+05:00 +Parsed timestamp is : 90099999 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : November 9, 1972 at 12:46:39 AM GMT+5 +Parsed timestamp is : 90099999 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Nov 9, 1972, 12:46:39 AM +Parsed timestamp is : 90099999 +------------ + +Input timestamp is : 3600 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Thursday, January 1, 1970 at 6:00:00 AM GMT+05:00 +Parsed timestamp is : 3600 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : January 1, 1970 at 6:00:00 AM GMT+5 +Parsed timestamp is : 3600 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Jan 1, 1970, 6:00:00 AM +Parsed timestamp is : 3600 +------------ + +Input timestamp is : -3600 +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted timestamp is : Thursday, January 1, 1970 at 4:00:00 AM GMT+05:00 +Parsed timestamp is : -3600 +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted timestamp is : January 1, 1970 at 4:00:00 AM GMT+5 +Parsed timestamp is : -3600 +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted timestamp is : Jan 1, 1970, 4:00:00 AM +Parsed timestamp is : -3600 +------------ + +Input localtime is : tm_sec : '24' , tm_min : '3' , tm_hour : '19' , tm_mday : '3' , tm_mon : '3' , tm_year : '105' , +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted localtime_array is : Sunday, April 3, 2005 at 7:03:24 PM GMT+05:00 +Parsed array is: tm_sec : '24' , tm_min : '3' , tm_hour : '19' , tm_year : '105' , tm_mday : '3' , tm_wday : '0' , tm_yday : '93' , tm_mon : '3' , tm_isdst : '0' , +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted localtime_array is : April 3, 2005 at 7:03:24 PM GMT+5 +Parsed array is: tm_sec : '24' , tm_min : '3' , tm_hour : '19' , tm_year : '105' , tm_mday : '3' , tm_wday : '0' , tm_yday : '93' , tm_mon : '3' , tm_isdst : '0' , +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted localtime_array is : Apr 3, 2005, 7:03:24 PM +Parsed array is: tm_sec : '24' , tm_min : '3' , tm_hour : '19' , tm_year : '105' , tm_mday : '3' , tm_wday : '0' , tm_yday : '93' , tm_mon : '3' , tm_isdst : '0' , +------------ + +Input localtime is : tm_sec : '21' , tm_min : '5' , tm_hour : '7' , tm_mday : '13' , tm_mon : '7' , tm_year : '205' , +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted localtime_array is : Thursday, August 13, 2105 at 7:05:21 AM GMT+05:00 +Parsed array is: tm_sec : '21' , tm_min : '5' , tm_hour : '7' , tm_year : '205' , tm_mday : '13' , tm_wday : '4' , tm_yday : '225' , tm_mon : '7' , tm_isdst : '0' , +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted localtime_array is : August 13, 2105 at 7:05:21 AM GMT+5 +Parsed array is: tm_sec : '21' , tm_min : '5' , tm_hour : '7' , tm_year : '205' , tm_mday : '13' , tm_wday : '4' , tm_yday : '225' , tm_mon : '7' , tm_isdst : '0' , +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted localtime_array is : Aug 13, 2105, 7:05:21 AM +Parsed array is: tm_sec : '21' , tm_min : '5' , tm_hour : '7' , tm_year : '205' , tm_mday : '13' , tm_wday : '4' , tm_yday : '225' , tm_mon : '7' , tm_isdst : '0' , +------------ + +Input localtime is : tm_sec : '11' , tm_min : '13' , tm_hour : '0' , tm_mday : '17' , tm_mon : '11' , tm_year : '-5' , +------------ + +IntlDateFormatter locale= en_US ,datetype = 0 ,timetype =0 +Formatted localtime_array is : Tuesday, December 17, 1895 at 12:13:11 AM GMT+05:00 +Parsed array is: tm_sec : '11' , tm_min : '13' , tm_hour : '0' , tm_year : '-5' , tm_mday : '17' , tm_wday : '2' , tm_yday : '351' , tm_mon : '11' , tm_isdst : '0' , +IntlDateFormatter locale= en_US ,datetype = 1 ,timetype =1 +Formatted localtime_array is : December 17, 1895 at 12:13:11 AM GMT+5 +Parsed array is: tm_sec : '11' , tm_min : '13' , tm_hour : '0' , tm_year : '-5' , tm_mday : '17' , tm_wday : '2' , tm_yday : '351' , tm_mon : '11' , tm_isdst : '0' , +IntlDateFormatter locale= en_US ,datetype = 2 ,timetype =2 +Formatted localtime_array is : Dec 17, 1895, 12:13:11 AM +Parsed array is: tm_sec : '11' , tm_min : '13' , tm_hour : '0' , tm_year : '-5' , tm_mday : '17' , tm_wday : '2' , tm_yday : '351' , tm_mon : '11' , tm_isdst : '0' , diff --git a/ext/intl/tests/dateformat_set_timezone_id3.phpt b/ext/intl/tests/dateformat_set_timezone_id3.phpt index e0d7cd0e52607..a4952eb5a3dcb 100644 --- a/ext/intl/tests/dateformat_set_timezone_id3.phpt +++ b/ext/intl/tests/dateformat_set_timezone_id3.phpt @@ -6,6 +6,7 @@ date.timezone=Atlantic/Azores intl --SKIPIF-- = 51.2'); ?> += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 4.8 +--INI-- +date.timezone=Atlantic/Azores +--EXTENSIONS-- +intl +--SKIPIF-- += 72.1'); ?> +--FILE-- + +--EXPECTF-- +Warning: IntlDateFormatter::setTimeZone(): datefmt_set_timezone: No such time zone: 'CN' in %sut_common.inc on line %d + +Warning: datefmt_set_timezone(): datefmt_set_timezone: No such time zone: 'CN' in %sut_common.inc on line %d + +After creation of the dateformatter : timezone_id= US/Pacific +----------- +Trying to set timezone_id= America/New_York +After call to set_timezone_id : timezone_id= America/New_York +Formatting timestamp=0 resulted in Wednesday, December 31, 1969 at 7:00:00 PM Eastern Standard Time +Formatting timestamp=3600 resulted in Wednesday, December 31, 1969 at 8:00:00 PM Eastern Standard Time +----------- +Trying to set timezone_id= America/Los_Angeles +After call to set_timezone_id : timezone_id= America/Los_Angeles +Formatting timestamp=0 resulted in Wednesday, December 31, 1969 at 4:00:00 PM Pacific Standard Time +Formatting timestamp=3600 resulted in Wednesday, December 31, 1969 at 5:00:00 PM Pacific Standard Time +----------- +Trying to set timezone_id= America/Chicago +After call to set_timezone_id : timezone_id= America/Chicago +Formatting timestamp=0 resulted in Wednesday, December 31, 1969 at 6:00:00 PM Central Standard Time +Formatting timestamp=3600 resulted in Wednesday, December 31, 1969 at 7:00:00 PM Central Standard Time +----------- +Trying to set timezone_id= CN +After call to set_timezone_id : timezone_id= America/Chicago +Formatting timestamp=0 resulted in Wednesday, December 31, 1969 at 6:00:00 PM Central Standard Time +Formatting timestamp=3600 resulted in Wednesday, December 31, 1969 at 7:00:00 PM Central Standard Time diff --git a/ext/intl/tests/datepatterngenerator_get_best_pattern.phpt b/ext/intl/tests/datepatterngenerator_get_best_pattern.phpt index 9e29bc90493e9..279554123b795 100644 --- a/ext/intl/tests/datepatterngenerator_get_best_pattern.phpt +++ b/ext/intl/tests/datepatterngenerator_get_best_pattern.phpt @@ -2,6 +2,8 @@ IntlDatePatternGenerator::getBestPattern() --EXTENSIONS-- intl +--SKIPIF-- += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +getBestPattern("jjmm"), "\n"; +echo $dtpg2->getBestPattern("jjmm"), "\n"; +echo $dtpg3->getBestPattern("YYYYMMMdd"), "\n"; +echo $dtpg4->getBestPattern("YYYYMMMdd"), "\n"; + +echo $dtpg->getBestPattern(""), "\n"; + +try { + $dtpg->getBestPattern(); +} catch(\ArgumentCountError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +h:mm a +HH:mm +MMM dd, YYYY +dd. MMM YYYY + +IntlDatePatternGenerator::getBestPattern() expects exactly 1 argument, 0 given diff --git a/ext/intl/tests/msgfmt_format_datetime.phpt b/ext/intl/tests/msgfmt_format_datetime.phpt index 8b3e37ab30f5d..e6a7451906f77 100644 --- a/ext/intl/tests/msgfmt_format_datetime.phpt +++ b/ext/intl/tests/msgfmt_format_datetime.phpt @@ -4,6 +4,8 @@ MessageFormatter::format(): DateTime accepted to format dates and times date.timezone=Atlantic/Azores --EXTENSIONS-- intl +--SKIPIF-- += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +format(array($dt))); +var_dump($mf->format(array($dti))); + +?> +--EXPECT-- +string(24) "May 6, 2012 5:00:42 PM" +string(24) "May 6, 2012 5:00:42 PM" diff --git a/ext/intl/tests/msgfmt_format_simple_types_numeric_strings.phpt b/ext/intl/tests/msgfmt_format_simple_types_numeric_strings.phpt index 9bc38b683cd0a..f92eaa2bdb54a 100644 --- a/ext/intl/tests/msgfmt_format_simple_types_numeric_strings.phpt +++ b/ext/intl/tests/msgfmt_format_simple_types_numeric_strings.phpt @@ -4,6 +4,8 @@ MessageFormatter::format(): simple types handling with numeric strings date.timezone=Atlantic/Azores --EXTENSIONS-- intl +--SKIPIF-- += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +format(array( +'a' => $ex, +'b' => $ex, +'c' => $ex, +'d' => $ex, +'e' => $ex, +'f' => " 1336317965.5", +'g' => " 1336317965.5", +'h' => $ex, +'i' => $ex, +'j' => $ex, +))); + +?> +--EXPECTF-- +string(%d) " + none 1336317965.5 str + number 1,336,317,965.5 + number integer 1,336,317,965 + number currency $1,336,317,965.50 + number percent 133,631,796,550% + date May %d, 2012 + time 3:26:05 PM + spellout one billion three hundred thirty-six million three hundred seventeen thousand nine hundred sixty-five point five + ordinal 1,336,317,966th + duration 371,199:26:06 +" diff --git a/ext/intl/tests/resourcebundle_null_mandatory_args_variant2.phpt b/ext/intl/tests/resourcebundle_null_mandatory_args_variant2.phpt index 31250027184b3..5aefcf268dff5 100644 --- a/ext/intl/tests/resourcebundle_null_mandatory_args_variant2.phpt +++ b/ext/intl/tests/resourcebundle_null_mandatory_args_variant2.phpt @@ -6,6 +6,7 @@ date.timezone=Atlantic/Azores intl --SKIPIF-- = 51.2'); ?> += 0) die('skip for ICU < 72.1'); ?> --FILE-- = 72.1'); ?> +--FILE-- +get('calendar')->get('gregorian')->get('DateTimePatterns')->get(0); +var_dump($c); + +ini_set('intl.default_locale', 'pt_PT'); +$r = new ResourceBundle(NULL, NULL); +$c = $r->get('calendar')->get('gregorian')->get('DateTimePatterns')->get(0); +var_dump($c); +?> +--EXPECT-- +string(16) "h:mm:ss a zzzz" +string(13) "HH:mm:ss zzzz" diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index b8fb4274215ff..51eca3d7cc4de 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -121,7 +121,7 @@ static zend_object *ldap_link_create_object(zend_class_entry *class_type) { } static zend_function *ldap_link_get_constructor(zend_object *object) { - zend_throw_error(NULL, "Cannot directly construct LDAP\\Connection, use ldap_create() instead"); + zend_throw_error(NULL, "Cannot directly construct LDAP\\Connection, use ldap_connect() instead"); return NULL; } diff --git a/ext/ldap/tests/ldap_constructor.phpt b/ext/ldap/tests/ldap_constructor.phpt index 37db6112dcee5..6c79dee8b3982 100644 --- a/ext/ldap/tests/ldap_constructor.phpt +++ b/ext/ldap/tests/ldap_constructor.phpt @@ -11,4 +11,4 @@ try { echo "Exception: ", $ex->getMessage(), "\n"; } --EXPECT-- -Exception: Cannot directly construct LDAP\Connection, use ldap_create() instead +Exception: Cannot directly construct LDAP\Connection, use ldap_connect() instead diff --git a/ext/mbstring/libmbfl/filters/mbfilter_sjis.c b/ext/mbstring/libmbfl/filters/mbfilter_sjis.c index 1f52e0aa97786..f23a8b08aceab 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_sjis.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_sjis.c @@ -40,7 +40,7 @@ static int mbfl_filt_conv_sjis_wchar_flush(mbfl_convert_filter *filter); static size_t mb_sjis_to_wchar(unsigned char **in, size_t *in_len, uint32_t *buf, size_t bufsize, unsigned int *state); static void mb_wchar_to_sjis(uint32_t *in, size_t len, mb_convert_buf *buf, bool end); -const unsigned char mblen_table_sjis[] = { /* 0x80-0x9f,0xE0-0xFF */ +const unsigned char mblen_table_sjis[] = { /* 0x81-0x9F,0xE0-0xEF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -49,14 +49,14 @@ const unsigned char mblen_table_sjis[] = { /* 0x80-0x9f,0xE0-0xFF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; static const char *mbfl_encoding_sjis_aliases[] = {"x-sjis", "SHIFT-JIS", NULL}; diff --git a/ext/mbstring/libmbfl/filters/mbfilter_sjis_2004.c b/ext/mbstring/libmbfl/filters/mbfilter_sjis_2004.c index 78d4bd431071f..737871eda8a31 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_sjis_2004.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_sjis_2004.c @@ -41,7 +41,7 @@ #include "unicode_table_jis2004.h" #include "unicode_table_jis.h" -extern const unsigned char mblen_table_sjis[]; +extern const unsigned char mblen_table_sjis_mobile[]; extern const unsigned char mblen_table_eucjp[]; static size_t mb_sjis2004_to_wchar(unsigned char **in, size_t *in_len, uint32_t *buf, size_t bufsize, unsigned int *state); @@ -62,7 +62,7 @@ const mbfl_encoding mbfl_encoding_sjis2004 = { "SJIS-2004", "Shift_JIS", mbfl_encoding_sjis2004_aliases, - mblen_table_sjis, + mblen_table_sjis_mobile, /* Leading byte values used for SJIS-2004 are the same as mobile SJIS variants */ MBFL_ENCTYPE_GL_UNSAFE, &vtbl_sjis2004_wchar, &vtbl_wchar_sjis2004, diff --git a/ext/mbstring/libmbfl/filters/mbfilter_sjis_mac.c b/ext/mbstring/libmbfl/filters/mbfilter_sjis_mac.c index 0cb93cc38e8c7..0ff2a198d36c8 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_sjis_mac.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_sjis_mac.c @@ -35,7 +35,24 @@ #include "sjis_mac2uni.h" -extern const unsigned char mblen_table_sjis[]; +const unsigned char mblen_table_sjismac[] = { /* 0x81-0x9F,0xE0-0xED */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; static int mbfl_filt_conv_wchar_sjis_mac_flush(mbfl_convert_filter *filter); static int mbfl_filt_conv_sjis_mac_wchar_flush(mbfl_convert_filter *filter); @@ -49,7 +66,7 @@ const mbfl_encoding mbfl_encoding_sjis_mac = { "SJIS-mac", "Shift_JIS", mbfl_encoding_sjis_mac_aliases, - mblen_table_sjis, + mblen_table_sjismac, MBFL_ENCTYPE_GL_UNSAFE, &vtbl_sjis_mac_wchar, &vtbl_wchar_sjis_mac, diff --git a/ext/mbstring/libmbfl/filters/mbfilter_sjis_mobile.c b/ext/mbstring/libmbfl/filters/mbfilter_sjis_mobile.c index 31a1e3a4d777f..448e0a74ca3aa 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_sjis_mobile.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_sjis_mobile.c @@ -35,8 +35,26 @@ #include "emoji2uni.h" +const unsigned char mblen_table_sjis_mobile[] = { /* 0x81-0x9F,0xE0-0xFC */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1 +}; + extern int mbfl_bisec_srch2(int w, const unsigned short tbl[], int n); -extern const unsigned char mblen_table_sjis[]; static int mbfl_filt_conv_sjis_wchar_flush(mbfl_convert_filter *filter); static size_t mb_sjis_docomo_to_wchar(unsigned char **in, size_t *in_len, uint32_t *buf, size_t bufsize, unsigned int *state); @@ -55,7 +73,7 @@ const mbfl_encoding mbfl_encoding_sjis_docomo = { "SJIS-Mobile#DOCOMO", "Shift_JIS", mbfl_encoding_sjis_docomo_aliases, - mblen_table_sjis, + mblen_table_sjis_mobile, MBFL_ENCTYPE_GL_UNSAFE, &vtbl_sjis_docomo_wchar, &vtbl_wchar_sjis_docomo, @@ -68,7 +86,7 @@ const mbfl_encoding mbfl_encoding_sjis_kddi = { "SJIS-Mobile#KDDI", "Shift_JIS", mbfl_encoding_sjis_kddi_aliases, - mblen_table_sjis, + mblen_table_sjis_mobile, MBFL_ENCTYPE_GL_UNSAFE, &vtbl_sjis_kddi_wchar, &vtbl_wchar_sjis_kddi, @@ -81,7 +99,7 @@ const mbfl_encoding mbfl_encoding_sjis_sb = { "SJIS-Mobile#SOFTBANK", "Shift_JIS", mbfl_encoding_sjis_sb_aliases, - mblen_table_sjis, + mblen_table_sjis_mobile, MBFL_ENCTYPE_GL_UNSAFE, &vtbl_sjis_sb_wchar, &vtbl_wchar_sjis_sb, diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 9735bfef80bb2..34678ea3d0f7f 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -3185,10 +3185,7 @@ PHP_FUNCTION(mb_convert_kana) * or all FW hiragana to FW katakana, or all FW katakana to FW hiragana, but not * more than one of these */ if (opt & MBFL_ZEN2HAN_HIRAGANA) { - if (opt & MBFL_ZEN2HAN_KATAKANA) { - zend_argument_value_error(2, "must not combine 'h' and 'k' flags"); - RETURN_THROWS(); - } else if (opt & MBFL_ZENKAKU_HIRA2KATA) { + if (opt & MBFL_ZENKAKU_HIRA2KATA) { zend_argument_value_error(2, "must not combine 'h' and 'C' flags"); RETURN_THROWS(); } else if (opt & MBFL_ZENKAKU_KATA2HIRA) { diff --git a/ext/mbstring/tests/mb_convert_kana.phpt b/ext/mbstring/tests/mb_convert_kana.phpt index 69ea5560d4803..b263fec0f1b67 100644 --- a/ext/mbstring/tests/mb_convert_kana.phpt +++ b/ext/mbstring/tests/mb_convert_kana.phpt @@ -78,6 +78,7 @@ echo bin2hex(mb_convert_kana("\x30\x9B", 'k', 'UTF-16BE')), "\n"; echo bin2hex(mb_convert_kana("\x30\x9C", 'k', 'UTF-16BE')), "\n"; echo bin2hex(mb_convert_kana("\x30\xFC", 'k', 'UTF-16BE')), "\n"; echo bin2hex(mb_convert_kana("\x30\xFB", 'k', 'UTF-16BE')), "\n"; +echo bin2hex(mb_convert_kana("fooあいうエオ", "rnaskh", "UTF-8")), "\n"; echo "Including one which will expand to two codepoints:\n"; echo bin2hex(mb_convert_kana("\x30\x52", 'h', 'UTF-16BE')), "\n\n"; @@ -117,7 +118,6 @@ tryIncompatibleFlags('R', 'r'); tryIncompatibleFlags('N', 'n'); tryIncompatibleFlags('S', 's'); tryIncompatibleFlags('K', 'H'); -tryIncompatibleFlags('k', 'h'); tryIncompatibleFlags('C', 'c'); tryIncompatibleFlags('M', 'm'); tryIncompatibleFlags('h', 'C'); @@ -204,6 +204,7 @@ ff9e ff9f ff70 ff65 +666f6fefbdb1efbdb2efbdb3efbdb4efbdb5 Including one which will expand to two codepoints: ff79ff9e @@ -237,8 +238,6 @@ mb_convert_kana(): Argument #2 ($mode) must not combine 'S' and 's' flags mb_convert_kana(): Argument #2 ($mode) must not combine 'S' and 's' flags mb_convert_kana(): Argument #2 ($mode) must not combine 'H' and 'K' flags mb_convert_kana(): Argument #2 ($mode) must not combine 'H' and 'K' flags -mb_convert_kana(): Argument #2 ($mode) must not combine 'h' and 'k' flags -mb_convert_kana(): Argument #2 ($mode) must not combine 'h' and 'k' flags mb_convert_kana(): Argument #2 ($mode) must not combine 'C' and 'c' flags mb_convert_kana(): Argument #2 ($mode) must not combine 'C' and 'c' flags mb_convert_kana(): Argument #2 ($mode) must not combine 'M' and 'm' flags diff --git a/ext/mbstring/tests/mb_str_split_jp.phpt b/ext/mbstring/tests/mb_str_split_jp.phpt index 22f39539608c3..41a123d58975f 100644 --- a/ext/mbstring/tests/mb_str_split_jp.phpt +++ b/ext/mbstring/tests/mb_str_split_jp.phpt @@ -69,6 +69,17 @@ if(end($array) !== $enc){ last array element: %s expected: %s\n", unpack("H*", end($array))[1],unpack("H*", $enc)[1]); } +/* SJIS byte 0x80 was previously wrongly treated as the starting byte for a 2-byte character */ +echo "== Regression test for SJIS byte 0x80 ==\n"; +foreach (['SJIS', 'SJIS-2004', 'MacJapanese', 'SJIS-Mobile#DOCOMO', 'SJIS-Mobile#KDDI', 'SJIS-Mobile#SoftBank'] as $encoding) { + $array = mb_str_split("\x80\xA1abc\x80\xA1", 2, $encoding); + echo "$encoding: [" . implode(', ', array_map('bin2hex', $array)) . "]\n"; + + // Also try bytes 0xFD, 0xFE, and 0xFF + $array = mb_str_split("abc\xFD\xFE\xFFab\xFD\xFE\xFF", 2, $encoding); + echo "$encoding: [" . implode(', ', array_map('bin2hex', $array)) . "]\n"; +} + ?> --EXPECT-- BIG-5: a4e9 a5bb @@ -80,3 +91,16 @@ UTF-16LE: e565 2c67 UTF-32BE: 000065e5 0000672c UTF-32LE: e5650000 2c670000 UTF-8: e697a5 e69cac +== Regression test for SJIS byte 0x80 == +SJIS: [80a1, 6162, 6380, a1] +SJIS: [6162, 63fd, feff, 6162, fdfe, ff] +SJIS-2004: [80a1, 6162, 6380, a1] +SJIS-2004: [6162, 63fd, feff, 6162, fdfe, ff] +MacJapanese: [80a1, 6162, 6380, a1] +MacJapanese: [6162, 63fd, feff, 6162, fdfe, ff] +SJIS-Mobile#DOCOMO: [80a1, 6162, 6380, a1] +SJIS-Mobile#DOCOMO: [6162, 63fd, feff, 6162, fdfe, ff] +SJIS-Mobile#KDDI: [80a1, 6162, 6380, a1] +SJIS-Mobile#KDDI: [6162, 63fd, feff, 6162, fdfe, ff] +SJIS-Mobile#SoftBank: [80a1, 6162, 6380, a1] +SJIS-Mobile#SoftBank: [6162, 63fd, feff, 6162, fdfe, ff] diff --git a/ext/mbstring/tests/mb_strlen.phpt b/ext/mbstring/tests/mb_strlen.phpt index 11225917140c2..5ebfcd1aec065 100644 --- a/ext/mbstring/tests/mb_strlen.phpt +++ b/ext/mbstring/tests/mb_strlen.phpt @@ -13,43 +13,59 @@ include_once('common.inc'); mb_detect_order('auto'); // Test string -$euc_jp = '0123����ʸ��������ܸ�Ǥ���EUC-JP��ȤäƤ��ޤ���0123���ܸ�����ݽ�����'; +$euc_jp = mb_convert_encoding("0123この文字列は日本語です。EUC-JPを使っています。0123日本語は面倒臭い。", 'EUC-JP', 'UTF-8'); $ascii = 'abcdefghijklmnopqrstuvwxyz;]=#0123456789'; -// ASCII echo "== ASCII ==\n"; -print mb_strlen($ascii,'ASCII') . "\n"; -print strlen($ascii) . "\n"; +print mb_strlen($ascii,'ASCII') . "\n"; +print strlen($ascii) . "\n"; -// EUC-JP echo "== EUC-JP ==\n"; -print mb_strlen($euc_jp,'EUC-JP') . "\n"; +print mb_strlen($euc_jp,'EUC-JP') . "\n"; mb_internal_encoding('EUC-JP') or print("mb_internal_encoding() failed\n"); -print strlen($euc_jp) . "\n"; +print strlen($euc_jp) . "\n"; -// SJIS echo "== SJIS ==\n"; $sjis = mb_convert_encoding($euc_jp, 'SJIS','EUC-JP'); -print mb_strlen($sjis,'SJIS') . "\n"; +print mb_strlen($sjis,'SJIS') . "\n"; mb_internal_encoding('SJIS') or print("mb_internal_encoding() failed\n"); -print strlen($sjis) . "\n"; +print strlen($sjis) . "\n"; +print "-- Testing illegal bytes 0x80,0xFD-FF --\n"; +// mb_strlen used to wrongly treat 0x80 as the starting byte of a 2-byte SJIS character +print mb_strlen("\x80\xA1", 'SJIS') . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'SJIS') . "\n"; + +echo "== MacJapanese ==\n"; +print mb_strlen("\x80\xA1", 'MacJapanese') . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'MacJapanese') . "\n"; + +echo "== SJIS-2004 ==\n"; +print mb_strlen("\x80\xA1", 'SJIS-2004') . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'SJIS-2004') . "\n"; + +echo "== SJIS-Mobile#DOCOMO ==\n"; +print mb_strlen("\x80\xA1", 'SJIS-Mobile#DOCOMO') . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'SJIS-Mobile#DOCOMO') . "\n"; + +echo "== SJIS-Mobile#KDDI ==\n"; +print mb_strlen("\x80\xA1", 'SJIS-Mobile#KDDI') . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'SJIS-Mobile#KDDI') . "\n"; + +echo "== SJIS-Mobile#SoftBank ==\n"; +print mb_strlen("\x80\xA1", 'SJIS-Mobile#SoftBank') . "\n"; +print mb_strlen("abc\xFD\xFE\xFF", 'SJIS-Mobile#SoftBank') . "\n"; -// JIS -// Note: either convert_encoding or strlen has problem echo "== JIS ==\n"; $jis = mb_convert_encoding($euc_jp, 'JIS','EUC-JP'); -print mb_strlen($jis,'JIS') . "\n"; +print mb_strlen($jis,'JIS') . "\n"; mb_internal_encoding('JIS') or print("mb_internal_encoding() failed\n"); -print strlen($jis) . "\n"; +print strlen($jis) . "\n"; -// UTF-8 -// Note: either convert_encoding or strlen has problem echo "== UTF-8 ==\n"; $utf8 = mb_convert_encoding($euc_jp, 'UTF-8','EUC-JP'); -print mb_strlen($utf8,'UTF-8') . "\n"; +print mb_strlen($utf8,'UTF-8') . "\n"; mb_internal_encoding('UTF-8') or print("mb_internal_encoding() failed\n"); -print strlen($utf8) . "\n"; - +print strlen($utf8) . "\n"; // Wrong Parameters echo "== WRONG PARAMETERS ==\n"; @@ -72,6 +88,24 @@ try { == SJIS == 43 72 +-- Testing illegal bytes 0x80,0xFD-FF -- +2 +6 +== MacJapanese == +2 +6 +== SJIS-2004 == +2 +6 +== SJIS-Mobile#DOCOMO == +2 +6 +== SJIS-Mobile#KDDI == +2 +6 +== SJIS-Mobile#SoftBank == +2 +6 == JIS == 43 90 diff --git a/ext/mbstring/tests/mb_substr.phpt b/ext/mbstring/tests/mb_substr.phpt index 6d5e9d42ac0da..5bd4a5e67f4e2 100644 --- a/ext/mbstring/tests/mb_substr.phpt +++ b/ext/mbstring/tests/mb_substr.phpt @@ -8,13 +8,13 @@ ini_set('include_path','.'); include_once('common.inc'); // EUC-JP -$euc_jp = "0123\xA4\xB3\xA4\xCE\xCA\xB8\xBB\xFA\xCE\xF3\xA4\xCF\xC6\xFC\xCB\xDC\xB8\xEC\xA4\xC7\xA4\xB9\xA1\xA3EUC-JP\xA4\xF2\xBB\xC8\xA4\xC3\xA4\xC6\xA4\xA4\xA4\xDE\xA4\xB9\xA1\xA3\xC6\xFC\xCB\xDC\xB8\xEC\xA4\xCF\xCC\xCC\xC5\xDD\xBD\xAD\xA4\xA4\xA1\xA3"; +$euc_jp = mb_convert_encoding('0123この文字列は日本語です。EUC-JPを使っています。日本語は面倒臭い。', 'EUC-JP', 'UTF-8'); // SJIS -$sjis = "\x93\xFA\x96{\x8C\xEA\x83e\x83L\x83X\x83g\x82\xC5\x82\xB7\x81B01234\x82T\x82U\x82V\x82W\x82X\x81B"; +$sjis = mb_convert_encoding('日本語テキストです。0123456789。', 'SJIS', 'UTF-8'); // ISO-2022-JP $iso2022jp = "\x1B\$B\x21\x21!r\x1B(BABC"; // GB-18030 -$gb18030 = "\xC3\xDC\xC2\xEB\xD3\xC3\xBB\xA7\xC3\xFB\xC3\xDC\xC2\xEB\xC3\xFB\xB3\xC6\xC3\xFB\xB3\xC6"; +$gb18030 = mb_convert_encoding('密码用户名密码名称名称', 'GB18030', 'UTF-8'); // HZ $hz = "The next sentence is in GB.~{<:Ky2;S{#,NpJ)l6HK!#~}Bye."; // UTF-8 @@ -40,6 +40,29 @@ print "2: " . bin2hex(mb_substr($sjis, -1, null, 'SJIS')) . "\n"; print "3: " . bin2hex(mb_substr($sjis, -5, 3, 'SJIS')) . "\n"; print "4: " . bin2hex(mb_substr($sjis, 1, null, 'SJIS')) . "\n"; print "5:" . bin2hex(mb_substr($sjis, 10, 0, 'SJIS')) . "\n"; +echo "-- Testing illegal SJIS byte 0x80 --\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 3, 2, 'SJIS')) . "\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 0, 3, 'SJIS')) . "\n"; + +echo "SJIS-2004:\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 3, 2, 'SJIS-2004')) . "\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 0, 3, 'SJIS-2004')) . "\n"; + +echo "MacJapanese:\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 3, 2, 'MacJapanese')) . "\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 0, 3, 'MacJapanese')) . "\n"; + +echo "SJIS-Mobile#DOCOMO:\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 3, 2, 'SJIS-Mobile#DOCOMO')) . "\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 0, 3, 'SJIS-Mobile#DOCOMO')) . "\n"; + +echo "SJIS-Mobile#KDDI:\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 3, 2, 'SJIS-Mobile#KDDI')) . "\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 0, 3, 'SJIS-Mobile#KDDI')) . "\n"; + +echo "SJIS-Mobile#SoftBank:\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 3, 2, 'SJIS-Mobile#SoftBank')) . "\n"; +print bin2hex(mb_substr("\x80abc\x80\xA1", 0, 3, 'SJIS-Mobile#SoftBank')) . "\n"; echo "ISO-2022-JP:\n"; print "1: " . bin2hex(mb_substr($iso2022jp, 0, 3, 'ISO-2022-JP')) . "\n"; @@ -98,6 +121,24 @@ SJIS: 3: 825582568257 4: 967b8cea8365834c8358836782c582b781423031323334825482558256825782588142 5: +-- Testing illegal SJIS byte 0x80 -- +6380 +806162 +SJIS-2004: +6380 +806162 +MacJapanese: +6380 +806162 +SJIS-Mobile#DOCOMO: +6380 +806162 +SJIS-Mobile#KDDI: +6380 +806162 +SJIS-Mobile#SoftBank: +6380 +806162 ISO-2022-JP: 1: 1b2442212121721b284241 2: 43 diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 2d0dcee76fc52..e26303fb5f43e 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -4412,6 +4412,8 @@ static int accel_preload(const char *config, bool in_child) if (PG(auto_globals_jit)) { ping_auto_globals_mask = zend_accel_get_auto_globals(); + } else { + ping_auto_globals_mask = 0; } if (EG(zend_constants)) { diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 181ab11615c82..429d8dbd49f5a 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -4259,7 +4259,7 @@ static int ZEND_FASTCALL zend_runtime_jit(void) /* perform real JIT for this function */ zend_real_jit_func(op_array, NULL, NULL); } zend_catch { - do_bailout = 0; + do_bailout = true; } zend_end_try(); zend_jit_protect(); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index ff7c84e07c6eb..d47539926b2c8 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -247,6 +247,13 @@ static void zend_jit_trace_add_code(const void *start, uint32_t size) t->code_size = size; } +/** + * Locate a trace in the #zend_jit_traces array with the specified + * #code_start address. + * + * @return the #zend_jit_traces index or 0 if no such #code_start + * address was found + */ static uint32_t zend_jit_find_trace(const void *addr) { uint32_t i; @@ -256,7 +263,6 @@ static uint32_t zend_jit_find_trace(const void *addr) return i; } } - ZEND_UNREACHABLE(); return 0; } @@ -6832,6 +6838,15 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par const void *timeout_exit_addr = NULL; t->link = zend_jit_find_trace(p->opline->handler); + if (t->link == 0) { + /* this can happen if ZEND_JIT_EXIT_INVALIDATE was handled + * by zend_jit_trace_exit() in another thread after this + * thread set ZEND_JIT_TRACE_STOP_LINK in zend_jit_trace_execute(); + * ZEND_JIT_EXIT_INVALIDATE resets the opline handler to one of + * the "_counter_handler" functions, and these are not registered + * tracer functions */ + goto jit_failure; + } if ((zend_jit_traces[t->link].flags & ZEND_JIT_TRACE_USES_INITIAL_IP) && !zend_jit_set_ip(&dasm_state, p->opline)) { goto jit_failure; @@ -7088,6 +7103,7 @@ static zend_jit_trace_stop zend_jit_compile_root_trace(zend_jit_trace_rec *trace if (t->stack_map_size) { zend_jit_trace_stack *shared_stack_map = (zend_jit_trace_stack*)zend_shared_alloc(t->stack_map_size * sizeof(zend_jit_trace_stack)); if (!shared_stack_map) { + efree(t->stack_map); ret = ZEND_JIT_TRACE_STOP_NO_SHM; goto exit; } @@ -8187,22 +8203,34 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf t = &zend_jit_traces[num]; } - SHM_UNPROTECT(); - zend_jit_unprotect(); + zend_shared_alloc_lock(); jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(t->op_array); - if (ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_START_LOOP) { - ((zend_op*)(t->opline))->handler = (const void*)zend_jit_loop_trace_counter_handler; - } else if (ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_START_ENTER) { - ((zend_op*)(t->opline))->handler = (const void*)zend_jit_func_trace_counter_handler; - } else if (ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_START_RETURN) { - ((zend_op*)(t->opline))->handler = (const void*)zend_jit_ret_trace_counter_handler; + + /* Checks under lock, just in case something has changed while we were waiting for the lock */ + if (!(ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & (ZEND_JIT_TRACE_JITED|ZEND_JIT_TRACE_BLACKLISTED))) { + /* skip: not JIT-ed nor blacklisted */ + } else if (ZEND_JIT_TRACE_NUM >= JIT_G(max_root_traces)) { + /* skip: too many root traces */ + } else { + SHM_UNPROTECT(); + zend_jit_unprotect(); + + if (ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_START_LOOP) { + ((zend_op*)(t->opline))->handler = (const void*)zend_jit_loop_trace_counter_handler; + } else if (ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_START_ENTER) { + ((zend_op*)(t->opline))->handler = (const void*)zend_jit_func_trace_counter_handler; + } else if (ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags & ZEND_JIT_TRACE_START_RETURN) { + ((zend_op*)(t->opline))->handler = (const void*)zend_jit_ret_trace_counter_handler; + } + ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags &= + ZEND_JIT_TRACE_START_LOOP|ZEND_JIT_TRACE_START_ENTER|ZEND_JIT_TRACE_START_RETURN; + + zend_jit_protect(); + SHM_PROTECT(); } - ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags &= - ZEND_JIT_TRACE_START_LOOP|ZEND_JIT_TRACE_START_ENTER|ZEND_JIT_TRACE_START_RETURN; - zend_jit_protect(); - SHM_PROTECT(); + zend_shared_alloc_unlock(); return 0; } diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index c2f2772208be5..f2bf8c2a9aceb 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -16178,11 +16178,17 @@ static zend_regset zend_jit_get_scratch_regset(const zend_op *opline, const zend } } if ((op1_info & MAY_BE_LONG) && (op2_info & MAY_BE_DOUBLE)) { + if (opline->op1_type == IS_CONST) { + ZEND_REGSET_INCL(regset, ZREG_R0); + } if (ssa_op->result_def != current_var) { ZEND_REGSET_INCL(regset, ZREG_XMM0); } } if ((op1_info & MAY_BE_DOUBLE) && (op2_info & MAY_BE_LONG)) { + if (opline->op2_type == IS_CONST) { + ZEND_REGSET_INCL(regset, ZREG_R0); + } if (zend_is_commutative(opline->opcode)) { if (ssa_op->result_def != current_var) { ZEND_REGSET_INCL(regset, ZREG_XMM0); diff --git a/ext/opcache/tests/gh9968-1.inc b/ext/opcache/tests/gh9968-1.inc index 468178c78483f..0f2b14673513c 100644 --- a/ext/opcache/tests/gh9968-1.inc +++ b/ext/opcache/tests/gh9968-1.inc @@ -1,3 +1,3 @@ $x,'y'=>$y]; +} +?> +--EXPECT-- +0: In;-0.000516528926;-0.000912408759;968.000000000000;548.000000000000;Out;967.500000000004;547.500000000009 +1: In;-0.000516528926;-0.000912408759;968.000000000000;548.000000000000;Out;967.500000000004;547.500000000009 +2: In;-0.000516528926;-0.000912408759;968.000000000000;548.000000000000;Out;967.500000000004;547.500000000009 diff --git a/ext/opcache/tests/opt/assign_op_002.phpt b/ext/opcache/tests/opt/assign_op_002.phpt new file mode 100644 index 0000000000000..63746ac1c86b6 --- /dev/null +++ b/ext/opcache/tests/opt/assign_op_002.phpt @@ -0,0 +1,18 @@ +--TEST-- +ASSIGN_OP 002: Incorrect optimization of ASSIGN_OP may lead to incorrect result (sub assign -> pre dec conversion for null values) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--FILE-- + +--EXPECT-- +int(1) +int(-1) diff --git a/ext/opcache/tests/opt/match_002.phpt b/ext/opcache/tests/opt/match_002.phpt new file mode 100644 index 0000000000000..ecda31ee7c0ec --- /dev/null +++ b/ext/opcache/tests/opt/match_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Match 002: memory leak because of incorrect optimization +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Undefined constant "y" in %smatch_002.php:2 +Stack trace: +#0 {main} + thrown in %smatch_002.php on line 2 diff --git a/ext/opcache/tests/preload_const_autoload.inc b/ext/opcache/tests/preload_const_autoload.inc index b9634de49a56e..02c4ece92fefa 100644 --- a/ext/opcache/tests/preload_const_autoload.inc +++ b/ext/opcache/tests/preload_const_autoload.inc @@ -4,4 +4,4 @@ spl_autoload_register(function($class) { var_dump($class); new Abc; }); -opcache_compile_file('preload_const_autoload_2.inc'); +opcache_compile_file(__DIR__ . '/preload_const_autoload_2.inc'); diff --git a/ext/opcache/tests/preloading_no_auto_globals_jit.inc b/ext/opcache/tests/preloading_no_auto_globals_jit.inc new file mode 100644 index 0000000000000..ad80df7733d1d --- /dev/null +++ b/ext/opcache/tests/preloading_no_auto_globals_jit.inc @@ -0,0 +1,6 @@ + +--FILE-- +count_global_server()); +?> +--EXPECTF-- +int(%d) diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 0de21811f26e4..ed45c6d7ce40e 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -1164,7 +1164,7 @@ PHP_METHOD(PDO, query) PHP_METHOD(PDO, quote) { pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); - zend_string *str; + zend_string *str, *quoted; zend_long paramtype = PDO_PARAM_STR; ZEND_PARSE_PARAMETERS_START(1, 2) @@ -1180,8 +1180,14 @@ PHP_METHOD(PDO, quote) pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support quoting"); RETURN_FALSE; } + quoted = dbh->methods->quoter(dbh, str, paramtype); - RETURN_STR(dbh->methods->quoter(dbh, str, paramtype)); + if (quoted == NULL) { + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + RETURN_STR(quoted); } /* }}} */ diff --git a/ext/pdo/pdo_sql_parser.re b/ext/pdo/pdo_sql_parser.re index 4c20700379731..6bb0837fb31f9 100644 --- a/ext/pdo/pdo_sql_parser.re +++ b/ext/pdo/pdo_sql_parser.re @@ -242,6 +242,13 @@ safe: if (buf) { zend_string_release_ex(buf, 0); } + if (plc->quoted == NULL) { + /* bork */ + ret = -1; + strncpy(stmt->error_code, stmt->dbh->error_code, 6); + goto clean_up; + } + } else { pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); ret = -1; diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c index 6a41e2162a934..de5170a35a96b 100644 --- a/ext/pdo_sqlite/sqlite_driver.c +++ b/ext/pdo_sqlite/sqlite_driver.c @@ -226,7 +226,11 @@ static zend_string *pdo_sqlite_last_insert_id(pdo_dbh_t *dbh, const zend_string /* NB: doesn't handle binary strings... use prepared stmts for that */ static zend_string* sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { - char *quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + char *quoted; + if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) { + return NULL; + } + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); /* TODO use %Q format? */ sqlite3_snprintf(2*ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted)); zend_string *quoted_str = zend_string_init(quoted, strlen(quoted), 0); diff --git a/ext/pdo_sqlite/tests/bug81740.phpt b/ext/pdo_sqlite/tests/bug81740.phpt new file mode 100644 index 0000000000000..2b8b9447f0fed --- /dev/null +++ b/ext/pdo_sqlite/tests/bug81740.phpt @@ -0,0 +1,20 @@ +--TEST-- +Bug #81740 (PDO::quote() may return unquoted string) +--EXTENSIONS-- +pdo +pdo_sqlite +--SKIPIF-- + +--INI-- +memory_limit=-1 +--FILE-- +quote($string)); +?> +--EXPECT-- +bool(false) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 5421b12198e8b..0e9f492b61f49 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -3314,7 +3314,7 @@ PHP_METHOD(Phar, compressFiles) } if (!pharobj_cancompress(&phar_obj->archive->manifest)) { - if (flags == PHAR_FILE_COMPRESSED_GZ) { + if (flags == PHAR_ENT_COMPRESSED_GZ) { zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot compress all files as Gzip, some are compressed as bzip2 and cannot be decompressed"); } else { diff --git a/ext/posix/posix.c b/ext/posix/posix.c index ed7a93c540996..788fca0b2879d 100644 --- a/ext/posix/posix.c +++ b/ext/posix/posix.c @@ -474,15 +474,15 @@ PHP_FUNCTION(posix_ttyname) efree(p); RETURN_FALSE; } - RETURN_STRING(p); + RETVAL_STRING(p); efree(p); #else if (NULL == (p = ttyname(fd))) { POSIX_G(last_error) = errno; RETURN_FALSE; } -#endif RETURN_STRING(p); +#endif } /* }}} */ diff --git a/ext/posix/tests/posix_getgrgid_basic.phpt b/ext/posix/tests/posix_getgrgid_basic.phpt index 6b0daa5e940fc..392e81d491d61 100644 --- a/ext/posix/tests/posix_getgrgid_basic.phpt +++ b/ext/posix/tests/posix_getgrgid_basic.phpt @@ -17,7 +17,7 @@ Basic test of POSIX getgid and getgrid functions Array ( [name] => %s - [passwd] => %a + [passwd] => %A [members] => Array %a diff --git a/ext/posix/tests/posix_getgrnam_basic.phpt b/ext/posix/tests/posix_getgrnam_basic.phpt index 5203d1ea0f056..acf8f4473e5d8 100644 --- a/ext/posix/tests/posix_getgrnam_basic.phpt +++ b/ext/posix/tests/posix_getgrnam_basic.phpt @@ -20,7 +20,7 @@ array(4) { ["name"]=> string(%d) "%s" ["passwd"]=> - string(1) "%s" + string(%d) "%S" ["members"]=> %a ["gid"]=> diff --git a/ext/random/random.c b/ext/random/random.c index 161eb8e685203..64e30e5087186 100644 --- a/ext/random/random.c +++ b/ext/random/random.c @@ -828,7 +828,7 @@ static PHP_GINIT_FUNCTION(random) /* {{{ PHP_GSHUTDOWN_FUNCTION */ static PHP_GSHUTDOWN_FUNCTION(random) { - if (random_globals->random_fd > 0) { + if (random_globals->random_fd >= 0) { close(random_globals->random_fd); random_globals->random_fd = -1; } diff --git a/ext/simplexml/tests/gh10200.phpt b/ext/simplexml/tests/gh10200.phpt new file mode 100644 index 0000000000000..ad02d45bfe63c --- /dev/null +++ b/ext/simplexml/tests/gh10200.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-10200 (zif_get_object_vars: Assertion `!(((__ht)->u.flags & (1<<2)) != 0)' failed.) +--EXTENSIONS-- +simplexml +--FILE-- + +https://github.com/php/php-src/issues/10200 not encountered +EOF; + +$xml = simplexml_load_string($xmlData); +$output = get_object_vars($xml); +var_dump($output); + +?> +--EXPECT-- +array(1) { + [0]=> + string(59) "https://github.com/php/php-src/issues/10200%20not%20encountered" +} diff --git a/ext/spl/tests/bug73029.phpt b/ext/spl/tests/bug73029.phpt index 5c3cd8420da8d..9e002f1182939 100644 --- a/ext/spl/tests/bug73029.phpt +++ b/ext/spl/tests/bug73029.phpt @@ -3,6 +3,13 @@ Bug #73029: Missing type check when unserializing SplArray --FILE-- getMessage() . "\n"; +} +try { $a = 'C:11:"ArrayObject":19:0x:i:0;r:2;;m:a:0:{}}'; $m = unserialize($a); $x = $m[2]; @@ -11,6 +18,10 @@ $x = $m[2]; } ?> DONE ---EXPECT-- +--EXPECTF-- Error at offset 10 of 19 bytes + +Notice: unserialize(): Error at offset 22 of 43 bytes in %s on line %d + +Warning: Trying to access array offset on value of type bool in %s on line %d DONE diff --git a/ext/standard/string.c b/ext/standard/string.c index a47687667a470..192a8b1602aad 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -2365,7 +2365,7 @@ PHP_FUNCTION(substr_replace) if (HT_IS_PACKED(repl_ht)) { while (repl_idx < repl_ht->nNumUsed) { tmp_repl = &repl_ht->arPacked[repl_idx]; - if (repl_ht != IS_UNDEF) { + if (Z_TYPE_P(tmp_repl) != IS_UNDEF) { break; } repl_idx++; @@ -2373,7 +2373,7 @@ PHP_FUNCTION(substr_replace) } else { while (repl_idx < repl_ht->nNumUsed) { tmp_repl = &repl_ht->arData[repl_idx].val; - if (repl_ht != IS_UNDEF) { + if (Z_TYPE_P(tmp_repl) != IS_UNDEF) { break; } repl_idx++; @@ -3990,19 +3990,23 @@ static zend_always_inline char *php_stripslashes_impl(const char *str, char *out quad_word q; vst1q_u8(q.mem, vceqq_u8(x, vdupq_n_u8('\\'))); if (q.dw[0] | q.dw[1]) { - int i = 0; - for (; i < 16; i++) { + unsigned int i = 0; + while (i < 16) { if (q.mem[i] == 0) { *out++ = str[i]; + i++; continue; } i++; /* skip the slash */ - char s = str[i]; - if (s == '0') - *out++ = '\0'; - else - *out++ = s; /* preserve the next character */ + if (i < len) { + char s = str[i]; + if (s == '0') + *out++ = '\0'; + else + *out++ = s; /* preserve the next character */ + i++; + } } str += i; len -= i; diff --git a/ext/standard/tests/serialize/bug73341.phpt b/ext/standard/tests/serialize/bug73341.phpt index 2f38949c1ae8f..3bdad06440be0 100644 --- a/ext/standard/tests/serialize/bug73341.phpt +++ b/ext/standard/tests/serialize/bug73341.phpt @@ -2,6 +2,13 @@ Bug #73144 (Use-afte-free in ArrayObject Deserialization) --FILE-- getMessage()."\n"; +} + try { $token = 'a:2:{i:0;O:1:"0":2:0s:1:"0";i:0;s:1:"0";a:1:{i:0;C:11:"ArrayObject":7:{x:i:0;r}'; $obj = unserialize($token); @@ -20,5 +27,7 @@ unserialize($exploit); --EXPECTF-- Error at offset 6 of 7 bytes +Notice: unserialize(): Error at offset 19 of 79 bytes in %s on line %d + Notice: ArrayObject::unserialize(): Unexpected end of serialized data in %sbug73341.php on line %d Error at offset 24 of 34 bytes diff --git a/ext/standard/tests/serialize/bug74111.phpt b/ext/standard/tests/serialize/bug74111.phpt index 62922bea55ae0..2062687aa271e 100644 --- a/ext/standard/tests/serialize/bug74111.phpt +++ b/ext/standard/tests/serialize/bug74111.phpt @@ -6,5 +6,5 @@ $s = 'O:8:"stdClass":00000000'; var_dump(unserialize($s)); ?> --EXPECTF-- -Notice: unserialize(): Error at offset 25 of 23 bytes in %s on line %d +Notice: unserialize(): Error at offset 23 of 23 bytes in %s on line %d bool(false) diff --git a/ext/standard/tests/serialize/serialization_objects_017.phpt b/ext/standard/tests/serialize/serialization_objects_017.phpt new file mode 100644 index 0000000000000..901d73f2b24aa --- /dev/null +++ b/ext/standard/tests/serialize/serialization_objects_017.phpt @@ -0,0 +1,17 @@ +--TEST-- +Object serialization / unserialization: Strict format +--FILE-- + +--EXPECTF-- +Notice: unserialize(): Error at offset 9 of 22 bytes in %s on line %d +bool(false) + +Notice: unserialize(): Error at offset 10 of 22 bytes in %s on line %d +bool(false) diff --git a/ext/standard/tests/serialize/serialization_objects_018.phpt b/ext/standard/tests/serialize/serialization_objects_018.phpt new file mode 100644 index 0000000000000..e05ff5ca04608 --- /dev/null +++ b/ext/standard/tests/serialize/serialization_objects_018.phpt @@ -0,0 +1,33 @@ +--TEST-- +Object serialization / unserialization: Strict format (2) +--FILE-- + +--EXPECTF-- +Notice: unserialize(): Error at offset 9 of 15 bytes in %s on line %d +bool(false) + +Notice: unserialize(): Error at offset 10 of 15 bytes in %s on line %d +bool(false) + +Notice: unserialize(): Error at offset 14 of 15 bytes in %s on line %d +bool(false) + +Notice: unserialize(): Error at offset 8 of 8 bytes in %s on line %d +bool(false) diff --git a/ext/standard/tests/strings/gh10187.phpt b/ext/standard/tests/strings/gh10187.phpt new file mode 100644 index 0000000000000..b42c95e591ed0 --- /dev/null +++ b/ext/standard/tests/strings/gh10187.phpt @@ -0,0 +1,8 @@ +--TEST-- +GH-10187 (Segfault in stripslashes() with arm64) +--FILE-- + +--EXPECT-- +string(15) "1234567890abcde" diff --git a/ext/standard/tests/strings/substr_replace_array_unset.phpt b/ext/standard/tests/strings/substr_replace_array_unset.phpt new file mode 100644 index 0000000000000..ff253d3984476 --- /dev/null +++ b/ext/standard/tests/strings/substr_replace_array_unset.phpt @@ -0,0 +1,27 @@ +--TEST-- +substr_replace() function - array with unset +--FILE-- + 'bar', 'baz']; +unset($replacement[42]); +$newarr = substr_replace(['1 string', '2 string'], $replacement, 0); +print_r($newarr); + +?> +--EXPECT-- +Array +( + [0] => A + [1] => B +) +Array +( + [0] => foo + [1] => baz +) diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index bcf19002fc0a2..313d7fadeb394 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -743,6 +743,19 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) datalen = parse_iv2((*p) + 2, p); + if (max - (*p) < 2) { + return 0; + } + + if ((*p)[0] != ':') { + return 0; + } + + if ((*p)[1] != '{') { + (*p) += 1; + return 0; + } + (*p) += 2; if (datalen < 0 || (max - (*p)) <= datalen) { @@ -754,6 +767,7 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) * with unserialize reading past the end of the passed buffer if the string is not * appropriately terminated (usually NUL terminated, but '}' is also sufficient.) */ if ((*p)[datalen] != '}') { + (*p) += datalen; return 0; } @@ -1293,6 +1307,16 @@ object ":" uiv ":" ["] { return 0; } + YYCURSOR = *p; + + if (*(YYCURSOR) != ':') { + return 0; + } + if (*(YYCURSOR+1) != '{') { + *p = YYCURSOR+1; + return 0; + } + *p += 2; has_unserialize = !incomplete_class && ce->__unserialize; diff --git a/ext/xmlwriter/php_xmlwriter.c b/ext/xmlwriter/php_xmlwriter.c index 8d4abb3d26b06..68961429dea59 100644 --- a/ext/xmlwriter/php_xmlwriter.c +++ b/ext/xmlwriter/php_xmlwriter.c @@ -449,7 +449,7 @@ PHP_FUNCTION(xmlwriter_write_element) if (retval == -1) { RETURN_FALSE; } - xmlTextWriterEndElement(ptr); + retval = xmlTextWriterEndElement(ptr); if (retval == -1) { RETURN_FALSE; } diff --git a/main/php_version.h b/main/php_version.h index 4cc596766d9c1..05099d8764096 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 1 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.1-dev" -#define PHP_VERSION_ID 80201 +#define PHP_RELEASE_VERSION 2 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.2.2" +#define PHP_VERSION_ID 80202 diff --git a/main/safe_bcmp.c b/main/safe_bcmp.c index 27a1756d79b46..3e806de4ab6e3 100644 --- a/main/safe_bcmp.c +++ b/main/safe_bcmp.c @@ -19,7 +19,7 @@ #include /* - * Returns 0 if both inputs match, 1 if they don't. + * Returns 0 if both inputs match, non-zero if they don't. * Returns -1 early if inputs do not have the same lengths. * */ @@ -34,6 +34,7 @@ PHPAPI int php_safe_bcmp(const zend_string *a, const zend_string *b) return -1; } + /* This is security sensitive code. Do not optimize this for speed. */ while (i < ZSTR_LEN(a)) { r |= ua[i] ^ ub[i]; ++i; diff --git a/run-tests.php b/run-tests.php index 81306bb90efdd..e7cc6a7f28c52 100755 --- a/run-tests.php +++ b/run-tests.php @@ -1504,6 +1504,10 @@ function run_all_tests_parallel(array $test_files, array $env, $redir_tested): v kill_children($workerProcs); error("Could not find worker stdout in array of worker stdouts, THIS SHOULD NOT HAPPEN."); } + if (feof($workerSock)) { + kill_children($workerProcs); + error("Worker $i died unexpectedly"); + } while (false !== ($rawMessage = fgets($workerSock))) { // work around fgets truncating things if (($rawMessageBuffers[$i] ?? '') !== '') { diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c index f2d9d3c088a75..ef9b35a9994aa 100644 --- a/sapi/fpm/fpm/fpm_conf.c +++ b/sapi/fpm/fpm/fpm_conf.c @@ -34,6 +34,7 @@ #include "fpm_status.h" #include "fpm_log.h" #include "fpm_events.h" +#include "fpm_unix.h" #include "zlog.h" #ifdef HAVE_SYSTEMD #include "fpm_systemd.h" @@ -1854,6 +1855,12 @@ int fpm_conf_init_main(int test_conf, int force_daemon) /* {{{ */ } if (test_conf) { + for (struct fpm_worker_pool_s *wp = fpm_worker_all_pools; wp; wp = wp->next) { + if (!fpm_unix_test_config(wp)) { + return -1; + } + } + if (test_conf > 1) { fpm_conf_dump(); } diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index a8d77ef3bed21..0a61abacd70df 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -149,6 +149,7 @@ typedef struct _php_cgi_globals_struct { bool force_redirect; bool discard_path; bool fcgi_logging; + bool fcgi_logging_request_started; char *redirect_status_env; HashTable user_config_cache; char *error_header; @@ -598,11 +599,16 @@ void sapi_cgi_log_fastcgi(int level, char *message, size_t len) * - logging is enabled (fastcgi.logging in php.ini) * - we are currently dealing with a request * - the message is not empty - * - the fcgi_write did not fail */ - if (CGIG(fcgi_logging) && request && message && len > 0 - && fcgi_write(request, FCGI_STDERR, message, len) < 0) { - php_handle_aborted_connection(); + if (CGIG(fcgi_logging) && request && message && len > 0) { + if (CGIG(fcgi_logging_request_started)) { + fcgi_write(request, FCGI_STDERR, "; ", 2); + } else { + CGIG(fcgi_logging_request_started) = true; + } + if (fcgi_write(request, FCGI_STDERR, message, len) < 0) { + php_handle_aborted_connection(); + } } } /* }}} */ @@ -1403,6 +1409,7 @@ static void php_cgi_globals_ctor(php_cgi_globals_struct *php_cgi_globals) php_cgi_globals->fix_pathinfo = 1; php_cgi_globals->discard_path = 0; php_cgi_globals->fcgi_logging = 1; + php_cgi_globals->fcgi_logging_request_started = false; zend_hash_init(&php_cgi_globals->user_config_cache, 0, NULL, user_config_cache_entry_dtor, 1); php_cgi_globals->error_header = NULL; php_cgi_globals->fpm_config = NULL; @@ -1791,7 +1798,8 @@ consult the installation file that came with this distribution, or visit \n\ zend_quiet_write(fpm_globals.send_config_pipe[1], &writeval, sizeof(writeval)); close(fpm_globals.send_config_pipe[1]); } - return FPM_EXIT_CONFIG; + exit_status = FPM_EXIT_CONFIG; + goto out; } if (fpm_globals.send_config_pipe[1]) { @@ -1820,6 +1828,7 @@ consult the installation file that came with this distribution, or visit \n\ char *primary_script = NULL; request_body_fd = -1; SG(server_context) = (void *) request; + CGIG(fcgi_logging_request_started) = false; init_request_info(); fpm_request_info(); @@ -1888,6 +1897,9 @@ consult the installation file that came with this distribution, or visit \n\ fpm_request_executing(); + /* Reset exit status from the previous execution */ + EG(exit_status) = 0; + php_execute_script(&file_handle); fastcgi_request_done: diff --git a/sapi/fpm/fpm/fpm_unix.c b/sapi/fpm/fpm/fpm_unix.c index b1127b35949d3..d10a6f3254b24 100644 --- a/sapi/fpm/fpm/fpm_unix.c +++ b/sapi/fpm/fpm/fpm_unix.c @@ -46,6 +46,55 @@ size_t fpm_pagesize; + +static inline bool fpm_unix_is_id(const char* name) +{ + return strlen(name) == strspn(name, "0123456789"); +} + +static struct passwd *fpm_unix_get_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags) +{ + struct passwd *pwd = getpwnam(name); + if (!pwd) { + zlog(flags, "[pool %s] cannot get uid for user '%s'", wp->config->name, name); + return NULL; + } + + return pwd; +} + +static inline bool fpm_unix_check_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags) +{ + return !name || fpm_unix_is_id(name) || fpm_unix_get_passwd(wp, name, flags); +} + +static struct group *fpm_unix_get_group(struct fpm_worker_pool_s *wp, const char *name, int flags) +{ + struct group *group = getgrnam(name); + if (!group) { + zlog(flags, "[pool %s] cannot get gid for group '%s'", wp->config->name, name); + return NULL; + } + + return group; +} + +static inline bool fpm_unix_check_group(struct fpm_worker_pool_s *wp, const char *name, int flags) +{ + return !name || fpm_unix_is_id(name) || fpm_unix_get_group(wp, name, flags); +} + +bool fpm_unix_test_config(struct fpm_worker_pool_s *wp) +{ + struct fpm_worker_pool_config_s *config = wp->config; + return ( + fpm_unix_check_passwd(wp, config->user, ZLOG_ERROR) && + fpm_unix_check_group(wp, config->group, ZLOG_ERROR) && + fpm_unix_check_passwd(wp, config->listen_owner, ZLOG_SYSERROR) && + fpm_unix_check_group(wp, config->listen_group, ZLOG_SYSERROR) + ); +} + int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ { struct fpm_worker_pool_config_s *c = wp->config; @@ -105,11 +154,10 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ if ((end = strchr(p, ','))) { *end++ = 0; } - pwd = getpwnam(p); + pwd = fpm_unix_get_passwd(wp, p, ZLOG_SYSERROR); if (pwd) { zlog(ZLOG_DEBUG, "[pool %s] user '%s' have uid=%d", wp->config->name, p, pwd->pw_uid); } else { - zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, p); acl_free(acl); efree(tmp); return -1; @@ -138,11 +186,10 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ if ((end = strchr(p, ','))) { *end++ = 0; } - grp = getgrnam(p); + grp = fpm_unix_get_group(wp, p, ZLOG_SYSERROR); if (grp) { zlog(ZLOG_DEBUG, "[pool %s] group '%s' have gid=%d", wp->config->name, p, grp->gr_gid); } else { - zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, p); acl_free(acl); efree(tmp); return -1; @@ -175,14 +222,13 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ #endif if (c->listen_owner && *c->listen_owner) { - if (strlen(c->listen_owner) == strspn(c->listen_owner, "0123456789")) { + if (fpm_unix_is_id(c->listen_owner)) { wp->socket_uid = strtoul(c->listen_owner, 0, 10); } else { struct passwd *pwd; - pwd = getpwnam(c->listen_owner); + pwd = fpm_unix_get_passwd(wp, c->listen_owner, ZLOG_SYSERROR); if (!pwd) { - zlog(ZLOG_SYSERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, c->listen_owner); return -1; } @@ -192,14 +238,13 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */ } if (c->listen_group && *c->listen_group) { - if (strlen(c->listen_group) == strspn(c->listen_group, "0123456789")) { + if (fpm_unix_is_id(c->listen_group)) { wp->socket_gid = strtoul(c->listen_group, 0, 10); } else { struct group *grp; - grp = getgrnam(c->listen_group); + grp = fpm_unix_get_group(wp, c->listen_group, ZLOG_SYSERROR); if (!grp) { - zlog(ZLOG_SYSERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, c->listen_group); return -1; } wp->socket_gid = grp->gr_gid; @@ -279,7 +324,7 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ if (is_root) { if (wp->config->user && *wp->config->user) { - if (strlen(wp->config->user) == strspn(wp->config->user, "0123456789")) { + if (fpm_unix_is_id(wp->config->user)) { wp->set_uid = strtoul(wp->config->user, 0, 10); pwd = getpwuid(wp->set_uid); if (pwd) { @@ -289,9 +334,8 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ } else { struct passwd *pwd; - pwd = getpwnam(wp->config->user); + pwd = fpm_unix_get_passwd(wp, wp->config->user, ZLOG_ERROR); if (!pwd) { - zlog(ZLOG_ERROR, "[pool %s] cannot get uid for user '%s'", wp->config->name, wp->config->user); return -1; } @@ -304,14 +348,13 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ } if (wp->config->group && *wp->config->group) { - if (strlen(wp->config->group) == strspn(wp->config->group, "0123456789")) { + if (fpm_unix_is_id(wp->config->group)) { wp->set_gid = strtoul(wp->config->group, 0, 10); } else { struct group *grp; - grp = getgrnam(wp->config->group); + grp = fpm_unix_get_group(wp, wp->config->group, ZLOG_ERROR); if (!grp) { - zlog(ZLOG_ERROR, "[pool %s] cannot get gid for group '%s'", wp->config->name, wp->config->group); return -1; } wp->set_gid = grp->gr_gid; diff --git a/sapi/fpm/fpm/fpm_unix.h b/sapi/fpm/fpm/fpm_unix.h index 0bb22687b02f9..6fc9e5e8450dc 100644 --- a/sapi/fpm/fpm/fpm_unix.h +++ b/sapi/fpm/fpm/fpm_unix.h @@ -5,6 +5,8 @@ #include "fpm_worker_pool.h" +bool fpm_unix_test_config(struct fpm_worker_pool_s *wp); + int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp); int fpm_unix_set_socket_permissions(struct fpm_worker_pool_s *wp, const char *path); int fpm_unix_free_socket_permissions(struct fpm_worker_pool_s *wp); diff --git a/sapi/fpm/tests/bug68591-conf-test-group.phpt b/sapi/fpm/tests/bug68591-conf-test-group.phpt new file mode 100644 index 0000000000000..d496d0549e87f --- /dev/null +++ b/sapi/fpm/tests/bug68591-conf-test-group.phpt @@ -0,0 +1,39 @@ +--TEST-- +FPM: bug68591 - config test group existence +--SKIPIF-- + +--FILE-- +testConfig(); + +?> +Done +--EXPECT-- +ERROR: [pool unconfined] cannot get gid for group 'aaaaaa' +ERROR: FPM initialization failed +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/bug68591-conf-test-listen-group.phpt b/sapi/fpm/tests/bug68591-conf-test-listen-group.phpt new file mode 100644 index 0000000000000..71ca71adf8316 --- /dev/null +++ b/sapi/fpm/tests/bug68591-conf-test-listen-group.phpt @@ -0,0 +1,39 @@ +--TEST-- +FPM: bug68591 - config test listen group existence +--SKIPIF-- + +--FILE-- +testConfig(); + +?> +Done +--EXPECTF-- +ERROR: [pool unconfined] cannot get gid for group 'aaaaaa': %s +ERROR: FPM initialization failed +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/bug68591-conf-test-listen-owner.phpt b/sapi/fpm/tests/bug68591-conf-test-listen-owner.phpt new file mode 100644 index 0000000000000..c6108bcddeb68 --- /dev/null +++ b/sapi/fpm/tests/bug68591-conf-test-listen-owner.phpt @@ -0,0 +1,39 @@ +--TEST-- +FPM: bug68591 - config test listen owner existence +--SKIPIF-- + +--FILE-- +testConfig(); + +?> +Done +--EXPECTF-- +ERROR: [pool unconfined] cannot get uid for user 'aaaaaa': %s +ERROR: FPM initialization failed +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/bug68591-conf-test-user.phpt b/sapi/fpm/tests/bug68591-conf-test-user.phpt new file mode 100644 index 0000000000000..5627151f3f13e --- /dev/null +++ b/sapi/fpm/tests/bug68591-conf-test-user.phpt @@ -0,0 +1,38 @@ +--TEST-- +FPM: bug68591 - config test user existence +--SKIPIF-- + +--FILE-- +testConfig(); + +?> +Done +--EXPECT-- +ERROR: [pool unconfined] cannot get uid for user 'aaaaaa' +ERROR: FPM initialization failed +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/bug77106-fcgi-missing-nl.phpt b/sapi/fpm/tests/bug77106-fcgi-missing-nl.phpt new file mode 100644 index 0000000000000..c4c7adf382545 --- /dev/null +++ b/sapi/fpm/tests/bug77106-fcgi-missing-nl.phpt @@ -0,0 +1,46 @@ +--TEST-- +FPM: bug77106 - Missing new lines in FCGI error stream +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$tester->request()->expectErrorPattern( + '/Undefined variable \$a in .+ on line \d+; PHP message: PHP Warning:/' +); +$tester->terminate(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/gh9981-fastcgi-error-header-reset.phpt b/sapi/fpm/tests/gh9981-fastcgi-error-header-reset.phpt new file mode 100644 index 0000000000000..5a1d65f1cb0bd --- /dev/null +++ b/sapi/fpm/tests/gh9981-fastcgi-error-header-reset.phpt @@ -0,0 +1,51 @@ +--TEST-- +FPM: gh9981 - fastcgi.error_header is not reset +--SKIPIF-- + +--FILE-- +start(iniEntries: [ + 'fastcgi.error_header' => '"HTTP/1.1 500 PHP Error"', + 'output_buffering' => 4096, +]); +$tester->expectLogStartNotices(); +$tester->request()->expectStatus('500 PHP Error'); +$tester->request('q=1')->expectNoStatus(); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->expectNoLogPattern('/Cannot modify header information/'); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc index dd74e42990b08..97e5c3263e290 100644 --- a/sapi/fpm/tests/response.inc +++ b/sapi/fpm/tests/response.inc @@ -41,6 +41,11 @@ class Response */ private $expectInvalid; + /** + * @var bool + */ + private bool $debugOutputted = false; + /** * @param string|array|null $data * @param bool $expectInvalid @@ -71,9 +76,9 @@ class Response $body = implode("\n", $body); } - if (!$this->checkIfValid()) { + if ( ! $this->checkIfValid()) { $this->error('Response is invalid'); - } elseif (!$this->checkDefaultHeaders($contentType)) { + } elseif ( ! $this->checkDefaultHeaders($contentType)) { $this->error('Response default headers not found'); } elseif ($body !== $this->rawBody) { if ($multiLine) { @@ -134,6 +139,57 @@ class Response return $this; } + /** + * Expect error pattern in the response. + * + * @param string $errorMessagePattern Expected error message RegExp patter. + * + * @return Response + */ + public function expectErrorPattern(string $errorMessagePattern): Response + { + $errorData = $this->getErrorData(); + if (preg_match($errorMessagePattern, $errorData) === 0) { + $this->error( + "The expected error pattern $errorMessagePattern does not match the returned error '$errorData'" + ); + $this->debugOutput(); + } + + return $this; + } + + /** + * Expect response status. + * + * @param string|null $status Expected status. + * + * @return Response + */ + public function expectStatus(string|null $status): Response { + $headers = $this->getHeaders(); + if (is_null($status) && !isset($headers['status'])) { + return $this; + } + if (!is_null($status) && !isset($headers['status'])) { + $this->error('Status is expected but not supplied'); + } elseif ($status !== $headers['status']) { + $statusMessage = $status === null ? "expected not to be set": "expected to be $status"; + $this->error("Status is $statusMessage but the actual value is {$headers['status']}"); + } + + return $this; + } + + /** + * Expect response status not to be set. + * + * @return Response + */ + public function expectNoStatus(): Response { + return $this->expectStatus(null); + } + /** * Expect no error in the response. * @@ -187,6 +243,8 @@ class Response echo "----------------- ERR -----------------\n"; echo $this->data['err_response'] . "\n"; echo "---------------------------------------\n\n"; + + $this->debugOutputted = true; } /** @@ -331,8 +389,11 @@ class Response * * @return bool */ - private function error($message): bool + private function error(string $message): bool { + if ( ! $this->debugOutputted) { + $this->debugOutput(); + } echo "ERROR: $message\n"; return false; diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 50de960aa8dde..db8f0c04f4a61 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -269,10 +269,11 @@ class Tester static public function skipIfConfigFails(string $configTemplate) { $tester = new self($configTemplate, '', [], self::getCallerFileName()); - $testResult = $tester->testConfig(); + $testResult = $tester->testConfig(true); if ($testResult !== null) { self::clean(2); - die("skip $testResult"); + $message = $testResult[0] ?? 'Config failed'; + die("skip $message"); } } @@ -378,17 +379,26 @@ class Tester /** * Test configuration file. * - * @return null|string + * @return null|array * @throws \Exception */ - public function testConfig() + public function testConfig($silent = false) { $configFile = $this->createConfig(); $cmd = self::findExecutable() . ' -tt -y ' . $configFile . ' 2>&1'; $this->trace('Testing config using command', $cmd, true); exec($cmd, $output, $code); if ($code) { - return preg_replace("/\[.+?\]/", "", $output[0]); + $messages = []; + foreach ($output as $outputLine) { + $message = preg_replace("/\[.+?\]/", "", $outputLine, 1); + $messages[] = $message; + if ( ! $silent) { + $this->error($message, null, false); + } + } + + return $messages; } return null; @@ -1259,14 +1269,15 @@ class Tester /** * Display error. * - * @param string $msg - * @param \Exception|null $exception + * @param string $msg Error message. + * @param \Exception|null $exception If there is an exception, log its message + * @param bool $prefix Whether to prefix the error message * * @return false */ - private function error($msg, \Exception $exception = null): bool + private function error(string $msg, \Exception $exception = null, bool $prefix = true): bool { - $this->error = 'ERROR: ' . $msg; + $this->error = $prefix ? 'ERROR: ' . $msg : ltrim($msg); if ($exception) { $this->error .= '; EXCEPTION: ' . $exception->getMessage(); } diff --git a/sapi/fpm/www.conf.in b/sapi/fpm/www.conf.in index dfb41b680e42c..37ac8a194f7f8 100644 --- a/sapi/fpm/www.conf.in +++ b/sapi/fpm/www.conf.in @@ -17,9 +17,14 @@ ; Default Value: none ;prefix = /path/to/pools/$pool -; Unix user/group of processes -; Note: The user is mandatory. If the group is not set, the default user's group -; will be used. +; Unix user/group of the child processes. This can be used only if the master +; process running user is root. It is set after the child process is created. +; The user and group can be specified either by their name or by their numeric +; IDs. +; Note: If the user is root, the executable needs to be started with +; --allow-to-run-as-root option to work. +; Default Values: The user is set to master process running user by default. +; If the group is not set, the user's group is used. user = @php_fpm_user@ group = @php_fpm_group@ @@ -43,11 +48,12 @@ listen = 127.0.0.1:9000 ; permissions must be set in order to allow connections from a web server. Many ; BSD-derived systems allow connections regardless of permissions. The owner ; and group can be specified either by name or by their numeric IDs. -; Default Values: user and group are set as the running user -; mode is set to 0660 +; Default Values: Owner is set to the master process running user. If the group +; is not set, the owner's group is used. Mode is set to 0660. ;listen.owner = @php_fpm_user@ ;listen.group = @php_fpm_group@ ;listen.mode = 0660 + ; When POSIX Access Control Lists are supported you can set them using ; these options, value is a comma separated list of user/group names. ; When set, listen.owner and listen.group are ignored diff --git a/sapi/phpdbg/phpdbg.c b/sapi/phpdbg/phpdbg.c index 0540676e0f5f2..d4ffcdf473df8 100644 --- a/sapi/phpdbg/phpdbg.c +++ b/sapi/phpdbg/phpdbg.c @@ -1378,6 +1378,8 @@ int main(int argc, char **argv) /* {{{ */ get_zend_version() ); } + PHPDBG_G(flags) |= PHPDBG_IS_QUITTING; + php_module_shutdown(); sapi_deactivate(); sapi_shutdown(); php_ini_builder_deinit(&ini_builder); diff --git a/sapi/phpdbg/phpdbg_bp.c b/sapi/phpdbg/phpdbg_bp.c index a20b4bd65db92..07a1fc7a0be5d 100644 --- a/sapi/phpdbg/phpdbg_bp.c +++ b/sapi/phpdbg/phpdbg_bp.c @@ -829,19 +829,21 @@ static inline void phpdbg_create_conditional_break(phpdbg_breakcond_t *brake, co uint32_t cops = CG(compiler_options); zend_string *bp_code; - switch (param->type) { - case STR_PARAM: - case NUMERIC_FUNCTION_PARAM: - case METHOD_PARAM: - case NUMERIC_METHOD_PARAM: - case FILE_PARAM: - case ADDR_PARAM: - /* do nothing */ - break; + if (param) { + switch (param->type) { + case STR_PARAM: + case NUMERIC_FUNCTION_PARAM: + case METHOD_PARAM: + case NUMERIC_METHOD_PARAM: + case FILE_PARAM: + case ADDR_PARAM: + /* do nothing */ + break; - default: - phpdbg_error("Invalid parameter type for conditional breakpoint"); - return; + default: + phpdbg_error("Invalid parameter type for conditional breakpoint"); + return; + } } PHPDBG_BREAK_INIT(new_break, PHPDBG_BREAK_COND); diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index a70034737273a..2464e39e5683c 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -1321,7 +1321,7 @@ PHPDBG_API const char *phpdbg_load_module_or_extension(char **path, const char * module_entry->handle = handle; if ((module_entry = zend_register_module_ex(module_entry)) == NULL) { - phpdbg_error("Unable to register module %s", module_entry->name); + phpdbg_error("Unable to register module %s", *name); goto quit; } diff --git a/sapi/phpdbg/phpdbg_utils.c b/sapi/phpdbg/phpdbg_utils.c index 344b9c73e476d..f638d608905ba 100644 --- a/sapi/phpdbg/phpdbg_utils.c +++ b/sapi/phpdbg/phpdbg_utils.c @@ -466,6 +466,9 @@ PHPDBG_API int phpdbg_parse_variable_with_arg(char *input, size_t len, HashTable case ']': break; case '>': + if (!last_index) { + goto error; + } if (last_index[index_len - 1] == '-') { new_index = 1; index_len--; diff --git a/sapi/phpdbg/tests/watch_007.phpt b/sapi/phpdbg/tests/watch_007.phpt new file mode 100644 index 0000000000000..f1980d60dd9de --- /dev/null +++ b/sapi/phpdbg/tests/watch_007.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test malformed watchpoint name +--INI-- +opcache.optimization_level=0 +--PHPDBG-- +b test +r +w $> +q +--EXPECTF-- +[Successful compilation of %s] +prompt> [Breakpoint #0 added at test] +prompt> [Breakpoint #0 in test() at %s:%d, hits: 1] +>00004: } + 00005: test(); + 00006: $a = 2; +prompt> [Malformed input] +prompt> +--FILE-- +