Skip to content

Commit 63e26ab

Browse files
Add trait support for internal classes (GH-20976)
Fixes GH-20974 Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
1 parent 36d8547 commit 63e26ab

11 files changed

Lines changed: 359 additions & 9 deletions

UPGRADING.INTERNALS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ PHP 8.6 INTERNALS UPGRADE NOTES
146146
php_stream_copy_to_stream_ex(). The mmap-based copy fallback was removed.
147147
. Added zend_string_equals_cstr_ci().
148148
. Added zend_string_ends_with() and related variants.
149+
. Added trait support for internal classes.
149150

150151
========================
151152
2. Build system changes

Zend/zend_API.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(const zend_class_entr
395395
ZEND_API zend_class_entry *zend_register_internal_class_with_flags(const zend_class_entry *class_entry, zend_class_entry *parent_ce, uint32_t flags);
396396
ZEND_API zend_class_entry *zend_register_internal_interface(const zend_class_entry *orig_class_entry);
397397
ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_interfaces, ...);
398+
ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...);
398399

399400
ZEND_API zend_result zend_register_class_alias_ex(const char *name, size_t name_len, zend_class_entry *ce, bool persistent);
400401

Zend/zend_inheritance.c

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,7 +2403,11 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
24032403
}
24042404
}
24052405

2406-
if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
2406+
if (ce->type == ZEND_INTERNAL_CLASS) {
2407+
ZEND_ASSERT(fn->type == ZEND_INTERNAL_FUNCTION);
2408+
new_fn = (zend_function*)(uintptr_t)malloc(sizeof(zend_internal_function));
2409+
memcpy(new_fn, fn, sizeof(zend_internal_function));
2410+
} else if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
24072411
new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
24082412
memcpy(new_fn, fn, sizeof(zend_internal_function));
24092413
new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED;
@@ -2833,7 +2837,11 @@ static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_ent
28332837
if (do_trait_constant_check(ce, constant, constant_name, traits, i)) {
28342838
zend_class_constant *ct = NULL;
28352839

2836-
ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant));
2840+
if (ce->type == ZEND_INTERNAL_CLASS) {
2841+
ct = malloc(sizeof(zend_class_constant));
2842+
} else {
2843+
ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant));
2844+
}
28372845
memcpy(ct, constant, sizeof(zend_class_constant));
28382846
constant = ct;
28392847

@@ -3012,6 +3020,49 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
30123020
}
30133021
/* }}} */
30143022

3023+
ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...)
3024+
{
3025+
ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED);
3026+
ZEND_ASSERT(num_traits >= 0);
3027+
3028+
if (UNEXPECTED(num_traits == 0)) {
3029+
return;
3030+
}
3031+
3032+
zend_class_entry **traits = safe_pemalloc(num_traits, sizeof(zend_class_entry *), 0, /* persistent */ true);
3033+
class_entry->trait_names = safe_pemalloc(num_traits, sizeof(zend_class_name), 0, /* persistent */ true);
3034+
class_entry->num_traits = num_traits;
3035+
3036+
va_list trait_list;
3037+
va_start(trait_list, num_traits);
3038+
for (int i = 0; i < num_traits; i++) {
3039+
zend_class_entry *trait_entry = va_arg(trait_list, zend_class_entry *);
3040+
class_entry->trait_names[i].name = zend_string_copy(trait_entry->name);
3041+
class_entry->trait_names[i].lc_name = zend_string_tolower_ex(zend_string_copy(trait_entry->name), /* persistent */ true);
3042+
3043+
if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) {
3044+
free(traits);
3045+
zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot use %s - it is not a trait",
3046+
ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name));
3047+
}
3048+
traits[i] = trait_entry;
3049+
}
3050+
va_end(trait_list);
3051+
3052+
bool contains_abstract_methods = false;
3053+
zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods);
3054+
zend_do_traits_constant_binding(class_entry, traits);
3055+
zend_do_traits_property_binding(class_entry, traits);
3056+
3057+
ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, zend_function *fn) {
3058+
zend_fixup_trait_method(fn, class_entry);
3059+
} ZEND_HASH_FOREACH_END();
3060+
3061+
free(traits);
3062+
3063+
/* TODO: Verify abstract trait method implementation requirements are enforced. */
3064+
}
3065+
30153066
#define MAX_ABSTRACT_INFO_CNT 3
30163067
#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
30173068
#define DISPLAY_ABSTRACT_FN(idx) \

Zend/zend_opcode.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ ZEND_API void destroy_zend_class(zval *zv)
485485
zend_string_release_ex(ce->name, 1);
486486

487487
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) {
488-
if (fn->common.scope == ce) {
488+
if (fn->common.scope == ce && !(fn->common.fn_flags & ZEND_ACC_TRAIT_CLONE)) {
489489
zend_free_internal_arg_info(&fn->internal_function, true);
490490

491491
if (fn->common.attributes) {
@@ -535,6 +535,13 @@ ZEND_API void destroy_zend_class(zval *zv)
535535
if (ce->attributes) {
536536
zend_hash_release(ce->attributes);
537537
}
538+
if (ce->num_traits > 0) {
539+
for (uint32_t i = 0; i < ce->num_traits; i++) {
540+
zend_string_release(ce->trait_names[i].name);
541+
zend_string_release(ce->trait_names[i].lc_name);
542+
}
543+
free(ce->trait_names);
544+
}
538545
free(ce);
539546
break;
540547
}

build/gen_stub.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use PhpParser\Node\Stmt\Enum_;
1414
use PhpParser\Node\Stmt\Interface_;
1515
use PhpParser\Node\Stmt\Trait_;
16+
use PhpParser\Node\Stmt\TraitUse;
1617
use PhpParser\PrettyPrinter\Standard;
1718
use PhpParser\PrettyPrinterAbstract;
1819

@@ -3383,6 +3384,7 @@ class ClassInfo {
33833384
* @param AttributeInfo[] $attributes
33843385
* @param Name[] $extends
33853386
* @param Name[] $implements
3387+
* @param Name[] $uses
33863388
* @param ConstInfo[] $constInfos
33873389
* @param PropertyInfo[] $propertyInfos
33883390
* @param FuncInfo[] $funcInfos
@@ -3401,6 +3403,7 @@ public function __construct(
34013403
private bool $isNotSerializable,
34023404
private readonly array $extends,
34033405
private readonly array $implements,
3406+
private readonly array $uses,
34043407
public /* readonly */ array $constInfos,
34053408
private /* readonly */ array $propertyInfos,
34063409
public array $funcInfos,
@@ -3421,6 +3424,9 @@ public function getRegistration(array $allConstInfos): string
34213424
foreach ($this->implements as $implements) {
34223425
$params[] = "zend_class_entry *class_entry_" . implode("_", $implements->getParts());
34233426
}
3427+
foreach ($this->uses as $use) {
3428+
$params[] = "zend_class_entry *class_entry_" . implode("_", $use->getParts());
3429+
}
34243430

34253431
$escapedName = implode("_", $this->name->getParts());
34263432

@@ -3518,6 +3524,17 @@ function (Name $item) {
35183524
$code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n";
35193525
}
35203526

3527+
$traits = array_map(
3528+
function (Name $item) {
3529+
return "class_entry_" . implode("_", $item->getParts());
3530+
},
3531+
$this->uses
3532+
);
3533+
3534+
if (!empty($traits)) {
3535+
$code .= "\tzend_class_use_internal_traits(class_entry, " . count($traits) . ", " . implode(", ", $traits) . ");\n";
3536+
}
3537+
35213538
if ($this->alias) {
35223539
$code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n";
35233540
}
@@ -4408,6 +4425,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
44084425
$propertyInfos = [];
44094426
$methodInfos = [];
44104427
$enumCaseInfos = [];
4428+
$traitUses = [];
44114429
foreach ($stmt->stmts as $classStmt) {
44124430
$cond = self::handlePreprocessorConditions($conds, $classStmt);
44134431
if ($classStmt instanceof Stmt\Nop) {
@@ -4469,6 +4487,13 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
44694487
$classStmt->expr,
44704488
$classStmt->expr ? $prettyPrinter->prettyPrintExpr($classStmt->expr) : null,
44714489
);
4490+
} else if ($classStmt instanceof TraitUse) {
4491+
if ($classStmt->adaptations) {
4492+
throw new Exception("Trait adaptations are not supported");
4493+
}
4494+
foreach ($classStmt->traits as $trait) {
4495+
$traitUses[] = $trait;
4496+
}
44724497
} else {
44734498
throw new Exception("Not implemented {$classStmt->getType()}");
44744499
}
@@ -4481,6 +4506,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
44814506
$propertyInfos,
44824507
$methodInfos,
44834508
$enumCaseInfos,
4509+
$traitUses,
44844510
$cond,
44854511
$this->getMinimumPhpVersionIdCompatibility(),
44864512
$this->isUndocumentable
@@ -5167,6 +5193,7 @@ function parseProperty(
51675193
* @param PropertyInfo[] $properties
51685194
* @param FuncInfo[] $methods
51695195
* @param EnumCaseInfo[] $enumCases
5196+
* @param Name[] $traitUses
51705197
*/
51715198
function parseClass(
51725199
Name $name,
@@ -5175,6 +5202,7 @@ function parseClass(
51755202
array $properties,
51765203
array $methods,
51775204
array $enumCases,
5205+
array $traitUses,
51785206
?string $cond,
51795207
?int $minimumPhpVersionIdCompatibility,
51805208
bool $isUndocumentable
@@ -5247,6 +5275,7 @@ function parseClass(
52475275
$isNotSerializable,
52485276
$extends,
52495277
$implements,
5278+
$traitUses,
52505279
$consts,
52515280
$properties,
52525281
$methods,

ext/zend_test/test.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ static zend_class_entry *zend_test_child_class;
5454
static zend_class_entry *zend_test_gen_stub_flag_compatibility_test;
5555
static zend_class_entry *zend_attribute_test_class;
5656
static zend_class_entry *zend_test_trait;
57+
static zend_class_entry *zend_test_trait_for_internal_class;
58+
static zend_class_entry *zend_test_trait_for_internal_class2;
59+
static zend_class_entry *zend_test_class_with_traits;
5760
static zend_class_entry *zend_test_attribute;
5861
static zend_class_entry *zend_test_repeatable_attribute;
5962
static zend_class_entry *zend_test_parameter_attribute;
@@ -1305,6 +1308,18 @@ static ZEND_METHOD(_ZendTestTrait, testMethod)
13051308
RETURN_TRUE;
13061309
}
13071310

1311+
static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod)
1312+
{
1313+
ZEND_PARSE_PARAMETERS_NONE();
1314+
RETURN_LONG(789);
1315+
}
1316+
1317+
static ZEND_METHOD(_ZendTestTraitForInternalClass2, traitMethod2)
1318+
{
1319+
ZEND_PARSE_PARAMETERS_NONE();
1320+
RETURN_LONG(101);
1321+
}
1322+
13081323
static ZEND_METHOD(ZendTestNS_Foo, method)
13091324
{
13101325
ZEND_PARSE_PARAMETERS_NONE();
@@ -1607,6 +1622,9 @@ PHP_MINIT_FUNCTION(zend_test)
16071622
zend_attribute_test_class = register_class_ZendAttributeTest();
16081623

16091624
zend_test_trait = register_class__ZendTestTrait();
1625+
zend_test_trait_for_internal_class = register_class__ZendTestTraitForInternalClass();
1626+
zend_test_trait_for_internal_class2 = register_class__ZendTestTraitForInternalClass2();
1627+
zend_test_class_with_traits = register_class__ZendTestClassWithTraits(zend_test_trait_for_internal_class, zend_test_trait_for_internal_class2);
16101628

16111629
register_test_symbols(module_number);
16121630

ext/zend_test/test.stub.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,32 @@ interface _ZendTestInterface
3535
public const DUMMY = 0;
3636
}
3737

38+
trait _ZendTestTraitForInternalClass
39+
{
40+
/** @var int */
41+
public const ZEND_TRAIT_CONST = 123;
42+
43+
public int $traitProp = 456;
44+
45+
public function traitMethod(): int {}
46+
}
47+
48+
trait _ZendTestTraitForInternalClass2
49+
{
50+
/** @var int */
51+
public const ZEND_TRAIT_CONST2 = 321;
52+
53+
public static int $staticTraitProp = 999;
54+
55+
public function traitMethod2(): int {}
56+
}
57+
58+
class _ZendTestClassWithTraits
59+
{
60+
use _ZendTestTraitForInternalClass;
61+
use _ZendTestTraitForInternalClass2;
62+
}
63+
3864
/** @alias _ZendTestClassAlias */
3965
class _ZendTestClass implements _ZendTestInterface {
4066
public const mixed TYPED_CLASS_CONST1 = [];

0 commit comments

Comments
 (0)