Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7c98e65
Fix GH-22118: Compare equivalent fake closures in FCCs (#22145)
prateekbhujel Jun 14, 2026
a2114a3
Zend: fix canonical case of magic-method name macros (#22275)
jorgsowa Jun 14, 2026
3d8ed85
[skip-ci] ext/standard/tests: use die() instead of echo for 64-bit sk…
arshidkv12 Jun 14, 2026
b6385ef
ext/exif: spell the Exif identifier code as a char literal (#22241)
iliaal Jun 14, 2026
a59f6ec
[skip ci] use die() instead of echo for 64-bit skip condition (#22299)
LamentXU123 Jun 14, 2026
d7e4fc7
zend_types: Move `zend_gc_*()` functions above the wrapper macros (#2…
TimWolla Jun 14, 2026
ceb4934
zend_operators: Remove `zend_binary_zval_str(n)cmp()` (#22298)
TimWolla Jun 14, 2026
d4a9726
exif: read from the correct pointer
ndossche Jun 7, 2026
e645c60
Merge branch 'PHP-8.4' into PHP-8.5
ndossche Jun 14, 2026
4badd48
Merge branch 'PHP-8.5'
ndossche Jun 14, 2026
3c6b25c
zend_string: Add `zend_string_equals_cstr_ci()` (#22296)
TimWolla Jun 14, 2026
8f44bd9
zend_ast: Quote names of invalid variable names when exporting AST (#…
TimWolla Jun 14, 2026
f26aae1
Microoptimize zval_long_or_null_to_lexbor_str
kocsismate Jun 13, 2026
3642a7a
Add a few missing modifiers for ext/uri functions
kocsismate Jun 14, 2026
9e9b309
Fix GH-22280: Ignore non-finally try blocks (#22286)
prateekbhujel Jun 14, 2026
2ec5f2d
Merge branch 'PHP-8.4' into PHP-8.5
TimWolla Jun 14, 2026
10bdc08
Merge branch 'PHP-8.5'
TimWolla Jun 14, 2026
c76b33d
zend_types: Remove the unused `Z_GC_*()` macros (#22303)
TimWolla Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ PHP NEWS
. Fixed bug GH-22046 (The unserialize function can lead to segfault when
non-Serializable internal classes are serialized back with the C format).
(kocsismate)
. Fixed bug GH-22292 (AST pretty printing does not correctly handle
invalid variable names). (timwolla)

- BCMath:
. Added NUL-byte validation to BCMath functions. (jorgsowa)
Expand Down
7 changes: 7 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. ZSTR_INIT_LITERAL(), zend_string_starts_with_literal(), and
zend_string_starts_with_literal_ci() now support strings containing NUL
bytes. Passing non-literal char* is no longer supported.
. Added zend_string_equals_cstr_ci().
. The misnamed ZVAL_IS_NULL() has been removed. Use Z_ISNULL() instead.
. New zend_class_entry.ce_flags2 and zend_function.fn_flags2 fields were
added, given the primary flags were running out of bits.
Expand Down Expand Up @@ -103,6 +104,12 @@ PHP 8.6 INTERNALS UPGRADE NOTES
. The deprecated Z_IMMUTABLE(), Z_IMMUTABLE_P(), Z_OPT_IMMUTABLE(), and
Z_OPT_IMMUTABLE_P() macros have been removed. Check for
IS_ARRAY && !REFCOUNTED directly.
. The unused Z_GC_*() macros have been removed. Use the corresponding
GC_*() macro on the result of Z_COUNTED().
. The zend_binary_zval_strcmp() and zend_binary_zval_strncmp() functions
have been removed, because they are unsafe by relying on the zvals
having a specific type. Use zend_binary_strcmp() / zend_binary_strncmp(),
string_compare_function() or similar instead.
. Added zend_fcall_info.consumed_args together with
zend_fci_consumed_arg(), which allows moving a selected callback argument
instead of copying it in zend_call_function(). Currently only a single
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/assert/expect_015.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ assert(0 && ($a = function (): ?static {
$x = "{$a}b";
$x = "{$a}b";
$x = " {$foo->bar} {${$foo->bar}} ";
$x = " ${---} ";
$x = " ${'---'} ";
foo();
\foo();
namespace\foo();
Expand Down
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-0.0.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(0.0);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-0.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(0);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-1.0.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(1.0);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(1);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
20 changes: 20 additions & 0 deletions Zend/tests/debug_info/debug_info-error-case-insensitive.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Testing __debugInfo() magic method declared with non-canonical case
--FILE--
<?php

class C {
public $val;
public function __DEBUGINFO() {
return $this->val;
}
public function __construct($val) {
$this->val = $val;
}
}

$c = new C(1);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-empty_str.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C("");
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-false.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(false);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-object.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(new stdClass);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-resource.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ $c = new C(fopen("data:text/plain,Foo", 'r'));
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-str.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C("foo");
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
2 changes: 1 addition & 1 deletion Zend/tests/debug_info/debug_info-error-true.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ $c = new C(true);
var_dump($c);
?>
--EXPECTF--
Fatal error: __debuginfo() must return an array in %s on line %d
Fatal error: __debugInfo() must return an array in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--TEST--
Stringable is automatically implemented for __toString() declared with non-canonical case
--FILE--
<?php

class Test {
public function __TOSTRING() {
return "foo";
}
}

var_dump(new Test instanceof Stringable);
var_dump((new ReflectionClass(Test::class))->getInterfaceNames());
var_dump((string) new Test);

?>
--EXPECT--
bool(true)
array(1) {
[0]=>
string(10) "Stringable"
}
string(3) "foo"
19 changes: 19 additions & 0 deletions Zend/tests/try/gh22280.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
GH-22280: goto to label before try/finally after try/catch
--FILE--
<?php
goto d;
try {
} catch (Throwable) {
}
d: try {
echo "try\n";
} finally {
echo "finally\n";
}
echo "done\n";
?>
--EXPECT--
try
finally
done
70 changes: 58 additions & 12 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -2809,18 +2809,18 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce,
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING);
zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY);
} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) {
zend_check_magic_method_args(2, ce, fptr, error_type);
zend_check_magic_method_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_STRING);
zend_check_magic_method_arg_type(1, ce, fptr, error_type, MAY_BE_ARRAY);
} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) {
} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) {
zend_check_magic_method_args(0, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_STRING);
} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) {
zend_check_magic_method_args(0, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
Expand All @@ -2829,18 +2829,18 @@ ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce,
zend_error(E_DEPRECATED, "Returning null from %s::__debugInfo() is deprecated, make the return type non-nullable and return an empty array instead",
ZSTR_VAL(ce->name));
}
} else if (zend_string_equals_literal(lcname, "__serialize")) {
} else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) {
zend_check_magic_method_args(0, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_ARRAY);
} else if (zend_string_equals_literal(lcname, "__unserialize")) {
} else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) {
zend_check_magic_method_args(1, ce, fptr, error_type);
zend_check_magic_method_non_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
zend_check_magic_method_arg_type(0, ce, fptr, error_type, MAY_BE_ARRAY);
zend_check_magic_method_return_type(ce, fptr, error_type, MAY_BE_VOID);
} else if (zend_string_equals_literal(lcname, "__set_state")) {
} else if (zend_string_equals_literal(lcname, ZEND_SET_STATE_FUNC_NAME)) {
zend_check_magic_method_args(1, ce, fptr, error_type);
zend_check_magic_method_static(ce, fptr, error_type);
zend_check_magic_method_public(ce, fptr);
Expand Down Expand Up @@ -2888,16 +2888,16 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, c
} else if (zend_string_equals_literal(lcname, ZEND_ISSET_FUNC_NAME)) {
ce->__isset = fptr;
ce->ce_flags |= ZEND_ACC_USE_GUARDS;
} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_NAME)) {
} else if (zend_string_equals_literal(lcname, ZEND_CALLSTATIC_FUNC_LCNAME)) {
ce->__callstatic = fptr;
} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)) {
} else if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)) {
ce->__tostring = fptr;
} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_LCNAME)) {
ce->__debugInfo = fptr;
ce->ce_flags |= ZEND_ACC_USE_GUARDS;
} else if (zend_string_equals_literal(lcname, "__serialize")) {
} else if (zend_string_equals_literal(lcname, ZEND_SERIALIZE_FUNC_NAME)) {
ce->__serialize = fptr;
} else if (zend_string_equals_literal(lcname, "__unserialize")) {
} else if (zend_string_equals_literal(lcname, ZEND_UNSERIALIZE_FUNC_NAME)) {
ce->__unserialize = fptr;
}
}
Expand Down Expand Up @@ -3111,7 +3111,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend

/* If not specified, add __toString() return type for compatibility with Stringable
* interface. */
if (scope && zend_string_equals_literal_ci(internal_function->function_name, "__tostring") &&
if (scope && zend_string_equals_literal_ci(internal_function->function_name, ZEND_TOSTRING_FUNC_LCNAME) &&
!(internal_function->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
zend_error(E_CORE_WARNING, "%s::__toString() implemented without string return type",
ZSTR_VAL(scope->name));
Expand Down Expand Up @@ -4096,6 +4096,52 @@ ZEND_API zend_string *zend_get_callable_name_ex(const zval *callable, const zend
}
/* }}} */

static bool zend_fcc_function_handler_equals(const zend_function *func1, const zend_function *func2) /* {{{ */
{
if (func1 == func2) {
return true;
}

const bool fake_closure1 = (func1->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;
const bool fake_closure2 = (func2->common.fn_flags & ZEND_ACC_FAKE_CLOSURE) != 0;

if (!fake_closure1 && !fake_closure2) {
return false;
}
if (((func1->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure1) ||
((func2->common.fn_flags & ZEND_ACC_CLOSURE) && !fake_closure2)) {
return false;
}
if (func1->type != func2->type ||
func1->common.scope != func2->common.scope ||
!zend_string_equals(func1->common.function_name, func2->common.function_name)) {
return false;
}

if (func1->type == ZEND_USER_FUNCTION) {
return func1->op_array.opcodes == func2->op_array.opcodes;
}

return func1->internal_function.handler == func2->internal_function.handler;
}
/* }}} */

ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b) /* {{{ */
{
const zend_function *func1 = a->function_handler;
const zend_function *func2 = b->function_handler;

if (a->closure && a->closure->ce == zend_ce_closure) {
func1 = zend_get_closure_method_def(a->closure);
}
if (b->closure && b->closure->ce == zend_ce_closure) {
func2 = zend_get_closure_method_def(b->closure);
}

return zend_fcc_function_handler_equals(func1, func2);
}
/* }}} */

ZEND_API zend_string *zend_get_callable_name(const zval *callable) /* {{{ */
{
return zend_get_callable_name_ex(callable, NULL);
Expand Down
11 changes: 9 additions & 2 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -758,20 +758,27 @@ ZEND_API void zend_fcall_info_argn(zend_fcall_info *fci, uint32_t argc, ...);
ZEND_API zend_result zend_fcall_info_call(zend_fcall_info *fci, zend_fcall_info_cache *fcc, zval *retval, zval *args);

/* Zend FCC API to store and handle PHP userland functions */
ZEND_API bool zend_fcc_closure_equals_ex(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b);

static zend_always_inline bool zend_fcc_equals(const zend_fcall_info_cache* a, const zend_fcall_info_cache* b)
{
if (a->closure || b->closure) {
return a->object == b->object
&& a->calling_scope == b->calling_scope
&& a->called_scope == b->called_scope
&& (a->closure == b->closure || zend_fcc_closure_equals_ex(a, b))
;
}
if (UNEXPECTED((a->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) &&
(b->function_handler->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))) {
return a->object == b->object
&& a->calling_scope == b->calling_scope
&& a->closure == b->closure
&& zend_string_equals(a->function_handler->common.function_name, b->function_handler->common.function_name)
;
}
return a->function_handler == b->function_handler
&& a->object == b->object
&& a->calling_scope == b->calling_scope
&& a->closure == b->closure
;
}

Expand Down
11 changes: 8 additions & 3 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -1723,9 +1723,14 @@ static ZEND_COLD void zend_ast_export_var(smart_str *str, zend_ast *ast, int ind
{
if (ast->kind == ZEND_AST_ZVAL) {
zval *zv = zend_ast_get_zval(ast);
if (Z_TYPE_P(zv) == IS_STRING &&
zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) {
smart_str_append(str, Z_STR_P(zv));
if (Z_TYPE_P(zv) == IS_STRING) {
if (zend_ast_valid_var_name(Z_STRVAL_P(zv), Z_STRLEN_P(zv))) {
smart_str_append(str, Z_STR_P(zv));
} else {
smart_str_appends(str, "{'");
zend_ast_export_str(str, Z_STR_P(zv));
smart_str_appends(str, "'}");
}
return;
}
} else if (ast->kind == ZEND_AST_VAR) {
Expand Down
4 changes: 2 additions & 2 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -8610,7 +8610,7 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string
}

zend_add_magic_method(ce, (zend_function *) op_array, lcname);
if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME)
if (zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME)
&& !(ce->ce_flags & ZEND_ACC_TRAIT)) {
add_stringable_interface(ce);
}
Expand Down Expand Up @@ -8841,7 +8841,7 @@ static zend_op_array *zend_compile_func_decl_ex(
}

zend_compile_params(params_ast, return_type_ast,
is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_NAME) ? IS_STRING : 0);
is_method && zend_string_equals_literal(lcname, ZEND_TOSTRING_FUNC_LCNAME) ? IS_STRING : 0);
if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) {
zend_mark_function_as_generator();
zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL);
Expand Down
16 changes: 12 additions & 4 deletions Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ typedef struct _zend_oparray_context {
#define ZEND_ACC_USES_THIS (1 << 17) /* | X | | */
/* | | | */
/* call through user function trampoline. e.g. | | | */
/* __call, __callstatic | | | */
/* __call, __callStatic | | | */
#define ZEND_ACC_CALL_VIA_TRAMPOLINE (1 << 18) /* | X | | */
/* | | | */
/* disable inline caching | | | */
Expand Down Expand Up @@ -1249,10 +1249,18 @@ END_EXTERN_C()
#define ZEND_UNSET_FUNC_NAME "__unset"
#define ZEND_ISSET_FUNC_NAME "__isset"
#define ZEND_CALL_FUNC_NAME "__call"
#define ZEND_CALLSTATIC_FUNC_NAME "__callstatic"
#define ZEND_TOSTRING_FUNC_NAME "__tostring"
#define ZEND_CALLSTATIC_FUNC_NAME "__callStatic"
#define ZEND_CALLSTATIC_FUNC_LCNAME "__callstatic"
#define ZEND_TOSTRING_FUNC_NAME "__toString"
#define ZEND_TOSTRING_FUNC_LCNAME "__tostring"
#define ZEND_INVOKE_FUNC_NAME "__invoke"
#define ZEND_DEBUGINFO_FUNC_NAME "__debuginfo"
#define ZEND_DEBUGINFO_FUNC_NAME "__debugInfo"
#define ZEND_DEBUGINFO_FUNC_LCNAME "__debuginfo"
#define ZEND_SLEEP_FUNC_NAME "__sleep"
#define ZEND_WAKEUP_FUNC_NAME "__wakeup"
#define ZEND_SERIALIZE_FUNC_NAME "__serialize"
#define ZEND_UNSERIALIZE_FUNC_NAME "__unserialize"
#define ZEND_SET_STATE_FUNC_NAME "__set_state"

/* The following constants may be combined in CG(compiler_options)
* to change the default compiler behavior */
Expand Down
Loading
Loading