From: Ilija Tovilo Date: Wed, 18 Mar 2020 23:51:51 +0000 (+0100) Subject: Make throw statement an expression X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=0810fcd0d0eb907b79b9619f73dc7d13aed5c611;p=php Make throw statement an expression RFC: https://wiki.php.net/rfc/throw_expression This has an open issue with temporaries that are live at the time of the throw being leaked. Landing this now for easier testing and will revert if we cannot resolve the issue. Closes GH-5279. --- diff --git a/UPGRADING b/UPGRADING index 07305869f7..c4eca6daaa 100644 --- a/UPGRADING +++ b/UPGRADING @@ -459,6 +459,9 @@ PHP 8.0 UPGRADE NOTES defines a __toString() method. RFC: https://wiki.php.net/rfc/stringable . Traits can now define abstract private methods. + RFC: https://wiki.php.net/rfc/abstract_trait_method_validation + . `throw` can now be used as an expression. + RFC: https://wiki.php.net/rfc/throw_expression - Date: . Added DateTime::createFromInterface() and diff --git a/Zend/tests/throw/001.phpt b/Zend/tests/throw/001.phpt new file mode 100644 index 0000000000..072d9f45b5 --- /dev/null +++ b/Zend/tests/throw/001.phpt @@ -0,0 +1,175 @@ +--TEST-- +throw expression +--FILE-- +getMessage()); +} + +try { + $result = false && throw new Exception("false && throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = true and throw new Exception("true and throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = false and throw new Exception("false and throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = true || throw new Exception("true || throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = false || throw new Exception("false || throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = true or throw new Exception("true or throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = false or throw new Exception("false or throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = null ?? throw new Exception("null ?? throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = "foo" ?? throw new Exception('"foo" ?? throw'); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = null ?: throw new Exception("null ?: throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = "foo" ?: throw new Exception('"foo" ?: throw'); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $callable = fn() => throw new Exception("fn() => throw"); + var_dump("not yet"); + $callable(); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +$result = "bar"; +try { + $result = throw new Exception(); +} catch (Exception $e) {} +var_dump($result); + +try { + var_dump( + throw new Exception("exception 1"), + throw new Exception("exception 2") + ); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = true ? true : throw new Exception("true ? true : throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + $result = false ? true : throw new Exception("false ? true : throw"); + var_dump($result); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +try { + throw new Exception() + 1; +} catch (Throwable $e) { + var_dump($e->getMessage()); +} + +try { + throw $exception = new Exception('throw $exception = new Exception();'); +} catch (Exception $e) {} +var_dump($exception->getMessage()); + +try { + $exception = null; + throw $exception ??= new Exception('throw $exception ??= new Exception();'); +} catch (Exception $e) {} +var_dump($exception->getMessage()); + +try { + throw null ?? new Exception('throw null ?? new Exception();'); +} catch (Exception $e) { + var_dump($e->getMessage()); +} + +?> +--EXPECTF-- +string(13) "true && throw" +bool(false) +string(14) "true and throw" +bool(false) +bool(true) +string(14) "false || throw" +bool(true) +string(14) "false or throw" +string(13) "null ?? throw" +string(3) "foo" +string(13) "null ?: throw" +string(3) "foo" +string(7) "not yet" +string(13) "fn() => throw" +string(3) "bar" +string(11) "exception 1" +bool(true) +string(20) "false ? true : throw" + +Notice: Object of class Exception could not be converted to number in %s on line %d +string(22) "Can only throw objects" +string(35) "throw $exception = new Exception();" +string(37) "throw $exception ??= new Exception();" +string(30) "throw null ?? new Exception();" diff --git a/Zend/tests/throw/002.phpt b/Zend/tests/throw/002.phpt new file mode 100644 index 0000000000..8736c27fc3 --- /dev/null +++ b/Zend/tests/throw/002.phpt @@ -0,0 +1,127 @@ +--TEST-- +Test throw with various expressions +--FILE-- +createNotFoundException(); + } + + public static function staticCreateNotFoundException() { + return new Exception('Static not found'); + } + + public static function staticThrowException() { + throw static::staticCreateNotFoundException(); + } +} + +try { + (new Foo())->throwException(); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + Foo::staticThrowException(); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + throw true ? new Exception('Ternary true 1') : new Exception('Ternary true 2'); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + throw false ? new Exception('Ternary false 1') : new Exception('Ternary false 2'); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $exception1 = new Exception('Coalesce non-null 1'); + $exception2 = new Exception('Coalesce non-null 2'); + throw $exception1 ?? $exception2; +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $exception1 = null; + $exception2 = new Exception('Coalesce null 2'); + throw $exception1 ?? $exception2; +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + throw $exception = new Exception('Assignment'); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $exception = null; + throw $exception ??= new Exception('Coalesce assignment null'); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $exception = new Exception('Coalesce assignment non-null 1'); + throw $exception ??= new Exception('Coalesce assignment non-null 2'); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +$andConditionalTest = function ($condition1, $condition2) { + throw $condition1 && $condition2 + ? new Exception('And in conditional 1') + : new Exception('And in conditional 2'); +}; + +try { + $andConditionalTest(false, false); +} catch(Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $andConditionalTest(false, true); +} catch (Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $andConditionalTest(true, false); +} catch (Exception $e) { + echo $e->getMessage() . "\n"; +} + +try { + $andConditionalTest(true, true); +} catch (Exception $e) { + echo $e->getMessage() . "\n"; +} + +--EXPECT-- +Not found +Static not found +Ternary true 1 +Ternary false 2 +Coalesce non-null 1 +Coalesce null 2 +Assignment +Coalesce assignment null +Coalesce assignment non-null 1 +And in conditional 2 +And in conditional 2 +And in conditional 2 +And in conditional 1 diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 2e30295980..aa108c4d7f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -4557,7 +4557,7 @@ void zend_compile_echo(zend_ast *ast) /* {{{ */ } /* }}} */ -void zend_compile_throw(zend_ast *ast) /* {{{ */ +void zend_compile_throw(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *expr_ast = ast->child[0]; @@ -4565,6 +4565,9 @@ void zend_compile_throw(zend_ast *ast) /* {{{ */ zend_compile_expr(&expr_node, expr_ast); zend_emit_op(NULL, ZEND_THROW, &expr_node, NULL); + + result->op_type = IS_CONST; + ZVAL_BOOL(&result->u.constant, 1); } /* }}} */ @@ -8741,9 +8744,6 @@ void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_ECHO: zend_compile_echo(ast); break; - case ZEND_AST_THROW: - zend_compile_throw(ast); - break; case ZEND_AST_BREAK: case ZEND_AST_CONTINUE: zend_compile_break_continue(ast); @@ -8953,6 +8953,9 @@ void zend_compile_expr(znode *result, zend_ast *ast) /* {{{ */ case ZEND_AST_ARROW_FUNC: zend_compile_func_decl(result, ast, 0); return; + case ZEND_AST_THROW: + zend_compile_throw(result, ast); + break; default: ZEND_ASSERT(0 /* not supported */); } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index c1ced9a35a..c75c1e6993 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -51,6 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %destructor { zend_ast_destroy($$); } %destructor { if ($$) zend_string_release_ex($$, 0); } +%precedence T_THROW %precedence PREC_ARROW_FUNCTION %precedence T_INCLUDE T_INCLUDE_ONCE T_REQUIRE T_REQUIRE_ONCE %left T_LOGICAL_OR @@ -457,7 +458,6 @@ statement: | ';' /* empty statement */ { $$ = NULL; } | T_TRY '{' inner_statement_list '}' catch_list finally_statement { $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); } - | T_THROW expr ';' { $$ = zend_ast_create(ZEND_AST_THROW, $2); } | T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); } | T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); } ; @@ -1019,6 +1019,7 @@ expr: | T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } | T_YIELD_FROM expr { $$ = zend_ast_create(ZEND_AST_YIELD_FROM, $2); CG(extra_fn_flags) |= ZEND_ACC_GENERATOR; } + | T_THROW expr { $$ = zend_ast_create(ZEND_AST_THROW, $2); } | inline_function { $$ = $1; } | T_STATIC inline_function { $$ = $2; ((zend_ast_decl *) $$)->flags |= ZEND_ACC_STATIC; } ; diff --git a/ext/tokenizer/tests/PhpToken_getAll.phpt b/ext/tokenizer/tests/PhpToken_getAll.phpt index 604a979023..baab0a5658 100644 --- a/ext/tokenizer/tests/PhpToken_getAll.phpt +++ b/ext/tokenizer/tests/PhpToken_getAll.phpt @@ -32,7 +32,7 @@ array(15) { [1]=> object(PhpToken)#2 (4) { ["id"]=> - int(342) + int(343) ["text"]=> string(8) "function" ["line"]=> @@ -54,7 +54,7 @@ array(15) { [3]=> object(PhpToken)#4 (4) { ["id"]=> - int(310) + int(311) ["text"]=> string(3) "foo" ["line"]=> @@ -121,7 +121,7 @@ array(15) { [9]=> object(PhpToken)#10 (4) { ["id"]=> - int(324) + int(325) ["text"]=> string(4) "echo" ["line"]=> @@ -143,7 +143,7 @@ array(15) { [11]=> object(PhpToken)#12 (4) { ["id"]=> - int(314) + int(315) ["text"]=> string(5) ""bar"" ["line"]=> @@ -202,7 +202,7 @@ array(15) { [1]=> object(PhpToken)#14 (4) { ["id"]=> - int(342) + int(343) ["text"]=> string(8) "function" ["line"]=> @@ -224,7 +224,7 @@ array(15) { [3]=> object(PhpToken)#12 (4) { ["id"]=> - int(310) + int(311) ["text"]=> string(3) "foo" ["line"]=> @@ -291,7 +291,7 @@ array(15) { [9]=> object(PhpToken)#6 (4) { ["id"]=> - int(324) + int(325) ["text"]=> string(4) "echo" ["line"]=> @@ -313,7 +313,7 @@ array(15) { [11]=> object(PhpToken)#4 (4) { ["id"]=> - int(314) + int(315) ["text"]=> string(5) ""bar"" ["line"]=> diff --git a/ext/tokenizer/tests/token_get_all_variation4.phpt b/ext/tokenizer/tests/token_get_all_variation4.phpt index 66e3cb413d..be18ddf0b3 100644 --- a/ext/tokenizer/tests/token_get_all_variation4.phpt +++ b/ext/tokenizer/tests/token_get_all_variation4.phpt @@ -80,7 +80,7 @@ array(88) { [5]=> array(3) { [0]=> - int(286) + int(%d) [1]=> string(2) "==" [2]=> diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 3ddf89521a..9c7df93207 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -25,6 +25,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { + REGISTER_LONG_CONSTANT("T_THROW", T_THROW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INCLUDE", T_INCLUDE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INCLUDE_ONCE", T_INCLUDE_ONCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_REQUIRE", T_REQUIRE, CONST_CS | CONST_PERSISTENT); @@ -114,7 +115,6 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_TRY", T_TRY, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_CATCH", T_CATCH, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_FINALLY", T_FINALLY, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_THROW", T_THROW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_USE", T_USE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_INSTEADOF", T_INSTEADOF, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_GLOBAL", T_GLOBAL, CONST_CS | CONST_PERSISTENT); @@ -168,6 +168,7 @@ char *get_token_type_name(int token_type) { switch (token_type) { + case T_THROW: return "T_THROW"; case T_INCLUDE: return "T_INCLUDE"; case T_INCLUDE_ONCE: return "T_INCLUDE_ONCE"; case T_REQUIRE: return "T_REQUIRE"; @@ -257,7 +258,6 @@ char *get_token_type_name(int token_type) case T_TRY: return "T_TRY"; case T_CATCH: return "T_CATCH"; case T_FINALLY: return "T_FINALLY"; - case T_THROW: return "T_THROW"; case T_USE: return "T_USE"; case T_INSTEADOF: return "T_INSTEADOF"; case T_GLOBAL: return "T_GLOBAL";