Skip to content

Commit 615b9ff

Browse files
Reapply GH-17712 with a fix for internal class constants
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
1 parent 386ab1d commit 615b9ff

File tree

10 files changed

+138
-11
lines changed

10 files changed

+138
-11
lines changed

NEWS

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ PHP NEWS
22
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
33
?? ??? ????, PHP 8.4.8
44

5+
- Core:
6+
. Fixed bugs GH-17711 and GH-18022 (Infinite recursion on deprecated attribute
7+
evaluation) and GH-18464 (Recursion protection for deprecation constants not
8+
released on bailout). (DanielEScherzer and ilutov)
9+
510
- Date:
611
. Fixed bug GH-18076 (Since PHP 8, the date_sun_info() function returns
712
inaccurate sunrise and sunset times, but other calculated times are
@@ -35,8 +40,6 @@ PHP NEWS
3540
24 Apr 2025, PHP 8.4.7
3641

3742
- Core:
38-
. Fixed bug GH-17711 and GH-18022 (Infinite recursion on deprecated attribute
39-
evaluation). (ilutov)
4043
. Fixed bug GH-18038 (Lazy proxy calls magic methods twice). (Arnaud)
4144
. Fixed bug GH-18209 (Use-after-free in extract() with EXTR_REFS). (ilutov)
4245
. Fixed bug GH-18268 (Segfault in array_walk() on object with added property
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
--TEST--
2+
GH-17711: Infinite recursion through deprecated class constants self-referencing through deprecation message
3+
--FILE--
4+
<?php
5+
6+
class C {
7+
#[\Deprecated(self::C)]
8+
const C = TEST;
9+
}
10+
11+
const TEST = 'Message';
12+
var_dump(C::C);
13+
14+
class D {
15+
#[\Deprecated(Alias::C)]
16+
const C = 'test';
17+
}
18+
19+
class_alias('D', 'Alias');
20+
var_dump(D::C);
21+
22+
?>
23+
--EXPECTF--
24+
Deprecated: Constant C::C is deprecated, Message in %s on line %d
25+
string(7) "Message"
26+
27+
Deprecated: Constant D::C is deprecated, test in %s on line %d
28+
string(4) "test"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
GH-18463: Recursion protection should not be applied to internal class constants
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
function handler($errno, $errstr, $errfile, $errline) {
9+
echo "$errstr in $errfile on line $errline\n";
10+
eval('class string {}');
11+
}
12+
13+
set_error_handler('handler');
14+
15+
var_dump(_ZendTestClass::ZEND_TEST_DEPRECATED);
16+
?>
17+
--EXPECTF--
18+
Constant _ZendTestClass::ZEND_TEST_DEPRECATED is deprecated in %s on line %d
19+
20+
Fatal error: Cannot use "string" as a class name as it is reserved in %s(%d) : eval()'d code on line %d

Zend/zend_API.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_
14391439

14401440
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) {
14411441
if (c->ce == class_type) {
1442-
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
1442+
if (Z_TYPE(c->value) == IS_CONSTANT_AST || (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED)) {
14431443
new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
14441444
memcpy(new_c, c, sizeof(zend_class_constant));
14451445
c = new_c;

Zend/zend_compile.c

+4
Original file line numberDiff line numberDiff line change
@@ -8822,6 +8822,10 @@ static void zend_compile_class_const_decl(zend_ast *ast, uint32_t flags, zend_as
88228822

88238823
if (deprecated) {
88248824
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_ACC_DEPRECATED;
8825+
/* For deprecated constants, we need to flag the zval for recursion
8826+
* detection. Make sure the zval is separated out of shm. */
8827+
ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS;
8828+
ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED;
88258829
}
88268830
}
88278831
}

Zend/zend_constants.c

+8-1
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,15 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string *
353353
}
354354

355355
if (UNEXPECTED(ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED)) {
356-
if ((flags & ZEND_FETCH_CLASS_SILENT) == 0) {
356+
if ((flags & ZEND_FETCH_CLASS_SILENT) == 0 && !CONST_IS_RECURSIVE(c)) {
357+
if (c->ce->type == ZEND_USER_CLASS) {
358+
/* Recursion protection only applied to user constants, GH-18463 */
359+
CONST_PROTECT_RECURSION(c);
360+
}
357361
zend_deprecated_class_constant(c, constant_name);
362+
if (c->ce->type == ZEND_USER_CLASS) {
363+
CONST_UNPROTECT_RECURSION(c);
364+
}
358365
if (EG(exception)) {
359366
goto failure;
360367
}

Zend/zend_constants.h

+11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@
2727
#define CONST_NO_FILE_CACHE (1<<1) /* Can't be saved in file cache */
2828
#define CONST_DEPRECATED (1<<2) /* Deprecated */
2929
#define CONST_OWNED (1<<3) /* constant should be destroyed together with class */
30+
#define CONST_RECURSIVE (1<<4) /* Recursion protection for constant evaluation */
31+
32+
#define CONST_IS_RECURSIVE(c) (Z_CONSTANT_FLAGS((c)->value) & CONST_RECURSIVE)
33+
#define CONST_PROTECT_RECURSION(c) \
34+
do { \
35+
Z_CONSTANT_FLAGS((c)->value) |= CONST_RECURSIVE; \
36+
} while (0)
37+
#define CONST_UNPROTECT_RECURSION(c) \
38+
do { \
39+
Z_CONSTANT_FLAGS((c)->value) &= ~CONST_RECURSIVE; \
40+
} while (0)
3041

3142
#define PHP_USER_CONSTANT 0x7fffff /* a constant defined in user space */
3243

Zend/zend_vm_def.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -6094,8 +6094,15 @@ ZEND_VM_HANDLER(181, ZEND_FETCH_CLASS_CONSTANT, VAR|CONST|UNUSED|CLASS_FETCH, CO
60946094
}
60956095

60966096
bool is_constant_deprecated = ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED;
6097-
if (UNEXPECTED(is_constant_deprecated)) {
6097+
if (UNEXPECTED(is_constant_deprecated) && !CONST_IS_RECURSIVE(c)) {
6098+
if (c->ce->type == ZEND_USER_CLASS) {
6099+
/* Recursion protection only applied to user constants, GH-18463 */
6100+
CONST_PROTECT_RECURSION(c);
6101+
}
60986102
zend_deprecated_class_constant(c, constant_name);
6103+
if (c->ce->type == ZEND_USER_CLASS) {
6104+
CONST_UNPROTECT_RECURSION(c);
6105+
}
60996106

61006107
if (EG(exception)) {
61016108
ZVAL_UNDEF(EX_VAR(opline->result.var));

Zend/zend_vm_execute.h

+48-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/opcache/ZendAccelerator.c

+5
Original file line numberDiff line numberDiff line change
@@ -3800,6 +3800,11 @@ static bool preload_try_resolve_constants(zend_class_entry *ce)
38003800
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) {
38013801
val = &c->value;
38023802
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3803+
/* For deprecated constants, we need to flag the zval for recursion
3804+
* detection. Make sure the zval is separated out of shm. */
3805+
if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED) {
3806+
ok = false;
3807+
}
38033808
if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) {
38043809
was_changed = changed = true;
38053810
} else {

0 commit comments

Comments
 (0)