Skip to content

Commit 0f09573

Browse files
committed
Add runtime variance checks
Known issues: - When there is an inheritance warning it gets generated twice. - The VERIFY_VARIANCE opcode is not generated in the correct place
1 parent 7275204 commit 0f09573

File tree

3 files changed

+57
-5
lines changed

3 files changed

+57
-5
lines changed

Zend/tests/return_types/generators003.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class SomeCollection implements Collection {
1414
}
1515
}
1616

17+
$some = new SomeCollection();
1718
echo get_class($some->getIterator());
1819

1920
--EXPECT--

Zend/zend_compile.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8135,6 +8135,7 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
81358135

81368136
/* Compile decl stmts */
81378137
{
8138+
zend_string *lcname;
81388139
HashTable unverified_types;
81398140
HashTable *prev_unverified_types;
81408141
zend_hash_init(&unverified_types, 0, NULL, NULL, 1);
@@ -8146,6 +8147,15 @@ void zend_compile_top_stmt(zend_ast *ast) /* {{{ */
81468147
}
81478148

81488149
/* todo: emit ZEND_VERIFY_VARIANCE */
8150+
ZEND_HASH_FOREACH_STR_KEY(&unverified_types, lcname) {
8151+
zend_op *opline = get_next_op(CG(active_op_array));
8152+
8153+
opline->op1_type = IS_CONST;
8154+
opline->opcode = ZEND_VERIFY_VARIANCE;
8155+
LITERAL_STR(opline->op1, lcname);
8156+
8157+
} ZEND_HASH_FOREACH_END();
8158+
81498159
zend_hash_destroy(&unverified_types);
81508160
CG(unverified_types) = prev_unverified_types;
81518161
}

Zend/zend_inheritance.c

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,6 +1996,31 @@ ZEND_API void zend_do_link_class(zend_class_entry *ce, zend_class_entry *parent)
19961996
}
19971997
/* }}} */
19981998

1999+
static void _inheritance_runtime_error_msg(zend_function *child, zend_function *parent)
2000+
{
2001+
int error_level;
2002+
const char *error_verb;
2003+
zend_string *method_prototype = zend_get_function_declaration(parent);
2004+
zend_string *child_prototype = zend_get_function_declaration(child);
2005+
2006+
if (child->common.prototype && (
2007+
child->common.prototype->common.fn_flags & ZEND_ACC_ABSTRACT
2008+
)) {
2009+
error_level = E_ERROR;
2010+
error_verb = "must";
2011+
} else if ((parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) &&
2012+
!_check_inherited_return_type(child, child->common.arg_info - 1, parent, parent->common.arg_info - 1)) {
2013+
error_level = E_ERROR;
2014+
error_verb = "must";
2015+
} else {
2016+
error_level = E_WARNING;
2017+
error_verb = "should";
2018+
}
2019+
zend_error(error_level, "Declaration of %s %s be compatible with %s", ZSTR_VAL(child_prototype), error_verb, ZSTR_VAL(method_prototype));
2020+
zend_string_efree(child_prototype);
2021+
zend_string_efree(method_prototype);
2022+
}
2023+
19992024
ZEND_API void zend_verify_variance(zend_class_entry *ce) /* {{{ */
20002025
{
20012026
// todo: name? "verify" typically means something else
@@ -2022,14 +2047,30 @@ ZEND_API void zend_verify_variance(zend_class_entry *ce) /* {{{ */
20222047
/* Check return type compatibility, but only if the prototype already
20232048
* specifies a return type. Adding a new return type is always valid. */
20242049
if (parent->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
2025-
int check = _check_inherited_return_type(
2026-
child, child->common.arg_info - 1,
2027-
parent, parent->common.arg_info - 1);
2028-
if (check < 0) {
2029-
zend_error_noreturn(E_ERROR, "Bad return!");
2050+
if (child->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
2051+
int check = _check_inherited_return_type(
2052+
child, &child->common.arg_info[-1],
2053+
parent, &parent->common.arg_info[-1]);
2054+
if (check < 0) {
2055+
_inheritance_runtime_error_msg(child, parent);
2056+
// todo: what to do with errors, not warnings?
2057+
continue;
2058+
}
2059+
} else {
2060+
_inheritance_runtime_error_msg(child, parent);
2061+
// todo: what to do with errors, not warnings?
2062+
continue;
20302063
}
20312064
}
20322065

2066+
if (parent->common.required_num_args < child->common.required_num_args
2067+
|| parent->common.num_args > child->common.num_args)
2068+
{
2069+
_inheritance_runtime_error_msg(child, parent);
2070+
// todo: what to do with errors, not warnings?
2071+
continue;
2072+
}
2073+
20332074
num_args = parent->common.num_args;
20342075
if (parent->common.fn_flags & ZEND_ACC_VARIADIC) {
20352076
num_args++;

0 commit comments

Comments
 (0)