diff --git a/.cirrus.yml b/.cirrus.yml index 64910be03267b..b25d29a870f55 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -62,7 +62,7 @@ arm_task: libgmp-dev libicu-dev libtidy-dev - libenchant-dev + libenchant-2-dev libaspell-dev libpspell-dev libsasl2-dev @@ -204,6 +204,7 @@ arm_task: -d opcache.jit=function -P -q -x -j2 -g FAIL,BORK,LEAK,XLEAK + --no-progress --offline --show-diff --show-slow 1000 @@ -216,6 +217,7 @@ arm_task: -d opcache.jit=tracing -P -q -x -j2 -g FAIL,BORK,LEAK,XLEAK + --no-progress --offline --show-diff --show-slow 1000 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8100982786552..2fb400c9ca75c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -641,7 +641,7 @@ jobs: with: # FIXME: There are new warnings # configurationParameters: --enable-werror - libmysql: mysql-8.0.30-linux-glibc2.12-x86_64.tar.xz + libmysql: mysql-8.0.33-linux-glibc2.12-x86_64.tar.xz withMysqli: ${{ matrix.branch.ref == 'PHP-8.1' }} - name: Test mysql-8.0 uses: ./.github/actions/test-libmysqlclient diff --git a/NEWS b/NEWS index e7c600b29fa6d..489079e6f6346 100644 --- a/NEWS +++ b/NEWS @@ -1,13 +1,90 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.6 +08 Jun 2023, PHP 8.2.7 + +- Core: + . Fixed bug GH-11152 (Unable to alias namespaces containing reserved class + names). (ilutov) + . Fixed bug GH-9068 (Conditional jump or move depends on uninitialised + value(s)). (nielsdos) + . Fixed bug GH-11189 (Exceeding memory limit in zend_hash_do_resize leaves + the array in an invalid state). (Bob) + . Fixed bug GH-11063 (Compilation error on old GCC versions). (ingamedeo) + . Fixed bug GH-11222 (foreach by-ref may jump over keys during a rehash). + (Bob) + +- Date: + . Fixed bug GH-11281 (DateTimeZone::getName() does not include seconds in + offset). (nielsdos) + +- Exif: + . Fixed bug GH-10834 (exif_read_data() cannot read smaller stream wrapper + chunk sizes). (nielsdos) + +- FPM: + . Fixed bug GH-10461 (PHP-FPM segfault due to after free usage of + child->ev_std(out|err)). (Jakub Zelenka) + . Fixed bug #64539 (FPM status page: query_string not properly JSON encoded). + (Jakub Zelenka) + . Fixed memory leak for invalid primary script file handle. (Jakub Zelenka) + +- Hash: + . Fixed bug GH-11180 (hash_file() appears to be restricted to 3 arguments). + (nielsdos) + +- LibXML: + . Fixed bug GH-11160 (Few tests failed building with new libxml 2.11.0). + (nielsdos) + +- MBString: + . Fix bug GH-11217 (Segfault in mb_strrpos / mb_strripos when using negative + offset and ASCII encoding). (ilutov) + +- Opcache: + . Fixed bug GH-11134 (Incorrect match default branch optimization). (ilutov) + . Fixed too wide OR and AND range inference. (nielsdos) + . Fixed missing class redeclaration error with OPcache enabled. (ilutov) + . Fixed bug GH-11245 (In some specific cases SWITCH with one default + statement will cause segfault). (nielsdos) + +- PCNTL: + . Fixed maximum argument count of pcntl_forkx(). (nielsdos) + +- PGSQL: + . Fixed parameter parsing of pg_lo_export(). (kocsismate) + +- Phar: + . Fixed bug GH-11099 (Generating phar.php during cross-compile can't be + done). (peter279k) + +- Soap: + . Fixed bug GHSA-76gg-c692-v2mw (Missing error check and insufficient random + bytes in HTTP Digest authentication for SOAP). (nielsdos, timwolla) + . Fixed bug GH-8426 (make test fail while soap extension build). (nielsdos) + +- SPL: + . Fixed bug GH-11178 (Segmentation fault in spl_array_it_get_current_data + (PHP 8.1.18)). (nielsdos) + +- Standard: + . Fixed bug GH-11138 (move_uploaded_file() emits open_basedir warning for + source file). (ilutov) + . Fixed bug GH-11274 (POST/PATCH request switches to GET after a HTTP 308 + redirect). (nielsdos) + +- Streams: + . Fixed bug GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted + irregularly for last chunk of data). (nielsdos) + . Fixed bug GH-11175 (Stream Socket Timeout). (nielsdos) + . Fixed bug GH-11177 (ASAN UndefinedBehaviorSanitizer when timeout = -1 + passed to stream_socket_accept/stream_socket_client). (nielsdos) + +11 May 2023, PHP 8.2.6 - Core: . Fix inconsistent float negation in constant expressions. (ilutov) . Fixed bug GH-8841 (php-cli core dump calling a badly formed function). (nielsdos) - . Fixed bug GH-10085 (Assertion when adding two arrays with += where the first - array is contained in the second). (ilutov) . Fixed bug GH-10737 (PHP 8.1.16 segfaults on line 597 of sapi/apache2handler/sapi_apache2.c). (nielsdos, ElliotNB) . Fixed bug GH-11028 (Heap Buffer Overflow in zval_undefined_cv.). (nielsdos) diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 0186dde313144..52375c252fe75 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -264,6 +264,10 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array break; case ZEND_FREE: + /* Note: Only remove the source if the source is local to this block. + * If it's not local, then the other blocks successors must also eventually either FREE or consume the temporary, + * hence removing the temporary is not safe in the general case, especially when other consumers are not FREE. + * A FREE may not be removed without also removing the source's result, because otherwise that would cause a memory leak. */ if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src) { @@ -272,6 +276,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_BOOL_NOT: /* T = BOOL(X), FREE(T) => T = BOOL(X) */ /* The remaining BOOL is removed by a separate optimization */ + /* The source is a bool, no source removals take place, so this may be done non-locally. */ VAR_SOURCE(opline->op1) = NULL; MAKE_NOP(opline); ++(*opt_count); @@ -290,6 +295,9 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_PRE_DEC_OBJ: case ZEND_PRE_INC_STATIC_PROP: case ZEND_PRE_DEC_STATIC_PROP: + if (src < op_array->opcodes + block->start) { + break; + } src->result_type = IS_UNUSED; VAR_SOURCE(opline->op1) = NULL; MAKE_NOP(opline); @@ -302,7 +310,7 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array } else if (opline->op1_type == IS_VAR) { src = VAR_SOURCE(opline->op1); /* V = OP, FREE(V) => OP. NOP */ - if (src && + if (src >= op_array->opcodes + block->start && src->opcode != ZEND_FETCH_R && src->opcode != ZEND_FETCH_STATIC_PROP_R && src->opcode != ZEND_FETCH_DIM_R && diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 39c1bc12af902..cc904594f8b13 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -938,32 +938,41 @@ static int zend_dfa_optimize_jmps(zend_op_array *op_array, zend_ssa *ssa) || (opline->opcode == ZEND_SWITCH_STRING && type == IS_STRING) || (opline->opcode == ZEND_MATCH && (type == IS_LONG || type == IS_STRING)); - if (!correct_type) { + /* Switch statements have a fallback chain for loose comparison. In those + * cases the SWITCH_* instruction is a NOP. Match does strict comparison and + * thus jumps to the default branch on mismatched types, so we need to + * convert MATCH to a jmp. */ + if (!correct_type && opline->opcode != ZEND_MATCH) { removed_ops++; MAKE_NOP(opline); opline->extended_value = 0; take_successor_ex(ssa, block_num, block, block->successors[block->successors_count - 1]); goto optimize_nop; - } else { + } + + uint32_t target; + if (correct_type) { HashTable *jmptable = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, opline->op2.constant)); zval *jmp_zv = type == IS_LONG ? zend_hash_index_find(jmptable, Z_LVAL_P(zv)) : zend_hash_find(jmptable, Z_STR_P(zv)); - uint32_t target; if (jmp_zv) { target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, Z_LVAL_P(jmp_zv)); } else { target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value); } - opline->opcode = ZEND_JMP; - opline->extended_value = 0; - SET_UNUSED(opline->op1); - ZEND_SET_OP_JMP_ADDR(opline, opline->op1, op_array->opcodes + target); - SET_UNUSED(opline->op2); - take_successor_ex(ssa, block_num, block, ssa->cfg.map[target]); - goto optimize_jmp; + } else { + ZEND_ASSERT(opline->opcode == ZEND_MATCH); + target = ZEND_OFFSET_TO_OPLINE_NUM(op_array, opline, opline->extended_value); } + opline->opcode = ZEND_JMP; + opline->extended_value = 0; + SET_UNUSED(opline->op1); + ZEND_SET_OP_JMP_ADDR(opline, opline->op1, op_array->opcodes + target); + SET_UNUSED(opline->op2); + take_successor_ex(ssa, block_num, block, ssa->cfg.map[target]); + goto optimize_jmp; } break; case ZEND_NOP: diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index fb1f06a984e5c..883983800d1fc 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -432,7 +432,7 @@ static void zend_ssa_range_or(zend_long a, zend_long b, zend_long c, zend_long d int x = ((a < 0) ? 8 : 0) | ((b < 0) ? 4 : 0) | ((c < 0) ? 2 : 0) | - ((d < 0) ? 2 : 0); + ((d < 0) ? 1 : 0); switch (x) { case 0x0: case 0x3: @@ -480,7 +480,7 @@ static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long int x = ((a < 0) ? 8 : 0) | ((b < 0) ? 4 : 0) | ((c < 0) ? 2 : 0) | - ((d < 0) ? 2 : 0); + ((d < 0) ? 1 : 0); switch (x) { case 0x0: case 0x3: diff --git a/Zend/tests/delayed_early_binding_redeclaration-1.inc b/Zend/tests/delayed_early_binding_redeclaration-1.inc new file mode 100644 index 0000000000000..abfccf90686e3 --- /dev/null +++ b/Zend/tests/delayed_early_binding_redeclaration-1.inc @@ -0,0 +1,2 @@ + +--EXPECTF-- +Fatal error: Cannot declare class Bar, because the name is already in use in %sdelayed_early_binding_redeclaration-2.inc on line %d diff --git a/Zend/tests/gh10085_1.phpt b/Zend/tests/gh10085_1.phpt deleted file mode 100644 index cc11c96a09d32..0000000000000 --- a/Zend/tests/gh10085_1.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -GH-10085: Assertion in add_function_array() ---FILE-- - ---EXPECT-- -array(2) { - [0]=> - array(2) { - [0]=> - array(0) { - } - [1]=> - int(0) - } - [1]=> - int(0) -} diff --git a/Zend/tests/gh10085_2.phpt b/Zend/tests/gh10085_2.phpt deleted file mode 100644 index 7895999f2cd05..0000000000000 --- a/Zend/tests/gh10085_2.phpt +++ /dev/null @@ -1,25 +0,0 @@ ---TEST-- -GH-10085: Assertion in add_function_array() ---FILE-- - ---EXPECT-- -array(2) { - [0]=> - array(2) { - [0]=> - array(0) { - } - [1]=> - int(0) - } - [1]=> - int(0) -} diff --git a/Zend/tests/gh11138.phpt b/Zend/tests/gh11138.phpt new file mode 100644 index 0000000000000..fcd5cd11cfb8e --- /dev/null +++ b/Zend/tests/gh11138.phpt @@ -0,0 +1,28 @@ +--TEST-- +move_uploaded_file() emits open_basedir warning for source file +--POST_RAW-- +Content-type: multipart/form-data, boundary=AaB03x + +--AaB03x +content-disposition: form-data; name="file"; filename="file.txt" +Content-Type: text/plain + +foo +--AaB03x-- +--FILE-- + +--CLEAN-- + +--EXPECT-- +bool(true) +foo diff --git a/Zend/tests/gh11152.phpt b/Zend/tests/gh11152.phpt new file mode 100644 index 0000000000000..4f0b7d9cbb031 --- /dev/null +++ b/Zend/tests/gh11152.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-11152: Allow aliasing namespaces containing reserved class names +--FILE-- + +--EXPECT-- +string(8) "string\C" diff --git a/Zend/tests/gh11171.phpt b/Zend/tests/gh11171.phpt new file mode 100644 index 0000000000000..8bda7da0b7b41 --- /dev/null +++ b/Zend/tests/gh11171.phpt @@ -0,0 +1,15 @@ +--TEST-- +GH-11171: Test +--FILE-- + +--EXPECT-- +array(1) { + [0]=> + &string(4) "test" +} diff --git a/Zend/tests/gh11189.phpt b/Zend/tests/gh11189.phpt new file mode 100644 index 0000000000000..f1c877f20ee47 --- /dev/null +++ b/Zend/tests/gh11189.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state (packed array) +--SKIPIF-- + +--INI-- +memory_limit=2M +--FILE-- + 0; --$i) { + $a[] = 2; + } + fwrite(STDOUT, "Success"); +}); + +$a = []; +// trigger OOM in a resize operation +while (1) { + $a[] = 1; +} + +?> +--EXPECTF-- +Success +Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d diff --git a/Zend/tests/gh11189_1.phpt b/Zend/tests/gh11189_1.phpt new file mode 100644 index 0000000000000..53727908e5e2a --- /dev/null +++ b/Zend/tests/gh11189_1.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-11189: Exceeding memory limit in zend_hash_do_resize leaves the array in an invalid state (not packed array) +--SKIPIF-- + +--INI-- +memory_limit=2M +--FILE-- + 0; --$i) { + $a[] = 2; + } + fwrite(STDOUT, "Success"); +}); + +$a = ["not packed" => 1]; +// trigger OOM in a resize operation +while (1) { + $a[] = 1; +} + +?> +--EXPECTF-- +Success +Fatal error: Allowed memory size of %s bytes exhausted%s(tried to allocate %s bytes) in %s on line %d diff --git a/Zend/tests/gh11222.phpt b/Zend/tests/gh11222.phpt new file mode 100644 index 0000000000000..c2c2b5eb4881a --- /dev/null +++ b/Zend/tests/gh11222.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-112222: foreach by-ref may jump over keys during a rehash +--FILE-- + 0, 1 => 1, 2, 3, 4, 5, 6]; +foreach ($a as $k => &$v) { + if ($k == 1) { + // force that it'll be rehashed by adding enough holes + unset($a[4], $a[5]); + // actually make the array larger than 8 elements to trigger rehash + $a[] = 8; $a[] = 9; $a[] = 10; + + } + // observe the iteration jumping from key 1 to key 6, skipping keys 2 and 3 + echo "$k => $v\n"; +} + +?> +--EXPECTF-- +k => 0 +1 => 1 +2 => 2 +3 => 3 +6 => 6 +7 => 8 +8 => 9 +9 => 10 diff --git a/Zend/tests/match/gh11134.phpt b/Zend/tests/match/gh11134.phpt new file mode 100644 index 0000000000000..94e3223d2a07a --- /dev/null +++ b/Zend/tests/match/gh11134.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11134: Incorrect match optimization +--FILE-- + 'foo', + 'bar' => 'bar', + default => 'baz', + }; +} + +function testSwitch() { + switch ($unset ?? null) { + case 'foo': return 'foo'; + case 'bar': return 'bar'; + default: return 'baz'; + } +} + +var_dump(testMatch()); +var_dump(testSwitch()); + +?> +--EXPECT-- +string(3) "baz" +string(3) "baz" diff --git a/Zend/tests/use_statement/aliasing_builtin_types.phpt b/Zend/tests/use_statement/aliasing_builtin_types.phpt deleted file mode 100644 index 681a77c24b28a..0000000000000 --- a/Zend/tests/use_statement/aliasing_builtin_types.phpt +++ /dev/null @@ -1,10 +0,0 @@ ---TEST-- -Aliasing built-in types ---FILE-- - ---EXPECTF-- -Fatal error: Cannot alias 'bool' as it is a built-in type in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index f5ea5dc911603..9f9f38f9a9357 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.2.6-dev" +#define ZEND_VERSION "4.2.7" #define ZEND_ENGINE_3 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9036fc8ca66db..53fb5c7f4f7f8 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8124,12 +8124,6 @@ static void zend_compile_use(zend_ast *ast) /* {{{ */ zend_string *old_name = zend_ast_get_str(old_name_ast); zend_string *new_name, *lookup_name; - /* Check that we are not attempting to alias a built-in type */ - if (type == ZEND_SYMBOL_CLASS && zend_is_reserved_class_name(old_name)) { - zend_error_noreturn(E_COMPILE_ERROR, - "Cannot alias '%s' as it is a built-in type", ZSTR_VAL(old_name)); - } - if (new_name_ast) { new_name = zend_string_copy(zend_ast_get_str(new_name_ast)); } else { diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 53f78417f5dbf..6a22d5c43cf9a 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -670,7 +670,7 @@ static HashTable *zend_fiber_object_gc(zend_object *object, zval **table, int *n 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, ex->call, buf, false); + HashTable *symTable = zend_unfinished_execution_gc_ex(ex, ex->func && ZEND_USER_CODE(ex->func->type) ? ex->call : NULL, buf, false); if (symTable) { if (lastSymTable) { zval *val; diff --git a/Zend/zend_hash.c b/Zend/zend_hash.c index eae03e4491fc6..e40ddbcec712c 100644 --- a/Zend/zend_hash.c +++ b/Zend/zend_hash.c @@ -240,7 +240,7 @@ ZEND_API const HashTable zend_empty_array = { .gc.u.type_info = IS_ARRAY | (GC_IMMUTABLE << GC_FLAGS_SHIFT), .u.flags = HASH_FLAG_UNINITIALIZED, .nTableMask = HT_MIN_MASK, - .arData = (Bucket*)&uninitialized_bucket[2], + {.arData = (Bucket*)&uninitialized_bucket[2]}, .nNumUsed = 0, .nNumOfElements = 0, .nTableSize = HT_MIN_SIZE, @@ -304,8 +304,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_grow(HashTable *ht) if (ht->nTableSize >= HT_MAX_SIZE) { zend_error_noreturn(E_ERROR, "Possible integer overflow in memory allocation (%u * %zu + %zu)", ht->nTableSize * 2, sizeof(Bucket), sizeof(Bucket)); } - ht->nTableSize += ht->nTableSize; - HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + uint32_t newTableSize = ht->nTableSize * 2; + HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(newTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + ht->nTableSize = newTableSize; } ZEND_API void ZEND_FASTCALL zend_hash_real_init(HashTable *ht, bool packed) @@ -343,8 +344,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_packed_to_hash(HashTable *ht) ZEND_ASSERT(HT_SIZE_TO_MASK(nSize)); HT_ASSERT_RC1(ht); - HT_FLAGS(ht) &= ~HASH_FLAG_PACKED; + // Alloc before assign to avoid inconsistencies on OOM new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); + HT_FLAGS(ht) &= ~HASH_FLAG_PACKED; ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); HT_SET_DATA_ADDR(ht, new_data); dst = ht->arData; @@ -398,8 +400,9 @@ ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool if (packed) { ZEND_ASSERT(HT_IS_PACKED(ht)); if (nSize > ht->nTableSize) { - ht->nTableSize = zend_hash_check_size(nSize); - HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(ht->nTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + uint32_t newTableSize = zend_hash_check_size(nSize); + HT_SET_DATA_ADDR(ht, perealloc2(HT_GET_DATA_ADDR(ht), HT_PACKED_SIZE_EX(newTableSize, HT_MIN_MASK), HT_PACKED_USED_SIZE(ht), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT)); + ht->nTableSize = newTableSize; } } else { ZEND_ASSERT(!HT_IS_PACKED(ht)); @@ -407,8 +410,8 @@ ZEND_API void ZEND_FASTCALL zend_hash_extend(HashTable *ht, uint32_t nSize, bool void *new_data, *old_data = HT_GET_DATA_ADDR(ht); Bucket *old_buckets = ht->arData; nSize = zend_hash_check_size(nSize); - ht->nTableSize = nSize; new_data = pemalloc(HT_SIZE_EX(nSize, HT_SIZE_TO_MASK(nSize)), GC_FLAGS(ht) & IS_ARRAY_PERSISTENT); + ht->nTableSize = nSize; ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); HT_SET_DATA_ADDR(ht, new_data); memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); @@ -1237,8 +1240,8 @@ static void ZEND_FASTCALL zend_hash_do_resize(HashTable *ht) 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->nTableSize = nSize; ht->nTableMask = HT_SIZE_TO_MASK(ht->nTableSize); HT_SET_DATA_ADDR(ht, new_data); memcpy(ht->arData, old_buckets, sizeof(Bucket) * ht->nNumUsed); @@ -1299,7 +1302,7 @@ ZEND_API void ZEND_FASTCALL zend_hash_rehash(HashTable *ht) } } } else { - uint32_t iter_pos = zend_hash_iterators_lower_pos(ht, 0); + uint32_t iter_pos = zend_hash_iterators_lower_pos(ht, i + 1); while (++i < ht->nNumUsed) { p++; diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 35df118cb2172..02ca378bd854f 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -1015,22 +1015,16 @@ static ZEND_COLD zend_never_inline void ZEND_FASTCALL zend_binop_error(const cha static zend_never_inline void ZEND_FASTCALL add_function_array(zval *result, zval *op1, zval *op2) /* {{{ */ { + if (result == op1 && Z_ARR_P(op1) == Z_ARR_P(op2)) { + /* $a += $a */ + return; + } if (result != op1) { ZVAL_ARR(result, zend_array_dup(Z_ARR_P(op1))); - zend_hash_merge(Z_ARRVAL_P(result), Z_ARRVAL_P(op2), zval_add_ref, 0); - } else if (Z_ARR_P(op1) == Z_ARR_P(op2)) { - /* $a += $a */ } else { - /* We have to duplicate op1 (even with refcount == 1) because it may be an element of op2 - * and therefore its reference counter may be increased by zend_hash_merge(). That leads to - * an assertion in _zend_hash_add_or_update_i() that only allows adding elements to hash - * tables with RC1. See GH-10085 and Zend/tests/gh10085*.phpt */ - zval tmp; - ZVAL_ARR(&tmp, zend_array_dup(Z_ARR_P(op1))); - zend_hash_merge(Z_ARRVAL(tmp), Z_ARRVAL_P(op2), zval_add_ref, 0); - zval_ptr_dtor(result); - ZVAL_COPY_VALUE(result, &tmp); + SEPARATE_ARRAY(result); } + zend_hash_merge(Z_ARRVAL_P(result), Z_ARRVAL_P(op2), zval_add_ref, 0); } /* }}} */ diff --git a/Zend/zend_string.c b/Zend/zend_string.c index b4214bc38cd6f..a7c42f26b88f2 100644 --- a/Zend/zend_string.c +++ b/Zend/zend_string.c @@ -370,11 +370,28 @@ ZEND_API void zend_interned_strings_switch_storage(bool request) # define I_REPLACE_SONAME_FNNAME_ZU(soname, fnname) _vgr00000ZU_ ## soname ## _ ## fnname #endif -ZEND_API bool ZEND_FASTCALL I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2) +/* See GH-9068 */ +#if defined(__GNUC__) && (__GNUC__ >= 11 || defined(__clang__)) && __has_attribute(no_caller_saved_registers) +# define NO_CALLER_SAVED_REGISTERS __attribute__((no_caller_saved_registers)) +# ifndef __clang__ +# pragma GCC push_options +# pragma GCC target ("general-regs-only") +# define POP_OPTIONS +# endif +#else +# define NO_CALLER_SAVED_REGISTERS +#endif + +ZEND_API bool ZEND_FASTCALL NO_CALLER_SAVED_REGISTERS I_REPLACE_SONAME_FNNAME_ZU(NONE,zend_string_equal_val)(const zend_string *s1, const zend_string *s2) { return !memcmp(ZSTR_VAL(s1), ZSTR_VAL(s2), ZSTR_LEN(s1)); } +#ifdef POP_OPTIONS +# pragma GCC pop_options +# undef POP_OPTIONS +#endif + #if defined(__GNUC__) && defined(__i386__) ZEND_API bool ZEND_FASTCALL zend_string_equal_val(const zend_string *s1, const zend_string *s2) { diff --git a/configure.ac b/configure.ac index 899ead28d3dbf..b13c45011d9b4 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.6-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.7],[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/php_date.c b/ext/date/php_date.c index a4dfeafba4d20..2c622f66904a0 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2024,13 +2024,25 @@ static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv) ZVAL_STRING(zv, tzobj->tzi.tz->name); break; case TIMELIB_ZONETYPE_OFFSET: { - zend_string *tmpstr = zend_string_alloc(sizeof("UTC+05:00")-1, 0); timelib_sll utc_offset = tzobj->tzi.utc_offset; + int seconds = utc_offset % 60; + size_t size; + const char *format; + if (seconds == 0) { + size = sizeof("+05:00"); + format = "%c%02d:%02d"; + } else { + size = sizeof("+05:00:01"); + format = "%c%02d:%02d:%02d"; + } + zend_string *tmpstr = zend_string_alloc(size - 1, 0); - ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), sizeof("+05:00"), "%c%02d:%02d", + /* Note: if seconds == 0, the seconds argument will be excessive and therefore ignored. */ + ZSTR_LEN(tmpstr) = snprintf(ZSTR_VAL(tmpstr), size, format, utc_offset < 0 ? '-' : '+', abs((int)(utc_offset / 3600)), - abs((int)(utc_offset % 3600) / 60)); + abs((int)(utc_offset % 3600) / 60), + abs(seconds)); ZVAL_NEW_STR(zv, tmpstr); } diff --git a/ext/date/tests/bug81097.phpt b/ext/date/tests/bug81097.phpt index 2cfd7e00a9dd4..7a3baf06a6389 100644 --- a/ext/date/tests/bug81097.phpt +++ b/ext/date/tests/bug81097.phpt @@ -10,5 +10,5 @@ object(DateTimeZone)#%d (%d) { ["timezone_type"]=> int(1) ["timezone"]=> - string(6) "+01:45" + string(9) "+01:45:30" } diff --git a/ext/date/tests/bug81565.phpt b/ext/date/tests/bug81565.phpt index 282093c7ec329..b23e950eafdf6 100644 --- a/ext/date/tests/bug81565.phpt +++ b/ext/date/tests/bug81565.phpt @@ -17,4 +17,4 @@ echo "\n", (new DatetimeZone('+01:45:30'))->getName(); 'timezone_type' => 1, 'timezone' => '+00:49', )) -+01:45 ++01:45:30 diff --git a/ext/date/tests/gh11281.phpt b/ext/date/tests/gh11281.phpt new file mode 100644 index 0000000000000..be1fe30b88c9d --- /dev/null +++ b/ext/date/tests/gh11281.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-11281 (DateTimeZone::getName() does not include seconds in offset) +--FILE-- +getName(), "\n"; +$tz = new DateTimeZone('+03:00:00'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:00'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('+03:00:01'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:01'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('+03:00:58'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:58'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('+03:00:59'); +echo $tz->getName(), "\n"; +$tz = new DateTimeZone('-03:00:59'); +echo $tz->getName(), "\n"; +?> +--EXPECT-- ++03:00 ++03:00 +-03:00 ++03:00:01 +-03:00:01 ++03:00:58 +-03:00:58 ++03:00:59 +-03:00:59 diff --git a/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt new file mode 100644 index 0000000000000..ff5ceb3fbed53 --- /dev/null +++ b/ext/dom/tests/DOMDocument_loadXML_error2_gte2_11.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test DOMDocument::loadXML() detects not-well formed XML +--SKIPIF-- += 2.11'); +?> +--DESCRIPTION-- +This test verifies the method detects attributes values not closed between " or ' +Environment variables used in the test: +- XML_FILE: the xml file to load +- LOAD_OPTIONS: the second parameter to pass to the method +- EXPECTED_RESULT: the expected result +--CREDITS-- +Antonio Diaz Ruiz +--INI-- +assert.bail=true +--EXTENSIONS-- +dom +--ENV-- +XML_FILE=/not_well_formed2.xml +LOAD_OPTIONS=0 +EXPECTED_RESULT=0 +--FILE_EXTERNAL-- +domdocumentloadxml_test_method.inc +--EXPECTF-- +Warning: DOMDocument::loadXML(): AttValue: " or ' expected in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): internal error: xmlParseStartTag: problem parsing attributes in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): Couldn't find end of Start Tag book line 4 in Entity, line: 4 in %s on line %d + +Warning: DOMDocument::loadXML(): Opening and ending tag mismatch: books line 3 and book in Entity, line: 7 in %s on line %d + +Warning: DOMDocument::loadXML(): Extra content at the end of the document in Entity, line: 8 in %s on line %d diff --git a/ext/dom/tests/DOMDocument_loadXML_error2.phpt b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt similarity index 90% rename from ext/dom/tests/DOMDocument_loadXML_error2.phpt rename to ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt index a24d5215da48b..c826386f7a4e1 100644 --- a/ext/dom/tests/DOMDocument_loadXML_error2.phpt +++ b/ext/dom/tests/DOMDocument_loadXML_error2_pre2_11.phpt @@ -1,5 +1,9 @@ --TEST-- Test DOMDocument::loadXML() detects not-well formed XML +--SKIPIF-- += 21100) die('skip libxml2 test variant for version < 2.11'); +?> --DESCRIPTION-- This test verifies the method detects attributes values not closed between " or ' Environment variables used in the test: diff --git a/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt new file mode 100644 index 0000000000000..32b6bf161142e --- /dev/null +++ b/ext/dom/tests/DOMDocument_load_error2_gte2_11.phpt @@ -0,0 +1,34 @@ +--TEST-- +Test DOMDocument::load() detects not-well formed +--SKIPIF-- += 2.11'); +?> +--DESCRIPTION-- +This test verifies the method detects attributes values not closed between " or ' +Environment variables used in the test: +- XML_FILE: the xml file to load +- LOAD_OPTIONS: the second parameter to pass to the method +- EXPECTED_RESULT: the expected result +--CREDITS-- +Antonio Diaz Ruiz +--INI-- +assert.bail=true +--EXTENSIONS-- +dom +--ENV-- +XML_FILE=/not_well_formed2.xml +LOAD_OPTIONS=0 +EXPECTED_RESULT=0 +--FILE_EXTERNAL-- +domdocumentload_test_method.inc +--EXPECTF-- +Warning: DOMDocument::load(): AttValue: " or ' expected in %s on line %d + +Warning: DOMDocument::load(): internal error: xmlParseStartTag: problem parsing attributes in %s on line %d + +Warning: DOMDocument::load(): Couldn't find end of Start Tag book line 4 in %s on line %d + +Warning: DOMDocument::load(): Opening and ending tag mismatch: books line 3 and book in %s on line %d + +Warning: DOMDocument::load(): Extra content at the end of the document in %s on line %d diff --git a/ext/dom/tests/DOMDocument_load_error2.phpt b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt similarity index 90% rename from ext/dom/tests/DOMDocument_load_error2.phpt rename to ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt index cd13b3f901b27..695740be9ca92 100644 --- a/ext/dom/tests/DOMDocument_load_error2.phpt +++ b/ext/dom/tests/DOMDocument_load_error2_pre2_11.phpt @@ -1,5 +1,9 @@ --TEST-- Test DOMDocument::load() detects not-well formed XML +--SKIPIF-- += 21100) die('skip libxml2 test variant for version < 2.11'); +?> --DESCRIPTION-- This test verifies the method detects attributes values not closed between " or ' Environment variables used in the test: diff --git a/ext/exif/exif.c b/ext/exif/exif.c index b055994ea19df..bf5fed01db52f 100644 --- a/ext/exif/exif.c +++ b/ext/exif/exif.c @@ -215,6 +215,25 @@ zend_module_entry exif_module_entry = { ZEND_GET_MODULE(exif) #endif +/* php_stream_read() may return early without reading all data, depending on the chunk size + * and whether it's a URL stream or not. This helper keeps reading until the requested amount + * is read or until there is no more data available to read. */ +static ssize_t exif_read_from_stream_file_looped(php_stream *stream, char *buf, size_t count) +{ + ssize_t total_read = 0; + while (total_read < count) { + ssize_t ret = php_stream_read(stream, buf + total_read, count - total_read); + if (ret == -1) { + return -1; + } + if (ret == 0) { + break; + } + total_read += ret; + } + return total_read; +} + /* {{{ php_strnlen * get length of string if buffer if less than buffer size or buffer size */ static size_t php_strnlen(char* str, size_t maxlen) { @@ -3321,7 +3340,7 @@ static bool exif_process_IFD_TAG_impl(image_info_type *ImageInfo, char *dir_entr exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Wrong file pointer: 0x%08X != 0x%08X", fgot, displacement+offset_val); return false; } - fgot = php_stream_read(ImageInfo->infile, value_ptr, byte_count); + fgot = exif_read_from_stream_file_looped(ImageInfo->infile, value_ptr, byte_count); php_stream_seek(ImageInfo->infile, fpos, SEEK_SET); if (fgot != byte_count) { EFREE_IF(outside); @@ -3854,7 +3873,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo) Data[0] = (uchar)lh; Data[1] = (uchar)ll; - got = php_stream_read(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ + got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(Data+2), itemlen-2); /* Read the whole section. */ if (got != itemlen-2) { exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Error reading from file: got=x%04X(=%d) != itemlen-2=x%04X(=%d)", got, got, itemlen-2, itemlen-2); return false; @@ -3872,7 +3891,7 @@ static bool exif_scan_JPEG_header(image_info_type *ImageInfo) size = ImageInfo->FileSize - fpos; sn = exif_file_sections_add(ImageInfo, M_PSEUDO, size, NULL); Data = ImageInfo->file.list[sn].data; - got = php_stream_read(ImageInfo->infile, (char*)Data, size); + got = exif_read_from_stream_file_looped(ImageInfo->infile, (char*)Data, size); if (got != size) { EXIF_ERRLOG_FILEEOF(ImageInfo) return false; @@ -4049,7 +4068,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD dir(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, 2); #endif php_stream_seek(ImageInfo->infile, dir_offset, SEEK_SET); /* we do not know the order of sections */ - php_stream_read(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2); + if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)ImageInfo->file.list[sn].data, 2) != 2)) { + return false; + } num_entries = php_ifd_get16u(ImageInfo->file.list[sn].data, ImageInfo->motorola_intel); dir_size = 2/*num dir entries*/ +12/*length of entry*/*(size_t)num_entries +4/* offset to next ifd (points to thumbnail or NULL)*/; if (ImageInfo->FileSize >= dir_size && ImageInfo->FileSize - dir_size >= dir_offset) { @@ -4059,7 +4080,9 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (exif_file_sections_realloc(ImageInfo, sn, dir_size)) { return false; } - php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2); + if (UNEXPECTED(exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+2), dir_size-2) != dir_size - 2)) { + return false; + } next_offset = php_ifd_get32u(ImageInfo->file.list[sn].data + dir_size - 4, ImageInfo->motorola_intel); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF done, next offset x%04X", next_offset); @@ -4147,7 +4170,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF: filesize(x%04X), IFD(x%04X + x%04X)", ImageInfo->FileSize, dir_offset, ifd_size); #endif - php_stream_read(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size); + exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(ImageInfo->file.list[sn].data+dir_size), ifd_size-dir_size); #ifdef EXIF_DEBUG exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_NOTICE, "Read from TIFF, done"); #endif @@ -4198,7 +4221,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (!ImageInfo->Thumbnail.data) { ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); - fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); if (fgot != ImageInfo->Thumbnail.size) { EXIF_ERRLOG_THUMBEOF(ImageInfo) efree(ImageInfo->Thumbnail.data); @@ -4238,7 +4261,7 @@ static bool exif_process_IFD_in_TIFF_impl(image_info_type *ImageInfo, size_t dir if (!ImageInfo->Thumbnail.data && ImageInfo->Thumbnail.offset && ImageInfo->Thumbnail.size && ImageInfo->read_thumbnail) { ImageInfo->Thumbnail.data = safe_emalloc(ImageInfo->Thumbnail.size, 1, 0); php_stream_seek(ImageInfo->infile, ImageInfo->Thumbnail.offset, SEEK_SET); - fgot = php_stream_read(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); + fgot = exif_read_from_stream_file_looped(ImageInfo->infile, ImageInfo->Thumbnail.data, ImageInfo->Thumbnail.size); if (fgot != ImageInfo->Thumbnail.size) { EXIF_ERRLOG_THUMBEOF(ImageInfo) efree(ImageInfo->Thumbnail.data); @@ -4293,7 +4316,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo) if (ImageInfo->FileSize >= 2) { php_stream_seek(ImageInfo->infile, 0, SEEK_SET); - if (php_stream_read(ImageInfo->infile, (char*)file_header, 2) != 2) { + if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)file_header, 2) != 2) { return false; } if ((file_header[0]==0xff) && (file_header[1]==M_SOI)) { @@ -4304,7 +4327,7 @@ static bool exif_scan_FILE_header(image_info_type *ImageInfo) exif_error_docref(NULL EXIFERR_CC, ImageInfo, E_WARNING, "Invalid JPEG file"); } } else if (ImageInfo->FileSize >= 8) { - if (php_stream_read(ImageInfo->infile, (char*)(file_header+2), 6) != 6) { + if (exif_read_from_stream_file_looped(ImageInfo->infile, (char*)(file_header+2), 6) != 6) { return false; } if (!memcmp(file_header, "II\x2A\x00", 4)) { diff --git a/ext/exif/tests/gh10834.phpt b/ext/exif/tests/gh10834.phpt new file mode 100644 index 0000000000000..3c9caebdb70b6 --- /dev/null +++ b/ext/exif/tests/gh10834.phpt @@ -0,0 +1,79 @@ +--TEST-- +GH-10834 (exif_read_data() cannot read smaller stream wrapper chunk sizes) +--EXTENSIONS-- +exif +--FILE-- +position >= strlen($this->data); + } + + function stream_open($path, $mode, $options, &$opened_path) { + $this->position = 0; + $this->data = file_get_contents(__DIR__.'/bug50845.jpg'); + return true; + } + + function stream_seek($offset, $whence) { + switch ($whence) { + case SEEK_SET: + if ($offset < strlen($this->data) && $offset >= 0) { + $this->position = $offset; + return true; + } else { + return false; + } + break; + case SEEK_CUR: + if ($offset >= 0) { + $this->position += $offset; + return true; + } else { + return false; + } + break; + case SEEK_END: + if (strlen($this->data) + $offset >= 0) { + $this->position = strlen($this->data) + $offset; + return true; + } else { + return false; + } + break; + default: + return false; + } + } + + function stream_read($count) { + $ret = substr($this->data, $this->position, $count); + $this->position += strlen($ret); + return $ret; + } + + function stream_tell() { + return $this->position; + } +} + +stream_wrapper_register('var', 'VariableStream'); + +$fp = fopen('var://myvar', 'rb'); + +stream_set_chunk_size($fp, 10); +$headers = exif_read_data($fp); +var_dump(is_array($headers)); + +fclose($fp); +?> +--EXPECT-- +bool(true) diff --git a/ext/hash/hash.c b/ext/hash/hash.c index ce665a57443f1..e6d8ae2a40366 100644 --- a/ext/hash/hash.c +++ b/ext/hash/hash.c @@ -450,7 +450,7 @@ PHP_FUNCTION(hash_file) bool raw_output = 0; HashTable *args = NULL; - ZEND_PARSE_PARAMETERS_START(2, 3) + ZEND_PARSE_PARAMETERS_START(2, 4) Z_PARAM_STR(algo) Z_PARAM_STRING(data, data_len) Z_PARAM_OPTIONAL diff --git a/ext/hash/tests/hash_file_basic1.phpt b/ext/hash/tests/hash_file_basic1.phpt index 9ffe9a2f6cb01..334bad8c2726a 100644 --- a/ext/hash/tests/hash_file_basic1.phpt +++ b/ext/hash/tests/hash_file_basic1.phpt @@ -42,6 +42,12 @@ echo "sha512: " . hash_file('sha512', $file). "\n"; echo "snefru: " . hash_file('snefru', $file). "\n"; echo "tiger192,3: " . hash_file('tiger192,3', $file). "\n"; echo "whirlpool: " . hash_file('whirlpool', $file). "\n"; +echo "murmur3a: " . hash_file('murmur3a', $file). "\n"; +echo "murmur3a: " . hash_file('murmur3a', $file, false, ['seed' => 1234]). "\n"; +echo "murmur3c: " . hash_file('murmur3c', $file). "\n"; +echo "murmur3c: " . hash_file('murmur3c', $file, false, ['seed' => 1234]). "\n"; +echo "murmur3f: " . hash_file('murmur3f', $file). "\n"; +echo "murmur3f: " . hash_file('murmur3f', $file, false, ['seed' => 1234]). "\n"; echo "adler32(raw): " . bin2hex(hash_file('adler32', $file, TRUE)) . "\n"; echo "md5(raw): " . bin2hex(hash_file('md5', $file, TRUE)). "\n"; @@ -70,6 +76,12 @@ sha512: 1f42adaf938fbf136e381b164bae5f984c7f9fe60c82728bd889c14f187c7d63e81a0305 snefru: d414b2345d3e7fa1a31c044cf334bfc1fec24d89e464411998d579d24663895f tiger192,3: 7acf4ebea075fac6fc8ea0e2b4af3cfa71b9460e4c53403a whirlpool: 4248b149e000477269a4a5f1a84d97cfc3d0199b7aaf505913e6f010a6f83276029d11a9ad545374bc710eb59c7d958985023ab886ffa9ec9a23852844c764ec +murmur3a: bc6554c8 +murmur3a: 432e4379 +murmur3c: 8779de509ffc06fb27bcf5fc861504d6 +murmur3c: b43afac65c38a617323020432c170005 +murmur3f: 2b84cd546b2f18a9ab6f893194224afd +murmur3f: 6cc7716646664d6a83d68cb6563ac38e adler32(raw): ff87222e md5(raw): 704bf818448f5bbb94061332d2c889aa sha256(raw): a0f5702fa5d3670b80033d668e8732b70550392abb53841355447f8bb0f72245 diff --git a/ext/json/json.c b/ext/json/json.c index 4fac95fab39fe..e36746f18660e 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -103,6 +103,21 @@ static PHP_MINFO_FUNCTION(json) } /* }}} */ +PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options) +{ + smart_str buf = {0}; + php_json_encoder encoder; + + php_json_encode_init(&encoder); + + if (php_json_escape_string(&buf, s, len, options, &encoder) == FAILURE) { + smart_str_free(&buf); + return NULL; + } + + return smart_str_extract(&buf); +} + PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth) /* {{{ */ { php_json_encoder encoder; diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c index adb53598326bd..14fd86d73426c 100644 --- a/ext/json/json_encoder.c +++ b/ext/json/json_encoder.c @@ -31,10 +31,6 @@ static const char digits[] = "0123456789abcdef"; -static zend_result php_json_escape_string( - smart_str *buf, const char *s, size_t len, - int options, php_json_encoder *encoder); - static int php_json_determine_array_type(zval *val) /* {{{ */ { zend_array *myht = Z_ARRVAL_P(val); @@ -319,7 +315,7 @@ static zend_result php_json_encode_array(smart_str *buf, zval *val, int options, } /* }}} */ -static zend_result php_json_escape_string( +zend_result php_json_escape_string( smart_str *buf, const char *s, size_t len, int options, php_json_encoder *encoder) /* {{{ */ { diff --git a/ext/json/php_json.h b/ext/json/php_json.h index d4d8ac421f886..3973f0f017a34 100644 --- a/ext/json/php_json.h +++ b/ext/json/php_json.h @@ -97,6 +97,8 @@ PHP_JSON_API ZEND_EXTERN_MODULE_GLOBALS(json) ZEND_TSRMLS_CACHE_EXTERN() #endif +PHP_JSON_API zend_string *php_json_encode_string(const char *s, size_t len, int options); + PHP_JSON_API zend_result php_json_encode_ex(smart_str *buf, zval *val, int options, zend_long depth); PHP_JSON_API zend_result php_json_encode(smart_str *buf, zval *val, int options); PHP_JSON_API zend_result php_json_decode_ex(zval *return_value, const char *str, size_t str_len, zend_long options, zend_long depth); diff --git a/ext/json/php_json_encoder.h b/ext/json/php_json_encoder.h index e1c48e2b50922..3174e77958124 100644 --- a/ext/json/php_json_encoder.h +++ b/ext/json/php_json_encoder.h @@ -35,4 +35,6 @@ static inline void php_json_encode_init(php_json_encoder *encoder) zend_result php_json_encode_zval(smart_str *buf, zval *val, int options, php_json_encoder *encoder); +int php_json_escape_string(smart_str *buf, const char *s, size_t len, int options, php_json_encoder *encoder); + #endif /* PHP_JSON_ENCODER_H */ diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c index 270a0367481bf..22eb1901b8909 100644 --- a/ext/libxml/libxml.c +++ b/ext/libxml/libxml.c @@ -516,6 +516,8 @@ static void php_libxml_ctx_error_level(int level, void *ctx, const char *msg) } else { php_error_docref(NULL, level, "%s in Entity, line: %d", msg, parser->input->line); } + } else { + php_error_docref(NULL, E_WARNING, "%s", msg); } } diff --git a/ext/libxml/tests/bug61367-read_2.phpt b/ext/libxml/tests/bug61367-read_2.phpt index 92f1829a44dcf..bbcf696773bf0 100644 --- a/ext/libxml/tests/bug61367-read_2.phpt +++ b/ext/libxml/tests/bug61367-read_2.phpt @@ -58,6 +58,6 @@ bool(true) int(4) bool(true) -Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d +Warning: DOMDocument::loadXML(): %Sfailed to load external entity "file:///%s/test_bug_61367-read/bad" in %s on line %d Warning: Attempt to read property "nodeValue" on null in %s on line %d diff --git a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt index ad253171625f9..182fe13cfda96 100644 --- a/ext/libxml/tests/libxml_disable_entity_loader_2.phpt +++ b/ext/libxml/tests/libxml_disable_entity_loader_2.phpt @@ -39,6 +39,6 @@ bool(true) Deprecated: Function libxml_disable_entity_loader() is deprecated in %s on line %d bool(false) -Warning: DOMDocument::loadXML(): I/O warning : failed to load external entity "%s" in %s on line %d +Warning: DOMDocument::loadXML(): %Sfailed to load external entity "%s" in %s on line %d bool(true) Done diff --git a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt index 9ebf2c0e9d32a..5657b727bacd2 100644 --- a/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt +++ b/ext/libxml/tests/libxml_set_external_entity_loader_variation2.phpt @@ -39,6 +39,8 @@ echo "Done.\n"; string(10) "-//FOO/BAR" string(%d) "%sfoobar.dtd" +Warning: DOMDocument::validate(): Failed to load external entity "-//FOO/BAR" in %s on line %d + Warning: DOMDocument::validate(): Could not load the external subset "foobar.dtd" in %s on line %d bool(false) bool(true) diff --git a/ext/mbstring/libmbfl/mbfl/mbfilter.c b/ext/mbstring/libmbfl/mbfl/mbfilter.c index 5f5ce07ce6f66..a517c12c72e02 100644 --- a/ext/mbstring/libmbfl/mbfl/mbfilter.c +++ b/ext/mbstring/libmbfl/mbfl/mbfilter.c @@ -594,7 +594,7 @@ mbfl_strpos( const unsigned char *offset_pointer; if (haystack->encoding->no_encoding != mbfl_no_encoding_utf8) { - mbfl_string_init(&_haystack_u8); + mbfl_string_init_set(&_haystack_u8, haystack->encoding); haystack_u8 = mbfl_convert_encoding(haystack, &_haystack_u8, &mbfl_encoding_utf8); if (haystack_u8 == NULL) { result = MBFL_ERROR_ENCODING; @@ -605,7 +605,7 @@ mbfl_strpos( } if (needle->encoding->no_encoding != mbfl_no_encoding_utf8) { - mbfl_string_init(&_needle_u8); + mbfl_string_init_set(&_needle_u8, needle->encoding); needle_u8 = mbfl_convert_encoding(needle, &_needle_u8, &mbfl_encoding_utf8); if (needle_u8 == NULL) { result = MBFL_ERROR_ENCODING; diff --git a/ext/mbstring/tests/gh11217.phpt b/ext/mbstring/tests/gh11217.phpt new file mode 100644 index 0000000000000..d500f22cbd7bb --- /dev/null +++ b/ext/mbstring/tests/gh11217.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-11217: Segfault in mb_strrpos/mb_strripos with ASCII encoding and negative offset +--EXTENSIONS-- +mbstring +--FILE-- + +--EXPECT-- +int(0) +int(0) diff --git a/ext/mbstring/tests/mb_strrpos_basic.phpt b/ext/mbstring/tests/mb_strrpos_basic.phpt index 28e038da406bc..599dfd38da12a 100644 --- a/ext/mbstring/tests/mb_strrpos_basic.phpt +++ b/ext/mbstring/tests/mb_strrpos_basic.phpt @@ -22,6 +22,9 @@ var_dump(mb_strrpos($string_ascii, 'is', 4, 'ISO-8859-1')); echo "\n-- ASCII string 2 --\n"; var_dump(mb_strrpos($string_ascii, 'hello, world')); +echo "\n-- ASCII string with negative offset --\n"; +var_dump(mb_strrpos($string_ascii, 'hello', -1, 'ISO-8859-1')); + echo "\n-- Multibyte string 1 --\n"; $needle1 = base64_decode('44CC'); var_dump(mb_strrpos($string_mb, $needle1)); @@ -41,6 +44,9 @@ int(15) -- ASCII string 2 -- bool(false) +-- ASCII string with negative offset -- +bool(false) + -- Multibyte string 1 -- int(20) diff --git a/ext/opcache/jit/zend_jit_arm64.dasc b/ext/opcache/jit/zend_jit_arm64.dasc index b61fb97350bc3..bd61e87b5aebc 100644 --- a/ext/opcache/jit/zend_jit_arm64.dasc +++ b/ext/opcache/jit/zend_jit_arm64.dasc @@ -4829,7 +4829,7 @@ static int zend_jit_long_math_helper(dasm_State **Dst, op2_reg = Z_REG(op2_addr); } - if (!op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { + if ((op2_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) || !op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { | cbz Rx(op2_reg), >1 |.cold_code |1: @@ -8734,7 +8734,17 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t | // if (CACHED_PTR(opline->result.num)) | ldr REG2, EX->run_time_cache | MEM_ACCESS_64_WITH_UOFFSET ldr, REG0, REG2, opline->result.num, TMP1 - | cbz REG0, >1 + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE + && func + && (func->common.fn_flags & ZEND_ACC_IMMUTABLE) + && opline->opcode != ZEND_INIT_FCALL) { + /* Called func may be changed because of recompilation. See ext/opcache/tests/jit/init_fcall_003.phpt */ + | LOAD_ADDR REG1, ((ptrdiff_t)func) + | cmp REG0, REG1 + | bne >1 + } else { + | cbz REG0, >1 + } |.cold_code |1: if (opline->opcode == ZEND_INIT_FCALL diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index d4a87d068cf02..46a85f7977f69 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -5286,7 +5286,7 @@ static int zend_jit_long_math_helper(dasm_State **Dst, } } } else { - if (!op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { + if ((op2_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) || !op2_range || (op2_range->min <= 0 && op2_range->max >= 0)) { if (Z_MODE(op2_addr) == IS_MEM_ZVAL) { | cmp aword [Ra(Z_REG(op2_addr))+Z_OFFSET(op2_addr)], 0 } else if (Z_MODE(op2_addr) == IS_REG) { @@ -9369,8 +9369,28 @@ static int zend_jit_init_fcall(dasm_State **Dst, const zend_op *opline, uint32_t | // if (CACHED_PTR(opline->result.num)) | mov r2, EX->run_time_cache | mov r0, aword [r2 + opline->result.num] - | test r0, r0 - | jz >1 + if (JIT_G(trigger) == ZEND_JIT_ON_HOT_TRACE + && func + && (func->common.fn_flags & ZEND_ACC_IMMUTABLE) + && opline->opcode != ZEND_INIT_FCALL) { + /* Called func may be changed because of recompilation. See ext/opcache/tests/jit/init_fcall_003.phpt */ + | .if X64 + || if (!IS_SIGNED_32BIT(func)) { + | mov64 r1, ((ptrdiff_t)func) + | cmp r0, r1 + || } else { + | cmp r0, func + || } + | .else + | cmp r0, func + | .endif + | jnz >1 + |.cold_code + |1: + } else { + | test r0, r0 + | jz >1 + } |.cold_code |1: if (opline->opcode == ZEND_INIT_FCALL diff --git a/ext/opcache/tests/jit/init_fcall_003.inc b/ext/opcache/tests/jit/init_fcall_003.inc new file mode 100644 index 0000000000000..b35315778f42a --- /dev/null +++ b/ext/opcache/tests/jit/init_fcall_003.inc @@ -0,0 +1,6 @@ + diff --git a/ext/opcache/tests/jit/init_fcall_003.phpt b/ext/opcache/tests/jit/init_fcall_003.phpt new file mode 100644 index 0000000000000..f37344cbce4a9 --- /dev/null +++ b/ext/opcache/tests/jit/init_fcall_003.phpt @@ -0,0 +1,27 @@ +--TEST-- +JIT INIT_FCALL: 003 incorrect init fcall guard (fail with tracing JIT and --repeat 3) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +opcache.jit_max_polymorphic_calls=0 +opcache.jit=tracing +opcache.jit_hot_loop=64 +opcache.jit_hot_func=127 +opcache.jit_hot_return=8 +opcache.jit_hot_side_exit=8 +--FILE-- + +DONE +--EXPECT-- +DONE diff --git a/ext/opcache/tests/jit/mod_007.phpt b/ext/opcache/tests/jit/mod_007.phpt new file mode 100644 index 0000000000000..c83bd0e5a058b --- /dev/null +++ b/ext/opcache/tests/jit/mod_007.phpt @@ -0,0 +1,23 @@ +--TEST-- +JIT MOD: 007 +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit_buffer_size=1M +opcache.protect_memory=1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught DivisionByZeroError: Modulo by zero in %smod_007.php:4 +Stack trace: +#0 %smod_007.php(7): test(NULL) +#1 {main} + thrown in %smod_007.php on line 4 \ No newline at end of file diff --git a/ext/opcache/tests/opt/gh11170.phpt b/ext/opcache/tests/opt/gh11170.phpt new file mode 100644 index 0000000000000..ca00c5e852966 --- /dev/null +++ b/ext/opcache/tests/opt/gh11170.phpt @@ -0,0 +1,145 @@ +--TEST-- +GH-11170 (Too wide OR and AND range inferred) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.opt_debug_level=0x400000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=5, args=0, vars=0, tmps=2, ssa_vars=0, no_loops) + ; (after dfa pass) + ; %s + ; return [long] RANGE[1..1] +BB0: + ; start exit lines=[0-4] + ; level=0 +0000 INIT_FCALL 0 %d string("test_or") +0001 DO_UCALL +0002 INIT_FCALL 0 %d string("test_and") +0003 DO_UCALL +0004 RETURN int(1) + +test_or: + ; (lines=11, args=0, vars=2, tmps=7, ssa_vars=11, no_loops) + ; (after dfa pass) + ; %s + ; return [long] RANGE[-20..-1] + ; #0.CV0($a) NOVAL [undef] + ; #1.CV1($b) NOVAL [undef] +BB0: + ; start lines=[0-3] + ; to=(BB2, BB1) + ; level=0 + ; children=(BB1, BB2, BB3) +0000 INIT_FCALL 0 %d string("rand") +0001 #2.V2 [long] = DO_ICALL +0002 #3.T3 [long] RANGE[MIN..MAX] = MOD #2.V2 [long] int(10) +0003 JMPZ #3.T3 [long] RANGE[MIN..MAX] BB2 + +BB1: + ; follow lines=[4-6] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0004 #4.CV0($a) [long] RANGE[-27..-27] = QM_ASSIGN int(-27) +0005 #5.CV1($b) [long] RANGE[-20..-20] = QM_ASSIGN int(-20) +0006 JMP BB3 + +BB2: + ; target lines=[7-8] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0007 #6.CV0($a) [long] RANGE[-7..-7] = QM_ASSIGN int(-7) +0008 #7.CV1($b) [long] RANGE[-10..-10] = QM_ASSIGN int(-10) + +BB3: + ; follow target exit lines=[9-10] + ; from=(BB1, BB2) + ; idom=BB0 + ; level=1 + #8.CV0($a) [long] RANGE[-27..-7] = Phi(#4.CV0($a) [long] RANGE[-27..-27], #6.CV0($a) [long] RANGE[-7..-7]) + #9.CV1($b) [long] RANGE[-20..-10] = Phi(#5.CV1($b) [long] RANGE[-20..-20], #7.CV1($b) [long] RANGE[-10..-10]) +0009 #10.T8 [long] RANGE[-20..-1] = BW_OR #8.CV0($a) [long] RANGE[-27..-7] #9.CV1($b) [long] RANGE[-20..-10] +0010 RETURN #10.T8 [long] RANGE[-20..-1] + +test_and: + ; (lines=11, args=0, vars=2, tmps=7, ssa_vars=11, no_loops) + ; (after dfa pass) + ; %s + ; return [long] RANGE[-28..-25] + ; #0.CV0($a) NOVAL [undef] + ; #1.CV1($b) NOVAL [undef] +BB0: + ; start lines=[0-3] + ; to=(BB2, BB1) + ; level=0 + ; children=(BB1, BB2, BB3) +0000 INIT_FCALL 0 %d string("rand") +0001 #2.V2 [long] = DO_ICALL +0002 #3.T3 [long] RANGE[MIN..MAX] = MOD #2.V2 [long] int(10) +0003 JMPZ #3.T3 [long] RANGE[MIN..MAX] BB2 + +BB1: + ; follow lines=[4-6] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0004 #4.CV0($a) [long] RANGE[-12..-12] = QM_ASSIGN int(-12) +0005 #5.CV1($b) [long] RANGE[-27..-27] = QM_ASSIGN int(-27) +0006 JMP BB3 + +BB2: + ; target lines=[7-8] + ; from=(BB0) + ; to=(BB3) + ; idom=BB0 + ; level=1 +0007 #6.CV0($a) [long] RANGE[-9..-9] = QM_ASSIGN int(-9) +0008 #7.CV1($b) [long] RANGE[-25..-25] = QM_ASSIGN int(-25) + +BB3: + ; follow target exit lines=[9-10] + ; from=(BB1, BB2) + ; idom=BB0 + ; level=1 + #8.CV0($a) [long] RANGE[-12..-9] = Phi(#4.CV0($a) [long] RANGE[-12..-12], #6.CV0($a) [long] RANGE[-9..-9]) + #9.CV1($b) [long] RANGE[-27..-25] = Phi(#5.CV1($b) [long] RANGE[-27..-27], #7.CV1($b) [long] RANGE[-25..-25]) +0009 #10.T8 [long] RANGE[-28..-25] = BW_AND #8.CV0($a) [long] RANGE[-12..-9] #9.CV1($b) [long] RANGE[-27..-25] +0010 RETURN #10.T8 [long] RANGE[-28..-25] diff --git a/ext/opcache/tests/opt/gh11245_1.phpt b/ext/opcache/tests/opt/gh11245_1.phpt new file mode 100644 index 0000000000000..eac085ac44025 --- /dev/null +++ b/ext/opcache/tests/opt/gh11245_1.phpt @@ -0,0 +1,33 @@ +--TEST-- +GH-11245: In some specific cases SWITCH with one default statement will cause segfault (VAR variation) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0x7FFFBFFF +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=4, args=0, vars=1, tmps=1) + ; (after optimizer) + ; %s +0000 T1 = ISSET_ISEMPTY_CV (empty) CV0($xx) +0001 JMPNZ T1 0003 +0002 RETURN null +0003 RETURN int(1) + +xx: + ; (lines=1, args=0, vars=0, tmps=0) + ; (after optimizer) + ; %s +0000 RETURN string("somegarbage") diff --git a/ext/opcache/tests/opt/gh11245_2.phpt b/ext/opcache/tests/opt/gh11245_2.phpt new file mode 100644 index 0000000000000..8e967bf9f41be --- /dev/null +++ b/ext/opcache/tests/opt/gh11245_2.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-11245: In some specific cases SWITCH with one default statement will cause segfault (TMP variation) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=0x7FFFBFFF +opcache.opt_debug_level=0x20000 +opcache.preload= +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECTF-- +$_main: + ; (lines=7, args=0, vars=1, tmps=2) + ; (after optimizer) + ; %s +0000 T1 = PRE_INC_STATIC_PROP string("prop") string("X") +0001 T2 = ISSET_ISEMPTY_CV (empty) CV0($xx) +0002 JMPZ T2 0005 +0003 FREE T1 +0004 RETURN null +0005 FREE T1 +0006 RETURN int(1) +LIVE RANGES: + 1: 0001 - 0005 (tmp/var) diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 9a64f92dab6ee..2ee60d4bbc493 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -358,9 +358,9 @@ static void zend_accel_do_delayed_early_binding( ce = zend_try_early_bind(orig_ce, parent_ce, early_binding->lcname, zv); } } - } - if (ce && early_binding->cache_slot != (uint32_t) -1) { - *(void**)((char*)run_time_cache + early_binding->cache_slot) = ce; + if (ce && early_binding->cache_slot != (uint32_t) -1) { + *(void**)((char*)run_time_cache + early_binding->cache_slot) = ce; + } } } CG(compiled_filename) = orig_compiled_filename; diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index d040f96889ee8..c2b7f390fcafd 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -1295,7 +1295,7 @@ PHP_FUNCTION(pcntl_forkx) zend_long flags; pid_t pid; - ZEND_PARSE_PARAMETERS_START(1, 2) + ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_LONG(flags) ZEND_PARSE_PARAMETERS_END(); diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 8972041de5be8..188570f7cead3 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -2660,7 +2660,7 @@ PHP_FUNCTION(pg_lo_export) /* allow string to handle large OID value correctly */ if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), - "rlP", &pgsql_link, pgsql_link_ce, &oid_long, &file_out) == SUCCESS) { + "OlP", &pgsql_link, pgsql_link_ce, &oid_long, &file_out) == SUCCESS) { if (oid_long <= (zend_long)InvalidOid) { zend_value_error("Invalid OID value passed"); RETURN_THROWS(); diff --git a/ext/phar/Makefile.frag b/ext/phar/Makefile.frag index 58789cae25b57..e5646b2029261 100644 --- a/ext/phar/Makefile.frag +++ b/ext/phar/Makefile.frag @@ -29,22 +29,38 @@ $(builddir)/phar/phar.inc: $(srcdir)/phar/phar.inc -@test -d $(builddir)/phar || mkdir $(builddir)/phar -@test -f $(builddir)/phar/phar.inc || cp $(srcdir)/phar/phar.inc $(builddir)/phar/phar.inc + +TEST_PHP_EXECUTABLE = $(shell $(PHP_EXECUTABLE) -v 2>&1) +TEST_PHP_EXECUTABLE_RES = $(shell echo "$(TEST_PHP_EXECUTABLE)" | grep -c 'Exec format error') + $(builddir)/phar.php: $(srcdir)/build_precommand.php $(srcdir)/phar/*.inc $(srcdir)/phar/*.php $(SAPI_CLI_PATH) - -@echo "Generating phar.php" - @$(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(srcdir)/build_precommand.php > $(builddir)/phar.php + -@(echo "Generating phar.php"; \ + if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + $(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(srcdir)/build_precommand.php > $(builddir)/phar.php; \ + else \ + echo "Skipping phar.php generating during cross compilation"; \ + fi) $(builddir)/phar.phar: $(builddir)/phar.php $(builddir)/phar/phar.inc $(srcdir)/phar/*.inc $(srcdir)/phar/*.php $(SAPI_CLI_PATH) - -@echo "Generating phar.phar" - -@rm -f $(builddir)/phar.phar - -@rm -f $(srcdir)/phar.phar - @$(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(builddir)/phar.php pack -f $(builddir)/phar.phar -a pharcommand -c auto -x \\.svn -p 0 -s $(srcdir)/phar/phar.php -h sha1 -b "$(PHP_PHARCMD_BANG)" $(srcdir)/phar/ - -@chmod +x $(builddir)/phar.phar + -@(echo "Generating phar.phar"; \ + if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + rm -f $(builddir)/phar.phar; \ + rm -f $(srcdir)/phar.phar; \ + $(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(builddir)/phar.php pack -f $(builddir)/phar.phar -a pharcommand -c auto -x \\.svn -p 0 -s $(srcdir)/phar/phar.php -h sha1 -b "$(PHP_PHARCMD_BANG)" $(srcdir)/phar/; \ + chmod +x $(builddir)/phar.phar; \ + else \ + echo "Skipping phar.phar generating during cross compilation"; \ + fi) install-pharcmd: pharcmd - -@$(mkinstalldirs) $(INSTALL_ROOT)$(bindir) - $(INSTALL) $(builddir)/phar.phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix).phar - -@rm -f $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix) - $(LN_S) -f $(program_prefix)phar$(program_suffix).phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix) - @$(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man1 - @$(INSTALL_DATA) $(builddir)/phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).1 - @$(INSTALL_DATA) $(builddir)/phar.phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).phar.1 + @(if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + $(mkinstalldirs) $(INSTALL_ROOT)$(bindir); \ + $(INSTALL) $(builddir)/phar.phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix).phar; \ + rm -f $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix); \ + $(LN_S) -f $(program_prefix)phar$(program_suffix).phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix); \ + $(mkinstalldirs) $(INSTALL_ROOT)$(mandir)/man1; \ + $(INSTALL_DATA) $(builddir)/phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).1; \ + $(INSTALL_DATA) $(builddir)/phar.phar.1 $(INSTALL_ROOT)$(mandir)/man1/$(program_prefix)phar$(program_suffix).phar.1; \ + else \ + echo "Skipping install-pharcmd during cross compilation"; \ + fi) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 0e9f492b61f49..8cd13e7282b7c 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -3699,7 +3699,7 @@ PHP_METHOD(Phar, offsetSet) { char *fname, *cont_str = NULL; size_t fname_len, cont_len; - zval *zresource; + zval *zresource = NULL; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "pr", &fname, &fname_len, &zresource) == FAILURE && zend_parse_parameters(ZEND_NUM_ARGS(), "ps", &fname, &fname_len, &cont_str, &cont_len) == FAILURE) { diff --git a/ext/soap/php_http.c b/ext/soap/php_http.c index 29a918fb8a2cb..5a887bd1e1e04 100644 --- a/ext/soap/php_http.c +++ b/ext/soap/php_http.c @@ -20,6 +20,7 @@ #include "ext/standard/base64.h" #include "ext/standard/md5.h" #include "ext/random/php_random.h" +#include "ext/hash/php_hash.h" static char *get_http_header_value_nodup(char *headers, char *type, size_t *len); static char *get_http_header_value(char *headers, char *type); @@ -657,18 +658,23 @@ int make_http_soap_request(zval *this_ptr, has_authorization = 1; if (Z_TYPE_P(digest) == IS_ARRAY) { char HA1[33], HA2[33], response[33], cnonce[33], nc[9]; - zend_long nonce; + unsigned char nonce[16]; PHP_MD5_CTX md5ctx; unsigned char hash[16]; - php_random_bytes_throw(&nonce, sizeof(nonce)); - nonce &= 0x7fffffff; + if (UNEXPECTED(php_random_bytes_throw(&nonce, sizeof(nonce)) != SUCCESS)) { + ZEND_ASSERT(EG(exception)); + php_stream_close(stream); + convert_to_null(Z_CLIENT_HTTPURL_P(this_ptr)); + convert_to_null(Z_CLIENT_HTTPSOCKET_P(this_ptr)); + convert_to_null(Z_CLIENT_USE_PROXY_P(this_ptr)); + smart_str_free(&soap_headers_z); + smart_str_free(&soap_headers); + return FALSE; + } - PHP_MD5Init(&md5ctx); - snprintf(cnonce, sizeof(cnonce), ZEND_LONG_FMT, nonce); - PHP_MD5Update(&md5ctx, (unsigned char*)cnonce, strlen(cnonce)); - PHP_MD5Final(hash, &md5ctx); - make_digest(cnonce, hash); + php_hash_bin2hex(cnonce, nonce, sizeof(nonce)); + cnonce[32] = 0; if ((tmp = zend_hash_str_find(Z_ARRVAL_P(digest), "nc", sizeof("nc")-1)) != NULL && Z_TYPE_P(tmp) == IS_LONG) { diff --git a/ext/soap/tests/bug73037.phpt b/ext/soap/tests/bug73037.phpt index 3853e50c26913..001940a4d37ed 100644 --- a/ext/soap/tests/bug73037.phpt +++ b/ext/soap/tests/bug73037.phpt @@ -59,8 +59,12 @@ function get_data($max) } $router = "bug73037_server.php"; -$args = substr(PHP_OS, 0, 3) == 'WIN' - ? ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=php_soap.dll"] : []; +$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX]; +if (php_ini_loaded_file()) { + // Necessary such that it works from a development directory in which case extension_dir might not be the real extension dir + $args[] = "-c"; + $args[] = php_ini_loaded_file(); +} $code = <<<'PHP' $s = new SoapServer(NULL, array('uri' => 'http://here/')); $s->setObject(new stdclass()); diff --git a/ext/soap/tests/custom_content_type.phpt b/ext/soap/tests/custom_content_type.phpt index b8bc8c9870113..d32f1df783591 100644 --- a/ext/soap/tests/custom_content_type.phpt +++ b/ext/soap/tests/custom_content_type.phpt @@ -13,8 +13,12 @@ soap include __DIR__ . "/../../../sapi/cli/tests/php_cli_server.inc"; -$args = substr(PHP_OS, 0, 3) == 'WIN' - ? ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=php_soap.dll"] : []; +$args = ["-d", "extension_dir=" . ini_get("extension_dir"), "-d", "extension=" . (substr(PHP_OS, 0, 3) == "WIN" ? "php_" : "") . "soap." . PHP_SHLIB_SUFFIX]; +if (php_ini_loaded_file()) { + // Necessary such that it works from a development directory in which case extension_dir might not be the real extension dir + $args[] = "-c"; + $args[] = php_ini_loaded_file(); +} $code = <<<'PHP' /* Receive */ $content = trim(file_get_contents("php://input")) . PHP_EOL; diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 0fc861b8bb66c..669f6f7b463ac 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -989,7 +989,8 @@ static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */ zend_hash_get_current_key_ex(aht, &key, NULL, spl_array_get_pos_ptr(aht, object)); zend_class_entry *ce = Z_OBJCE(object->array); zend_property_info *prop_info = zend_get_property_info(ce, key, true); - if (ZEND_TYPE_IS_SET(prop_info->type)) { + ZEND_ASSERT(prop_info != ZEND_WRONG_PROPERTY_INFO); + if (EXPECTED(prop_info != NULL) && ZEND_TYPE_IS_SET(prop_info->type)) { if (prop_info->flags & ZEND_ACC_READONLY) { zend_throw_error(NULL, "Cannot acquire reference to readonly property %s::$%s", diff --git a/ext/spl/tests/gh11178.phpt b/ext/spl/tests/gh11178.phpt new file mode 100644 index 0000000000000..3732c57a59d19 --- /dev/null +++ b/ext/spl/tests/gh11178.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11178 (Segmentation fault in spl_array_it_get_current_data (PHP 8.1.18)) +--FILE-- +{'x'} = 1; + } + + function getIterator(): Traversable { + return new ArrayIterator($this); + } +} + +$obj = new A; + +foreach ($obj as $k => &$v) { + $v = 3; +} + +var_dump($obj); +?> +--EXPECT-- +object(A)#1 (1) { + ["x"]=> + &int(3) +} diff --git a/ext/standard/file.c b/ext/standard/file.c index f40579dde5952..64aa46f29bdc9 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1539,8 +1539,9 @@ PHPAPI int php_copy_file_ctx(const char *src, const char *dest, int src_flg, php php_stream *srcstream = NULL, *deststream = NULL; int ret = FAILURE; php_stream_statbuf src_s, dest_s; + int src_stat_flags = (src_flg & STREAM_DISABLE_OPEN_BASEDIR) ? PHP_STREAM_URL_STAT_IGNORE_OPEN_BASEDIR : 0; - switch (php_stream_stat_path_ex(src, 0, &src_s, ctx)) { + switch (php_stream_stat_path_ex(src, src_stat_flags, &src_s, ctx)) { case -1: /* non-statable stream */ goto safe_to_copy; diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index be0ee30b7d832..a9950fa6d48d2 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -79,6 +79,7 @@ #define HTTP_WRAPPER_HEADER_INIT 1 #define HTTP_WRAPPER_REDIRECTED 2 +#define HTTP_WRAPPER_KEEP_METHOD 4 static inline void strip_header(char *header_bag, char *lc_header_bag, const char *lc_header_name) @@ -140,6 +141,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *user_headers = NULL; int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); + int redirect_keep_method = ((flags & HTTP_WRAPPER_KEEP_METHOD) != 0); bool follow_location = 1; php_stream_filter *transfer_encoding = NULL; int response_code; @@ -363,8 +365,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (context && (tmpzval = php_stream_context_get_option(context, "http", "method")) != NULL) { if (Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { /* As per the RFC, automatically redirected requests MUST NOT use other methods than - * GET and HEAD unless it can be confirmed by the user */ - if (!redirected + * GET and HEAD unless it can be confirmed by the user. */ + if (!redirected || redirect_keep_method || zend_string_equals_literal(Z_STR_P(tmpzval), "GET") || zend_string_equals_literal(Z_STR_P(tmpzval), "HEAD") ) { @@ -458,7 +460,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, zend_str_tolower(ZSTR_VAL(tmp), ZSTR_LEN(tmp)); t = ZSTR_VAL(tmp); - if (!header_init) { + if (!header_init && !redirect_keep_method) { /* strip POST headers on redirect */ strip_header(user_headers, t, "content-length:"); strip_header(user_headers, t, "content-type:"); @@ -606,7 +608,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, * see bug #44603 for details. Since Content-Type maybe part of user's headers we need to do this check first. */ if ( - header_init && + (header_init || redirect_keep_method) && context && !(have_header & HTTP_HEADER_CONTENT_LENGTH) && (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && @@ -624,7 +626,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } /* Request content, such as for POST requests */ - if (header_init && context && + if ((header_init || redirect_keep_method) && context && (tmpzval = php_stream_context_get_option(context, "http", "content")) != NULL && Z_TYPE_P(tmpzval) == IS_STRING && Z_STRLEN_P(tmpzval) > 0) { if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) { @@ -913,9 +915,16 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, CHECK_FOR_CNTRL_CHARS(resource->pass); CHECK_FOR_CNTRL_CHARS(resource->path); } + int new_flags = HTTP_WRAPPER_REDIRECTED; + if (response_code == 307 || response_code == 308) { + /* RFC 7538 specifies that status code 308 does not allow changing the request method from POST to GET. + * RFC 7231 does the same for status code 307. + * To keep consistency between POST and PATCH requests, we'll also not change the request method from PATCH to GET, even though it's allowed it's not mandated by the RFC. */ + new_flags |= HTTP_WRAPPER_KEEP_METHOD; + } stream = php_stream_url_wrap_http_ex( wrapper, new_path, mode, options, opened_path, context, - --redirect_max, HTTP_WRAPPER_REDIRECTED, response_header STREAMS_CC); + --redirect_max, new_flags, response_header STREAMS_CC); } else { php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); } @@ -955,6 +964,13 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (transfer_encoding) { php_stream_filter_append(&stream->readfilters, transfer_encoding); } + + /* It's possible that the server already sent in more data than just the headers. + * We account for this by adjusting the progress counter by the difference of + * already read header data and the body. */ + if (stream->writepos > stream->readpos) { + php_stream_notify_progress_increment(context, stream->writepos - stream->readpos, 0); + } } return stream; diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index f224268c00d80..53fa8d33dad6b 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -33,11 +33,13 @@ #ifndef PHP_WIN32 #define php_select(m, r, w, e, t) select(m, r, w, e, t) typedef unsigned long long php_timeout_ull; +#define PHP_TIMEOUT_ULL_MAX ULLONG_MAX #else #include "win32/select.h" #include "win32/sockets.h" #include "win32/console.h" typedef unsigned __int64 php_timeout_ull; +#define PHP_TIMEOUT_ULL_MAX UINT64_MAX #endif #define GET_CTX_OPT(stream, wrapper, name, val) (PHP_STREAM_CONTEXT(stream) && NULL != (val = php_stream_context_get_option(PHP_STREAM_CONTEXT(stream), wrapper, name))) @@ -134,14 +136,21 @@ PHP_FUNCTION(stream_socket_client) } /* prepare the timeout value for use */ - conv = (php_timeout_ull) (timeout * 1000000.0); + struct timeval *tv_pointer; + if (timeout < 0.0 || timeout >= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0) { + tv_pointer = NULL; + } else { + conv = (php_timeout_ull) (timeout * 1000000.0); #ifdef PHP_WIN32 - tv.tv_sec = (long)(conv / 1000000); - tv.tv_usec =(long)(conv % 1000000); + tv.tv_sec = (long)(conv / 1000000); + tv.tv_usec = (long)(conv % 1000000); #else - tv.tv_sec = conv / 1000000; - tv.tv_usec = conv % 1000000; + tv.tv_sec = conv / 1000000; + tv.tv_usec = conv % 1000000; #endif + tv_pointer = &tv; + } + if (zerrno) { ZEND_TRY_ASSIGN_REF_LONG(zerrno, 0); } @@ -152,7 +161,7 @@ PHP_FUNCTION(stream_socket_client) stream = php_stream_xport_create(ZSTR_VAL(host), ZSTR_LEN(host), REPORT_ERRORS, STREAM_XPORT_CLIENT | (flags & PHP_STREAM_CLIENT_CONNECT ? STREAM_XPORT_CONNECT : 0) | (flags & PHP_STREAM_CLIENT_ASYNC_CONNECT ? STREAM_XPORT_CONNECT_ASYNC : 0), - hashkey, &tv, context, &errstr, &err); + hashkey, tv_pointer, context, &errstr, &err); if (stream == NULL) { @@ -275,19 +284,25 @@ PHP_FUNCTION(stream_socket_accept) php_stream_from_zval(stream, zstream); /* prepare the timeout value for use */ - conv = (php_timeout_ull) (timeout * 1000000.0); + struct timeval *tv_pointer; + if (timeout < 0.0 || timeout >= (double) PHP_TIMEOUT_ULL_MAX / 1000000.0) { + tv_pointer = NULL; + } else { + conv = (php_timeout_ull) (timeout * 1000000.0); #ifdef PHP_WIN32 - tv.tv_sec = (long)(conv / 1000000); - tv.tv_usec = (long)(conv % 1000000); + tv.tv_sec = (long)(conv / 1000000); + tv.tv_usec = (long)(conv % 1000000); #else - tv.tv_sec = conv / 1000000; - tv.tv_usec = conv % 1000000; + tv.tv_sec = conv / 1000000; + tv.tv_usec = conv % 1000000; #endif + tv_pointer = &tv; + } if (0 == php_stream_xport_accept(stream, &clistream, zpeername ? &peername : NULL, NULL, NULL, - &tv, &errstr + tv_pointer, &errstr ) && clistream) { if (peername) { diff --git a/ext/standard/tests/http/bug67430.phpt b/ext/standard/tests/http/bug67430.phpt index e72e419fc02ac..1a515537e6609 100644 --- a/ext/standard/tests/http/bug67430.phpt +++ b/ext/standard/tests/http/bug67430.phpt @@ -41,7 +41,7 @@ POST / HTTP/1.1 Host: %s:%d Connection: close -GET /foo HTTP/1.1 +POST /foo HTTP/1.1 Host: %s:%d Connection: close diff --git a/ext/standard/tests/http/gh11274.phpt b/ext/standard/tests/http/gh11274.phpt new file mode 100644 index 0000000000000..fc125bfc494cf --- /dev/null +++ b/ext/standard/tests/http/gh11274.phpt @@ -0,0 +1,62 @@ +--TEST-- +GH-11274 (POST/PATCH request via file_get_contents + stream_context_create switches to GET after a HTTP 308 redirect) +--INI-- +allow_url_fopen=1 +--CONFLICTS-- +server +--FILE-- + ['method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); + echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test$suffix", false, stream_context_create(['http' => ['method' => 'PATCH', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); + echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test/$suffix", false, stream_context_create(['http' => ['method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); + echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/test/$suffix", false, stream_context_create(['http' => ['method' => 'PATCH', 'header' => 'Content-type: application/x-www-form-urlencoded', 'content' => http_build_query(['hello' => 'world'])]])); +} +?> +--EXPECT-- +-- Testing unredirected request -- +method: POST; body: hello=world +method: PATCH; body: hello=world +method: POST; body: hello=world +method: PATCH; body: hello=world +-- Testing redirect status code 301 -- +method: GET; body: +method: GET; body: +method: GET; body: +method: GET; body: +-- Testing redirect status code 302 -- +method: GET; body: +method: GET; body: +method: GET; body: +method: GET; body: +-- Testing redirect status code 307 -- +method: POST; body: hello=world +method: PATCH; body: hello=world +method: POST; body: hello=world +method: PATCH; body: hello=world +-- Testing redirect status code 308 -- +method: POST; body: hello=world +method: PATCH; body: hello=world +method: POST; body: hello=world +method: PATCH; body: hello=world diff --git a/ext/standard/tests/streams/gh10031.phpt b/ext/standard/tests/streams/gh10031.phpt new file mode 100644 index 0000000000000..aa3576dab51ad --- /dev/null +++ b/ext/standard/tests/streams/gh10031.phpt @@ -0,0 +1,52 @@ +--TEST-- +GH-10031 ([Stream] STREAM_NOTIFY_PROGRESS over HTTP emitted irregularly for last chunk of data) +--SKIPIF-- + +--INI-- +allow_url_fopen=1 +--CONFLICTS-- +server +--FILE-- + ['ignore_errors' => true,]]); +$lastBytesTransferred = 0; +stream_context_set_params($context, ['notification' => function ($code, $s, $m, $mc, $bytes_transferred, $bytes_max) +use (&$lastBytesTransferred) { + if ($code === STREAM_NOTIFY_FILE_SIZE_IS) echo "expected filesize=$bytes_max".PHP_EOL; + $lastBytesTransferred = $bytes_transferred; + @ob_flush(); +}]); + +$get = file_get_contents("http://".PHP_CLI_SERVER_ADDRESS, false, $context); + +echo "got filesize=" . strlen($get) . PHP_EOL; +var_dump($lastBytesTransferred); + +?> +--EXPECT-- +expected filesize=1000 +got filesize=1000 +int(1000) diff --git a/ext/xml/tests/bug26614_libxml_gte2_11.phpt b/ext/xml/tests/bug26614_libxml_gte2_11.phpt new file mode 100644 index 0000000000000..9a81b67686d14 --- /dev/null +++ b/ext/xml/tests/bug26614_libxml_gte2_11.phpt @@ -0,0 +1,95 @@ +--TEST-- +Bug #26614 (CDATA sections skipped on line count) +--EXTENSIONS-- +xml +--SKIPIF-- += 2.11'); +?> +--FILE-- + + + +'; + +// Case 2: replace some characters so that we get comments instead +$xmls["Comment"] =' + + +'; + +// Case 3: replace even more characters so that only textual data is left +$xmls["Text"] =' + +-!-- ATA[ +multi +line +CDATA +block +--- +'; + +function startElement($parser, $name, $attrs) { + printf("<$name> at line %d, col %d (byte %d)\n", + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser)); +} + +function endElement($parser, $name) { + printf(" at line %d, col %d (byte %d)\n", + xml_get_current_line_number($parser), + xml_get_current_column_number($parser), + xml_get_current_byte_index($parser)); +} + +function characterData($parser, $data) { + // dummy +} + +foreach ($xmls as $desc => $xml) { + echo "$desc\n"; + $xml_parser = xml_parser_create(); + xml_set_element_handler($xml_parser, "startElement", "endElement"); + xml_set_character_data_handler($xml_parser, "characterData"); + if (!xml_parse($xml_parser, $xml, true)) + echo "Error: ".xml_error_string(xml_get_error_code($xml_parser))."\n"; + xml_parser_free($xml_parser); +} +?> +--EXPECTF-- +CDATA + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) +Comment + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) +Text + at line 2, col %d (byte 50) + at line 9, col %d (byte 96) diff --git a/ext/xml/tests/bug26614_libxml.phpt b/ext/xml/tests/bug26614_libxml_pre2_11.phpt similarity index 96% rename from ext/xml/tests/bug26614_libxml.phpt rename to ext/xml/tests/bug26614_libxml_pre2_11.phpt index 6acf2c44b2a66..c581a08e9b8fb 100644 --- a/ext/xml/tests/bug26614_libxml.phpt +++ b/ext/xml/tests/bug26614_libxml_pre2_11.phpt @@ -5,6 +5,7 @@ xml --SKIPIF-- = 21100) die('skip libxml2 test variant for version < 2.11'); ?> --FILE-- getName(); - if ($num_repeats > 1 && $test->hasSection('FILE_EXTERNAL')) { - return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); + if ($test->hasSection('FILE_EXTERNAL')) { + $retriable = false; + if ($num_repeats > 1) { + return skip_test($tested, $tested_file, $shortname, 'Test with FILE_EXTERNAL might not be repeatable'); + } } if ($test->hasSection('CAPTURE_STDIO')) { @@ -1873,6 +1880,7 @@ function run_test(string $php, $file, array $env): string } $php = $php_cgi . ' -C '; $uses_cgi = true; + $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'CGI does not support --repeat'); } @@ -1890,20 +1898,18 @@ function run_test(string $php, $file, array $env): string } else { return skip_test($tested, $tested_file, $shortname, 'phpdbg not available'); } + $retriable = false; if ($num_repeats > 1) { return skip_test($tested, $tested_file, $shortname, 'phpdbg does not support --repeat'); } } - if ($num_repeats > 1) { - if ($test->hasSection('CLEAN')) { - return skip_test($tested, $tested_file, $shortname, 'Test with CLEAN might not be repeatable'); - } - if ($test->hasSection('STDIN')) { - return skip_test($tested, $tested_file, $shortname, 'Test with STDIN might not be repeatable'); - } - if ($test->hasSection('CAPTURE_STDIO')) { - return skip_test($tested, $tested_file, $shortname, 'Test with CAPTURE_STDIO might not be repeatable'); + foreach (['CLEAN', 'STDIN', 'CAPTURE_STDIO'] as $section) { + if ($test->hasSection($section)) { + $retriable = false; + if ($num_repeats > 1) { + return skip_test($tested, $tested_file, $shortname, "Test with $section might not be repeatable"); + } } } @@ -2085,8 +2091,11 @@ function run_test(string $php, $file, array $env): string $ini = preg_replace('/{MAIL:(\S+)}/', $replacement, $ini); settings2array(preg_split("/[\n\r]+/", $ini), $ini_settings); - if ($num_repeats > 1 && isset($ini_settings['opcache.opt_debug_level'])) { - return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); + if (isset($ini_settings['opcache.opt_debug_level'])) { + $retriable = false; + if ($num_repeats > 1) { + return skip_test($tested, $tested_file, $shortname, 'opt_debug_level tests are not repeatable'); + } } } @@ -2617,6 +2626,10 @@ function run_test(string $php, $file, array $env): string $wanted_re = null; } + if (!$passed && !$retried && $retriable && error_may_be_retried($output)) { + $retried = true; + goto retry; + } if ($passed) { if (!$cfg['keep']['php'] && !$leaked) { @@ -2647,6 +2660,9 @@ function run_test(string $php, $file, array $env): string } elseif ($test->hasSection('XLEAK')) { $warn = true; $info = " (warn: XLEAK section but test passes)"; + } elseif ($retried) { + $warn = true; + $info = " (warn: Test passed on retry attempt)"; } else { show_result("PASS", $tested, $tested_file, '', $temp_filenames); $junit->markTestAs('PASS', $shortname, $tested); @@ -2790,6 +2806,11 @@ function run_test(string $php, $file, array $env): string return $restype[0] . 'ED'; } +function error_may_be_retried(string $output): bool +{ + return preg_match('((timed out)|(connection refused))i', $output) === 1; +} + /** * @return bool|int */ diff --git a/sapi/fpm/fpm/fpm_children.c b/sapi/fpm/fpm/fpm_children.c index 2f8e3dc4d0acc..1c9780e3de3c1 100644 --- a/sapi/fpm/fpm/fpm_children.c +++ b/sapi/fpm/fpm/fpm_children.c @@ -63,10 +63,27 @@ static void fpm_child_free(struct fpm_child_s *child) /* {{{ */ } /* }}} */ +static void fpm_postponed_child_free(struct fpm_event_s *ev, short which, void *arg) +{ + struct fpm_child_s *child = (struct fpm_child_s *) arg; + + if (child->fd_stdout != -1) { + fpm_event_del(&child->ev_stdout); + close(child->fd_stdout); + } + if (child->fd_stderr != -1) { + fpm_event_del(&child->ev_stderr); + close(child->fd_stderr); + } + + fpm_child_free((struct fpm_child_s *) child); +} + static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ */ { if (child->fd_stdout != -1) { if (in_event_loop) { + child->postponed_free = true; fpm_event_fire(&child->ev_stdout); } if (child->fd_stdout != -1) { @@ -76,6 +93,7 @@ static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ if (child->fd_stderr != -1) { if (in_event_loop) { + child->postponed_free = true; fpm_event_fire(&child->ev_stderr); } if (child->fd_stderr != -1) { @@ -83,7 +101,12 @@ static void fpm_child_close(struct fpm_child_s *child, int in_event_loop) /* {{{ } } - fpm_child_free(child); + if (in_event_loop && child->postponed_free) { + fpm_event_set_timer(&child->ev_free, 0, &fpm_postponed_child_free, child); + fpm_event_add(&child->ev_free, 1000); + } else { + fpm_child_free(child); + } } /* }}} */ diff --git a/sapi/fpm/fpm/fpm_children.h b/sapi/fpm/fpm/fpm_children.h index 679c34ba0383e..fe06eb3ba84cd 100644 --- a/sapi/fpm/fpm/fpm_children.h +++ b/sapi/fpm/fpm/fpm_children.h @@ -23,12 +23,13 @@ struct fpm_child_s { struct fpm_child_s *prev, *next; struct timeval started; struct fpm_worker_pool_s *wp; - struct fpm_event_s ev_stdout, ev_stderr; + struct fpm_event_s ev_stdout, ev_stderr, ev_free; int shm_slot_i; int fd_stdout, fd_stderr; void (*tracer)(struct fpm_child_s *); struct timeval slow_logged; - int idle_kill; + bool idle_kill; + bool postponed_free; pid_t pid; int scoreboard_i; struct zlog_stream *log_stream; diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index 7ef0372c08ef1..b3ae2f69cc556 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -1909,19 +1909,16 @@ consult the installation file that came with this distribution, or visit \n\ } } zend_catch { } zend_end_try(); - /* we want to serve more requests if this is fastcgi - * so cleanup and continue, request shutdown is - * handled later */ - - goto fastcgi_request_done; - } - - fpm_request_executing(); + /* We want to serve more requests if this is fastcgi so cleanup and continue, + * request shutdown is handled later. */ + } else { + fpm_request_executing(); - /* Reset exit status from the previous execution */ - EG(exit_status) = 0; + /* Reset exit status from the previous execution */ + EG(exit_status) = 0; - php_execute_script(&file_handle); + php_execute_script(&file_handle); + } /* Without opcache, or the first time with opcache, the file handle will be placed * in the CG(open_files) list by open_file_for_scanning(). Starting from the second diff --git a/sapi/fpm/fpm/fpm_process_ctl.c b/sapi/fpm/fpm/fpm_process_ctl.c index 48eb0003d4918..7a55d98b046fc 100644 --- a/sapi/fpm/fpm/fpm_process_ctl.c +++ b/sapi/fpm/fpm/fpm_process_ctl.c @@ -318,7 +318,7 @@ static void fpm_pctl_kill_idle_child(struct fpm_child_s *child) /* {{{ */ if (child->idle_kill) { fpm_pctl_kill(child->pid, FPM_PCTL_KILL); } else { - child->idle_kill = 1; + child->idle_kill = true; fpm_pctl_kill(child->pid, FPM_PCTL_QUIT); } } diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index 514d60d176e39..f0d869444afce 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -13,7 +13,8 @@ #include "fpm_atomic.h" #include "fpm_conf.h" #include "fpm_php.h" -#include +#include "ext/standard/html.h" +#include "ext/json/php_json.h" static char *fpm_status_uri = NULL; static char *fpm_status_ping_uri = NULL; @@ -140,7 +141,8 @@ int fpm_status_handle_request(void) /* {{{ */ struct fpm_scoreboard_proc_s *proc; char *buffer, *time_format, time_buffer[64]; time_t now_epoch; - int full, encode, has_start_time; + int full, has_start_time; + bool encode_html, encode_json; char *short_syntax, *short_post; char *full_pre, *full_syntax, *full_post, *full_separator; zend_string *_GET_str; @@ -175,7 +177,8 @@ int fpm_status_handle_request(void) /* {{{ */ full = (fpm_php_get_string_from_table(_GET_str, "full") != NULL); short_syntax = short_post = NULL; full_separator = full_pre = full_syntax = full_post = NULL; - encode = 0; + encode_html = false; + encode_json = false; has_start_time = 1; scoreboard_p = fpm_scoreboard_get(); @@ -218,7 +221,7 @@ int fpm_status_handle_request(void) /* {{{ */ if (fpm_php_get_string_from_table(_GET_str, "html")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/html"), 1, 1); time_format = "%d/%b/%Y:%H:%M:%S %z"; - encode = 1; + encode_html = true; short_syntax = "\n" @@ -287,7 +290,7 @@ int fpm_status_handle_request(void) /* {{{ */ } else if (fpm_php_get_string_from_table(_GET_str, "xml")) { sapi_add_header_ex(ZEND_STRL("Content-Type: text/xml"), 1, 1); time_format = "%s"; - encode = 1; + encode_html = true; short_syntax = "\n" @@ -336,6 +339,8 @@ int fpm_status_handle_request(void) /* {{{ */ sapi_add_header_ex(ZEND_STRL("Content-Type: application/json"), 1, 1); time_format = "%s"; + encode_json = true; + short_syntax = "{" "\"pool\":\"%s\"," @@ -549,11 +554,24 @@ int fpm_status_handle_request(void) /* {{{ */ query_string = NULL; tmp_query_string = NULL; if (proc->query_string[0] != '\0') { - if (!encode) { - query_string = proc->query_string; + if (encode_html) { + tmp_query_string = php_escape_html_entities_ex( + (const unsigned char *) proc->query_string, + strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + } else if (encode_json) { + tmp_query_string = php_json_encode_string(proc->query_string, + strlen(proc->query_string), PHP_JSON_INVALID_UTF8_IGNORE); } else { - tmp_query_string = php_escape_html_entities_ex((const unsigned char *) proc->query_string, strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, NULL, /* double_encode */ 1, /* quiet */ 0); + query_string = proc->query_string; + } + if (tmp_query_string) { query_string = ZSTR_VAL(tmp_query_string); + /* remove quotes around the string */ + if (encode_json && ZSTR_LEN(tmp_query_string) >= 2) { + query_string[ZSTR_LEN(tmp_query_string) - 1] = '\0'; + ++query_string; + } } } diff --git a/sapi/fpm/fpm/fpm_stdio.c b/sapi/fpm/fpm/fpm_stdio.c index 46cf4aaa2851d..c796f17197381 100644 --- a/sapi/fpm/fpm/fpm_stdio.c +++ b/sapi/fpm/fpm/fpm_stdio.c @@ -181,10 +181,7 @@ static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) if (!arg) { return; } - child = fpm_child_find((intptr_t) arg); - if (!child) { - return; - } + child = (struct fpm_child_s *) arg; is_stdout = (fd == child->fd_stdout); if (is_stdout) { @@ -277,6 +274,7 @@ static void fpm_stdio_child_said(struct fpm_event_s *ev, short which, void *arg) fpm_event_del(event); + child->postponed_free = true; if (is_stdout) { close(child->fd_stdout); child->fd_stdout = -1; @@ -330,10 +328,10 @@ int fpm_stdio_parent_use_pipes(struct fpm_child_s *child) /* {{{ */ child->fd_stdout = fd_stdout[0]; child->fd_stderr = fd_stderr[0]; - fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, (void *) (intptr_t) child->pid); + fpm_event_set(&child->ev_stdout, child->fd_stdout, FPM_EV_READ, fpm_stdio_child_said, child); fpm_event_add(&child->ev_stdout, 0); - fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, (void *) (intptr_t) child->pid); + fpm_event_set(&child->ev_stderr, child->fd_stderr, FPM_EV_READ, fpm_stdio_child_said, child); fpm_event_add(&child->ev_stderr, 0); return 0; } diff --git a/sapi/fpm/tests/bug64539-status-json-encoding.phpt b/sapi/fpm/tests/bug64539-status-json-encoding.phpt new file mode 100644 index 0000000000000..f5c856c11c8a5 --- /dev/null +++ b/sapi/fpm/tests/bug64539-status-json-encoding.phpt @@ -0,0 +1,48 @@ +--TEST-- +FPM: bug64539 - status json format escaping +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$responses = $tester + ->multiRequest([ + ['query' => 'a=b"c'], + ['uri' => '/status', 'query' => 'full&json', 'delay' => 100000], + ]); +$responses[1]->expectJsonBodyPatternForStatusProcessField('request uri', '\?a=b"c$'); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +Done +--CLEAN-- + diff --git a/sapi/fpm/tests/response.inc b/sapi/fpm/tests/response.inc index 97e5c3263e290..cd5e19d708c52 100644 --- a/sapi/fpm/tests/response.inc +++ b/sapi/fpm/tests/response.inc @@ -96,6 +96,37 @@ class Response return $this; } + /** + * Expect that one of the processes in json status process list has a field with value that + * matches the supplied pattern. + * + * @param string $fieldName + * @param string $pattern + * + * @return Response + */ + public function expectJsonBodyPatternForStatusProcessField(string $fieldName, string $pattern) + { + $rawData = $this->getBody('application/json'); + $data = json_decode($rawData, true); + if (empty($data['processes']) || !is_array($data['processes'])) { + $this->error( + "The body data is not a valid status json containing processes field '$rawData'" + ); + } + foreach ($data['processes'] as $process) { + if (preg_match('|' . $pattern . '|', $process[$fieldName]) !== false) { + return $this; + } + } + + $this->error( + "No field $fieldName matched pattern $pattern for any process in status data '$rawData'" + ); + + return $this; + } + /** * @return Response */ @@ -218,18 +249,22 @@ class Response /** * Print raw body. + * + * @param string $contentType Expect body to have specified content type. */ - public function dumpBody() + public function dumpBody(string $contentType = 'text/html') { - var_dump($this->getBody()); + var_dump($this->getBody($contentType)); } /** * Print raw body. + * + * @param string $contentType Expect body to have specified content type. */ - public function printBody() + public function printBody(string $contentType = 'text/html') { - echo $this->getBody() . "\n"; + echo $this->getBody($contentType) . "\n"; } /** diff --git a/sapi/fpm/tests/tester.inc b/sapi/fpm/tests/tester.inc index 39c964761b282..a3f40ed9bcc40 100644 --- a/sapi/fpm/tests/tester.inc +++ b/sapi/fpm/tests/tester.inc @@ -826,6 +826,10 @@ class Tester $requestData['uri'] ?? null ); + if (isset($requestData['delay'])) { + usleep($requestData['delay']); + } + return [ 'client' => $client, 'requestId' => $client->async_request($params, false),