gen_stub: Add support for attributes on constants in stubs (#18735)

Update to PHP-Parser 5.5.0 and add support for attributes on constants in
stubs. For now, I have only migrated over E_STRICT, once the support is in
place I'll do a larger migration of the existing deprecated constants.

In the process, fix the logic in `copy_zend_constant()` for copying attributes
when a constant is copied; just increase the reference count for the attributes
table rather than trying to duplicate the contents.
This commit is contained in:
DanielEScherzer 2025-06-05 14:46:46 -07:00 committed by GitHub
parent 99d56248f8
commit 8f3cdf6236
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 143 additions and 16 deletions

View file

@ -10,5 +10,5 @@ var_dump(E_STRICT);
--EXPECTF--
int(30719)
Deprecated: Constant E_STRICT is deprecated in %s on line %d
Deprecated: Constant E_STRICT is deprecated since 8.4, the error level was removed in %s on line %d
int(2048)

View file

@ -21,6 +21,7 @@
#define ZEND_ATTRIBUTES_H
#include "zend_compile.h"
#include "zend_constants.h"
#define ZEND_ATTRIBUTE_TARGET_CLASS (1<<0)
#define ZEND_ATTRIBUTE_TARGET_FUNCTION (1<<1)
@ -126,6 +127,12 @@ static zend_always_inline zend_attribute *zend_add_class_constant_attribute(zend
return zend_add_attribute(&c->attributes, name, argc, flags, 0, 0);
}
static zend_always_inline zend_attribute *zend_add_global_constant_attribute(zend_constant *c, zend_string *name, uint32_t argc)
{
uint32_t flags = ZEND_CONSTANT_MODULE_NUMBER(c) == PHP_USER_CONSTANT ? 0 : ZEND_ATTRIBUTE_PERSISTENT;
return zend_add_attribute(&c->attributes, name, argc, flags, 0, 0);
}
void zend_register_attribute_ce(void);
void zend_attributes_shutdown(void);

View file

@ -85,7 +85,8 @@ static void copy_zend_constant(zval *zv)
c->filename = zend_string_copy(c->filename);
}
if (c->attributes != NULL) {
c->attributes = zend_array_dup(c->attributes);
// Use the same attributes table
GC_ADDREF(c->attributes);
}
if (Z_TYPE(c->value) == IS_STRING) {
Z_STR(c->value) = zend_string_dup(Z_STR(c->value), 1);

View file

@ -71,9 +71,9 @@ const E_USER_NOTICE = UNKNOWN;
/**
* @var int
* @cvalue E_STRICT
* @deprecated
* @todo Remove in PHP 9.0
*/
#[\Deprecated(since: '8.4', message: 'the error level was removed')]
const E_STRICT = UNKNOWN;
/**

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 65be08c1bdace83ad1fa1175fc824262e07eac2a */
* Stub hash: 5e224893a5fb72b3f93249235c2a1634233ce505 */
static void register_zend_constants_symbols(int module_number)
{
@ -26,4 +26,18 @@ static void register_zend_constants_symbols(int module_number)
REGISTER_BOOL_CONSTANT("TRUE", true, CONST_PERSISTENT);
REGISTER_BOOL_CONSTANT("FALSE", false, CONST_PERSISTENT);
REGISTER_NULL_CONSTANT("NULL", CONST_PERSISTENT);
zend_constant *const_E_STRICT = zend_hash_str_find_ptr(EG(zend_constants), "E_STRICT", sizeof("E_STRICT") - 1);
zend_attribute *attribute_Deprecated_const_E_STRICT_0 = zend_add_global_constant_attribute(const_E_STRICT, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2);
zval attribute_Deprecated_const_E_STRICT_0_arg0;
zend_string *attribute_Deprecated_const_E_STRICT_0_arg0_str = zend_string_init("8.4", strlen("8.4"), 1);
ZVAL_STR(&attribute_Deprecated_const_E_STRICT_0_arg0, attribute_Deprecated_const_E_STRICT_0_arg0_str);
ZVAL_COPY_VALUE(&attribute_Deprecated_const_E_STRICT_0->args[0].value, &attribute_Deprecated_const_E_STRICT_0_arg0);
attribute_Deprecated_const_E_STRICT_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE);
zval attribute_Deprecated_const_E_STRICT_0_arg1;
zend_string *attribute_Deprecated_const_E_STRICT_0_arg1_str = zend_string_init("the error level was removed", strlen("the error level was removed"), 1);
ZVAL_STR(&attribute_Deprecated_const_E_STRICT_0_arg1, attribute_Deprecated_const_E_STRICT_0_arg1_str);
ZVAL_COPY_VALUE(&attribute_Deprecated_const_E_STRICT_0->args[1].value, &attribute_Deprecated_const_E_STRICT_0_arg1);
attribute_Deprecated_const_E_STRICT_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE);
}

View file

@ -2622,6 +2622,13 @@ class ConstInfo extends VariableLike
?ExposedDocComment $exposedDocComment,
bool $isFileCacheAllowed
) {
foreach ($attributes as $attr) {
if ($attr->class === "Deprecated") {
$isDeprecated = true;
break;
}
}
$this->name = $name;
$this->value = $value;
$this->valueString = $valueString;
@ -2915,17 +2922,11 @@ class ConstInfo extends VariableLike
{
$flags = parent::getFlagsByPhpVersion();
// $this->isDeprecated also accounts for any #[\Deprecated] attributes
if ($this->isDeprecated) {
$flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_DEPRECATED", PHP_80_VERSION_ID);
}
foreach ($this->attributes as $attr) {
if ($attr->class === "Deprecated") {
$flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_DEPRECATED", PHP_80_VERSION_ID);
break;
}
}
if ($this->flags & Modifiers::FINAL) {
$flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_FINAL", PHP_81_VERSION_ID);
}
@ -4340,7 +4341,7 @@ class FileInfo {
$cond,
$this->isUndocumentable,
$this->getMinimumPhpVersionIdCompatibility(),
[]
AttributeInfo::createFromGroups($stmt->attrGroups)
);
}
continue;
@ -5177,7 +5178,9 @@ function generateArgInfoCode(
$php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID;
if ($fileInfo->generateClassEntries) {
if ($attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null)) {
$attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null);
$attributeInitializationCode .= generateGlobalConstantAttributeInitialization($fileInfo->constInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null);
if ($attributeInitializationCode) {
if (!$php80MinimumCompatibility) {
$attributeInitializationCode = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")" . $attributeInitializationCode . "#endif\n";
}
@ -5289,6 +5292,51 @@ function generateFunctionAttributeInitialization(iterable $funcInfos, array $all
);
}
/**
* @param iterable<ConstInfo> $constInfos
* @param array<string, ConstInfo> $allConstInfos
*/
function generateGlobalConstantAttributeInitialization(
iterable $constInfos,
array $allConstInfos,
?int $phpVersionIdMinimumCompatibility,
?string $parentCond = null
): string {
$isConditional = false;
if ($phpVersionIdMinimumCompatibility !== null && $phpVersionIdMinimumCompatibility < PHP_85_VERSION_ID) {
$isConditional = true;
$phpVersionIdMinimumCompatibility = PHP_85_VERSION_ID;
}
$code = generateCodeWithConditions(
$constInfos,
"",
static function (ConstInfo $constInfo) use ($allConstInfos, $phpVersionIdMinimumCompatibility) {
if ($constInfo->attributes === []) {
return null;
}
$constName = str_replace('\\', '\\\\', $constInfo->name->__toString());
$constVarName = 'const_' . $constName;
$code .= "\tzend_constant *$constVarName = zend_hash_str_find_ptr(EG(zend_constants), \"" . $constName . "\", sizeof(\"" . $constName . "\") - 1);\n";
foreach ($constInfo->attributes as $key => $attribute) {
$code .= $attribute->generateCode(
"zend_add_global_constant_attribute($constVarName",
$constVarName . "_$key",
$allConstInfos,
$phpVersionIdMinimumCompatibility
);
}
return $code;
},
$parentCond
);
if ($code && $isConditional) {
return "\n#if (PHP_VERSION_ID >= " . PHP_85_VERSION_ID . ")\n" . $code . "#endif\n";
}
return $code;
}
/**
* @param iterable<ConstInfo> $constInfos
* @param array<string, ConstInfo> $allConstInfos
@ -6030,7 +6078,7 @@ function initPhpParser() {
}
$isInitialized = true;
$version = "5.3.1";
$version = "5.5.0";
$phpParserDir = __DIR__ . "/PHP-Parser-$version";
if (!is_dir($phpParserDir)) {
installPhpParser($version, $phpParserDir);

View file

@ -0,0 +1,17 @@
--TEST--
ReflectionConstant::getAttributes() with attribute (internal constant)
--FILE--
<?php
$reflectionConstant = new ReflectionConstant('E_STRICT');
var_dump($reflectionConstant->getAttributes());
?>
--EXPECTF--
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(10) "Deprecated"
}
}

View file

@ -17,6 +17,12 @@ namespace {
/** @var string */
const ZEND_CONSTANT_A = "global";
/**
* @var int
*/
#[\Deprecated(message: "use something else", since: "version 1.5")]
const ZEND_TEST_ATTRIBUTED_CONSTANT = 42;
interface _ZendTestInterface
{
/** @var int */

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 1fd4c80ed74efcc50698748b2afc89391ed69c72 */
* Stub hash: 37ac76dddea2da24d3275cccc748b8fea4c8d09c */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
ZEND_END_ARG_INFO()
@ -579,6 +579,7 @@ static void register_test_symbols(int module_number)
{
REGISTER_LONG_CONSTANT("ZEND_TEST_DEPRECATED", 42, CONST_PERSISTENT | CONST_DEPRECATED);
REGISTER_STRING_CONSTANT("ZEND_CONSTANT_A", "global", CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("ZEND_TEST_ATTRIBUTED_CONSTANT", 42, CONST_PERSISTENT | CONST_DEPRECATED);
REGISTER_STRING_CONSTANT("ZendTestNS2\\ZEND_CONSTANT_A", "namespaced", CONST_PERSISTENT);
REGISTER_STRING_CONSTANT("ZendTestNS2\\ZendSubNS\\ZEND_CONSTANT_A", "namespaced", CONST_PERSISTENT);
@ -635,6 +636,22 @@ static void register_test_symbols(int module_number)
ZVAL_STR(&attribute_ZendTestAttributeWithArguments_func_zend_test_attribute_with_named_argument_0_arg0, attribute_ZendTestAttributeWithArguments_func_zend_test_attribute_with_named_argument_0_arg0_str);
ZVAL_COPY_VALUE(&attribute_ZendTestAttributeWithArguments_func_zend_test_attribute_with_named_argument_0->args[0].value, &attribute_ZendTestAttributeWithArguments_func_zend_test_attribute_with_named_argument_0_arg0);
attribute_ZendTestAttributeWithArguments_func_zend_test_attribute_with_named_argument_0->args[0].name = zend_string_init_interned("arg", sizeof("arg") - 1, 1);
#if (PHP_VERSION_ID >= 80500)
zend_constant *const_ZEND_TEST_ATTRIBUTED_CONSTANT = zend_hash_str_find_ptr(EG(zend_constants), "ZEND_TEST_ATTRIBUTED_CONSTANT", sizeof("ZEND_TEST_ATTRIBUTED_CONSTANT") - 1);
zend_attribute *attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0 = zend_add_global_constant_attribute(const_ZEND_TEST_ATTRIBUTED_CONSTANT, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2);
zval attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg0;
zend_string *attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg0_str = zend_string_init("use something else", strlen("use something else"), 1);
ZVAL_STR(&attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg0, attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg0_str);
ZVAL_COPY_VALUE(&attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0->args[0].value, &attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg0);
attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE);
zval attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg1;
zend_string *attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg1_str = zend_string_init("version 1.5", strlen("version 1.5"), 1);
ZVAL_STR(&attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg1, attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg1_str);
ZVAL_COPY_VALUE(&attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0->args[1].value, &attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0_arg1);
attribute_Deprecated_const_ZEND_TEST_ATTRIBUTED_CONSTANT_0->args[1].name = ZSTR_KNOWN(ZEND_STR_SINCE);
#endif
}
static zend_class_entry *register_class__ZendTestInterface(void)

View file

@ -17,6 +17,12 @@ $reflection = new ReflectionClassConstant('_ZendTestClass', 'ZEND_TEST_DEPRECATE
var_dump($reflection->getAttributes()[0]->newInstance());
var_dump($reflection->isDeprecated());
ZEND_TEST_ATTRIBUTED_CONSTANT;
$reflection = new ReflectionConstant('ZEND_TEST_ATTRIBUTED_CONSTANT');
var_dump($reflection->getAttributes()[0]->newInstance());
var_dump($reflection->isDeprecated());
?>
--EXPECTF--
Deprecated: Function zend_test_deprecated() is deprecated in %s on line %d
@ -38,3 +44,12 @@ object(Deprecated)#%d (2) {
NULL
}
bool(true)
Deprecated: Constant ZEND_TEST_ATTRIBUTED_CONSTANT is deprecated since version 1.5, use something else in %s on line %d
object(Deprecated)#%d (2) {
["message"]=>
string(18) "use something else"
["since"]=>
string(11) "version 1.5"
}
bool(true)

View file

@ -13,11 +13,13 @@ var_dump(get_defined_constants(true)["user"]);
?>
--EXPECT--
array(4) {
array(5) {
["ZEND_TEST_DEPRECATED"]=>
int(42)
["ZEND_CONSTANT_A"]=>
string(6) "global"
["ZEND_TEST_ATTRIBUTED_CONSTANT"]=>
int(42)
["ZendTestNS2\ZEND_CONSTANT_A"]=>
string(10) "namespaced"
["ZendTestNS2\ZendSubNS\ZEND_CONSTANT_A"]=>