From 8d95d930f6f97f5c4d32f105e067cb654e4ca91f Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sat, 30 Aug 2014 16:13:59 -0600 Subject: [PATCH 1/3] Implement return types Works for methods, functions, and closures (all 'function' types). There needs to be a few more tests for 'parent' functionality, and ReflectionType needs to implement get_debug_info. --- Zend/tests/return_types/001.phpt | 12 + Zend/tests/return_types/002.phpt | 13 + Zend/tests/return_types/003.phpt | 12 + Zend/tests/return_types/004.phpt | 13 + Zend/tests/return_types/005.phpt | 18 ++ Zend/tests/return_types/006.phpt | 21 ++ Zend/tests/return_types/007.phpt | 19 ++ Zend/tests/return_types/008.phpt | 22 ++ Zend/tests/return_types/009.phpt | 19 ++ Zend/tests/return_types/010.phpt | 14 + Zend/tests/return_types/011.phpt | 14 + Zend/tests/return_types/012.phpt | 28 ++ Zend/tests/return_types/013.phpt | 19 ++ Zend/tests/return_types/014.phpt | 12 + Zend/tests/return_types/015.phpt | 24 ++ Zend/tests/return_types/016.phpt | 20 ++ Zend/tests/return_types/017.phpt | 24 ++ Zend/tests/return_types/018.phpt | 12 + Zend/tests/return_types/019.phpt | 12 + Zend/tests/return_types/020.phpt | 17 ++ Zend/tests/return_types/022.phpt | 17 ++ Zend/tests/return_types/023.phpt | 21 ++ Zend/tests/return_types/029.phpt | 12 + Zend/tests/return_types/030.phpt | 10 + Zend/tests/return_types/031.phpt | 10 + Zend/tests/return_types/classes.php | 5 + Zend/tests/return_types/generators001.phpt | 30 ++ Zend/tests/return_types/generators002.phpt | 11 + Zend/tests/return_types/generators003.phpt | 23 ++ Zend/tests/return_types/generators004.phpt | 17 ++ Zend/tests/return_types/inheritance001.phpt | 16 + Zend/tests/return_types/inheritance002.phpt | 16 + Zend/tests/return_types/inheritance003.phpt | 16 + Zend/tests/return_types/inheritance005.phpt | 25 ++ Zend/tests/return_types/inheritance006.phpt | 30 ++ Zend/tests/return_types/inheritance007.phpt | 42 +++ Zend/tests/return_types/reflection001.phpt | 21 ++ Zend/tests/return_types/reflection002.phpt | 21 ++ Zend/tests/return_types/reflection003.phpt | 26 ++ Zend/tests/return_types/reflection004.phpt | 26 ++ Zend/tests/return_types/reflection005.phpt | 26 ++ Zend/tests/return_types/reflection006.phpt | 26 ++ Zend/tests/return_types/reflection007.phpt | 26 ++ Zend/tests/return_types/reflection008.phpt | 28 ++ Zend/tests/return_types/rfc001.phpt | 14 + Zend/tests/return_types/rfc002.phpt | 13 + Zend/tests/return_types/rfc003.phpt | 13 + Zend/tests/return_types/rfc004.phpt | 21 ++ Zend/zend_API.c | 5 + Zend/zend_ast.c | 4 +- Zend/zend_ast.h | 4 +- Zend/zend_compile.c | 145 ++++++++- Zend/zend_compile.h | 24 ++ Zend/zend_execute.c | 80 +++++ Zend/zend_execute.h | 1 + Zend/zend_inheritance.c | 313 ++++++++++++-------- Zend/zend_inheritance.h | 2 + Zend/zend_language_parser.y | 50 ++-- Zend/zend_opcode.c | 9 + Zend/zend_vm_def.h | 25 ++ Zend/zend_vm_execute.h | 75 +++++ Zend/zend_vm_opcodes.c | 3 +- Zend/zend_vm_opcodes.h | 1 + ext/opcache/zend_persist.c | 8 + ext/opcache/zend_persist_calc.c | 4 + ext/reflection/php_reflection.c | 118 +++++++- 66 files changed, 1613 insertions(+), 165 deletions(-) create mode 100644 Zend/tests/return_types/001.phpt create mode 100644 Zend/tests/return_types/002.phpt create mode 100644 Zend/tests/return_types/003.phpt create mode 100644 Zend/tests/return_types/004.phpt create mode 100644 Zend/tests/return_types/005.phpt create mode 100644 Zend/tests/return_types/006.phpt create mode 100644 Zend/tests/return_types/007.phpt create mode 100644 Zend/tests/return_types/008.phpt create mode 100644 Zend/tests/return_types/009.phpt create mode 100644 Zend/tests/return_types/010.phpt create mode 100644 Zend/tests/return_types/011.phpt create mode 100644 Zend/tests/return_types/012.phpt create mode 100644 Zend/tests/return_types/013.phpt create mode 100644 Zend/tests/return_types/014.phpt create mode 100644 Zend/tests/return_types/015.phpt create mode 100644 Zend/tests/return_types/016.phpt create mode 100644 Zend/tests/return_types/017.phpt create mode 100644 Zend/tests/return_types/018.phpt create mode 100644 Zend/tests/return_types/019.phpt create mode 100644 Zend/tests/return_types/020.phpt create mode 100644 Zend/tests/return_types/022.phpt create mode 100644 Zend/tests/return_types/023.phpt create mode 100644 Zend/tests/return_types/029.phpt create mode 100644 Zend/tests/return_types/030.phpt create mode 100644 Zend/tests/return_types/031.phpt create mode 100644 Zend/tests/return_types/classes.php create mode 100644 Zend/tests/return_types/generators001.phpt create mode 100644 Zend/tests/return_types/generators002.phpt create mode 100644 Zend/tests/return_types/generators003.phpt create mode 100644 Zend/tests/return_types/generators004.phpt create mode 100644 Zend/tests/return_types/inheritance001.phpt create mode 100644 Zend/tests/return_types/inheritance002.phpt create mode 100644 Zend/tests/return_types/inheritance003.phpt create mode 100644 Zend/tests/return_types/inheritance005.phpt create mode 100644 Zend/tests/return_types/inheritance006.phpt create mode 100644 Zend/tests/return_types/inheritance007.phpt create mode 100644 Zend/tests/return_types/reflection001.phpt create mode 100644 Zend/tests/return_types/reflection002.phpt create mode 100644 Zend/tests/return_types/reflection003.phpt create mode 100644 Zend/tests/return_types/reflection004.phpt create mode 100644 Zend/tests/return_types/reflection005.phpt create mode 100644 Zend/tests/return_types/reflection006.phpt create mode 100644 Zend/tests/return_types/reflection007.phpt create mode 100644 Zend/tests/return_types/reflection008.phpt create mode 100644 Zend/tests/return_types/rfc001.phpt create mode 100644 Zend/tests/return_types/rfc002.phpt create mode 100644 Zend/tests/return_types/rfc003.phpt create mode 100644 Zend/tests/return_types/rfc004.phpt diff --git a/Zend/tests/return_types/001.phpt b/Zend/tests/return_types/001.phpt new file mode 100644 index 0000000000000..722b7c2642661 --- /dev/null +++ b/Zend/tests/return_types/001.phpt @@ -0,0 +1,12 @@ +--TEST-- +Returned nothing, expected array + +--FILE-- +foo(); + +--EXPECTF-- +Catchable fatal error: The function qux::foo was expected to return an object of class foo and returned an object of class qux in %s on line %d diff --git a/Zend/tests/return_types/006.phpt b/Zend/tests/return_types/006.phpt new file mode 100644 index 0000000000000..359b25a28df3c --- /dev/null +++ b/Zend/tests/return_types/006.phpt @@ -0,0 +1,21 @@ +--TEST-- +Return type allowed in child when parent does not have return type + +--FILE-- +foo()); + +--EXPECTF-- +object(qux)#%d (%d) { +} diff --git a/Zend/tests/return_types/008.phpt b/Zend/tests/return_types/008.phpt new file mode 100644 index 0000000000000..35a9a3716bf6a --- /dev/null +++ b/Zend/tests/return_types/008.phpt @@ -0,0 +1,22 @@ +--TEST-- +Return type covariance in interface implementation + +--FILE-- +bar()); + +--EXPECTF-- +object(qux)#%d (%d) { +} diff --git a/Zend/tests/return_types/009.phpt b/Zend/tests/return_types/009.phpt new file mode 100644 index 0000000000000..949044388fab3 --- /dev/null +++ b/Zend/tests/return_types/009.phpt @@ -0,0 +1,19 @@ +--TEST-- +Return type covariance error + +--FILE-- +bar()); + +--EXPECT-- +object(Closure)#2 (2) { + ["static"]=> + array(1) { + ["test"]=> + string(3) "one" + } + ["this"]=> + object(foo)#1 (0) { + } +} diff --git a/Zend/tests/return_types/013.phpt b/Zend/tests/return_types/013.phpt new file mode 100644 index 0000000000000..34a9d02cc27d3 --- /dev/null +++ b/Zend/tests/return_types/013.phpt @@ -0,0 +1,19 @@ +--TEST-- +Closure inside method returned null, expected array + +--FILE-- +bar(), $func()); + +--EXPECTF-- +Catchable fatal error: The function %s was expected to return an array and returned null in %s on line %d diff --git a/Zend/tests/return_types/014.phpt b/Zend/tests/return_types/014.phpt new file mode 100644 index 0000000000000..abf8b1210b406 --- /dev/null +++ b/Zend/tests/return_types/014.phpt @@ -0,0 +1,12 @@ +--TEST-- +Constructors cannot declare a return type + +--FILE-- +values()); + +--EXPECTF-- +object(Collections\Vector)#%d (%d) { +} diff --git a/Zend/tests/return_types/016.phpt b/Zend/tests/return_types/016.phpt new file mode 100644 index 0000000000000..f3636751c1aaa --- /dev/null +++ b/Zend/tests/return_types/016.phpt @@ -0,0 +1,20 @@ +--TEST-- +Fully qualified classes are allowed in return types + +--FILE-- +foo(new \EmptyIterator())); + +--EXPECTF-- +object(EmptyIterator)#%d (0) { +} diff --git a/Zend/tests/return_types/017.phpt b/Zend/tests/return_types/017.phpt new file mode 100644 index 0000000000000..d44b26fc04270 --- /dev/null +++ b/Zend/tests/return_types/017.phpt @@ -0,0 +1,24 @@ +--TEST-- +Fully qualified classes in trait return types + +--FILE-- +foo([])); + +--EXPECTF-- +object(EmptyIterator)#%d (%d) { +} diff --git a/Zend/tests/return_types/018.phpt b/Zend/tests/return_types/018.phpt new file mode 100644 index 0000000000000..6d6e0c7e734ee --- /dev/null +++ b/Zend/tests/return_types/018.phpt @@ -0,0 +1,12 @@ +--TEST-- +Destructors cannot declare a return type + +--FILE-- +data as $key => $value) { + yield $key => $value; + } + } +} + +$some = new SomeCollection(); +var_dump($some->getIterator()); + +--EXPECTF-- +object(Generator)#%d (%d) { +} diff --git a/Zend/tests/return_types/generators004.phpt b/Zend/tests/return_types/generators004.phpt new file mode 100644 index 0000000000000..74aa801638211 --- /dev/null +++ b/Zend/tests/return_types/generators004.phpt @@ -0,0 +1,17 @@ +--TEST-- +Generator with return type does not fail with empty return + +--FILE-- + + array(2) { + [0]=> + int(1) + [1]=> + int(2) + } +} +object(ArrayIterator)#%d (1) { + ["storage":"ArrayIterator":private]=> + array(2) { + [0]=> + int(1) + [1]=> + int(2) + } +} diff --git a/Zend/tests/return_types/reflection001.phpt b/Zend/tests/return_types/reflection001.phpt new file mode 100644 index 0000000000000..631516a1c7e0b --- /dev/null +++ b/Zend/tests/return_types/reflection001.phpt @@ -0,0 +1,21 @@ +--TEST-- +Reflection::hasReturnType true + +--SKIPIF-- +hasReturnType()); + +--EXPECT-- +bool(true) diff --git a/Zend/tests/return_types/reflection002.phpt b/Zend/tests/return_types/reflection002.phpt new file mode 100644 index 0000000000000..ac57aefb679ea --- /dev/null +++ b/Zend/tests/return_types/reflection002.phpt @@ -0,0 +1,21 @@ +--TEST-- +Reflection::hasReturnType false + +--SKIPIF-- +hasReturnType()); + +--EXPECT-- +bool(false) diff --git a/Zend/tests/return_types/reflection003.phpt b/Zend/tests/return_types/reflection003.phpt new file mode 100644 index 0000000000000..7e8dd5309efb7 --- /dev/null +++ b/Zend/tests/return_types/reflection003.phpt @@ -0,0 +1,26 @@ +--TEST-- +Reflection::getReturnType empty + +--SKIPIF-- +getReturnType(); +var_dump($rt->getKind() == $rt::IS_UNDECLARED); +var_dump((string) $rt); +var_dump((string) $rt === $rt->getName()); + +--EXPECT-- +bool(true) +string(0) "" +bool(true) diff --git a/Zend/tests/return_types/reflection004.phpt b/Zend/tests/return_types/reflection004.phpt new file mode 100644 index 0000000000000..014e03c34a0ce --- /dev/null +++ b/Zend/tests/return_types/reflection004.phpt @@ -0,0 +1,26 @@ +--TEST-- +Reflection::getReturnType array + +--SKIPIF-- +getReturnType(); +var_dump($rt->getKind() == $rt::IS_ARRAY); +var_dump((string) $rt); +var_dump((string) $rt === $rt->getName()); + +--EXPECT-- +bool(true) +string(5) "array" +bool(true) diff --git a/Zend/tests/return_types/reflection005.phpt b/Zend/tests/return_types/reflection005.phpt new file mode 100644 index 0000000000000..85b9f731761b1 --- /dev/null +++ b/Zend/tests/return_types/reflection005.phpt @@ -0,0 +1,26 @@ +--TEST-- +Reflection::getReturnType callable + +--SKIPIF-- +getReturnType(); +var_dump($rt->getKind() == $rt::IS_CALLABLE); +var_dump((string) $rt); +var_dump((string) $rt === $rt->getName()); + +--EXPECT-- +bool(true) +string(8) "callable" +bool(true) diff --git a/Zend/tests/return_types/reflection006.phpt b/Zend/tests/return_types/reflection006.phpt new file mode 100644 index 0000000000000..0a0c248238df5 --- /dev/null +++ b/Zend/tests/return_types/reflection006.phpt @@ -0,0 +1,26 @@ +--TEST-- +Reflection::getReturnType object + +--SKIPIF-- +getReturnType(); +var_dump($rt->getKind() == $rt::IS_OBJECT); +var_dump((string) $rt); +var_dump((string) $rt === $rt->getName()); + +--EXPECT-- +bool(true) +string(8) "StdClass" +bool(true) diff --git a/Zend/tests/return_types/reflection007.phpt b/Zend/tests/return_types/reflection007.phpt new file mode 100644 index 0000000000000..d3df407bd3c99 --- /dev/null +++ b/Zend/tests/return_types/reflection007.phpt @@ -0,0 +1,26 @@ +--TEST-- +Reflection::getReturnType object + +--SKIPIF-- +getMethod("foo")->getReturnType(); +var_dump($rt->getKind() == $rt::IS_OBJECT); +var_dump((string) $rt); +var_dump((string) $rt === $rt->getName()); + +--EXPECT-- +bool(true) +string(1) "A" +bool(true) diff --git a/Zend/tests/return_types/reflection008.phpt b/Zend/tests/return_types/reflection008.phpt new file mode 100644 index 0000000000000..600426a4ae6cb --- /dev/null +++ b/Zend/tests/return_types/reflection008.phpt @@ -0,0 +1,28 @@ +--TEST-- +Reflection::getReturnType object + +--SKIPIF-- +getMethod("foo")->getReturnType(); +var_dump($rt->getKind() == $rt::IS_ARRAY); +var_dump((string) $rt); +var_dump((string) $rt === $rt->getName()); + +--EXPECT-- +bool(true) +string(5) "array" +bool(true) diff --git a/Zend/tests/return_types/rfc001.phpt b/Zend/tests/return_types/rfc001.phpt new file mode 100644 index 0000000000000..557c82ef4a3c5 --- /dev/null +++ b/Zend/tests/return_types/rfc001.phpt @@ -0,0 +1,14 @@ +--TEST-- +RFC example: returned type does not match the type declaration + +--FILE-- +num_args = 0; internal_function->required_num_args = 0; } + + /* TODO: allow internal functions to declare return types somehow */ + internal_function->return_type.name = NULL; + internal_function->return_type.kind = IS_UNDEF; + if (ptr->flags & ZEND_ACC_ABSTRACT) { if (scope) { /* This is a class that must be abstract itself. Here we set the check info. */ diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 9604079a9eb0a..2c569d543bce0 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -67,7 +67,7 @@ ZEND_API zend_ast *zend_ast_create_zval_ex(zval *zv, zend_ast_attr attr) { ZEND_API zend_ast *zend_ast_create_decl( zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment, - zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2 + zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3 ) { zend_ast_decl *ast; TSRMLS_FETCH(); @@ -84,6 +84,7 @@ ZEND_API zend_ast *zend_ast_create_decl( ast->child[0] = child0; ast->child[1] = child1; ast->child[2] = child2; + ast->child[3] = child3; return (zend_ast *) ast; } @@ -400,6 +401,7 @@ static void zend_ast_destroy_ex(zend_ast *ast, zend_bool free) { zend_ast_destroy_ex(decl->child[0], free); zend_ast_destroy_ex(decl->child[1], free); zend_ast_destroy_ex(decl->child[2], free); + zend_ast_destroy_ex(decl->child[3], free); break; } default: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 6bd6850d83f74..a5aa6e883fc91 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -183,7 +183,7 @@ typedef struct _zend_ast_decl { unsigned char *lex_pos; zend_string *doc_comment; zend_string *name; - zend_ast *child[3]; + zend_ast *child[4]; } zend_ast_decl; ZEND_API zend_ast *zend_ast_create_zval_ex(zval *zv, zend_ast_attr attr); @@ -193,7 +193,7 @@ ZEND_API zend_ast *zend_ast_create(zend_ast_kind kind, ...); ZEND_API zend_ast *zend_ast_create_decl( zend_ast_kind kind, uint32_t flags, uint32_t start_lineno, zend_string *doc_comment, - zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2 + zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3 ); ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 4f373e6e73e21..aa1c37ce168bc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -446,14 +446,6 @@ static int zend_add_const_name_literal(zend_op_array *op_array, zend_string *nam op.constant = zend_add_literal(CG(active_op_array), &_c TSRMLS_CC); \ } while (0) -#define MAKE_NOP(opline) do { \ - opline->opcode = ZEND_NOP; \ - memset(&opline->result, 0, sizeof(opline->result)); \ - memset(&opline->op1, 0, sizeof(opline->op1)); \ - memset(&opline->op2, 0, sizeof(opline->op2)); \ - opline->result_type = opline->op1_type = opline->op2_type = IS_UNUSED; \ -} while (0) - void zend_stop_lexing(TSRMLS_D) { LANG_SCNG(yy_cursor) = LANG_SCNG(yy_limit); } @@ -1033,9 +1025,10 @@ void zend_do_early_binding(TSRMLS_D) /* {{{ */ parent_name = &CONSTANT(fetch_class_opline->op2.constant); if (((ce = zend_lookup_class(Z_STR_P(parent_name) TSRMLS_CC)) == NULL) || + (ce->ce_flags & ZEND_ACC_HAS_RETURN_TYPE) || ((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) && (ce->type == ZEND_INTERNAL_CLASS))) { - if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) { + if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) { uint32_t *opline_num = &CG(active_op_array)->early_binding; while (*opline_num != -1) { @@ -1086,7 +1079,8 @@ ZEND_API void zend_do_delayed_early_binding(const zend_op_array *op_array TSRMLS CG(in_compilation) = 1; while (opline_num != -1) { - if ((ce = zend_lookup_class(Z_STR_P(op_array->opcodes[opline_num-1].op2.zv) TSRMLS_CC)) != NULL) { + if ((ce = zend_lookup_class(Z_STR_P(op_array->opcodes[opline_num-1].op2.zv) TSRMLS_CC)) != NULL + && (ce->ce_flags & ZEND_ACC_HAS_RETURN_TYPE) == 0) { do_bind_inherited_class(op_array, &op_array->opcodes[opline_num], EG(class_table), ce, 0 TSRMLS_CC); } opline_num = op_array->opcodes[opline_num].result.opline_num; @@ -3107,6 +3101,45 @@ static void zend_free_foreach_and_switch_variables(TSRMLS_D) /* {{{ */ } /* }}} */ + +static void zend_emit_return_type_check(znode *expr, zend_type_decl *return_type TSRMLS_DC) /* {{{ */ +{ + /* Up to two opcodes are emitted: ZEND_FETCH_CLASS and + * ZEND_VERIFY_RETURN_TYPE. + * The ZEND_VERIFY_RETURN_TYPE handler takes up to 2 operands: + * - 0 operands: issues an error because there must be a return value + * if a return type is specified. + * - 1 operand: check if op1 (return_value) is of type array or + * callable depending on function return type. + * - 2 operands: check if op1 (return_value) is an instance of op2 + * (expected return type). ZEND_FETCH_CLASS is used to fetch the + * expected return type's zend_class_entry*. + * + * If used op2 should be IS_CONST with the name of the expected class. + */ + if (expr && return_type->kind == IS_OBJECT) { + zend_op *opline; + znode name_node; + uint32_t fetch_type = zend_get_class_fetch_type(return_type->name); + + opline = zend_emit_op(&name_node, ZEND_FETCH_CLASS, NULL, NULL TSRMLS_CC); + /* If the return type is not loaded then the returned object + * cannot fulfill that type; no need to autoload it */ + opline->extended_value = fetch_type | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT; + + if (fetch_type == ZEND_FETCH_CLASS_DEFAULT) { + opline->op2_type = IS_CONST; + opline->op2.constant = zend_add_class_name_literal(CG(active_op_array), zend_string_copy(return_type->name) TSRMLS_CC); + } + + opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, &name_node TSRMLS_CC); + } else { + zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL TSRMLS_CC); + } +} +/* }}} */ + + void zend_compile_return(zend_ast *ast TSRMLS_DC) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; @@ -3132,6 +3165,9 @@ void zend_compile_return(zend_ast *ast TSRMLS_DC) /* {{{ */ opline->op1.var = CG(context).fast_call_var; } + if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_emit_return_type_check(&expr_node, &CG(active_op_array)->return_type TSRMLS_CC); + } opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL TSRMLS_CC); @@ -4140,12 +4176,45 @@ static void zend_begin_func_decl(znode *result, zend_op_array *op_array, zend_as } /* }}} */ + +static void zend_compile_return_type(zend_function *func, zend_bool is_method, zend_ast *ast TSRMLS_DC) /* {{{ */ +{ + zend_string *class_name; + zend_string *resolved_name; + + func->common.fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; + if (ast->kind == ZEND_AST_TYPE) { + zend_type_decl_ctor(&func->common.return_type, ast->attr, NULL); + return; + } + + assert(ast->kind == ZEND_AST_ZVAL); + + class_name = zend_ast_get_str(ast); + + if (zend_is_const_default_class_ref(ast)) { + resolved_name = zend_resolve_class_name(class_name, ast->attr TSRMLS_CC); + } else { + if (!is_method) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare a return type of %s outside of a class scope", class_name->val); + return; + } + resolved_name = zend_string_copy(class_name); // self, parent, static + } + + zend_type_decl_ctor(&func->common.return_type, IS_OBJECT, resolved_name); + + zend_string_release(resolved_name); +} +/* }}} */ + void zend_compile_func_decl(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *params_ast = decl->child[0]; zend_ast *uses_ast = decl->child[1]; zend_ast *stmt_ast = decl->child[2]; + zend_ast *return_type_ast = decl->child[3]; zend_bool is_method = decl->kind == ZEND_AST_METHOD; zend_op_array *orig_op_array = CG(active_op_array); @@ -4172,6 +4241,10 @@ void zend_compile_func_decl(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */ zend_begin_func_decl(result, op_array, decl TSRMLS_CC); } + if (return_type_ast) { + zend_compile_return_type((zend_function *) op_array, is_method, return_type_ast TSRMLS_CC); + } + CG(active_op_array) = op_array; zend_stack_push(&CG(context_stack), (void *) &CG(context)); zend_init_compiler_context(TSRMLS_C); @@ -4198,8 +4271,14 @@ void zend_compile_func_decl(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */ if (is_method) { zend_check_magic_method_implementation( CG(active_class_entry), (zend_function *) op_array, E_COMPILE_ERROR TSRMLS_CC); + if ((op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) && (op_array->fn_flags & (ZEND_ACC_PROTECTED | ZEND_ACC_PUBLIC))) { + CG(active_class_entry)->ce_flags |= ZEND_ACC_HAS_RETURN_TYPE; + } } + if (return_type_ast) { + zend_emit_return_type_check(NULL, &op_array->return_type TSRMLS_CC); + } zend_do_extended_info(TSRMLS_C); zend_emit_final_return(NULL TSRMLS_CC); @@ -4580,6 +4659,10 @@ void zend_compile_class_decl(zend_ast *ast TSRMLS_DC) /* {{{ */ if (ce->constructor->common.fn_flags & ZEND_ACC_STATIC) { zend_error_noreturn(E_COMPILE_ERROR, "Constructor %s::%s() cannot be static", ce->name->val, ce->constructor->common.function_name->val); + } else if (ce->constructor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Constructor %s::%s() cannot declare a return type", + ce->name->val, ce->constructor->common.function_name->val); } } if (ce->destructor) { @@ -4587,6 +4670,10 @@ void zend_compile_class_decl(zend_ast *ast TSRMLS_DC) /* {{{ */ if (ce->destructor->common.fn_flags & ZEND_ACC_STATIC) { zend_error_noreturn(E_COMPILE_ERROR, "Destructor %s::%s() cannot be static", ce->name->val, ce->destructor->common.function_name->val); + } else if (ce->destructor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_error_noreturn(E_COMPILE_ERROR, + "Destructor %s::%s() cannot declare a return type", + ce->name->val, ce->destructor->common.function_name->val); } } if (ce->clone) { @@ -4594,6 +4681,10 @@ void zend_compile_class_decl(zend_ast *ast TSRMLS_DC) /* {{{ */ if (ce->clone->common.fn_flags & ZEND_ACC_STATIC) { zend_error_noreturn(E_COMPILE_ERROR, "Clone method %s::%s() cannot be static", ce->name->val, ce->clone->common.function_name->val); + } else if (ce->clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_error_noreturn(E_COMPILE_ERROR, + "%s::%s() cannot declare a return type", + ce->name->val, ce->clone->common.function_name->val); } } @@ -5404,6 +5495,19 @@ void zend_compile_yield(znode *result, zend_ast *ast TSRMLS_DC) /* {{{ */ zend_error_noreturn(E_COMPILE_ERROR, "The \"yield\" expression can only be used inside a function"); } + if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + const char *msg = "Generators may only declare a return type of Generator, Iterator or Traversable, %s is not permitted"; + if (CG(active_op_array)->return_type.kind != IS_OBJECT) { + zend_error_noreturn(E_COMPILE_ERROR, msg, + zend_get_type_by_const(CG(active_op_array)->return_type.kind)); + } + if (!zend_string_equals_literal_ci(CG(active_op_array)->return_type.name, "Traversable") + && !zend_string_equals_literal_ci(CG(active_op_array)->return_type.name, "Iterator") + && !zend_string_equals_literal_ci(CG(active_op_array)->return_type.name, "Generator") + ) { + zend_error_noreturn(E_COMPILE_ERROR, msg, CG(active_op_array)->return_type.name->val); + } + } CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR; @@ -6407,6 +6511,27 @@ void zend_eval_const_expr(zend_ast **ast_ptr TSRMLS_DC) /* {{{ */ } /* }}} */ +void zend_type_decl_ctor(zend_type_decl *type, zend_uchar kind, zend_string *name) /* {{{ */ +{ + assert(kind == IS_UNDEF || kind == IS_ARRAY || kind == IS_CALLABLE || kind == IS_OBJECT); + if (name) { + zend_string_addref(name); + } + type->kind = kind; + type->name = name; +} +/* }}} */ + +void zend_type_decl_dtor(zend_type_decl *type) /* {{{ */ +{ + if (type->name) { + zend_string_release(type->name); + } + type->kind = IS_UNDEF; + type->name = NULL; +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 4cf60c618146f..b13fbdec81944 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -35,6 +35,14 @@ #define SET_UNUSED(op) op ## _type = IS_UNUSED +#define MAKE_NOP(opline) do { \ + opline->opcode = ZEND_NOP; \ + memset(&opline->result, 0, sizeof(opline->result)); \ + memset(&opline->op1, 0, sizeof(opline->op1)); \ + memset(&opline->op2, 0, sizeof(opline->op2)); \ + opline->result_type = opline->op1_type = opline->op2_type = IS_UNUSED; \ +} while (0) + #define RESET_DOC_COMMENT() do { \ if (CG(doc_comment)) { \ zend_string_release(CG(doc_comment)); \ @@ -223,6 +231,9 @@ typedef struct _zend_try_catch_element { /* internal function is allocated at arena */ #define ZEND_ACC_ARENA_ALLOCATED 0x20000000 +/* Function has a return type hint (or class has such non-private function) */ +#define ZEND_ACC_HAS_RETURN_TYPE 0x40000000 + #define ZEND_CE_IS_TRAIT(ce) (((ce)->ce_flags & ZEND_ACC_TRAIT) == ZEND_ACC_TRAIT) char *zend_visibility_string(uint32_t fn_flags); @@ -256,6 +267,16 @@ typedef struct _zend_arg_info { zend_bool is_variadic; } zend_arg_info; +/* TODO: (maybe) unify type information across the various places, such as + * return types, parameter types and exceptions */ +typedef struct _zend_type_decl { + zend_string *name; + zend_uchar kind; +} zend_type_decl; + +void zend_type_decl_ctor(zend_type_decl*, zend_uchar, zend_string*); +void zend_type_decl_dtor(zend_type_decl*); + /* the following structure repeats the layout of zend_arg_info, * but its fields have different meaning. It's used as the first element of * arg_info array to define properties of internal functions. @@ -281,6 +302,7 @@ struct _zend_op_array { uint32_t num_args; uint32_t required_num_args; zend_arg_info *arg_info; + zend_type_decl return_type; /* END of common elements */ uint32_t *refcount; @@ -331,6 +353,7 @@ typedef struct _zend_internal_function { uint32_t num_args; uint32_t required_num_args; zend_arg_info *arg_info; + zend_type_decl return_type; /* END of common elements */ void (*handler)(INTERNAL_FUNCTION_PARAMETERS); @@ -351,6 +374,7 @@ union _zend_function { uint32_t num_args; uint32_t required_num_args; zend_arg_info *arg_info; + zend_type_decl return_type; } common; zend_op_array op_array; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 2fbd1d50116dc..69aa0a7cf3b9c 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -78,6 +78,7 @@ static const zend_internal_function zend_pass_function = { 0, /* num_args */ 0, /* required_num_args */ NULL, /* arg_info */ + {NULL, IS_UNDEF}, /* return_type */ ZEND_FN(pass), /* handler */ NULL /* module */ }; @@ -525,6 +526,60 @@ static inline zval* make_real_object(zval *object_ptr TSRMLS_DC) return object; } +ZEND_API void zend_return_type_error(int type, zend_function *function, zval *returned, const char *message TSRMLS_DC) { + char *scope = NULL; + char *expected = NULL; + char *got = NULL; + char *ftype = (function->common.fn_flags & ZEND_ACC_GENERATOR) ? "generator" : "function"; + + if (function->common.scope) { + zend_spprintf(&scope, 0, "%s::%s", + ZEND_FN_SCOPE_NAME(function), + function->common.function_name->val); + } else scope = estrdup(function->common.function_name->val); + + switch (function->common.return_type.kind) { + case IS_OBJECT: zend_spprintf(&expected, 0, "an object of class %s", function->common.return_type.name->val); break; + case IS_ARRAY: zend_spprintf(&expected, 0, "an array"); break; + default: + zend_spprintf(&expected, 0, "a %s", zend_get_type_by_const(function->common.return_type.kind)); + } + + if (message) { + zend_error(type, "The %s %s was expected to return %s, %s", ftype, scope, expected, message); + efree(scope); + efree(expected); + return; + } + + if (!returned) { + got = "nothing"; + } else { + if (Z_TYPE_P(returned) == IS_OBJECT) { + zend_spprintf(&got, 0, "an object of class %s", Z_OBJCE_P(returned)->name->val); + } else switch (Z_TYPE_P(returned)) { + case IS_NULL: + got = estrdup("null"); + break; + + case IS_LONG: + case IS_ARRAY: { + zend_spprintf(&got, 0, "an %s", zend_zval_type_name(returned)); + } break; + + default: + zend_spprintf(&got, 0, "a %s", zend_zval_type_name(returned)); + } + } + + zend_error(type, "The %s %s was expected to return %s and returned %s", ftype, scope, expected, got); + + efree(scope); + efree(expected); + if (returned) + efree(got); +} + ZEND_API char * zend_verify_arg_class_kind(const zend_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce TSRMLS_DC) { zend_string *key; @@ -698,6 +753,31 @@ static void zend_verify_missing_arg(zend_execute_data *execute_data, uint32_t ar } } +static zend_bool zval_fits_type(const zval *variable, const zend_uchar type, const zend_class_entry *type_ce TSRMLS_DC) +{ + assert(variable != NULL); + switch (type) { + case IS_ARRAY: + return Z_TYPE_P(variable) == IS_ARRAY; + + case IS_CALLABLE: + return zend_is_callable(variable, IS_CALLABLE_CHECK_SILENT, NULL TSRMLS_CC); + + case IS_OBJECT: + /* If the type does not exist then the variable can't be of that type */ + if (type_ce == NULL) { + return 0; + } + return instanceof_function(Z_OBJCE_P(variable), type_ce TSRMLS_CC); + + case IS_UNDEF: + return 1; + + default: + assert (false && "Unknown type"); + } +} + static zend_always_inline void zend_assign_to_object(zval *retval, zval *object, uint32_t object_op_type, zval *property_name, int value_type, const znode_op *value_op, const zend_execute_data *execute_data, int opcode, void **cache_slot TSRMLS_DC) { zend_free_op free_value; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 8a621cf34829d..a980bfe182876 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -48,6 +48,7 @@ ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char ZEND_API int zend_eval_string_ex(char *str, zval *retval_ptr, char *string_name, int handle_exceptions TSRMLS_DC); ZEND_API int zend_eval_stringl_ex(char *str, size_t str_len, zval *retval_ptr, char *string_name, int handle_exceptions TSRMLS_DC); +ZEND_API void zend_return_type_error(int type, zend_function *function, zval *returned, const char *message TSRMLS_DC); ZEND_API char * zend_verify_arg_class_kind(const zend_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce TSRMLS_DC); ZEND_API void zend_verify_arg_error(int error_type, const zend_function *zf, uint32_t arg_num, const char *need_msg, const char *need_kind, const char *given_msg, const char *given_kind, zval *arg TSRMLS_DC); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 43ef22074faf7..d17e9677b5aef 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -21,6 +21,7 @@ #include "zend_API.h" #include "zend_compile.h" #include "zend_execute.h" +#include "zend_inheritance.h" #include "zend_smart_str.h" static void ptr_dtor(zval *zv) /* {{{ */ @@ -29,6 +30,149 @@ static void ptr_dtor(zval *zv) /* {{{ */ } /* }}} */ +static zend_string *zend_get_function_declaration(const zend_function *fptr TSRMLS_DC) /* {{{ */ +{ + smart_str str = {0}; + + if (fptr->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) { + smart_str_appends(&str, "& "); + } + + if (fptr->common.scope) { + smart_str_append(&str, fptr->common.scope->name); + smart_str_appends(&str, "::"); + } + + smart_str_append(&str, fptr->common.function_name); + smart_str_appendc(&str, '('); + + if (fptr->common.arg_info) { + uint32_t i, required; + zend_arg_info *arg_info = fptr->common.arg_info; + + required = fptr->common.required_num_args; + for (i = 0; i < fptr->common.num_args;) { + if (arg_info->class_name) { + const char *class_name; + size_t class_name_len; + if (!strcasecmp(arg_info->class_name, "self") && fptr->common.scope) { + class_name = fptr->common.scope->name->val; + class_name_len = fptr->common.scope->name->len; + } else if (!strcasecmp(arg_info->class_name, "parent") && fptr->common.scope->parent) { + class_name = fptr->common.scope->parent->name->val; + class_name_len = fptr->common.scope->parent->name->len; + } else { + class_name = arg_info->class_name; + class_name_len = arg_info->class_name_len; + } + + smart_str_appendl(&str, class_name, class_name_len); + smart_str_appendc(&str, ' '); + } else if (arg_info->type_hint) { + const char *type_name = zend_get_type_by_const(arg_info->type_hint); + smart_str_appends(&str, type_name); + smart_str_appendc(&str, ' '); + } + + if (arg_info->pass_by_reference) { + smart_str_appendc(&str, '&'); + } + + if (arg_info->is_variadic) { + smart_str_appends(&str, "..."); + } + + smart_str_appendc(&str, '$'); + + if (arg_info->name) { + smart_str_appendl(&str, arg_info->name, arg_info->name_len); + } else { + smart_str_appends(&str, "param"); + smart_str_append_unsigned(&str, i); + } + + if (i >= required && !arg_info->is_variadic) { + smart_str_appends(&str, " = "); + if (fptr->type == ZEND_USER_FUNCTION) { + zend_op *precv = NULL; + { + uint32_t idx = i; + zend_op *op = fptr->op_array.opcodes; + zend_op *end = op + fptr->op_array.last; + + ++idx; + while (op < end) { + if ((op->opcode == ZEND_RECV || op->opcode == ZEND_RECV_INIT) + && op->op1.num == (zend_ulong)idx) + { + precv = op; + } + ++op; + } + } + if (precv && precv->opcode == ZEND_RECV_INIT && precv->op2_type != IS_UNUSED) { + zval *zv = precv->op2.zv; + + if (Z_TYPE_P(zv) == IS_CONSTANT) { + smart_str_append(&str, Z_STR_P(zv)); + } else if (Z_TYPE_P(zv) == IS_FALSE) { + smart_str_appends(&str, "false"); + } else if (Z_TYPE_P(zv) == IS_TRUE) { + smart_str_appends(&str, "true"); + } else if (Z_TYPE_P(zv) == IS_NULL) { + smart_str_appends(&str, "NULL"); + } else if (Z_TYPE_P(zv) == IS_STRING) { + smart_str_appendc(&str, '\''); + smart_str_appendl(&str, Z_STRVAL_P(zv), MIN(Z_STRLEN_P(zv), 10)); + if (Z_STRLEN_P(zv) > 10) { + smart_str_appends(&str, "..."); + } + smart_str_appendc(&str, '\''); + } else if (Z_TYPE_P(zv) == IS_ARRAY) { + smart_str_appends(&str, "Array"); + } else if (Z_TYPE_P(zv) == IS_CONSTANT_AST) { + smart_str_appends(&str, ""); + } else { + zend_string *zv_str = zval_get_string(zv); + smart_str_append(&str, zv_str); + zend_string_release(zv_str); + } + } + } else { + smart_str_appends(&str, "NULL"); + } + } + + if (++i < fptr->common.num_args) { + smart_str_appends(&str, ", "); + } + arg_info++; + } + } + smart_str_appendc(&str, ')'); + + if (fptr->common.return_type.kind) { + smart_str_appends(&str, ": "); + + switch (fptr->common.return_type.kind) { + case IS_OBJECT: + smart_str_appends(&str, fptr->common.return_type.name->val); + break; + + default: { + char *type = zend_get_type_by_const(fptr->common.return_type.kind); + if (type) { + smart_str_appends(&str, type); + } + } + } + } + smart_str_0(&str); + + return str.s; +} +/* }}} */ + static zend_property_info *zend_duplicate_property_info(zend_property_info *property_info TSRMLS_DC) /* {{{ */ { zend_property_info* new_property_info; @@ -235,10 +379,48 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c } /* If both methods are private do not enforce a signature */ - if ((fe->common.fn_flags & ZEND_ACC_PRIVATE) && (proto->common.fn_flags & ZEND_ACC_PRIVATE)) { + if ((fe->common.fn_flags & ZEND_ACC_PRIVATE) && (proto->common.fn_flags & ZEND_ACC_PRIVATE)) { return 1; } + /* Check return type compatibility */ + if (proto->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + if (!(fe->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + return 0; + } else if (fe->common.return_type.kind != proto->common.return_type.kind) { + return 0; + } else if (fe->common.return_type.kind == IS_OBJECT) { + /* This must be checked at runtime */ + + zend_class_entry *child_ce; + zend_class_entry *parent_ce; + if (zend_string_equals_literal_ci(fe->common.return_type.name, "parent")) { + assert(fe->common.scope && fe->common.scope->parent); + child_ce = fe->common.scope->parent; + } else if (zend_string_equals_literal_ci(fe->common.return_type.name, "self")) { + assert(fe->common.scope); + child_ce = fe->common.scope; + } else { + child_ce = zend_fetch_class_by_name(fe->common.return_type.name, NULL, 0 TSRMLS_CC); + } + if (zend_string_equals_literal_ci(proto->common.return_type.name, "parent")) { + assert(proto->common.scope && proto->common.scope->parent); + parent_ce = proto->common.scope->parent; + } else if (zend_string_equals_literal_ci(proto->common.return_type.name, "self")) { + assert(proto->common.scope); + parent_ce = proto->common.scope; + } else { + parent_ce = zend_fetch_class_by_name(proto->common.return_type.name, NULL, 0 TSRMLS_CC); + } + + if (!instanceof_function(child_ce, parent_ce TSRMLS_CC)) { + zend_string *method_prototype = zend_get_function_declaration(proto TSRMLS_CC); + zend_error(E_COMPILE_ERROR, "Declaration of %s::%s should be compatible with %s, return type mismatch", ZEND_FN_SCOPE_NAME(fe), fe->common.function_name->val, method_prototype->val); + zend_string_free(method_prototype); + } + } + } + /* check number of arguments */ if (proto->common.required_num_args < fe->common.required_num_args || proto->common.num_args > fe->common.num_args) { @@ -348,133 +530,6 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c } /* }}} */ -static zend_string *zend_get_function_declaration(zend_function *fptr TSRMLS_DC) /* {{{ */ -{ - smart_str str = {0}; - - if (fptr->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) { - smart_str_appends(&str, "& "); - } - - if (fptr->common.scope) { - smart_str_append(&str, fptr->common.scope->name); - smart_str_appends(&str, "::"); - } - - smart_str_append(&str, fptr->common.function_name); - smart_str_appendc(&str, '('); - - if (fptr->common.arg_info) { - uint32_t i, required; - zend_arg_info *arg_info = fptr->common.arg_info; - - required = fptr->common.required_num_args; - for (i = 0; i < fptr->common.num_args;) { - if (arg_info->class_name) { - const char *class_name; - size_t class_name_len; - if (!strcasecmp(arg_info->class_name, "self") && fptr->common.scope) { - class_name = fptr->common.scope->name->val; - class_name_len = fptr->common.scope->name->len; - } else if (!strcasecmp(arg_info->class_name, "parent") && fptr->common.scope->parent) { - class_name = fptr->common.scope->parent->name->val; - class_name_len = fptr->common.scope->parent->name->len; - } else { - class_name = arg_info->class_name; - class_name_len = arg_info->class_name_len; - } - - smart_str_appendl(&str, class_name, class_name_len); - smart_str_appendc(&str, ' '); - } else if (arg_info->type_hint) { - const char *type_name = zend_get_type_by_const(arg_info->type_hint); - smart_str_appends(&str, type_name); - smart_str_appendc(&str, ' '); - } - - if (arg_info->pass_by_reference) { - smart_str_appendc(&str, '&'); - } - - if (arg_info->is_variadic) { - smart_str_appends(&str, "..."); - } - - smart_str_appendc(&str, '$'); - - if (arg_info->name) { - smart_str_appendl(&str, arg_info->name, arg_info->name_len); - } else { - smart_str_appends(&str, "param"); - smart_str_append_unsigned(&str, i); - } - - if (i >= required && !arg_info->is_variadic) { - smart_str_appends(&str, " = "); - if (fptr->type == ZEND_USER_FUNCTION) { - zend_op *precv = NULL; - { - uint32_t idx = i; - zend_op *op = fptr->op_array.opcodes; - zend_op *end = op + fptr->op_array.last; - - ++idx; - while (op < end) { - if ((op->opcode == ZEND_RECV || op->opcode == ZEND_RECV_INIT) - && op->op1.num == (zend_ulong)idx) - { - precv = op; - } - ++op; - } - } - if (precv && precv->opcode == ZEND_RECV_INIT && precv->op2_type != IS_UNUSED) { - zval *zv = precv->op2.zv; - - if (Z_TYPE_P(zv) == IS_CONSTANT) { - smart_str_append(&str, Z_STR_P(zv)); - } else if (Z_TYPE_P(zv) == IS_FALSE) { - smart_str_appends(&str, "false"); - } else if (Z_TYPE_P(zv) == IS_TRUE) { - smart_str_appends(&str, "true"); - } else if (Z_TYPE_P(zv) == IS_NULL) { - smart_str_appends(&str, "NULL"); - } else if (Z_TYPE_P(zv) == IS_STRING) { - smart_str_appendc(&str, '\''); - smart_str_appendl(&str, Z_STRVAL_P(zv), MIN(Z_STRLEN_P(zv), 10)); - if (Z_STRLEN_P(zv) > 10) { - smart_str_appends(&str, "..."); - } - smart_str_appendc(&str, '\''); - } else if (Z_TYPE_P(zv) == IS_ARRAY) { - smart_str_appends(&str, "Array"); - } else if (Z_TYPE_P(zv) == IS_CONSTANT_AST) { - smart_str_appends(&str, ""); - } else { - zend_string *zv_str = zval_get_string(zv); - smart_str_append(&str, zv_str); - zend_string_release(zv_str); - } - } - } else { - smart_str_appends(&str, "NULL"); - } - } - - if (++i < fptr->common.num_args) { - smart_str_appends(&str, ", "); - } - arg_info++; - } - } - - smart_str_appendc(&str, ')'); - smart_str_0(&str); - - return str.s; -} -/* }}} */ - static void do_inheritance_check_on_method(zend_function *child, zend_function *parent TSRMLS_DC) /* {{{ */ { uint32_t child_flags; @@ -835,7 +890,7 @@ ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent /* The verification will be done in runtime by ZEND_VERIFY_ABSTRACT_CLASS */ zend_verify_abstract_class(ce TSRMLS_CC); } - ce->ce_flags |= parent_ce->ce_flags & ZEND_HAS_STATIC_IN_METHODS; + ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_RETURN_TYPE); } /* }}} */ diff --git a/Zend/zend_inheritance.h b/Zend/zend_inheritance.h index 647b4923710f0..456960fb864d9 100644 --- a/Zend/zend_inheritance.h +++ b/Zend/zend_inheritance.h @@ -33,6 +33,8 @@ ZEND_API void zend_do_bind_traits(zend_class_entry *ce TSRMLS_DC); ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce TSRMLS_DC); void zend_do_early_binding(TSRMLS_D); +void zend_verify_class_return_type_variance(zend_class_entry *child_ce, zend_class_entry *parent_ce TSRMLS_DC); + END_EXTERN_C() #endif diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 88286203da953..c3e680707e9fb 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -243,7 +243,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type variable_class_name dereferencable_scalar class_name_scalar constant dereferencable %type callable_expr callable_variable static_member new_variable %type assignment_list_element array_pair encaps_var encaps_var_offset isset_variables -%type isset_variable %type top_statement_list use_declarations const_list inner_statement_list if_stmt %type alt_if_stmt for_exprs switch_case_list global_var_list static_var_list %type echo_expr_list unset_variables catch_list parameter_list class_statement_list @@ -252,7 +251,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type class_const_list name_list trait_adaptations method_body non_empty_for_exprs %type ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars %type lexical_var_list encaps_list array_pair_list non_empty_array_pair_list -%type assignment_list +%type assignment_list isset_variable type return_type %type returns_ref function is_reference is_variadic class_type variable_modifiers %type method_modifiers trait_modifiers non_empty_member_modifiers member_modifier @@ -405,10 +404,10 @@ unset_variable: ; function_declaration_statement: - function returns_ref T_STRING '(' parameter_list ')' backup_doc_comment - '{' inner_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $7, - zend_ast_get_str($3), $5, NULL, $9); } + function returns_ref T_STRING '(' parameter_list ')' return_type + backup_doc_comment '{' inner_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $8, + zend_ast_get_str($3), $5, NULL, $10, $7); } ; is_reference: @@ -425,11 +424,11 @@ class_declaration_statement: class_type { $$ = CG(zend_lineno); } T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $2, $6, - zend_ast_get_str($3), $4, $5, $8); } + zend_ast_get_str($3), $4, $5, $8, NULL); } | T_INTERFACE { $$ = CG(zend_lineno); } T_STRING interface_extends_list backup_doc_comment '{' class_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $2, $5, - zend_ast_get_str($3), NULL, $4, $7); } + zend_ast_get_str($3), NULL, $4, $7, NULL); } ; class_type: @@ -556,11 +555,20 @@ parameter: optional_type: /* empty */ { $$ = NULL; } - | T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); } + | type { $$ = $1; } +; + +type: + T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); } | T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); } | name { $$ = $1; } ; +return_type: + /* empty */ { $$ = NULL; } + | ':' type { $$ = $2; } +; + argument_list: '(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); } | '(' non_empty_argument_list ')' { $$ = $2; } @@ -615,10 +623,10 @@ class_statement: { $$ = $2; RESET_DOC_COMMENT(); } | T_USE name_list trait_adaptations { $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); } - | method_modifiers function returns_ref T_STRING '(' parameter_list ')' backup_doc_comment - method_body - { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1, $2, $8, - zend_ast_get_str($4), $6, NULL, $9); } + | method_modifiers function returns_ref T_STRING '(' parameter_list ')' + return_type backup_doc_comment method_body + { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1, $2, $9, + zend_ast_get_str($4), $6, NULL, $10, $8); } ; name_list: @@ -852,16 +860,16 @@ expr_without_variable: | T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); } | T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); } | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); } - | function returns_ref '(' parameter_list ')' lexical_vars backup_doc_comment - '{' inner_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $7, + | function returns_ref '(' parameter_list ')' lexical_vars return_type + backup_doc_comment '{' inner_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $8, zend_string_init("{closure}", sizeof("{closure}") - 1, 0), - $4, $6, $9); } - | T_STATIC function returns_ref '(' parameter_list ')' lexical_vars backup_doc_comment - '{' inner_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | ZEND_ACC_STATIC, $2, $8, + $4, $6, $10, $7); } + | T_STATIC function returns_ref '(' parameter_list ')' lexical_vars + return_type backup_doc_comment '{' inner_statement_list '}' + { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | ZEND_ACC_STATIC, $2, $9, zend_string_init("{closure}", sizeof("{closure}") - 1, 0), - $5, $7, $10); } + $5, $7, $11, $8); } ; function: diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 1bbd35f40fada..61d997d9e1c3c 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -93,6 +93,8 @@ void init_op_array(zend_op_array *op_array, zend_uchar type, int initial_ops_siz op_array->run_time_cache = NULL; op_array->last_cache_slot = 0; + zend_type_decl_ctor(&op_array->return_type, IS_UNDEF, NULL); + memset(op_array->reserved, 0, ZEND_MAX_RESERVED_RESOURCES * sizeof(void*)); zend_llist_apply_with_argument(&zend_extensions, (llist_apply_with_arg_func_t) zend_extension_op_array_ctor_handler, op_array TSRMLS_CC); @@ -377,6 +379,8 @@ ZEND_API void destroy_op_array(zend_op_array *op_array TSRMLS_DC) } efree(op_array->arg_info); } + + zend_type_decl_dtor(&op_array->return_type); } void init_op(zend_op *op TSRMLS_DC) @@ -758,6 +762,11 @@ ZEND_API int pass_two(zend_op_array *op_array TSRMLS_DC) case ZEND_FE_FETCH: opline->op2.jmp_addr = &op_array->opcodes[opline->op2.opline_num]; break; + case ZEND_VERIFY_RETURN_TYPE: + if (op_array->fn_flags & ZEND_ACC_GENERATOR) { + MAKE_NOP(opline); + } + break; case ZEND_RETURN: case ZEND_RETURN_BY_REF: if (op_array->fn_flags & ZEND_ACC_GENERATOR) { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 760f5f99f72df..72324a0213be8 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2808,6 +2808,31 @@ ZEND_VM_C_LABEL(fcall_end): ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HANDLER(170, ZEND_VERIFY_RETURN_TYPE, ANY, VAR|UNUSED) +{ + USE_OPLINE + zval *retval_ptr; + zend_free_op free_op1, free_op2; + zend_type_decl *return_type = NULL; + zend_class_entry *ce = NULL; + + SAVE_OPLINE(); + retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R); + + if (OP1_TYPE == IS_UNUSED || Z_ISNULL_P(retval_ptr)) { + zend_return_type_error(E_RECOVERABLE_ERROR, EX(func), retval_ptr, NULL TSRMLS_CC); + } else { + return_type = &EX(func)->common.return_type; + ce = Z_CE_P(EX_VAR(opline->op2.var)); + + if (!zval_fits_type(retval_ptr, return_type->kind, ce TSRMLS_CC)) { + zend_return_type_error(E_RECOVERABLE_ERROR, EX(func), retval_ptr, NULL TSRMLS_CC); + } + } + CHECK_EXCEPTION(); + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY) { USE_OPLINE diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index dc3ee25c5219a..ffc3fe9225914 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -2125,6 +2125,31 @@ static int ZEND_FASTCALL ZEND_INIT_FCALL_BY_NAME_SPEC_VAR_HANDLER(ZEND_OPCODE_H } } +static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *retval_ptr; + zend_free_op free_op1; + zend_type_decl *return_type = NULL; + zend_class_entry *ce = NULL; + + SAVE_OPLINE(); + retval_ptr = get_zval_ptr(opline->op1_type, &opline->op1, execute_data, &free_op1, BP_VAR_R); + + if (opline->op1_type == IS_UNUSED || Z_ISNULL_P(retval_ptr)) { + zend_return_type_error(E_RECOVERABLE_ERROR, EX(func), retval_ptr, NULL TSRMLS_CC); + } else { + return_type = &EX(func)->common.return_type; + ce = Z_CE_P(EX_VAR(opline->op2.var)); + + if (!zval_fits_type(retval_ptr, return_type->kind, ce TSRMLS_CC)) { + zend_return_type_error(E_RECOVERABLE_ERROR, EX(func), retval_ptr, NULL TSRMLS_CC); + } + } + CHECK_EXCEPTION(); + ZEND_VM_NEXT_OPCODE(); +} + static int ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -2164,6 +2189,31 @@ static int ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDL } } +static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *retval_ptr; + zend_free_op free_op1; + zend_type_decl *return_type = NULL; + zend_class_entry *ce = NULL; + + SAVE_OPLINE(); + retval_ptr = get_zval_ptr(opline->op1_type, &opline->op1, execute_data, &free_op1, BP_VAR_R); + + if (opline->op1_type == IS_UNUSED || Z_ISNULL_P(retval_ptr)) { + zend_return_type_error(E_RECOVERABLE_ERROR, EX(func), retval_ptr, NULL TSRMLS_CC); + } else { + return_type = &EX(func)->common.return_type; + ce = Z_CE_P(EX_VAR(opline->op2.var)); + + if (!zval_fits_type(retval_ptr, return_type->kind, ce TSRMLS_CC)) { + zend_return_type_error(E_RECOVERABLE_ERROR, EX(func), retval_ptr, NULL TSRMLS_CC); + } + } + CHECK_EXCEPTION(); + ZEND_VM_NEXT_OPCODE(); +} + static int ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -48875,6 +48925,31 @@ void zend_init_opcodes_handlers(void) ZEND_COALESCE_SPEC_CV_HANDLER, ZEND_COALESCE_SPEC_CV_HANDLER, ZEND_COALESCE_SPEC_CV_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_NULL_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_HANDLER, + ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_HANDLER, + ZEND_NULL_HANDLER, ZEND_NULL_HANDLER }; zend_opcode_handlers = (opcode_handler_t*)labels; diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c index 067fd362e686a..0151f8ac8ca74 100644 --- a/Zend/zend_vm_opcodes.c +++ b/Zend/zend_vm_opcodes.c @@ -21,7 +21,7 @@ #include #include -const char *zend_vm_opcodes_map[170] = { +const char *zend_vm_opcodes_map[171] = { "ZEND_NOP", "ZEND_ADD", "ZEND_SUB", @@ -192,6 +192,7 @@ const char *zend_vm_opcodes_map[170] = { "ZEND_ASSIGN_POW", "ZEND_BIND_GLOBAL", "ZEND_COALESCE", + "ZEND_VERIFY_RETURN_TYPE", }; ZEND_API const char* zend_get_opcode_name(zend_uchar opcode) { diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h index 9d969877e0b8c..fee652f9f3048 100644 --- a/Zend/zend_vm_opcodes.h +++ b/Zend/zend_vm_opcodes.h @@ -182,5 +182,6 @@ END_EXTERN_C() #define ZEND_ASSIGN_POW 167 #define ZEND_BIND_GLOBAL 168 #define ZEND_COALESCE 169 +#define ZEND_VERIFY_RETURN_TYPE 170 #endif diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 3e91740a71341..b96ee51541d9e 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -430,6 +430,14 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc } } + if (op_array->return_type.kind == IS_OBJECT) { + if (already_stored) { + op_array->return_type.name = zend_shared_alloc_get_xlat_entry(op_array->return_type.name); + } else { + zend_accel_store_string(op_array->return_type.name); + } + } + if (op_array->brk_cont_array) { zend_accel_store(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont); } diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 0a7b8356487ca..e0d04cc990135 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -210,6 +210,10 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array TSRMLS_DC) } } + if (op_array->return_type.kind == IS_OBJECT) { + ADD_STRING(op_array->return_type.name); + } + if (op_array->brk_cont_array) { ADD_DUP_SIZE(op_array->brk_cont_array, sizeof(zend_brk_cont_element) * op_array->last_brk_cont); } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index e9c59ea86e935..fded7ad54c006 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -53,6 +53,7 @@ PHPAPI zend_class_entry *reflector_ptr; PHPAPI zend_class_entry *reflection_exception_ptr; PHPAPI zend_class_entry *reflection_ptr; +PHPAPI zend_class_entry *reflection_type_ptr; PHPAPI zend_class_entry *reflection_function_abstract_ptr; PHPAPI zend_class_entry *reflection_function_ptr; PHPAPI zend_class_entry *reflection_parameter_ptr; @@ -203,7 +204,8 @@ typedef enum { REF_TYPE_FUNCTION, REF_TYPE_PARAMETER, REF_TYPE_PROPERTY, - REF_TYPE_DYNAMIC_PROPERTY + REF_TYPE_DYNAMIC_PROPERTY, + REF_TYPE_TYPE } reflection_type_t; /* Struct for reflection objects */ @@ -315,6 +317,7 @@ static void reflection_free_objects_storage(zend_object *object TSRMLS_DC) /* {{ zend_string_release(prop_reference->prop.name); efree(intern->ptr); break; + case REF_TYPE_TYPE: case REF_TYPE_OTHER: break; } @@ -1293,6 +1296,20 @@ static void reflection_method_factory(zend_class_entry *ce, zend_function *metho } /* }}} */ +/* {{{ reflection_type_factory */ +static void reflection_type_factory(zend_type_decl *type, zval *object TSRMLS_DC) +{ + zend_class_entry *ce = reflection_type_ptr; + reflection_object *intern; + + reflection_instantiate(ce, object TSRMLS_CC); + intern = Z_REFLECTION_P(object); + intern->ptr = type; + intern->ref_type = REF_TYPE_TYPE; + intern->ce = ce; +} +/* }}} */ + /* {{{ reflection_property_factory */ static void reflection_property_factory(zend_class_entry *ce, zend_property_info *prop, zval *object TSRMLS_DC) { @@ -2003,6 +2020,33 @@ ZEND_METHOD(reflection_function, returnsReference) } /* }}} */ +/* {{{ proto public bool ReflectionFunction::hasReturnType() + Returns whether this function has a return type */ +ZEND_METHOD(reflection_function, hasReturnType) +{ + reflection_object *intern; + zend_function *func; + + GET_REFLECTION_OBJECT_PTR(func); + + RETURN_BOOL(func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE); +} +/* }}} */ + +/* {{{ proto public string ReflectionFunction::getReturnType() + Gets the return type as a string; "" if there is not a return type */ +ZEND_METHOD(reflection_function, getReturnType) +{ + reflection_object *intern; + zend_function *func; + + GET_REFLECTION_OBJECT_PTR(func); + + reflection_type_factory(&func->common.return_type, return_value TSRMLS_CC); + +} +/* }}} */ + /* {{{ proto public bool ReflectionFunction::getNumberOfParameters() Gets the number of required parameters */ ZEND_METHOD(reflection_function, getNumberOfParameters) @@ -5652,6 +5696,57 @@ ZEND_METHOD(reflection_zend_extension, getCopyright) } /* }}} */ +/* {{{ proto public static mixed ReflectionType::export(string name [, bool return]) throws ReflectionException + Exports a reflection object. Returns the output if TRUE is specified for return, printing it otherwise. */ +ZEND_METHOD(reflection_type, export) +{ + _reflection_export(INTERNAL_FUNCTION_PARAM_PASSTHRU, reflection_method_ptr, 1); +} +/* }}} */ + +/* {{{ */ +ZEND_METHOD(reflection_type, getKind) +{ + reflection_object *intern; + zend_type_decl *type; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + GET_REFLECTION_OBJECT_PTR(type); + + RETURN_LONG(type->kind); +} +/* }}} */ + +/* {{{ */ +ZEND_METHOD(reflection_type, __toString) +{ + reflection_object *intern; + zend_type_decl *type; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + GET_REFLECTION_OBJECT_PTR(type); + + switch (type->kind) { + case IS_CALLABLE: + RETURN_STRING("callable"); + + case IS_ARRAY: + RETURN_STRING("array"); + + case IS_OBJECT: + RETURN_STRINGL(type->name->val, type->name->len); + + case IS_UNDEF: + default: + RETURN_EMPTY_STRING(); + } +} +/* }}} */ + /* {{{ method tables */ static const zend_function_entry reflection_exception_functions[] = { PHP_FE_END @@ -5723,6 +5818,8 @@ static const zend_function_entry reflection_function_abstract_functions[] = { ZEND_ME(reflection_function, getShortName, arginfo_reflection__void, 0) ZEND_ME(reflection_function, getStartLine, arginfo_reflection__void, 0) ZEND_ME(reflection_function, getStaticVariables, arginfo_reflection__void, 0) + ZEND_ME(reflection_function, hasReturnType, arginfo_reflection__void, 0) + ZEND_ME(reflection_function, getReturnType, arginfo_reflection__void, 0) ZEND_ME(reflection_function, returnsReference, arginfo_reflection__void, 0) PHP_FE_END }; @@ -6058,6 +6155,15 @@ static const zend_function_entry reflection_zend_extension_functions[] = { ZEND_ME(reflection_zend_extension, getCopyright, arginfo_reflection__void, 0) PHP_FE_END }; + +static const zend_function_entry reflection_type_functions[] = { + ZEND_ME(reflection_type, export, arginfo_reflection_extension_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC) + ZEND_ME(reflection_type, __toString, arginfo_reflection__void, ZEND_ACC_PUBLIC) + ZEND_MALIAS(reflection_type, getName, __toString, arginfo_reflection__void, ZEND_ACC_PUBLIC) + + ZEND_ME(reflection_type, getKind, arginfo_reflection__void, 0) + PHP_FE_END +}; /* }}} */ const zend_function_entry reflection_ext_functions[] = { /* {{{ */ @@ -6174,6 +6280,16 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ zend_class_implements(reflection_zend_extension_ptr TSRMLS_CC, 1, reflector_ptr); zend_declare_property_string(reflection_zend_extension_ptr, "name", sizeof("name")-1, "", ZEND_ACC_PUBLIC TSRMLS_CC); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionType", reflection_type_functions); + _reflection_entry.create_object = reflection_objects_new; + reflection_type_ptr = zend_register_internal_class(&_reflection_entry TSRMLS_CC); + zend_class_implements(reflection_type_ptr TSRMLS_CC, 1, reflector_ptr); + + REGISTER_REFLECTION_CLASS_CONST_LONG(type, "IS_UNDECLARED", IS_UNDEF); + REGISTER_REFLECTION_CLASS_CONST_LONG(type, "IS_ARRAY", IS_ARRAY); + REGISTER_REFLECTION_CLASS_CONST_LONG(type, "IS_CALLABLE", IS_CALLABLE); + REGISTER_REFLECTION_CLASS_CONST_LONG(type, "IS_OBJECT", IS_OBJECT); + return SUCCESS; } /* }}} */ From f212bc31e76a3d5d9897095ec4fe081fe1a025d3 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sun, 16 Nov 2014 19:43:02 -0700 Subject: [PATCH 2/3] Fix return type covariance bug Clean up and document the covariance check too. --- Zend/tests/return_types/009.phpt | 2 +- Zend/tests/return_types/inheritance001.phpt | 2 +- Zend/tests/return_types/inheritance002.phpt | 2 +- Zend/tests/return_types/inheritance003.phpt | 2 +- Zend/tests/return_types/inheritance004.phpt | 15 +++ Zend/tests/return_types/inheritance008.phpt | 15 +++ Zend/zend_inheritance.c | 108 +++++++++++++------- 7 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 Zend/tests/return_types/inheritance004.phpt create mode 100644 Zend/tests/return_types/inheritance008.phpt diff --git a/Zend/tests/return_types/009.phpt b/Zend/tests/return_types/009.phpt index 949044388fab3..0c2b4376a188b 100644 --- a/Zend/tests/return_types/009.phpt +++ b/Zend/tests/return_types/009.phpt @@ -16,4 +16,4 @@ class qux implements foo { } --EXPECTF-- -Fatal error: Declaration of qux::bar should be compatible with foo::bar(): foo, return type mismatch in %s on line %d +Fatal error: Declaration of qux::bar() must be compatible with foo::bar(): foo in %s on line %d diff --git a/Zend/tests/return_types/inheritance001.phpt b/Zend/tests/return_types/inheritance001.phpt index df4049e9fb938..ce33d97995e53 100644 --- a/Zend/tests/return_types/inheritance001.phpt +++ b/Zend/tests/return_types/inheritance001.phpt @@ -13,4 +13,4 @@ class B extends A { } --EXPECTF-- -Fatal error: Declaration of B::foo should be compatible with A::foo(): A, return type mismatch in %s on line %d +Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d diff --git a/Zend/tests/return_types/inheritance002.phpt b/Zend/tests/return_types/inheritance002.phpt index 6bd0cb01e25fc..f63a8ebe89f67 100644 --- a/Zend/tests/return_types/inheritance002.phpt +++ b/Zend/tests/return_types/inheritance002.phpt @@ -13,4 +13,4 @@ class B extends A { } --EXPECTF-- -Fatal error: Declaration of B::foo should be compatible with A::foo(): A, return type mismatch in %s on line %d +Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d diff --git a/Zend/tests/return_types/inheritance003.phpt b/Zend/tests/return_types/inheritance003.phpt index 858e7a457af0b..e627363426ef5 100644 --- a/Zend/tests/return_types/inheritance003.phpt +++ b/Zend/tests/return_types/inheritance003.phpt @@ -13,4 +13,4 @@ class B implements A { } --EXPECTF-- -Fatal error: Declaration of B::foo should be compatible with A::foo(): A, return type mismatch in %s on line %d +Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d diff --git a/Zend/tests/return_types/inheritance004.phpt b/Zend/tests/return_types/inheritance004.phpt new file mode 100644 index 0000000000000..29bc13a2b8556 --- /dev/null +++ b/Zend/tests/return_types/inheritance004.phpt @@ -0,0 +1,15 @@ +--TEST-- +Compile-time return type invariance; self and parent + +--FILE-- +common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { + return 0; + } + + if (fe->common.return_type.kind != proto->common.return_type.kind) { + return 0; + } + + if (fe->common.return_type.kind != IS_OBJECT) { + return 1; + } + + /* For objects there are two major steps: + * 1. Checking for invariance (cheap) + * 2. Checking for covariance (not as cheap) */ + child_name = fe->common.return_type.name; + parent_name = proto->common.return_type.name; + + /* 1a. Resolve "parent" and "self" to class names */ + if (zend_string_equals_literal_ci(child_name, "parent")) { + child_name = proto->common.scope->name; + } else if (zend_string_equals_literal_ci(child_name, "self")) { + child_name = fe->common.scope->name; + } + + if (proto->common.scope->parent && zend_string_equals_literal_ci(parent_name, "parent")) { + parent_name = proto->common.scope->parent->name; + } else if (zend_string_equals_literal_ci(parent_name, "self")) { + parent_name = proto->common.scope->name; + } + + /* 1b. If the type names are equal they are invariant */ + if (zend_string_equals(child_name, parent_name)) { + return 1; + } + + /* 2a. Bind the type names to class entries; fetch the class if needed + */ + if (zend_string_equals(child_name, proto->common.scope->name)) { + child_ce = proto->common.scope; + } else if (zend_string_equals(child_name, fe->common.scope->name)) { + child_ce = fe->common.scope; + } else { + child_ce = zend_fetch_class_by_name(child_name, NULL, 0 TSRMLS_CC); + } + + if (proto->common.scope->parent && zend_string_equals(parent_name, proto->common.scope->parent->name)) { + parent_ce = proto->common.scope->parent; + } else if (zend_string_equals(parent_name, proto->common.scope->name)) { + parent_ce = proto->common.scope; + } else { + parent_ce = zend_fetch_class_by_name(parent_name, NULL, 0 TSRMLS_CC); + } + + /* 2b. Check for covariance; if the child type is not a subclass of + * the parent then they are not covariant */ + return instanceof_function(child_ce, parent_ce TSRMLS_CC); +} + static zend_function *do_inherit_method(zend_function *old_function, zend_class_entry *ce TSRMLS_DC) /* {{{ */ { zend_function *new_function; @@ -384,41 +452,9 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c } /* Check return type compatibility */ - if (proto->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { - if (!(fe->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { - return 0; - } else if (fe->common.return_type.kind != proto->common.return_type.kind) { - return 0; - } else if (fe->common.return_type.kind == IS_OBJECT) { - /* This must be checked at runtime */ - - zend_class_entry *child_ce; - zend_class_entry *parent_ce; - if (zend_string_equals_literal_ci(fe->common.return_type.name, "parent")) { - assert(fe->common.scope && fe->common.scope->parent); - child_ce = fe->common.scope->parent; - } else if (zend_string_equals_literal_ci(fe->common.return_type.name, "self")) { - assert(fe->common.scope); - child_ce = fe->common.scope; - } else { - child_ce = zend_fetch_class_by_name(fe->common.return_type.name, NULL, 0 TSRMLS_CC); - } - if (zend_string_equals_literal_ci(proto->common.return_type.name, "parent")) { - assert(proto->common.scope && proto->common.scope->parent); - parent_ce = proto->common.scope->parent; - } else if (zend_string_equals_literal_ci(proto->common.return_type.name, "self")) { - assert(proto->common.scope); - parent_ce = proto->common.scope; - } else { - parent_ce = zend_fetch_class_by_name(proto->common.return_type.name, NULL, 0 TSRMLS_CC); - } - - if (!instanceof_function(child_ce, parent_ce TSRMLS_CC)) { - zend_string *method_prototype = zend_get_function_declaration(proto TSRMLS_CC); - zend_error(E_COMPILE_ERROR, "Declaration of %s::%s should be compatible with %s, return type mismatch", ZEND_FN_SCOPE_NAME(fe), fe->common.function_name->val, method_prototype->val); - zend_string_free(method_prototype); - } - } + if ((proto->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) + && !zend_do_return_type_inheritance_check(fe, proto TSRMLS_CC)) { + return 0; } /* check number of arguments */ @@ -588,7 +624,7 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function * child->common.prototype = parent->common.prototype ? parent->common.prototype : parent; } - if (child->common.prototype && (child->common.prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) { + if (child->common.prototype && (child->common.prototype->common.fn_flags & (ZEND_ACC_ABSTRACT | ZEND_ACC_HAS_RETURN_TYPE))) { if (!zend_do_perform_implementation_check(child, child->common.prototype TSRMLS_CC)) { zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s::%s() must be compatible with %s", ZEND_FN_SCOPE_NAME(child), child->common.function_name->val, zend_get_function_declaration(child->common.prototype TSRMLS_CC)->val); } From 170d129df8cdef436f174d82d4518de99e31f6a3 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Sun, 23 Nov 2014 18:26:49 -0700 Subject: [PATCH 3/3] Fix case insensitivity for return types --- Zend/zend_execute.c | 2 +- Zend/zend_inheritance.c | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 69aa0a7cf3b9c..f59657ab5f169 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -774,7 +774,7 @@ static zend_bool zval_fits_type(const zval *variable, const zend_uchar type, con return 1; default: - assert (false && "Unknown type"); + assert (0 && "Unknown type"); } } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 7b2e76d685535..9523a245f7ddc 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -332,8 +332,17 @@ char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ /* }}} */ +/* TODO: move this elsewhere */ +static inline +zend_bool zend_string_equals_ci(const zend_string *a, const zend_string *b) /* {{{ */ +{ + return a->len == b->len && !zend_binary_strcasecmp(a->val, a->len, b->val, b->len); +} +/* }}} */ + + static -zend_bool zend_do_return_type_inheritance_check(const zend_function *fe, const zend_function *proto TSRMLS_DC) +zend_bool zend_do_return_type_inheritance_check(const zend_function *fe, const zend_function *proto TSRMLS_DC) /* {{{ */ { zend_class_entry *child_ce; zend_class_entry *parent_ce; @@ -372,23 +381,23 @@ zend_bool zend_do_return_type_inheritance_check(const zend_function *fe, const z } /* 1b. If the type names are equal they are invariant */ - if (zend_string_equals(child_name, parent_name)) { + if (zend_string_equals_ci(child_name, parent_name)) { return 1; } /* 2a. Bind the type names to class entries; fetch the class if needed */ - if (zend_string_equals(child_name, proto->common.scope->name)) { + if (zend_string_equals_ci(child_name, proto->common.scope->name)) { child_ce = proto->common.scope; - } else if (zend_string_equals(child_name, fe->common.scope->name)) { + } else if (zend_string_equals_ci(child_name, fe->common.scope->name)) { child_ce = fe->common.scope; } else { child_ce = zend_fetch_class_by_name(child_name, NULL, 0 TSRMLS_CC); } - if (proto->common.scope->parent && zend_string_equals(parent_name, proto->common.scope->parent->name)) { + if (proto->common.scope->parent && zend_string_equals_ci(parent_name, proto->common.scope->parent->name)) { parent_ce = proto->common.scope->parent; - } else if (zend_string_equals(parent_name, proto->common.scope->name)) { + } else if (zend_string_equals_ci(parent_name, proto->common.scope->name)) { parent_ce = proto->common.scope; } else { parent_ce = zend_fetch_class_by_name(parent_name, NULL, 0 TSRMLS_CC); @@ -398,6 +407,7 @@ zend_bool zend_do_return_type_inheritance_check(const zend_function *fe, const z * the parent then they are not covariant */ return instanceof_function(child_ce, parent_ce TSRMLS_CC); } +/* }}} */ static zend_function *do_inherit_method(zend_function *old_function, zend_class_entry *ce TSRMLS_DC) /* {{{ */ {