Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ PHP NEWS
. Added support for configuring the URI parser for the FTP/FTPS as well as
the SSL/TLS stream wrappers as described in
https://wiki.php.net/rfc/url_parsing_api#plugability. (kocsismate)
. Fixed bug GH-19548 (Shared memory violation on property inheritance).
(alexandre-daubois)
. Fixed bug GH-19544 (GC treats ZEND_WEAKREF_TAG_MAP references as WeakMap
references). (Arnaud, timwolla)
. Introduced the TAILCALL VM, enabled by default when compiling with Clang>=19
on x86_64 or aarch64. (Arnaud)

- Filter:
. Added support for configuring the URI parser for FILTER_VALIDATE_URL
Expand Down Expand Up @@ -48,6 +54,10 @@ PHP NEWS
(Girgias)
. Added support for partitioned cookies. (nielsdos)

- Tokenizer:
. Fixed bug GH-19507 (Corrupted result after recursive tokenization during
token_get_all()). (kubawerlos, nielsdos, Arnaud)

14 Aug 2025, PHP 8.5.0beta1

- Core:
Expand Down
5 changes: 5 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,11 @@ PHP 8.5 UPGRADE NOTES
. Creating exception objects is now much faster.
. The parts of the code that used SSE2 have been adapted to use SIMD
with ARM NEON as well.
. Introduced the TAILCALL VM, enabled by default when compiling with Clang>=19
on x86_64 or aarch64. The TAILCALL VM is as fast as the HYBRID VM used when
compiling with GCC. This makes PHP binaries built with Clang>=19 as fast as
binaries built with GCC. The performance of the CALL VM, used with other
compilers, has also improved considerably.

- Intl:
. Now avoids creating extra string copies when converting strings
Expand Down
101 changes: 101 additions & 0 deletions Zend/Zend.m4
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ ZEND_CHECK_STACK_DIRECTION
ZEND_CHECK_FLOAT_PRECISION
ZEND_DLSYM_CHECK
ZEND_CHECK_GLOBAL_REGISTER_VARIABLES
ZEND_CHECK_PRESERVE_NONE
ZEND_CHECK_CPUID_COUNT

AC_MSG_CHECKING([whether to enable thread safety])
Expand Down Expand Up @@ -464,3 +465,103 @@ AS_VAR_IF([ZEND_MAX_EXECUTION_TIMERS], [yes],
AC_MSG_CHECKING([whether to enable Zend max execution timers])
AC_MSG_RESULT([$ZEND_MAX_EXECUTION_TIMERS])
])

dnl
dnl ZEND_CHECK_PRESERVE_NONE
dnl
dnl Check if the preserve_none calling convention is supported and matches our
dnl expectations.
dnl
AC_DEFUN([ZEND_CHECK_PRESERVE_NONE], [dnl
AC_CACHE_CHECK([for preserve_none calling convention],
[php_cv_preverve_none],
[AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <stdio.h>
#include <stdint.h>

const char * const1 = "str1";
const char * const2 = "str2";
const char * const3 = "str3";
uint64_t key = UINT64_C(0x9d7f71d2bd296364);

uintptr_t _a = 0;
uintptr_t _b = 0;

uintptr_t __attribute__((preserve_none)) fun(uintptr_t a, uintptr_t b) {
_a = a;
_b = b;
return (uintptr_t)const3;
}

uintptr_t __attribute__((preserve_none)) test(void) {
uintptr_t ret;

#if defined(__x86_64__)
__asm__ __volatile__(
/* XORing to make it unlikely the value exists in any other register */
"movq %1, %%r12\n"
"xorq %3, %%r12\n"
"movq %2, %%r13\n"
"xorq %3, %%r13\n"
"xorq %%rax, %%rax\n"
"call fun\n"
: "=a" (ret)
: "r" (const1), "r" (const2), "r" (key)
: "r12", "r13"
);
#elif defined(__aarch64__)
__asm__ __volatile__(
/* XORing to make it unlikely the value exists in any other register */
"eor x20, %1, %3\n"
"eor x21, %2, %3\n"
"eor x0, x0, x0\n"
"bl fun\n"
"mov %0, x0\n"
: "=r" (ret)
: "r" (const1), "r" (const2), "r" (key)
: "x0", "x21", "x22", "x30"
);
#else
# error
#endif

return ret;
}

int main(void) {

/* JIT is making the following expectations about preserve_none:
* - The registers used for integer args 1 and 2
* - The register used for a single integer return value
*
* We check these expectations here:
*/

uintptr_t ret = test();

if (_a != ((uintptr_t)const1 ^ key)) {
fprintf(stderr, "arg1 mismatch\n");
return 1;
}
if (_b != ((uintptr_t)const2 ^ key)) {
fprintf(stderr, "arg2 mismatch\n");
return 2;
}
if (ret != (uintptr_t)const3) {
fprintf(stderr, "ret mismatch\n");
return 3;
}

fprintf(stderr, "OK\n");

return 0;
}]])],
[php_cv_preserve_none=yes],
[php_cv_preserve_none=no],
[php_cv_preserve_none=no])
])
AS_VAR_IF([php_cv_preserve_none], [yes], [
AC_DEFINE([HAVE_PRESERVE_NONE], [1],
[Define to 1 if you have preserve_none support.])
])
])
17 changes: 17 additions & 0 deletions Zend/tests/gh19543-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
GH-19543 001: GC treats ZEND_WEAKREF_TAG_MAP references as WeakMap references
--EXTENSIONS--
zend_test
--FILE--
<?php

$e = new Exception();
$a = new stdClass();
zend_weakmap_attach($e, $a);
unset($a);
gc_collect_cycles();

?>
==DONE==
--EXPECT--
==DONE==
19 changes: 19 additions & 0 deletions Zend/tests/gh19543-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
GH-19543 002: GC treats ZEND_WEAKREF_TAG_MAP references as WeakMap references
--EXTENSIONS--
zend_test
--FILE--
<?php

$e = new Exception();
$a = new stdClass();
zend_weakmap_attach($e, $a);
unset($a);
$e2 = $e;
unset($e2); // add to roots
gc_collect_cycles();

?>
==DONE==
--EXPECT--
==DONE==
19 changes: 19 additions & 0 deletions Zend/tests/property_hooks/gh19548.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
GH-19548: Segfault when using inherited properties and opcache
--FILE--
<?php

interface I {
public mixed $i { get; }
}
class P {
public mixed $i;
}

class C extends P implements I {}

echo "Test passed - no segmentation fault\n";

?>
--EXPECT--
Test passed - no segmentation fault
29 changes: 29 additions & 0 deletions Zend/tests/property_hooks/gh19548_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
GH-19548: Segfault when using inherited properties and opcache (multiple properties)
--FILE--
<?php

interface I1 {
public mixed $a { get; }
public mixed $b { get; }
}
class P1 {
public mixed $a;
public mixed $b;
}
class C1 extends P1 implements I1 {}

interface I2 {
public mixed $prop { get; }
}
class P2 {
public mixed $prop;
}
class Q2 extends P2 {}
class C2 extends Q2 implements I2 {}

echo "Multiple property test passed - no segmentation fault\n";

?>
--EXPECT--
Multiple property test passed - no segmentation fault
4 changes: 3 additions & 1 deletion Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -1564,7 +1564,9 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke
ZSTR_VAL(parent_info->ce->name));
}

child_info->flags &= ~ZEND_ACC_OVERRIDE;
if (child_info->ce == ce) {
child_info->flags &= ~ZEND_ACC_OVERRIDE;
}
}
} else {
zend_function **hooks = parent_info->hooks;
Expand Down
20 changes: 19 additions & 1 deletion Zend/zend_language_scanner.l
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ void shutdown_scanner(void)
zend_ptr_stack_destroy(&SCNG(heredoc_label_stack));
SCNG(heredoc_scan_ahead) = 0;
SCNG(on_event) = NULL;
SCNG(on_event_context) = NULL;
}

ZEND_API void zend_save_lexical_state(zend_lex_state *lex_state)
Expand Down Expand Up @@ -581,6 +582,8 @@ ZEND_API zend_result open_file_for_scanning(zend_file_handle *file_handle)
zend_set_compiled_filename(compiled_filename);
zend_string_release_ex(compiled_filename, 0);

SCNG(on_event) = NULL;
SCNG(on_event_context) = NULL;
RESET_DOC_COMMENT();
CG(zend_lineno) = 1;
CG(increment_lineno) = 0;
Expand Down Expand Up @@ -766,6 +769,8 @@ ZEND_API void zend_prepare_string_for_scanning(zval *str, zend_string *filename)
zend_set_compiled_filename(filename);
CG(zend_lineno) = 1;
CG(increment_lineno) = 0;
SCNG(on_event) = NULL;
SCNG(on_event_context) = NULL;
RESET_DOC_COMMENT();
}

Expand Down Expand Up @@ -921,7 +926,7 @@ ZEND_API void zend_multibyte_yyinput_again(zend_encoding_filter old_input_filter
ZVAL_STRINGL(zendlval, yytext, yyleng); \
}

static zend_result zend_scan_escape_string(zval *zendlval, char *str, int len, char quote_type)
static zend_result zend_scan_escape_string(zval *zendlval, char *str, size_t len, char quote_type)
{
char *s, *t;
char *end;
Expand Down Expand Up @@ -1636,6 +1641,9 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
<ST_IN_SCRIPTING>"("{TABS_AND_SPACES}("integer"){TABS_AND_SPACES}")" {
if (PARSER_MODE()) {
zend_error(E_DEPRECATED, "Non-canonical cast (integer) is deprecated, use the (int) cast instead");
if (PARSER_MODE() && EG(exception)) {
RETURN_TOKEN(T_ERROR);
}
}
RETURN_TOKEN(T_INT_CAST);
}
Expand All @@ -1647,6 +1655,9 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
<ST_IN_SCRIPTING>"("{TABS_AND_SPACES}("double"){TABS_AND_SPACES}")" {
if (PARSER_MODE()) {
zend_error(E_DEPRECATED, "Non-canonical cast (double) is deprecated, use the (float) cast instead");
if (PARSER_MODE() && EG(exception)) {
RETURN_TOKEN(T_ERROR);
}
}
RETURN_TOKEN(T_DOUBLE_CAST);
}
Expand All @@ -1666,6 +1677,9 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
<ST_IN_SCRIPTING>"("{TABS_AND_SPACES}("binary"){TABS_AND_SPACES}")" {
if (PARSER_MODE()) {
zend_error(E_DEPRECATED, "Non-canonical cast (binary) is deprecated, use the (string) cast instead");
if (PARSER_MODE() && EG(exception)) {
RETURN_TOKEN(T_ERROR);
}
}
RETURN_TOKEN(T_STRING_CAST);
}
Expand All @@ -1685,6 +1699,9 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_
<ST_IN_SCRIPTING>"("{TABS_AND_SPACES}("boolean"){TABS_AND_SPACES}")" {
if (PARSER_MODE()) {
zend_error(E_DEPRECATED, "Non-canonical cast (boolean) is deprecated, use the (bool) cast instead");
if (PARSER_MODE() && EG(exception)) {
RETURN_TOKEN(T_ERROR);
}
}
RETURN_TOKEN(T_BOOL_CAST);
}
Expand Down Expand Up @@ -2741,6 +2758,7 @@ skip_escape_conversion:
SCNG(heredoc_scan_ahead) = 1;
SCNG(heredoc_indentation) = 0;
SCNG(heredoc_indentation_uses_spaces) = 0;
SCNG(on_event_context) = NULL;
LANG_SCNG(on_event) = NULL;
CG(doc_comment) = NULL;

Expand Down
9 changes: 9 additions & 0 deletions Zend/zend_portability.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,15 @@ char *alloca();
# define ZEND_FASTCALL
#endif

#ifdef HAVE_PRESERVE_NONE
# define ZEND_PRESERVE_NONE __attribute__((preserve_none))
#endif

#if __has_attribute(musttail)
# define HAVE_MUSTTAIL
# define ZEND_MUSTTAIL __attribute__((musttail))
#endif

#if (defined(__GNUC__) && __GNUC__ >= 3 && !defined(__INTEL_COMPILER) && !defined(__APPLE__) && !defined(__hpux) && !defined(_AIX) && !defined(__osf__)) || __has_attribute(noreturn)
# define HAVE_NORETURN
# define ZEND_NORETURN __attribute__((noreturn))
Expand Down
7 changes: 5 additions & 2 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2962,7 +2962,10 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
{
zend_execute_data *old_execute_data;
uint32_t call_info = EX_CALL_INFO();
#if ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL
/* zend_leave_helper may be called with opline=call_leave_op in TAILCALL VM */
SAVE_OPLINE();
#endif

if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) {
EG(current_execute_data) = EX(prev_execute_data);
Expand Down Expand Up @@ -4917,7 +4920,7 @@ ZEND_VM_HOT_SEND_HANDLER(116, ZEND_SEND_VAL_EX, CONST|TMP, CONST|UNUSED|NUM, SPE
ZEND_VM_C_GOTO(send_val_by_ref);
}
} else if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
ZEND_VM_C_LABEL(send_val_by_ref):
ZEND_VM_C_LABEL(send_val_by_ref):;
ZEND_VM_DISPATCH_TO_HELPER(zend_cannot_pass_by_ref_helper, _arg_num, arg_num, _arg, arg);
}
value = GET_OP1_ZVAL_PTR(BP_VAR_R);
Expand Down Expand Up @@ -6162,7 +6165,7 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
}

bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
if (c->ce->type == ZEND_USER_CLASS) {
/* Recursion protection only applied to user constants, GH-18463 */
CONST_PROTECT_RECURSION(c);
Expand Down
Loading