Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Zend/tests/arginfo_zpp_mismatch.inc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function skipFunction($function): bool {
|| $function === 'zend_test_array_return'
|| $function === 'zend_test_crash'
|| $function === 'zend_leak_bytes'
|| $function === 'zend_test_use_internal_traits_not_trait'
/* mess with output */
|| (is_string($function) && str_starts_with($function, 'ob_'))
|| $function === 'output_add_rewrite_var'
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ ZEND_API zend_class_entry *zend_register_internal_class_ex(const zend_class_entr
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);
ZEND_API zend_class_entry *zend_register_internal_interface(const zend_class_entry *orig_class_entry);
ZEND_API void zend_class_implements(zend_class_entry *class_entry, int num_interfaces, ...);
ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...);

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

Expand Down
62 changes: 60 additions & 2 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -2402,7 +2402,11 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
}
}

if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
if (ce->type == ZEND_INTERNAL_CLASS) {
ZEND_ASSERT(fn->type == ZEND_INTERNAL_FUNCTION);
new_fn = (zend_function*)(uintptr_t)malloc(sizeof(zend_internal_function));
memcpy(new_fn, fn, sizeof(zend_internal_function));
} else if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memcpy(new_fn, fn, sizeof(zend_internal_function));
new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED;
Expand Down Expand Up @@ -2823,7 +2827,11 @@ static void zend_do_traits_constant_binding(zend_class_entry *ce, zend_class_ent
if (do_trait_constant_check(ce, constant, constant_name, traits, i)) {
zend_class_constant *ct = NULL;

ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant));
if (ce->type == ZEND_INTERNAL_CLASS) {
ct = malloc(sizeof(zend_class_constant));
} else {
ct = zend_arena_alloc(&CG(arena),sizeof(zend_class_constant));
}
memcpy(ct, constant, sizeof(zend_class_constant));
constant = ct;

Expand Down Expand Up @@ -3004,6 +3012,56 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
}
/* }}} */

ZEND_API void zend_class_use_internal_traits(zend_class_entry *class_entry, int num_traits, ...) /* {{{ */
{
zend_class_entry *trait_entry;
va_list trait_list;
zend_class_entry **traits;
zend_function *fn;

ZEND_ASSERT(class_entry->ce_flags & ZEND_ACC_LINKED);
ZEND_ASSERT(num_traits >= 0);

if (UNEXPECTED(num_traits == 0)) {
return;
}

traits = safe_emalloc(num_traits, sizeof(zend_class_entry *), 0);
class_entry->trait_names = safe_pemalloc(num_traits, sizeof(zend_class_name), 0, 1);
class_entry->num_traits = num_traits;

va_start(trait_list, num_traits);
for (int i = 0; i < num_traits; i++) {
trait_entry = va_arg(trait_list, zend_class_entry *);
class_entry->trait_names[i].name = zend_string_copy(trait_entry->name);
class_entry->trait_names[i].lc_name = zend_string_tolower_ex(zend_string_copy(trait_entry->name), 1);

if (UNEXPECTED(!(trait_entry->ce_flags & ZEND_ACC_TRAIT))) {
efree(traits);
zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot use %s - it is not a trait",
ZSTR_VAL(class_entry->name), ZSTR_VAL(trait_entry->name));
}
traits[i] = trait_entry;
}
va_end(trait_list);

bool contains_abstract_methods = false;
zend_do_traits_method_binding(class_entry, traits, NULL, NULL, false, &contains_abstract_methods);

zend_do_traits_constant_binding(class_entry, traits);

zend_do_traits_property_binding(class_entry, traits);

ZEND_HASH_MAP_FOREACH_PTR(&class_entry->function_table, fn) {
zend_fixup_trait_method(fn, class_entry);
} ZEND_HASH_FOREACH_END();

efree(traits);

/* TODO: Verify abstract trait method implementation requirements are enforced. */
}
/* }}} */

#define MAX_ABSTRACT_INFO_CNT 3
#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
#define DISPLAY_ABSTRACT_FN(idx) \
Expand Down
9 changes: 8 additions & 1 deletion Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ ZEND_API void destroy_zend_class(zval *zv)
zend_string_release_ex(ce->name, 1);

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

if (fn->common.attributes) {
Expand Down Expand Up @@ -536,6 +536,13 @@ ZEND_API void destroy_zend_class(zval *zv)
if (ce->attributes) {
zend_hash_release(ce->attributes);
}
if (ce->num_traits > 0) {
for (uint32_t i = 0; i < ce->num_traits; i++) {
zend_string_release(ce->trait_names[i].name);
zend_string_release(ce->trait_names[i].lc_name);
}
free(ce->trait_names);
}
free(ce);
break;
}
Expand Down
29 changes: 29 additions & 0 deletions build/gen_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Trait_;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\PrettyPrinter\Standard;
use PhpParser\PrettyPrinterAbstract;

Expand Down Expand Up @@ -3414,6 +3415,8 @@ class ClassInfo {
private /* readonly */ array $extends;
/** @var Name[] */
private /* readonly */ array $implements;
/** @var Name[] */
private /* readonly */ array $uses;
/** @var ConstInfo[] */
public /* readonly */ array $constInfos;
/** @var PropertyInfo[] */
Expand All @@ -3430,6 +3433,7 @@ class ClassInfo {
* @param AttributeInfo[] $attributes
* @param Name[] $extends
* @param Name[] $implements
* @param Name[] $uses
* @param ConstInfo[] $constInfos
* @param PropertyInfo[] $propertyInfos
* @param FuncInfo[] $funcInfos
Expand All @@ -3448,6 +3452,7 @@ public function __construct(
bool $isNotSerializable,
array $extends,
array $implements,
array $uses,
array $constInfos,
array $propertyInfos,
array $funcInfos,
Expand All @@ -3468,6 +3473,7 @@ public function __construct(
$this->isNotSerializable = $isNotSerializable;
$this->extends = $extends;
$this->implements = $implements;
$this->uses = $uses;
$this->constInfos = $constInfos;
$this->propertyInfos = $propertyInfos;
$this->funcInfos = $funcInfos;
Expand All @@ -3487,6 +3493,9 @@ public function getRegistration(array $allConstInfos): string
foreach ($this->implements as $implements) {
$params[] = "zend_class_entry *class_entry_" . implode("_", $implements->getParts());
}
foreach ($this->uses as $use) {
$params[] = "zend_class_entry *class_entry_" . implode("_", $use->getParts());
}

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

Expand Down Expand Up @@ -3584,6 +3593,17 @@ function (Name $item) {
$code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n";
}

$traits = array_map(
function (Name $item) {
return "class_entry_" . implode("_", $item->getParts());
},
$this->uses
);

if (!empty($traits)) {
$code .= "\tzend_class_use_internal_traits(class_entry, " . count($traits) . ", " . implode(", ", $traits) . ");\n";
}

if ($this->alias) {
$code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n";
}
Expand Down Expand Up @@ -4385,6 +4405,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
$propertyInfos = [];
$methodInfos = [];
$enumCaseInfos = [];
$traitUses = [];
foreach ($stmt->stmts as $classStmt) {
$cond = self::handlePreprocessorConditions($conds, $classStmt);
if ($classStmt instanceof Stmt\Nop) {
Expand Down Expand Up @@ -4443,6 +4464,10 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
} else if ($classStmt instanceof Stmt\EnumCase) {
$enumCaseInfos[] = new EnumCaseInfo(
$classStmt->name->toString(), $classStmt->expr);
} else if ($classStmt instanceof TraitUse) {
foreach ($classStmt->traits as $trait) {
$traitUses[] = $trait;
}
} else {
throw new Exception("Not implemented {$classStmt->getType()}");
}
Expand All @@ -4455,6 +4480,7 @@ private function handleStatements(array $stmts, PrettyPrinterAbstract $prettyPri
$propertyInfos,
$methodInfos,
$enumCaseInfos,
$traitUses,
$cond,
$this->getMinimumPhpVersionIdCompatibility(),
$this->isUndocumentable
Expand Down Expand Up @@ -5000,6 +5026,7 @@ function parseProperty(
* @param PropertyInfo[] $properties
* @param FuncInfo[] $methods
* @param EnumCaseInfo[] $enumCases
* @param Name[] $traitUses
*/
function parseClass(
Name $name,
Expand All @@ -5008,6 +5035,7 @@ function parseClass(
array $properties,
array $methods,
array $enumCases,
array $traitUses,
?string $cond,
?int $minimumPhpVersionIdCompatibility,
bool $isUndocumentable
Expand Down Expand Up @@ -5083,6 +5111,7 @@ function parseClass(
$isNotSerializable,
$extends,
$implements,
$traitUses,
$consts,
$properties,
$methods,
Expand Down
28 changes: 28 additions & 0 deletions ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ static zend_class_entry *zend_test_child_class;
static zend_class_entry *zend_test_gen_stub_flag_compatibility_test;
static zend_class_entry *zend_attribute_test_class;
static zend_class_entry *zend_test_trait;
static zend_class_entry *zend_test_trait_for_internal_class;
static zend_class_entry *zend_test_class_with_trait;
static zend_class_entry *zend_test_not_a_trait_for_internal_traits;
static zend_class_entry *zend_test_internal_traits_driver;
static zend_class_entry *zend_test_attribute;
static zend_class_entry *zend_test_repeatable_attribute;
static zend_class_entry *zend_test_parameter_attribute;
Expand Down Expand Up @@ -1235,6 +1239,26 @@ static ZEND_METHOD(_ZendTestTrait, testMethod)
RETURN_TRUE;
}

static ZEND_METHOD(_ZendTestTraitForInternalClass, traitMethod)
{
ZEND_PARSE_PARAMETERS_NONE();
RETURN_LONG(789);
}

static ZEND_FUNCTION(zend_test_use_internal_traits_zero)
{
ZEND_PARSE_PARAMETERS_NONE();

zend_class_use_internal_traits(zend_test_internal_traits_driver, 0);
}

static ZEND_FUNCTION(zend_test_use_internal_traits_not_trait)
{
ZEND_PARSE_PARAMETERS_NONE();

zend_class_use_internal_traits(zend_test_internal_traits_driver, 1, zend_test_not_a_trait_for_internal_traits);
}

static ZEND_METHOD(ZendTestNS_Foo, method)
{
ZEND_PARSE_PARAMETERS_NONE();
Expand Down Expand Up @@ -1536,6 +1560,10 @@ PHP_MINIT_FUNCTION(zend_test)
zend_attribute_test_class = register_class_ZendAttributeTest();

zend_test_trait = register_class__ZendTestTrait();
zend_test_trait_for_internal_class = register_class__ZendTestTraitForInternalClass();
zend_test_class_with_trait = register_class__ZendTestClassWithTrait(zend_test_trait_for_internal_class);
zend_test_not_a_trait_for_internal_traits = register_class__ZendTestNotATraitForInternalTraits();
zend_test_internal_traits_driver = register_class__ZendTestInternalTraitsDriver();

register_test_symbols(module_number);

Expand Down
22 changes: 22 additions & 0 deletions ext/zend_test/test.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ interface _ZendTestInterface
public const DUMMY = 0;
}

trait _ZendTestTraitForInternalClass
{
/** @var int */
public const ZEND_TRAIT_CONST = 123;

public int $traitProp = 456;

public function traitMethod(): int {}
}

class _ZendTestClassWithTrait
{
use _ZendTestTraitForInternalClass;
}

class _ZendTestNotATraitForInternalTraits {}

class _ZendTestInternalTraitsDriver {}

function zend_test_use_internal_traits_zero(): void {}
function zend_test_use_internal_traits_not_trait(): void {}

/** @alias _ZendTestClassAlias */
class _ZendTestClass implements _ZendTestInterface {
public const mixed TYPED_CLASS_CONST1 = [];
Expand Down
Loading
Loading