Fix inheritance of class constants if mutable data used

Class constants from parents should always be directly reused,
rather than re-evaluated as a separate copy. Previously this used
to happen automatically, as we'd just inherit the class constant
entry from the parent class. With mutable data there may now be
a separate copy of the constant, so we need to use that copy
when updating constants. Otherwise we may evaluate the same
constant multiple times.

Closes GH-7658.
This commit is contained in:
Nikita Popov 2021-11-16 14:15:01 +01:00
parent 61b432ca24
commit 44e5d25300
3 changed files with 61 additions and 7 deletions

View file

@ -0,0 +1,35 @@
--TEST--
Class constant inheritance with mutable data
--FILE--
<?php
// This would previously leak under opcache.
class A {
const X = 'X' . self::Y;
const Y = 'Y';
}
interface I {
const X2 = 'X2' . self::Y2;
const Y2 = 'Y2';
}
eval('class B extends A implements I {}');
var_dump(new B);
var_dump(B::X, B::X2);
// This should only produce one warning, not two.
class X {
const C = 1 % 1.5;
}
class Y extends X {
}
var_dump(X::C, Y::C);
?>
--EXPECTF--
object(B)#1 (0) {
}
string(2) "XY"
string(4) "X2Y2"
Deprecated: Implicit conversion from float 1.5 to int loses precision in %s on line %d
int(0)
int(0)

View file

@ -1322,12 +1322,19 @@ ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_
zend_hash_extend(constants_table, zend_hash_num_elements(&class_type->constants_table), 0);
ZEND_HASH_FOREACH_STR_KEY_PTR(&class_type->constants_table, key, c) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
memcpy(new_c, c, sizeof(zend_class_constant));
c = new_c;
if (c->ce == class_type) {
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
new_c = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant));
memcpy(new_c, c, sizeof(zend_class_constant));
c = new_c;
}
Z_TRY_ADDREF(c->value);
} else {
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(c->ce), key);
ZEND_ASSERT(c);
}
}
Z_TRY_ADDREF(c->value);
_zend_hash_append_ptr(constants_table, key, c);
} ZEND_HASH_FOREACH_END();
@ -1409,8 +1416,18 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type) /
} else {
constants_table = &class_type->constants_table;
}
ZEND_HASH_FOREACH_PTR(constants_table, c) {
zend_string *name;
ZEND_HASH_FOREACH_STR_KEY_VAL(constants_table, name, val) {
c = Z_PTR_P(val);
if (Z_TYPE(c->value) == IS_CONSTANT_AST) {
if (c->ce != class_type) {
Z_PTR_P(val) = c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(c->ce), name);
if (Z_TYPE(c->value) != IS_CONSTANT_AST) {
continue;
}
}
val = &c->value;
if (UNEXPECTED(zval_update_constant_ex(val, c->ce) != SUCCESS)) {
return FAILURE;

View file

@ -250,7 +250,9 @@ ZEND_API void zend_cleanup_mutable_class_data(zend_class_entry *ce)
zend_class_constant *c;
ZEND_HASH_FOREACH_PTR(constants_table, c) {
zval_ptr_dtor_nogc(&c->value);
if (c->ce == ce) {
zval_ptr_dtor_nogc(&c->value);
}
} ZEND_HASH_FOREACH_END();
zend_hash_destroy(constants_table);
mutable_data->constants_table = NULL;