Skip to content

Commit cd751f9

Browse files
Reapply phpGH-17712 with a fix for internal class constants (php#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 phpGH-18463 Fixes phpGH-17711
1 parent 78f03cd commit cd751f9

File tree

10 files changed

+138
-9
lines changed

10 files changed

+138
-9
lines changed

NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ PHP NEWS
66
. Fixed bug GH-18641 (Accessing a BcMath\Number property by ref crashes).
77
(nielsdos)
88

9+
- Core:
10+
. Fixed bugs GH-17711 and GH-18022 (Infinite recursion on deprecated attribute
11+
evaluation) and GH-18464 (Recursion protection for deprecation constants not
12+
released on bailout). (DanielEScherzer and ilutov)
13+
914
- Intl:
1015
. Fix memory leak in intl_datetime_decompose() on failure. (nielsdos)
1116

Lines changed: 28 additions & 0 deletions
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"
Lines changed: 20 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 8 additions & 1 deletion
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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 8 additions & 1 deletion
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

Lines changed: 48 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/opcache/ZendAccelerator.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3806,6 +3806,11 @@ static bool preload_try_resolve_constants(zend_class_entry *ce)
38063806
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->constants_table, key, c) {
38073807
val = &c->value;
38083808
if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
3809+
/* For deprecated constants, we need to flag the zval for recursion
3810+
* detection. Make sure the zval is separated out of shm. */
3811+
if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_DEPRECATED) {
3812+
ok = false;
3813+
}
38093814
if (EXPECTED(zend_update_class_constant(c, key, c->ce) == SUCCESS)) {
38103815
was_changed = changed = true;
38113816
} else {

0 commit comments

Comments
 (0)