Only allow "nearly linked" classes for parent/interface

The requirements for parent/interface are difference than for the
variance checks in type declarations. The latter can work on fully
unlinked classes, but the former need inheritance to be essentially
finished, only variance checks may still be outstanding.

Adding a new flag for this because we have lots of space, but we
could also represent these "inheritance states" more compactly in
the future.
This commit is contained in:
Nikita Popov 2019-09-11 16:27:28 +02:00
parent b7c353c8d0
commit 270e5e3c5b
6 changed files with 47 additions and 9 deletions

View File

@ -10,4 +10,4 @@ var_dump($a instanceOf A);
echo "ok\n";
?>
--EXPECTF--
Fatal error: Interface RecurisiveFooFar cannot implement itself in %s on line %d
Fatal error: Interface 'RecurisiveFooFar' not found in %s on line %d

View File

@ -0,0 +1,15 @@
--TEST--
Using an unlinked parent class
--FILE--
<?php
spl_autoload_register(function($class) {
class X extends B {}
});
class B extends A {
}
?>
--EXPECTF--
Fatal error: Class 'B' not found in %s on line %d

View File

@ -0,0 +1,15 @@
--TEST--
Using an unlinked parent interface
--FILE--
<?php
spl_autoload_register(function($class) {
class X implements B {}
});
interface B extends A {
}
?>
--EXPECTF--
Fatal error: Interface 'B' not found in %s on line %d

View File

@ -229,7 +229,7 @@ typedef struct _zend_oparray_context {
/* op_array or class is preloaded | | | */
#define ZEND_ACC_PRELOADED (1 << 10) /* X | X | | */
/* | | | */
/* Class Flags (unused: 22...) | | | */
/* Class Flags (unused: 23...) | | | */
/* =========== | | | */
/* | | | */
/* Special class types | | | */
@ -278,6 +278,9 @@ typedef struct _zend_oparray_context {
/* Class has unresolved variance obligations. | | | */
#define ZEND_ACC_UNRESOLVED_VARIANCE (1 << 21) /* X | | | */
/* | | | */
/* Class is linked apart from variance obligations. | | | */
#define ZEND_ACC_NEARLY_LINKED (1 << 22) /* X | | | */
/* | | | */
/* Function Flags (unused: 23, 26) | | | */
/* ============== | | | */
/* | | | */
@ -864,6 +867,7 @@ void zend_assert_valid_class_name(const zend_string *const_name);
#define ZEND_FETCH_CLASS_SILENT 0x0100
#define ZEND_FETCH_CLASS_EXCEPTION 0x0200
#define ZEND_FETCH_CLASS_ALLOW_UNLINKED 0x0400
#define ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED 0x0800
#define ZEND_PARAM_REF (1<<0)
#define ZEND_PARAM_VARIADIC (1<<1)

View File

@ -917,8 +917,12 @@ ZEND_API zend_class_entry *zend_lookup_class_ex(zend_string *name, zend_string *
zend_string_release_ex(lc_name, 0);
}
ce = (zend_class_entry*)Z_PTR_P(zv);
if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_LINKED)) &&
!(flags & ZEND_FETCH_CLASS_ALLOW_UNLINKED)) {
if (UNEXPECTED(!(ce->ce_flags & ZEND_ACC_LINKED))) {
if ((flags & ZEND_FETCH_CLASS_ALLOW_UNLINKED) ||
((flags & ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED) &&
(ce->ce_flags & ZEND_ACC_NEARLY_LINKED))) {
return ce;
}
return NULL;
}
return ce;

View File

@ -1005,9 +1005,8 @@ static inline void do_implement_interface(zend_class_entry *ce, zend_class_entry
if (!(ce->ce_flags & ZEND_ACC_INTERFACE) && iface->interface_gets_implemented && iface->interface_gets_implemented(iface, ce) == FAILURE) {
zend_error_noreturn(E_CORE_ERROR, "Class %s could not implement interface %s", ZSTR_VAL(ce->name), ZSTR_VAL(iface->name));
}
if (UNEXPECTED(ce == iface)) {
zend_error_noreturn(E_ERROR, "Interface %s cannot implement itself", ZSTR_VAL(ce->name));
}
/* This should be prevented by the class lookup logic. */
ZEND_ASSERT(ce != iface);
}
/* }}} */
@ -1457,7 +1456,7 @@ static void zend_do_implement_interfaces(zend_class_entry *ce) /* {{{ */
for (i = 0; i < ce->num_interfaces; i++) {
iface = zend_fetch_class_by_name(
ce->interface_names[i].name, ce->interface_names[i].lc_name,
ZEND_FETCH_CLASS_INTERFACE|ZEND_FETCH_CLASS_ALLOW_UNLINKED);
ZEND_FETCH_CLASS_INTERFACE|ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED);
if (!(iface->ce_flags & ZEND_ACC_LINKED)) {
add_dependency_obligation(ce, iface);
}
@ -2404,7 +2403,7 @@ ZEND_API void zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_na
{
if (ce->parent_name) {
zend_class_entry *parent = zend_fetch_class_by_name(
ce->parent_name, lc_parent_name, ZEND_FETCH_CLASS_ALLOW_UNLINKED);
ce->parent_name, lc_parent_name, ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED);
if (!(parent->ce_flags & ZEND_ACC_LINKED)) {
add_dependency_obligation(ce, parent);
}
@ -2427,6 +2426,7 @@ ZEND_API void zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_na
return;
}
ce->ce_flags |= ZEND_ACC_NEARLY_LINKED;
load_delayed_classes();
if (ce->ce_flags & ZEND_ACC_UNRESOLVED_VARIANCE) {
resolve_delayed_variance_obligations(ce);