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 */