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
12 changes: 10 additions & 2 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1709,7 +1709,7 @@ static bool zend_try_ct_eval_const(zval *zv, zend_string *name, bool is_fully_qu
}
/* }}} */

static inline bool zend_is_scope_known(void) /* {{{ */
static inline bool zend_is_scope_known_ex(bool allow_traits) /* {{{ */
{
if (!CG(active_op_array)) {
/* This can only happen when evaluating a default value string. */
Expand All @@ -1728,10 +1728,14 @@ static inline bool zend_is_scope_known(void) /* {{{ */
}

/* For traits self etc refers to the using class, not the trait itself */
return (CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT) == 0;
return allow_traits || (CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT) == 0;
}
/* }}} */

static inline bool zend_is_scope_known(void) {
return zend_is_scope_known_ex(false);
}

static inline bool class_name_refers_to_active_ce(const zend_string *class_name, uint32_t fetch_type) /* {{{ */
{
if (!CG(active_class_entry)) {
Expand Down Expand Up @@ -7420,6 +7424,10 @@ static zend_type zend_compile_single_typename(zend_ast *ast)
ZEND_ASSERT(class_name && "must know class name when resolving parent type at compile time");
}
}
if (zend_is_scope_known_ex(true)) {
zend_op_array *op_array = CG(active_op_array);
op_array->fn_flags2 = ZEND_ACC_RESOLVE_RELATIVE_TYPE;
}
zend_string_addref(class_name);
}

Expand Down
3 changes: 2 additions & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,8 @@ typedef struct _zend_oparray_context {
/* Function Flags 2 (fn_flags2) (unused: 0-31) | | | */
/* ============================ | | | */
/* | | | */
/* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */
/* method had self or parent type from trait | | | */
#define ZEND_ACC_RESOLVE_RELATIVE_TYPE (1 << 1) /* | X | | */

#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE)
#define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET)
Expand Down
77 changes: 77 additions & 0 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -2361,6 +2361,74 @@ 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;
}

static void zend_resolve_type(zend_type *type, const zend_class_entry *const ce)
{
/* We are adding trait methods to another trait, delay resolution */
if (ce->ce_flags & ZEND_ACC_TRAIT) {
return;
}
/* Only built-in types + static */
if (!ZEND_TYPE_IS_COMPLEX(*type)) {
return;
}
/* Intersection types cannot have un-resolved relative class types */
if (ZEND_TYPE_IS_INTERSECTION(*type)) {
return;
}

ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*type) || (ZEND_TYPE_HAS_LIST(*type)));
if (ZEND_TYPE_HAS_NAME(*type)) {
if (zend_string_equals_ci(ZEND_TYPE_NAME(*type), ZSTR_KNOWN(ZEND_STR_SELF))) {
ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->name));
} 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");
}

if (ce->ce_flags & ZEND_ACC_RESOLVED_PARENT) {
ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->parent->name));
} else {
ZEND_TYPE_SET_PTR(*type, zend_string_copy(ce->parent_name));
}
}
return;
}

zend_type_list *union_type_list = ZEND_TYPE_LIST(*type);
zend_type *list_type;
ZEND_TYPE_LIST_FOREACH_MUTABLE(union_type_list, list_type) {
zend_resolve_type(list_type, ce);
} ZEND_TYPE_LIST_FOREACH_END();
}

static void zend_resolve_trait_relative_class_types(zend_function *const fn, const zend_class_entry *const ce)
{
/* 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;

/* We can allocate the arg_infos on the arena as the fn is also allocated on the arena */
new_arg_infos = zend_arena_alloc(&CG(arena), 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;

for (uint32_t i = 0; i < num_args + has_return_type; i++) {
zend_type type = new_arg_infos[i].type;
zend_type_copy_ctor(&type, true, ce->type == ZEND_INTERNAL_CLASS);
zend_resolve_type(&type, ce);
}
}

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 @@ -2415,6 +2483,9 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
/* Reassign method name, in case it is an alias. */
new_fn->common.function_name = name;
function_add_ref(new_fn);
if (new_fn->op_array.fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) {
zend_resolve_trait_relative_class_types(new_fn, ce);
}
fn = zend_hash_update_ptr(&ce->function_table, key, new_fn);
zend_add_magic_method(ce, fn, key);
}
Expand Down Expand Up @@ -2959,6 +3030,8 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
zend_type type = property_info->type;
/* Assumption: only userland classes can use traits, as such the type must be arena allocated */
zend_type_copy_ctor(&type, /* use arena */ true, /* persistent */ false);
/* Resolve possible self/parent types */
zend_resolve_type(&type, ce);
zend_property_info *new_prop = zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, type);

if (property_info->attributes) {
Expand Down Expand Up @@ -2989,6 +3062,10 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent

zend_fixup_trait_method(new_fn, ce);

if (new_fn->op_array.fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) {
zend_resolve_trait_relative_class_types(new_fn, ce);
}

hooks[j] = new_fn;
}
}
Expand Down
47 changes: 30 additions & 17 deletions Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,28 @@ ZEND_API void zend_destroy_static_vars(zend_op_array *op_array)
}
}

static void zend_destroy_arg_infos_from_op_array(zend_op_array *op_array) {

uint32_t num_args = op_array->num_args;
zend_arg_info *arg_info = op_array->arg_info;

if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
arg_info--;
num_args++;
}
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
for (uint32_t i = 0 ; i < num_args; i++) {
if (arg_info[i].name) {
zend_string_release_ex(arg_info[i].name, 0);
}
zend_type_release(arg_info[i].type, /* persistent */ false);
}
efree(arg_info);
op_array->arg_info = NULL;
}

ZEND_API void destroy_op_array(zend_op_array *op_array)
{
uint32_t i;
Expand All @@ -574,9 +596,16 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
zend_string_release_ex(op_array->function_name, 0);
}

//if (op_array->refcount == NULL) {
// return;
//}

if (!op_array->refcount || --(*op_array->refcount) > 0) {
return;
}
if (op_array->fn_flags2 & ZEND_ACC_RESOLVE_RELATIVE_TYPE) {
zend_destroy_arg_infos_from_op_array(op_array);
}

efree_size(op_array->refcount, sizeof(*(op_array->refcount)));

Expand Down Expand Up @@ -634,23 +663,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
}
}
if (op_array->arg_info) {
uint32_t num_args = op_array->num_args;
zend_arg_info *arg_info = op_array->arg_info;

if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
arg_info--;
num_args++;
}
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
for (i = 0 ; i < num_args; i++) {
if (arg_info[i].name) {
zend_string_release_ex(arg_info[i].name, 0);
}
zend_type_release(arg_info[i].type, /* persistent */ false);
}
efree(arg_info);
zend_destroy_arg_infos_from_op_array(op_array);
}
if (op_array->static_variables) {
zend_array_destroy(op_array->static_variables);
Expand Down
Loading