Reapply GH-17712 with a fix for internal class constants (#18464)

Add recursion protection when emitting deprecation warnings for class
constants, since the deprecation message can come from an attribute that is
using the same constant for the message, or otherwise result in recursion.

But, internal constants are persisted, and thus cannot have recursion
protection. Otherwise, if a user error handler triggers bailout before the
recursion flag is removed then a subsequent request (e.g. with `--repeat 2`)
would start with that flag already applied. Internal constants can presumably
be trusted not to use deprecation messages that come from recursive attributes.

Fixes GH-18463
Fixes GH-17711
This commit is contained in:
DanielEScherzer 2025-05-25 16:43:36 -07:00 committed by GitHub
parent 78f03cd5f2
commit cd751f98cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 138 additions and 9 deletions

5
NEWS
View file

@ -6,6 +6,11 @@ PHP NEWS
. Fixed bug GH-18641 (Accessing a BcMath\Number property by ref crashes).
(nielsdos)
- Core:
. Fixed bugs GH-17711 and GH-18022 (Infinite recursion on deprecated attribute
evaluation) and GH-18464 (Recursion protection for deprecation constants not
released on bailout). (DanielEScherzer and ilutov)
- Intl:
. Fix memory leak in intl_datetime_decompose() on failure. (nielsdos)

View file

@ -0,0 +1,28 @@
--TEST--
GH-17711: Infinite recursion through deprecated class constants self-referencing through deprecation message
--FILE--
<?php
class C {
#[\Deprecated(self::C)]
const C = TEST;
}
const TEST = 'Message';
var_dump(C::C);
class D {
#[\Deprecated(Alias::C)]
const C = 'test';
}
class_alias('D', 'Alias');
var_dump(D::C);
?>
--EXPECTF--
Deprecated: Constant C::C is deprecated, Message in %s on line %d
string(7) "Message"
Deprecated: Constant D::C is deprecated, test in %s on line %d
string(4) "test"

View file

@ -0,0 +1,20 @@
--TEST--
GH-18463: Recursion protection should not be applied to internal class constants
--EXTENSIONS--
zend_test
--FILE--
<?php
function handler($errno, $errstr, $errfile, $errline) {
echo "$errstr in $errfile on line $errline\n";
eval('class string {}');
}
set_error_handler('handler');
var_dump(_ZendTestClass::ZEND_TEST_DEPRECATED);
?>
--EXPECTF--
Constant _ZendTestClass::ZEND_TEST_DEPRECATED is deprecated in %s on line %d
Fatal error: Cannot use "string" as a class name as it is reserved in %s(%d) : eval()'d code on line %d

View file

@ -1439,7 +1439,7 @@ ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) {
if (c->ce == class_type) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST || (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED)) {
new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
memcpy(new_c, c, sizeof(zend_class_constant));
c = new_c;

View file

@ -8822,6 +8822,10 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
if (deprecated) {
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_DEPRECATED;
/* For deprecated constants, we need to flag the zval for recursion
* detection. Make sure the zval is separated out of shm. */
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
}
}
}

View file

@ -353,8 +353,15 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string *
}
if (UNEXPECTED(ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED)) {
if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) {
if ((flags & ZEND_FETCH_CLASS_SILENT) == 0 && !CONST_IS_RECURSIVE(c)) {
if (c->ce->type == ZEND_USER_CLASS) {
/* Recursion protection only applied to user constants, GH-18463 */
CONST_PROTECT_RECURSION(c);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
goto failure;
}

View file

@ -27,6 +27,17 @@
#define CONST_NO_FILE_CACHE (1<<1) /* Can't be saved in file cache */
#define CONST_DEPRECATED (1<<2) /* Deprecated */
#define CONST_OWNED (1<<3) /* constant should be destroyed together with class */
#define CONST_RECURSIVE (1<<4) /* Recursion protection for constant evaluation */
#define CONST_IS_RECURSIVE(c) (Z_CONSTANT_FLAGS((c)->value) & CONST_RECURSIVE)
#define CONST_PROTECT_RECURSION(c) \
do { \
Z_CONSTANT_FLAGS((c)->value) |= CONST_RECURSIVE; \
} while (0)
#define CONST_UNPROTECT_RECURSION(c) \
do { \
Z_CONSTANT_FLAGS((c)->value) &= ~CONST_RECURSIVE; \
} while (0)
#define PHP_USER_CONSTANT 0x7fffff /* a constant defined in user space */

View file

@ -6094,8 +6094,15 @@ 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)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));

54
Zend/zend_vm_execute.h generated
View file

@ -7615,8 +7615,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS
}
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
@ -8776,8 +8783,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_CONS
}
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
@ -25882,8 +25896,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_
}
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
@ -26452,8 +26473,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_VAR_
}
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
@ -35294,8 +35322,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS
}
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));
@ -35654,8 +35689,15 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_CONSTANT_SPEC_UNUS
}
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
if (UNEXPECTED(is_constant_deprecated)) {
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);
}
zend_deprecated_class_constant(c, constant_name);
if (c->ce->type == ZEND_USER_CLASS) {
CONST_UNPROTECT_RECURSION(c);
}
if (EG(exception)) {
ZVAL_UNDEF(EX_VAR(opline->result.var));

View file

@ -3806,6 +3806,11 @@ static bool preload_try_resolve_constants(zend_class_entry *ce)
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) {
val = &c->value;
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
/* For deprecated constants, we need to flag the zval for recursion
* detection. Make sure the zval is separated out of shm. */
if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED) {
ok = false;
}
if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) {
was_changed = changed = true;
} else {