diff options
68 files changed, 1431 insertions, 183 deletions
@@ -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(¶m_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) |