]> granicus.if.org Git - php/commitdiff
Make throw statement an expression
authorIlija Tovilo <ilija.tovilo@me.com>
Wed, 18 Mar 2020 23:51:51 +0000 (00:51 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Thu, 23 Apr 2020 10:51:23 +0000 (12:51 +0200)
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.

UPGRADING
Zend/tests/throw/001.phpt [new file with mode: 0644]
Zend/tests/throw/002.phpt [new file with mode: 0644]
Zend/zend_compile.c
Zend/zend_language_parser.y
ext/tokenizer/tests/PhpToken_getAll.phpt
ext/tokenizer/tests/token_get_all_variation4.phpt
ext/tokenizer/tokenizer_data.c

index 07305869f776c486a08c84fff24c5fe0e0969093..c4eca6daaa0e04663117248c71a6a9dde37a17c4 100644 (file)
--- 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 (file)
index 0000000..072d9f4
--- /dev/null
@@ -0,0 +1,175 @@
+--TEST--
+throw expression
+--FILE--
+<?php
+
+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 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 (file)
index 0000000..8736c27
--- /dev/null
@@ -0,0 +1,127 @@
+--TEST--
+Test throw with various expressions
+--FILE--
+<?php
+
+class Foo {
+    public function createNotFoundException() {
+        return new Exception('Not found');
+    }
+
+    public function throwException() {
+        throw $this->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
index 2e30295980c4feacbde28d8a3dfe053069665e56..aa108c4d7f91c22564c0e7cca7da5823506eb18c 100644 (file)
@@ -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 */);
        }
index c1ced9a35aa21cd78fbe9f63d0e13e5587ec5d50..c75c1e69938656370521570ee6f82175b409a25d 100644 (file)
@@ -51,6 +51,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
 %destructor { zend_ast_destroy($$); } <ast>
 %destructor { if ($$) zend_string_release_ex($$, 0); } <str>
 
+%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; }
 ;
index 604a979023ed78902916477c702c4cd6b6e76a9c..baab0a5658e24595ba726550682aa4f532e2f456 100644 (file)
@@ -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"]=>
index 66e3cb413d2c4fbc1e173a39387252d93de16f27..be18ddf0b35c1dbce87fd849cc678fd5496a2994 100644 (file)
@@ -80,7 +80,7 @@ array(88) {
   [5]=>
   array(3) {
     [0]=>
-    int(286)
+    int(%d)
     [1]=>
     string(2) "=="
     [2]=>
index 3ddf89521a8cd1b623a41abeefe91c34893ec43c..9c7df932076ce848eb0c979d7ee644091e504897 100644 (file)
@@ -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";