diff options
76 files changed, 3333 insertions, 526 deletions
diff --git a/Zend/tests/generators/generator_return_without_value.phpt b/Zend/tests/generators/generator_return_without_value.phpt new file mode 100644 index 0000000000..dfea5aa78d --- /dev/null +++ b/Zend/tests/generators/generator_return_without_value.phpt @@ -0,0 +1,43 @@ +--TEST-- +Generators can return without values +--FILE-- +<?php + +function gen() { + yield; + return; +} + +function gen2() { + yield; + return null; +} + +function gen3() { + return; + yield; +} + +function gen4() { + return; + yield; +} + +var_dump(gen()); + +var_dump(gen2()); + +var_dump(gen3()); + +var_dump(gen4()); + +?> +--EXPECTF-- +object(Generator)#%d (0) { +} +object(Generator)#%d (0) { +} +object(Generator)#%d (0) { +} +object(Generator)#%d (0) { +}
\ No newline at end of file diff --git a/Zend/tests/is_a.phpt b/Zend/tests/is_a.phpt index 0a01eb756e..f4161f2390 100644 --- a/Zend/tests/is_a.phpt +++ b/Zend/tests/is_a.phpt @@ -12,21 +12,21 @@ function __autoload($name) { class BASE { } -interface INT { +interface I { } -class A extends BASE implements INT { +class A extends BASE implements I { } $a = new A; var_dump(is_a($a, "B1")); var_dump(is_a($a, "A")); var_dump(is_a($a, "BASE")); -var_dump(is_a($a, "INT")); +var_dump(is_a($a, "I")); var_dump(is_subclass_of($a, "B2")); var_dump(is_subclass_of($a, "A")); var_dump(is_subclass_of($a, "BASE")); -var_dump(is_subclass_of($a, "INT")); +var_dump(is_subclass_of($a, "I")); var_dump(is_subclass_of("X1", "X2")); ?> diff --git a/Zend/tests/return_types/return_reference_separation.phpt b/Zend/tests/return_types/return_reference_separation.phpt new file mode 100644 index 0000000000..2ec0939088 --- /dev/null +++ b/Zend/tests/return_types/return_reference_separation.phpt @@ -0,0 +1,34 @@ +--TEST-- +Return value separation + +--FILE-- +<?php +function test1(&$abc) : string { + return $abc; +} + +function &test2(int $abc) : string { + return $abc; +} + +function &test3(int &$abc) : string { + return $abc; +} + +$a = 123; + +var_dump(test1($a)); +var_dump($a); +var_dump(test2($a)); +var_dump($a); +var_dump(test3($a)); +var_dump($a); + +?> +--EXPECTF-- +string(3) "123" +int(123) +string(3) "123" +int(123) +string(3) "123" +string(3) "123"
\ No newline at end of file diff --git a/Zend/tests/return_types/rfc002.phpt b/Zend/tests/return_types/rfc002.phpt index 7ccef60d3d..2f3ff02b1c 100644 --- a/Zend/tests/return_types/rfc002.phpt +++ b/Zend/tests/return_types/rfc002.phpt @@ -1,5 +1,5 @@ --TEST-- -RFC example: expected class int and returned integer +RFC example: Scalar Types --FILE-- <?php @@ -7,7 +7,7 @@ function answer(): int { return 42; } -answer(); +var_dump(answer()); --EXPECTF-- -Fatal error: Return value of answer() must be an instance of int, integer returned in %s on line %d +int(42) diff --git a/Zend/tests/typehints/default_boolean_hint_values.phpt b/Zend/tests/typehints/default_boolean_hint_values.phpt new file mode 100644 index 0000000000..6ba262848d --- /dev/null +++ b/Zend/tests/typehints/default_boolean_hint_values.phpt @@ -0,0 +1,16 @@ +--TEST-- +Default values for boolean hints should work +--FILE-- +<?php + +function foo(bool $x = true, bool $y = false) { + var_dump($x, $y); +} + + + +foo(); +?> +--EXPECTF-- +bool(true) +bool(false) diff --git a/Zend/tests/typehints/explicit_weak_include_strict.phpt b/Zend/tests/typehints/explicit_weak_include_strict.phpt new file mode 100644 index 0000000000..a5f267cbef --- /dev/null +++ b/Zend/tests/typehints/explicit_weak_include_strict.phpt @@ -0,0 +1,14 @@ +--TEST-- +explicitly strict_types=0 code including strict_types=1 code +--FILE-- +<?php + +declare(strict_types=0); + +// file with strict_types=1 +require 'weak_include_strict_2.inc'; + +// calls within that file should stay strict, despite being included by weak file +?> +--EXPECTF-- +Fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sweak_include_strict_2.inc on line 9 and defined in %sweak_include_strict_2.inc on line 5 diff --git a/Zend/tests/typehints/internal_function_strict_mode.phpt b/Zend/tests/typehints/internal_function_strict_mode.phpt new file mode 100644 index 0000000000..3e5629af95 --- /dev/null +++ b/Zend/tests/typehints/internal_function_strict_mode.phpt @@ -0,0 +1,35 @@ +--TEST-- +Scalar type hint - internal function strict mode +--FILE-- +<?php +declare(strict_types=1); + +echo "*** Trying Ord With Integer" . PHP_EOL; +try { + var_dump(ord(1)); +} catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; +} + +echo "*** Trying Array Map With Invalid Callback" . PHP_EOL; +try { + array_map([null, "bar"], []); +} catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; +} + +echo "*** Trying Strlen With Float" . PHP_EOL; +try { + var_dump(strlen(1.5)); +} catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; +} + +?> +--EXPECTF-- +*** Trying Ord With Integer +*** Caught ord() expects parameter 1 to be string, integer given +*** Trying Array Map With Invalid Callback +*** Caught array_map() expects parameter 1 to be a valid callback, first array member is not a valid class name or object +*** Trying Strlen With Float +*** Caught strlen() expects parameter 1 to be string, float given
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_basic.phpt b/Zend/tests/typehints/scalar_basic.phpt new file mode 100644 index 0000000000..08a0121bbe --- /dev/null +++ b/Zend/tests/typehints/scalar_basic.phpt @@ -0,0 +1,277 @@ +--TEST-- +Scalar type hint basics +--FILE-- +<?php + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function (int $i) { return $i; }, + 'float' => function (float $f) { return $f; }, + 'string' => function (string $s) { return $s; }, + 'bool' => function (bool $b) { return $b; } +]; + +class Stringable { + public function __toString() { + return "foobar"; + } +} + +$values = [ + 1, + "1", + 1.0, + 1.5, + "1a", + "a", + "", + PHP_INT_MAX, + NAN, + TRUE, + FALSE, + NULL, + [], + new StdClass, + new Stringable, + fopen("data:text/plain,foobar", "r") +]; + +foreach ($functions as $type => $function) { + echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL; + foreach ($values as $value) { + echo PHP_EOL . "*** Trying "; + var_dump($value); + try { + var_dump($function($value)); + } catch (\TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } + } +} +echo PHP_EOL . "Done"; +?> +--EXPECTF-- + +Testing 'int' typehint: + +*** Trying int(1) +int(1) + +*** Trying string(1) "1" +int(1) + +*** Trying float(1) +int(1) + +*** Trying float(1.5) +int(1) + +*** Trying string(2) "1a" +E_NOTICE: A non well formed numeric value encountered on line %d +int(1) + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying int(%d) +int(%d) + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying bool(true) +int(1) + +*** Trying bool(false) +int(0) + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type integer, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, array given, called in %s on line %d + +*** Trying object(stdClass)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying object(Stringable)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying resource(%d) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type integer, resource given, called in %s on line %d + +Testing 'float' typehint: + +*** Trying int(1) +float(1) + +*** Trying string(1) "1" +float(1) + +*** Trying float(1) +float(1) + +*** Trying float(1.5) +float(1.5) + +*** Trying string(2) "1a" +E_NOTICE: A non well formed numeric value encountered on line %d +float(1) + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying int(%d) +float(%s) + +*** Trying float(NAN) +float(NAN) + +*** Trying bool(true) +float(1) + +*** Trying bool(false) +float(0) + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type float, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, array given, called in %s on line %d + +*** Trying object(stdClass)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying object(Stringable)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying resource(%d) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type float, resource given, called in %s on line %d + +Testing 'string' typehint: + +*** Trying int(1) +string(1) "1" + +*** Trying string(1) "1" +string(1) "1" + +*** Trying float(1) +string(1) "1" + +*** Trying float(1.5) +string(3) "1.5" + +*** Trying string(2) "1a" +string(2) "1a" + +*** Trying string(1) "a" +string(1) "a" + +*** Trying string(0) "" +string(0) "" + +*** Trying int(%d) +string(%d) "%d" + +*** Trying float(NAN) +string(3) "NAN" + +*** Trying bool(true) +string(1) "1" + +*** Trying bool(false) +string(0) "" + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type string, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, array given, called in %s on line %d + +*** Trying object(stdClass)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d + +*** Trying object(Stringable)#%s (0) { +} +string(6) "foobar" + +*** Trying resource(%d) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type string, resource given, called in %s on line %d + +Testing 'bool' typehint: + +*** Trying int(1) +bool(true) + +*** Trying string(1) "1" +bool(true) + +*** Trying float(1) +bool(true) + +*** Trying float(1.5) +bool(true) + +*** Trying string(2) "1a" +bool(true) + +*** Trying string(1) "a" +bool(true) + +*** Trying string(0) "" +bool(false) + +*** Trying int(%d) +bool(true) + +*** Trying float(NAN) +bool(true) + +*** Trying bool(true) +bool(true) + +*** Trying bool(false) +bool(false) + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type boolean, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, array given, called in %s on line %d + +*** Trying object(stdClass)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying object(Stringable)#%s (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying resource(%d) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, resource given, called in %s on line %d + +Done diff --git a/Zend/tests/typehints/scalar_constant_defaults.phpt b/Zend/tests/typehints/scalar_constant_defaults.phpt new file mode 100644 index 0000000000..4441022e16 --- /dev/null +++ b/Zend/tests/typehints/scalar_constant_defaults.phpt @@ -0,0 +1,83 @@ +--TEST-- +Scalar type hint - default via constants +--FILE-- +<?php + +const INT_VAL = 10; +const FLOAT_VAL = 10.5; +const STRING_VAL = "this is a test"; +const INT_ADD_VAL = 10 + 15; +const FLOAT_ADD_VAL = 10.5 + 0.2; +const STRING_ADD_VAL = "this" . " is a test"; +const NULL_VAL = null; + +function int_val(int $a = INT_VAL): int { + return $a; +} + +function float_val(float $a = FLOAT_VAL): float { + return $a; +} + +function string_val(string $a = STRING_VAL): string { + return $a; +} + +function int_add_val(int $a = INT_ADD_VAL): int { + return $a; +} + +function float_add_val(float $a = FLOAT_ADD_VAL): float { + return $a; +} + +function string_add_val(string $a = STRING_ADD_VAL): string { + return $a; +} + +function int_val_default_null(int $a = NULL_VAL) { + return $a; +} + +echo "Testing int val" . PHP_EOL; +var_dump(int_val()); + +echo "Testing float val" . PHP_EOL; +var_dump(float_val()); + +echo "Testing string val" . PHP_EOL; +var_dump(string_val()); + +echo "Testing int add val" . PHP_EOL; +var_dump(int_add_val()); + +echo "Testing float add val" . PHP_EOL; +var_dump(float_add_val()); + +echo "Testing string add val" . PHP_EOL; +var_dump(string_add_val()); + +echo "Testing int with default null constant" . PHP_EOL; +var_dump(int_val_default_null()); + +echo "Testing int with null null constant" . PHP_EOL; +var_dump(int_val_default_null(null)); + +?> +--EXPECTF-- +Testing int val +int(10) +Testing float val +float(10.5) +Testing string val +string(14) "this is a test" +Testing int add val +int(25) +Testing float add val +float(10.7) +Testing string add val +string(14) "this is a test" +Testing int with default null constant +NULL +Testing int with null null constant +NULL
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_constant_defaults_error.phpt b/Zend/tests/typehints/scalar_constant_defaults_error.phpt new file mode 100644 index 0000000000..53938d724f --- /dev/null +++ b/Zend/tests/typehints/scalar_constant_defaults_error.phpt @@ -0,0 +1,16 @@ +--TEST-- +Scalar type hint - default via constants - error condition +--FILE-- +<?php + +const STRING_VAL = "test"; + +function int_val(int $a = STRING_VAL): int { + return $a; +} + +var_dump(int_val()); + +?> +--EXPECTF-- +Fatal error: Argument 1 passed to int_val() must be of the type integer, string given, called in %s on line %d and defined in %s on line %d
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_none.phpt b/Zend/tests/typehints/scalar_none.phpt new file mode 100644 index 0000000000..af6d8c8dfa --- /dev/null +++ b/Zend/tests/typehints/scalar_none.phpt @@ -0,0 +1,54 @@ +--TEST-- +Scalar type hint missing parameters +--FILE-- +<?php + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function (int $i) { return $i; }, + 'float' => function (float $f) { return $f; }, + 'string' => function (string $s) { return $s; }, + 'bool' => function (bool $b) { return $b; }, + 'int nullable' => function (int $i = NULL) { return $i; }, + 'float nullable' => function (float $f = NULL) { return $f; }, + 'string nullable' => function (string $s = NULL) { return $s; }, + 'bool nullable' => function (bool $b = NULL) { return $b; } +]; + +foreach ($functions as $type => $function) { + echo "Testing $type:", PHP_EOL; + try { + var_dump($function()); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } +} +echo PHP_EOL . "Done"; +--EXPECTF-- +Testing int: +*** Caught Argument 1 passed to {closure}() must be of the type integer, none given, called in %s on line %d +Testing float: +*** Caught Argument 1 passed to {closure}() must be of the type float, none given, called in %s on line %d +Testing string: +*** Caught Argument 1 passed to {closure}() must be of the type string, none given, called in %s on line %d +Testing bool: +*** Caught Argument 1 passed to {closure}() must be of the type boolean, none given, called in %s on line %d +Testing int nullable: +NULL +Testing float nullable: +NULL +Testing string nullable: +NULL +Testing bool nullable: +NULL + +Done diff --git a/Zend/tests/typehints/scalar_null.phpt b/Zend/tests/typehints/scalar_null.phpt new file mode 100644 index 0000000000..e149388ef0 --- /dev/null +++ b/Zend/tests/typehints/scalar_null.phpt @@ -0,0 +1,56 @@ +--TEST-- +Scalar type hint nullability +--FILE-- +<?php + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function (int $i) { return $i; }, + 'float' => function (float $f) { return $f; }, + 'string' => function (string $s) { return $s; }, + 'bool' => function (bool $b) { return $b; }, + 'int nullable' => function (int $i = NULL) { return $i; }, + 'float nullable' => function (float $f = NULL) { return $f; }, + 'string nullable' => function (string $s = NULL) { return $s; }, + 'bool nullable' => function (bool $b = NULL) { return $b; } +]; + +foreach ($functions as $type => $function) { + echo "Testing $type:", PHP_EOL; + try { + var_dump($function(null)); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } +} + +echo PHP_EOL . "Done"; +?> +--EXPECTF-- +Testing int: +*** Caught Argument 1 passed to {closure}() must be of the type integer, null given, called in %s on line %d +Testing float: +*** Caught Argument 1 passed to {closure}() must be of the type float, null given, called in %s on line %d +Testing string: +*** Caught Argument 1 passed to {closure}() must be of the type string, null given, called in %s on line %d +Testing bool: +*** Caught Argument 1 passed to {closure}() must be of the type boolean, null given, called in %s on line %d +Testing int nullable: +NULL +Testing float nullable: +NULL +Testing string nullable: +NULL +Testing bool nullable: +NULL + +Done diff --git a/Zend/tests/typehints/scalar_relative_typehint_disallowed.phpt b/Zend/tests/typehints/scalar_relative_typehint_disallowed.phpt new file mode 100644 index 0000000000..30d2bd8b74 --- /dev/null +++ b/Zend/tests/typehints/scalar_relative_typehint_disallowed.phpt @@ -0,0 +1,14 @@ +--TEST-- +Scalar type hint - disallow relative typehints +--FILE-- +<?php + +function foo(bar\int $a): int { + return $a; +} + +foo(10); + +?> +--EXPECTF-- +Fatal error: "bar\int" cannot be used as a type declaration in %s on line %d
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_reserved2.phpt b/Zend/tests/typehints/scalar_reserved2.phpt new file mode 100644 index 0000000000..8ed4102497 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved2.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (2) +--FILE-- +<?php + +class int {} +--EXPECTF-- +Fatal error: "int" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved2_class_alias.phpt b/Zend/tests/typehints/scalar_reserved2_class_alias.phpt new file mode 100644 index 0000000000..2348f76df8 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved2_class_alias.phpt @@ -0,0 +1,9 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (2) - class_alias +--FILE-- +<?php + +class foobar {} +class_alias("foobar", "int"); +--EXPECTF-- +Fatal error: "int" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved2_use.phpt b/Zend/tests/typehints/scalar_reserved2_use.phpt new file mode 100644 index 0000000000..b7a1baed36 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved2_use.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (2) - use +--FILE-- +<?php + +use foobar as int; +--EXPECTF-- +Fatal error: "int" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved3.phpt b/Zend/tests/typehints/scalar_reserved3.phpt new file mode 100644 index 0000000000..00750955b7 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved3.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (3) +--FILE-- +<?php + +class float {} +--EXPECTF-- +Fatal error: "float" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved3_class_alias.phpt b/Zend/tests/typehints/scalar_reserved3_class_alias.phpt new file mode 100644 index 0000000000..05438b579c --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved3_class_alias.phpt @@ -0,0 +1,9 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (3) - class_alias +--FILE-- +<?php + +class foobar {} +class_alias("foobar", "float"); +--EXPECTF-- +Fatal error: "float" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved3_use.phpt b/Zend/tests/typehints/scalar_reserved3_use.phpt new file mode 100644 index 0000000000..6f52e060ff --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved3_use.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (3) - use +--FILE-- +<?php + +use foobar as float; +--EXPECTF-- +Fatal error: "float" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved4.phpt b/Zend/tests/typehints/scalar_reserved4.phpt new file mode 100644 index 0000000000..2e92514b8b --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved4.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (4) +--FILE-- +<?php + +class string {} +--EXPECTF-- +Fatal error: "string" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved4_class_alias.phpt b/Zend/tests/typehints/scalar_reserved4_class_alias.phpt new file mode 100644 index 0000000000..ff7c6adc79 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved4_class_alias.phpt @@ -0,0 +1,9 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (4) - class_alias +--FILE-- +<?php + +class foobar {} +class_alias("foobar", "string"); +--EXPECTF-- +Fatal error: "string" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved4_use.phpt b/Zend/tests/typehints/scalar_reserved4_use.phpt new file mode 100644 index 0000000000..1d7e4039d8 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved4_use.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (4) - use +--FILE-- +<?php + +use foobar as string; +--EXPECTF-- +Fatal error: "string" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved6.phpt b/Zend/tests/typehints/scalar_reserved6.phpt new file mode 100644 index 0000000000..46b8c730e9 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved6.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (6) +--FILE-- +<?php + +class bool {} +--EXPECTF-- +Fatal error: "bool" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved6_class_alias.phpt b/Zend/tests/typehints/scalar_reserved6_class_alias.phpt new file mode 100644 index 0000000000..4a4c824eb6 --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved6_class_alias.phpt @@ -0,0 +1,9 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (6) - class_alias +--FILE-- +<?php + +class foobar {} +class_alias("foobar", "bool"); +--EXPECTF-- +Fatal error: "bool" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved6_use.phpt b/Zend/tests/typehints/scalar_reserved6_use.phpt new file mode 100644 index 0000000000..6a6cb7a34c --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved6_use.phpt @@ -0,0 +1,8 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (6) - use +--FILE-- +<?php + +use foobar as bool; +--EXPECTF-- +Fatal error: "bool" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_reserved7.phpt b/Zend/tests/typehints/scalar_reserved7.phpt new file mode 100644 index 0000000000..330235d9db --- /dev/null +++ b/Zend/tests/typehints/scalar_reserved7.phpt @@ -0,0 +1,9 @@ +--TEST-- +Scalar type hint names cannot be used as class, trait or interface names (7) +--FILE-- +<?php +namespace foo; + +class int {} +--EXPECTF-- +Fatal error: "int" cannot be used as a class name in %s on line %d diff --git a/Zend/tests/typehints/scalar_return_basic.phpt b/Zend/tests/typehints/scalar_return_basic.phpt new file mode 100644 index 0000000000..76b01a8c89 --- /dev/null +++ b/Zend/tests/typehints/scalar_return_basic.phpt @@ -0,0 +1,216 @@ +--TEST-- +Return scalar type hint basics +--SKIPIF-- +<?php if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only"); ?> +--FILE-- +<?php + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function ($i): int { return $i; }, + 'float' => function ($f): float { return $f; }, + 'string' => function ($s): string { return $s; }, + 'bool' => function ($b): bool { return $b; } +]; + +class Stringable { + public function __toString() { + return "foobar"; + } +} + +$values = [ + 1, + "1", + 1.0, + 1.5, + "1a", + "a", + "", + PHP_INT_MAX, + NAN, + TRUE, + FALSE, + NULL, + [], + new StdClass, + new Stringable, + fopen("data:text/plain,foobar", "r") +]; + +foreach ($functions as $type => $function) { + echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL; + foreach ($values as $value) { + echo "*** Trying "; + var_dump($value); + try { + var_dump($function($value)); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } + } +} + +echo PHP_EOL . "Done"; +?> +--EXPECTF-- +Testing 'int' typehint: +*** Trying int(1) +int(1) +*** Trying string(1) "1" +int(1) +*** Trying float(1) +int(1) +*** Trying float(1.5) +int(1) +*** Trying string(2) "1a" +E_NOTICE: A non well formed numeric value encountered on line %d +int(1) +*** Trying string(1) "a" +*** Caught Return value of {closure}() must be of the type integer, string returned in %s on line %d +*** Trying string(0) "" +*** Caught Return value of {closure}() must be of the type integer, string returned in %s on line %d +*** Trying int(2147483647) +int(2147483647) +*** Trying float(NAN) +*** Caught Return value of {closure}() must be of the type integer, float returned in %s on line %d +*** Trying bool(true) +int(1) +*** Trying bool(false) +int(0) +*** Trying NULL +*** Caught Return value of {closure}() must be of the type integer, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type integer, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type integer, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +*** Caught Return value of {closure}() must be of the type integer, object returned in %s on line %d +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type integer, resource returned in %s on line %d + +Testing 'float' typehint: +*** Trying int(1) +float(1) +*** Trying string(1) "1" +float(1) +*** Trying float(1) +float(1) +*** Trying float(1.5) +float(1.5) +*** Trying string(2) "1a" +E_NOTICE: A non well formed numeric value encountered on line %d +float(1) +*** Trying string(1) "a" +*** Caught Return value of {closure}() must be of the type float, string returned in %s on line %d +*** Trying string(0) "" +*** Caught Return value of {closure}() must be of the type float, string returned in %s on line %d +*** Trying int(2147483647) +float(2147483647) +*** Trying float(NAN) +float(NAN) +*** Trying bool(true) +float(1) +*** Trying bool(false) +float(0) +*** Trying NULL +*** Caught Return value of {closure}() must be of the type float, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type float, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type float, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +*** Caught Return value of {closure}() must be of the type float, object returned in %s on line %d +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type float, resource returned in %s on line %d + +Testing 'string' typehint: +*** Trying int(1) +string(1) "1" +*** Trying string(1) "1" +string(1) "1" +*** Trying float(1) +string(1) "1" +*** Trying float(1.5) +string(3) "1.5" +*** Trying string(2) "1a" +string(2) "1a" +*** Trying string(1) "a" +string(1) "a" +*** Trying string(0) "" +string(0) "" +*** Trying int(2147483647) +string(10) "2147483647" +*** Trying float(NAN) +string(3) "NAN" +*** Trying bool(true) +string(1) "1" +*** Trying bool(false) +string(0) "" +*** Trying NULL +*** Caught Return value of {closure}() must be of the type string, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type string, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type string, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +string(6) "foobar" +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type string, resource returned in %s on line %d + +Testing 'bool' typehint: +*** Trying int(1) +bool(true) +*** Trying string(1) "1" +bool(true) +*** Trying float(1) +bool(true) +*** Trying float(1.5) +bool(true) +*** Trying string(2) "1a" +bool(true) +*** Trying string(1) "a" +bool(true) +*** Trying string(0) "" +bool(false) +*** Trying int(2147483647) +bool(true) +*** Trying float(NAN) +bool(true) +*** Trying bool(true) +bool(true) +*** Trying bool(false) +bool(false) +*** Trying NULL +*** Caught Return value of {closure}() must be of the type boolean, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type boolean, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type boolean, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +*** Caught Return value of {closure}() must be of the type boolean, object returned in %s on line %d +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type boolean, resource returned in %s on line %d + +Done diff --git a/Zend/tests/typehints/scalar_return_basic_64bit.phpt b/Zend/tests/typehints/scalar_return_basic_64bit.phpt new file mode 100644 index 0000000000..edb5231dc0 --- /dev/null +++ b/Zend/tests/typehints/scalar_return_basic_64bit.phpt @@ -0,0 +1,216 @@ +--TEST-- +Return scalar type hint basics +--SKIPIF-- +<?php if (PHP_INT_SIZE != 8) die("skip this test is for 64bit platform only"); ?> +--FILE-- +<?php + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function ($i): int { return $i; }, + 'float' => function ($f): float { return $f; }, + 'string' => function ($s): string { return $s; }, + 'bool' => function ($b): bool { return $b; } +]; + +class Stringable { + public function __toString() { + return "foobar"; + } +} + +$values = [ + 1, + "1", + 1.0, + 1.5, + "1a", + "a", + "", + PHP_INT_MAX, + NAN, + TRUE, + FALSE, + NULL, + [], + new StdClass, + new Stringable, + fopen("data:text/plain,foobar", "r") +]; + +foreach ($functions as $type => $function) { + echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL; + foreach ($values as $value) { + echo "*** Trying "; + var_dump($value); + try { + var_dump($function($value)); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } + } +} + +echo PHP_EOL . "Done"; +?> +--EXPECTF-- +Testing 'int' typehint: +*** Trying int(1) +int(1) +*** Trying string(1) "1" +int(1) +*** Trying float(1) +int(1) +*** Trying float(1.5) +int(1) +*** Trying string(2) "1a" +E_NOTICE: A non well formed numeric value encountered on line %d +int(1) +*** Trying string(1) "a" +*** Caught Return value of {closure}() must be of the type integer, string returned in %s on line %d +*** Trying string(0) "" +*** Caught Return value of {closure}() must be of the type integer, string returned in %s on line %d +*** Trying int(9223372036854775807) +int(9223372036854775807) +*** Trying float(NAN) +*** Caught Return value of {closure}() must be of the type integer, float returned in %s on line %d +*** Trying bool(true) +int(1) +*** Trying bool(false) +int(0) +*** Trying NULL +*** Caught Return value of {closure}() must be of the type integer, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type integer, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type integer, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +*** Caught Return value of {closure}() must be of the type integer, object returned in %s on line %d +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type integer, resource returned in %s on line %d + +Testing 'float' typehint: +*** Trying int(1) +float(1) +*** Trying string(1) "1" +float(1) +*** Trying float(1) +float(1) +*** Trying float(1.5) +float(1.5) +*** Trying string(2) "1a" +E_NOTICE: A non well formed numeric value encountered on line %d +float(1) +*** Trying string(1) "a" +*** Caught Return value of {closure}() must be of the type float, string returned in %s on line %d +*** Trying string(0) "" +*** Caught Return value of {closure}() must be of the type float, string returned in %s on line %d +*** Trying int(9223372036854775807) +float(9.2233720368548E+18) +*** Trying float(NAN) +float(NAN) +*** Trying bool(true) +float(1) +*** Trying bool(false) +float(0) +*** Trying NULL +*** Caught Return value of {closure}() must be of the type float, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type float, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type float, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +*** Caught Return value of {closure}() must be of the type float, object returned in %s on line %d +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type float, resource returned in %s on line %d + +Testing 'string' typehint: +*** Trying int(1) +string(1) "1" +*** Trying string(1) "1" +string(1) "1" +*** Trying float(1) +string(1) "1" +*** Trying float(1.5) +string(3) "1.5" +*** Trying string(2) "1a" +string(2) "1a" +*** Trying string(1) "a" +string(1) "a" +*** Trying string(0) "" +string(0) "" +*** Trying int(9223372036854775807) +string(19) "9223372036854775807" +*** Trying float(NAN) +string(3) "NAN" +*** Trying bool(true) +string(1) "1" +*** Trying bool(false) +string(0) "" +*** Trying NULL +*** Caught Return value of {closure}() must be of the type string, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type string, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type string, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +string(6) "foobar" +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type string, resource returned in %s on line %d + +Testing 'bool' typehint: +*** Trying int(1) +bool(true) +*** Trying string(1) "1" +bool(true) +*** Trying float(1) +bool(true) +*** Trying float(1.5) +bool(true) +*** Trying string(2) "1a" +bool(true) +*** Trying string(1) "a" +bool(true) +*** Trying string(0) "" +bool(false) +*** Trying int(9223372036854775807) +bool(true) +*** Trying float(NAN) +bool(true) +*** Trying bool(true) +bool(true) +*** Trying bool(false) +bool(false) +*** Trying NULL +*** Caught Return value of {closure}() must be of the type boolean, null returned in %s on line %d +*** Trying array(0) { +} +*** Caught Return value of {closure}() must be of the type boolean, array returned in %s on line %d +*** Trying object(stdClass)#6 (0) { +} +*** Caught Return value of {closure}() must be of the type boolean, object returned in %s on line %d +*** Trying object(Stringable)#7 (0) { +} +*** Caught Return value of {closure}() must be of the type boolean, object returned in %s on line %d +*** Trying resource(5) of type (stream) +*** Caught Return value of {closure}() must be of the type boolean, resource returned in %s on line %d + +Done diff --git a/Zend/tests/typehints/scalar_strict.phpt b/Zend/tests/typehints/scalar_strict.phpt new file mode 100644 index 0000000000..e0f89f95b1 --- /dev/null +++ b/Zend/tests/typehints/scalar_strict.phpt @@ -0,0 +1,279 @@ +--TEST-- +Scalar type hint strict mode +--SKIPIF-- +<?php if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platform only"); ?> +--FILE-- +<?php +declare(strict_types=1); + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function (int $i) { return $i; }, + 'float' => function (float $f) { return $f; }, + 'string' => function (string $s) { return $s; }, + 'bool' => function (bool $b) { return $b; } +]; + +class Stringable { + public function __toString() { + return "foobar"; + } +} + +$values = [ + 1, + "1", + 1.0, + 1.5, + "1a", + "a", + "", + PHP_INT_MAX, + NAN, + TRUE, + FALSE, + NULL, + [], + new StdClass, + new Stringable, + fopen("data:text/plain,foobar", "r") +]; + +foreach ($functions as $type => $function) { + echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL; + foreach ($values as $value) { + echo PHP_EOL . "*** Trying "; + var_dump($value); + try { + var_dump($function($value)); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } + } +} + +echo PHP_EOL . "Done"; +?> +--EXPECTF-- +Testing 'int' typehint: + +*** Trying int(1) +int(1) + +*** Trying string(1) "1" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying float(1) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying float(1.5) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying string(2) "1a" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying int(2147483647) +int(2147483647) + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying bool(true) +*** Caught Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d + +*** Trying bool(false) +*** Caught Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type integer, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type integer, resource given, called in %s on line %d + +Testing 'float' typehint: + +*** Trying int(1) +float(1) + +*** Trying string(1) "1" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying float(1) +float(1) + +*** Trying float(1.5) +float(1.5) + +*** Trying string(2) "1a" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying int(2147483647) +float(2147483647) + +*** Trying float(NAN) +float(NAN) + +*** Trying bool(true) +*** Caught Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d + +*** Trying bool(false) +*** Caught Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type float, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type float, resource given, called in %s on line %d + +Testing 'string' typehint: + +*** Trying int(1) +*** Caught Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d + +*** Trying string(1) "1" +string(1) "1" + +*** Trying float(1) +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying float(1.5) +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying string(2) "1a" +string(2) "1a" + +*** Trying string(1) "a" +string(1) "a" + +*** Trying string(0) "" +string(0) "" + +*** Trying int(2147483647) +*** Caught Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying bool(true) +*** Caught Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d + +*** Trying bool(false) +*** Caught Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type string, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type string, resource given, called in %s on line %d + +Testing 'bool' typehint: + +*** Trying int(1) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d + +*** Trying string(1) "1" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying float(1) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying float(1.5) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying string(2) "1a" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying int(2147483647) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying bool(true) +bool(true) + +*** Trying bool(false) +bool(false) + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type boolean, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, resource given, called in %s on line %d + +Done diff --git a/Zend/tests/typehints/scalar_strict_64bit.phpt b/Zend/tests/typehints/scalar_strict_64bit.phpt new file mode 100644 index 0000000000..da3e1e4546 --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_64bit.phpt @@ -0,0 +1,279 @@ +--TEST-- +Scalar type hint strict mode +--SKIPIF-- +<?php if (PHP_INT_SIZE != 8) die("skip this test is for 64bit platform only"); ?> +--FILE-- +<?php +declare(strict_types=1); + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function (int $i) { return $i; }, + 'float' => function (float $f) { return $f; }, + 'string' => function (string $s) { return $s; }, + 'bool' => function (bool $b) { return $b; } +]; + +class Stringable { + public function __toString() { + return "foobar"; + } +} + +$values = [ + 1, + "1", + 1.0, + 1.5, + "1a", + "a", + "", + PHP_INT_MAX, + NAN, + TRUE, + FALSE, + NULL, + [], + new StdClass, + new Stringable, + fopen("data:text/plain,foobar", "r") +]; + +foreach ($functions as $type => $function) { + echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL; + foreach ($values as $value) { + echo PHP_EOL . "*** Trying "; + var_dump($value); + try { + var_dump($function($value)); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } + } +} + +echo PHP_EOL . "Done"; +?> +--EXPECTF-- +Testing 'int' typehint: + +*** Trying int(1) +int(1) + +*** Trying string(1) "1" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying float(1) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying float(1.5) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying string(2) "1a" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying int(9223372036854775807) +int(9223372036854775807) + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying bool(true) +*** Caught Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d + +*** Trying bool(false) +*** Caught Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type integer, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type integer, resource given, called in %s on line %d + +Testing 'float' typehint: + +*** Trying int(1) +float(1) + +*** Trying string(1) "1" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying float(1) +float(1) + +*** Trying float(1.5) +float(1.5) + +*** Trying string(2) "1a" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying int(9223372036854775807) +float(9.2233720368548E+18) + +*** Trying float(NAN) +float(NAN) + +*** Trying bool(true) +*** Caught Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d + +*** Trying bool(false) +*** Caught Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type float, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type float, resource given, called in %s on line %d + +Testing 'string' typehint: + +*** Trying int(1) +*** Caught Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d + +*** Trying string(1) "1" +string(1) "1" + +*** Trying float(1) +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying float(1.5) +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying string(2) "1a" +string(2) "1a" + +*** Trying string(1) "a" +string(1) "a" + +*** Trying string(0) "" +string(0) "" + +*** Trying int(9223372036854775807) +*** Caught Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying bool(true) +*** Caught Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d + +*** Trying bool(false) +*** Caught Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type string, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type string, resource given, called in %s on line %d + +Testing 'bool' typehint: + +*** Trying int(1) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d + +*** Trying string(1) "1" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying float(1) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying float(1.5) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying string(2) "1a" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying string(1) "a" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying string(0) "" +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying int(9223372036854775807) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d + +*** Trying float(NAN) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying bool(true) +bool(true) + +*** Trying bool(false) +bool(false) + +*** Trying NULL +*** Caught Argument 1 passed to {closure}() must be of the type boolean, null given, called in %s on line %d + +*** Trying array(0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, array given, called in %s on line %d + +*** Trying object(stdClass)#6 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying object(Stringable)#7 (0) { +} +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying resource(5) of type (stream) +*** Caught Argument 1 passed to {closure}() must be of the type boolean, resource given, called in %s on line %d + +Done diff --git a/Zend/tests/typehints/scalar_strict_basic.phpt b/Zend/tests/typehints/scalar_strict_basic.phpt new file mode 100644 index 0000000000..d56af159d7 --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_basic.phpt @@ -0,0 +1,181 @@ +--TEST-- +Strict scalar type hint basics +--FILE-- +<?php + +declare(strict_types=1); + +$errnames = [ + E_NOTICE => 'E_NOTICE', + E_WARNING => 'E_WARNING', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR' +]; + +set_error_handler(function (int $errno, string $errmsg, string $file, int $line) use ($errnames) { + echo "$errnames[$errno]: $errmsg on line $line\n"; + return true; +}); + +$functions = [ + 'int' => function (int $i) { return $i; }, + 'float' => function (float $f) { return $f; }, + 'string' => function (string $s) { return $s; }, + 'bool' => function (bool $b) { return $b; } +]; + +$values = [ + 1, + 1.0, + "1", + TRUE, + FALSE, + NULL, + [], + new StdClass, + fopen("data:text/plain,foobar", "r") +]; + +function type($value) { + if (is_float($value)) { + return "float"; + } else if (is_bool($value)) { + return $value ? "true" : "false"; + } else if (is_null($value)) { + return "null"; + } else { + return gettype($value); + } +} + +foreach ($functions as $type => $function) { + echo PHP_EOL, "Testing '$type' typehint:", PHP_EOL; + foreach ($values as $value) { + $errored = false; + echo PHP_EOL . "*** Trying ", type($value), " value", PHP_EOL; + try { + var_dump($function($value)); + } catch (TypeException $e) { + echo "*** Caught " . $e->getMessage() . PHP_EOL; + } + } +} +echo PHP_EOL . "Done"; +?> +--EXPECTF-- +Testing 'int' typehint: + +*** Trying integer value +int(1) + +*** Trying float value +*** Caught Argument 1 passed to {closure}() must be of the type integer, float given, called in %s on line %d + +*** Trying string value +*** Caught Argument 1 passed to {closure}() must be of the type integer, string given, called in %s on line %d + +*** Trying true value +*** Caught Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d + +*** Trying false value +*** Caught Argument 1 passed to {closure}() must be of the type integer, boolean given, called in %s on line %d + +*** Trying null value +*** Caught Argument 1 passed to {closure}() must be of the type integer, null given, called in %s on line %d + +*** Trying array value +*** Caught Argument 1 passed to {closure}() must be of the type integer, array given, called in %s on line %d + +*** Trying object value +*** Caught Argument 1 passed to {closure}() must be of the type integer, object given, called in %s on line %d + +*** Trying resource value +*** Caught Argument 1 passed to {closure}() must be of the type integer, resource given, called in %s on line %d + +Testing 'float' typehint: + +*** Trying integer value +float(1) + +*** Trying float value +float(1) + +*** Trying string value +*** Caught Argument 1 passed to {closure}() must be of the type float, string given, called in %s on line %d + +*** Trying true value +*** Caught Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d + +*** Trying false value +*** Caught Argument 1 passed to {closure}() must be of the type float, boolean given, called in %s on line %d + +*** Trying null value +*** Caught Argument 1 passed to {closure}() must be of the type float, null given, called in %s on line %d + +*** Trying array value +*** Caught Argument 1 passed to {closure}() must be of the type float, array given, called in %s on line %d + +*** Trying object value +*** Caught Argument 1 passed to {closure}() must be of the type float, object given, called in %s on line %d + +*** Trying resource value +*** Caught Argument 1 passed to {closure}() must be of the type float, resource given, called in %s on line %d + +Testing 'string' typehint: + +*** Trying integer value +*** Caught Argument 1 passed to {closure}() must be of the type string, integer given, called in %s on line %d + +*** Trying float value +*** Caught Argument 1 passed to {closure}() must be of the type string, float given, called in %s on line %d + +*** Trying string value +string(1) "1" + +*** Trying true value +*** Caught Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d + +*** Trying false value +*** Caught Argument 1 passed to {closure}() must be of the type string, boolean given, called in %s on line %d + +*** Trying null value +*** Caught Argument 1 passed to {closure}() must be of the type string, null given, called in %s on line %d + +*** Trying array value +*** Caught Argument 1 passed to {closure}() must be of the type string, array given, called in %s on line %d + +*** Trying object value +*** Caught Argument 1 passed to {closure}() must be of the type string, object given, called in %s on line %d + +*** Trying resource value +*** Caught Argument 1 passed to {closure}() must be of the type string, resource given, called in %s on line %d + +Testing 'bool' typehint: + +*** Trying integer value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, integer given, called in %s on line %d + +*** Trying float value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, float given, called in %s on line %d + +*** Trying string value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, string given, called in %s on line %d + +*** Trying true value +bool(true) + +*** Trying false value +bool(false) + +*** Trying null value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, null given, called in %s on line %d + +*** Trying array value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, array given, called in %s on line %d + +*** Trying object value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, object given, called in %s on line %d + +*** Trying resource value +*** Caught Argument 1 passed to {closure}() must be of the type boolean, resource given, called in %s on line %d + +Done diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_001.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_001.phpt new file mode 100644 index 0000000000..2a2b3e6fd3 --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test strict declaration being first operation only +--FILE-- +<?php + +function takes_int(int $x) { + global $errored; + if ($errored) { + echo "Failure!", PHP_EOL; + $errored = FALSE; + } else { + echo "Success!", PHP_EOL; + } +} + +?> +<?php + +declare(strict_types=1); +var_dump(takes_int(32)); + +?> +--EXPECTF-- +Fatal error: strict_types declaration must be the very first statement in the script in %s on line %d diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_002.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_002.phpt new file mode 100644 index 0000000000..09ac1119ab --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_002.phpt @@ -0,0 +1,14 @@ +--TEST-- +Test strict declaration being first operation only 002 +--FILE-- +<?php + +namespace Foo; + +declare(strict_types=1); + +var_dump(strlen("abc")); + +?> +--EXPECTF-- +Fatal error: strict_types declaration must be the very first statement in the script in %s on line %d diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_003.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_003.phpt new file mode 100644 index 0000000000..6ec5b59b98 --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_003.phpt @@ -0,0 +1,11 @@ +--TEST-- +Test strict declaration being first operation only 003 +--FILE-- +hi<?php + +declare(strict_types=1); +var_dump(strlen(32)); + +?> +--EXPECTF-- +Fatal error: strict_types declaration must be the very first statement in the script in %s on line %d diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_004.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_004.phpt new file mode 100644 index 0000000000..60003241cf --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_004.phpt @@ -0,0 +1,17 @@ +--TEST-- +Test strict declaration being first operation only 004 +--FILE-- +<?php +declare(strict_types=1); + +namespace Foo { + function add1(int $arg): int { + return $arg + 1; + } +} + +namespace { + var_dump(Foo\add1(123)); +} +--EXPECT-- +int(124) diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_005.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_005.phpt new file mode 100644 index 0000000000..8611a205cf --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_005.phpt @@ -0,0 +1,14 @@ +--TEST-- +Test strict declaration being first operation only 005 +--FILE-- +<?php + +declare(strict_types=1); + +namespace Foo; + +var_dump(strlen("abc")); + +?> +--EXPECTF-- +int(3)
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_006.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_006.phpt new file mode 100644 index 0000000000..2af05b6c65 --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_006.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test strict declaration being first operation only 006 +--SKIPIF-- +<?php +if (!in_array("zend.detect_unicode", array_keys(ini_get_all()))) { + die("skip Requires configure --enable-zend-multibyte option"); +} +if (!extension_loaded("mbstring")) { + die("skip Requires mbstring extension"); +} +?> +--INI-- +zend.multibyte=1 +--FILE-- +<?php +declare(encoding="UTF-8"); +declare(strict_types=1); + +namespace Foo; + +var_dump(strlen("abc")); + +?> +--EXPECTF-- +int(3)
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_007.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_007.phpt new file mode 100644 index 0000000000..3e4c693afb --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_007.phpt @@ -0,0 +1,25 @@ +--TEST-- +Test strict declaration being first operation only 007 +--SKIPIF-- +<?php +if (!in_array("zend.detect_unicode", array_keys(ini_get_all()))) { + die("skip Requires configure --enable-zend-multibyte option"); +} +if (!extension_loaded("mbstring")) { + die("skip Requires mbstring extension"); +} +?> +--INI-- +zend.multibyte=1 +--FILE-- +<?php +declare(strict_types=1); +declare(encoding="ISO-8859-1"); + +namespace Foo; + +var_dump(strlen("abc")); + +?> +--EXPECTF-- +int(3)
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_strict_declaration_placement_008.phpt b/Zend/tests/typehints/scalar_strict_declaration_placement_008.phpt new file mode 100644 index 0000000000..87a09af96c --- /dev/null +++ b/Zend/tests/typehints/scalar_strict_declaration_placement_008.phpt @@ -0,0 +1,11 @@ +--TEST-- +Test strict declaration block declaration 008 +--FILE-- +<?php +declare(strict_types=1) { + var_dump(strlen("abc")); +} + +?> +--EXPECTF-- +Fatal error: strict_types declaration must not use block mode in %s on line %d
\ No newline at end of file diff --git a/Zend/tests/typehints/scalar_weak_reference.phpt b/Zend/tests/typehints/scalar_weak_reference.phpt new file mode 100644 index 0000000000..f397ceaa8d --- /dev/null +++ b/Zend/tests/typehints/scalar_weak_reference.phpt @@ -0,0 +1,29 @@ +--TEST-- +Weak scalar type hints, with references +--FILE-- +<?php + +// implicitly weak mode code + +function to_int(int &$x) {} +function to_float(float &$x) {} +function to_string(string &$x) {} +function to_bool(bool &$x) {} + +$x = 1.0; +var_dump($x); +to_int($x); // because $x is by-reference, the weak type hint converts it +var_dump($x); +to_float($x); +var_dump($x); +to_string($x); +var_dump($x); +to_bool($x); +var_dump($x); +?> +--EXPECT-- +float(1) +int(1) +float(1) +string(1) "1" +bool(true) diff --git a/Zend/tests/typehints/strict_call_weak.phpt b/Zend/tests/typehints/strict_call_weak.phpt new file mode 100644 index 0000000000..94d5953f2b --- /dev/null +++ b/Zend/tests/typehints/strict_call_weak.phpt @@ -0,0 +1,17 @@ +--TEST-- +strict_types=1 code calling strict_types=0 code +--FILE-- +<?php + +declare(strict_types=1); + +// file that's implicitly weak +require 'strict_call_weak_2.inc'; + +// Will succeed: Function was declared in weak mode, but that does not matter +// This file uses strict mode, so the call is strict, and float denied for int +function_declared_in_weak_mode(1.0); +?> +--EXPECTF-- +Fatal error: Argument 1 passed to function_declared_in_weak_mode() must be of the type integer, float given, called in %sstrict_call_weak.php on line 10 and defined in %sstrict_call_weak_2.inc on line 5 + diff --git a/Zend/tests/typehints/strict_call_weak_2.inc b/Zend/tests/typehints/strict_call_weak_2.inc new file mode 100644 index 0000000000..cba5512897 --- /dev/null +++ b/Zend/tests/typehints/strict_call_weak_2.inc @@ -0,0 +1,7 @@ +<?php + +// implicitly weak code + +function function_declared_in_weak_mode(int $x) { + echo "Success!"; +} diff --git a/Zend/tests/typehints/strict_call_weak_explicit.phpt b/Zend/tests/typehints/strict_call_weak_explicit.phpt new file mode 100644 index 0000000000..2d573e85f2 --- /dev/null +++ b/Zend/tests/typehints/strict_call_weak_explicit.phpt @@ -0,0 +1,17 @@ +--TEST-- +strict_types=1 code calling explicitly strict_types=0 code +--FILE-- +<?php + +declare(strict_types=1); + +// file that's explicitly weak +require 'strict_call_weak_explicit_2.inc'; + +// Will succeed: Function was declared in weak mode, but that does not matter +// This file uses strict mode, so the call is strict, and float denied for int +function_declared_in_weak_mode(1.0); +?> +--EXPECTF-- +Fatal error: Argument 1 passed to function_declared_in_weak_mode() must be of the type integer, float given, called in %sstrict_call_weak_explicit.php on line 10 and defined in %sstrict_call_weak_explicit_2.inc on line 5 + diff --git a/Zend/tests/typehints/strict_call_weak_explicit_2.inc b/Zend/tests/typehints/strict_call_weak_explicit_2.inc new file mode 100644 index 0000000000..d26c84761c --- /dev/null +++ b/Zend/tests/typehints/strict_call_weak_explicit_2.inc @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=0); + +function function_declared_in_weak_mode(int $x) { + echo "Success!"; +} diff --git a/Zend/tests/typehints/strict_include_explicit_weak.phpt b/Zend/tests/typehints/strict_include_explicit_weak.phpt new file mode 100644 index 0000000000..a42d633f47 --- /dev/null +++ b/Zend/tests/typehints/strict_include_explicit_weak.phpt @@ -0,0 +1,14 @@ +--TEST-- +strict_types=1 code including explicitly strict_types=0 code +--FILE-- +<?php + +declare(strict_types=1); + +// file that's explicitly weak +require 'strict_include_explicit_weak_2.inc'; + +// calls within that file should stay weak, despite being included by strict fille +?> +--EXPECTF-- +Success! diff --git a/Zend/tests/typehints/strict_include_explicit_weak_2.inc b/Zend/tests/typehints/strict_include_explicit_weak_2.inc new file mode 100644 index 0000000000..3da32e1738 --- /dev/null +++ b/Zend/tests/typehints/strict_include_explicit_weak_2.inc @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=0); + +function takes_int(int $x) { + echo "Success!"; +} + +takes_int(1.0); // succeeds in weak mode diff --git a/Zend/tests/typehints/strict_include_weak.phpt b/Zend/tests/typehints/strict_include_weak.phpt new file mode 100644 index 0000000000..ce29db7e75 --- /dev/null +++ b/Zend/tests/typehints/strict_include_weak.phpt @@ -0,0 +1,14 @@ +--TEST-- +strict_types=1 code including strict_types=0 code +--FILE-- +<?php + +declare(strict_types=1); + +// file that's implicitly weak +require 'strict_include_weak_2.inc'; + +// calls within that file should stay weak, despite being included by strict file +?> +--EXPECTF-- +Success! diff --git a/Zend/tests/typehints/strict_include_weak_2.inc b/Zend/tests/typehints/strict_include_weak_2.inc new file mode 100644 index 0000000000..d4ee8a836b --- /dev/null +++ b/Zend/tests/typehints/strict_include_weak_2.inc @@ -0,0 +1,9 @@ +<?php + +// implicitly weak code + +function takes_int(int $x) { + echo "Success!"; +} + +takes_int(1.0); // succeeds in weak mode diff --git a/Zend/tests/typehints/strict_nested.phpt b/Zend/tests/typehints/strict_nested.phpt new file mode 100644 index 0000000000..2001f8b1f4 --- /dev/null +++ b/Zend/tests/typehints/strict_nested.phpt @@ -0,0 +1,43 @@ +--TEST-- +Test nested function calls in strict_types=0 and strict_types=1 modes +--FILE-- +<?php + +function takes_int(int $x) { + global $errored; + if ($errored) { + echo "Failure!", PHP_EOL; + $errored = FALSE; + } else { + echo "Success!", PHP_EOL; + } +} + +declare(strict_types=1) { + function strict_calls_takes_int() { + takes_int(1.0); // should fail, strict mode + } + + class StrictTakesIntCaller { + public function call() { + takes_int(1.0); // should fail, strict mode + } + } +} + +declare(strict_types=0) { + function explicit_weak_calls_takes_int() { + takes_int(1.0); // should succeed, weak mode + } + + class ExplicitWeakTakesIntCaller { + public function call() { + takes_int(1.0); // should succeed, weak mode + } + } +} + + +?> +--EXPECTF-- +Fatal error: strict_types declaration must be the very first statement in the script in %s on line %d diff --git a/Zend/tests/typehints/weak_call_strict.phpt b/Zend/tests/typehints/weak_call_strict.phpt new file mode 100644 index 0000000000..b0ea78ce67 --- /dev/null +++ b/Zend/tests/typehints/weak_call_strict.phpt @@ -0,0 +1,16 @@ +--TEST-- +strict_types=0 code calling strict_types=1 code +--FILE-- +<?php + +// implicitly strict_types=0 + +// file with strict_types=1 +require 'weak_call_strict_2.inc'; + +// Will succeed: Function was declared in strict mode, but that does not matter +// This file uses weak mode, so the call is weak, and float accepted for int +function_declared_in_strict_mode(1.0); +?> +--EXPECT-- +Success! diff --git a/Zend/tests/typehints/weak_call_strict_2.inc b/Zend/tests/typehints/weak_call_strict_2.inc new file mode 100644 index 0000000000..fe2a17f682 --- /dev/null +++ b/Zend/tests/typehints/weak_call_strict_2.inc @@ -0,0 +1,7 @@ +<?php + +declare(strict_types=1); + +function function_declared_in_strict_mode(int $x) { + echo "Success!"; +} diff --git a/Zend/tests/typehints/weak_explicit_call_strict.phpt b/Zend/tests/typehints/weak_explicit_call_strict.phpt new file mode 100644 index 0000000000..b07cef5ece --- /dev/null +++ b/Zend/tests/typehints/weak_explicit_call_strict.phpt @@ -0,0 +1,16 @@ +--TEST-- +Explicitly strict_types=0 code calling strict_types=1 code +--FILE-- +<?php + +declare(strict_types=0); + +// file with strict_types=1 +require 'weak_call_strict_2.inc'; + +// Will succeed: Function was declared in strict mode, but that does not matter +// This file uses weak mode, so the call is weak, and float accepted for int +function_declared_in_strict_mode(1.0); +?> +--EXPECT-- +Success! diff --git a/Zend/tests/typehints/weak_include_strict.phpt b/Zend/tests/typehints/weak_include_strict.phpt new file mode 100644 index 0000000000..c3a0820657 --- /dev/null +++ b/Zend/tests/typehints/weak_include_strict.phpt @@ -0,0 +1,14 @@ +--TEST-- +strict_types=0 code including strict_types=1 code +--FILE-- +<?php + +// implicitly weak code + +// file with strict_types=1 +require 'weak_include_strict_2.inc'; + +// calls within that file should stay strict, despite being included by weak file +?> +--EXPECTF-- +Fatal error: Argument 1 passed to takes_int() must be of the type integer, float given, called in %sweak_include_strict_2.inc on line 9 and defined in %sweak_include_strict_2.inc on line 5 diff --git a/Zend/tests/typehints/weak_include_strict_2.inc b/Zend/tests/typehints/weak_include_strict_2.inc new file mode 100644 index 0000000000..8acfc039e8 --- /dev/null +++ b/Zend/tests/typehints/weak_include_strict_2.inc @@ -0,0 +1,9 @@ +<?php + +declare(strict_types=1); + +function takes_int(int $x) { + echo "Success!"; +} + +takes_int(1.0); // fails in strict mode diff --git a/Zend/tests/variadic/typehint_suppressed_error.phpt b/Zend/tests/variadic/typehint_suppressed_error.phpt index f03ec43d0a..5879de6d95 100644 --- a/Zend/tests/variadic/typehint_suppressed_error.phpt +++ b/Zend/tests/variadic/typehint_suppressed_error.phpt @@ -15,4 +15,4 @@ try { ?> --EXPECTF-- -string(%d) "Argument 3 passed to test() must be of the type array, integer given, called in %s on line %d and defined" +string(%d) "Argument 3 passed to test() must be of the type array, integer given, called in %s on line %d" diff --git a/Zend/zend.c b/Zend/zend.c index fe507d7154..6bb614fab5 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1277,6 +1277,35 @@ ZEND_API ZEND_NORETURN void zend_error_noreturn(int type, const char *format, .. # endif #endif +ZEND_API void zend_type_error(const char *format, ...) /* {{{ */ +{ + va_list va; + char *message = NULL; + + va_start(va, format); + zend_vspprintf(&message, 0, format, va); + zend_throw_exception(zend_get_type_exception(), message, E_ERROR); + efree(message); + va_end(va); +} /* }}} */ + +ZEND_API void zend_internal_type_error(zend_bool strict, const char *format, ...) /* {{{ */ +{ + va_list va; + char *message = NULL; + + va_start(va, format); + zend_vspprintf(&message, 0, format, va); + if (strict) { + zend_throw_exception(zend_get_type_exception(), message, E_ERROR); + } else { + zend_error(E_WARNING, message); + } + efree(message); + + va_end(va); +} /* }}} */ + ZEND_API void zend_output_debug_string(zend_bool trigger_break, const char *format, ...) /* {{{ */ { #if ZEND_DEBUG diff --git a/Zend/zend.h b/Zend/zend.h index 94cab74af1..db132a583a 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -285,6 +285,8 @@ extern ZEND_API char *(*zend_getenv)(char *name, size_t name_len); extern ZEND_API zend_string *(*zend_resolve_path)(const char *filename, int filename_len); ZEND_API void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3); +ZEND_API void zend_type_error(const char *format, ...); +ZEND_API void zend_internal_type_error(zend_bool strict, const char *format, ...); void zenderror(const char *error); diff --git a/Zend/zend_API.c b/Zend/zend_API.c index c513d5db5e..91247bbecc 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -154,7 +154,7 @@ ZEND_API void zend_wrong_param_count(void) /* {{{ */ const char *space; const char *class_name = get_active_class_name(&space); - zend_error(E_WARNING, "Wrong parameter count for %s%s%s()", class_name, space, get_active_function_name()); + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "Wrong parameter count for %s%s%s()", class_name, space, get_active_function_name()); } /* }}} */ @@ -164,6 +164,7 @@ ZEND_API char *zend_get_type_by_const(int type) /* {{{ */ switch(type) { case IS_FALSE: case IS_TRUE: + case _IS_BOOL: return "boolean"; case IS_LONG: return "integer"; @@ -194,51 +195,13 @@ ZEND_API char *zend_zval_type_name(const zval *arg) /* {{{ */ } /* }}} */ -ZEND_API int parse_arg_object_to_str(zval *arg, zend_string **str, int type) /* {{{ */ -{ - if (Z_OBJ_HANDLER_P(arg, cast_object)) { - zval obj; - if (Z_OBJ_HANDLER_P(arg, cast_object)(arg, &obj, type) == SUCCESS) { - zval_ptr_dtor(arg); - ZVAL_COPY_VALUE(arg, &obj); - *str = Z_STR_P(arg); - return SUCCESS; - } - } - /* Standard PHP objects */ - if (Z_OBJ_HT_P(arg) == &std_object_handlers || !Z_OBJ_HANDLER_P(arg, cast_object)) { - SEPARATE_ZVAL_NOREF(arg); - if (zend_std_cast_object_tostring(arg, arg, type) == SUCCESS) { - *str = Z_STR_P(arg); - return SUCCESS; - } - } - if (!Z_OBJ_HANDLER_P(arg, cast_object) && Z_OBJ_HANDLER_P(arg, get)) { - zval rv; - zval *z = Z_OBJ_HANDLER_P(arg, get)(arg, &rv); - Z_ADDREF_P(z); - if(Z_TYPE_P(z) != IS_OBJECT) { - zval_dtor(arg); - ZVAL_NULL(arg); - if (!zend_make_printable_zval(z, arg)) { - ZVAL_ZVAL(arg, z, 1, 1); - } - *str = Z_STR_P(arg); - return SUCCESS; - } - zval_ptr_dtor(z); - } - return FAILURE; -} -/* }}} */ - #ifdef FAST_ZPP -ZEND_API void zend_wrong_paramers_count_error(int num_args, int min_num_args, int max_num_args) /* {{{ */ +ZEND_API void ZEND_FASTCALL zend_wrong_paramers_count_error(int num_args, int min_num_args, int max_num_args) /* {{{ */ { zend_function *active_function = EG(current_execute_data)->func; const char *class_name = active_function->common.scope ? active_function->common.scope->name->val : ""; - zend_error(E_WARNING, "%s%s%s() expects %s %d parameter%s, %d given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects %s %d parameter%s, %d given", class_name, \ class_name[0] ? "::" : "", \ active_function->common.function_name->val, @@ -249,7 +212,7 @@ ZEND_API void zend_wrong_paramers_count_error(int num_args, int min_num_args, in } /* }}} */ -ZEND_API void zend_wrong_paramer_type_error(int num, zend_expected_type expected_type, zval *arg) /* {{{ */ +ZEND_API void ZEND_FASTCALL zend_wrong_paramer_type_error(int num, zend_expected_type expected_type, zval *arg) /* {{{ */ { const char *space; const char *class_name = get_active_class_name(&space); @@ -258,33 +221,38 @@ ZEND_API void zend_wrong_paramer_type_error(int num, zend_expected_type expected NULL }; - zend_error(E_WARNING, "%s%s%s() expects parameter %d to be %s, %s given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d to be %s, %s given", class_name, space, get_active_function_name(), num, expected_error[expected_type], zend_zval_type_name(arg)); } /* }}} */ -ZEND_API void zend_wrong_paramer_class_error(int num, char *name, zval *arg) /* {{{ */ +ZEND_API void ZEND_FASTCALL zend_wrong_paramer_class_error(int num, char *name, zval *arg) /* {{{ */ { const char *space; const char *class_name = get_active_class_name(&space); - zend_error(E_WARNING, "%s%s%s() expects parameter %d to be %s, %s given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d to be %s, %s given", class_name, space, get_active_function_name(), num, name, zend_zval_type_name(arg)); } /* }}} */ -ZEND_API void zend_wrong_callback_error(int severity, int num, char *error) /* {{{ */ +ZEND_API void ZEND_FASTCALL zend_wrong_callback_error(int severity, int num, char *error) /* {{{ */ { const char *space; const char *class_name = get_active_class_name(&space); - zend_error(severity, "%s%s%s() expects parameter %d to be a valid callback, %s", - class_name, space, get_active_function_name(), num, error); + if (severity == E_WARNING) { + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d to be a valid callback, %s", + class_name, space, get_active_function_name(), num, error); + } else { + zend_error(severity, "%s%s%s() expects parameter %d to be a valid callback, %s", + class_name, space, get_active_function_name(), num, error); + } efree(error); } /* }}} */ -ZEND_API int zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, int check_null) /* {{{ */ +ZEND_API int ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, int check_null) /* {{{ */ { zend_class_entry *ce_base = *pce; @@ -299,7 +267,7 @@ ZEND_API int zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, in const char *space; const char *class_name = get_active_class_name(&space); - zend_error(E_WARNING, "%s%s%s() expects parameter %d to be a class name derived from %s, '%s' given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d to be a class name derived from %s, '%s' given", class_name, space, get_active_function_name(), num, ce_base->name->val, Z_STRVAL_P(arg)); *pce = NULL; @@ -310,7 +278,7 @@ ZEND_API int zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, in const char *space; const char *class_name = get_active_class_name(&space); - zend_error(E_WARNING, "%s%s%s() expects parameter %d to be a valid class name, '%s' given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d to be a valid class name, '%s' given", class_name, space, get_active_function_name(), num, Z_STRVAL_P(arg)); return 0; @@ -320,6 +288,218 @@ ZEND_API int zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, in /* }}} */ #endif +ZEND_API int ZEND_FASTCALL zend_parse_arg_bool_weak(zval *arg, zend_bool *dest) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(arg) <= IS_STRING)) { + *dest = zend_is_true(arg); + } else { + return 0; + } + return 1; +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_bool_slow(zval *arg, zend_bool *dest) /* {{{ */ +{ + if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) { + return 0; + } + return zend_parse_arg_bool_weak(arg, dest); +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_weak(zval *arg, zend_long *dest) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(arg) == IS_DOUBLE)) { + if (UNEXPECTED(zend_isnan(Z_DVAL_P(arg)))) { + return 0; + } + if (UNEXPECTED(!ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(arg)))) { + return 0; + } else { + *dest = zend_dval_to_lval(Z_DVAL_P(arg)); + } + } else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) { + double d; + int type; + + if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), dest, &d)) != IS_LONG)) { + if (EXPECTED(type != 0)) { + if (UNEXPECTED(zend_isnan(d))) { + return 0; + } + if (UNEXPECTED(!ZEND_DOUBLE_FITS_LONG(d))) { + return 0; + } else { + *dest = zend_dval_to_lval(d); + } + } else { + return 0; + } + } + } else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) { + *dest = 0; + } else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) { + *dest = 1; + } else { + return 0; + } + return 1; +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_slow(zval *arg, zend_long *dest) /* {{{ */ +{ + if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) { + return 0; + } + return zend_parse_arg_long_weak(arg, dest); +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_cap_weak(zval *arg, zend_long *dest) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(arg) == IS_DOUBLE)) { + if (UNEXPECTED(zend_isnan(Z_DVAL_P(arg)))) { + return 0; + } + if (UNEXPECTED(!ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(arg)))) { + *dest = (Z_DVAL_P(arg) > 0) ? ZEND_LONG_MAX : ZEND_LONG_MIN; + } else { + *dest = zend_dval_to_lval(Z_DVAL_P(arg)); + } + } else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) { + double d; + int type; + + if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), dest, &d)) != IS_LONG)) { + if (EXPECTED(type != 0)) { + if (UNEXPECTED(zend_isnan(d))) { + return 0; + } + if (UNEXPECTED(!ZEND_DOUBLE_FITS_LONG(d))) { + *dest = (d > 0) ? ZEND_LONG_MAX : ZEND_LONG_MIN; + } else { + *dest = zend_dval_to_lval(d); + } + } else { + return 0; + } + } + } else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) { + *dest = 0; + } else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) { + *dest = 1; + } else { + return 0; + } + return 1; +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_cap_slow(zval *arg, zend_long *dest) /* {{{ */ +{ + if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) { + return 0; + } + return zend_parse_arg_long_cap_weak(arg, dest); +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_double_weak(zval *arg, double *dest) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) { + *dest = (double)Z_LVAL_P(arg); + } else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) { + zend_long l; + int type; + + if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), &l, dest)) != IS_DOUBLE)) { + if (EXPECTED(type != 0)) { + *dest = (double)(l); + } else { + return 0; + } + } + } else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) { + *dest = 0.0; + } else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) { + *dest = 1.0; + } else { + return 0; + } + return 1; +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_double_slow(zval *arg, double *dest) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) { + /* SSTH Exception: IS_LONG may be accepted instead as IS_DOUBLE */ + *dest = (double)Z_LVAL_P(arg); + } else if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) { + return 0; + } + return zend_parse_arg_double_weak(arg, dest); +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **dest) /* {{{ */ +{ + if (EXPECTED(Z_TYPE_P(arg) < IS_STRING)) { + convert_to_string(arg); + *dest = Z_STR_P(arg); + } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { + if (Z_OBJ_HANDLER_P(arg, cast_object)) { + zval obj; + if (Z_OBJ_HANDLER_P(arg, cast_object)(arg, &obj, IS_STRING) == SUCCESS) { + zval_ptr_dtor(arg); + ZVAL_COPY_VALUE(arg, &obj); + *dest = Z_STR_P(arg); + return 1; + } + } + /* Standard PHP objects */ + if (Z_OBJ_HT_P(arg) == &std_object_handlers || !Z_OBJ_HANDLER_P(arg, cast_object)) { + SEPARATE_ZVAL_NOREF(arg); + if (zend_std_cast_object_tostring(arg, arg, IS_STRING) == SUCCESS) { + *dest = Z_STR_P(arg); + return 1; + } + } + if (!Z_OBJ_HANDLER_P(arg, cast_object) && Z_OBJ_HANDLER_P(arg, get)) { + zval rv; + zval *z = Z_OBJ_HANDLER_P(arg, get)(arg, &rv); + + Z_ADDREF_P(z); + if (Z_TYPE_P(z) != IS_OBJECT) { + zval_dtor(arg); + ZVAL_NULL(arg); + if (!zend_make_printable_zval(z, arg)) { + ZVAL_ZVAL(arg, z, 1, 1); + } + *dest = Z_STR_P(arg); + return 1; + } + zval_ptr_dtor(z); + } + return 0; + } else { + return 0; + } + return 1; +} +/* }}} */ + +ZEND_API int ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, zend_string **dest) /* {{{ */ +{ + if (UNEXPECTED(ZEND_ARG_USES_STRICT_TYPES())) { + return 0; + } + return zend_parse_arg_str_weak(arg, dest); +} +/* }}} */ + static const char *zend_parse_arg_impl(int arg_num, zval *arg, va_list *va, const char **spec, char **error, int *severity) /* {{{ */ { const char *spec_walk = *spec; @@ -539,7 +719,7 @@ static const char *zend_parse_arg_impl(int arg_num, zval *arg, va_list *va, cons break; } else { if (is_callable_error) { - *severity = E_WARNING; + *severity = E_EXCEPTION | E_ERROR; zend_spprintf(error, 0, "to be a valid callback, %s", is_callable_error); efree(is_callable_error); return ""; @@ -570,24 +750,24 @@ static const char *zend_parse_arg_impl(int arg_num, zval *arg, va_list *va, cons } /* }}} */ -static int zend_parse_arg(int arg_num, zval *arg, va_list *va, const char **spec, int quiet) /* {{{ */ +static int zend_parse_arg(int arg_num, zval *arg, va_list *va, const char **spec, int flags) /* {{{ */ { const char *expected_type = NULL; char *error = NULL; - int severity = E_WARNING; + int severity; expected_type = zend_parse_arg_impl(arg_num, arg, va, spec, &error, &severity); if (expected_type) { - if (!quiet && (*expected_type || error)) { + if (!(flags & ZEND_PARSE_PARAMS_QUIET) && (*expected_type || error)) { const char *space; const char *class_name = get_active_class_name(&space); if (error) { - zend_error(severity, "%s%s%s() expects parameter %d %s", - class_name, space, get_active_function_name(), arg_num, error); + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d %s", + class_name, space, get_active_function_name(), arg_num, error); efree(error); } else { - zend_error(severity, "%s%s%s() expects parameter %d to be %s, %s given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects parameter %d to be %s, %s given", class_name, space, get_active_function_name(), arg_num, expected_type, zend_zval_type_name(arg)); } @@ -605,10 +785,9 @@ ZEND_API int zend_parse_parameter(int flags, int arg_num, zval *arg, const char { va_list va; int ret; - int quiet = flags & ZEND_PARSE_PARAMS_QUIET; va_start(va, spec); - ret = zend_parse_arg(arg_num, arg, &va, &spec, quiet); + ret = zend_parse_arg(arg_num, arg, &va, &spec, flags); va_end(va); return ret; @@ -623,7 +802,6 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, int post_varargs = 0; zval *arg; int arg_count; - int quiet = flags & ZEND_PARSE_PARAMS_QUIET; zend_bool have_varargs = 0; zval **varargs = NULL; int *n_varargs = NULL; @@ -656,9 +834,10 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, case '*': case '+': if (have_varargs) { - if (!quiet) { + if (!(flags & ZEND_PARSE_PARAMS_QUIET)) { zend_function *active_function = EG(current_execute_data)->func; const char *class_name = active_function->common.scope ? active_function->common.scope->name->val : ""; + zend_error(E_WARNING, "%s%s%s(): only one varargs specifier (* or +) is permitted", class_name, class_name[0] ? "::" : "", @@ -676,7 +855,7 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, break; default: - if (!quiet) { + if (!(flags & ZEND_PARSE_PARAMS_QUIET)) { zend_function *active_function = EG(current_execute_data)->func; const char *class_name = active_function->common.scope ? active_function->common.scope->name->val : ""; zend_error(E_WARNING, "%s%s%s(): bad type specifier while parsing parameters", @@ -699,10 +878,10 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, } if (num_args < min_num_args || (num_args > max_num_args && max_num_args > 0)) { - if (!quiet) { + if (!(flags & ZEND_PARSE_PARAMS_QUIET)) { zend_function *active_function = EG(current_execute_data)->func; const char *class_name = active_function->common.scope ? active_function->common.scope->name->val : ""; - zend_error(E_WARNING, "%s%s%s() expects %s %d parameter%s, %d given", + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects %s %d parameter%s, %d given", class_name, class_name[0] ? "::" : "", active_function->common.function_name->val, @@ -751,7 +930,7 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, arg = ZEND_CALL_ARG(EG(current_execute_data), i + 1); - if (zend_parse_arg(i+1, arg, va, &type_spec, quiet) == FAILURE) { + if (zend_parse_arg(i+1, arg, va, &type_spec, flags) == FAILURE) { /* clean up varargs array if it was used */ if (varargs && *varargs) { *varargs = NULL; @@ -765,13 +944,13 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, } /* }}} */ -#define RETURN_IF_ZERO_ARGS(num_args, type_spec, quiet) { \ +#define RETURN_IF_ZERO_ARGS(num_args, type_spec, flags) { \ int __num_args = (num_args); \ \ - if (0 == (type_spec)[0] && 0 != __num_args && !(quiet)) { \ + if (0 == (type_spec)[0] && 0 != __num_args && !(flags & ZEND_PARSE_PARAMS_QUIET)) { \ const char *__space; \ const char * __class_name = get_active_class_name(&__space); \ - zend_error(E_WARNING, "%s%s%s() expects exactly 0 parameters, %d given", \ + zend_internal_type_error(ZEND_ARG_USES_STRICT_TYPES(), "%s%s%s() expects exactly 0 parameters, %d given", \ __class_name, __space, \ get_active_function_name(), __num_args); \ return FAILURE; \ @@ -783,7 +962,7 @@ ZEND_API int zend_parse_parameters_ex(int flags, int num_args, const char *type_ va_list va; int retval; - RETURN_IF_ZERO_ARGS(num_args, type_spec, flags & ZEND_PARSE_PARAMS_QUIET); + RETURN_IF_ZERO_ARGS(num_args, type_spec, flags); va_start(va, type_spec); retval = zend_parse_va_args(num_args, type_spec, &va, flags); @@ -797,11 +976,12 @@ ZEND_API int zend_parse_parameters(int num_args, const char *type_spec, ...) /* { va_list va; int retval; + int flags = 0; - RETURN_IF_ZERO_ARGS(num_args, type_spec, 0); + RETURN_IF_ZERO_ARGS(num_args, type_spec, flags); va_start(va, type_spec); - retval = zend_parse_va_args(num_args, type_spec, &va, 0); + retval = zend_parse_va_args(num_args, type_spec, &va, flags); va_end(va); return retval; @@ -812,6 +992,7 @@ ZEND_API int zend_parse_method_parameters(int num_args, zval *this_ptr, const ch { va_list va; int retval; + int flags = 0; const char *p = type_spec; zval **object; zend_class_entry *ce; @@ -821,15 +1002,16 @@ ZEND_API int zend_parse_method_parameters(int num_args, zval *this_ptr, const ch * In that case EG(This) would still be the $this from the calling code and we'd take the * wrong branch here. */ zend_bool is_method = EG(current_execute_data)->func->common.scope != NULL; + if (!is_method || !this_ptr || Z_TYPE_P(this_ptr) != IS_OBJECT) { - RETURN_IF_ZERO_ARGS(num_args, p, 0); + RETURN_IF_ZERO_ARGS(num_args, p, flags); va_start(va, type_spec); - retval = zend_parse_va_args(num_args, type_spec, &va, 0); + retval = zend_parse_va_args(num_args, type_spec, &va, flags); va_end(va); } else { p++; - RETURN_IF_ZERO_ARGS(num_args, p, 0); + RETURN_IF_ZERO_ARGS(num_args, p, flags); va_start(va, type_spec); @@ -842,7 +1024,7 @@ ZEND_API int zend_parse_method_parameters(int num_args, zval *this_ptr, const ch Z_OBJCE_P(this_ptr)->name->val, get_active_function_name(), ce->name->val, get_active_function_name()); } - retval = zend_parse_va_args(num_args, p, &va, 0); + retval = zend_parse_va_args(num_args, p, &va, flags); va_end(va); } return retval; @@ -856,17 +1038,16 @@ ZEND_API int zend_parse_method_parameters_ex(int flags, int num_args, zval *this const char *p = type_spec; zval **object; zend_class_entry *ce; - int quiet = flags & ZEND_PARSE_PARAMS_QUIET; if (!this_ptr) { - RETURN_IF_ZERO_ARGS(num_args, p, quiet); + RETURN_IF_ZERO_ARGS(num_args, p, flags); va_start(va, type_spec); retval = zend_parse_va_args(num_args, type_spec, &va, flags); va_end(va); } else { p++; - RETURN_IF_ZERO_ARGS(num_args, p, quiet); + RETURN_IF_ZERO_ARGS(num_args, p, flags); va_start(va, type_spec); @@ -875,7 +1056,7 @@ ZEND_API int zend_parse_method_parameters_ex(int flags, int num_args, zval *this *object = this_ptr; if (ce && !instanceof_function(Z_OBJCE_P(this_ptr), ce)) { - if (!quiet) { + if (!(flags & ZEND_PARSE_PARAMS_QUIET)) { zend_error(E_CORE_ERROR, "%s::%s() must be derived from %s::%s", ce->name->val, get_active_function_name(), Z_OBJCE_P(this_ptr)->name->val, get_active_function_name()); } @@ -2535,6 +2716,9 @@ ZEND_API int zend_register_class_alias_ex(const char *name, size_t name_len, zen lcname = zend_string_alloc(name_len, 1); zend_str_tolower_copy(lcname->val, name, name_len); } + + zend_assert_valid_class_name(lcname); + ce = zend_hash_add_ptr(CG(class_table), lcname, ce); zend_string_release(lcname); if (ce) { diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 41ef47e4ff..bd90e953fe 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -704,10 +704,10 @@ typedef enum _zend_expected_type { Z_EXPECTED_LAST } zend_expected_type; -ZEND_API void zend_wrong_paramers_count_error(int num_args, int min_num_args, int max_num_args); -ZEND_API void zend_wrong_paramer_type_error(int num, zend_expected_type expected_type, zval *arg); -ZEND_API void zend_wrong_paramer_class_error(int num, char *name, zval *arg); -ZEND_API void zend_wrong_callback_error(int severity, int num, char *error); +ZEND_API void ZEND_FASTCALL zend_wrong_paramers_count_error(int num_args, int min_num_args, int max_num_args); +ZEND_API void ZEND_FASTCALL zend_wrong_paramer_type_error(int num, zend_expected_type expected_type, zval *arg); +ZEND_API void ZEND_FASTCALL zend_wrong_paramer_class_error(int num, char *name, zval *arg); +ZEND_API void ZEND_FASTCALL zend_wrong_callback_error(int severity, int num, char *error); #define ZPP_ERROR_OK 0 #define ZPP_ERROR_FAILURE 1 @@ -1048,8 +1048,17 @@ ZEND_API void zend_wrong_callback_error(int severity, int num, char *error); /* Inlined implementations shared by new and old parameter parsing APIs */ -ZEND_API int parse_arg_object_to_str(zval *arg, zend_string **str, int type); -ZEND_API int zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, int check_null); +ZEND_API int ZEND_FASTCALL zend_parse_arg_class(zval *arg, zend_class_entry **pce, int num, int check_null); +ZEND_API int ZEND_FASTCALL zend_parse_arg_bool_slow(zval *arg, zend_bool *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_bool_weak(zval *arg, zend_bool *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_slow(zval *arg, zend_long *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_weak(zval *arg, zend_long *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_cap_slow(zval *arg, zend_long *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_long_cap_weak(zval *arg, zend_long *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_double_slow(zval *arg, double *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_double_weak(zval *arg, double *dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_str_slow(zval *arg, zend_string **dest); +ZEND_API int ZEND_FASTCALL zend_parse_arg_str_weak(zval *arg, zend_string **dest); static zend_always_inline int zend_parse_arg_bool(zval *arg, zend_bool *dest, zend_bool *is_null, int check_null) { @@ -1058,71 +1067,31 @@ static zend_always_inline int zend_parse_arg_bool(zval *arg, zend_bool *dest, ze } if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) { *dest = 1; - } else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) { - if (check_null) { - *is_null = (Z_TYPE_P(arg) == IS_NULL); - } + } else if (EXPECTED(Z_TYPE_P(arg) == IS_FALSE)) { + *dest = 0; + } else if (check_null && Z_TYPE_P(arg) == IS_NULL) { + *is_null = 1; *dest = 0; - } else if (EXPECTED(Z_TYPE_P(arg) <= IS_STRING)) { - *dest = zend_is_true(arg); } else { - return 0; + return zend_parse_arg_bool_slow(arg, dest); } return 1; } -static zend_always_inline int zend_parse_arg_long(zval *arg, zend_long *dest, zend_bool *is_null, int check_null, int strict) +static zend_always_inline int zend_parse_arg_long(zval *arg, zend_long *dest, zend_bool *is_null, int check_null, int cap) { if (check_null) { *is_null = 0; } if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) { *dest = Z_LVAL_P(arg); - } else if (EXPECTED(Z_TYPE_P(arg) == IS_DOUBLE)) { - if (UNEXPECTED(zend_isnan(Z_DVAL_P(arg)))) { - return 0; - } - if (UNEXPECTED(!ZEND_DOUBLE_FITS_LONG(Z_DVAL_P(arg)))) { - /* Ironically, the strict parameter makes zpp *non*-strict here */ - if (strict) { - *dest = (Z_DVAL_P(arg) > 0) ? ZEND_LONG_MAX : ZEND_LONG_MIN; - } else { - return 0; - } - } else { - *dest = zend_dval_to_lval(Z_DVAL_P(arg)); - } - } else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) { - double d; - int type; - - if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), dest, &d)) != IS_LONG)) { - if (EXPECTED(type != 0)) { - if (UNEXPECTED(zend_isnan(d))) { - return 0; - } - if (UNEXPECTED(!ZEND_DOUBLE_FITS_LONG(d))) { - if (strict) { - *dest = (d > 0) ? ZEND_LONG_MAX : ZEND_LONG_MIN; - } else { - return 0; - } - } else { - *dest = zend_dval_to_lval(d); - } - } else { - return 0; - } - } - } else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) { - if (check_null) { - *is_null = (Z_TYPE_P(arg) == IS_NULL); - } + } else if (check_null && Z_TYPE_P(arg) == IS_NULL) { + *is_null = 1; *dest = 0; - } else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) { - *dest = 1; + } else if (cap) { + return zend_parse_arg_long_cap_slow(arg, dest); } else { - return 0; + return zend_parse_arg_long_slow(arg, dest); } return 1; } @@ -1134,28 +1103,11 @@ static zend_always_inline int zend_parse_arg_double(zval *arg, double *dest, zen } if (EXPECTED(Z_TYPE_P(arg) == IS_DOUBLE)) { *dest = Z_DVAL_P(arg); - } else if (EXPECTED(Z_TYPE_P(arg) == IS_LONG)) { - *dest = (double)Z_LVAL_P(arg); - } else if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) { - zend_long l; - int type; - - if (UNEXPECTED((type = is_numeric_str_function(Z_STR_P(arg), &l, dest)) != IS_DOUBLE)) { - if (EXPECTED(type != 0)) { - *dest = (double)(l); - } else { - return 0; - } - } - } else if (EXPECTED(Z_TYPE_P(arg) < IS_TRUE)) { - if (check_null) { - *is_null = (Z_TYPE_P(arg) == IS_NULL); - } + } else if (check_null && Z_TYPE_P(arg) == IS_NULL) { + *is_null = 1; *dest = 0.0; - } else if (EXPECTED(Z_TYPE_P(arg) == IS_TRUE)) { - *dest = 1.0; } else { - return 0; + return zend_parse_arg_double_slow(arg, dest); } return 1; } @@ -1164,20 +1116,10 @@ static zend_always_inline int zend_parse_arg_str(zval *arg, zend_string **dest, { if (EXPECTED(Z_TYPE_P(arg) == IS_STRING)) { *dest = Z_STR_P(arg); - } else if (EXPECTED(Z_TYPE_P(arg) < IS_STRING)) { - if (check_null && UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { - *dest = NULL; - } else { - if (Z_COPYABLE_P(arg) && Z_REFCOUNT_P(arg) > 1) { - Z_DELREF_P(arg); - zval_copy_ctor_func(arg); - } - convert_to_string(arg); - *dest = Z_STR_P(arg); - } - } else if (UNEXPECTED(Z_TYPE_P(arg) != IS_OBJECT) || - UNEXPECTED(parse_arg_object_to_str(arg, dest, IS_STRING) != SUCCESS)) { - return 0; + } else if (check_null && Z_TYPE_P(arg) == IS_NULL) { + *dest = NULL; + } else { + return zend_parse_arg_str_slow(arg, dest); } return 1; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b7c25eea8d..64d96e452d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -129,6 +129,72 @@ static zend_bool zend_get_unqualified_name(const zend_string *name, const char * } /* }}} */ +typedef struct _scalar_typehint_info { + const char* name; + const size_t name_len; + const zend_uchar type; +} scalar_typehint_info; + +static const scalar_typehint_info scalar_typehints[] = { + {"int", sizeof("int") - 1, IS_LONG}, + {"float", sizeof("float") - 1, IS_DOUBLE}, + {"string", sizeof("string") - 1, IS_STRING}, + {"bool", sizeof("bool") - 1, _IS_BOOL}, + {NULL, 0, IS_UNDEF} +}; + +static zend_always_inline const scalar_typehint_info* zend_find_scalar_typehint(const zend_string *const_name) /* {{{ */ +{ + const scalar_typehint_info *info = &scalar_typehints[0]; + const char *uqname; + size_t uqname_len; + + if (!zend_get_unqualified_name(const_name, &uqname, &uqname_len)) { + uqname = const_name->val; + uqname_len = const_name->len; + } + + while (info->name) { + if (uqname_len == info->name_len && zend_binary_strcasecmp(uqname, uqname_len, info->name, info->name_len) == 0) { + break; + } + info++; + } + + if (info->name) { + return info; + } else { + return NULL; + } +} +/* }}} */ + +ZEND_API void zend_assert_valid_class_name(const zend_string *const_name) /* {{{ */ +{ + const scalar_typehint_info *info = zend_find_scalar_typehint(const_name); + + if (info) { + zend_error_noreturn(E_COMPILE_ERROR, "\"%s\" cannot be used as a class name", info->name); + } +} +/* }}} */ + +static zend_always_inline zend_uchar zend_lookup_scalar_typehint_by_name(const zend_string *const_name) /* {{{ */ +{ + const scalar_typehint_info *info = zend_find_scalar_typehint(const_name); + + if (info) { + if (const_name->len != info->name_len) { + zend_error_noreturn(E_COMPILE_ERROR, "\"%s\" cannot be used as a type declaration", const_name->val); + } + return info->type; + } else { + return 0; + } +} +/* }}} */ + + static void init_compiler_declarables(void) /* {{{ */ { ZVAL_LONG(&CG(declarables).ticks, 0); @@ -1864,7 +1930,11 @@ 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); + zend_op *opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL); + if (expr && expr->op_type == IS_CONST) { + opline->result_type = expr->op_type = IS_TMP_VAR; + opline->result.var = expr->u.op.var = get_temporary_variable(CG(active_op_array)); + } } } /* }}} */ @@ -3334,11 +3404,9 @@ 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); - } + /* Generator return types are handled separately */ + if (!(CG(active_op_array)->fn_flags & ZEND_ACC_GENERATOR) && CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_emit_return_type_check(expr_ast ? &expr_node : NULL, CG(active_op_array)->arg_info - 1); } opline = zend_emit_op(NULL, by_ref ? ZEND_RETURN_BY_REF : ZEND_RETURN, &expr_node, NULL); @@ -3908,6 +3976,28 @@ void zend_handle_encoding_declaration(zend_ast *ast) /* {{{ */ } /* }}} */ +static int zend_declare_is_first_statement(zend_ast *ast) /* {{{ */ +{ + uint32_t i = 0; + zend_ast_list *file_ast = zend_ast_get_list(CG(ast)); + + /* Check to see if this declare is preceeded only by declare statements */ + while (i < file_ast->children) { + if (file_ast->child[i] == ast) { + return SUCCESS; + } else if (file_ast->child[i] == NULL) { + /* Empty statements are not allowed prior to a declare */ + return FAILURE; + } else if (file_ast->child[i]->kind != ZEND_AST_DECLARE) { + /* declares can only be preceeded by other declares */ + return FAILURE; + } + i++; + } + return FAILURE; +} +/* }}} */ + void zend_compile_declare(zend_ast *ast) /* {{{ */ { zend_ast_list *declares = zend_ast_get_list(ast->child[0]); @@ -3928,29 +4018,34 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */ ZVAL_COPY_VALUE(&CG(declarables).ticks, &value_zv); zval_dtor(&value_zv); } else if (zend_string_equals_literal_ci(name, "encoding")) { - uint32_t i = 0; - zend_bool valid = 0; - /* Encoding declaration was already handled during parsing. Here we - * only check that it is the first statement in the file. */ - zend_ast_list *file_ast = zend_ast_get_list(CG(ast)); - - /* Check to see if this declare is preceeded only by declare statements */ - while (valid == 0 && i < file_ast->children) { - if (file_ast->child[i] == ast) { - valid = 1; - } else if (file_ast->child[i] == NULL) { - /* Empty statements are not allowed prior to a declare */ - break; - } else if (file_ast->child[i]->kind != ZEND_AST_DECLARE) { - /* declares can only be preceeded by other declares */ - break; - } - i++; - } - if (valid != 1) { + + if (FAILURE == zend_declare_is_first_statement(ast)) { zend_error_noreturn(E_COMPILE_ERROR, "Encoding declaration pragma must be " "the very first statement in the script"); } + } else if (zend_string_equals_literal_ci(name, "strict_types")) { + zval value_zv; + + if (FAILURE == zend_declare_is_first_statement(ast)) { + zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must be " + "the very first statement in the script"); + } + + if (ast->child[1] != NULL) { + zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must not " + "use block mode"); + } + + zend_const_expr_to_zval(&value_zv, value_ast); + + if (Z_TYPE(value_zv) != IS_LONG || (Z_LVAL(value_zv) != 0 && Z_LVAL(value_zv) != 1)) { + zend_error_noreturn(E_COMPILE_ERROR, "strict_types declaration must have 0 or 1 as its value"); + } + + if (Z_LVAL(value_zv) == 1) { + CG(active_op_array)->fn_flags |= ZEND_ACC_STRICT_TYPES; + } + } else { zend_error(E_COMPILE_WARNING, "Unsupported declare '%s'", name->val); } @@ -3995,19 +4090,24 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_bool is_ arg_infos->type_hint = return_type_ast->attr; } else { zend_string *class_name = zend_ast_get_str(return_type_ast); + zend_uchar type = zend_lookup_scalar_typehint_by_name(class_name); - if (zend_is_const_default_class_ref(return_type_ast)) { - class_name = zend_resolve_class_name_ast(return_type_ast); + if (type != 0) { + arg_infos->type_hint = type; } 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; + 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->type_hint = IS_OBJECT; + arg_infos->class_name = class_name; + } } arg_infos++; @@ -4117,19 +4217,30 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, zend_bool is_ } } else { zend_string *class_name = zend_ast_get_str(type_ast); + zend_uchar type; - if (zend_is_const_default_class_ref(type_ast)) { - class_name = zend_resolve_class_name_ast(type_ast); + type = zend_lookup_scalar_typehint_by_name(class_name); + if (type != 0) { + arg_info->type_hint = type; } else { - zend_string_addref(class_name); - } - arg_info->type_hint = IS_OBJECT; - arg_info->class_name = class_name; + if (zend_is_const_default_class_ref(type_ast)) { + class_name = zend_resolve_class_name_ast(type_ast); + } else { + zend_string_addref(class_name); + } + arg_info->type_hint = IS_OBJECT; + arg_info->class_name = class_name; + } if (default_ast && !has_null_default && !Z_CONSTANT(default_node.u.constant)) { + if (arg_info->class_name) { zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " "with a class type hint can only be NULL"); + } else if (!ZEND_SAME_FAKE_TYPE(arg_info->type_hint, Z_TYPE(default_node.u.constant))) { + zend_error_noreturn(E_COMPILE_ERROR, "Default value for parameters " + "with a %s type hint can only be %s or NULL", class_name->val, class_name->val); + } } } } @@ -4409,6 +4520,7 @@ void zend_compile_func_decl(znode *result, zend_ast *ast) /* {{{ */ init_op_array(op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE); + op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_STRICT_TYPES); op_array->fn_flags |= decl->flags; op_array->line_start = decl->start_lineno; op_array->line_end = decl->end_lineno; @@ -4743,6 +4855,8 @@ void zend_compile_class_decl(zend_ast *ast) /* {{{ */ import_name = zend_hash_find_ptr(CG(current_import), lcname); } + zend_assert_valid_class_name(name); + if (CG(current_namespace)) { name = zend_prefix_with_ns(name); @@ -4978,6 +5092,10 @@ void zend_compile_use(zend_ast *ast) /* {{{ */ } } + if (type == T_CLASS) { + zend_assert_valid_class_name(new_name); + } + if (case_sensitive) { lookup_name = zend_string_copy(new_name); } else { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 45441e2108..c53833f149 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -252,6 +252,9 @@ typedef struct _zend_try_catch_element { /* Function has a return type hint (or class has such non-private function) */ #define ZEND_ACC_HAS_RETURN_TYPE 0x40000000 +/* op_array uses strict mode types */ +#define ZEND_ACC_STRICT_TYPES 0x80000000 + char *zend_visibility_string(uint32_t fn_flags); typedef struct _zend_property_info { @@ -459,6 +462,20 @@ struct _zend_execute_data { #define EX_CALL_KIND() ZEND_CALL_KIND(execute_data) #define EX_NUM_ARGS() ZEND_CALL_NUM_ARGS(execute_data) +#define ZEND_CALL_USES_STRICT_TYPES(call) \ + (((call)->func->common.fn_flags & ZEND_ACC_STRICT_TYPES) != 0) + +#define EX_USES_STRICT_TYPES() \ + ZEND_CALL_USES_STRICT_TYPES(execute_data) + +#define ZEND_ARG_USES_STRICT_TYPES() \ + (EG(current_execute_data)->prev_execute_data && \ + EG(current_execute_data)->prev_execute_data->func && \ + ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)->prev_execute_data)) + +#define ZEND_RET_USES_STRICT_TYPES() \ + ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data)) + #define EX_VAR(n) ZEND_CALL_VAR(execute_data, n) #define EX_VAR_NUM(n) ZEND_CALL_VAR_NUM(execute_data, n) @@ -732,6 +749,8 @@ int zendlex(zend_parser_stack_elem *elem); int zend_add_literal(zend_op_array *op_array, zval *zv); +ZEND_API void zend_assert_valid_class_name(const zend_string *const_name); + /* BEGIN: OPCODES */ #include "zend_vm_opcodes.h" diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 7070934426..b88c13e6d0 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -35,6 +35,7 @@ static zend_class_entry *default_exception_ce; static zend_class_entry *error_exception_ce; static zend_class_entry *engine_exception_ce; static zend_class_entry *parse_exception_ce; +static zend_class_entry *type_exception_ce; static zend_object_handlers default_exception_handlers; ZEND_API void (*zend_throw_exception_hook)(zval *ex); @@ -778,6 +779,10 @@ void zend_register_default_exception(void) /* {{{ */ INIT_CLASS_ENTRY(ce, "ParseException", NULL); parse_exception_ce = zend_register_internal_class_ex(&ce, base_exception_ce); parse_exception_ce->create_object = zend_default_exception_new; + + INIT_CLASS_ENTRY(ce, "TypeException", NULL); + type_exception_ce = zend_register_internal_class_ex(&ce, engine_exception_ce); + type_exception_ce->create_object = zend_default_exception_new; } /* }}} */ @@ -811,6 +816,12 @@ ZEND_API zend_class_entry *zend_get_parse_exception(void) /* {{{ */ } /* }}} */ +ZEND_API zend_class_entry *zend_get_type_exception(void) /* {{{ */ +{ + return type_exception_ce; +} +/* }}} */ + ZEND_API zend_object *zend_throw_exception(zend_class_entry *exception_ce, const char *message, zend_long code) /* {{{ */ { @@ -892,13 +903,18 @@ ZEND_API void zend_exception_error(zend_object *ex, int severity) /* {{{ */ ZVAL_OBJ(&exception, ex); ce_exception = Z_OBJCE(exception); EG(exception) = NULL; - if (ce_exception == parse_exception_ce || ce_exception == engine_exception_ce) { + if (ce_exception == parse_exception_ce || ce_exception == engine_exception_ce || ce_exception == type_exception_ce) { zend_string *message = zval_get_string(GET_PROPERTY(&exception, "message")); zend_string *file = zval_get_string(GET_PROPERTY_SILENT(&exception, "file")); zend_long line = zval_get_long(GET_PROPERTY_SILENT(&exception, "line")); zend_long code = zval_get_long(GET_PROPERTY_SILENT(&exception, "code")); - zend_error_helper(code, file->val, line, "%s", message->val); + if (ce_exception == type_exception_ce && strstr(message->val, ", called in ")) { + zend_error_helper(code, file->val, line, "%s and defined", message->val); + } else { + zend_error_helper(code, file->val, line, "%s", message->val); + } + zend_string_release(file); zend_string_release(message); OBJ_RELEASE(ex); diff --git a/Zend/zend_exceptions.h b/Zend/zend_exceptions.h index 10609fecb6..e2c1f1fac7 100644 --- a/Zend/zend_exceptions.h +++ b/Zend/zend_exceptions.h @@ -39,6 +39,7 @@ ZEND_API zend_class_entry *zend_exception_get_default(void); ZEND_API zend_class_entry *zend_get_error_exception(void); ZEND_API zend_class_entry *zend_get_engine_exception(void); ZEND_API zend_class_entry *zend_get_parse_exception(void); +ZEND_API zend_class_entry *zend_get_type_exception(void); ZEND_API void zend_register_default_classes(void); /* exception_ce NULL or zend_exception_get_default() or a derived class diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index caaeb6a5fd..0fe2a26e97 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -598,7 +598,7 @@ ZEND_API char * zend_verify_arg_class_kind(const zend_arg_info *cur_arg_info, ch } } -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_arg_error(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_execute_data *ptr = EG(current_execute_data)->prev_execute_data; const char *fname = zf->common.function_name->val; @@ -621,16 +621,18 @@ ZEND_API void zend_verify_arg_error(int error_type, const zend_function *zf, uin } if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) { - zend_error(error_type, "Argument %d passed to %s%s%s() must %s%s, %s%s given, called in %s on line %d and defined", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind, ptr->func->op_array.filename->val, ptr->opline->lineno); + zend_type_error("Argument %d passed to %s%s%s() must %s%s, %s%s given, called in %s on line %d", + arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind, + ptr->func->op_array.filename->val, ptr->opline->lineno); } else { - zend_error(error_type, "Argument %d passed to %s%s%s() must %s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind); } if (arg) { ZVAL_COPY_VALUE(arg, &old_arg); } } else { - zend_error(error_type, "Argument %d passed to %s%s%s() must %s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, given_msg, given_kind); } } @@ -649,10 +651,71 @@ static int is_null_constant(zval *default_value) return 0; } +static zend_bool zend_verify_weak_scalar_type_hint(zend_uchar type_hint, zval *arg) +{ + switch (type_hint) { + case _IS_BOOL: { + zend_bool dest; + + if (!zend_parse_arg_bool_weak(arg, &dest)) { + return 0; + } + zval_ptr_dtor(arg); + ZVAL_BOOL(arg, dest); + return 1; + } + case IS_LONG: { + zend_long dest; + + if (!zend_parse_arg_long_weak(arg, &dest)) { + return 0; + } + zval_ptr_dtor(arg); + ZVAL_LONG(arg, dest); + return 1; + } + case IS_DOUBLE: { + double dest; + + if (!zend_parse_arg_double_weak(arg, &dest)) { + return 0; + } + zval_ptr_dtor(arg); + ZVAL_DOUBLE(arg, dest); + return 1; + } + case IS_STRING: { + zend_string *dest; + + /* on success "arg" is converted to IS_STRING */ + if (!zend_parse_arg_str_weak(arg, &dest)) { + return 0; + } + return 1; + } + default: + return 0; + } +} + +static zend_bool zend_verify_scalar_type_hint(zend_uchar type_hint, zval *arg, zend_bool strict) +{ + if (UNEXPECTED(strict)) { + /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */ + if (type_hint != IS_DOUBLE || Z_TYPE_P(arg) != IS_LONG) { + return 0; + } + } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { + /* NULL may be accepted only by nullable hints (this is already checked) */ + return 0; + } + return zend_verify_weak_scalar_type_hint(type_hint, arg); +} + static void zend_verify_internal_arg_type(zend_function *zf, uint32_t arg_num, zval *arg) { zend_internal_arg_info *cur_arg_info; - char *need_msg; + char *need_msg, *class_name; zend_class_entry *ce; if (EXPECTED(arg_num <= zf->internal_function.num_args)) { @@ -663,33 +726,29 @@ static void zend_verify_internal_arg_type(zend_function *zf, uint32_t arg_num, z return; } - if (cur_arg_info->class_name) { - char *class_name; - + if (cur_arg_info->type_hint) { ZVAL_DEREF(arg); - if (Z_TYPE_P(arg) == IS_OBJECT) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce); - if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, need_msg, class_name, "instance of ", Z_OBJCE_P(arg)->name->val, arg); + if (EXPECTED(cur_arg_info->type_hint == Z_TYPE_P(arg))) { + if (cur_arg_info->class_name) { + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce); + if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { + zend_verify_arg_error(zf, arg_num, need_msg, class_name, "instance of ", Z_OBJCE_P(arg)->name->val, arg); + } } } else if (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce); - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), "", arg); - } - } else if (cur_arg_info->type_hint) { - if (cur_arg_info->type_hint == IS_ARRAY) { - ZVAL_DEREF(arg); - if (Z_TYPE_P(arg) != IS_ARRAY && (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null)) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, "be of the type array", "", zend_zval_type_name(arg), "", arg); - } - } else if (cur_arg_info->type_hint == IS_CALLABLE) { - if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) && (Z_TYPE_P(arg) != IS_NULL || !cur_arg_info->allow_null)) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, "be callable", "", zend_zval_type_name(arg), "", arg); + if (cur_arg_info->class_name) { + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info*)cur_arg_info, &class_name, &ce); + zend_verify_arg_error(zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), "", arg); + } else if (cur_arg_info->type_hint == IS_CALLABLE) { + if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + zend_verify_arg_error(zf, arg_num, "be callable", "", zend_zval_type_name(arg), "", arg); + } + } else if (cur_arg_info->type_hint == _IS_BOOL && + EXPECTED(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE)) { + /* pass */ + } else if (UNEXPECTED(!zend_verify_scalar_type_hint(cur_arg_info->type_hint, arg, ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data))))) { + zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), zend_zval_type_name(arg), "", arg); } -#if ZEND_DEBUG - } else { - zend_error(E_ERROR, "Unknown typehint"); -#endif } } } @@ -697,7 +756,7 @@ static void zend_verify_internal_arg_type(zend_function *zf, uint32_t arg_num, z static void zend_verify_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, zval *default_value) { zend_arg_info *cur_arg_info; - char *need_msg; + char *need_msg, *class_name; zend_class_entry *ce; if (EXPECTED(arg_num <= zf->common.num_args)) { @@ -708,33 +767,29 @@ static void zend_verify_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, return; } - if (cur_arg_info->class_name) { - char *class_name; - + if (cur_arg_info->type_hint) { ZVAL_DEREF(arg); - if (Z_TYPE_P(arg) == IS_OBJECT) { - need_msg = zend_verify_arg_class_kind(cur_arg_info, &class_name, &ce); - if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, need_msg, class_name, "instance of ", Z_OBJCE_P(arg)->name->val, arg); + if (EXPECTED(cur_arg_info->type_hint == Z_TYPE_P(arg))) { + if (cur_arg_info->class_name) { + need_msg = zend_verify_arg_class_kind(cur_arg_info, &class_name, &ce); + if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { + zend_verify_arg_error(zf, arg_num, need_msg, class_name, "instance of ", Z_OBJCE_P(arg)->name->val, arg); + } } } else if (Z_TYPE_P(arg) != IS_NULL || !(cur_arg_info->allow_null || (default_value && is_null_constant(default_value)))) { - need_msg = zend_verify_arg_class_kind(cur_arg_info, &class_name, &ce); - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), "", arg); - } - } else if (cur_arg_info->type_hint) { - if (cur_arg_info->type_hint == IS_ARRAY) { - ZVAL_DEREF(arg); - if (Z_TYPE_P(arg) != IS_ARRAY && (Z_TYPE_P(arg) != IS_NULL || !(cur_arg_info->allow_null || (default_value && is_null_constant(default_value))))) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, "be of the type array", "", zend_zval_type_name(arg), "", arg); - } - } else if (cur_arg_info->type_hint == IS_CALLABLE) { - if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) && (Z_TYPE_P(arg) != IS_NULL || !(cur_arg_info->allow_null || (default_value && is_null_constant(default_value))))) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, "be callable", "", zend_zval_type_name(arg), "", arg); + if (cur_arg_info->class_name) { + need_msg = zend_verify_arg_class_kind(cur_arg_info, &class_name, &ce); + zend_verify_arg_error(zf, arg_num, need_msg, class_name, zend_zval_type_name(arg), "", arg); + } else if (cur_arg_info->type_hint == IS_CALLABLE) { + if (!zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + zend_verify_arg_error(zf, arg_num, "be callable", "", zend_zval_type_name(arg), "", arg); + } + } else if (cur_arg_info->type_hint == _IS_BOOL && + EXPECTED(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE)) { + /* pass */ + } else if (UNEXPECTED(!zend_verify_scalar_type_hint(cur_arg_info->type_hint, arg, ZEND_ARG_USES_STRICT_TYPES()))) { + zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), zend_zval_type_name(arg), "", arg); } -#if ZEND_DEBUG - } else { - zend_error(E_ERROR, "Unknown typehint"); -#endif } } } @@ -753,21 +808,17 @@ static inline int zend_verify_missing_arg_type(zend_function *zf, uint32_t arg_n return 1; } - if (cur_arg_info->class_name) { - char *class_name; + if (cur_arg_info->type_hint) { + if (cur_arg_info->class_name) { + char *class_name; - need_msg = zend_verify_arg_class_kind(cur_arg_info, &class_name, &ce); - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, need_msg, class_name, "none", "", NULL); - return 0; - } else if (cur_arg_info->type_hint) { - if (cur_arg_info->type_hint == IS_ARRAY) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, "be of the type array", "", "none", "", NULL); + need_msg = zend_verify_arg_class_kind(cur_arg_info, &class_name, &ce); + zend_verify_arg_error(zf, arg_num, need_msg, class_name, "none", "", NULL); + return 0; } else if (cur_arg_info->type_hint == IS_CALLABLE) { - zend_verify_arg_error(E_EXCEPTION | E_ERROR, zf, arg_num, "be callable", "", "none", "", NULL); -#if ZEND_DEBUG + zend_verify_arg_error(zf, arg_num, "be callable", "", "none", "", NULL); } else { - zend_error(E_ERROR, "Unknown typehint"); -#endif + zend_verify_arg_error(zf, arg_num, "be of the type ", zend_get_type_by_const(cur_arg_info->type_hint), "none", "", NULL); } return 0; } @@ -791,7 +842,7 @@ 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) +ZEND_API void zend_verify_return_error(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; @@ -805,8 +856,31 @@ ZEND_API void zend_verify_return_error(int error_type, const zend_function *zf, fclass = ""; } - zend_error(error_type, - "Return value of %s%s%s() must %s%s, %s%s returned", + if (zf->common.type == ZEND_USER_FUNCTION) { + zend_type_error("Return value of %s%s%s() must %s%s, %s%s returned in %s on line %d", + fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind, + zf->op_array.filename->val, EG(current_execute_data)->opline->lineno); + } else { + zend_type_error("Return value of %s%s%s() must %s%s, %s%s returned", + fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind); + } +} + +ZEND_API void zend_verify_internal_return_error(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(E_CORE_ERROR, "Return value of %s%s%s() must %s%s, %s%s returned", fclass, fsep, fname, need_msg, need_kind, returned_msg, returned_kind); } @@ -814,39 +888,36 @@ ZEND_API void zend_verify_return_error(int error_type, const zend_function *zf, static int zend_verify_internal_return_type(zend_function *zf, zval *ret) { zend_arg_info *ret_info = zf->common.arg_info - 1; - char *need_msg; + char *need_msg, *class_name; zend_class_entry *ce; - if (ret_info->class_name) { - char *class_name; - if (Z_TYPE_P(ret) == IS_OBJECT) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce); - if (!ce || !instanceof_function(Z_OBJCE_P(ret), ce)) { - zend_verify_return_error(E_CORE_ERROR, zf, need_msg, class_name, "instance of ", Z_OBJCE_P(ret)->name->val); - return 0; + if (ret_info->type_hint) { + if (EXPECTED(ret_info->type_hint == Z_TYPE_P(ret))) { + if (ret_info->class_name) { + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce); + if (!ce || !instanceof_function(Z_OBJCE_P(ret), ce)) { + zend_verify_internal_return_error(zf, need_msg, class_name, "instance of ", Z_OBJCE_P(ret)->name->val); + return 0; + } } } else if (Z_TYPE_P(ret) != IS_NULL || !ret_info->allow_null) { - need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce); - zend_verify_return_error(E_CORE_ERROR, zf, need_msg, class_name, zend_zval_type_name(ret), ""); - return 0; - } - } 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_CORE_ERROR, zf, "be of the type array", "", zend_zval_type_name(ret), ""); - return 0; - } - } 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_CORE_ERROR, zf, "be callable", "", zend_zval_type_name(ret), ""); + if (ret_info->class_name) { + need_msg = zend_verify_internal_arg_class_kind((zend_internal_arg_info *)ret_info, &class_name, &ce); + zend_verify_internal_return_error(zf, need_msg, class_name, 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_internal_return_error(zf, "be callable", "", zend_zval_type_name(ret), ""); + return 0; + } + } else if (ret_info->type_hint == _IS_BOOL && + EXPECTED(Z_TYPE_P(ret) == IS_FALSE || Z_TYPE_P(ret) == IS_TRUE)) { + /* pass */ + } else { + /* Use strict check to verify return value of internal function */ + zend_verify_internal_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), zend_zval_type_name(ret), ""); return 0; } -#if ZEND_DEBUG - } else { - zend_error(E_CORE_ERROR, "Unknown typehint"); - return 0; -#endif } } return 1; @@ -856,34 +927,31 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) static void zend_verify_return_type(zend_function *zf, zval *ret) { zend_arg_info *ret_info = zf->common.arg_info - 1; - char *need_msg; + char *need_msg, *class_name; 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_EXCEPTION | E_ERROR, zf, need_msg, class_name, "instance of ", Z_OBJCE_P(ret)->name->val); + if (ret_info->type_hint) { + if (EXPECTED(ret_info->type_hint == Z_TYPE_P(ret))) { + if (ret_info->class_name) { + 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(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_EXCEPTION | E_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_EXCEPTION | E_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_EXCEPTION | E_ERROR, zf, "be callable", "", zend_zval_type_name(ret), ""); + if (ret_info->class_name) { + need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce); + zend_verify_return_error(zf, need_msg, class_name, zend_zval_type_name(ret), ""); + } else if (ret_info->type_hint == IS_CALLABLE) { + if (!zend_is_callable(ret, IS_CALLABLE_CHECK_SILENT, NULL)) { + zend_verify_return_error(zf, "be callable", "", zend_zval_type_name(ret), ""); + } + } else if (ret_info->type_hint == _IS_BOOL && + EXPECTED(Z_TYPE_P(ret) == IS_FALSE || Z_TYPE_P(ret) == IS_TRUE)) { + /* pass */ + } else if (UNEXPECTED(!zend_verify_scalar_type_hint(ret_info->type_hint, ret, ZEND_RET_USES_STRICT_TYPES()))) { + zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), zend_zval_type_name(ret), ""); } -#if ZEND_DEBUG - } else { - zend_error(E_ERROR, "Unknown typehint"); -#endif } } } @@ -894,21 +962,17 @@ static inline int zend_verify_missing_return_type(zend_function *zf) char *need_msg; zend_class_entry *ce; - if (ret_info->class_name) { - char *class_name; + if (ret_info->type_hint) { + 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_EXCEPTION | E_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_EXCEPTION | E_ERROR, zf, "be of the type array", "", "none", ""); + need_msg = zend_verify_arg_class_kind(ret_info, &class_name, &ce); + zend_verify_return_error(zf, need_msg, class_name, "none", ""); + return 0; } else if (ret_info->type_hint == IS_CALLABLE) { - zend_verify_return_error(E_EXCEPTION | E_ERROR, zf, "be callable", "", "none", ""); -#if ZEND_DEBUG + zend_verify_return_error(zf, "be callable", "", "none", ""); } else { - zend_error(E_ERROR, "Unknown typehint"); -#endif + zend_verify_return_error(zf, "be of the type ", zend_get_type_by_const(ret_info->type_hint), "none", ""); } return 0; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 0149da6929..239185ccb7 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -49,8 +49,9 @@ 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); +ZEND_API void zend_verify_arg_error(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(const zend_function *zf, const char *need_msg, const char *need_kind, const char *returned_msg, const char *returned_kind); +ZEND_API void zend_verify_internal_return_error(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_types.h b/Zend/zend_types.h index 0cfd8fca90..39464df282 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -313,6 +313,11 @@ static zend_always_inline zend_uchar zval_get_type(const zval* pz) { return pz->u1.v.type; } +#define ZEND_SAME_FAKE_TYPE(faketype, realtype) ( \ + (faketype) == (realtype) \ + || ((faketype) == _IS_BOOL && ((realtype) == IS_TRUE || (realtype) == IS_FALSE)) \ +) + /* we should never set just Z_TYPE, we should set Z_TYPE_INFO */ #define Z_TYPE(zval) zval_get_type(&(zval)) #define Z_TYPE_P(zval_p) Z_TYPE(*(zval_p)) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index ab2c7b89a3..95408ebdd4 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3133,7 +3133,7 @@ ZEND_VM_HANDLER(118, ZEND_INIT_USER_CALL, CONST, CONST|TMPVAR|CV) } } } else { - zend_error(E_WARNING, "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); + zend_internal_type_error(EX_USES_STRICT_TYPES(), "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); efree(error); func = (zend_function*)&zend_pass_function; called_scope = NULL; @@ -3589,15 +3589,39 @@ ZEND_VM_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV, UNUSED) #if !defined(ZEND_VM_SPEC) || (OP1_TYPE != IS_UNUSED) USE_OPLINE #endif + SAVE_OPLINE(); if (OP1_TYPE == IS_UNUSED) { zend_verify_missing_return_type(EX(func)); } else { +/* prevents "undefined variable opline" errors */ +#if !defined(ZEND_VM_SPEC) || (OP1_TYPE != IS_UNUSED) zval *retval_ptr; zend_free_op free_op1; + zend_arg_info *ret_info = EX(func)->common.arg_info - 1; + + retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R); + + if (OP1_TYPE == IS_CONST) { + ZVAL_COPY(EX_VAR(opline->result.var), retval_ptr); + retval_ptr = EX_VAR(opline->result.var); + } - retval_ptr = GET_OP1_ZVAL_PTR_DEREF(BP_VAR_R); + if (UNEXPECTED(!ret_info->class_name + && ret_info->type_hint != IS_CALLABLE + && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)))) { + /* A cast or an error will happen, so separate the zval to prevent overwriting it */ + + if (EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) == 0)) { + /* Does not return by reference */ + SEPARATE_ZVAL(retval_ptr); + } else { + ZVAL_DEREF(retval_ptr); + SEPARATE_ZVAL_NOREF(retval_ptr); + } + } zend_verify_return_type(EX(func), retval_ptr); +#endif } CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); @@ -4169,7 +4193,7 @@ ZEND_VM_HANDLER(119, ZEND_SEND_ARRAY, ANY, ANY) ZEND_VM_C_GOTO(send_array); } } - zend_error(E_WARNING, "call_user_func_array() expects parameter 2 to be array, %s given", zend_get_type_by_const(Z_TYPE_P(args))); + zend_internal_type_error(EX_USES_STRICT_TYPES(), "call_user_func_array() expects parameter 2 to be array, %s given", zend_get_type_by_const(Z_TYPE_P(args))); if (EX(call)->func->common.fn_flags & ZEND_ACC_CLOSURE) { OBJ_RELEASE((zend_object*)EX(call)->func->common.prototype); } @@ -7250,32 +7274,29 @@ ZEND_VM_C_LABEL(try_strlen): if (EXPECTED(Z_TYPE_P(value) == IS_STRING)) { ZVAL_LONG(EX_VAR(opline->result.var), Z_STRLEN_P(value)); } else { + zend_bool strict; + if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_TYPE_P(value) == IS_REFERENCE) { value = Z_REFVAL_P(value); ZEND_VM_C_GOTO(try_strlen); - } else if (Z_TYPE_P(value) < IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 0); - } else if (Z_TYPE_P(value) == IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 1); - } else if (Z_TYPE_P(value) <= IS_DOUBLE) { - zend_string *str = zval_get_string(value); - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zend_string_release(str); - } else if (Z_TYPE_P(value) == IS_OBJECT) { - zend_string *str; - zval tmp; - - ZVAL_COPY(&tmp, value); - if (parse_arg_object_to_str(&tmp, &str, IS_STRING) == FAILURE) { - ZEND_VM_C_GOTO(strlen_error); - } - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zval_dtor(&tmp); - } else { -ZEND_VM_C_LABEL(strlen_error): - zend_error(E_WARNING, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); - ZVAL_NULL(EX_VAR(opline->result.var)); } + strict = EX_USES_STRICT_TYPES(); + do { + if (EXPECTED(!strict)) { + zend_string *str; + zval tmp; + + ZVAL_COPY(&tmp, value); + if (zend_parse_arg_str_weak(&tmp, &str)) { + ZVAL_LONG(EX_VAR(opline->result.var), str->len); + zval_ptr_dtor(&tmp); + break; + } + zval_ptr_dtor(&tmp); + } + zend_internal_type_error(strict, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } while (0); } FREE_OP1(); CHECK_EXCEPTION(); @@ -7373,4 +7394,5 @@ ZEND_VM_HANDLER(157, ZEND_FETCH_CLASS_NAME, ANY, ANY) ZVAL_EMPTY_STRING(EX_VAR(opline->result.var)); } ZEND_VM_NEXT_OPCODE(); -}
\ No newline at end of file +} + diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index e5547f3e1e..eb551ee15f 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1098,7 +1098,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_ARRAY_SPEC_HANDLER(ZEND_O goto send_array; } } - zend_error(E_WARNING, "call_user_func_array() expects parameter 2 to be array, %s given", zend_get_type_by_const(Z_TYPE_P(args))); + zend_internal_type_error(EX_USES_STRICT_TYPES(), "call_user_func_array() expects parameter 2 to be array, %s given", zend_get_type_by_const(Z_TYPE_P(args))); if (EX(call)->func->common.fn_flags & ZEND_ACC_CLOSURE) { OBJ_RELEASE((zend_object*)EX(call)->func->common.prototype); } @@ -1789,7 +1789,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_NAME_SPEC_HANDLER( ZVAL_EMPTY_STRING(EX_VAR(opline->result.var)); } ZEND_VM_NEXT_OPCODE(); -}static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +} + +static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CLASS_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -3808,32 +3810,29 @@ try_strlen: if (EXPECTED(Z_TYPE_P(value) == IS_STRING)) { ZVAL_LONG(EX_VAR(opline->result.var), Z_STRLEN_P(value)); } else { + zend_bool strict; + if ((IS_CONST & (IS_VAR|IS_CV)) && Z_TYPE_P(value) == IS_REFERENCE) { value = Z_REFVAL_P(value); goto try_strlen; - } else if (Z_TYPE_P(value) < IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 0); - } else if (Z_TYPE_P(value) == IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 1); - } else if (Z_TYPE_P(value) <= IS_DOUBLE) { - zend_string *str = zval_get_string(value); - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zend_string_release(str); - } else if (Z_TYPE_P(value) == IS_OBJECT) { - zend_string *str; - zval tmp; - - ZVAL_COPY(&tmp, value); - if (parse_arg_object_to_str(&tmp, &str, IS_STRING) == FAILURE) { - goto strlen_error; - } - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zval_dtor(&tmp); - } else { -strlen_error: - zend_error(E_WARNING, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); - ZVAL_NULL(EX_VAR(opline->result.var)); } + strict = EX_USES_STRICT_TYPES(); + do { + if (EXPECTED(!strict)) { + zend_string *str; + zval tmp; + + ZVAL_COPY(&tmp, value); + if (zend_parse_arg_str_weak(&tmp, &str)) { + ZVAL_LONG(EX_VAR(opline->result.var), str->len); + zval_ptr_dtor(&tmp); + break; + } + zval_ptr_dtor(&tmp); + } + zend_internal_type_error(strict, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } while (0); } CHECK_EXCEPTION(); @@ -5097,7 +5096,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CONS } } } else { - zend_error(E_WARNING, "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); + zend_internal_type_error(EX_USES_STRICT_TYPES(), "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); efree(error); func = (zend_function*)&zend_pass_function; called_scope = NULL; @@ -6958,15 +6957,39 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CONST_ #if 0 || (IS_CONST != IS_UNUSED) USE_OPLINE #endif + SAVE_OPLINE(); if (IS_CONST == IS_UNUSED) { zend_verify_missing_return_type(EX(func)); } else { +/* prevents "undefined variable opline" errors */ +#if 0 || (IS_CONST != IS_UNUSED) zval *retval_ptr; + zend_arg_info *ret_info = EX(func)->common.arg_info - 1; retval_ptr = EX_CONSTANT(opline->op1); + + if (IS_CONST == IS_CONST) { + ZVAL_COPY(EX_VAR(opline->result.var), retval_ptr); + retval_ptr = EX_VAR(opline->result.var); + } + + if (UNEXPECTED(!ret_info->class_name + && ret_info->type_hint != IS_CALLABLE + && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)))) { + /* A cast or an error will happen, so separate the zval to prevent overwriting it */ + + if (EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) == 0)) { + /* Does not return by reference */ + SEPARATE_ZVAL(retval_ptr); + } else { + ZVAL_DEREF(retval_ptr); + SEPARATE_ZVAL_NOREF(retval_ptr); + } + } zend_verify_return_type(EX(func), retval_ptr); +#endif } CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); @@ -8415,7 +8438,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_CV_H } } } else { - zend_error(E_WARNING, "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); + zend_internal_type_error(EX_USES_STRICT_TYPES(), "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); efree(error); func = (zend_function*)&zend_pass_function; called_scope = NULL; @@ -9928,7 +9951,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_INIT_USER_CALL_SPEC_CONST_TMPV } } } else { - zend_error(E_WARNING, "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); + zend_internal_type_error(EX_USES_STRICT_TYPES(), "%s() expects parameter 1 to be a valid callback, %s", Z_STRVAL_P(EX_CONSTANT(opline->op1)), error); efree(error); func = (zend_function*)&zend_pass_function; called_scope = NULL; @@ -11900,15 +11923,39 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN #if 0 || (IS_TMP_VAR != IS_UNUSED) USE_OPLINE #endif + SAVE_OPLINE(); if (IS_TMP_VAR == IS_UNUSED) { zend_verify_missing_return_type(EX(func)); } else { +/* prevents "undefined variable opline" errors */ +#if 0 || (IS_TMP_VAR != IS_UNUSED) zval *retval_ptr; zend_free_op free_op1; + zend_arg_info *ret_info = EX(func)->common.arg_info - 1; retval_ptr = _get_zval_ptr_tmp(opline->op1.var, execute_data, &free_op1); + + if (IS_TMP_VAR == IS_CONST) { + ZVAL_COPY(EX_VAR(opline->result.var), retval_ptr); + retval_ptr = EX_VAR(opline->result.var); + } + + if (UNEXPECTED(!ret_info->class_name + && ret_info->type_hint != IS_CALLABLE + && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)))) { + /* A cast or an error will happen, so separate the zval to prevent overwriting it */ + + if (EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) == 0)) { + /* Does not return by reference */ + SEPARATE_ZVAL(retval_ptr); + } else { + ZVAL_DEREF(retval_ptr); + SEPARATE_ZVAL_NOREF(retval_ptr); + } + } zend_verify_return_type(EX(func), retval_ptr); +#endif } CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); @@ -17242,15 +17289,39 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN #if 0 || (IS_VAR != IS_UNUSED) USE_OPLINE #endif + SAVE_OPLINE(); if (IS_VAR == IS_UNUSED) { zend_verify_missing_return_type(EX(func)); } else { +/* prevents "undefined variable opline" errors */ +#if 0 || (IS_VAR != IS_UNUSED) zval *retval_ptr; zend_free_op free_op1; + zend_arg_info *ret_info = EX(func)->common.arg_info - 1; + + retval_ptr = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); + + if (IS_VAR == IS_CONST) { + ZVAL_COPY(EX_VAR(opline->result.var), retval_ptr); + retval_ptr = EX_VAR(opline->result.var); + } + + if (UNEXPECTED(!ret_info->class_name + && ret_info->type_hint != IS_CALLABLE + && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)))) { + /* A cast or an error will happen, so separate the zval to prevent overwriting it */ - retval_ptr = _get_zval_ptr_var_deref(opline->op1.var, execute_data, &free_op1); + if (EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) == 0)) { + /* Does not return by reference */ + SEPARATE_ZVAL(retval_ptr); + } else { + ZVAL_DEREF(retval_ptr); + SEPARATE_ZVAL_NOREF(retval_ptr); + } + } zend_verify_return_type(EX(func), retval_ptr); +#endif } CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); @@ -22841,15 +22912,39 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED #if 0 || (IS_UNUSED != IS_UNUSED) USE_OPLINE #endif + SAVE_OPLINE(); if (IS_UNUSED == IS_UNUSED) { zend_verify_missing_return_type(EX(func)); } else { +/* prevents "undefined variable opline" errors */ +#if 0 || (IS_UNUSED != IS_UNUSED) zval *retval_ptr; + zend_arg_info *ret_info = EX(func)->common.arg_info - 1; retval_ptr = NULL; + + if (IS_UNUSED == IS_CONST) { + ZVAL_COPY(EX_VAR(opline->result.var), retval_ptr); + retval_ptr = EX_VAR(opline->result.var); + } + + if (UNEXPECTED(!ret_info->class_name + && ret_info->type_hint != IS_CALLABLE + && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)))) { + /* A cast or an error will happen, so separate the zval to prevent overwriting it */ + + if (EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) == 0)) { + /* Does not return by reference */ + SEPARATE_ZVAL(retval_ptr); + } else { + ZVAL_DEREF(retval_ptr); + SEPARATE_ZVAL_NOREF(retval_ptr); + } + } zend_verify_return_type(EX(func), retval_ptr); +#endif } CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); @@ -27245,32 +27340,29 @@ try_strlen: if (EXPECTED(Z_TYPE_P(value) == IS_STRING)) { ZVAL_LONG(EX_VAR(opline->result.var), Z_STRLEN_P(value)); } else { + zend_bool strict; + if ((IS_CV & (IS_VAR|IS_CV)) && Z_TYPE_P(value) == IS_REFERENCE) { value = Z_REFVAL_P(value); goto try_strlen; - } else if (Z_TYPE_P(value) < IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 0); - } else if (Z_TYPE_P(value) == IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 1); - } else if (Z_TYPE_P(value) <= IS_DOUBLE) { - zend_string *str = zval_get_string(value); - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zend_string_release(str); - } else if (Z_TYPE_P(value) == IS_OBJECT) { - zend_string *str; - zval tmp; - - ZVAL_COPY(&tmp, value); - if (parse_arg_object_to_str(&tmp, &str, IS_STRING) == FAILURE) { - goto strlen_error; - } - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zval_dtor(&tmp); - } else { -strlen_error: - zend_error(E_WARNING, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); - ZVAL_NULL(EX_VAR(opline->result.var)); } + strict = EX_USES_STRICT_TYPES(); + do { + if (EXPECTED(!strict)) { + zend_string *str; + zval tmp; + + ZVAL_COPY(&tmp, value); + if (zend_parse_arg_str_weak(&tmp, &str)) { + ZVAL_LONG(EX_VAR(opline->result.var), str->len); + zval_ptr_dtor(&tmp); + break; + } + zval_ptr_dtor(&tmp); + } + zend_internal_type_error(strict, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } while (0); } CHECK_EXCEPTION(); @@ -31767,15 +31859,39 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU #if 0 || (IS_CV != IS_UNUSED) USE_OPLINE #endif + SAVE_OPLINE(); if (IS_CV == IS_UNUSED) { zend_verify_missing_return_type(EX(func)); } else { +/* prevents "undefined variable opline" errors */ +#if 0 || (IS_CV != IS_UNUSED) zval *retval_ptr; + zend_arg_info *ret_info = EX(func)->common.arg_info - 1; + + retval_ptr = _get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op1.var); + + if (IS_CV == IS_CONST) { + ZVAL_COPY(EX_VAR(opline->result.var), retval_ptr); + retval_ptr = EX_VAR(opline->result.var); + } + + if (UNEXPECTED(!ret_info->class_name + && ret_info->type_hint != IS_CALLABLE + && !ZEND_SAME_FAKE_TYPE(ret_info->type_hint, Z_TYPE_P(retval_ptr)))) { + /* A cast or an error will happen, so separate the zval to prevent overwriting it */ - retval_ptr = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var); + if (EXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) == 0)) { + /* Does not return by reference */ + SEPARATE_ZVAL(retval_ptr); + } else { + ZVAL_DEREF(retval_ptr); + SEPARATE_ZVAL_NOREF(retval_ptr); + } + } zend_verify_return_type(EX(func), retval_ptr); +#endif } CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); @@ -37492,32 +37608,29 @@ try_strlen: if (EXPECTED(Z_TYPE_P(value) == IS_STRING)) { ZVAL_LONG(EX_VAR(opline->result.var), Z_STRLEN_P(value)); } else { + zend_bool strict; + if (((IS_TMP_VAR|IS_VAR) & (IS_VAR|IS_CV)) && Z_TYPE_P(value) == IS_REFERENCE) { value = Z_REFVAL_P(value); goto try_strlen; - } else if (Z_TYPE_P(value) < IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 0); - } else if (Z_TYPE_P(value) == IS_TRUE) { - ZVAL_LONG(EX_VAR(opline->result.var), 1); - } else if (Z_TYPE_P(value) <= IS_DOUBLE) { - zend_string *str = zval_get_string(value); - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zend_string_release(str); - } else if (Z_TYPE_P(value) == IS_OBJECT) { - zend_string *str; - zval tmp; - - ZVAL_COPY(&tmp, value); - if (parse_arg_object_to_str(&tmp, &str, IS_STRING) == FAILURE) { - goto strlen_error; - } - ZVAL_LONG(EX_VAR(opline->result.var), str->len); - zval_dtor(&tmp); - } else { -strlen_error: - zend_error(E_WARNING, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); - ZVAL_NULL(EX_VAR(opline->result.var)); } + strict = EX_USES_STRICT_TYPES(); + do { + if (EXPECTED(!strict)) { + zend_string *str; + zval tmp; + + ZVAL_COPY(&tmp, value); + if (zend_parse_arg_str_weak(&tmp, &str)) { + ZVAL_LONG(EX_VAR(opline->result.var), str->len); + zval_ptr_dtor(&tmp); + break; + } + zval_ptr_dtor(&tmp); + } + zend_internal_type_error(strict, "strlen() expects parameter 1 to be string, %s given", zend_get_type_by_const(Z_TYPE_P(value))); + ZVAL_NULL(EX_VAR(opline->result.var)); + } while (0); } zval_ptr_dtor_nogc(free_op1); CHECK_EXCEPTION(); diff --git a/ext/standard/tests/array/krsort_object.phpt b/ext/standard/tests/array/krsort_object.phpt index 36d8589a82..6236595796 100644 --- a/ext/standard/tests/array/krsort_object.phpt +++ b/ext/standard/tests/array/krsort_object.phpt @@ -15,7 +15,7 @@ Test krsort() function : object functionality - sort objects echo "*** Testing krsort() : object functionality ***\n"; // class declaration for integer objects -class Integer +class IntegerObject { public $class_value; // initializing object member value @@ -25,7 +25,7 @@ class Integer } // class declaration for string objects -class String +class StringObject { public $class_value; // initializing object member value @@ -42,17 +42,17 @@ class String // array of integer objects with different key values $unsorted_int_obj = array ( - 10 => new Integer(11), 20 => new Integer(66), - 3 => new Integer(23), 4 => new Integer(-5), - 50 => new Integer(0.001), 6 => new Integer(0) + 10 => new IntegerObject(11), 20 => new IntegerObject(66), + 3 => new IntegerObject(23), 4 => new IntegerObject(-5), + 50 => new IntegerObject(0.001), 6 => new IntegerObject(0) ); // array of string objects with different key values $unsorted_str_obj = array ( - "axx" => new String("axx"), "t" => new String("t"), - "w" => new String("w"), "py" => new String("py"), - "apple" => new String("apple"), "Orange" => new String("Orange"), - "Lemon" => new String("Lemon"), "aPPle" => new String("aPPle") + "axx" => new StringObject("axx"), "t" => new StringObject("t"), + "w" => new StringObject("w"), "py" => new StringObject("py"), + "apple" => new StringObject("apple"), "Orange" => new StringObject("Orange"), + "Lemon" => new StringObject("Lemon"), "aPPle" => new StringObject("aPPle") ); @@ -88,32 +88,32 @@ echo "Done\n"; bool(true) array(6) { [50]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> float(0.001) } [20]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(66) } [10]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(11) } [6]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(0) } [4]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(-5) } [3]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(23) } @@ -121,42 +121,42 @@ array(6) { bool(true) array(8) { ["w"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "w" } ["t"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "t" } ["py"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(2) "py" } ["axx"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(3) "axx" } ["apple"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "apple" } ["aPPle"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "aPPle" } ["Orange"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(6) "Orange" } ["Lemon"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "Lemon" } @@ -166,32 +166,32 @@ array(8) { bool(true) array(6) { [50]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> float(0.001) } [20]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(66) } [10]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(11) } [6]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(0) } [4]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(-5) } [3]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(23) } @@ -199,42 +199,42 @@ array(6) { bool(true) array(8) { ["w"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "w" } ["t"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "t" } ["py"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(2) "py" } ["axx"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(3) "axx" } ["apple"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "apple" } ["aPPle"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "aPPle" } ["Orange"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(6) "Orange" } ["Lemon"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "Lemon" } diff --git a/ext/standard/tests/array/ksort_object.phpt b/ext/standard/tests/array/ksort_object.phpt index 20e8ba26eb..2d76026aa4 100644 --- a/ext/standard/tests/array/ksort_object.phpt +++ b/ext/standard/tests/array/ksort_object.phpt @@ -15,7 +15,7 @@ Test ksort() function : object functionality - sort objects echo "*** Testing ksort() : object functionality ***\n"; // class declaration for integer objects -class Integer +class IntegerObject { public $class_value; // initializing object member value @@ -26,7 +26,7 @@ class Integer } // class declaration for string objects -class String +class StringObject { public $class_value; // initializing object member value @@ -43,17 +43,17 @@ class String // array of integer objects $unsorted_int_obj = array ( - 11 => new Integer(11), 66 => new Integer(66), - 23 => new Integer(23), -5 => new Integer(-5), - 1 => new Integer(0.001), 0 => new Integer(0) + 11 => new IntegerObject(11), 66 => new IntegerObject(66), + 23 => new IntegerObject(23), -5 => new IntegerObject(-5), + 1 => new IntegerObject(0.001), 0 => new IntegerObject(0) ); // array of string objects $unsorted_str_obj = array ( - "axx" => new String("axx"), "t" => new String("t"), - "w" => new String("w"), "py" => new String("py"), - "apple" => new String("apple"), "Orange" => new String("Orange"), - "Lemon" => new String("Lemon"), "aPPle" => new String("aPPle") + "axx" => new StringObject("axx"), "t" => new StringObject("t"), + "w" => new StringObject("w"), "py" => new StringObject("py"), + "apple" => new StringObject("apple"), "Orange" => new StringObject("Orange"), + "Lemon" => new StringObject("Lemon"), "aPPle" => new StringObject("aPPle") ); echo "\n-- Testing ksort() by supplying various object arrays, 'flag' value is defualt --\n"; @@ -87,32 +87,32 @@ echo "Done\n"; bool(true) array(6) { [-5]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(-5) } [0]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(0) } [1]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> float(0.001) } [11]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(11) } [23]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(23) } [66]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(66) } @@ -120,42 +120,42 @@ array(6) { bool(true) array(8) { ["Lemon"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "Lemon" } ["Orange"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(6) "Orange" } ["aPPle"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "aPPle" } ["apple"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "apple" } ["axx"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(3) "axx" } ["py"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(2) "py" } ["t"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "t" } ["w"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "w" } @@ -165,32 +165,32 @@ array(8) { bool(true) array(6) { [-5]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(-5) } [0]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(0) } [1]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> float(0.001) } [11]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(11) } [23]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(23) } [66]=> - object(Integer)#%d (1) { + object(IntegerObject)#%d (1) { ["class_value"]=> int(66) } @@ -198,42 +198,42 @@ array(6) { bool(true) array(8) { ["Lemon"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "Lemon" } ["Orange"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(6) "Orange" } ["aPPle"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "aPPle" } ["apple"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(5) "apple" } ["axx"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(3) "axx" } ["py"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(2) "py" } ["t"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "t" } ["w"]=> - object(String)#%d (1) { + object(StringObject)#%d (1) { ["class_value"]=> string(1) "w" } diff --git a/ext/standard/tests/strings/lcfirst.phpt b/ext/standard/tests/strings/lcfirst.phpt Binary files differindex e603f4bbf8..6cbd213fbe 100644 --- a/ext/standard/tests/strings/lcfirst.phpt +++ b/ext/standard/tests/strings/lcfirst.phpt diff --git a/ext/standard/tests/strings/strlen.phpt b/ext/standard/tests/strings/strlen.phpt Binary files differindex df39f2469c..ab54445943 100644 --- a/ext/standard/tests/strings/strlen.phpt +++ b/ext/standard/tests/strings/strlen.phpt diff --git a/ext/standard/tests/strings/strpos.phpt b/ext/standard/tests/strings/strpos.phpt Binary files differindex b2bfedeb87..36854d1b37 100644 --- a/ext/standard/tests/strings/strpos.phpt +++ b/ext/standard/tests/strings/strpos.phpt diff --git a/ext/standard/tests/strings/strstr.phpt b/ext/standard/tests/strings/strstr.phpt Binary files differindex bdedb7e9f6..fd7f58ef1a 100644 --- a/ext/standard/tests/strings/strstr.phpt +++ b/ext/standard/tests/strings/strstr.phpt diff --git a/ext/standard/tests/strings/ucfirst.phpt b/ext/standard/tests/strings/ucfirst.phpt Binary files differindex 468f7f034e..8fb1a156b4 100644 --- a/ext/standard/tests/strings/ucfirst.phpt +++ b/ext/standard/tests/strings/ucfirst.phpt diff --git a/tests/lang/catchable_error_002.phpt b/tests/lang/catchable_error_002.phpt index a80928523b..bc585f7b10 100644 --- a/tests/lang/catchable_error_002.phpt +++ b/tests/lang/catchable_error_002.phpt @@ -25,5 +25,5 @@ Catchable fatal error [2] echo "ALIVE!\n"; ?> --EXPECTF-- -Argument 1 passed to blah() must be an instance of Foo, instance of stdClass given, called in %scatchable_error_002.php on line 18 and defined +Argument 1 passed to blah() must be an instance of Foo, instance of stdClass given, called in %scatchable_error_002.php on line %d ALIVE! |