diff --git a/NEWS b/NEWS index 7dd8530af905..de11135f0d3b 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,12 @@ PHP NEWS - Core: . Implemented partial function application RFC. (Arnaud) + . Fixed bug GH-22263 (reset typed property default on every unserialize + failure path). (David Carlier) + +- DOM: + . Fixed bug GH-22825 (DOMElement::setAttribute() fails silently when the DTD + declares a default value for the attribute). (iliaal) - GMP: . Fixed GMP power and shift operators to reject GMP right operands outside diff --git a/UPGRADING b/UPGRADING index 1b7cda4b8612..077a7faf6562 100644 --- a/UPGRADING +++ b/UPGRADING @@ -19,6 +19,11 @@ PHP 8.6 UPGRADE NOTES 1. Backward Incompatible Changes ======================================== +- Core: + . ??/empty() on a magic property no longer call __get() when __isset() + has materialised the property by writing into the property table. + The freshly-written value is returned directly. isset() is unaffected. + - COM . It is no longer possible to clone variant objects, this is because the cloning behaviour was ill defined. @@ -566,6 +571,8 @@ PHP 8.6 UPGRADE NOTES - Core: . In case of a hard OOM PHP now calls abort() instead of exit(1), changing the exit code to 134 and possibly creating a core dump. + . The PHP_OS_FAMILY constant has an AIX value for when running on AIX or + IBM i via PASE. ======================================== 14. Performance Improvements diff --git a/Zend/tests/magic_methods/gh12695.phpt b/Zend/tests/magic_methods/gh12695.phpt new file mode 100644 index 000000000000..957f474fb6a1 --- /dev/null +++ b/Zend/tests/magic_methods/gh12695.phpt @@ -0,0 +1,65 @@ +--TEST-- +GH-12695: ?? on unset property does not call __get() when __isset() materialized the property +--FILE-- +$n = 123; + return true; + } +} + +echo "Dynamic property materialised in __isset, then `??`:\n"; +$a = new A; +var_dump($a->foo ?? 'fallback'); + +echo "\nSame on a declared (unset) property:\n"; +class B { + public int $x = 99; + public function __get($n) { + throw new Exception("__get must not be called when __isset materialised the property"); + } + public function __isset($n) { + echo " __isset($n)\n"; + $this->$n = 7; + return true; + } +} +$b = new B; +unset($b->x); +var_dump($b->x ?? 'fallback'); + +echo "\nWhen __isset() materialises the property to null, `??` falls back:\n"; +#[AllowDynamicProperties] +class D { + public function __get($n) { + throw new Exception("__get must not be called when __isset materialised the property"); + } + public function __isset($n) { + echo " __isset($n)\n"; + $this->$n = null; + return true; + } +} +$d = new D; +var_dump($d->foo ?? 'fallback'); + +?> +--EXPECT-- +Dynamic property materialised in __isset, then `??`: + __isset(foo) +int(123) + +Same on a declared (unset) property: + __isset(x) +int(7) + +When __isset() materialises the property to null, `??` falls back: + __isset(foo) +string(8) "fallback" diff --git a/Zend/tests/magic_methods/gh12695_empty.phpt b/Zend/tests/magic_methods/gh12695_empty.phpt new file mode 100644 index 000000000000..99806820cc6f --- /dev/null +++ b/Zend/tests/magic_methods/gh12695_empty.phpt @@ -0,0 +1,55 @@ +--TEST-- +GH-12695: empty() on unset property does not call __get() when __isset() materialized the property +--FILE-- +$n = $GLOBALS['next_value']; + return true; + } +} + +echo "empty() when __isset materialised a truthy value: __get is not called, empty=false:\n"; +$GLOBALS['next_value'] = 7; +$a = new A; +var_dump(empty($a->foo)); + +echo "\nempty() when __isset materialised a falsy value: __get is not called, empty=true:\n"; +$GLOBALS['next_value'] = 0; +$a = new A; +var_dump(empty($a->bar)); + +echo "\nempty() with no materialization: __get is still called (legacy path preserved):\n"; +class B { + public function __get($n) { + echo " __get($n)\n"; + return 'value'; + } + public function __isset($n) { + echo " __isset($n)\n"; + return true; + } +} +$b = new B; +var_dump(empty($b->any)); + +?> +--EXPECT-- +empty() when __isset materialised a truthy value: __get is not called, empty=false: + __isset(foo) +bool(false) + +empty() when __isset materialised a falsy value: __get is not called, empty=true: + __isset(bar) +bool(true) + +empty() with no materialization: __get is still called (legacy path preserved): + __isset(any) + __get(any) +bool(false) diff --git a/Zend/tests/magic_methods/gh12695_no_materialization.phpt b/Zend/tests/magic_methods/gh12695_no_materialization.phpt new file mode 100644 index 000000000000..700b605e2984 --- /dev/null +++ b/Zend/tests/magic_methods/gh12695_no_materialization.phpt @@ -0,0 +1,65 @@ +--TEST-- +GH-12695: __get() invocation is based on __isset()'s return value +--FILE-- +any ?? 'fallback'); + +echo "\n`??` when __isset=true and __get returns null: __get is called and fallback is used:\n"; +class D { + public function __get($n) { + echo " __get($n)\n"; + return null; + } + public function __isset($n) { + echo " __isset($n)\n"; + return true; + } +} +$d = new D; +var_dump($d->any ?? 'fallback'); + +echo "\n`??` when __isset returns false: __get is not called:\n"; +class E { + public function __get($n) { + throw new Exception("__get must not be called when __isset returned false"); + } + public function __isset($n) { + echo " __isset($n)\n"; + return false; + } +} +$e = new E; +var_dump($e->any ?? 'fallback'); + +?> +--EXPECT-- +`??` when __isset=true and __get returns a value: __get is called: + __isset(any) + __get(any) +string(8) "from-get" + +`??` when __isset=true and __get returns null: __get is called and fallback is used: + __isset(any) + __get(any) +string(8) "fallback" + +`??` when __isset returns false: __get is not called: + __isset(any) +string(8) "fallback" diff --git a/Zend/tests/magic_methods/gh12695_object_released_in_isset.phpt b/Zend/tests/magic_methods/gh12695_object_released_in_isset.phpt new file mode 100644 index 000000000000..911ffde637a9 --- /dev/null +++ b/Zend/tests/magic_methods/gh12695_object_released_in_isset.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-12695: Object freed by __isset() during materialization +--FILE-- +prop = 'materialised'; + return true; + } +} + +$obj = new C(); +unset($obj->prop); +var_dump($obj->prop ?? 'fb'); + +?> +--EXPECT-- +string(12) "materialised" diff --git a/Zend/tests/partial_application/default_const_expr.phpt b/Zend/tests/partial_application/default_const_expr.phpt new file mode 100644 index 000000000000..12d25adedeff --- /dev/null +++ b/Zend/tests/partial_application/default_const_expr.phpt @@ -0,0 +1,116 @@ +--TEST-- +PFA: constant-expression default for a skipped optional parameter +--FILE-- +name, $c]; +} + +$p = f(a: 10, c: ?); +var_dump($p(99)); + +$v = variadic(a: 10, c: ?, foo: 1); +var_dump($v(99)); + +$q = C::m(a: 1, c: ?); +var_dump($q(9)); + +$r = g(a: 1, c: ?); +var_dump($r(9)); + +$l = late(a: 1, c: ?); +define('App\LATE', 'defined after the partial was created'); +var_dump($l(9)); + +define('GLOBAL_ONLY', 'global fallback'); +$s = fallback(a: 1, c: ?); +var_dump($s(9)); + +?> +--EXPECT-- +array(3) { + [0]=> + int(10) + [1]=> + int(7) + [2]=> + int(99) +} +array(4) { + [0]=> + int(10) + [1]=> + int(7) + [2]=> + int(99) + [3]=> + array(1) { + ["foo"]=> + int(1) + } +} +array(3) { + [0]=> + int(1) + [1]=> + int(42) + [2]=> + int(9) +} +array(3) { + [0]=> + int(1) + [1]=> + string(1) "A" + [2]=> + int(9) +} +array(3) { + [0]=> + int(1) + [1]=> + string(37) "defined after the partial was created" + [2]=> + int(9) +} +array(3) { + [0]=> + int(1) + [1]=> + string(15) "global fallback" + [2]=> + int(9) +} diff --git a/Zend/tests/unserialize_typed_prop_reset_on_failure.phpt b/Zend/tests/unserialize_typed_prop_reset_on_failure.phpt new file mode 100644 index 000000000000..2380c8e5550e --- /dev/null +++ b/Zend/tests/unserialize_typed_prop_reset_on_failure.phpt @@ -0,0 +1,37 @@ +--TEST-- +unserialize() resets a typed property to its default on every failure path +--FILE-- +getPrevious()) { + printf("%s: %s\n", $e::class, $e->getMessage()); + } +} + +/* By-ref type violation: the slot is reset to its default. */ +class C { public array $a; } +try { + var_dump(unserialize('O:1:"C":1:{s:1:"a";R:1;}')); +} catch (\Throwable $e) { + printf("%s: %s\n", $e::class, $e->getMessage()); +} +echo "OK\n"; +?> +--EXPECTF-- +Warning: unserialize(): Error at offset %d of %d bytes in %s on line %d +TypeError: %s +OK diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f44784239533..bb24d73fb240 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -11519,12 +11519,25 @@ static void zend_compile_array(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_emit_fetch_constant(znode *result, zend_string *resolved_name, bool unqualified_in_namespace) +{ + zend_op *opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL); + opline->op2_type = IS_CONST; + + if (unqualified_in_namespace) { + opline->op1.num = IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE; + opline->op2.constant = zend_add_const_name_literal(resolved_name, true); + } else { + opline->op1.num = 0; + opline->op2.constant = zend_add_const_name_literal(resolved_name, false); + } + opline->extended_value = zend_alloc_cache_slot(); +} + static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */ { zend_ast *name_ast = ast->child[0]; - zend_op *opline; - bool is_fully_qualified; zend_string *orig_name = zend_ast_get_str(name_ast); zend_string *resolved_name = zend_resolve_const_name(orig_name, name_ast->attr, &is_fully_qualified); @@ -11553,22 +11566,19 @@ static void zend_compile_const(znode *result, const zend_ast *ast) /* {{{ */ return; } - opline = zend_emit_op_tmp(result, ZEND_FETCH_CONSTANT, NULL, NULL); - opline->op2_type = IS_CONST; - - if (is_fully_qualified || !FC(current_namespace)) { - opline->op1.num = 0; - opline->op2.constant = zend_add_const_name_literal( - resolved_name, false); - } else { - opline->op1.num = IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE; - opline->op2.constant = zend_add_const_name_literal( - resolved_name, true); - } - opline->extended_value = zend_alloc_cache_slot(); + zend_emit_fetch_constant(result, resolved_name, + !is_fully_qualified && FC(current_namespace)); } /* }}} */ +static void zend_compile_constant(znode *result, zend_ast *ast) +{ + zend_string *name = zend_ast_get_constant_name(ast); + + zend_emit_fetch_constant(result, zend_string_copy(name), + (ast->attr & IS_CONSTANT_UNQUALIFIED_IN_NAMESPACE) != 0); +} + static void zend_compile_class_const(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *class_ast; @@ -12436,6 +12446,9 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_CONST: zend_compile_const(result, ast); return; + case ZEND_AST_CONSTANT: + zend_compile_constant(result, ast); + return; case ZEND_AST_CLASS_CONST: zend_compile_class_const(result, ast); return; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 7ccd9f26190a..313113d7dc28 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -934,6 +934,35 @@ ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int } zval_ptr_dtor(&tmp_result); + + /* __isset() may have materialised the property by writing into + * the property table. Re-check it before deferring to __get(), + * so the freshly-written value is returned directly without a + * redundant __get() call (GH-12695). The value is copied into + * `rv` because the property table can be freed by the OBJ_RELEASE + * below (e.g. when __isset() drops the last external reference + * to the object). */ + if (IS_VALID_PROPERTY_OFFSET(property_offset)) { + retval = OBJ_PROP(zobj, property_offset); + if (Z_TYPE_P(retval) != IS_UNDEF) { + ZVAL_COPY(rv, retval); + retval = rv; + OBJ_RELEASE(zobj); + goto exit; + } + } else if (IS_DYNAMIC_PROPERTY_OFFSET(property_offset)) { + if (zobj->properties != NULL) { + retval = zend_hash_find(zobj->properties, name); + if (retval) { + ZVAL_COPY(rv, retval); + retval = rv; + OBJ_RELEASE(zobj); + goto exit; + } + } + } + retval = &EG(uninitialized_zval); + if (zobj->ce->__get && !((*guard) & IN_GET)) { goto call_getter; } @@ -2498,7 +2527,20 @@ ZEND_API int zend_std_has_property(zend_object *zobj, zend_string *name, int has result = zend_is_true(&rv); zval_ptr_dtor(&rv); if (has_set_exists == ZEND_PROPERTY_NOT_EMPTY && result) { - if (EXPECTED(!EG(exception)) && zobj->ce->__get && !((*guard) & IN_GET)) { + /* GH-12695, see above. */ + zval *prop = NULL; + if (IS_VALID_PROPERTY_OFFSET(property_offset)) { + prop = OBJ_PROP(zobj, property_offset); + if (Z_TYPE_P(prop) == IS_UNDEF) { + prop = NULL; + } + } else if (IS_DYNAMIC_PROPERTY_OFFSET(property_offset) + && zobj->properties != NULL) { + prop = zend_hash_find(zobj->properties, name); + } + if (prop) { + result = i_zend_is_true(prop); + } else if (EXPECTED(!EG(exception)) && zobj->ce->__get && !((*guard) & IN_GET)) { (*guard) |= IN_GET; zend_std_call_getter(zobj, name, &rv); (*guard) &= ~IN_GET; diff --git a/Zend/zend_partial.c b/Zend/zend_partial.c index ef335cd805c3..e7a3e1c92b58 100644 --- a/Zend/zend_partial.c +++ b/Zend/zend_partial.c @@ -588,6 +588,7 @@ static zend_ast *zp_compile_forwarding_call( if (Z_TYPE(default_value) == IS_CONSTANT_AST) { /* Must dup AST because we are going to destroy it */ default_value_ast = zend_ast_dup(Z_ASTVAL(default_value)); + zval_ptr_dtor_nogc(&default_value); } else { default_value_ast = zend_ast_create_zval(&default_value); } diff --git a/ext/dom/element.c b/ext/dom/element.c index 8e6102e9658e..71fa39b59b5a 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -464,6 +464,8 @@ PHP_METHOD(DOMElement, setAttribute) break; case XML_NAMESPACE_DECL: RETURN_FALSE; + case XML_ATTRIBUTE_DECL: + break; default: ZEND_UNREACHABLE(); } } @@ -591,6 +593,8 @@ static bool dom_remove_attribute(xmlNodePtr thisp, xmlNodePtr attrp) break; } + case XML_ATTRIBUTE_DECL: + return false; default: ZEND_UNREACHABLE(); } return true; @@ -720,7 +724,11 @@ static void dom_element_set_attribute_node_common(INTERNAL_FUNCTION_PARAMETERS, existattrp = xmlHasProp(nodep, attrp->name); } - if (existattrp != NULL && existattrp->type != XML_ATTRIBUTE_DECL) { + if (existattrp != NULL && existattrp->type == XML_ATTRIBUTE_DECL) { + existattrp = NULL; + } + + if (existattrp != NULL) { if ((oldobj = php_dom_object_get_data((xmlNodePtr) existattrp)) != NULL && ((php_libxml_node_ptr *)oldobj->ptr)->node == (xmlNodePtr) attrp) { @@ -1933,8 +1941,7 @@ PHP_METHOD(DOMElement, toggleAttribute) /* Step 5 */ if (force_is_null || !force) { - dom_remove_attribute(thisp, attribute); - retval = false; + retval = !dom_remove_attribute(thisp, attribute); goto out; } diff --git a/ext/dom/tests/gh22825.phpt b/ext/dom/tests/gh22825.phpt new file mode 100644 index 000000000000..987ed57a1454 --- /dev/null +++ b/ext/dom/tests/gh22825.phpt @@ -0,0 +1,98 @@ +--TEST-- +GH-22825 (DOMElement::setAttribute() reaches EMPTY_SWITCH_DEFAULT_CASE() with DTD #FIXED default attributes) +--EXTENSIONS-- +dom +--FILE-- +', '', 'A'], + ['', '', 'A'], + ['', '', 'p:A'], +]; + +function element(string $attlist, string $root): DOMElement { + $doc = new DOMDocument(); + $doc->loadXML("$root"); + return $doc->documentElement; +} + +foreach ($cases as [$attlist, $root, $name]) { + echo "--- $attlist ---\n"; + + $el = element($attlist, $root); + echo "hasAttribute: "; + var_dump($el->hasAttribute($name)); + echo "getAttribute: "; + var_dump($el->getAttribute($name)); + + $el = element($attlist, $root); + $result = $el->setAttribute($name, 'v'); + echo "setAttribute: ", is_object($result) ? $result::class : var_export($result, true), "\n"; + echo "after setAttribute: ", trim($el->ownerDocument->saveXML($el)), "\n"; + + $el = element($attlist, $root); + echo "removeAttribute: "; + var_dump($el->removeAttribute($name)); + echo "still present after removeAttribute: "; + var_dump($el->hasAttribute($name)); + + $el = element($attlist, $root); + echo "toggleAttribute(false): "; + var_dump($el->toggleAttribute($name, false)); + echo "still present after toggleAttribute(false): "; + var_dump($el->hasAttribute($name)); + + $el = element($attlist, $root); + echo "toggleAttribute(true): "; + var_dump($el->toggleAttribute($name, true)); + echo "still present after toggleAttribute(true): "; + var_dump($el->hasAttribute($name)); + + $el = element($attlist, $root); + $attr = $el->ownerDocument->createAttribute($name); + $attr->value = 'z'; + echo "setAttributeNode: "; + var_dump($el->setAttributeNode($attr)); + echo "after setAttributeNode: ", trim($el->ownerDocument->saveXML($el)), "\n"; +} +?> +--EXPECT-- +--- --- +hasAttribute: bool(true) +getAttribute: string(1) "d" +setAttribute: DOMAttr +after setAttribute: +removeAttribute: bool(false) +still present after removeAttribute: bool(true) +toggleAttribute(false): bool(true) +still present after toggleAttribute(false): bool(true) +toggleAttribute(true): bool(true) +still present after toggleAttribute(true): bool(true) +setAttributeNode: NULL +after setAttributeNode: +--- --- +hasAttribute: bool(true) +getAttribute: string(1) "d" +setAttribute: DOMAttr +after setAttribute: +removeAttribute: bool(false) +still present after removeAttribute: bool(true) +toggleAttribute(false): bool(true) +still present after toggleAttribute(false): bool(true) +toggleAttribute(true): bool(true) +still present after toggleAttribute(true): bool(true) +setAttributeNode: NULL +after setAttributeNode: +--- --- +hasAttribute: bool(true) +getAttribute: string(1) "d" +setAttribute: DOMAttr +after setAttribute: +removeAttribute: bool(false) +still present after removeAttribute: bool(true) +toggleAttribute(false): bool(true) +still present after toggleAttribute(false): bool(true) +toggleAttribute(true): bool(true) +still present after toggleAttribute(true): bool(true) +setAttributeNode: NULL +after setAttributeNode: diff --git a/ext/opcache/jit/tls/zend_jit_tls_x86_64.c b/ext/opcache/jit/tls/zend_jit_tls_x86_64.c index 1b2730c4be92..6c58dbc1c43d 100644 --- a/ext/opcache/jit/tls/zend_jit_tls_x86_64.c +++ b/ext/opcache/jit/tls/zend_jit_tls_x86_64.c @@ -100,7 +100,7 @@ zend_result zend_jit_resolve_tsrm_ls_cache_offsets( "leaq _tsrm_ls_cache@tlsgd(%%rip), %%rdi\n" ".word 0x6666\n" "rex64\n" - "call __tls_get_addr\n" + "call __tls_get_addr@PLT\n" /* Load thread pointer address */ "movq %%fs:0, %%rsi\n" : "=a" (addr), "=b" (code), "=S" (thread_pointer) diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index ca82b1b09322..25c9e6917e43 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -276,8 +276,13 @@ static int pdo_sqlite_stmt_get_col( int64_t i = sqlite3_column_int64(S->stmt, colno); #if SIZEOF_ZEND_LONG < 8 if (i > ZEND_LONG_MAX || i < ZEND_LONG_MIN) { + const char *text = (const char *) sqlite3_column_text(S->stmt, colno); + if (UNEXPECTED(!text)) { + pdo_sqlite_error_stmt(stmt); + return 0; + } ZVAL_STRINGL(result, - (char *) sqlite3_column_text(S->stmt, colno), + text, sqlite3_column_bytes(S->stmt, colno)); return 1; } @@ -295,10 +300,15 @@ static int pdo_sqlite_stmt_get_col( sqlite3_column_blob(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); return 1; - default: - ZVAL_STRINGL_FAST(result, - (char *) sqlite3_column_text(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + default: { + const char *text = (const char *) sqlite3_column_text(S->stmt, colno); + if (UNEXPECTED(!text)) { + pdo_sqlite_error_stmt(stmt); + return 0; + } + ZVAL_STRINGL_FAST(result, text, sqlite3_column_bytes(S->stmt, colno)); return 1; + } } } diff --git a/ext/standard/io_poll.c b/ext/standard/io_poll.c index 837470a9f753..ca363c7b37d9 100644 --- a/ext/standard/io_poll.c +++ b/ext/standard/io_poll.c @@ -44,6 +44,8 @@ static zend_object_handlers php_io_poll_context_object_handlers; static zend_object_handlers php_io_poll_watcher_object_handlers; static zend_object_handlers php_io_poll_handle_object_handlers; +typedef struct php_io_poll_context_object php_io_poll_context_object; + /* Watcher object structure */ typedef struct php_io_poll_watcher_object { php_poll_handle_object *handle; @@ -51,16 +53,16 @@ typedef struct php_io_poll_watcher_object { uint32_t triggered_events; zval data; bool active; - php_poll_ctx *poll_ctx; /* Back reference to poll context */ + php_io_poll_context_object *context; /* Back reference to Context object */ zend_object std; } php_io_poll_watcher_object; /* Context object structure */ -typedef struct php_io_poll_context_object { +struct php_io_poll_context_object { php_poll_ctx *ctx; HashTable *watchers; /* Maps handle pointer -> watcher object */ zend_object std; -} php_io_poll_context_object; +}; /* Stream poll handle specific data */ typedef struct php_stream_poll_handle_data { @@ -251,7 +253,7 @@ static zend_object *php_io_poll_watcher_create_object(zend_class_entry *ce) intern->watched_events = 0; intern->triggered_events = 0; intern->active = false; - intern->poll_ctx = NULL; + intern->context = NULL; ZVAL_NULL(&intern->data); return &intern->std; @@ -293,7 +295,7 @@ static void php_io_poll_context_free_object(zend_object *obj) ZEND_HASH_FOREACH_VAL(intern->watchers, zval *zv) { php_io_poll_watcher_object *watcher = PHP_POLL_WATCHER_OBJ_FROM_ZOBJ(Z_OBJ_P(zv)); watcher->active = false; - watcher->poll_ctx = NULL; + watcher->context = NULL; } ZEND_HASH_FOREACH_END(); } @@ -349,7 +351,7 @@ static zend_always_inline zend_ulong php_io_poll_compute_ptr_key(void *ptr) static zend_result php_io_poll_watcher_modify_events( php_io_poll_watcher_object *watcher, uint32_t events) { - if (!watcher->active || !watcher->poll_ctx) { + if (!watcher->active || !watcher->context) { zend_throw_exception( php_io_poll_inactive_watcher_class_entry, "Cannot modify inactive watcher", 0); return FAILURE; @@ -363,8 +365,9 @@ static zend_result php_io_poll_watcher_modify_events( } /* Modify in poll context */ - if (php_poll_modify(watcher->poll_ctx, (int) fd, events, watcher) != SUCCESS) { - php_poll_error err = php_poll_get_error(watcher->poll_ctx); + php_poll_ctx *poll_ctx = watcher->context->ctx; + if (php_poll_modify(poll_ctx, (int) fd, events, watcher) != SUCCESS) { + php_poll_error err = php_poll_get_error(poll_ctx); php_io_poll_throw_failed_operation(php_io_poll_failed_watcher_mod_class_entry, "Failed to modify watcher in polling system", err); return FAILURE; @@ -632,19 +635,27 @@ PHP_METHOD(Io_Poll_Watcher, remove) php_io_poll_watcher_object *intern = PHP_POLL_WATCHER_OBJ_FROM_ZV(getThis()); - if (!intern->active || !intern->poll_ctx) { + if (!intern->active || !intern->context) { zend_throw_exception( php_io_poll_inactive_watcher_class_entry, "Cannot remove inactive watcher", 0); RETURN_THROWS(); } + php_io_poll_context_object *context = intern->context; + php_poll_ctx *poll_ctx = context->ctx; + HashTable *watchers = context->watchers; + zend_ulong hash_key = php_io_poll_compute_ptr_key(intern->handle); php_socket_t fd = php_poll_handle_get_fd(intern->handle); if (fd != SOCK_ERR) { - php_poll_remove(intern->poll_ctx, (int) fd); + php_poll_remove(poll_ctx, (int) fd); } intern->active = false; - intern->poll_ctx = NULL; + intern->context = NULL; + + if (watchers) { + zend_hash_index_del(watchers, hash_key); + } } PHP_METHOD(Io_Poll_Context, __construct) @@ -727,8 +738,6 @@ PHP_METHOD(Io_Poll_Context, add) watcher->handle = handle; watcher->watched_events = events; watcher->triggered_events = 0; - watcher->active = true; - watcher->poll_ctx = intern->ctx; GC_ADDREF(&handle->std); @@ -757,7 +766,10 @@ PHP_METHOD(Io_Poll_Context, add) GC_ADDREF(&watcher->std); zend_ulong hash_key = php_io_poll_compute_ptr_key(handle); - zend_hash_index_add(intern->watchers, hash_key, &watcher_zv); + zend_hash_index_add_new(intern->watchers, hash_key, &watcher_zv); + + watcher->active = true; + watcher->context = intern; } PHP_METHOD(Io_Poll_Context, wait) diff --git a/ext/standard/tests/poll/gh22759.phpt b/ext/standard/tests/poll/gh22759.phpt new file mode 100644 index 000000000000..3df68c4f1314 --- /dev/null +++ b/ext/standard/tests/poll/gh22759.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-22759 (Io\Poll: re-adding a removed handle keeps the new watcher tracked) +--FILE-- +add($handle, [Io\Poll\Event::Read]); +$first->remove(); + +$ref = WeakReference::create($first); +unset($first); +gc_collect_cycles(); +var_dump($ref->get()); + +$second = $context->add($handle, [Io\Poll\Event::Read]); + +unset($context); +gc_collect_cycles(); + +var_dump($second->isActive()); + +try { + $second->remove(); +} catch (Io\Poll\InactiveWatcherException $e) { + echo $e->getMessage(), "\n"; +} + +echo "done\n"; +?> +--EXPECT-- +NULL +bool(false) +Cannot remove inactive watcher +done diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 4a9b278c116c..a9f1f96f366f 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -689,11 +689,7 @@ second_try: if (!php_var_unserialize_internal(data, p, max, var_hash)) { if (info) { - if (Z_ISREF_P(data)) { - ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(data), info); - } else { - var_restore_prop_default(var_hash, obj, info, data); - } + var_restore_prop_default(var_hash, obj, info, data); } goto failure; } diff --git a/main/php.h b/main/php.h index c7516bca3124..a0c639810b8f 100644 --- a/main/php.h +++ b/main/php.h @@ -42,6 +42,8 @@ # define PHP_OS_FAMILY "Darwin" #elif defined(__sun__) # define PHP_OS_FAMILY "Solaris" +#elif defined(_AIX) +# define PHP_OS_FAMILY "AIX" #elif defined(__linux__) # define PHP_OS_FAMILY "Linux" #else diff --git a/main/streams/php_stream_errors.h b/main/streams/php_stream_errors.h index b21f47505248..67112c8932b7 100644 --- a/main/streams/php_stream_errors.h +++ b/main/streams/php_stream_errors.h @@ -73,7 +73,6 @@ typedef struct _php_stream_error_entry { zend_string *message; zend_enum_StreamErrorCode code; char *wrapper_name; - char *param; char *docref; int severity; bool terminating; diff --git a/main/streams/stream_errors.c b/main/streams/stream_errors.c index 2fa2cc646636..b2dfdbb217ad 100644 --- a/main/streams/stream_errors.c +++ b/main/streams/stream_errors.c @@ -66,13 +66,6 @@ static void php_stream_error_create_object(zval *zv, php_stream_error_entry *ent zend_update_property_bool( php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("terminating"), entry->terminating); - - if (entry->param) { - zend_update_property_string( - php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param"), entry->param); - } else { - zend_update_property_null(php_ce_stream_error, Z_OBJ_P(zv), ZEND_STRL("param")); - } } /* Create array of StreamError objects from error chain */ @@ -213,7 +206,6 @@ static void php_stream_error_entry_free(php_stream_error_entry *entry) php_stream_error_entry *next = entry->next; zend_string_release(entry->message); efree(entry->wrapper_name); - efree(entry->param); efree(entry->docref); efree(entry); entry = next; @@ -326,7 +318,7 @@ PHPAPI php_stream_error_operation *php_stream_error_operation_begin(void) } static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wrapper_name, - zend_string *message, const char *docref, char *param, int severity, bool terminating) + zend_string *message, const char *docref, int severity, bool terminating) { php_stream_error_operation *op = FG(stream_error_state).current_operation; ZEND_ASSERT(op != NULL); @@ -335,7 +327,6 @@ static void php_stream_error_add(zend_enum_StreamErrorCode code, const char *wra entry->message = message; entry->code = code; entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; - entry->param = param; entry->docref = docref ? estrdup(docref) : NULL; entry->severity = severity; entry->terminating = terminating; @@ -399,15 +390,9 @@ static void php_stream_report_errors(php_stream_context *context, php_stream_err { switch (error_mode) { case PHP_STREAM_ERROR_MODE_ERROR: { - php_stream_error_entry *entry = op->first_error; + const php_stream_error_entry *entry = op->first_error; while (entry) { - if (entry->param) { - php_error_docref1(entry->docref, entry->param, entry->severity, "%s", - ZSTR_VAL(entry->message)); - } else { - php_error_docref( - entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message)); - } + php_error_docref(entry->docref, entry->severity, "%s", ZSTR_VAL(entry->message)); entry = entry->next; } break; @@ -575,15 +560,15 @@ PHPAPI void php_stream_error_operation_abort(void) /* Wrapper error reporting */ static void php_stream_wrapper_error_internal(const char *wrapper_name, php_stream_context *context, - const char *docref, int options, int severity, bool terminating, - zend_enum_StreamErrorCode code, char *param, zend_string *message) + const char *docref, int severity, bool terminating, + zend_enum_StreamErrorCode code, zend_string *message) { bool implicit_operation = (FG(stream_error_state).current_operation == NULL); if (implicit_operation) { php_stream_error_operation_begin(); } - php_stream_error_add(code, wrapper_name, message, docref, param, severity, terminating); + php_stream_error_add(code, wrapper_name, message, docref, severity, terminating); if (implicit_operation) { php_stream_error_operation_end(context); @@ -604,7 +589,7 @@ PHPAPI void php_stream_wrapper_error_with_name(const char *wrapper_name, va_end(args); php_stream_wrapper_error_internal( - wrapper_name, context, docref, options, severity, terminating, code, NULL, message); + wrapper_name, context, docref, severity, terminating, code, message); } PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_context *context, @@ -623,7 +608,7 @@ PHPAPI void php_stream_wrapper_error(php_stream_wrapper *wrapper, php_stream_con const char *wrapper_name = PHP_STREAM_ERROR_WRAPPER_NAME(wrapper); php_stream_wrapper_error_internal( - wrapper_name, context, docref, options, severity, terminating, code, NULL, message); + wrapper_name, context, docref, severity, terminating, code, message); } /* Stream error reporting */ @@ -641,8 +626,8 @@ PHPAPI void php_stream_error(php_stream *stream, const char *docref, int severit php_stream_context *context = PHP_STREAM_CONTEXT(stream); - php_stream_wrapper_error_internal(wrapper_name, context, docref, REPORT_ERRORS, severity, - terminating, code, NULL, message); + php_stream_wrapper_error_internal(wrapper_name, context, docref, severity, + terminating, code, message); } /* Legacy wrapper error logging */ @@ -652,7 +637,6 @@ static void php_stream_error_entry_dtor_legacy(void *error) php_stream_error_entry *entry = *(php_stream_error_entry **) error; zend_string_release(entry->message); efree(entry->wrapper_name); - efree(entry->param); efree(entry->docref); efree(entry); } @@ -671,7 +655,6 @@ static void php_stream_wrapper_log_store_error(zend_string *message, zend_enum_S entry->message = message; entry->code = code; entry->wrapper_name = wrapper_name ? estrdup(wrapper_name) : NULL; - entry->param = NULL; entry->severity = severity; entry->terminating = terminating; @@ -705,7 +688,7 @@ PHPAPI void php_stream_wrapper_log_error(const php_stream_wrapper *wrapper, if (options & REPORT_ERRORS) { php_stream_wrapper_error_internal( - wrapper_name, context, NULL, options, severity, terminating, code, NULL, message); + wrapper_name, context, NULL, severity, terminating, code, message); } else { php_stream_wrapper_log_store_error( message, code, wrapper_name, severity, terminating); @@ -784,8 +767,8 @@ PHPAPI void php_stream_display_wrapper_name_errors(const char *wrapper_name, zend_string *message = strpprintf(0, "%s: %s", caption, msg); - php_stream_wrapper_error_internal(wrapper_name, context, NULL, REPORT_ERRORS, E_WARNING, true, - code, NULL, message); + php_stream_wrapper_error_internal(wrapper_name, context, NULL, E_WARNING, true, + code, message); if (free_msg) { efree(msg); diff --git a/main/streams/stream_errors.stub.php b/main/streams/stream_errors.stub.php index 255c92beb89e..28fb381df24d 100644 --- a/main/streams/stream_errors.stub.php +++ b/main/streams/stream_errors.stub.php @@ -132,7 +132,6 @@ enum StreamErrorStore public string $wrapperName; public int $severity; public bool $terminating; - public ?string $param; } class StreamException extends Exception diff --git a/main/streams/stream_errors_arginfo.h b/main/streams/stream_errors_arginfo.h index 89df622beb05..4558f64b77f5 100644 --- a/main/streams/stream_errors_arginfo.h +++ b/main/streams/stream_errors_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit stream_errors.stub.php instead. - * Stub hash: 4cddf758cc9f2041802d8cbbaaa45593022a5db1 + * Stub hash: d3087b608996f81bf0dd19c25792feec9744e768 * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_StreamException_getErrors, 0, 0, IS_ARRAY, 0) @@ -237,12 +237,6 @@ static zend_class_entry *register_class_StreamError(void) zend_declare_typed_property(class_entry, property_terminating_name, &property_terminating_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); zend_string_release_ex(property_terminating_name, true); - zval property_param_default_value; - ZVAL_UNDEF(&property_param_default_value); - zend_string *property_param_name = zend_string_init("param", sizeof("param") - 1, true); - zend_declare_typed_property(class_entry, property_param_name, &property_param_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); - zend_string_release_ex(property_param_name, true); - return class_entry; } diff --git a/main/streams/stream_errors_decl.h b/main/streams/stream_errors_decl.h index 69fe96252b4c..3a18055b9e74 100644 --- a/main/streams/stream_errors_decl.h +++ b/main/streams/stream_errors_decl.h @@ -1,8 +1,8 @@ /* This is a generated file, edit stream_errors.stub.php instead. - * Stub hash: 4cddf758cc9f2041802d8cbbaaa45593022a5db1 */ + * Stub hash: d3087b608996f81bf0dd19c25792feec9744e768 */ -#ifndef ZEND_STREAM_ERRORS_DECL_4cddf758cc9f2041802d8cbbaaa45593022a5db1_H -#define ZEND_STREAM_ERRORS_DECL_4cddf758cc9f2041802d8cbbaaa45593022a5db1_H +#ifndef ZEND_STREAM_ERRORS_DECL_d3087b608996f81bf0dd19c25792feec9744e768_H +#define ZEND_STREAM_ERRORS_DECL_d3087b608996f81bf0dd19c25792feec9744e768_H typedef enum zend_enum_StreamErrorCode { ZEND_ENUM_StreamErrorCode_None = 1, @@ -184,4 +184,4 @@ typedef enum zend_enum_StreamErrorStore { ZEND_ENUM_StreamErrorStore_All = 5, } zend_enum_StreamErrorStore; -#endif /* ZEND_STREAM_ERRORS_DECL_4cddf758cc9f2041802d8cbbaaa45593022a5db1_H */ +#endif /* ZEND_STREAM_ERRORS_DECL_d3087b608996f81bf0dd19c25792feec9744e768_H */