Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ class A {
}
?>
DONE
--EXPECT--
DONE
--EXPECTF--
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ class A {
}
?>
DONE
--EXPECT--
DONE
--EXPECTF--
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ class A {
}
?>
DONE
--EXPECT--
DONE
--EXPECTF--
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
--TEST--
Cannot use a trait which references parent as a type in a class with no parent, DNF type
Cannot use a trait which references parent as a type in a class with no parent, non-simple union type
--FILE--
<?php

class T {}

trait TraitExample {
public function bar(): (X&Y)|parent { return parent::class; }
public function bar(): T|parent { return parent::class; }
}

class A {
use TraitExample;
}
?>
DONE
--EXPECT--
DONE
--EXPECTF--
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Cannot use a trait which references parent as a type in a class with no parent, DNF type
--FILE--
<?php
trait TraitExample {
public function bar(): (X&Y)|parent { return parent::class; }
}

class A {
use TraitExample;
}
?>
DONE
--EXPECTF--
Fatal error: Cannot use trait which has "parent" as a type when current class scope has no parent in %s on line %d
116 changes: 116 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -2303,6 +2303,120 @@ static zend_class_entry *fixup_trait_scope(const zend_function *fn, zend_class_e
return fn->common.scope->ce_flags & ZEND_ACC_TRAIT ? ce : fn->common.scope;
}

/* If the type was resolved then either the zend_string pointer is different, or the zend_type_list is */
static inline bool zend_was_type_resolved(const zend_type *original_type, const zend_type *resolved_type)
{
return original_type->ptr != resolved_type->ptr;
}

// TODO Use resolve_class_name() somehow?
static zend_type zend_resolve_name_type(zend_type type, const zend_class_entry *const ce)
{
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(type));
if (zend_string_equals_ci(ZEND_TYPE_NAME(type), ZSTR_KNOWN(ZEND_STR_SELF))) {
zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(type));
return resolved_type;
} else if (zend_string_equals_ci(ZEND_TYPE_NAME(type), ZSTR_KNOWN(ZEND_STR_PARENT))) {
if (!ce->parent) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use trait which has \"parent\" as a type when current class scope has no parent");
}
zend_type resolved_type = (zend_type) ZEND_TYPE_INIT_CLASS(zend_string_copy(ce->parent->name), /* allows_null */ false, /* extra_flags */ ZEND_TYPE_FULL_MASK(type));
return resolved_type;
} else {
return type;
}
}

/* We cannot modify the type in-place (e.g. via a pointer) as it is written to SHM */
static zend_type zend_resolve_type(zend_type type, const zend_class_entry *const ce)
{
/* Only built-in types + static */
if (!ZEND_TYPE_IS_COMPLEX(type)) {
return type;
}

ZEND_ASSERT(ZEND_TYPE_HAS_NAME(type) || (ZEND_TYPE_HAS_LIST(type)));
if (ZEND_TYPE_HAS_NAME(type)) {
return zend_resolve_name_type(type, ce);
}

/* Intersection types cannot have un-resolved relative class types */
if (ZEND_TYPE_IS_INTERSECTION(type)) {
return type;
}

zend_type_list *union_type_list = ZEND_TYPE_LIST(type);
bool has_resolved_type = false;
zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(union_type_list, list_type) {
/* Intersections types cannot have self or parent */
if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
continue;
}
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type));
const zend_string *name = ZEND_TYPE_NAME(*list_type);
if (zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_SELF)) || zend_string_equals_ci(name, ZSTR_KNOWN(ZEND_STR_PARENT))) {
has_resolved_type = true;
break;
}
} ZEND_TYPE_LIST_FOREACH_END();

if (has_resolved_type) {
zend_type_copy_ctor(&type, true, false);
zend_type_list *new_union_type_list = ZEND_TYPE_LIST(type);

/* We don't use ZEND_TYPE_LIST_FOREACH() as we need to keep track of the array index */
for (uint32_t i = 0; i < union_type_list->num_types; i++) {
zend_type single_type = union_type_list->types[i];

/* Intersections types cannot have self or parent */
if (ZEND_TYPE_IS_INTERSECTION(single_type)) {
continue;
}

new_union_type_list->types[i] = zend_resolve_name_type(single_type, ce);
}
}
return type;
}

static void zend_resolve_trait_relative_class_types(zend_function *const fn, const zend_class_entry *const ce)
{
/* We are adding trait methods to another trait, delay resolution */
if (ce->ce_flags & ZEND_ACC_TRAIT) {
return;
}
/* No type info */
if (!fn->common.arg_info) {
return;
}

bool has_return_type = fn->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE;
/* Variadic parameters are not counted as part of the standard number of arguments */
bool has_variadic_type = fn->common.fn_flags & ZEND_ACC_VARIADIC;
uint32_t num_args = fn->common.num_args + has_variadic_type;
size_t allocated_size = sizeof(zend_arg_info) * (has_return_type + num_args);

zend_arg_info *new_arg_infos = fn->common.arg_info - has_return_type;
bool has_resolved_type = false;

for (uint32_t i = 0; i < num_args + has_return_type; i++) {
zend_type type = new_arg_infos[i].type;
zend_type resolved_type = zend_resolve_type(type, ce);
if (zend_was_type_resolved(&type, &resolved_type)) {
//ZEND_ASSERT(ZEND_TYPE_USES_ARENA(resolved_type));
if (!has_resolved_type) {
new_arg_infos = emalloc(allocated_size);
memcpy(new_arg_infos, fn->common.arg_info - has_return_type, allocated_size);
fn->common.arg_info = new_arg_infos + has_return_type;
has_resolved_type = true;
}
new_arg_infos[i].type = resolved_type;
}
}
}

static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_string *key, zend_function *fn) /* {{{ */
{
zend_function *existing_fn = NULL;
Expand Down Expand Up @@ -2349,10 +2463,12 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
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));
zend_resolve_trait_relative_class_types(new_fn, ce);
new_fn->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED;
} else {
new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_op_array));
memcpy(new_fn, fn, sizeof(zend_op_array));
zend_resolve_trait_relative_class_types(new_fn, ce);
new_fn->op_array.fn_flags &= ~ZEND_ACC_IMMUTABLE;
}
new_fn->common.fn_flags |= ZEND_ACC_TRAIT_CLONE;
Expand Down
Loading