Skip to content

DNF types bug fixes and others #11961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
16 changes: 16 additions & 0 deletions Zend/tests/disable_class_userland.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Disable non-internal class
--INI--
disable_classes=UserLand
--FILE--
<?php

class UserLand {}
$o = new UserLand();
var_dump($o);
?>
DONE
--EXPECT--
object(UserLand)#1 (0) {
}
DONE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Disable class with typed properties
--EXTENSIONS--
reflection
--INI--
disable_classes=Exception
--XFAIL--
Use After Free due to types being freed by zend_disable_class()
--FILE--
<?php

//$o = new ReflectionMethod(Exception::class, "__toString");
//var_dump($o);

$o = new Exception();
//var_dump($o);
?>
--EXPECTF--
Warning: ReflectionMethod() has been disabled for security reasons in %s
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
DNF typed properties in internal classes that is disabled
--EXTENSIONS--
zend_test
--INI--
disable_classes=_ZendTestClass
--XFAIL--
Use After Free due to types being freed by zend_disable_class()
--FILE--
<?php
$o = new _ZendTestClass();
//var_dump($o);
?>
--EXPECT--
string(42) "_ZendTestInterface|(Traversable&Countable)"
Cannot assign array to property _ZendTestClass::$dnfProperty of type _ZendTestInterface|(Traversable&Countable)
24 changes: 24 additions & 0 deletions Zend/tests/type_declarations/dnf_types/dnf_property_trait.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
DNF type in trait
--FILE--
<?php

trait A {
public A|(X&Y)|Traversable $v;
}

class C {
use A;
}

$o = new C();
var_dump($o);

?>
===DONE===
--EXPECT--
object(C)#1 (0) {
["v"]=>
uninitialized(A|(X&Y)|Traversable)
}
===DONE===
24 changes: 24 additions & 0 deletions Zend/tests/type_declarations/dnf_types/internal_dnf_property.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
DNF typed properties in internal classes
--EXTENSIONS--
zend_test
reflection
spl
--FILE--
<?php

$prop = new \ReflectionProperty(_ZendTestClass::class, 'dnfProperty');
$type = $prop->getType();
var_dump((string)$type);

$obj = new _ZendTestClass();
$obj->dnfProperty = new \ArrayIterator([1, 2, 3]);
try {
$obj->dnfProperty = [];
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
string(42) "_ZendTestInterface|(Traversable&Countable)"
Cannot assign array to property _ZendTestClass::$dnfProperty of type _ZendTestInterface|(Traversable&Countable)
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Property types must be invariant
--FILE--
<?php

interface X {}
interface Y {}

class A {
public (X&Y&Z)|L $prop;
}
class B extends A {
public (X&Y)|L $prop;
}

?>
--EXPECTF--
Fatal error: Type of B::$prop must be (X&Y&Z)|L (as in class A) in %s on line %d
4 changes: 4 additions & 0 deletions Zend/tests/type_declarations/typed_properties_095.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ object(_ZendTestClass)#1 (3) {
uninitialized(Traversable&Countable)
["readonlyProp"]=>
uninitialized(int)
["dnfProperty"]=>
uninitialized(_ZendTestInterface|(Traversable&Countable))
}
int(123)
Cannot assign string to property _ZendTestClass::$intProp of type int
Expand All @@ -91,6 +93,8 @@ object(Test)#4 (3) {
uninitialized(Traversable&Countable)
["readonlyProp"]=>
uninitialized(int)
["dnfProperty"]=>
uninitialized(_ZendTestInterface|(Traversable&Countable))
}
int(123)
Cannot assign string to property _ZendTestClass::$staticIntProp of type int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Internal trait used typed property (union type)
--EXTENSIONS--
zend_test
--FILE--
<?php

class C {
use _ZendTestTrait;
}

$o = new C();
var_dump($o);

$prop = new \ReflectionProperty(C::class, 'testTypedProp');
$union = $prop->getType();
$types = $union->getTypes();
var_dump($types, (string)$types[0], (string)$types[1]);

?>
===DONE===
--EXPECT--
object(C)#1 (1) {
["testProp"]=>
NULL
["testTypedProp"]=>
uninitialized(Traversable|Countable)
}
array(2) {
[0]=>
object(ReflectionNamedType)#4 (0) {
}
[1]=>
object(ReflectionNamedType)#5 (0) {
}
}
string(11) "Traversable"
string(9) "Countable"
===DONE===
69 changes: 67 additions & 2 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -2756,6 +2756,50 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arg_info_toString, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()

#ifdef ZEND_DEBUG
ZEND_API bool zend_verify_type_is_valid(zend_type type, int nesting_level, bool is_arena_alloc)
{
bool status = false;

if (!ZEND_TYPE_IS_SET(type)) {
return nesting_level == 0;
} else if (ZEND_TYPE_IS_ONLY_MASK(type)) {
return true;
} else if (ZEND_TYPE_HAS_NAME(type)) {
zend_string *name = ZEND_TYPE_NAME(type);
if (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_ITERABLE))) {
printf("iterable type should not exist at level %d\n", nesting_level);
return false;
}
return true;
} else if (ZEND_TYPE_HAS_LIST(type)) {
if (!ZEND_TYPE_IS_INTERSECTION(type) && !ZEND_TYPE_IS_UNION(type)) {
printf("Missing type list variant at level %d\n", nesting_level);
return false;
}
if (ZEND_TYPE_USES_ARENA(type) != is_arena_alloc) {
printf("Unexpected allocation type at level %d\n", nesting_level);
return false;
}
if (nesting_level >= 2) {
printf("Nesting exceeded: %d\n", nesting_level);
return false;
}

zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type);
status = zend_verify_type_is_valid(*list_type, nesting_level + 1, is_arena_alloc);
if (!status) {
return false;
}
ZEND_TYPE_LIST_FOREACH_END();
return true;
}
printf("Error at level %d\n", nesting_level);
return status;
}
#endif

/* registers all functions in *library_functions in the function hash */
ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend_function_entry *functions, HashTable *function_table, int type) /* {{{ */
{
Expand Down Expand Up @@ -2935,6 +2979,15 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
reg_function->arg_info = new_arg_info + 1;
for (i = 0; i < num_args; i++) {
if (ZEND_TYPE_IS_COMPLEX(new_arg_info[i].type)) {
/* gen_stub cannot produce such a type due to memory allocation of the type list
* As such union types currently abuse the literal type name by being written as T1|... |TN */
if (ZEND_TYPE_HAS_LIST(new_arg_info[i].type)) {
ZEND_ASSERT(
zend_verify_type_is_valid(new_arg_info[i].type, /* nesting_level */ 0, /* is_arena_alloca */ false)
&& "zend_type with list is invalid"
);
continue;
}
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type)
&& "Should be stored as simple name");
const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
Expand Down Expand Up @@ -4270,6 +4323,11 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
if (ZEND_TYPE_IS_SET(type)) {
ce->ce_flags |= ZEND_ACC_HAS_TYPE_HINTS;

ZEND_ASSERT(
zend_verify_type_is_valid(type, /* nesting_level */ 0, /* is_arena_alloca */ ce->type == ZEND_USER_CLASS)
&& "zend_type is invalid"
);

if (access_type & ZEND_ACC_READONLY) {
ce->ce_flags |= ZEND_ACC_HAS_READONLY_PROPS;
}
Expand Down Expand Up @@ -4369,8 +4427,15 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
if (is_persistent_class(ce)) {
zend_type *single_type;
ZEND_TYPE_FOREACH(property_info->type, single_type) {
// TODO Add support and test cases when gen_stub support added
ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*single_type));
// TODO Add support and test cases when gen_stub supports DNF
// Currently just verify that an extension provided type is valid
if (ZEND_TYPE_HAS_LIST(*single_type)) {
ZEND_ASSERT(
zend_verify_type_is_valid(*single_type, /* nesting_level */ 0, /* is_arena_alloca */ false)
&& "zend_type_list is invalid"
);
continue;
}
if (ZEND_TYPE_HAS_NAME(*single_type)) {
zend_string *name = zend_new_interned_string(ZEND_TYPE_NAME(*single_type));
ZEND_TYPE_SET_PTR(*single_type, name);
Expand Down
7 changes: 7 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,13 @@ ZEND_API void zend_check_magic_method_implementation(
const zend_class_entry *ce, const zend_function *fptr, zend_string *lcname, int error_type);
ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, zend_string *lcname);

/* Type debugging utility */
#ifdef ZEND_DEBUG
ZEND_API bool zend_verify_type_is_valid(zend_type type, int nesting_level, bool is_arena_alloc);
#else
#define zend_verify_type_is_valid(type, nesting_level, is_arena_alloc) true
#endif

ZEND_API zend_class_entry *zend_register_internal_class(zend_class_entry *class_entry);
ZEND_API zend_class_entry *zend_register_internal_class_ex(zend_class_entry *class_entry, zend_class_entry *parent_ce);
ZEND_API zend_class_entry *zend_register_internal_interface(zend_class_entry *orig_class_entry);
Expand Down
2 changes: 2 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6570,7 +6570,9 @@ static zend_type zend_compile_typename_ex(
ZEND_TYPE_SET_LIST(type, type_list);

single_type = zend_compile_typename(type_ast, false);
ZEND_ASSERT(ZEND_TYPE_HAS_LIST(single_type));
ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(single_type));
ZEND_ASSERT(ZEND_TYPE_USES_ARENA(single_type));

type_list->types[type_list->num_types++] = single_type;

Expand Down
57 changes: 34 additions & 23 deletions Zend/zend_inheritance.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,33 @@ static void ZEND_COLD emit_incompatible_method_error(
const zend_function *parent, zend_class_entry *parent_scope,
inheritance_status status);

static void zend_type_copy_ctor(zend_type *type, bool persistent) {
static void zend_type_copy_ctor(zend_type *type, bool use_arena, bool persistent);

static void zend_type_list_copy_ctor(
zend_type *const global_type,
const zend_type_list *const old_list,
bool use_arena,
bool persistent
) {
size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types);
zend_type_list *new_list = use_arena
? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent);

memcpy(new_list, old_list, size);
ZEND_TYPE_SET_LIST(*global_type, new_list);
if (use_arena) {
ZEND_TYPE_FULL_MASK(*global_type) |= _ZEND_TYPE_ARENA_BIT;
}

zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(new_list, list_type) {
zend_type_copy_ctor(list_type, use_arena, persistent);
} ZEND_TYPE_LIST_FOREACH_END();
}

static void zend_type_copy_ctor(zend_type *type, bool use_arena, bool persistent) {
if (ZEND_TYPE_HAS_LIST(*type)) {
zend_type_list *old_list = ZEND_TYPE_LIST(*type);
size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types);
zend_type_list *new_list = ZEND_TYPE_USES_ARENA(*type)
? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent);
memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types));
ZEND_TYPE_SET_PTR(*type, new_list);

zend_type *list_type;
ZEND_TYPE_LIST_FOREACH(new_list, list_type) {
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(*list_type));
zend_string_addref(ZEND_TYPE_NAME(*list_type));
} ZEND_TYPE_LIST_FOREACH_END();
zend_type_list_copy_ctor(type, ZEND_TYPE_LIST(*type), use_arena, persistent);
} else if (ZEND_TYPE_HAS_NAME(*type)) {
zend_string_addref(ZEND_TYPE_NAME(*type));
}
Expand Down Expand Up @@ -2506,7 +2519,12 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL;

zend_type type = property_info->type;
zend_type_copy_ctor(&type, /* persistent */ 0);
/* 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);
ZEND_ASSERT(
zend_verify_type_is_valid(type, /* nesting_level */ 0, /* is_arena_alloca */ true)
&& "zend_type is invalid"
);
new_prop = zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, type);

if (property_info->attributes) {
Expand Down Expand Up @@ -2920,15 +2938,8 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce)
Z_PTR(p->val) = new_prop_info;
memcpy(new_prop_info, prop_info, sizeof(zend_property_info));
new_prop_info->ce = ce;
if (ZEND_TYPE_HAS_LIST(new_prop_info->type)) {
zend_type_list *new_list;
zend_type_list *list = ZEND_TYPE_LIST(new_prop_info->type);

new_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->num_types));
memcpy(new_list, list, ZEND_TYPE_LIST_SIZE(list->num_types));
ZEND_TYPE_SET_PTR(new_prop_info->type, list);
ZEND_TYPE_FULL_MASK(new_prop_info->type) |= _ZEND_TYPE_ARENA_BIT;
}
/* Deep copy the type information */
zend_type_copy_ctor(&new_prop_info->type, /* use_arena */ true, /* persistent */ false);
}
}

Expand Down
Loading