Skip to content

Commit

Permalink
[RFC] implement throws type for functions and methods
Browse files Browse the repository at this point in the history
  • Loading branch information
krakjoe committed Feb 5, 2017
1 parent 0ecffc8 commit 46d8c66
Show file tree
Hide file tree
Showing 28 changed files with 1,374 additions and 927 deletions.
9 changes: 9 additions & 0 deletions Zend/tests/exception_types/001.phpt
Original file line number Diff line number Diff line change
@@ -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

10 changes: 10 additions & 0 deletions Zend/tests/exception_types/002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Testing basic exception type compiler pass
--FILE--
<?php
function test () throws Exception {}
?>
OK
--EXPECT--
OK

12 changes: 12 additions & 0 deletions Zend/tests/exception_types/003.phpt
Original file line number Diff line number Diff line change
@@ -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

12 changes: 12 additions & 0 deletions Zend/tests/exception_types/004.phpt
Original file line number Diff line number Diff line change
@@ -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

21 changes: 21 additions & 0 deletions Zend/tests/exception_types/005.phpt
Original file line number Diff line number Diff line change
@@ -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



21 changes: 21 additions & 0 deletions Zend/tests/exception_types/006.phpt
Original file line number Diff line number Diff line change
@@ -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


21 changes: 21 additions & 0 deletions Zend/tests/exception_types/007.phpt
Original file line number Diff line number Diff line change
@@ -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



20 changes: 20 additions & 0 deletions Zend/tests/exception_types/010.phpt
Original file line number Diff line number Diff line change
@@ -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

17 changes: 17 additions & 0 deletions Zend/tests/exception_types/011.phpt
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions Zend/tests/exception_types/012.phpt
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions Zend/tests/exception_types/013.phpt
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
}
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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, ...);
Expand Down
53 changes: 41 additions & 12 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
/* }}} */
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 8 additions & 1 deletion Zend/zend_compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
25 changes: 25 additions & 0 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 46d8c66

Please sign in to comment.