From 07d308a0debc1219d4ef4b789d6a46ec4fb740d3 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Fri, 11 Jul 2025 15:33:16 +0200 Subject: [PATCH 1/8] Tweak lineno compilation Fixes GH-18985 Closes GH-22833 zend_compile_expr() and zend_compile_var() now restore CG(zend_lineno) after compilation of the node has finished. This makes the compiled lineno more predictable. Note that there are many cases left that are incorrect. For example: debug_level & ZEND_DUMP_BEFORE_OPTIMIZER) { - zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES, "before optimizer", NULL); + uint32_t additional_dump_flags = (ctx->debug_level & ZEND_DUMP_LINE_NUMBERS_PASSTHRU) + ? ZEND_DUMP_LINE_NUMBERS + : 0; + zend_dump_op_array(op_array, ZEND_DUMP_LIVE_RANGES|additional_dump_flags, "before optimizer", NULL); } /* pass 1 (Simple local optimizations) diff --git a/Zend/Optimizer/zend_optimizer.h b/Zend/Optimizer/zend_optimizer.h index d2847c92869c..54acec940f5a 100644 --- a/Zend/Optimizer/zend_optimizer.h +++ b/Zend/Optimizer/zend_optimizer.h @@ -80,6 +80,7 @@ #define ZEND_DUMP_DFA_SSA (1<<27) #define ZEND_DUMP_DFA_SSA_VARS (1<<28) #define ZEND_DUMP_SCCP (1<<29) +#define ZEND_DUMP_LINE_NUMBERS_PASSTHRU (1<<30) typedef struct _zend_script { zend_string *filename; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6a677e71a6b6..450b192ac482 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7125,6 +7125,8 @@ static void zend_compile_match(znode *result, zend_ast *ast) zend_ast *arm_ast = arms->child[i]; zend_ast *body_ast = arm_ast->child[1]; + CG(zend_lineno) = zend_ast_get_lineno(arm_ast); + if (arm_ast->child[0] != NULL) { zend_ast_list *conds = zend_ast_get_list(arm_ast->child[0]); @@ -10728,6 +10730,8 @@ static void zend_compile_binary_op(znode *result, zend_ast *ast) /* {{{ */ zend_compile_expr(&left_node, left_ast); zend_compile_expr(&right_node, right_ast); + CG(zend_lineno) = ast->lineno; + if (left_node.op_type == IS_CONST && right_node.op_type == IS_CONST) { if (zend_try_ct_eval_binary_op(&result->u.constant, opcode, &left_node.u.constant, &right_node.u.constant) @@ -12335,9 +12339,6 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ { - /* CG(zend_lineno) = ast->lineno; */ - CG(zend_lineno) = zend_ast_get_lineno(ast); - if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) { zend_compile_memoized_expr(result, ast, BP_VAR_R); return; @@ -12479,6 +12480,9 @@ static void zend_compile_expr(znode *result, zend_ast *ast) { zend_check_stack_limit(); + uint32_t prev_lineno = CG(zend_lineno); + CG(zend_lineno) = zend_ast_get_lineno(ast); + uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_compile_expr_inner(result, ast); zend_short_circuiting_commit(checkpoint, result, ast); @@ -12488,12 +12492,12 @@ static void zend_compile_expr(znode *result, zend_ast *ast) ZEND_ASSERT(result->op_type != IS_VAR); } #endif + + CG(zend_lineno) = prev_lineno; } static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t type, bool by_ref) { - CG(zend_lineno) = zend_ast_get_lineno(ast); - if (CG(memoize_mode) != ZEND_MEMOIZE_NONE) { switch (ast->kind) { case ZEND_AST_CALL: @@ -12554,6 +12558,9 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo { zend_check_stack_limit(); + uint32_t prev_lineno = CG(zend_lineno); + CG(zend_lineno) = zend_ast_get_lineno(ast); + uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_op *opcode = zend_compile_var_inner(result, ast, type, by_ref); zend_short_circuiting_commit(checkpoint, result, ast); @@ -12567,6 +12574,9 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo ZEND_ASSERT(result->op_type != IS_VAR); } #endif + + CG(zend_lineno) = prev_lineno; + return opcode; } @@ -12574,25 +12584,37 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t { zend_check_stack_limit(); + uint32_t prev_lineno = CG(zend_lineno); + CG(zend_lineno) = zend_ast_get_lineno(ast); + + zend_op *opline; switch (ast->kind) { case ZEND_AST_VAR: - return zend_compile_simple_var(result, ast, type, true); + opline = zend_compile_simple_var(result, ast, type, true); + break; case ZEND_AST_DIM: - return zend_delayed_compile_dim(result, ast, type, by_ref); + opline = zend_delayed_compile_dim(result, ast, type, by_ref); + break; case ZEND_AST_PROP: case ZEND_AST_NULLSAFE_PROP: { - zend_op *opline = zend_delayed_compile_prop(result, ast, type); + opline = zend_delayed_compile_prop(result, ast, type); if (by_ref) { opline->extended_value |= ZEND_FETCH_REF; } - return opline; + break; } case ZEND_AST_STATIC_PROP: - return zend_compile_static_prop(result, ast, type, by_ref, true); + opline = zend_compile_static_prop(result, ast, type, by_ref, true); + break; default: - return zend_compile_var(result, ast, type, false); + opline = zend_compile_var(result, ast, type, false); + break; } + + CG(zend_lineno) = prev_lineno; + + return opline; } /* }}} */ diff --git a/ext/opcache/tests/gh18985.phpt b/ext/opcache/tests/gh18985.phpt new file mode 100644 index 000000000000..22c7783cf777 --- /dev/null +++ b/ext/opcache/tests/gh18985.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-18985: Wrong lineno for multiline expressions +--EXTENSIONS-- +opcache +--INI-- +opcache.enable_cli=1 +opcache.opt_debug_level=0x40010000 +--FILE-- + "A", + 15 => "B", + default => "C", +}; + +?> +--EXPECTF-- +$_main: + ; (lines=9, args=0, vars=0, tmps=%s) + ; (before optimizer) + ; %sgh18985.php:1-10 + ; return [] RANGE[0..0] +L0003 0000 MATCH int(15) 13: 0001, 15: 0003, default: 0005 +L0004 0001 T1 = QM_ASSIGN string("A") +L0004 0002 JMP 0007 +L0005 0003 T1 = QM_ASSIGN string("B") +L0005 0004 JMP 0007 +L0006 0005 T1 = QM_ASSIGN string("C") +L0006 0006 JMP 0007 +L0003 0007 ECHO T1 +L0010 0008 RETURN int(1) +LIVE RANGES: + 1: 0006 - 0007 (tmp/var) +B diff --git a/ext/opcache/tests/jit/shift_right_004.phpt b/ext/opcache/tests/jit/shift_right_004.phpt index df65b747ca4d..5b816893c53f 100644 --- a/ext/opcache/tests/jit/shift_right_004.phpt +++ b/ext/opcache/tests/jit/shift_right_004.phpt @@ -30,9 +30,9 @@ Warning: Undefined array key 0 in %sshift_right_004.php on line 7 Deprecated: Implicit conversion from float %f to int loses precision in %sshift_right_004.php on line 8 -Warning: A non-numeric value encountered in %sshift_right_004.php on line 7 +Warning: A non-numeric value encountered in %sshift_right_004.php on line 6 -Warning: A non-numeric value encountered in %sshift_right_004.php on line 7 +Warning: A non-numeric value encountered in %sshift_right_004.php on line 6 Fatal error: Uncaught ArithmeticError: Bit shift by negative number in %sshift_right_004.php:8 Stack trace: diff --git a/ext/opcache/tests/jit/switch_001.phpt b/ext/opcache/tests/jit/switch_001.phpt index 57ee3a40b840..898ebb363f21 100644 --- a/ext/opcache/tests/jit/switch_001.phpt +++ b/ext/opcache/tests/jit/switch_001.phpt @@ -16,7 +16,7 @@ foo(); ?> DONE --EXPECTF-- -Warning: Undefined variable $y in %sswitch_001.php on line 4 +Warning: Undefined variable $y in %sswitch_001.php on line 3 -Warning: Undefined variable $y in %sswitch_001.php on line 5 +Warning: Undefined variable $y in %sswitch_001.php on line 3 DONE From 250f138329e4e255a6361a110a313f3bf3112e80 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Fri, 24 Jul 2026 11:14:15 -0400 Subject: [PATCH 2/8] Fix phpdbg over-read when watching a packed array element (#22756) phpdbg watched every array element as a Bucket, but packed arrays store bare zvals and de-indirected stack variables aren't buckets, so reading Bucket.h/.key over-read the neighbour and a sibling write tripped a phantom break. Watch such elements as WATCH_ON_ZVAL and compare the type info first, reading the value union only for initialised slots so an uninitialised CV is never compared by value. A separate pre-existing bug crashed 32-bit: phpdbg_btree_insert_or_update linked each new node into the tree before initialising its child pointers, so a write to a node on a watched page faulted into the watchpoint handler, which walked the half-built node. Build the node fully, then publish it with a single store. Closes GH-22756 --- sapi/phpdbg/phpdbg_btree.c | 13 +++++--- sapi/phpdbg/phpdbg_watch.c | 54 +++++++++++++++++++++++++------- sapi/phpdbg/phpdbg_watch.h | 1 + sapi/phpdbg/tests/watch_005.phpt | 9 ------ sapi/phpdbg/tests/watch_006.phpt | 6 ---- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/sapi/phpdbg/phpdbg_btree.c b/sapi/phpdbg/phpdbg_btree.c index f7c554884d9d..788e9b464c14 100644 --- a/sapi/phpdbg/phpdbg_btree.c +++ b/sapi/phpdbg/phpdbg_btree.c @@ -156,13 +156,18 @@ int phpdbg_btree_insert_or_update(phpdbg_btree *tree, zend_ulong idx, void *ptr, } { - phpdbg_btree_branch *memory = *branch = pemalloc((i + 2) * sizeof(phpdbg_btree_branch), tree->persistent); + phpdbg_btree_branch *memory = pemalloc((i + 2) * sizeof(phpdbg_btree_branch), tree->persistent); + phpdbg_btree_branch *node = memory; do { - (*branch)->branches[!((idx >> i) % 2)] = NULL; - branch = &(*branch)->branches[(idx >> i) % 2]; - *branch = ++memory; + node->branches[!((idx >> i) % 2)] = NULL; + node->branches[(idx >> i) % 2] = node + 1; + node++; } while (i--); + node->result.idx = idx; + node->result.ptr = ptr; + *branch = memory; tree->count++; + return SUCCESS; } } else if (!(flags & PHPDBG_BTREE_UPDATE)) { return FAILURE; diff --git a/sapi/phpdbg/phpdbg_watch.c b/sapi/phpdbg/phpdbg_watch.c index ed6e6dfc70b9..1e67d2c57670 100644 --- a/sapi/phpdbg/phpdbg_watch.c +++ b/sapi/phpdbg/phpdbg_watch.c @@ -133,6 +133,16 @@ const phpdbg_command_t phpdbg_watch_commands[] = { #define HT_WATCH_HT(watch) HT_PTR_HT((watch)->addr.ptr) /* ### PRINTING POINTER DIFFERENCES ### */ +static bool phpdbg_check_zval_watch_diff(zval *oldPtr, zval *newPtr) { + if (Z_TYPE_INFO_P(oldPtr) != Z_TYPE_INFO_P(newPtr)) { + return true; + } + if (Z_TYPE_P(oldPtr) < IS_LONG) { + return false; + } + return memcmp(oldPtr, newPtr, sizeof(zend_value)) != 0; +} + bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) { switch (type) { case WATCH_ON_BUCKET: @@ -142,7 +152,7 @@ bool phpdbg_check_watch_diff(phpdbg_watchtype type, void *oldPtr, void *newPtr) /* Fall through to also compare the value from the bucket. */ ZEND_FALLTHROUGH; case WATCH_ON_ZVAL: - return memcmp(oldPtr, newPtr, sizeof(zend_value) + sizeof(uint32_t) /* value + typeinfo */) != 0; + return phpdbg_check_zval_watch_diff((zval *) oldPtr, (zval *) newPtr); case WATCH_ON_HASHTABLE: return zend_hash_num_elements(HT_PTR_HT(oldPtr)) != zend_hash_num_elements(HT_PTR_HT(newPtr)); case WATCH_ON_REFCOUNTED: @@ -568,9 +578,26 @@ phpdbg_watch_element *phpdbg_add_watch_element(phpdbg_watchpoint_t *watch, phpdb return element; } -phpdbg_watch_element *phpdbg_add_bucket_watch_element(Bucket *bucket, phpdbg_watch_element *element, bool *is_new) { +static bool phpdbg_zval_is_bucket_of(HashTable *ht, zval *zv) { + if (!ht || HT_IS_PACKED(ht)) { + return false; + } + if ((uintptr_t) zv < (uintptr_t) ht->arData + || (uintptr_t) zv >= (uintptr_t) (ht->arData + ht->nNumUsed)) { + return false; + } + ZEND_ASSERT(((uintptr_t) zv - (uintptr_t) ht->arData) % sizeof(Bucket) == 0); + return true; +} + +phpdbg_watch_element *phpdbg_add_bucket_watch_element(zval *zv, phpdbg_watch_element *element, bool *is_new) { phpdbg_watchpoint_t watch; - phpdbg_set_bucket_watchpoint(bucket, &watch); + + if (phpdbg_zval_is_bucket_of(element->parent_container, zv)) { + phpdbg_set_bucket_watchpoint((Bucket *) zv, &watch); + } else { + phpdbg_set_zval_watchpoint(zv, &watch); + } bool added_new; phpdbg_watch_element *added = phpdbg_add_watch_element(&watch, element, &added_new); if (added_new) { @@ -637,7 +664,7 @@ void phpdbg_add_recursive_watch_from_ht(phpdbg_watch_element *element, zend_long return; } bool added_new; - phpdbg_add_bucket_watch_element((Bucket *) zv, child, &added_new); + phpdbg_add_bucket_watch_element(zv, child, &added_new); if (added_new) { zend_hash_add_ptr(&element->child_container, child->str, child); } @@ -697,7 +724,7 @@ void phpdbg_recurse_watch_element(phpdbg_watch_element *element) { } void phpdbg_watch_parent_ht(phpdbg_watch_element *element) { - if (element->watch->type == WATCH_ON_BUCKET) { + if (element->watch->type == WATCH_ON_BUCKET || element->watch->type == WATCH_ON_ZVAL) { phpdbg_btree_result *res; phpdbg_watch_ht_info *hti; ZEND_ASSERT(element->parent_container); @@ -716,14 +743,17 @@ void phpdbg_watch_parent_ht(phpdbg_watch_element *element) { hti = (phpdbg_watch_ht_info *) res->ptr; } - zend_hash_add_ptr(&hti->watches, element->name_in_parent, element); + if (zend_hash_add_ptr(&hti->watches, element->name_in_parent, element)) { + element->flags |= PHPDBG_WATCH_HT_REGISTERED; + } } } void phpdbg_unwatch_parent_ht(phpdbg_watch_element *element) { - if (element->watch && element->watch->type == WATCH_ON_BUCKET) { + if (element->flags & PHPDBG_WATCH_HT_REGISTERED) { phpdbg_btree_result *res = phpdbg_btree_find(&PHPDBG_G(watch_HashTables), (zend_ulong) element->parent_container); ZEND_ASSERT(element->parent_container); + element->flags &= ~PHPDBG_WATCH_HT_REGISTERED; if (res) { phpdbg_watch_ht_info *hti = res->ptr; @@ -809,7 +839,7 @@ bool phpdbg_try_re_adding_watch_element(zval *parent, phpdbg_watch_element *elem return false; } element->parent_container = ht; - element = phpdbg_add_bucket_watch_element((Bucket *) zv, element, NULL); + element = phpdbg_add_bucket_watch_element(zv, element, NULL); } else { return false; } @@ -1320,7 +1350,7 @@ static phpdbg_watch_add_result phpdbg_create_simple_watchpoint(zval *zv, phpdbg_ phpdbg_watch_element *added; (*element)->flags = PHPDBG_WATCH_SIMPLE; bool added_new; - added = phpdbg_add_bucket_watch_element((Bucket *) zv, *element, &added_new); + added = phpdbg_add_bucket_watch_element(zv, *element, &added_new); if (!added_new) { *element = added; return PHPDBG_WATCH_ADD_DUPLICATE; @@ -1344,7 +1374,7 @@ static phpdbg_watch_add_result phpdbg_create_array_watchpoint(zval *zv, phpdbg_w element->str = str; element->flags = PHPDBG_WATCH_IMPLICIT; bool added_new; - added = phpdbg_add_bucket_watch_element((Bucket *) orig_zv, element, &added_new); + added = phpdbg_add_bucket_watch_element(orig_zv, element, &added_new); if (!added_new) { *element_ptr = added; return PHPDBG_WATCH_ADD_DUPLICATE; @@ -1372,7 +1402,7 @@ static phpdbg_watch_add_result phpdbg_create_recursive_watchpoint(zval *zv, phpd (*element)->flags = PHPDBG_WATCH_RECURSIVE | PHPDBG_WATCH_RECURSIVE_ROOT; (*element)->child = NULL; bool added_new; - added = phpdbg_add_bucket_watch_element((Bucket *) zv, *element, &added_new); + added = phpdbg_add_bucket_watch_element(zv, *element, &added_new); if (!added_new) { *element = added; return PHPDBG_WATCH_ADD_DUPLICATE; @@ -1451,7 +1481,7 @@ static int phpdbg_watchpoint_parse_step(char *name, size_t namelen, char *key, s element->name_in_parent = zend_string_init(key, keylen, 0); element->parent_container = parent; element->parent = PHPDBG_G(watch_tmp); - element = phpdbg_add_bucket_watch_element((Bucket *) zv, element, NULL); + element = phpdbg_add_bucket_watch_element(zv, element, NULL); efree(name); efree(key); diff --git a/sapi/phpdbg/phpdbg_watch.h b/sapi/phpdbg/phpdbg_watch.h index e87dd77f0543..9f2549a96a52 100644 --- a/sapi/phpdbg/phpdbg_watch.h +++ b/sapi/phpdbg/phpdbg_watch.h @@ -54,6 +54,7 @@ typedef enum { #define PHPDBG_WATCH_NORMAL (PHPDBG_WATCH_SIMPLE | PHPDBG_WATCH_RECURSIVE) #define PHPDBG_WATCH_IMPLICIT 0x10 #define PHPDBG_WATCH_RECURSIVE_ROOT 0x20 +#define PHPDBG_WATCH_HT_REGISTERED 0x40 typedef struct _phpdbg_watch_collision phpdbg_watch_collision; diff --git a/sapi/phpdbg/tests/watch_005.phpt b/sapi/phpdbg/tests/watch_005.phpt index aacc158f6174..d6bae9d3f3fa 100644 --- a/sapi/phpdbg/tests/watch_005.phpt +++ b/sapi/phpdbg/tests/watch_005.phpt @@ -1,14 +1,5 @@ --TEST-- Test proper watch comparisons when having multiple levels of indirection from a zval to its value ---SKIPIF-- - --PHPDBG-- b 3 r diff --git a/sapi/phpdbg/tests/watch_006.phpt b/sapi/phpdbg/tests/watch_006.phpt index bf38b8eff1fb..21b77a982d63 100644 --- a/sapi/phpdbg/tests/watch_006.phpt +++ b/sapi/phpdbg/tests/watch_006.phpt @@ -11,7 +11,6 @@ c - q --EXPECTF-- [Successful compilation of %s] @@ -48,11 +47,6 @@ prompt> [Element 1 has been added to watchpoint] 00010: prompt> [Breaking on watchpoint $b] Old value inaccessible or destroyed -New value (reference): Array ([0] => 2,[1] => 3) ->00009: $b = &$c; - 00010: -prompt> [Breaking on watchpoint $b] -Old value inaccessible or destroyed New value (reference): Array ([0] => 1) >00010: prompt> [$b has been removed, removing watchpoint recursively] From 3db04878609913866d791946d3e29036506fa7b3 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 28 Jun 2026 13:35:50 -0400 Subject: [PATCH 3/8] Don't expose a freed stream resource to user filters When a stream is freed from its resource destructor, the on-close write-filter flush runs the user filter callback while the stream's resource is already dtor'd (type == -1) and about to be freed. Exposing it through $this->stream let user code capture the dead resource in an exception backtrace, a use-after-free. Assign null when the resource is no longer live; the explicit fclose() flush still runs before the resource is closed, so live streams are unaffected. Fixes GH-15836 Closes GH-22503 --- NEWS | 4 +++ ext/standard/tests/filters/bug54350.phpt | 2 +- ext/standard/tests/filters/gh15836.phpt | 34 ++++++++++++++++++++++++ ext/standard/user_filters.c | 6 ++++- 4 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 ext/standard/tests/filters/gh15836.phpt diff --git a/NEWS b/NEWS index e4667b1d3b99..0aa44c4d5006 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,10 @@ PHP NEWS - Sockets: . Fixed various memory related issues in ext/sockets. (David Carlier) +- Streams: + . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses + $this->stream during the close flush). (iliaal) + 30 Jul 2026, PHP 8.4.24 - Calendar: diff --git a/ext/standard/tests/filters/bug54350.phpt b/ext/standard/tests/filters/bug54350.phpt index a017893eed7b..704ca46ab53a 100644 --- a/ext/standard/tests/filters/bug54350.phpt +++ b/ext/standard/tests/filters/bug54350.phpt @@ -23,4 +23,4 @@ fwrite($fd, "foo"); ?> --EXPECTF-- Warning: fclose(): 5 is not a valid stream resource in %s on line %d -fclose(): supplied resource is not a valid stream resource +fclose(): Argument #1 ($stream) must be of type resource, null given diff --git a/ext/standard/tests/filters/gh15836.phpt b/ext/standard/tests/filters/gh15836.phpt new file mode 100644 index 000000000000..593b93dcdc70 --- /dev/null +++ b/ext/standard/tests/filters/gh15836.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-15836 (use-after-free when a user filter reads $this->stream during the close flush) +--FILE-- +stream, "x"); + } catch (TypeError $e) { + self::$e = $e; + } + } + return PSFS_PASS_ON; + } +} +var_dump(stream_filter_register("my_filter", "my_filter")); + +function run() { + $s = fopen("php://memory", "wb+"); + stream_filter_append($s, "my_filter", STREAM_FILTER_WRITE); +} +run(); + +echo my_filter::$e->getTraceAsString(), "\n"; +echo "done\n"; +?> +--EXPECTF-- +bool(true) +#0 %s(%d): stream_bucket_new(NULL, 'x') +#1 %s(%d): my_filter->filter(Resource id #%d, Resource id #%d, 0, true) +#2 {main} +done diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index 83b1986b82a3..735dd8390de8 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -156,7 +156,11 @@ static php_stream_filter_status_t userfilter_filter( bool stream_property_exists = Z_OBJ_HT_P(obj)->has_property(Z_OBJ_P(obj), stream_name, ZEND_PROPERTY_EXISTS, NULL); if (stream_property_exists) { zval stream_zval; - php_stream_to_zval(stream, &stream_zval); + if (EXPECTED(stream->res && stream->res->type >= 0)) { + php_stream_to_zval(stream, &stream_zval); + } else { + ZVAL_NULL(&stream_zval); + } zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), stream_name, &stream_zval); /* If property update threw an exception, skip filter execution */ if (EG(exception)) { From 68d605fa924a34247aa800f06ab86a7b60b423ce Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Sun, 21 Jun 2026 08:10:09 -0400 Subject: [PATCH 4/8] Fix OOB read in opcache POSIX largepage page-size selection create_segments() under HAVE_SHM_CREATE_LARGEPAGE stored the int return of getpagesizes() in a size_t and iterated with a size_t counter. On the getpagesizes() error return (-1) the size_t became SIZE_MAX, passing the > 0 guard, and the unsigned loop counter made i >= 0 always true, so the loop ran from a huge index and read far outside the 3-element shared_segment_sindexes array; even on success, if no returned page size divided requested_size the counter wrapped past 0. Capture the result in a signed int and iterate signed so the error return is rejected and the loop terminates. Closes GH-22429 --- ext/opcache/shared_alloc_posix.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/opcache/shared_alloc_posix.c b/ext/opcache/shared_alloc_posix.c index 3f1e097fe97c..aa474729594b 100644 --- a/ext/opcache/shared_alloc_posix.c +++ b/ext/opcache/shared_alloc_posix.c @@ -51,9 +51,10 @@ static int create_segments(size_t requested_size, zend_shared_segment_posix ***s * only then amd64/i386/arm64 and perharps risc64* * archs are on interest here. */ - size_t i, shared_segment_sizes = 0, shared_segment_lg_index = 0; + size_t shared_segment_lg_index = 0; size_t shared_segment_sindexes[3] = {0}; const size_t entries = sizeof(shared_segment_sindexes) / sizeof(shared_segment_sindexes[0]); + int i, shared_segment_sizes; shared_segment_sizes = getpagesizes(shared_segment_sindexes, entries); From f2ca6f882d887d8384530eb98baf396528e3d788 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Fri, 24 Jul 2026 23:59:29 +0800 Subject: [PATCH 5/8] [skip ci] Fix several NEWS and UPGRADING entries --- NEWS | 2 +- UPGRADING | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 244ea8ea94f6..484e462698d9 100644 --- a/NEWS +++ b/NEWS @@ -222,7 +222,7 @@ PHP NEWS . Fix OSS-Fuzz #429429090 (Failed assertion on unset() with uninitialized container). (ilutov) . Fixed GH-20564 (Don't call autoloaders with pending exception). (ilutov) - . Fix deprecation now showing when accessing null key of an array with JIT. + . Fix deprecation not showing when accessing null key of an array with JIT. (alexandre-daubois) . Fixed bug GH-20174 (Assertion failure in ReflectionProperty::skipLazyInitialization after failed LazyProxy diff --git a/UPGRADING b/UPGRADING index 97670e09246d..93245f87f42a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -297,7 +297,7 @@ PHP 8.6 UPGRADE NOTES requests, implementing custom server-side session storage, and controlling session cache behavior. RFC: https://wiki.php.net/rfc/tls_session_resumption - . Added TLS external PSK support for streams with new strem context options: + . Added TLS external PSK support for streams with new stream context options: psk_client_cb and psk_server_cb. This allows setting and receiving PSK. - Phar: From 00f4cd6b800872c6220257f9463f683578cfb624 Mon Sep 17 00:00:00 2001 From: Weilin Du Date: Sat, 25 Jul 2026 01:06:29 +0800 Subject: [PATCH 6/8] Refactor various raw C-string concatenation to use existing API (#22879) In #21607 we introduce several inline helpers for raw C-string concatenation. This PR use them to refactor several manual C string construction. --- ext/opcache/ZendAccelerator.c | 16 +++++++------- ext/pcntl/pcntl.c | 11 ++++------ ext/pdo_pgsql/pgsql_driver.c | 9 ++++---- ext/pgsql/pgsql.c | 6 ++---- ext/phar/phar_object.c | 6 +++--- ext/soap/php_sdl.c | 39 ++++++++++++++--------------------- ext/standard/fsock.c | 15 ++++---------- ext/zip/php_zip.c | 5 +---- main/SAPI.c | 15 +++++++------- sapi/fpm/fpm/fpm_main.c | 29 ++++++++------------------ sapi/phpdbg/phpdbg_utils.c | 6 +----- win32/registry.c | 6 +++--- 12 files changed, 62 insertions(+), 101 deletions(-) diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 0a7a62eae9a9..6f7cc8a6552a 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1943,10 +1943,10 @@ static zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int /* ext/phar has to load phar's metadata into memory */ if (persistent_script->is_phar) { php_stream_statbuf ssb; - char *fname = emalloc(sizeof("phar://") + ZSTR_LEN(persistent_script->script.filename)); - - memcpy(fname, "phar://", sizeof("phar://") - 1); - memcpy(fname + sizeof("phar://") - 1, ZSTR_VAL(persistent_script->script.filename), ZSTR_LEN(persistent_script->script.filename) + 1); + char *fname = zend_cstr_concat( + "phar://", sizeof("phar://") - 1, + ZSTR_VAL(persistent_script->script.filename), + ZSTR_LEN(persistent_script->script.filename)); php_stream_stat_path(fname, &ssb); efree(fname); } @@ -2457,10 +2457,10 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) /* ext/phar has to load phar's metadata into memory */ if (persistent_script->is_phar) { php_stream_statbuf ssb; - char *fname = emalloc(sizeof("phar://") + ZSTR_LEN(persistent_script->script.filename)); - - memcpy(fname, "phar://", sizeof("phar://") - 1); - memcpy(fname + sizeof("phar://") - 1, ZSTR_VAL(persistent_script->script.filename), ZSTR_LEN(persistent_script->script.filename) + 1); + char *fname = zend_cstr_concat( + "phar://", sizeof("phar://") - 1, + ZSTR_VAL(persistent_script->script.filename), + ZSTR_LEN(persistent_script->script.filename)); php_stream_stat_path(fname, &ssb); efree(fname); } diff --git a/ext/pcntl/pcntl.c b/ext/pcntl/pcntl.c index 794e75a1716e..265b47e52dc7 100644 --- a/ext/pcntl/pcntl.c +++ b/ext/pcntl/pcntl.c @@ -746,13 +746,10 @@ PHP_FUNCTION(pcntl_exec) zend_string_addref(key); } - /* Length of element + equal sign + length of key + null */ - *pair = safe_emalloc(ZSTR_LEN(element_str) + 1, sizeof(char), ZSTR_LEN(key) + 1); - /* Copy key=element + final null byte into buffer */ - memcpy(*pair, ZSTR_VAL(key), ZSTR_LEN(key)); - (*pair)[ZSTR_LEN(key)] = '='; - /* Copy null byte */ - memcpy(*pair + ZSTR_LEN(key) + 1, ZSTR_VAL(element_str), ZSTR_LEN(element_str) + 1); + *pair = zend_cstr_concat3( + ZSTR_VAL(key), ZSTR_LEN(key), + "=", 1, + ZSTR_VAL(element_str), ZSTR_LEN(element_str)); /* Cleanup */ zend_string_release_ex(key, false); diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 8f5e22640ccc..52aae986dee1 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -392,11 +392,10 @@ static zend_string* pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo return NULL; } quotedlen = tmp_len + 1; - quoted = emalloc(quotedlen + 1); - memcpy(quoted+1, escaped, quotedlen-2); - quoted[0] = '\''; - quoted[quotedlen-1] = '\''; - quoted[quotedlen] = '\0'; + quoted = zend_cstr_concat3( + "'", 1, + (const char *) escaped, quotedlen - 2, + "'", 1); PQfreemem(escaped); break; default: diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 0ea766c12b42..87ccbe3424a7 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -3433,10 +3433,8 @@ static zend_result pgsql_copy_from_query(PGconn *pgsql, PGresult *pgsql_result, int result; if (ZSTR_LEN(tmp) > 0 && ZSTR_VAL(tmp)[ZSTR_LEN(tmp) - 1] != '\n') { - char *zquery = emalloc(ZSTR_LEN(tmp) + 2); - memcpy(zquery, ZSTR_VAL(tmp), ZSTR_LEN(tmp)); - zquery[ZSTR_LEN(tmp)] = '\n'; - zquery[ZSTR_LEN(tmp) + 1] = '\0'; + char *zquery = zend_cstr_append_char( + ZSTR_VAL(tmp), ZSTR_LEN(tmp), '\n'); result = PQputCopyData(pgsql, zquery, ZSTR_LEN(tmp) + 1); efree(zquery); } else { diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 0139f0889a81..8a330a954b89 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -633,9 +633,9 @@ PHP_METHOD(Phar, webPhar) IS_STRING == Z_TYPE_P(z_path_info)) { entry_len = Z_STRLEN_P(z_path_info); entry = estrndup(Z_STRVAL_P(z_path_info), entry_len); - path_info = emalloc(Z_STRLEN_P(z_script_name) + entry_len + 1); - memcpy(path_info, Z_STRVAL_P(z_script_name), Z_STRLEN_P(z_script_name)); - memcpy(path_info + Z_STRLEN_P(z_script_name), entry, entry_len + 1); + path_info = zend_cstr_concat( + Z_STRVAL_P(z_script_name), Z_STRLEN_P(z_script_name), + entry, entry_len); free_pathinfo = 1; } else { entry_len = 0; diff --git a/ext/soap/php_sdl.c b/ext/soap/php_sdl.c index 73886af5a567..77d73a2326d7 100644 --- a/ext/soap/php_sdl.c +++ b/ext/soap/php_sdl.c @@ -85,12 +85,10 @@ static sdlTypePtr get_element(sdlPtr sdl, xmlNodePtr node, const xmlChar *type) size_t ns_len = xmlStrlen(nsptr->href); size_t type_len = strlen(cptype); size_t len = ns_len + type_len + 1; - char *nscat = emalloc(len + 1); - - memcpy(nscat, nsptr->href, ns_len); - nscat[ns_len] = ':'; - memcpy(nscat+ns_len+1, cptype, type_len); - nscat[len] = '\0'; + char *nscat = zend_cstr_concat3( + (const char *) nsptr->href, ns_len, + ":", 1, + cptype, type_len); if ((sdl_type = zend_hash_str_find_ptr(sdl->elements, nscat, len)) != NULL) { ret = sdl_type; @@ -117,13 +115,10 @@ encodePtr get_encoder(sdlPtr sdl, const char *ns, const char *type) size_t type_len = strlen(type); size_t len = ns_len + type_len + 1; - nscat = emalloc(len + 1); - if (ns) { - memcpy(nscat, ns, ns_len); - } - nscat[ns_len] = ':'; - memcpy(nscat+ns_len+1, type, type_len); - nscat[len] = '\0'; + nscat = zend_cstr_concat3( + ns, ns_len, + ":", 1, + type, type_len); enc = get_encoder_ex(sdl, nscat, len); @@ -138,11 +133,10 @@ encodePtr get_encoder(sdlPtr sdl, const char *ns, const char *type) enc_ns_len = sizeof(XSD_NAMESPACE)-1; enc_len = enc_ns_len + type_len + 1; - enc_nscat = emalloc(enc_len + 1); - memcpy(enc_nscat, XSD_NAMESPACE, sizeof(XSD_NAMESPACE)-1); - enc_nscat[enc_ns_len] = ':'; - memcpy(enc_nscat+enc_ns_len+1, type, type_len); - enc_nscat[enc_len] = '\0'; + enc_nscat = zend_cstr_concat3( + XSD_NAMESPACE, enc_ns_len, + ":", 1, + type, type_len); enc = get_encoder_ex(NULL, enc_nscat, enc_len); efree(enc_nscat); @@ -1407,11 +1401,10 @@ static void sdl_deserialize_encoder(encodePtr enc, sdlTypePtr *types, char **in) enc_ns_len = sizeof(XSD_NAMESPACE)-1; enc_len = enc_ns_len + type_len + 1; - enc_nscat = emalloc(enc_len + 1); - memcpy(enc_nscat, XSD_NAMESPACE, sizeof(XSD_NAMESPACE)-1); - enc_nscat[enc_ns_len] = ':'; - memcpy(enc_nscat+enc_ns_len+1, enc->details.type_str, type_len); - enc_nscat[enc_len] = '\0'; + enc_nscat = zend_cstr_concat3( + XSD_NAMESPACE, enc_ns_len, + ":", 1, + enc->details.type_str, type_len); real_enc = get_encoder_ex(NULL, enc_nscat, enc_len); efree(enc_nscat); diff --git a/ext/standard/fsock.c b/ext/standard/fsock.c index e1a8c1868b93..79d45b76396a 100644 --- a/ext/standard/fsock.c +++ b/ext/standard/fsock.c @@ -29,17 +29,10 @@ static size_t php_fsockopen_format_host_port(char **message, const char *prefix, int portlen = snprintf(portbuf, sizeof(portbuf), ":" ZEND_LONG_FMT, port); size_t total_len = prefix_len + host_len + portlen; - char *result = emalloc(total_len + 1); - - if (prefix_len > 0) { - memcpy(result, prefix, prefix_len); - } - memcpy(result + prefix_len, host, host_len); - memcpy(result + prefix_len + host_len, portbuf, portlen); - - result[total_len] = '\0'; - - *message = result; + *message = zend_cstr_concat3( + prefix, prefix_len, + host, host_len, + portbuf, portlen); return total_len; } diff --git a/ext/zip/php_zip.c b/ext/zip/php_zip.c index a41007b7aa89..33b0dadb900f 100644 --- a/ext/zip/php_zip.c +++ b/ext/zip/php_zip.c @@ -1765,10 +1765,7 @@ PHP_METHOD(ZipArchive, addEmptyDir) } if (dirname[dirname_len-1] != '/') { - s=(char *)safe_emalloc(dirname_len, 1, 2); - strcpy(s, dirname); - s[dirname_len] = '/'; - s[dirname_len+1] = '\0'; + s = zend_cstr_append_char(dirname, dirname_len, '/'); } else { s = dirname; } diff --git a/main/SAPI.c b/main/SAPI.c index 62e1c89e4bb9..58e85539d54f 100644 --- a/main/SAPI.c +++ b/main/SAPI.c @@ -369,10 +369,10 @@ SAPI_API size_t sapi_apply_default_charset(char **mimetype, size_t len) if (*mimetype != NULL) { if (*charset && strncmp(*mimetype, "text/", 5) == 0 && strstr(*mimetype, "charset=") == NULL) { newlen = len + (sizeof(";charset=")-1) + strlen(charset); - newtype = emalloc(newlen + 1); - PHP_STRLCPY(newtype, *mimetype, newlen + 1, len); - strlcat(newtype, ";charset=", newlen + 1); - strlcat(newtype, charset, newlen + 1); + newtype = zend_cstr_concat3( + *mimetype, len, + ";charset=", sizeof(";charset=")-1, + charset, strlen(charset)); efree(*mimetype); *mimetype = newtype; return newlen; @@ -878,10 +878,9 @@ SAPI_API int sapi_send_headers(void) SG(sapi_headers).mimetype = default_mimetype; default_header.header_len = sizeof("Content-type: ") - 1 + len; - default_header.header = emalloc(default_header.header_len + 1); - - memcpy(default_header.header, "Content-type: ", sizeof("Content-type: ") - 1); - memcpy(default_header.header + sizeof("Content-type: ") - 1, SG(sapi_headers).mimetype, len + 1); + default_header.header = zend_cstr_concat( + "Content-type: ", sizeof("Content-type: ") - 1, + SG(sapi_headers).mimetype, len); sapi_header_add_op(SAPI_HEADER_ADD, &default_header); } else { diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index 3a8435f9e6cc..827225387487 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -568,15 +568,10 @@ static void sapi_cgi_register_variables(zval *track_vars_array) /* {{{ */ unsigned int path_info_len = path_info ? strlen(path_info) : 0; php_self_len = script_name_len + path_info_len; - php_self = emalloc(php_self_len + 1); - /* Concat script_name and path_info into php_self */ - if (script_name) { - memcpy(php_self, script_name, script_name_len + 1); - } - if (path_info) { - memcpy(php_self + script_name_len, path_info, path_info_len + 1); - } + php_self = zend_cstr_concat( + script_name, script_name_len, + path_info, path_info_len); /* Build the special-case PHP_SELF variable for the CGI version */ if (sapi_module.input_filter(PARSE_SERVER, "PHP_SELF", &php_self, php_self_len, &php_self_len)) { @@ -1235,12 +1230,9 @@ static void init_request_info(void) /* PATH_TRANSLATED = DOCUMENT_ROOT + PATH_INFO */ path_translated_len = l + (env_path_info ? strlen(env_path_info) : 0); - path_translated = (char *) emalloc(path_translated_len + 1); - memcpy(path_translated, env_document_root, l); - if (env_path_info) { - memcpy(path_translated + l, env_path_info, (path_translated_len - l)); - } - path_translated[path_translated_len] = '\0'; + path_translated = zend_cstr_concat( + env_document_root, l, + env_path_info, path_translated_len - l); if (orig_path_translated) { FCGI_PUTENV(request, "ORIG_PATH_TRANSLATED", orig_path_translated); } @@ -1254,12 +1246,9 @@ static void init_request_info(void) int path_translated_len = ptlen + (env_path_info ? strlen(env_path_info) : 0); char *path_translated = NULL; - path_translated = (char *) emalloc(path_translated_len + 1); - memcpy(path_translated, pt, ptlen); - if (env_path_info) { - memcpy(path_translated + ptlen, env_path_info, path_translated_len - ptlen); - } - path_translated[path_translated_len] = '\0'; + path_translated = zend_cstr_concat( + pt, ptlen, + env_path_info, path_translated_len - ptlen); if (orig_path_translated) { FCGI_PUTENV(request, "ORIG_PATH_TRANSLATED", orig_path_translated); } diff --git a/sapi/phpdbg/phpdbg_utils.c b/sapi/phpdbg/phpdbg_utils.c index 831595939f64..c259a0d1aced 100644 --- a/sapi/phpdbg/phpdbg_utils.c +++ b/sapi/phpdbg/phpdbg_utils.c @@ -672,11 +672,7 @@ char *phpdbg_short_zval_print(zval *zv, int maxlen) /* {{{ */ /* Make sure it looks like a float */ if (zend_finite(Z_DVAL_P(zv)) && !strchr(decode, '.')) { size_t len = strlen(decode); - char *decode2 = emalloc(len + strlen(".0") + 1); - memcpy(decode2, decode, len); - decode2[len] = '.'; - decode2[len+1] = '0'; - decode2[len+2] = '\0'; + char *decode2 = zend_cstr_concat(decode, len, ".0", strlen(".0")); efree(decode); decode = decode2; } diff --git a/win32/registry.c b/win32/registry.c index 3597d57c85a4..8445fffd11b1 100644 --- a/win32/registry.c +++ b/win32/registry.c @@ -49,9 +49,9 @@ static int OpenPhpRegistryKey(char* sub_key, HKEY *hKey) LONG ret; main_key_len = strlen(*key_name); - reg_key = emalloc(main_key_len + sub_key_len + 1); - memcpy(reg_key, *key_name, main_key_len); - memcpy(reg_key + main_key_len, sub_key, sub_key_len + 1); + reg_key = zend_cstr_concat( + *key_name, main_key_len, + sub_key, sub_key_len); ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_key, 0, KEY_READ, hKey); efree(reg_key); From 16da1f51bab2c1764d87ac7d891c0cc10e491da5 Mon Sep 17 00:00:00 2001 From: Florian Engelhardt Date: Fri, 24 Jul 2026 20:07:06 +0200 Subject: [PATCH 7/8] Rather than erroring, disable JIT when pthread protection is unavailable (GH-22853) --- ext/opcache/jit/zend_jit.c | 15 ++++++++++----- ext/opcache/tests/jit/gh14267_001.phpt | 5 ++++- ext/opcache/tests/jit/gh16393.phpt | 7 +++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index cb2791fb45ac..6533f00ede6e 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -3724,6 +3724,16 @@ int zend_jit_check_support(void) { int i; +#ifdef ZEND_JIT_USE_APPLE_MAP_JIT + if (!pthread_jit_write_protect_supported_np()) { + zend_accel_error(ACCEL_LOG_WARNING, + "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support. JIT disabled."); + JIT_G(enabled) = 0; + JIT_G(on) = 0; + return FAILURE; + } +#endif + if (zend_execute_ex != execute_ex) { if (zend_dtrace_enabled) { zend_error(E_WARNING, "JIT is incompatible with DTrace. JIT disabled."); @@ -3792,11 +3802,6 @@ void zend_jit_startup(void *buf, size_t size, bool reattached) "Unable to share JIT buffer across fork using minherit(): %s (%d)", strerror(error), error); } - if (!pthread_jit_write_protect_supported_np()) { - munmap(buf, size); - zend_accel_error_noreturn(ACCEL_LOG_FATAL, - "Apple Silicon ZTS JIT requires pthread_jit_write_protect_np() support"); - } #endif dasm_buf = buf; diff --git a/ext/opcache/tests/jit/gh14267_001.phpt b/ext/opcache/tests/jit/gh14267_001.phpt index 52be177b9155..2e23016f899b 100644 --- a/ext/opcache/tests/jit/gh14267_001.phpt +++ b/ext/opcache/tests/jit/gh14267_001.phpt @@ -9,7 +9,10 @@ opcache.jit_buffer_size=32M opcache --FILE-- ===DONE=== --EXPECT-- diff --git a/ext/opcache/tests/jit/gh16393.phpt b/ext/opcache/tests/jit/gh16393.phpt index c93b06fda8ce..e90fb53e21a2 100644 --- a/ext/opcache/tests/jit/gh16393.phpt +++ b/ext/opcache/tests/jit/gh16393.phpt @@ -7,7 +7,10 @@ opcache.jit=1215 opcache.jit_buffer_size=64M --FILE-- --EXPECTF-- -Warning: Undefined variable $test in %sgh16393.php on line 6 +Warning: Undefined variable $test in %sgh16393.php on line 9 From de5a5820efbc98906f01cd9b8ff2759ab8c29172 Mon Sep 17 00:00:00 2001 From: Calvin Buckley Date: Fri, 24 Jul 2026 15:14:46 -0300 Subject: [PATCH 8/8] Use `php_pollfd_for_ms` more consistently for single fd polling (#22872) * ftp: For single fd poll calls, use php_pollfd_for_ms * pgsql: Use php_pollfd_for_ms in compat shim This is only used on pre-libpq 17. * fastcgi: Use php_pollfd_for_ms for single fd This also handles the case where poll isn't available (does that even matter nowadays?) * io: Use php_pollfd_for_ms for single fd poll * cli: Convert to php_pollfd_for from select This is not a poll call, but it is functionally the same as one; convert it to the single fd polling function which is clearer to read. --- ext/ftp/ftp.c | 36 ++++++++-------------------------- ext/pgsql/pgsql.c | 13 ++++--------- main/fastcgi.c | 39 ++----------------------------------- main/io/php_io.c | 7 +------ main/io/php_io_copy_linux.c | 6 +----- sapi/cli/php_cli.c | 16 +-------------- 6 files changed, 17 insertions(+), 100 deletions(-) diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 0035e8508da1..20a67950627b 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -308,14 +308,9 @@ bool ftp_login(ftpbuf_t *ftp, const char *user, const size_t user_len, const cha case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: { - php_pollfd p; - int i; + int i, events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT; - p.fd = ftp->fd; - p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT; - p.revents = 0; - - i = php_poll2(&p, 1, 300); + i = php_pollfd_for_ms(ftp->fd, events, 300); retry = i > 0; } @@ -1388,14 +1383,9 @@ static int single_send(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t size) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_CONNECT: { - php_pollfd p; - int i; - - p.fd = fd; - p.events = POLLOUT; - p.revents = 0; + int i, events = POLLOUT; - i = php_poll2(&p, 1, 300); + i = php_pollfd_for_ms(fd, events, 300); retry = i > 0; } @@ -1521,14 +1511,9 @@ static int my_recv(ftpbuf_t *ftp, php_socket_t s, void *buf, size_t len) case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_CONNECT: { - php_pollfd p; - int i; + int i, events = POLLIN|POLLPRI; - p.fd = fd; - p.events = POLLIN|POLLPRI; - p.revents = 0; - - i = php_poll2(&p, 1, 300); + i = php_pollfd_for_ms(fd, events, 300); retry = i > 0; } @@ -1825,14 +1810,9 @@ static databuf_t* data_accept(databuf_t *data, ftpbuf_t *ftp) case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: { - php_pollfd p; - int i; - - p.fd = data->fd; - p.events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT; - p.revents = 0; + int i, events = (err == SSL_ERROR_WANT_READ) ? (POLLIN|POLLPRI) : POLLOUT; - i = php_poll2(&p, 1, 300); + i = php_pollfd_for_ms(data->fd, events, 300); retry = i > 0; } diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 87ccbe3424a7..29ef4855a122 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -448,19 +448,14 @@ static int PQsocketPoll(int socket, int read, int write, time_t timeout) if (!read && !write) return 0; - php_pollfd fd; - int ts = -1; - - fd.fd = socket; - fd.events = POLLERR; - fd.revents = 0; + int ts = -1, events = 0; if (read) { - fd.events |= POLLIN; + events |= POLLIN; } if (write) { - fd.events |= POLLOUT; + events |= POLLOUT; } if (timeout != (time_t)ts) { @@ -473,7 +468,7 @@ static int PQsocketPoll(int socket, int read, int write, time_t timeout) } } - return php_poll2(&fd, 1, ts); + return php_pollfd_for_ms(socket, events, ts); } #endif diff --git a/main/fastcgi.c b/main/fastcgi.c index 02ef614f4639..abd558928036 100644 --- a/main/fastcgi.c +++ b/main/fastcgi.c @@ -68,15 +68,6 @@ static int is_impersonate = 0; # include # include -# if defined(HAVE_POLL_H) && defined(HAVE_POLL) -# include -# elif defined(HAVE_SYS_POLL_H) && defined(HAVE_POLL) -# include -# endif -# if defined(HAVE_SYS_SELECT_H) -# include -# endif - #ifndef INADDR_NONE #define INADDR_NONE ((unsigned long) -1) #endif @@ -1426,42 +1417,16 @@ int fcgi_accept_request(fcgi_request *req) break; #else if (req->fd >= 0) { -#if defined(HAVE_POLL) - struct pollfd fds; int ret; - fds.fd = req->fd; - fds.events = POLLIN; - fds.revents = 0; do { errno = 0; - ret = poll(&fds, 1, 5000); + ret = php_pollfd_for_ms(req->fd, POLLIN, 5000); } while (ret < 0 && errno == EINTR); - if (ret > 0 && (fds.revents & POLLIN)) { + if (ret & POLLIN) { break; } fcgi_close(req, 1, 0); -#else - if (req->fd < FD_SETSIZE) { - struct timeval tv = {5,0}; - fd_set set; - int ret; - - FD_ZERO(&set); - FD_SET(req->fd, &set); - do { - errno = 0; - ret = select(req->fd + 1, &set, NULL, NULL, &tv) >= 0; - } while (ret < 0 && errno == EINTR); - if (ret > 0 && FD_ISSET(req->fd, &set)) { - break; - } - fcgi_close(req, 1, 0); - } else { - fcgi_log(FCGI_ERROR, "Too many open file descriptors. FD_SETSIZE limit exceeded."); - fcgi_close(req, 1, 0); - } -#endif } #endif } diff --git a/main/io/php_io.c b/main/io/php_io.c index bd45ec888790..6954cd4bfde5 100644 --- a/main/io/php_io.c +++ b/main/io/php_io.c @@ -23,7 +23,6 @@ #include #else #include -#include #endif static php_io php_io_instance = { @@ -106,13 +105,9 @@ static int php_io_generic_wait_for_data(php_io_fd *fd) ? -1 : (int) (fd->timeout.tv_sec * 1000 + fd->timeout.tv_usec / 1000); - struct pollfd pfd; - pfd.fd = fd->fd; - pfd.events = POLLIN; - int ret; do { - ret = poll(&pfd, 1, timeout_ms); + ret = php_pollfd_for_ms(fd->fd, POLLIN, timeout_ms); } while (ret == -1 && errno == EINTR); return ret; diff --git a/main/io/php_io_copy_linux.c b/main/io/php_io_copy_linux.c index 9a1810349d3d..af92b14a64d3 100644 --- a/main/io/php_io_copy_linux.c +++ b/main/io/php_io_copy_linux.c @@ -54,13 +54,9 @@ static inline int php_io_linux_wait_for_data(php_io_fd *fd) timeout_ms = ptimeout->tv_sec * 1000 + ptimeout->tv_usec / 1000; } - struct pollfd pfd; - pfd.fd = fd->fd; - pfd.events = POLLIN; - int ret; do { - ret = poll(&pfd, 1, timeout_ms); + ret = php_pollfd_for_ms(fd->fd, POLLIN, timeout_ms); } while (ret == -1 && errno == EINTR); return ret; diff --git a/sapi/cli/php_cli.c b/sapi/cli/php_cli.c index e5e573d1533c..9095fb10dd41 100644 --- a/sapi/cli/php_cli.c +++ b/sapi/cli/php_cli.c @@ -78,12 +78,6 @@ #include "php_cli_process_title.h" #include "php_cli_process_title_arginfo.h" -#ifndef PHP_WIN32 -# define php_select(m, r, w, e, t) select(m, r, w, e, t) -#else -# include "win32/select.h" -#endif - #if defined(PHP_WIN32) && defined(HAVE_OPENSSL_EXT) # include "openssl/applink.c" #endif @@ -218,20 +212,12 @@ static void print_extensions(void) /* {{{ */ #ifdef PHP_WRITE_STDOUT static inline bool sapi_cli_select(php_socket_t fd) { - fd_set wfd; struct timeval tv; - int ret; - - FD_ZERO(&wfd); - - PHP_SAFE_FD_SET(fd, &wfd); tv.tv_sec = (long)FG(default_socket_timeout); tv.tv_usec = 0; - ret = php_select(fd+1, NULL, &wfd, NULL, &tv); - - return ret != -1; + return php_pollfd_for(fd, POLLOUT, &tv) != -1; } #endif