From d6be30e3252fa6a52ed03f666745ace7b4c717ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 6 Jul 2026 22:46:38 +0200 Subject: [PATCH 1/3] iconv: Fix memory management in autoconf test for `//IGNORE` (#22620) Sometimes running `./configure` for ASAN hangs on my machine. This is reproducible when compiling and running the program manually using clang-16 -g -fsanitize=undefined,address test.c It seems this is related to the LLVM symbolizer getting stuck, and the symbolizer is likely required to report the memory leak. After adding the memory management functions it seems to work (or rather fail) reliably. --- ext/iconv/config.m4 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/iconv/config.m4 b/ext/iconv/config.m4 index e86f3535a97c..bea97b27f4f7 100644 --- a/ext/iconv/config.m4 +++ b/ext/iconv/config.m4 @@ -112,6 +112,8 @@ int main(void) { char *out = malloc(out_left); char *out_p = out; size_t result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left); + free(out); + iconv_close(cd); if(result == (size_t)-1) { return 1; } From e758d885b24ed3b276babe51db6c9ea7332198f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 6 Jul 2026 22:50:17 +0200 Subject: [PATCH 2/3] zend_types: Remove `Z_*CONSTANT_*()` (#22295) This macro is very rarely used and badly named by neither including the `IS` nor the `AST` in its name. --- UPGRADING.INTERNALS | 3 +++ Zend/zend_compile.c | 4 ++-- Zend/zend_ini_parser.y | 2 +- Zend/zend_types.h | 5 ----- Zend/zend_vm_def.h | 4 ++-- Zend/zend_vm_execute.h | 8 ++++---- ext/opcache/jit/zend_jit_ir.c | 2 +- 7 files changed, 13 insertions(+), 15 deletions(-) diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index a8eb6b805413..adfc91880f29 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -120,6 +120,9 @@ PHP 8.6 INTERNALS UPGRADE NOTES . Added ZEND_CONTAINER_OF(). . The OPENBASEDIR_CHECKPATH() compatibility macro has been removed, instead use php_check_open_basedir() directly. + . The Z_CONSTANT(), Z_CONSTANT_P(), Z_OPT_CONSTANT(), and + Z_OPT_CONSTANT_P() macros have been removed. Check for IS_CONSTANT_AST + directly. . Added zend_reflection_property_set_raw_value_without_lazy_initialization(), zend_reflection_property_set_raw_value() to expose the functionality of ReflectionProperty::setRawValueWithoutLazyInitialization() and diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index d9fa3a1cb504..b73e2009075f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9224,7 +9224,7 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f if (*value_ast_ptr) { zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); - if (ZEND_TYPE_IS_SET(type) && !Z_CONSTANT(value_zv) + if (ZEND_TYPE_IS_SET(type) && Z_TYPE(value_zv) != IS_CONSTANT_AST && !zend_is_valid_default_value(type, &value_zv)) { zend_string *str = zend_type_to_string(type); if (Z_TYPE(value_zv) == IS_NULL && !ZEND_TYPE_IS_INTERSECTION(type)) { @@ -9348,7 +9348,7 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as zend_const_expr_to_zval(&value_zv, value_ast_ptr, /* allow_dynamic */ false); - if (!Z_CONSTANT(value_zv) && ZEND_TYPE_IS_SET(type) && !zend_is_valid_default_value(type, &value_zv)) { + if (Z_TYPE(value_zv) != IS_CONSTANT_AST && ZEND_TYPE_IS_SET(type) && !zend_is_valid_default_value(type, &value_zv)) { zend_string *type_str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Cannot use %s as value for class constant %s::%s of type %s", diff --git a/Zend/zend_ini_parser.y b/Zend/zend_ini_parser.y index d35853ab5acd..47b05de4e692 100644 --- a/Zend/zend_ini_parser.y +++ b/Zend/zend_ini_parser.y @@ -151,7 +151,7 @@ static void zend_ini_get_constant(zval *result, zval *name) && (c = zend_get_constant(Z_STR_P(name))) != 0) { if (Z_TYPE_P(c) != IS_STRING) { ZVAL_COPY_OR_DUP(&tmp, c); - if (Z_OPT_CONSTANT(tmp)) { + if (Z_OPT_TYPE(tmp) == IS_CONSTANT_AST) { zval_update_constant_ex(&tmp, NULL); } convert_to_string(&tmp); diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 2cdfdcb1961f..56774e4e9d8f 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -1017,8 +1017,6 @@ static zend_always_inline uint32_t zend_gc_delref_ex(zend_refcounted_h *p, uint3 } while(0) /* All data types < IS_STRING have their constructor/destructors skipped */ -#define Z_CONSTANT(zval) (Z_TYPE(zval) == IS_CONSTANT_AST) -#define Z_CONSTANT_P(zval_p) Z_CONSTANT(*(zval_p)) #if 1 /* This optimized version assumes that we have a single "type_flag" */ @@ -1036,9 +1034,6 @@ static zend_always_inline uint32_t zend_gc_delref_ex(zend_refcounted_h *p, uint3 #define Z_OPT_TYPE(zval) (Z_TYPE_INFO(zval) & Z_TYPE_MASK) #define Z_OPT_TYPE_P(zval_p) Z_OPT_TYPE(*(zval_p)) -#define Z_OPT_CONSTANT(zval) (Z_OPT_TYPE(zval) == IS_CONSTANT_AST) -#define Z_OPT_CONSTANT_P(zval_p) Z_OPT_CONSTANT(*(zval_p)) - #define Z_OPT_REFCOUNTED(zval) Z_TYPE_INFO_REFCOUNTED(Z_TYPE_INFO(zval)) #define Z_OPT_REFCOUNTED_P(zval_p) Z_OPT_REFCOUNTED(*(zval_p)) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index a0bfad488f85..ec9a97eeb254 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -8323,7 +8323,7 @@ ZEND_VM_HANDLER(143, ZEND_DECLARE_CONST, CONST, CONST) val = GET_OP2_ZVAL_PTR(BP_VAR_R); ZVAL_COPY(&c.value, val); - if (Z_OPT_CONSTANT(c.value)) { + if (Z_OPT_TYPE(c.value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(&c.value, EX(func)->op_array.scope) != SUCCESS)) { zval_ptr_dtor_nogc(&c.value); FREE_OP1(); @@ -8355,7 +8355,7 @@ ZEND_VM_HANDLER(210, ZEND_DECLARE_ATTRIBUTED_CONST, CONST, CONST) val = GET_OP2_ZVAL_PTR(BP_VAR_R); ZVAL_COPY(&c.value, val); - if (Z_OPT_CONSTANT(c.value)) { + if (Z_OPT_TYPE(c.value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(&c.value, EX(func)->op_array.scope) != SUCCESS)) { zval_ptr_dtor_nogc(&c.value); FREE_OP1(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index cedc735bbb1e..34b2455ce439 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8290,7 +8290,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_DECLARE_CONST val = RT_CONSTANT(opline, opline->op2); ZVAL_COPY(&c.value, val); - if (Z_OPT_CONSTANT(c.value)) { + if (Z_OPT_TYPE(c.value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(&c.value, EX(func)->op_array.scope) != SUCCESS)) { zval_ptr_dtor_nogc(&c.value); @@ -8325,7 +8325,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_DECLARE_ATTRI val = RT_CONSTANT(opline, opline->op2); ZVAL_COPY(&c.value, val); - if (Z_OPT_CONSTANT(c.value)) { + if (Z_OPT_TYPE(c.value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(&c.value, EX(func)->op_array.scope) != SUCCESS)) { zval_ptr_dtor_nogc(&c.value); @@ -60979,7 +60979,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DECLARE_CONST_SPEC val = RT_CONSTANT(opline, opline->op2); ZVAL_COPY(&c.value, val); - if (Z_OPT_CONSTANT(c.value)) { + if (Z_OPT_TYPE(c.value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(&c.value, EX(func)->op_array.scope) != SUCCESS)) { zval_ptr_dtor_nogc(&c.value); @@ -61014,7 +61014,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_DECLARE_ATTRIBUTED val = RT_CONSTANT(opline, opline->op2); ZVAL_COPY(&c.value, val); - if (Z_OPT_CONSTANT(c.value)) { + if (Z_OPT_TYPE(c.value) == IS_CONSTANT_AST) { if (UNEXPECTED(zval_update_constant_ex(&c.value, EX(func)->op_array.scope) != SUCCESS)) { zval_ptr_dtor_nogc(&c.value); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index d62ef95b5513..b0b04456706b 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10924,7 +10924,7 @@ static int zend_jit_recv_init(zend_jit_ctx *jit, const zend_op *opline, const ze zv, true); } - if (Z_CONSTANT_P(zv)) { + if (Z_TYPE_P(zv) == IS_CONSTANT_AST) { jit_SET_EX_OPLINE(jit, opline); ref = ir_CALL_2(IR_I32, ir_CONST_FC_FUNC(zval_update_constant_ex), jit_ZVAL_ADDR(jit, res_addr), From 7330366438084f60cfe857d92f98fa02f98ebc1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 6 Jul 2026 22:50:58 +0200 Subject: [PATCH 3/3] zend_ast: Wrap class names in parens during export when they are an expression (#22389) Fixes php/php-src#22387. --- NEWS | 2 + Zend/zend_ast.c | 64 ++++++++++++++++++-------- Zend/zend_ast.h | 3 ++ Zend/zend_compile.c | 12 +---- ext/standard/tests/assert/gh22387.phpt | 62 +++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 31 deletions(-) create mode 100644 ext/standard/tests/assert/gh22387.phpt diff --git a/NEWS b/NEWS index 2a266243a747..705ff85b2a6e 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ PHP NEWS - Core: . Sync Boost.Context assembly with 1.91.0. (kn1g78) + . Fixed bug GH-22387 (AST pretty-printing drops meaningful parentheses around + RHS of instanceof). (timwolla) - DBA: . Fixed OOB read on malformed length field in dba flatfile handler. (alhudz) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index d3ce419c737e..3aa04de860a1 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1686,6 +1686,19 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int } zend_ast_export_ex(str, ast, priority, indent); } +static ZEND_COLD void zend_ast_export_ns_name_or_expression(smart_str *str, zend_ast *ast, int priority, int indent) +{ + switch (ast->kind) { + case ZEND_AST_ZVAL: + case ZEND_AST_VAR: + zend_ast_export_ns_name(str, ast, priority, indent); + break; + default: + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast, priority, indent); + smart_str_appendc(str, ')'); + } +} static ZEND_COLD bool zend_ast_valid_var_name(const char *s, size_t len) { @@ -2523,25 +2536,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_var(str, ast->child[1], indent); break; case ZEND_AST_STATIC_PROP: - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent); smart_str_appends(str, "::$"); zend_ast_export_var(str, ast->child[1], indent); break; case ZEND_AST_CALL: { - zend_ast *left = ast->child[0]; - switch (left->kind) { - /* ZEND_AST_ZVAL is a regular function call. */ - case ZEND_AST_ZVAL: - /* ZEND_AST_VAR ($foo()) is unambiguous without parens. */ - case ZEND_AST_VAR: - zend_ast_export_ns_name(str, left, 0, indent); - break; - default: - smart_str_appendc(str, '('); - zend_ast_export_ex(str, left, 0, indent); - smart_str_appendc(str, ')'); - break; - } + zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent); smart_str_appendc(str, '('); zend_ast_export_ex(str, ast->child[1], 0, indent); smart_str_appendc(str, ')'); @@ -2553,7 +2553,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio goto simple_list; } case ZEND_AST_CLASS_CONST: - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent); smart_str_appends(str, "::"); zend_ast_export_name(str, ast->child[1], 0, indent); break; @@ -2570,7 +2570,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio default: ZEND_UNREACHABLE(); } } else { - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent); } smart_str_appends(str, "::class"); break; @@ -2649,7 +2649,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } zend_ast_export_class_no_header(str, decl, indent); } else { - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent); smart_str_appendc(str, '('); zend_ast_export_ex(str, ast->child[1], 0, indent); smart_str_appendc(str, ')'); @@ -2658,7 +2658,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio case ZEND_AST_INSTANCEOF: zend_ast_export_ex(str, ast->child[0], 0, indent); smart_str_appends(str, " instanceof "); - zend_ast_export_ns_name(str, ast->child[1], 0, indent); + zend_ast_export_ns_name_or_expression(str, ast->child[1], 0, indent); break; case ZEND_AST_YIELD: if (priority > 70) smart_str_appendc(str, '('); @@ -2851,7 +2851,12 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio smart_str_appendc(str, ')'); break; case ZEND_AST_STATIC_CALL: - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + if (zend_ast_is_parent_hook_call(ast)) { + zend_ast_export_ns_name(str, ast->child[0], 0, indent); + } else { + zend_ast_export_ns_name_or_expression(str, ast->child[0], 0, indent); + } + smart_str_appends(str, "::"); zend_ast_export_var(str, ast->child[1], indent); smart_str_appendc(str, '('); @@ -3082,3 +3087,22 @@ zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast) ZEND_UNREACHABLE(); return NULL; } + +bool zend_ast_is_parent_hook_call(const zend_ast *ast) +{ + ZEND_ASSERT(ast->kind == ZEND_AST_STATIC_CALL); + + const zend_ast *class_ast = ast->child[0]; + zend_ast *method_ast = ast->child[1]; + + return class_ast->kind == ZEND_AST_STATIC_PROP + && !(class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP) + && class_ast->child[0]->kind == ZEND_AST_ZVAL + && Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) == IS_STRING + && zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) == ZEND_FETCH_CLASS_PARENT + && class_ast->child[1]->kind == ZEND_AST_ZVAL + && method_ast->kind == ZEND_AST_ZVAL + && Z_TYPE_P(zend_ast_get_zval(method_ast)) == IS_STRING + && (zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get") + || zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set")); +} diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 24b77d7d3493..86a68c1cbaf9 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -440,4 +440,7 @@ zend_ast * ZEND_FASTCALL zend_ast_with_attributes(zend_ast *ast, zend_ast *attr) zend_ast * ZEND_FASTCALL zend_ast_call_get_args(zend_ast *ast); +/* Recognize parent::$prop::get() pattern. */ +bool zend_ast_is_parent_hook_call(const zend_ast *ast); + #endif diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b73e2009075f..ac5a9d71a6ea 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5353,17 +5353,7 @@ static bool zend_compile_parent_property_hook_call(znode *result, const zend_ast const zend_ast *class_ast = ast->child[0]; zend_ast *method_ast = ast->child[1]; - /* Recognize parent::$prop::get() pattern. */ - if (class_ast->kind != ZEND_AST_STATIC_PROP - || (class_ast->attr & ZEND_PARENTHESIZED_STATIC_PROP) - || class_ast->child[0]->kind != ZEND_AST_ZVAL - || Z_TYPE_P(zend_ast_get_zval(class_ast->child[0])) != IS_STRING - || zend_get_class_fetch_type(zend_ast_get_str(class_ast->child[0])) != ZEND_FETCH_CLASS_PARENT - || class_ast->child[1]->kind != ZEND_AST_ZVAL - || method_ast->kind != ZEND_AST_ZVAL - || Z_TYPE_P(zend_ast_get_zval(method_ast)) != IS_STRING - || (!zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "get") - && !zend_string_equals_literal_ci(zend_ast_get_str(method_ast), "set"))) { + if (!zend_ast_is_parent_hook_call(ast)) { return false; } diff --git a/ext/standard/tests/assert/gh22387.phpt b/ext/standard/tests/assert/gh22387.phpt new file mode 100644 index 000000000000..38c93921608a --- /dev/null +++ b/ext/standard/tests/assert/gh22387.phpt @@ -0,0 +1,62 @@ +--TEST-- +GH-22387: AST pretty-printing drops meaningful parentheses around RHS of instanceof +--FILE-- +getMessage(), PHP_EOL; +} + +try { + assert(!new (bar)()); +} catch (AssertionError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(!(bar)::m()); +} catch (AssertionError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(!(bar)::$p); +} catch (AssertionError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert(!(bar)::C); +} catch (AssertionError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + assert((baz)::class !== 'stdClass'); +} catch (AssertionError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +assert(!$foo instanceof (bar)) +assert(!new (bar)()) +assert(!(bar)::m()) +assert(!(bar)::$p) +assert(!(bar)::C) +assert((baz)::class !== 'stdClass')