summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--Zend/tests/return_types/001.phpt12
-rw-r--r--Zend/tests/return_types/002.phpt13
-rw-r--r--Zend/tests/return_types/003.phpt12
-rw-r--r--Zend/tests/return_types/004.phpt13
-rw-r--r--Zend/tests/return_types/005.phpt18
-rw-r--r--Zend/tests/return_types/006.phpt21
-rw-r--r--Zend/tests/return_types/007.phpt19
-rw-r--r--Zend/tests/return_types/008.phpt21
-rw-r--r--Zend/tests/return_types/009.phpt19
-rw-r--r--Zend/tests/return_types/010.phpt14
-rw-r--r--Zend/tests/return_types/011.phpt14
-rw-r--r--Zend/tests/return_types/012.phpt28
-rw-r--r--Zend/tests/return_types/013.phpt19
-rw-r--r--Zend/tests/return_types/014.phpt12
-rw-r--r--Zend/tests/return_types/015.phpt24
-rw-r--r--Zend/tests/return_types/016.phpt20
-rw-r--r--Zend/tests/return_types/017.phpt24
-rw-r--r--Zend/tests/return_types/018.phpt12
-rw-r--r--Zend/tests/return_types/019.phpt12
-rw-r--r--Zend/tests/return_types/020.phpt17
-rw-r--r--Zend/tests/return_types/021.phpt21
-rw-r--r--Zend/tests/return_types/022.phpt17
-rw-r--r--Zend/tests/return_types/023.phpt12
-rw-r--r--Zend/tests/return_types/024.phpt10
-rw-r--r--Zend/tests/return_types/025.phpt10
-rw-r--r--Zend/tests/return_types/classes.php.inc5
-rw-r--r--Zend/tests/return_types/generators001.phpt30
-rw-r--r--Zend/tests/return_types/generators002.phpt11
-rw-r--r--Zend/tests/return_types/generators003.phpt22
-rw-r--r--Zend/tests/return_types/generators004.phpt17
-rw-r--r--Zend/tests/return_types/generators005.phpt23
-rw-r--r--Zend/tests/return_types/inheritance001.phpt16
-rw-r--r--Zend/tests/return_types/inheritance002.phpt16
-rw-r--r--Zend/tests/return_types/inheritance003.phpt16
-rw-r--r--Zend/tests/return_types/inheritance004.phpt25
-rw-r--r--Zend/tests/return_types/inheritance005.phpt19
-rw-r--r--Zend/tests/return_types/inheritance006.phpt24
-rw-r--r--Zend/tests/return_types/inheritance007.phpt22
-rw-r--r--Zend/tests/return_types/inheritance008.phpt30
-rw-r--r--Zend/tests/return_types/inheritance009.phpt42
-rw-r--r--Zend/tests/return_types/reflection001.phpt47
-rw-r--r--Zend/tests/return_types/rfc001.phpt14
-rw-r--r--Zend/tests/return_types/rfc002.phpt13
-rw-r--r--Zend/tests/return_types/rfc003.phpt13
-rw-r--r--Zend/tests/return_types/rfc004.phpt21
-rw-r--r--Zend/zend_API.c3
-rw-r--r--Zend/zend_ast.c4
-rw-r--r--Zend/zend_ast.h4
-rw-r--r--Zend/zend_closures.c4
-rw-r--r--Zend/zend_compile.c103
-rw-r--r--Zend/zend_compile.h18
-rw-r--r--Zend/zend_execute.c81
-rw-r--r--Zend/zend_execute.h1
-rw-r--r--Zend/zend_inheritance.c240
-rw-r--r--Zend/zend_language_parser.y54
-rw-r--r--Zend/zend_opcode.c25
-rw-r--r--Zend/zend_vm_def.h18
-rw-r--r--Zend/zend_vm_execute.h100
-rw-r--r--Zend/zend_vm_opcodes.c2
-rw-r--r--Zend/zend_vm_opcodes.h1
-rw-r--r--ext/interbase/tests/ibase_trans_001.phpt4
-rw-r--r--ext/opcache/zend_persist.c29
-rw-r--r--ext/opcache/zend_persist_calc.c18
-rw-r--r--ext/openssl/openssl.c10
-rw-r--r--ext/reflection/php_reflection.c18
-rw-r--r--ext/session/session.c21
-rw-r--r--ext/session/tests/session_regenerate_id_fastshutdown.phpt14
68 files changed, 1431 insertions, 183 deletions
diff --git a/NEWS b/NEWS
index 9d669be89a..417c2ac3ee 100644
--- a/NEWS
+++ b/NEWS
@@ -8,6 +8,8 @@
. Update the MIME type list from the one shipped by Apache HTTPD. (Adam)
- Core:
+ . Fixed #68868 (Segfault in clean_non_persistent_constants() in SugarCRM
+ 6.5.20). (Laruence)
. Fixed bug #68104 (Segfault while pre-evaluating a disabled function).
(Laruence)
. Fixed bug #68252 (segfault in Zend/zend_hash.c in function
diff --git a/Zend/tests/return_types/001.phpt b/Zend/tests/return_types/001.phpt
new file mode 100644
index 0000000000..a751bd3f2f
--- /dev/null
+++ b/Zend/tests/return_types/001.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Returned nothing, expected array
+
+--FILE--
+<?php
+function test1() : array {
+}
+
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, none returned in %s on line %d
diff --git a/Zend/tests/return_types/002.phpt b/Zend/tests/return_types/002.phpt
new file mode 100644
index 0000000000..d8b77925e3
--- /dev/null
+++ b/Zend/tests/return_types/002.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Returned null, expected array
+
+--FILE--
+<?php
+function test1() : array {
+ return null;
+}
+
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, null returned in %s on line %d
diff --git a/Zend/tests/return_types/003.phpt b/Zend/tests/return_types/003.phpt
new file mode 100644
index 0000000000..0ca023b308
--- /dev/null
+++ b/Zend/tests/return_types/003.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Returned 1, expected array
+
+--FILE--
+<?php
+function test1() : array {
+ return 1;
+}
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, integer returned in %s on line %d
diff --git a/Zend/tests/return_types/004.phpt b/Zend/tests/return_types/004.phpt
new file mode 100644
index 0000000000..44712f5dba
--- /dev/null
+++ b/Zend/tests/return_types/004.phpt
@@ -0,0 +1,13 @@
+--TEST--
+Returned string, expected array
+
+--FILE--
+<?php
+function test1() : array {
+ return "hello";
+}
+
+test1();
+
+--EXPECTF--
+Catchable fatal error: Return value of test1() must be of the type array, string returned in %s on line %d
diff --git a/Zend/tests/return_types/005.phpt b/Zend/tests/return_types/005.phpt
new file mode 100644
index 0000000000..5b979a3b67
--- /dev/null
+++ b/Zend/tests/return_types/005.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Return value fails inheritance check in method
+
+--FILE--
+<?php
+class foo {}
+
+class qux {
+ public function foo() : foo {
+ return $this;
+ }
+}
+
+$qux = new qux();
+$qux->foo();
+
+--EXPECTF--
+Catchable fatal error: Return value of qux::foo() must be an instance of foo, instance of qux returned in %s on line %d
diff --git a/Zend/tests/return_types/006.phpt b/Zend/tests/return_types/006.phpt
new file mode 100644
index 0000000000..359b25a28d
--- /dev/null
+++ b/Zend/tests/return_types/006.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Return type allowed in child when parent does not have return type
+
+--FILE--
+<?php
+class Comment {}
+
+class CommentsIterator extends ArrayIterator implements Iterator {
+ function current() : Comment {
+ return parent::current();
+ }
+}
+
+$comments = new CommentsIterator([new Comment]);
+foreach ($comments as $comment) {
+ var_dump($comment);
+}
+
+--EXPECTF--
+object(Comment)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/007.phpt b/Zend/tests/return_types/007.phpt
new file mode 100644
index 0000000000..0bbfb9fbba
--- /dev/null
+++ b/Zend/tests/return_types/007.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Return value is subclass of return type
+
+--FILE--
+<?php
+class foo {}
+
+class qux extends foo {
+ public function foo() : foo {
+ return $this;
+ }
+}
+
+$qux = new qux();
+var_dump($qux->foo());
+
+--EXPECTF--
+object(qux)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/008.phpt b/Zend/tests/return_types/008.phpt
new file mode 100644
index 0000000000..68262af9da
--- /dev/null
+++ b/Zend/tests/return_types/008.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Return type covariance in interface implementation
+
+--FILE--
+<?php
+interface foo {
+ public function bar() : foo;
+}
+
+
+class qux implements foo {
+ public function bar() : qux {
+ return $this;
+ }
+}
+
+$qux = new qux();
+var_dump($qux->bar());
+
+--EXPECTF--
+Fatal error: Declaration of qux::bar() must be compatible with foo::bar(): foo in %s008.php on line 7
diff --git a/Zend/tests/return_types/009.phpt b/Zend/tests/return_types/009.phpt
new file mode 100644
index 0000000000..0c2b4376a1
--- /dev/null
+++ b/Zend/tests/return_types/009.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Return type covariance error
+
+--FILE--
+<?php
+interface foo {
+ public function bar() : foo;
+}
+
+interface biz {}
+
+class qux implements foo {
+ public function bar() : biz {
+ return $this;
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of qux::bar() must be compatible with foo::bar(): foo in %s on line %d
diff --git a/Zend/tests/return_types/010.phpt b/Zend/tests/return_types/010.phpt
new file mode 100644
index 0000000000..75deb3a32b
--- /dev/null
+++ b/Zend/tests/return_types/010.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Returned null, expected array reference
+
+--FILE--
+<?php
+function &foo(array &$in) : array {
+ return null;
+}
+
+$array = [1, 2, 3];
+var_dump(foo($array));
+
+--EXPECTF--
+Catchable fatal error: Return value of foo() must be of the type array, null returned in %s on line %d
diff --git a/Zend/tests/return_types/011.phpt b/Zend/tests/return_types/011.phpt
new file mode 100644
index 0000000000..f3b000a706
--- /dev/null
+++ b/Zend/tests/return_types/011.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Function returned callable, expected callable
+
+--FILE--
+<?php
+function foo() : callable {
+ return function() {};
+}
+
+var_dump(foo());
+
+--EXPECTF--
+object(Closure)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/012.phpt b/Zend/tests/return_types/012.phpt
new file mode 100644
index 0000000000..39fa8e0ec6
--- /dev/null
+++ b/Zend/tests/return_types/012.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Method returned callable, expected callable
+
+--FILE--
+<?php
+class foo {
+ public function bar() : callable {
+ $test = "one";
+ return function() use($test) : array {
+ return array($test);
+ };
+ }
+}
+
+$baz = new foo();
+var_dump($baz->bar());
+
+--EXPECT--
+object(Closure)#2 (2) {
+ ["static"]=>
+ array(1) {
+ ["test"]=>
+ string(3) "one"
+ }
+ ["this"]=>
+ object(foo)#1 (0) {
+ }
+}
diff --git a/Zend/tests/return_types/013.phpt b/Zend/tests/return_types/013.phpt
new file mode 100644
index 0000000000..ca03d2f983
--- /dev/null
+++ b/Zend/tests/return_types/013.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Closure inside method returned null, expected array
+
+--FILE--
+<?php
+class foo {
+ public function bar() : callable {
+ $test = "one";
+ return function() use($test) : array {
+ return null;
+ };
+ }
+}
+
+$baz = new foo();
+var_dump($func=$baz->bar(), $func());
+
+--EXPECTF--
+Catchable fatal error: Return value of foo::{closure}() must be of the type array, null returned in %s on line %d
diff --git a/Zend/tests/return_types/014.phpt b/Zend/tests/return_types/014.phpt
new file mode 100644
index 0000000000..abf8b1210b
--- /dev/null
+++ b/Zend/tests/return_types/014.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Constructors cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function __construct() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: Constructor %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/015.phpt b/Zend/tests/return_types/015.phpt
new file mode 100644
index 0000000000..1c2cd564ff
--- /dev/null
+++ b/Zend/tests/return_types/015.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Return types allowed in namespace
+
+--FILE--
+<?php
+
+namespace Collections;
+
+interface Collection {
+ function values(): Collection;
+}
+
+class Vector implements Collection {
+ function values(): Collection {
+ return $this;
+ }
+}
+
+$v = new Vector;
+var_dump($v->values());
+
+--EXPECTF--
+object(Collections\Vector)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/016.phpt b/Zend/tests/return_types/016.phpt
new file mode 100644
index 0000000000..f3636751c1
--- /dev/null
+++ b/Zend/tests/return_types/016.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Fully qualified classes are allowed in return types
+
+--FILE--
+<?php
+
+namespace Collections;
+
+class Foo {
+ function foo(\Iterator $i): \Iterator {
+ return $i;
+ }
+}
+
+$foo = new Foo;
+var_dump($foo->foo(new \EmptyIterator()));
+
+--EXPECTF--
+object(EmptyIterator)#%d (0) {
+}
diff --git a/Zend/tests/return_types/017.phpt b/Zend/tests/return_types/017.phpt
new file mode 100644
index 0000000000..d44b26fc04
--- /dev/null
+++ b/Zend/tests/return_types/017.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Fully qualified classes in trait return types
+
+--FILE--
+<?php
+
+namespace FooSpace;
+
+trait Fooable {
+ function foo(): \Iterator {
+ return new \EmptyIterator();
+ }
+}
+
+class Foo {
+ use Fooable;
+}
+
+$foo = new Foo;
+var_dump($foo->foo([]));
+
+--EXPECTF--
+object(EmptyIterator)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/018.phpt b/Zend/tests/return_types/018.phpt
new file mode 100644
index 0000000000..6d6e0c7e73
--- /dev/null
+++ b/Zend/tests/return_types/018.phpt
@@ -0,0 +1,12 @@
+--TEST--
+Destructors cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function __destruct() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: Destructor %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/019.phpt b/Zend/tests/return_types/019.phpt
new file mode 100644
index 0000000000..cebf483285
--- /dev/null
+++ b/Zend/tests/return_types/019.phpt
@@ -0,0 +1,12 @@
+--TEST--
+__clone cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function __clone() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/020.phpt b/Zend/tests/return_types/020.phpt
new file mode 100644
index 0000000000..d43496b441
--- /dev/null
+++ b/Zend/tests/return_types/020.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Exception thrown from function with return type
+
+--FILE--
+<?php
+function test() : array {
+ throw new Exception();
+}
+
+test();
+
+--EXPECTF--
+Fatal error: Uncaught exception 'Exception' in %s:%d
+Stack trace:
+#0 %s(%d): test()
+#1 {main}
+ thrown in %s on line %d
diff --git a/Zend/tests/return_types/021.phpt b/Zend/tests/return_types/021.phpt
new file mode 100644
index 0000000000..051f7dce69
--- /dev/null
+++ b/Zend/tests/return_types/021.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Return type allows self
+
+--FILE--
+<?php
+class Foo {
+ public static function getInstance() : self {
+ return new static();
+ }
+}
+
+class Bar extends Foo {}
+
+var_dump(Foo::getInstance());
+var_dump(Bar::getInstance());
+
+--EXPECTF--
+object(Foo)#%d (%d) {
+}
+object(Bar)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/022.phpt b/Zend/tests/return_types/022.phpt
new file mode 100644
index 0000000000..47cbb791df
--- /dev/null
+++ b/Zend/tests/return_types/022.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Hint on closure with lexical vars
+
+--FILE--
+<?php
+$foo = "bar";
+$test = function() use($foo) : Closure {
+ return function() use ($foo) {
+ return $foo;
+ };
+};
+
+$callable = $test();
+var_dump($callable());
+
+--EXPECTF--
+string(3) "bar"
diff --git a/Zend/tests/return_types/023.phpt b/Zend/tests/return_types/023.phpt
new file mode 100644
index 0000000000..61a9e1c4e0
--- /dev/null
+++ b/Zend/tests/return_types/023.phpt
@@ -0,0 +1,12 @@
+--TEST--
+PHP 4 Constructors cannot declare a return type
+
+--FILE--
+<?php
+
+class Foo {
+ function foo() : Foo {}
+}
+
+--EXPECTF--
+Fatal error: Constructor %s::%s() cannot declare a return type in %s on line %s
diff --git a/Zend/tests/return_types/024.phpt b/Zend/tests/return_types/024.phpt
new file mode 100644
index 0000000000..9f2691f214
--- /dev/null
+++ b/Zend/tests/return_types/024.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Return type of self is not allowed in function
+
+--FILE--
+<?php
+
+function test(): self {}
+
+--EXPECTF--
+Fatal error: Cannot declare a return type of self outside of a class scope in %s on line 3
diff --git a/Zend/tests/return_types/025.phpt b/Zend/tests/return_types/025.phpt
new file mode 100644
index 0000000000..650fb08574
--- /dev/null
+++ b/Zend/tests/return_types/025.phpt
@@ -0,0 +1,10 @@
+--TEST--
+Return type of self is not allowed in closure
+
+--FILE--
+<?php
+
+$c = function(): self {};
+
+--EXPECTF--
+Fatal error: Cannot declare a return type of self outside of a class scope in %s on line 3
diff --git a/Zend/tests/return_types/classes.php.inc b/Zend/tests/return_types/classes.php.inc
new file mode 100644
index 0000000000..e4b691e353
--- /dev/null
+++ b/Zend/tests/return_types/classes.php.inc
@@ -0,0 +1,5 @@
+<?php
+
+class A {}
+class B extends A {}
+
diff --git a/Zend/tests/return_types/generators001.phpt b/Zend/tests/return_types/generators001.phpt
new file mode 100644
index 0000000000..f2ac88a7ad
--- /dev/null
+++ b/Zend/tests/return_types/generators001.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Valid generator return types
+
+--FILE--
+<?php
+function test1() : Generator {
+ yield 1;
+}
+
+function test2() : Iterator {
+ yield 2;
+}
+
+function test3() : Traversable {
+ yield 3;
+}
+
+var_dump(
+ test1(),
+ test2(),
+ test3()
+);
+
+--EXPECTF--
+object(Generator)#%d (%d) {
+}
+object(Generator)#%d (%d) {
+}
+object(Generator)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt
new file mode 100644
index 0000000000..f7dbfda69b
--- /dev/null
+++ b/Zend/tests/return_types/generators002.phpt
@@ -0,0 +1,11 @@
+--TEST--
+Generator return type must be Generator, Iterator or Traversable
+
+--FILE--
+<?php
+function test1() : StdClass {
+ yield 1;
+}
+
+--EXPECTF--
+Fatal error: Generators may only declare a return type of Generator, Iterator or Traversable, StdClass is not permitted in %s on line %d
diff --git a/Zend/tests/return_types/generators003.phpt b/Zend/tests/return_types/generators003.phpt
new file mode 100644
index 0000000000..b90f67a268
--- /dev/null
+++ b/Zend/tests/return_types/generators003.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Return type covariance works with generators
+
+--FILE--
+<?php
+interface Collection extends IteratorAggregate {
+ function getIterator(): Iterator;
+}
+
+class SomeCollection implements Collection {
+ function getIterator(): Generator {
+ foreach ($this->data as $key => $value) {
+ yield $key => $value;
+ }
+ }
+}
+
+$some = new SomeCollection();
+var_dump($some->getIterator());
+
+--EXPECTF--
+Fatal error: Declaration of SomeCollection::getIterator() must be compatible with Collection::getIterator(): Iterator in %sgenerators003.php on line 6
diff --git a/Zend/tests/return_types/generators004.phpt b/Zend/tests/return_types/generators004.phpt
new file mode 100644
index 0000000000..74aa801638
--- /dev/null
+++ b/Zend/tests/return_types/generators004.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Generator with return type does not fail with empty return
+
+--FILE--
+<?php
+
+$a = function(): \Iterator {
+ yield 1;
+ return;
+};
+
+foreach($a() as $value) {
+ echo $value;
+}
+
+--EXPECT--
+1
diff --git a/Zend/tests/return_types/generators005.phpt b/Zend/tests/return_types/generators005.phpt
new file mode 100644
index 0000000000..21e6c4bef1
--- /dev/null
+++ b/Zend/tests/return_types/generators005.phpt
@@ -0,0 +1,23 @@
+--TEST--
+Return type covariance works with generators
+
+--FILE--
+<?php
+interface Collection extends IteratorAggregate {
+ function getIterator(): Iterator;
+}
+
+class SomeCollection implements Collection {
+ function getIterator(): Iterator {
+ foreach ($this->data as $key => $value) {
+ yield $key => $value;
+ }
+ }
+}
+
+$some = new SomeCollection();
+var_dump($some->getIterator());
+
+--EXPECTF--
+object(Generator)#%d (%d) {
+}
diff --git a/Zend/tests/return_types/inheritance001.phpt b/Zend/tests/return_types/inheritance001.phpt
new file mode 100644
index 0000000000..ce33d97995
--- /dev/null
+++ b/Zend/tests/return_types/inheritance001.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Return type covariance; extends class
+
+--FILE--
+<?php
+
+class A {
+ function foo(): A {}
+}
+
+class B extends A {
+ function foo(): StdClass {}
+}
+
+--EXPECTF--
+Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d
diff --git a/Zend/tests/return_types/inheritance002.phpt b/Zend/tests/return_types/inheritance002.phpt
new file mode 100644
index 0000000000..f63a8ebe89
--- /dev/null
+++ b/Zend/tests/return_types/inheritance002.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Return type covariance; extends abstract class
+
+--FILE--
+<?php
+
+abstract class A {
+ abstract function foo(): A;
+}
+
+class B extends A {
+ function foo(): StdClass {}
+}
+
+--EXPECTF--
+Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d
diff --git a/Zend/tests/return_types/inheritance003.phpt b/Zend/tests/return_types/inheritance003.phpt
new file mode 100644
index 0000000000..e627363426
--- /dev/null
+++ b/Zend/tests/return_types/inheritance003.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Return type mismatch; implements interface
+
+--FILE--
+<?php
+
+interface A {
+ function foo(): A;
+}
+
+class B implements A {
+ function foo(): StdClass {}
+}
+
+--EXPECTF--
+Fatal error: Declaration of B::foo() must be compatible with A::foo(): A in %s on line %d
diff --git a/Zend/tests/return_types/inheritance004.phpt b/Zend/tests/return_types/inheritance004.phpt
new file mode 100644
index 0000000000..66d35564f7
--- /dev/null
+++ b/Zend/tests/return_types/inheritance004.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Internal covariant return type of self
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : self {
+ return new Foo;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : parent {
+ return new Bar;
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+object(Bar)#%d (0) {
+}
+object(Foo)#%d (0) {
+}
diff --git a/Zend/tests/return_types/inheritance005.phpt b/Zend/tests/return_types/inheritance005.phpt
new file mode 100644
index 0000000000..6ab52363c3
--- /dev/null
+++ b/Zend/tests/return_types/inheritance005.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Internal covariant return type of self
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : self {
+ return new Foo;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : self {
+ return new Bar;
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of Bar::test() must be compatible with Foo::test(): Foo in %sinheritance005.php on line 12
diff --git a/Zend/tests/return_types/inheritance006.phpt b/Zend/tests/return_types/inheritance006.phpt
new file mode 100644
index 0000000000..521c983040
--- /dev/null
+++ b/Zend/tests/return_types/inheritance006.phpt
@@ -0,0 +1,24 @@
+--TEST--
+External covariant return type of self
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+require __DIR__ . "/classes.php.inc";
+
+class Foo {
+ public static function test() : A {
+ return new A;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : B {
+ return new B;
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of Bar::test() must be compatible with Foo::test(): A in %sinheritance006.php on line 14
diff --git a/Zend/tests/return_types/inheritance007.phpt b/Zend/tests/return_types/inheritance007.phpt
new file mode 100644
index 0000000000..8de277817e
--- /dev/null
+++ b/Zend/tests/return_types/inheritance007.phpt
@@ -0,0 +1,22 @@
+--TEST--
+Inheritance Hinting Compile Checking Failure Internal Classes
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : Traversable {
+ return new ArrayIterator([1, 2]);
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : ArrayObject {
+ return new ArrayObject([1, 2]);
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of Bar::test() must be compatible with Foo::test(): Traversable in %sinheritance007.php on line 12
diff --git a/Zend/tests/return_types/inheritance008.phpt b/Zend/tests/return_types/inheritance008.phpt
new file mode 100644
index 0000000000..242c50dcf0
--- /dev/null
+++ b/Zend/tests/return_types/inheritance008.phpt
@@ -0,0 +1,30 @@
+--TEST--
+External covariant return type of self
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+require __DIR__ . "/classes.php.inc";
+
+class Foo {
+ public static function test() : A {
+ return new A;
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : A {
+ return new B;
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+object(B)#%d (0) {
+}
+object(A)#%d (0) {
+}
diff --git a/Zend/tests/return_types/inheritance009.phpt b/Zend/tests/return_types/inheritance009.phpt
new file mode 100644
index 0000000000..382007e8f0
--- /dev/null
+++ b/Zend/tests/return_types/inheritance009.phpt
@@ -0,0 +1,42 @@
+--TEST--
+Inheritance Hinting Compile Checking Failure Internal Classes
+
+--INI--
+opcache.enable_cli=1
+
+--FILE--
+<?php
+class Foo {
+ public static function test() : Traversable {
+ return new ArrayIterator([1, 2]);
+ }
+}
+
+class Bar extends Foo {
+ public static function test() : Traversable {
+ return new ArrayObject([1, 2]);
+ }
+}
+
+var_dump(Bar::test());
+var_dump(Foo::test());
+
+--EXPECTF--
+object(ArrayObject)#%d (1) {
+ ["storage":"ArrayObject":private]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ }
+}
+object(ArrayIterator)#%d (1) {
+ ["storage":"ArrayIterator":private]=>
+ array(2) {
+ [0]=>
+ int(1)
+ [1]=>
+ int(2)
+ }
+}
diff --git a/Zend/tests/return_types/reflection001.phpt b/Zend/tests/return_types/reflection001.phpt
new file mode 100644
index 0000000000..f616504747
--- /dev/null
+++ b/Zend/tests/return_types/reflection001.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Return type hinting and Reflection::export()
+
+--SKIPIF--
+<?php
+if (!extension_loaded('reflection') || !defined('PHP_VERSION_ID') || PHP_VERSION_ID < 70000) {
+ print 'skip';
+}
+
+--FILE--
+<?php
+
+class A {
+ function foo(array $a): array {
+ return $a;
+ }
+}
+
+ReflectionClass::export("A");
+--EXPECTF--
+Class [ <user> class A ] {
+ @@ %sreflection001.php 3-7
+
+ - Constants [0] {
+ }
+
+ - Static properties [0] {
+ }
+
+ - Static methods [0] {
+ }
+
+ - Properties [0] {
+ }
+
+ - Methods [1] {
+ Method [ <user> public method foo ] {
+ @@ %sreflection001.php 4 - 6
+
+ - Parameters [1] {
+ Parameter #0 [ <required> array $a ]
+ }
+ - Return [ array ]
+ }
+ }
+}
+
diff --git a/Zend/tests/return_types/rfc001.phpt b/Zend/tests/return_types/rfc001.phpt
new file mode 100644
index 0000000000..4d5275227d
--- /dev/null
+++ b/Zend/tests/return_types/rfc001.phpt
@@ -0,0 +1,14 @@
+--TEST--
+RFC example: returned type does not match the type declaration
+
+--FILE--
+<?php
+
+function get_config(): array {
+ return 42;
+}
+
+get_config();
+
+--EXPECTF--
+Catchable fatal error: Return value of get_config() must be of the type array, integer returned in %s on line %d
diff --git a/Zend/tests/return_types/rfc002.phpt b/Zend/tests/return_types/rfc002.phpt
new file mode 100644
index 0000000000..29b5ed3220
--- /dev/null
+++ b/Zend/tests/return_types/rfc002.phpt
@@ -0,0 +1,13 @@
+--TEST--
+RFC example: expected class int and returned integer
+
+--FILE--
+<?php
+function answer(): int {
+ return 42;
+}
+
+answer();
+
+--EXPECTF--
+Catchable fatal error: Return value of answer() must be an instance of int, integer returned in %s on line %d
diff --git a/Zend/tests/return_types/rfc003.phpt b/Zend/tests/return_types/rfc003.phpt
new file mode 100644
index 0000000000..3aad309b47
--- /dev/null
+++ b/Zend/tests/return_types/rfc003.phpt
@@ -0,0 +1,13 @@
+--TEST--
+RFC example: cannot return null with a return type declaration
+
+--FILE--
+<?php
+function foo(): DateTime {
+ return null;
+}
+
+foo();
+
+--EXPECTF--
+Catchable fatal error: Return value of foo() must be an instance of DateTime, null returned in %s on line %d
diff --git a/Zend/tests/return_types/rfc004.phpt b/Zend/tests/return_types/rfc004.phpt
new file mode 100644
index 0000000000..13bfe72747
--- /dev/null
+++ b/Zend/tests/return_types/rfc004.phpt
@@ -0,0 +1,21 @@
+--TEST--
+RFC example: missing return type on override
+
+--FILE--
+<?php
+
+class User {}
+
+interface UserGateway {
+ function find($id) : User;
+}
+
+class UserGateway_MySql implements UserGateway {
+ // must return User or subtype of User
+ function find($id) {
+ return new User;
+ }
+}
+
+--EXPECTF--
+Fatal error: Declaration of UserGateway_MySql::find() must be compatible with UserGateway::find($id): User in %s on line 9
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index f160bb4456..440e96d311 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2001,6 +2001,9 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
/* Don't count the variadic argument */
internal_function->num_args--;
}
+ if (info->type_hint) {
+ internal_function->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE;
+ }
} else {
internal_function->arg_info = NULL;
internal_function->num_args = 0;
diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c
index 24fe8bd6f9..242c99ff7f 100644
--- a/Zend/zend_ast.c
+++ b/Zend/zend_ast.c
@@ -67,7 +67,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_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3
) {
zend_ast_decl *ast;
@@ -83,6 +83,7 @@ ZEND_API zend_ast *zend_ast_create_decl(
ast->child[0] = child0;
ast->child[1] = child1;
ast->child[2] = child2;
+ ast->child[3] = child3;
return (zend_ast *) ast;
}
@@ -396,6 +397,7 @@ static void zend_ast_destroy_ex(zend_ast *ast, zend_bool free) {
zend_ast_destroy_ex(decl->child[0], 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);
break;
}
default:
diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h
index 5165925696..a9d58428ec 100644
--- a/Zend/zend_ast.h
+++ b/Zend/zend_ast.h
@@ -183,7 +183,7 @@ typedef struct _zend_ast_decl {
unsigned char *lex_pos;
zend_string *doc_comment;
zend_string *name;
- zend_ast *child[3];
+ zend_ast *child[4];
} 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_string *name, zend_ast *child0, zend_ast *child1, zend_ast *child2, zend_ast *child3
);
ZEND_API zend_ast *zend_ast_create_list(uint32_t init_children, zend_ast_kind kind, ...);
diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c
index d50b42c0a2..023e741360 100644
--- a/Zend/zend_closures.c
+++ b/Zend/zend_closures.c
@@ -359,7 +359,9 @@ static HashTable *zend_closure_get_debug_info(zval *object, int *is_temp) /* {{{
zend_hash_str_update(closure->debug_info, "this", sizeof("this")-1, &closure->this_ptr);
}
- if (arg_info) {
+ if (arg_info &&
+ (closure->func.common.num_args ||
+ (closure->func.common.fn_flags & ZEND_ACC_VARIADIC))) {
uint32_t i, num_args, required = closure->func.common.required_num_args;
array_init(&val);
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index e3b15e0f49..7476d475de 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -436,14 +436,6 @@ static int zend_add_const_name_literal(zend_op_array *op_array, zend_string *nam
op.constant = zend_add_literal(CG(active_op_array), &_c); \
} while (0)
-#define MAKE_NOP(opline) do { \
- opline->opcode = ZEND_NOP; \
- memset(&opline->result, 0, sizeof(opline->result)); \
- memset(&opline->op1, 0, sizeof(opline->op1)); \
- memset(&opline->op2, 0, sizeof(opline->op2)); \
- opline->result_type = opline->op1_type = opline->op2_type = IS_UNUSED; \
-} while (0)
-
void zend_stop_lexing(void) {
LANG_SCNG(yy_cursor) = LANG_SCNG(yy_limit);
}
@@ -1040,7 +1032,7 @@ void zend_do_early_binding(void) /* {{{ */
if (((ce = zend_lookup_class(Z_STR_P(parent_name))) == NULL) ||
((CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_CLASSES) &&
(ce->type == ZEND_INTERNAL_CLASS))) {
- if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
+ if (CG(compiler_options) & ZEND_COMPILE_DELAYED_BINDING) {
uint32_t *opline_num = &CG(active_op_array)->early_binding;
while (*opline_num != (uint32_t)-1) {
@@ -1829,11 +1821,25 @@ static zend_op *zend_delayed_compile_end(uint32_t offset) /* {{{ */
}
/* }}} */
+
+static void zend_emit_return_type_check(znode *expr, zend_arg_info *return_info) /* {{{ */
+{
+ if (return_info->type_hint != IS_UNDEF) {
+ zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL);
+ }
+}
+/* }}} */
+
+
void zend_emit_final_return(zval *zv) /* {{{ */
{
znode zn;
zend_bool returns_reference = (CG(active_op_array)->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0;
+ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_emit_return_type_check(NULL, CG(active_op_array)->arg_info - 1);
+ }
+
zn.op_type = IS_CONST;
if (zv) {
ZVAL_COPY_VALUE(&zn.u.constant, zv);
@@ -3119,6 +3125,7 @@ static void zend_free_foreach_and_switch_variables(void) /* {{{ */
}
/* }}} */
+
void zend_compile_return(zend_ast *ast) /* {{{ */
{
zend_ast *expr_ast = ast->child[0];
@@ -3144,6 +3151,12 @@ void zend_compile_return(zend_ast *ast) /* {{{ */
opline->op1.var = CG(context).fast_call_var;
}
+ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_emit_return_type_check(&expr_node, CG(active_op_array)->arg_info - 1);
+ if (expr_node.op_type == IS_CONST) {
+ zval_copy_ctor(&expr_node.u.constant);
+ }
+ }
opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN,
&expr_node, NULL);
@@ -3780,18 +3793,51 @@ void zend_compile_stmt_list(zend_ast *ast) /* {{{ */
}
/* }}} */
-void zend_compile_params(zend_ast *ast) /* {{{ */
+void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_bool is_method) /* {{{ */
{
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;
+
+ if (return_type_ast) {
+ /* Use op_array->arg_info[-1] for return type hinting */
+ arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0);
+ arg_infos->name = NULL;
+ arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0;
+ arg_infos->is_variadic = 0;
+ arg_infos->type_hint = 0;
+ arg_infos->allow_null = 0;
+ arg_infos->class_name = NULL;
+
+ if (return_type_ast->kind == ZEND_AST_TYPE) {
+ arg_infos->type_hint = return_type_ast->attr;
+ } else {
+ zend_string *class_name = zend_ast_get_str(return_type_ast);
- if (list->children == 0) {
- return;
+ if (zend_is_const_default_class_ref(return_type_ast)) {
+ class_name = zend_resolve_class_name_ast(return_type_ast);
+ } else {
+ zend_string_addref(class_name);
+ if (!is_method) {
+ zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare a return type of %s outside of a class scope", class_name->val);
+ return;
+ }
+ }
+
+ arg_infos->type_hint = IS_OBJECT;
+ arg_infos->class_name = class_name;
+ }
+
+ 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);
}
- arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children, 0);
for (i = 0; i < list->children; ++i) {
zend_ast *param_ast = list->child[i];
zend_ast *type_ast = param_ast->child[0];
@@ -4164,6 +4210,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */
zend_ast *params_ast = decl->child[0];
zend_ast *uses_ast = decl->child[1];
zend_ast *stmt_ast = decl->child[2];
+ zend_ast *return_type_ast = decl->child[3];
zend_bool is_method = decl->kind == ZEND_AST_METHOD;
zend_op_array *orig_op_array = CG(active_op_array);
@@ -4207,7 +4254,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);
+ zend_compile_params(params_ast, return_type_ast, is_method);
if (uses_ast) {
zend_compile_closure_uses(uses_ast);
}
@@ -4585,12 +4632,21 @@ void zend_compile_class_decl(zend_ast *ast) /* {{{ */
zend_error_noreturn(E_COMPILE_ERROR, "Constructor %s::%s() cannot be static",
ce->name->val, ce->constructor->common.function_name->val);
}
+ if (ce->constructor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Constructor %s::%s() cannot declare a return type",
+ ce->name->val, ce->constructor->common.function_name->val);
+ }
}
if (ce->destructor) {
ce->destructor->common.fn_flags |= ZEND_ACC_DTOR;
if (ce->destructor->common.fn_flags & ZEND_ACC_STATIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Destructor %s::%s() cannot be static",
ce->name->val, ce->destructor->common.function_name->val);
+ } else if (ce->destructor->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "Destructor %s::%s() cannot declare a return type",
+ ce->name->val, ce->destructor->common.function_name->val);
}
}
if (ce->clone) {
@@ -4598,6 +4654,10 @@ void zend_compile_class_decl(zend_ast *ast) /* {{{ */
if (ce->clone->common.fn_flags & ZEND_ACC_STATIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Clone method %s::%s() cannot be static",
ce->name->val, ce->clone->common.function_name->val);
+ } else if (ce->clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_error_noreturn(E_COMPILE_ERROR,
+ "%s::%s() cannot declare a return type",
+ ce->name->val, ce->clone->common.function_name->val);
}
}
@@ -5435,6 +5495,21 @@ void zend_compile_yield(znode *result, zend_ast *ast) /* {{{ */
zend_error_noreturn(E_COMPILE_ERROR,
"The \"yield\" expression can only be used inside a function");
}
+ if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ const char *msg = "Generators may only declare a return type of Generator, Iterator or Traversable, %s is not permitted";
+ if (!CG(active_op_array)->arg_info[-1].class_name) {
+ zend_error_noreturn(E_COMPILE_ERROR, msg,
+ zend_get_type_by_const(CG(active_op_array)->arg_info[-1].type_hint));
+ }
+ if (!(CG(active_op_array)->arg_info[-1].class_name->len == sizeof("Traversable")-1
+ && zend_binary_strcasecmp(CG(active_op_array)->arg_info[-1].class_name->val, sizeof("Traversable")-1, "Traversable", sizeof("Traversable")-1) == 0) &&
+ !(CG(active_op_array)->arg_info[-1].class_name->len == sizeof("Iterator")-1
+ && zend_binary_strcasecmp(CG(active_op_array)->arg_info[-1].class_name->val, sizeof("Iterator")-1, "Iterator", sizeof("Iterator")-1) == 0) &&
+ !(CG(active_op_array)->arg_info[-1].class_name->len == sizeof("Generator")-1
+ && zend_binary_strcasecmp(CG(active_op_array)->arg_info[-1].class_name->val, sizeof("Generator")-1, "Generator", sizeof("Generator")-1) == 0)) {
+ zend_error_noreturn(E_COMPILE_ERROR, msg, CG(active_op_array)->arg_info[-1].class_name->val);
+ }
+ }
CG(active_op_array)->fn_flags |= ZEND_ACC_GENERATOR;
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 7a65a80b08..7c82c67d6d 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -35,6 +35,14 @@
#define SET_UNUSED(op) op ## _type = IS_UNUSED
+#define MAKE_NOP(opline) do { \
+ opline->opcode = ZEND_NOP; \
+ memset(&opline->result, 0, sizeof(opline->result)); \
+ memset(&opline->op1, 0, sizeof(opline->op1)); \
+ memset(&opline->op2, 0, sizeof(opline->op2)); \
+ opline->result_type = opline->op1_type = opline->op2_type = IS_UNUSED; \
+} while (0)
+
#define RESET_DOC_COMMENT() do { \
if (CG(doc_comment)) { \
zend_string_release(CG(doc_comment)); \
@@ -243,6 +251,9 @@ typedef struct _zend_try_catch_element {
/* internal function is allocated at arena */
#define ZEND_ACC_ARENA_ALLOCATED 0x20000000
+/* Function has a return type hint (or class has such non-private function) */
+#define ZEND_ACC_HAS_RETURN_TYPE 0x40000000
+
#define ZEND_CE_IS_TRAIT(ce) (((ce)->ce_flags & ZEND_ACC_TRAIT) == ZEND_ACC_TRAIT)
char *zend_visibility_string(uint32_t fn_flags);
@@ -288,13 +299,14 @@ typedef struct _zend_arg_info {
/* the following structure repeats the layout of zend_internal_arg_info,
* but its fields have different meaning. It's used as the first element of
* arg_info array to define properties of internal functions.
+ * It's also used for return type hinting.
*/
typedef struct _zend_internal_function_info {
zend_uintptr_t required_num_args;
- const char *_class_name;
- zend_uchar _type_hint;
+ const char *class_name;
+ zend_uchar type_hint;
zend_bool return_reference;
- zend_bool _allow_null;
+ zend_bool allow_null;
zend_bool _is_variadic;
} zend_internal_function_info;
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 3092d05a96..7a79defb05 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -751,6 +751,87 @@ static void zend_verify_missing_arg(zend_execute_data *execute_data, uint32_t ar
}
}
+ZEND_API void zend_verify_return_error(int error_type, const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind)
+{
+ const char *fname = zf->common.function_name->val;
+ const char *fsep;
+ const char *fclass;
+
+ if (zf->common.scope) {
+ fsep = "::";
+ fclass = zf->common.scope->name->val;
+ } else {
+ fsep = "";
+ fclass = "";
+ }
+
+ zend_error(error_type,
+ "Return value of %s%s%s() must %s%s, %s%s returned",
+ fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind);
+}
+
+static void zend_verify_return_type(zend_function *zf, zval *ret)
+{
+ zend_arg_info *ret_info = zf->common.arg_info - 1;
+ char *need_msg;
+ zend_class_entry *ce;
+
+ if (ret_info->class_name) {
+ char *class_name;
+
+ if (Z_TYPE_P(ret) == IS_OBJECT) {
+ need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce);
+ if (!ce || !instanceof_function(Z_OBJCE_P(ret), ce)) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, need_msg, class_name, "instance of ", Z_OBJCE_P(ret)->name->val);
+ }
+ } else if (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null) {
+ need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce);
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, need_msg, class_name, zend_zval_type_name(ret), "");
+ }
+ } else if (ret_info->type_hint) {
+ if (ret_info->type_hint == IS_ARRAY) {
+ if (Z_TYPE_P(ret) != IS_ARRAY && (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null)) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be of the type array", "", zend_zval_type_name(ret), "");
+ }
+ } else if (ret_info->type_hint == IS_CALLABLE) {
+ if (!zend_is_callable(ret, IS_CALLABLE_CHECK_SILENT, NULL) && (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null)) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be callable", "", zend_zval_type_name(ret), "");
+ }
+#if ZEND_DEBUG
+ } else {
+ zend_error(E_ERROR, "Unknown typehint");
+#endif
+ }
+ }
+}
+
+static inline int zend_verify_missing_return_type(zend_function *zf)
+{
+ zend_arg_info *ret_info = zf->common.arg_info - 1;
+ char *need_msg;
+ zend_class_entry *ce;
+
+ if (ret_info->class_name) {
+ char *class_name;
+
+ need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce);
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, need_msg, class_name, "none", "");
+ return 0;
+ } else if (ret_info->type_hint) {
+ if (ret_info->type_hint == IS_ARRAY) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be of the type array", "", "none", "");
+ } else if (ret_info->type_hint == IS_CALLABLE) {
+ zend_verify_return_error(E_RECOVERABLE_ERROR, zf, "be callable", "", "none", "");
+#if ZEND_DEBUG
+ } else {
+ zend_error(E_ERROR, "Unknown typehint");
+#endif
+ }
+ return 0;
+ }
+ return 1;
+}
+
static zend_always_inline void zend_assign_to_object(zval *retval, zval *object, uint32_t object_op_type, zval *property_name, uint32_t property_op_type, int value_type, znode_op value_op, const zend_execute_data *execute_data, void **cache_slot)
{
zend_free_op free_value;
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index 16d143a46c..0fdf76453d 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -50,6 +50,7 @@ ZEND_API int zend_eval_stringl_ex(char *str, size_t str_len, zval *retval_ptr, c
ZEND_API char * zend_verify_internal_arg_class_kind(const zend_internal_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce);
ZEND_API char * zend_verify_arg_class_kind(const zend_arg_info *cur_arg_info, char **class_name, zend_class_entry **pce);
ZEND_API void zend_verify_arg_error(int error_type, const zend_function *zf, uint32_t arg_num, const char *need_msg, const char *need_kind, const char *given_msg, const char *given_kind, zval *arg);
+ZEND_API void zend_verify_return_error(int error_type, const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind);
static zend_always_inline zval* zend_assign_to_variable(zval *variable_ptr, zval *value, zend_uchar value_type)
{
diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c
index c69714ad19..ab188f2cfb 100644
--- a/Zend/zend_inheritance.c
+++ b/Zend/zend_inheritance.c
@@ -21,6 +21,7 @@
#include "zend_API.h"
#include "zend_compile.h"
#include "zend_execute.h"
+#include "zend_inheritance.h"
#include "zend_smart_str.h"
static void ptr_dtor(zval *zv) /* {{{ */
@@ -211,6 +212,90 @@ static zend_function *do_inherit_method(zend_function *old_function, zend_class_
}
/* }}} */
+static int zend_do_perform_type_hint_check(zend_function *fe, zend_arg_info *fe_arg_info, zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */
+{
+ if (ZEND_LOG_XOR(fe_arg_info->class_name, proto_arg_info->class_name)) {
+ /* Only one has a type hint and the other one doesn't */
+ return 0;
+ }
+
+ if (fe_arg_info->class_name) {
+ zend_string *fe_class_name, *proto_class_name;
+ const char *class_name;
+
+ if (fe->type == ZEND_INTERNAL_FUNCTION) {
+ fe_class_name = NULL;
+ class_name = ((zend_internal_arg_info*)fe_arg_info)->class_name;
+ } else {
+ fe_class_name = fe_arg_info->class_name;
+ class_name = fe_arg_info->class_name->val;
+ }
+ if (!strcasecmp(class_name, "parent") && proto->common.scope) {
+ fe_class_name = zend_string_copy(proto->common.scope->name);
+ } else if (!strcasecmp(class_name, "self") && fe->common.scope) {
+ fe_class_name = zend_string_copy(fe->common.scope->name);
+ } else if (fe_class_name) {
+ zend_string_addref(fe_class_name);
+ } else {
+ fe_class_name = zend_string_init(class_name, strlen(class_name), 0);
+ }
+
+ if (proto->type == ZEND_INTERNAL_FUNCTION) {
+ proto_class_name = NULL;
+ class_name = ((zend_internal_arg_info*)proto_arg_info)->class_name;
+ } else {
+ proto_class_name = proto_arg_info->class_name;
+ class_name = proto_arg_info->class_name->val;
+ }
+ if (!strcasecmp(class_name, "parent") && proto->common.scope && proto->common.scope->parent) {
+ proto_class_name = zend_string_copy(proto->common.scope->parent->name);
+ } else if (!strcasecmp(class_name, "self") && proto->common.scope) {
+ proto_class_name = zend_string_copy(proto->common.scope->name);
+ } else if (proto_class_name) {
+ zend_string_addref(proto_class_name);
+ } else {
+ proto_class_name = zend_string_init(class_name, strlen(class_name), 0);
+ }
+
+ if (strcasecmp(fe_class_name->val, proto_class_name->val) != 0) {
+ const char *colon;
+
+ if (fe->common.type != ZEND_USER_FUNCTION) {
+ zend_string_release(proto_class_name);
+ zend_string_release(fe_class_name);
+ return 0;
+ } else if (strchr(proto_class_name->val, '\\') != NULL ||
+ (colon = zend_memrchr(fe_class_name->val, '\\', fe_class_name->len)) == NULL ||
+ strcasecmp(colon+1, proto_class_name->val) != 0) {
+ zend_class_entry *fe_ce, *proto_ce;
+
+ fe_ce = zend_lookup_class(fe_class_name);
+ proto_ce = zend_lookup_class(proto_class_name);
+
+ /* Check for class alias */
+ if (!fe_ce || !proto_ce ||
+ fe_ce->type == ZEND_INTERNAL_CLASS ||
+ proto_ce->type == ZEND_INTERNAL_CLASS ||
+ fe_ce != proto_ce) {
+ zend_string_release(proto_class_name);
+ zend_string_release(fe_class_name);
+ return 0;
+ }
+ }
+ }
+ zend_string_release(proto_class_name);
+ zend_string_release(fe_class_name);
+ }
+
+ if (fe_arg_info->type_hint != proto_arg_info->type_hint) {
+ /* Incompatible type hint */
+ return 0;
+ }
+
+ return 1;
+}
+/* }}} */
+
static zend_bool zend_do_perform_implementation_check(const zend_function *fe, const zend_function *proto) /* {{{ */
{
uint32_t i, num_args;
@@ -279,90 +364,62 @@ static zend_bool zend_do_perform_implementation_check(const zend_function *fe, c
proto_arg_info = &proto->common.arg_info[proto->common.num_args];
}
- if (ZEND_LOG_XOR(fe_arg_info->class_name, proto_arg_info->class_name)) {
- /* Only one has a type hint and the other one doesn't */
+ if (!zend_do_perform_type_hint_check(fe, fe_arg_info, proto, proto_arg_info)) {
return 0;
}
- if (fe_arg_info->class_name) {
- zend_string *fe_class_name, *proto_class_name;
- const char *class_name;
-
- if (fe->type == ZEND_INTERNAL_FUNCTION) {
- fe_class_name = NULL;
- class_name = ((zend_internal_arg_info*)fe_arg_info)->class_name;
- } else {
- fe_class_name = fe_arg_info->class_name;
- class_name = fe_arg_info->class_name->val;
- }
- if (!strcasecmp(class_name, "parent") && proto->common.scope) {
- fe_class_name = zend_string_copy(proto->common.scope->name);
- } else if (!strcasecmp(class_name, "self") && fe->common.scope) {
- fe_class_name = zend_string_copy(fe->common.scope->name);
- } else if (fe_class_name) {
- zend_string_addref(fe_class_name);
- } else {
- fe_class_name = zend_string_init(class_name, strlen(class_name), 0);
- }
+ /* by-ref constraints on arguments are invariant */
+ if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) {
+ return 0;
+ }
+ }
- if (proto->type == ZEND_INTERNAL_FUNCTION) {
- proto_class_name = NULL;
- class_name = ((zend_internal_arg_info*)proto_arg_info)->class_name;
- } else {
- proto_class_name = proto_arg_info->class_name;
- class_name = proto_arg_info->class_name->val;
- }
- if (!strcasecmp(class_name, "parent") && proto->common.scope && proto->common.scope->parent) {
- proto_class_name = zend_string_copy(proto->common.scope->parent->name);
- } else if (!strcasecmp(class_name, "self") && proto->common.scope) {
- proto_class_name = zend_string_copy(proto->common.scope->name);
- } else if (proto_class_name) {
- zend_string_addref(proto_class_name);
- } else {
- proto_class_name = zend_string_init(class_name, strlen(class_name), 0);
- }
+ /* check return type compataibility */
+ if ((proto->common.fn_flags | fe->common.fn_flags) & ZEND_ACC_HAS_RETURN_TYPE) {
+ if ((proto->common.fn_flags ^ fe->common.fn_flags) & ZEND_ACC_HAS_RETURN_TYPE) {
+ return 0;
+ }
+ if (!zend_do_perform_type_hint_check(fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1)) {
+ return 0;
+ }
+ }
+ return 1;
+}
+/* }}} */
- if (strcasecmp(fe_class_name->val, proto_class_name->val)!=0) {
- const char *colon;
+static void zend_append_type_hint(smart_str *str, zend_function *fptr, zend_arg_info *arg_info, int return_hint) /* {{{ */
+{
+ if (arg_info->class_name) {
+ const char *class_name;
+ size_t class_name_len;
- if (fe->common.type != ZEND_USER_FUNCTION) {
- zend_string_release(proto_class_name);
- zend_string_release(fe_class_name);
- return 0;
- } else if (strchr(proto_class_name->val, '\\') != NULL ||
- (colon = zend_memrchr(fe_class_name->val, '\\', fe_class_name->len)) == NULL ||
- strcasecmp(colon+1, proto_class_name->val) != 0) {
- zend_class_entry *fe_ce, *proto_ce;
-
- fe_ce = zend_lookup_class(fe_class_name);
- proto_ce = zend_lookup_class(proto_class_name);
-
- /* Check for class alias */
- if (!fe_ce || !proto_ce ||
- fe_ce->type == ZEND_INTERNAL_CLASS ||
- proto_ce->type == ZEND_INTERNAL_CLASS ||
- fe_ce != proto_ce) {
- zend_string_release(proto_class_name);
- zend_string_release(fe_class_name);
- return 0;
- }
- }
- }
- zend_string_release(proto_class_name);
- zend_string_release(fe_class_name);
+ if (fptr->type == ZEND_INTERNAL_FUNCTION) {
+ class_name = ((zend_internal_arg_info*)arg_info)->class_name;
+ class_name_len = strlen(class_name);
+ } else {
+ class_name = arg_info->class_name->val;
+ class_name_len = arg_info->class_name->len;
}
- if (fe_arg_info->type_hint != proto_arg_info->type_hint) {
- /* Incompatible type hint */
- return 0;
+
+ if (!strcasecmp(class_name, "self") && fptr->common.scope) {
+ class_name = fptr->common.scope->name->val;
+ class_name_len = fptr->common.scope->name->len;
+ } else if (!strcasecmp(class_name, "parent") && fptr->common.scope->parent) {
+ class_name = fptr->common.scope->parent->name->val;
+ class_name_len = fptr->common.scope->parent->name->len;
}
- /* by-ref constraints on arguments are invariant */
- if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) {
- return 0;
+ smart_str_appendl(str, class_name, class_name_len);
+ if (!return_hint) {
+ smart_str_appendc(str, ' ');
+ }
+ } else if (arg_info->type_hint) {
+ const char *type_name = zend_get_type_by_const(arg_info->type_hint);
+ smart_str_appends(str, type_name);
+ if (!return_hint) {
+ smart_str_appendc(str, ' ');
}
}
-
- return 1;
}
/* }}} */
@@ -392,33 +449,7 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */
num_args++;
}
for (i = 0; i < num_args;) {
- if (arg_info->class_name) {
- const char *class_name;
- size_t class_name_len;
-
- if (fptr->type == ZEND_INTERNAL_FUNCTION) {
- class_name = ((zend_internal_arg_info*)arg_info)->class_name;
- class_name_len = strlen(class_name);
- } else {
- class_name = arg_info->class_name->val;
- class_name_len = arg_info->class_name->len;
- }
-
- if (!strcasecmp(class_name, "self") && fptr->common.scope) {
- class_name = fptr->common.scope->name->val;
- class_name_len = fptr->common.scope->name->len;
- } else if (!strcasecmp(class_name, "parent") && fptr->common.scope->parent) {
- class_name = fptr->common.scope->parent->name->val;
- class_name_len = fptr->common.scope->parent->name->len;
- }
-
- smart_str_appendl(&str, class_name, class_name_len);
- smart_str_appendc(&str, ' ');
- } else if (arg_info->type_hint) {
- const char *type_name = zend_get_type_by_const(arg_info->type_hint);
- smart_str_appends(&str, type_name);
- smart_str_appendc(&str, ' ');
- }
+ zend_append_type_hint(&str, fptr, arg_info, 0);
if (arg_info->pass_by_reference) {
smart_str_appendc(&str, '&');
@@ -501,6 +532,11 @@ static zend_string *zend_get_function_declaration(zend_function *fptr) /* {{{ */
}
smart_str_appendc(&str, ')');
+
+ if (fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ smart_str_appends(&str, ": ");
+ zend_append_type_hint(&str, fptr, fptr->common.arg_info - 1, 1);
+ }
smart_str_0(&str);
return str.s;
@@ -565,7 +601,9 @@ static void do_inheritance_check_on_method(zend_function *child, zend_function *
child->common.prototype = parent->common.prototype ? parent->common.prototype : parent;
}
- if (child->common.prototype && (child->common.prototype->common.fn_flags & ZEND_ACC_ABSTRACT)) {
+ if (child->common.prototype && (
+ child->common.prototype->common.fn_flags & (ZEND_ACC_ABSTRACT | ZEND_ACC_HAS_RETURN_TYPE)
+ )) {
if (!zend_do_perform_implementation_check(child, child->common.prototype)) {
zend_error_noreturn(E_COMPILE_ERROR, "Declaration of %s::%s() must be compatible with %s", ZEND_FN_SCOPE_NAME(child), child->common.function_name->val, zend_get_function_declaration(child->common.prototype)->val);
}
diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y
index dffd1d0f25..7acec68b47 100644
--- a/Zend/zend_language_parser.y
+++ b/Zend/zend_language_parser.y
@@ -241,7 +241,6 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> variable_class_name dereferencable_scalar class_name_scalar constant dereferencable
%type <ast> callable_expr callable_variable static_member new_variable
%type <ast> assignment_list_element array_pair encaps_var encaps_var_offset isset_variables
-%type <ast> isset_variable
%type <ast> top_statement_list use_declarations const_list inner_statement_list if_stmt
%type <ast> alt_if_stmt for_exprs switch_case_list global_var_list static_var_list
%type <ast> echo_expr_list unset_variables catch_list parameter_list class_statement_list
@@ -250,7 +249,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*);
%type <ast> class_const_list name_list trait_adaptations method_body non_empty_for_exprs
%type <ast> ctor_arguments alt_if_stmt_without_else trait_adaptation_list lexical_vars
%type <ast> lexical_var_list encaps_list array_pair_list non_empty_array_pair_list
-%type <ast> assignment_list
+%type <ast> assignment_list isset_variable type return_type
%type <num> returns_ref function is_reference is_variadic variable_modifiers
%type <num> method_modifiers trait_modifiers non_empty_member_modifiers member_modifier
@@ -408,10 +407,10 @@ unset_variable:
;
function_declaration_statement:
- function returns_ref T_STRING '(' parameter_list ')' backup_doc_comment
- '{' inner_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $7,
- zend_ast_get_str($3), $5, NULL, $9); }
+ function returns_ref T_STRING '(' parameter_list ')' return_type
+ backup_doc_comment '{' inner_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $8,
+ zend_ast_get_str($3), $5, NULL, $10, $7); }
;
is_reference:
@@ -427,10 +426,10 @@ is_variadic:
class_declaration_statement:
class_modifiers T_CLASS { $<num>$ = CG(zend_lineno); }
T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $<num>3, $7, zend_ast_get_str($4), $5, $6, $9); }
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, $1, $<num>3, $7, zend_ast_get_str($4), $5, $6, $9, NULL); }
| T_CLASS { $<num>$ = CG(zend_lineno); }
T_STRING extends_from implements_list backup_doc_comment '{' class_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $<num>2, $6, zend_ast_get_str($3), $4, $5, $8); }
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, 0, $<num>2, $6, zend_ast_get_str($3), $4, $5, $8, NULL); }
;
class_modifiers:
@@ -446,13 +445,13 @@ class_modifier:
trait_declaration_statement:
T_TRAIT { $<num>$ = CG(zend_lineno); }
T_STRING backup_doc_comment '{' class_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $<num>2, $4, zend_ast_get_str($3), NULL, NULL, $6); }
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_TRAIT, $<num>2, $4, zend_ast_get_str($3), NULL, NULL, $6, NULL); }
;
interface_declaration_statement:
T_INTERFACE { $<num>$ = CG(zend_lineno); }
T_STRING interface_extends_list backup_doc_comment '{' class_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $<num>2, $5, zend_ast_get_str($3), NULL, $4, $7); }
+ { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_INTERFACE, $<num>2, $5, zend_ast_get_str($3), NULL, $4, $7, NULL); }
;
extends_from:
@@ -572,11 +571,20 @@ parameter:
optional_type:
/* empty */ { $$ = NULL; }
- | T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
+ | type { $$ = $1; }
+;
+
+type:
+ T_ARRAY { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_ARRAY); }
| T_CALLABLE { $$ = zend_ast_create_ex(ZEND_AST_TYPE, IS_CALLABLE); }
| name { $$ = $1; }
;
+return_type:
+ /* empty */ { $$ = NULL; }
+ | ':' type { $$ = $2; }
+;
+
argument_list:
'(' ')' { $$ = zend_ast_create_list(0, ZEND_AST_ARG_LIST); }
| '(' non_empty_argument_list ')' { $$ = $2; }
@@ -631,10 +639,10 @@ class_statement:
{ $$ = $2; RESET_DOC_COMMENT(); }
| T_USE name_list trait_adaptations
{ $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); }
- | method_modifiers function returns_ref T_STRING '(' parameter_list ')' backup_doc_comment
- method_body
- { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1, $2, $8,
- zend_ast_get_str($4), $6, NULL, $9); }
+ | method_modifiers function returns_ref T_STRING '(' parameter_list ')'
+ return_type backup_doc_comment method_body
+ { $$ = zend_ast_create_decl(ZEND_AST_METHOD, $3 | $1, $2, $9,
+ zend_ast_get_str($4), $6, NULL, $10, $8); }
;
name_list:
@@ -866,16 +874,16 @@ expr_without_variable:
| T_YIELD { $$ = zend_ast_create(ZEND_AST_YIELD, NULL, NULL); }
| T_YIELD expr { $$ = zend_ast_create(ZEND_AST_YIELD, $2, NULL); }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = zend_ast_create(ZEND_AST_YIELD, $4, $2); }
- | function returns_ref '(' parameter_list ')' lexical_vars backup_doc_comment
- '{' inner_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $7,
+ | function returns_ref '(' parameter_list ')' lexical_vars return_type
+ backup_doc_comment '{' inner_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $2, $1, $8,
zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
- $4, $6, $9); }
- | T_STATIC function returns_ref '(' parameter_list ')' lexical_vars backup_doc_comment
- '{' inner_statement_list '}'
- { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | ZEND_ACC_STATIC, $2, $8,
+ $4, $6, $10, $7); }
+ | T_STATIC function returns_ref '(' parameter_list ')' lexical_vars
+ return_type backup_doc_comment '{' inner_statement_list '}'
+ { $$ = zend_ast_create_decl(ZEND_AST_CLOSURE, $3 | ZEND_ACC_STATIC, $2, $9,
zend_string_init("{closure}", sizeof("{closure}") - 1, 0),
- $5, $7, $10); }
+ $5, $7, $11, $8); }
;
function:
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index aa2abb2f6b..a6d041f131 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -367,18 +367,26 @@ ZEND_API void destroy_op_array(zend_op_array *op_array)
zend_llist_apply_with_argument(&zend_extensions, (llist_apply_with_arg_func_t) zend_extension_op_array_dtor_handler, op_array);
}
if (op_array->arg_info) {
- uint32_t num_args = op_array->num_args;
+ int32_t num_args = op_array->num_args;
+ zend_arg_info *arg_info = op_array->arg_info;
+ int32_t i;
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ arg_info--;
+ num_args++;
+ }
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
- for (i = 0; i < num_args; i++) {
- zend_string_release(op_array->arg_info[i].name);
- if (op_array->arg_info[i].class_name) {
- zend_string_release(op_array->arg_info[i].class_name);
+ for (i = 0 ; i < num_args; i++) {
+ if (arg_info[i].name) {
+ zend_string_release(arg_info[i].name);
+ }
+ if (arg_info[i].class_name) {
+ zend_string_release(arg_info[i].class_name);
}
}
- efree(op_array->arg_info);
+ efree(arg_info);
}
}
@@ -762,6 +770,11 @@ ZEND_API int pass_two(zend_op_array *op_array)
case ZEND_FE_FETCH:
ZEND_PASS_TWO_UPDATE_JMP_TARGET(op_array, opline, opline->op2);
break;
+ case ZEND_VERIFY_RETURN_TYPE:
+ if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
+ MAKE_NOP(opline);
+ }
+ break;
case ZEND_RETURN:
case ZEND_RETURN_BY_REF:
if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 84dc9a4a41..24bb3fd3d6 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -2943,6 +2943,24 @@ ZEND_VM_C_LABEL(fcall_end):
ZEND_VM_NEXT_OPCODE();
}
+ZEND_VM_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (OP1_TYPE == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+ zend_free_op free_op1;
+
+ retval_ptr = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
ZEND_VM_HANDLER(62, ZEND_RETURN, CONST|TMP|VAR|CV, ANY)
{
USE_OPLINE
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 2ef731b578..9a7b23d87a 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -6094,6 +6094,24 @@ static int ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_CONST_UNUSED_HANDLER
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_CONST == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+
+
+ retval_ptr = EX_CONSTANT(opline->op1);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_CONST_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -10093,6 +10111,24 @@ static int ZEND_FASTCALL ZEND_FETCH_DIM_FUNC_ARG_SPEC_TMP_UNUSED_HANDLER(ZEND_O
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_TMP_VAR == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+ zend_free_op free_op1;
+
+ retval_ptr = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_TMP_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -14915,6 +14951,24 @@ static int ZEND_FASTCALL ZEND_INIT_STATIC_METHOD_CALL_SPEC_VAR_UNUSED_HANDLER(Z
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_VAR == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+ zend_free_op free_op1;
+
+ retval_ptr = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_VAR_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -20159,6 +20213,24 @@ static int ZEND_FASTCALL ZEND_ASSIGN_BW_XOR_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPC
#endif
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_UNUSED == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+
+
+ retval_ptr = NULL;
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_UNUSED_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zval *array;
@@ -28324,6 +28396,24 @@ try_assign_dim_array:
ZEND_VM_NEXT_OPCODE();
}
+static int ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
+{
+ USE_OPLINE
+
+ SAVE_OPLINE();
+ if (IS_CV == IS_UNUSED) {
+ zend_verify_missing_return_type(EX(func));
+ } else {
+ zval *retval_ptr;
+
+
+ retval_ptr = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var);
+ zend_verify_return_type(EX(func), retval_ptr);
+ }
+ CHECK_EXCEPTION();
+ ZEND_VM_NEXT_OPCODE();
+}
+
static int ZEND_FASTCALL ZEND_ADD_ARRAY_ELEMENT_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
@@ -39383,27 +39473,27 @@ void zend_init_opcodes_handlers(void)
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
- ZEND_NULL_HANDLER,
+ ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNUSED_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
ZEND_NULL_HANDLER,
diff --git a/Zend/zend_vm_opcodes.c b/Zend/zend_vm_opcodes.c
index 8a09143c16..c3006e6036 100644
--- a/Zend/zend_vm_opcodes.c
+++ b/Zend/zend_vm_opcodes.c
@@ -146,7 +146,7 @@ const char *zend_vm_opcodes_map[170] = {
"ZEND_STRLEN",
"ZEND_DEFINED",
"ZEND_TYPE_CHECK",
- NULL,
+ "ZEND_VERIFY_RETURN_TYPE",
NULL,
NULL,
NULL,
diff --git a/Zend/zend_vm_opcodes.h b/Zend/zend_vm_opcodes.h
index db076b48e8..f0fc7c7182 100644
--- a/Zend/zend_vm_opcodes.h
+++ b/Zend/zend_vm_opcodes.h
@@ -148,6 +148,7 @@ END_EXTERN_C()
#define ZEND_STRLEN 121
#define ZEND_DEFINED 122
#define ZEND_TYPE_CHECK 123
+#define ZEND_VERIFY_RETURN_TYPE 124
#define ZEND_PRE_INC_OBJ 132
#define ZEND_PRE_DEC_OBJ 133
#define ZEND_POST_INC_OBJ 134
diff --git a/ext/interbase/tests/ibase_trans_001.phpt b/ext/interbase/tests/ibase_trans_001.phpt
index cceb60e9a1..d8b7c81a1b 100644
--- a/ext/interbase/tests/ibase_trans_001.phpt
+++ b/ext/interbase/tests/ibase_trans_001.phpt
@@ -18,4 +18,6 @@ var_dump(ibase_close($x));
resource(%d) of type (Firebird/InterBase transaction)
resource(%d) of type (Firebird/InterBase transaction)
bool(true)
-bool(true)
+
+Warning: ibase_close(): supplied resource is not a valid Firebird/InterBase link resource in %s on line %d
+bool(false)
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index 1e157e18c3..3d63f96e34 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -414,27 +414,36 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc
}
if (op_array->arg_info) {
+ zend_arg_info *arg_info = op_array->arg_info;
+ uint32_t num_args = op_array->num_args;
+
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ arg_info--;
+ num_args++;
+ }
if (already_stored) {
- zend_arg_info *new_ptr = zend_shared_alloc_get_xlat_entry(op_array->arg_info);
- ZEND_ASSERT(new_ptr != NULL);
- op_array->arg_info = new_ptr;
+ arg_info = zend_shared_alloc_get_xlat_entry(arg_info);
+ ZEND_ASSERT(arg_info != NULL);
} else {
- uint32_t i, num_args;
+ uint32_t i;
- num_args = op_array->num_args;
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
- zend_accel_store(op_array->arg_info, sizeof(zend_arg_info) * num_args);
+ zend_accel_store(arg_info, sizeof(zend_arg_info) * num_args);
for (i = 0; i < num_args; i++) {
- if (op_array->arg_info[i].name) {
- zend_accel_store_interned_string(op_array->arg_info[i].name);
+ if (arg_info[i].name) {
+ zend_accel_store_interned_string(arg_info[i].name);
}
- if (op_array->arg_info[i].class_name) {
- zend_accel_store_interned_string(op_array->arg_info[i].class_name);
+ if (arg_info[i].class_name) {
+ zend_accel_store_interned_string(arg_info[i].class_name);
}
}
}
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ arg_info++;
+ }
+ op_array->arg_info = arg_info;
}
if (op_array->brk_cont_array) {
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index 31ec320141..c5523e7d89 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -194,21 +194,27 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array)
}
if (op_array->arg_info) {
- uint32_t i, num_args;
+ zend_arg_info *arg_info = op_array->arg_info;
+ uint32_t num_args = op_array->num_args;
+ uint32_t i;
num_args = op_array->num_args;
if (op_array->fn_flags & ZEND_ACC_VARIADIC) {
num_args++;
}
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ arg_info--;
+ num_args++;
+ }
ADD_DUP_SIZE(op_array->arg_info, sizeof(zend_arg_info) * num_args);
+ ADD_DUP_SIZE(arg_info, sizeof(zend_arg_info) * num_args);
for (i = 0; i < num_args; i++) {
- if (op_array->arg_info[i].name) {
- ADD_INTERNED_STRING(op_array->arg_info[i].name, 1);
+ if (arg_info[i].name) {
+ ADD_INTERNED_STRING(arg_info[i].name, 1);
}
- if (op_array->arg_info[i].class_name) {
- ADD_INTERNED_STRING(op_array->arg_info[i].class_name, 1);
+ if (arg_info[i].class_name) {
+ ADD_INTERNED_STRING(arg_info[i].class_name, 1);
}
-
}
}
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index 74d9f0c11f..1a4c1c3052 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -5067,7 +5067,7 @@ PHP_FUNCTION(openssl_digest)
}
/* }}} */
-static zend_bool php_openssl_validate_iv(char **piv, int *piv_len, int iv_required_len)
+static zend_bool php_openssl_validate_iv(char **piv, size_t *piv_len, size_t iv_required_len)
{
char *iv_new;
@@ -5078,7 +5078,7 @@ static zend_bool php_openssl_validate_iv(char **piv, int *piv_len, int iv_requir
iv_new = ecalloc(1, iv_required_len + 1);
- if (*piv_len <= 0) {
+ if (*piv_len == 0) {
/* BC behavior */
*piv_len = iv_required_len;
*piv = iv_new;
@@ -5134,10 +5134,10 @@ PHP_FUNCTION(openssl_encrypt)
}
max_iv_len = EVP_CIPHER_iv_length(cipher_type);
- if (iv_len <= 0 && max_iv_len > 0) {
+ if (iv_len == 0 && max_iv_len > 0) {
php_error_docref(NULL, E_WARNING, "Using an empty Initialization Vector (iv) is potentially insecure and not recommended");
}
- free_iv = php_openssl_validate_iv(&iv, (int *)&iv_len, max_iv_len);
+ free_iv = php_openssl_validate_iv(&iv, &iv_len, max_iv_len);
outlen = data_len + EVP_CIPHER_block_size(cipher_type);
outbuf = zend_string_alloc(outlen, 0);
@@ -5230,7 +5230,7 @@ PHP_FUNCTION(openssl_decrypt)
key = (unsigned char*)password;
}
- free_iv = php_openssl_validate_iv(&iv, (int *)&iv_len, EVP_CIPHER_iv_length(cipher_type));
+ free_iv = php_openssl_validate_iv(&iv, &iv_len, EVP_CIPHER_iv_length(cipher_type));
outlen = data_len + EVP_CIPHER_block_size(cipher_type);
outbuf = zend_string_alloc(outlen, 0);
diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c
index c95ec0d81e..5dddd05cea 100644
--- a/ext/reflection/php_reflection.c
+++ b/ext/reflection/php_reflection.c
@@ -907,6 +907,24 @@ static void _function_string(string *str, zend_function *fptr, zend_class_entry
}
_function_parameter_string(str, fptr, param_indent.buf->val);
string_free(&param_indent);
+ if (fptr->op_array.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ string_printf(str, " %s- Return [ ", indent);
+ if (fptr->common.arg_info[-1].class_name) {
+ string_printf(str, "%s ",
+ (fptr->type == ZEND_INTERNAL_FUNCTION) ?
+ ((zend_internal_arg_info*)(fptr->common.arg_info - 1))->class_name :
+ fptr->common.arg_info[-1].class_name->val);
+ if (fptr->common.arg_info[-1].allow_null) {
+ string_printf(str, "or NULL ");
+ }
+ } else if (fptr->common.arg_info[-1].type_hint) {
+ string_printf(str, "%s ", zend_get_type_by_const(fptr->common.arg_info[-1].type_hint));
+ if (fptr->common.arg_info[-1].allow_null) {
+ string_printf(str, "or NULL ");
+ }
+ }
+ string_printf(str, "]\n");
+ }
string_printf(str, "%s}\n", indent);
}
/* }}} */
diff --git a/ext/session/session.c b/ext/session/session.c
index ab328573be..7d78b91e54 100644
--- a/ext/session/session.c
+++ b/ext/session/session.c
@@ -1401,6 +1401,7 @@ static void ppid2sid(zval *ppid) {
PHPAPI void php_session_reset_id(void) /* {{{ */
{
int module_number = PS(module_number);
+ zval *sid;
if (!PS(id)) {
php_error_docref(NULL, E_WARNING, "Cannot set session ID - session ID is not initialized");
@@ -1413,7 +1414,9 @@ PHPAPI void php_session_reset_id(void) /* {{{ */
}
/* if the SID constant exists, destroy it. */
- zend_hash_str_del(EG(zend_constants), "sid", sizeof("sid") - 1);
+ /* We must not delete any items in EG(zend_contants) */
+ /* zend_hash_str_del(EG(zend_constants), "sid", sizeof("sid") - 1); */
+ sid = zend_get_constant_str("SID", sizeof("SID") - 1);
if (PS(define_sid)) {
smart_str var = {0};
@@ -1422,10 +1425,20 @@ PHPAPI void php_session_reset_id(void) /* {{{ */
smart_str_appendc(&var, '=');
smart_str_appends(&var, PS(id)->val);
smart_str_0(&var);
- REGISTER_STRINGL_CONSTANT("SID", var.s->val, var.s->len, 0);
- smart_str_free(&var);
+ if (sid) {
+ zend_string_release(Z_STR_P(sid));
+ ZVAL_STR(sid, var.s);
+ } else {
+ REGISTER_STRINGL_CONSTANT("SID", var.s->val, var.s->len, 0);
+ smart_str_free(&var);
+ }
} else {
- REGISTER_STRINGL_CONSTANT("SID", "", 0, 0);
+ if (sid) {
+ zend_string_release(Z_STR_P(sid));
+ ZVAL_EMPTY_STRING(sid);
+ } else {
+ REGISTER_STRINGL_CONSTANT("SID", "", 0, 0);
+ }
}
if (PS(apply_trans_sid)) {
diff --git a/ext/session/tests/session_regenerate_id_fastshutdown.phpt b/ext/session/tests/session_regenerate_id_fastshutdown.phpt
new file mode 100644
index 0000000000..961965fb74
--- /dev/null
+++ b/ext/session/tests/session_regenerate_id_fastshutdown.phpt
@@ -0,0 +1,14 @@
+--TEST--
+Test session_regenerate_id() function : basic functionality
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--INI--
+opcache.fast_shutdown=1
+--FILE--
+<?php
+session_start();
+define ("user", "foo");
+var_dump(session_regenerate_id());
+?>
+--EXPECT--
+bool(true)