Permalink
Browse files

[RFC] implement throws type for functions and methods

  • Loading branch information...
krakjoe committed Feb 5, 2017
1 parent 0ecffc8 commit 46d8c666663d1ed0720bd14d9602a44b886f88dc
@@ -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.