Permalink
Browse files

[RFC] implement throws type for functions and methods

1 parent 0ecffc8 commit 46d8c666663d1ed0720bd14d9602a44b886f88dc @krakjoe committed Feb 5, 2017
@@ -0,0 +1,9 @@
+--TEST--
+Testing basic exception type compiler wrong type
+--FILE--
+<?php
+function () throws int { }
+?>
+--EXPECTF--
+Fatal error: Throws type must be an object in %s on line 2
+
@@ -0,0 +1,10 @@
+--TEST--
+Testing basic exception type compiler pass
+--FILE--
+<?php
+function test () throws Exception {}
+?>
+OK
+--EXPECT--
+OK
+
@@ -0,0 +1,12 @@
+--TEST--
+Testing basic exception type compiler pass with return
+--FILE--
+<?php
+function test () : int throws Exception {
+ return 1;
+}
+?>
+OK
+--EXPECT--
+OK
+
@@ -0,0 +1,12 @@
+--TEST--
+Testing basic exception type compiler pass with param
+--FILE--
+<?php
+function test (int $param) throws Exception {
+ return $param;
+}
+?>
+OK
+--EXPECT--
+OK
+
@@ -0,0 +1,21 @@
+--TEST--
+Testing exception type compiler fail inheritance remove throws
+--FILE--
+<?php
+class A {
+ public function throwing () throws Exception {
+
+ }
+}
+
+class B extends A {
+ public function throwing() {
+
+ }
+}
+?>
+--EXPECTF--
+Fatal error: Declaration of B::throwing() must be compatible with A::throwing() throws Exception in %s on line 12
+
+
+
@@ -0,0 +1,21 @@
+--TEST--
+Testing exception type compiler pass inheritance add throws
+--FILE--
+<?php
+class A {
+ public function throwing () {
+
+ }
+}
+
+class B extends A {
+ public function throwing() throws Exception {
+
+ }
+}
+?>
+OK
+--EXPECT--
+OK
+
+
@@ -0,0 +1,21 @@
+--TEST--
+Testing exception type compiler fail inheritance changes throws
+--FILE--
+<?php
+class A {
+ public function throwing () throws Exception {
+
+ }
+}
+
+class B extends A {
+ public function throwing() throws FancyException {
+
+ }
+}
+?>
+--EXPECTF--
+Fatal error: Declaration of B::throwing() throws FancyException must be compatible with A::throwing() throws Exception in %s on line 12
+
+
+
@@ -0,0 +1,20 @@
+--TEST--
+Testing exception type executor fail
+--DESCRIPTION--
+I have no idea how to update Optimizer, so temporarily disabled it
+--INI--
+opcache.optimization_level=0
+--FILE--
+<?php
+function test () throws FancyException {
+ throw new Exception();
+}
+test();
+?>
+--EXPECTF--
+Fatal error: Uncaught TypeError: Exception thrown by test() expected to be an instance of FancyException, instance of Exception thrown in %s:3
+Stack trace:
+#0 %s(5): test()
+#1 {main}
+ thrown in %s on line 3
+
@@ -0,0 +1,17 @@
+--TEST--
+Testing exception type executor pass
+--FILE--
+<?php
+class FancyException extends Exception {}
+
+function test () throws FancyException {
+ throw new FancyException();
+}
+
+try {
+ test();
+} catch (FancyException $ex) {}
+?>
+OK
+--EXPECT--
+OK
@@ -0,0 +1,17 @@
+--TEST--
+Testing exception type executor with return pass
+--FILE--
+<?php
+class FancyException extends Exception {}
+
+function test () : int throws FancyException {
+ throw new FancyException();
+}
+
+try {
+ test();
+} catch (FancyException $ex) {}
+?>
+OK
+--EXPECT--
+OK
@@ -0,0 +1,15 @@
+--TEST--
+Testing exception type executor with return and param pass
+--FILE--
+<?php
+function test (int $param) : int throws Exception {
+ return $param;
+}
+
+try {
+ test(10);
+} catch (FancyException $ex) {}
+?>
+OK
+--EXPECT--
+OK
View
@@ -70,7 +70,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_ast *child3
+ zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
) {
zend_ast_decl *ast;
@@ -87,6 +87,7 @@ ZEND_API zend_ast *zend_ast_create_decl(
ast->child[1] = child1;
ast->child[2] = child2;
ast->child[3] = child3;
+ ast->child[4] = child4;
return (zend_ast *) ast;
}
@@ -505,6 +506,7 @@ static void zend_ast_destroy_ex(zend_ast *ast, zend_bool 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);
+ zend_ast_destroy_ex(decl->child[4], free);
break;
}
default:
@@ -1071,6 +1073,10 @@ static void zend_ast_export_ex(smart_str *str, zend_ast *ast, int priority, int
smart_str_appends(str, ": ");
zend_ast_export_ns_name(str, decl->child[3], 0, indent);
}
+ if (decl->child[4]) {
+ smart_str_appends(str, "throws ");
+ zend_ast_export_ns_name(str, decl->child[4], 0, indent);
+ }
if (decl->child[2]) {
smart_str_appends(str, " {\n");
zend_ast_export_stmt(str, decl->child[2], indent + 1);
View
@@ -183,7 +183,7 @@ typedef struct _zend_ast_decl {
unsigned char *lex_pos;
zend_string *doc_comment;
zend_string *name;
- zend_ast *child[4];
+ zend_ast *child[5];
} zend_ast_decl;
typedef void (*zend_ast_process_t)(zend_ast *ast);
@@ -196,7 +196,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_ast *child3
+ zend_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3, zend_ast *child4
);
ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...);
View
@@ -4239,6 +4239,12 @@ void zend_compile_throw(zend_ast *ast) /* {{{ */
znode expr_node;
zend_compile_expr(&expr_node, expr_ast);
+ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_THROWS_TYPE) {
+ zend_op *opline = zend_emit_op(NULL,
+ ZEND_VERIFY_THROW_TYPE, &expr_node, NULL);
+ zend_alloc_cache_slot(opline->op2.constant);
+ }
+
zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL);
}
/* }}} */
@@ -5069,18 +5075,44 @@ static void zend_compile_typename(zend_ast *ast, zend_arg_info *arg_info, zend_b
}
/* }}} */
-void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
+void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_ast *throws_type_ast) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
zend_op_array *op_array = CG(active_op_array);
zend_arg_info *arg_infos;
-
+ uint32_t narg_infos = list->children;
+
if (return_type_ast) {
- zend_bool allow_null = 0;
+ narg_infos++;
+ }
+
+ if (throws_type_ast) {
+ narg_infos++;
+ }
+
+ if (!narg_infos) {
+ return;
+ }
+
+ arg_infos = safe_emalloc(sizeof(zend_arg_info), narg_infos, 0);
+
+ if (throws_type_ast) {
+ memset(arg_infos, 0, sizeof(zend_arg_info));
- /* Use op_array->arg_info[-1] for return type */
- arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0);
+ zend_compile_typename(throws_type_ast, arg_infos, 0);
+
+ if (!ZEND_TYPE_IS_CLASS(arg_infos->type)) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Throws type must be an object");
+ }
+
+ arg_infos++;
+ op_array->fn_flags |= ZEND_ACC_HAS_THROWS_TYPE;
+ }
+
+ if (return_type_ast) {
+ zend_bool allow_null = 0;
+
arg_infos->name = NULL;
arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0;
arg_infos->is_variadic = 0;
@@ -5098,12 +5130,7 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */
}
arg_infos++;
- op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE;
- } else {
- if (list->children == 0) {
- return;
- }
- arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children, 0);
+ op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE;
}
for (i = 0; i < list->children; ++i) {
@@ -5561,6 +5588,8 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
zend_ast *uses_ast = decl->child[1];
zend_ast *stmt_ast = decl->child[2];
zend_ast *return_type_ast = decl->child[3];
+ zend_ast *throws_type_ast = decl->child[4];
+
zend_bool is_method = decl->kind == ZEND_AST_METHOD;
zend_op_array *orig_op_array = CG(active_op_array);
@@ -5607,7 +5636,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
zend_stack_push(&CG(loop_var_stack), (void *) &dummy_var);
}
- zend_compile_params(params_ast, return_type_ast);
+ zend_compile_params(params_ast, return_type_ast, throws_type_ast);
if (CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) {
zend_mark_function_as_generator();
zend_emit_op(NULL, ZEND_GENERATOR_CREATE, NULL, NULL);
View
@@ -290,8 +290,15 @@ typedef struct _zend_oparray_context {
/* Function has a return type (or class has such non-private function) */
#define ZEND_ACC_HAS_RETURN_TYPE 0x40000000
+/* Function has a throws type */
+#define ZEND_ACC_HAS_THROWS_TYPE 0x80000000
+
/* op_array uses strict mode types */
-#define ZEND_ACC_STRICT_TYPES 0x80000000
+#define ZEND_ACC_STRICT_TYPES 0x10
+
+#define ZEND_THROWS_INFO(func) (func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) ? \
+ func->common.arg_info - 2 : \
+ func->common.arg_info - 1
char *zend_visibility_string(uint32_t fn_flags);
View
@@ -913,6 +913,21 @@ static ZEND_COLD void zend_verify_return_error(
fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind);
}
+static ZEND_COLD void zend_verify_throw_error(
+ const zend_function *zf, const zend_class_entry *ce, zval *value)
+{
+ const zend_arg_info *arg_info = ZEND_THROWS_INFO(zf);
+ const char *fname, *fsep, *fclass;
+ const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind;
+
+ zend_verify_type_error_common(
+ zf, arg_info, ce, value,
+ &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind);
+
+ zend_type_error("Exception thrown by %s%s%s() expected to %s%s%s, %s%s thrown",
+ fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind);
+}
+
#if ZEND_DEBUG
static ZEND_COLD void zend_verify_internal_return_error(
const zend_function *zf, const zend_class_entry *ce, zval *value)
@@ -977,6 +992,16 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval *
}
}
+static zend_always_inline void zend_verify_throw_type(zend_function *zf, zval *ex, void **cache_slot)
+{
+ zend_arg_info *ex_info = ZEND_THROWS_INFO(zf);
+ zend_class_entry *ce = NULL;
+
+ if (UNEXPECTED(!zend_check_type(ex_info->type, ex, &ce, cache_slot, NULL, NULL, 1))) {
+ zend_verify_throw_error(zf, ce, ex);
+ }
+}
+
static ZEND_COLD int zend_verify_missing_return_type(zend_function *zf, void **cache_slot)
{
zend_arg_info *ret_info = zf->common.arg_info - 1;
Oops, something went wrong.

0 comments on commit 46d8c66

Please sign in to comment.