diff --git a/Zend/tests/assert/expect_015.phpt b/Zend/tests/assert/expect_015.phpt index 72f13ff83f15f..769eed8270aa3 100644 --- a/Zend/tests/assert/expect_015.phpt +++ b/Zend/tests/assert/expect_015.phpt @@ -62,7 +62,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use ($c,&$d) : ?X { } })); -assert(0 && ($a = function &(array &$a, X $b = null) use ($c,&$d) : X { +assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use ($c,&$d) : X { final class A { final protected function f2() { if (!$x) { @@ -204,7 +204,7 @@ Warning: assert(): assert(0 && ($a = function &(array &$a, ?X $b = null) use($c, })) failed in %sexpect_015.php on line %d -Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null) use($c, &$d): X { +Warning: assert(): assert(0 && ($a = function &(array &$a, X $b = null, int|float $c) use($c, &$d): X { final class A { protected final function f2() { if (!$x) { diff --git a/Zend/tests/return_types/generators002.phpt b/Zend/tests/return_types/generators002.phpt index 90bada714b5c8..2e42f4b052880 100644 --- a/Zend/tests/return_types/generators002.phpt +++ b/Zend/tests/return_types/generators002.phpt @@ -6,4 +6,4 @@ function test1() : StdClass { yield 1; } --EXPECTF-- -Fatal error: Generators may only declare a return type of Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d +Fatal error: Generators may only declare a return type containing Generator, Iterator, Traversable, or iterable, StdClass is not permitted in %s on line %d diff --git a/Zend/tests/type_declarations/nullable_void.phpt b/Zend/tests/type_declarations/nullable_void.phpt index 4ff0edb0d8130..725c11bb594c2 100644 --- a/Zend/tests/type_declarations/nullable_void.phpt +++ b/Zend/tests/type_declarations/nullable_void.phpt @@ -8,4 +8,4 @@ function test() : ?void { ?> --EXPECTF-- -Fatal error: Void type cannot be nullable in %s on line %d +Fatal error: Void can only be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/typed_properties_043.phpt b/Zend/tests/type_declarations/typed_properties_043.phpt index eefe35879626c..79f01545e151d 100644 --- a/Zend/tests/type_declarations/typed_properties_043.phpt +++ b/Zend/tests/type_declarations/typed_properties_043.phpt @@ -41,9 +41,9 @@ var_dump(Bar::$selfProp, Bar::$selfNullProp, Bar::$parentProp); ?> --EXPECT-- -Cannot write a value to a 'self' typed static property of a trait -Cannot write a non-null value to a 'self' typed static property of a trait -Cannot access parent:: when current class scope has no parent +Cannot assign stdClass to property Test::$selfProp of type self +Cannot assign stdClass to property Test::$selfNullProp of type ?self +Cannot assign stdClass to property Test::$parentProp of type parent NULL object(Bar)#3 (0) { } diff --git a/Zend/tests/type_declarations/typed_properties_095.phpt b/Zend/tests/type_declarations/typed_properties_095.phpt index 3f1027f08f8b3..8470d4f437b8e 100644 --- a/Zend/tests/type_declarations/typed_properties_095.phpt +++ b/Zend/tests/type_declarations/typed_properties_095.phpt @@ -62,22 +62,26 @@ var_dump(_ZendTestClass::$staticIntProp); int(123) Cannot assign string to property _ZendTestClass::$intProp of type int Cannot assign _ZendTestClass to property _ZendTestClass::$classProp of type ?stdClass -object(_ZendTestClass)#1 (2) { +object(_ZendTestClass)#1 (3) { ["intProp"]=> int(456) ["classProp"]=> object(stdClass)#2 (0) { } + ["classUnionProp"]=> + NULL } int(123) Cannot assign string to property _ZendTestClass::$intProp of type int Cannot assign Test to property _ZendTestClass::$classProp of type ?stdClass -object(Test)#4 (2) { +object(Test)#4 (3) { ["intProp"]=> int(456) ["classProp"]=> object(stdClass)#1 (0) { } + ["classUnionProp"]=> + NULL } int(123) Cannot assign string to property _ZendTestClass::$staticIntProp of type int diff --git a/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt b/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt new file mode 100644 index 0000000000000..7c369090a546c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/generator_return_containing_extra_types.phpt @@ -0,0 +1,21 @@ +--TEST-- +Generator return value has to have Traversable-ish, but may also have extra types +--FILE-- +test()); + +?> +--EXPECT-- +object(Generator)#2 (0) { +} diff --git a/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt b/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt new file mode 100644 index 0000000000000..8526c65537bff --- /dev/null +++ b/Zend/tests/type_declarations/union_types/generator_return_multiple_classes.phpt @@ -0,0 +1,18 @@ +--TEST-- +Generator return type with multiple classes +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt new file mode 100644 index 0000000000000..6c40adc8f2d5a --- /dev/null +++ b/Zend/tests/type_declarations/union_types/illegal_default_value_argument.phpt @@ -0,0 +1,11 @@ +--TEST-- +Argument default value not legal for any type in the union +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use string as default value for parameter $arg of type int|float in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt b/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt new file mode 100644 index 0000000000000..f5941751dc22e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/illegal_default_value_property.phpt @@ -0,0 +1,12 @@ +--TEST-- +Default value not legal for any type in the union +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use string as default value for property Test::$prop of type int|float in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/incdec_prop.phpt b/Zend/tests/type_declarations/union_types/incdec_prop.phpt new file mode 100644 index 0000000000000..dde6f595264a3 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/incdec_prop.phpt @@ -0,0 +1,132 @@ +--TEST-- +Increment/decrement a typed property with int|float type +--FILE-- +prop = PHP_INT_MAX; +$x = $test->prop++; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MAX; +$x = ++$test->prop; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = $test->prop--; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = --$test->prop; +var_dump(is_double($test->prop)); + +$test = new Test; +$test->prop = PHP_INT_MAX; +$r =& $test->prop; +$x = $test->prop++; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MAX; +$x = ++$test->prop; +$r =& $test->prop; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = $test->prop--; +$r =& $test->prop; +var_dump(is_double($test->prop)); + +$test->prop = PHP_INT_MIN; +$x = --$test->prop; +$r =& $test->prop; +var_dump(is_double($test->prop)); + +/* Incrementing a non-int|float property past int min/max is an error, + * even if the result of the overflow (a float) would technically be allowed + * under a type coercion. */ + +try { + $test->prop2 = PHP_INT_MAX; + $x = $test->prop2++; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MAX; + $x = ++$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $x = $test->prop2--; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $x = --$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MAX; + $r =& $test->prop2; + $x = $test->prop2++; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MAX; + $r =& $test->prop2; + $x = ++$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $r =& $test->prop2; + $x = $test->prop2--; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->prop2 = PHP_INT_MIN; + $r =& $test->prop2; + $x = --$test->prop2; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +Cannot increment property Test::$prop2 of type int|bool past its maximal value +Cannot increment property Test::$prop2 of type int|bool past its maximal value +Cannot decrement property Test::$prop2 of type int|bool past its minimal value +Cannot decrement property Test::$prop2 of type int|bool past its minimal value +Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value +Cannot increment a reference held by property Test::$prop2 of type int|bool past its maximal value +Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value +Cannot decrement a reference held by property Test::$prop2 of type int|bool past its minimal value diff --git a/Zend/tests/type_declarations/union_types/inheritance.phpt b/Zend/tests/type_declarations/union_types/inheritance.phpt new file mode 100644 index 0000000000000..a0a1b65912323 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/inheritance.phpt @@ -0,0 +1,46 @@ +--TEST-- +Various inheritance scenarios for properties/methods with union types +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/inheritance_internal.phpt b/Zend/tests/type_declarations/union_types/inheritance_internal.phpt new file mode 100644 index 0000000000000..bb53411cad618 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/inheritance_internal.phpt @@ -0,0 +1,33 @@ +--TEST-- +Inheritance of union type from internal class +--SKIPIF-- + +--FILE-- +classUnionProp = new stdClass; +$obj->classUnionProp = new ArrayIterator; +try { + $obj->classUnionProp = new DateTime; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +$obj = new C; +$obj->classUnionProp = new stdClass; +$obj->classUnionProp = new ArrayIterator; +try { + $obj->classUnionProp = new DateTime; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null +Cannot assign DateTime to property _ZendTestClass::$classUnionProp of type stdClass|Iterator|null diff --git a/Zend/tests/type_declarations/union_types/legal_default_values.phpt b/Zend/tests/type_declarations/union_types/legal_default_values.phpt new file mode 100644 index 0000000000000..2807397734625 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/legal_default_values.phpt @@ -0,0 +1,45 @@ +--TEST-- +The default value must be legal for one of the types in the union +--FILE-- + +--EXPECT-- +object(Test)#1 (5) { + ["a"]=> + int(1) + ["b"]=> + float(2) + ["c"]=> + float(3) + ["d"]=> + float(4) + ["e"]=> + string(1) "5" +} +int(1) +float(2) +float(3) +float(4) +string(1) "5" diff --git a/Zend/tests/type_declarations/union_types/multiple_classes.phpt b/Zend/tests/type_declarations/union_types/multiple_classes.phpt new file mode 100644 index 0000000000000..aac56c6603f66 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/multiple_classes.phpt @@ -0,0 +1,83 @@ +--TEST-- +Union types with multiple classes +--FILE-- +prop = 42; +var_dump($test->prop); +var_dump($test->method(42)); + +$test->prop = "42"; +var_dump($test->prop); +var_dump($test->method("42")); + +try { + $test->prop = new stdClass; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $test->method(new stdClass); +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +if (true) { + class X {} +} + +$test->prop = new X; +var_dump($test->prop); +var_dump($test->method(new X)); + +if (true) { + class Z {} +} + +$test->prop = new Z; +var_dump($test->prop); +var_dump($test->method(new Z)); + +if (true) { + class Y {} +} + +$test->prop = new Y; +var_dump($test->prop); +var_dump($test->method(new Y)); + +?> +--EXPECTF-- +int(42) +int(42) +int(42) +int(42) +Cannot assign stdClass to property Test::$prop of type X|Y|Z|int +Argument 1 passed to Test::method() must be of type X|Y|Z|int, instance of stdClass given, called in %s on line %d +object(X)#4 (0) { +} +object(X)#6 (0) { +} +object(Z)#6 (0) { +} +object(Z)#4 (0) { +} +object(Y)#4 (0) { +} +object(Y)#6 (0) { +} diff --git a/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt b/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt new file mode 100644 index 0000000000000..a8db8fca9cd51 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/prop_ref_assign.phpt @@ -0,0 +1,59 @@ +--TEST-- +Assignments to references that are held by properties with union types +--FILE-- +x =& $r; +$test->y =& $r; + +$v = 42; +try { + $r = $v; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($r, $v); + +$v = 42.0; +try { + $r = $v; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +var_dump($r, $v); + +unset($r, $test->x, $test->y); + +$test->x = 42; +try { + $test->y =& $test->x; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +unset($test->x, $test->y); + +$test->y = 42.0; +try { + $test->x =& $test->y; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Cannot assign int to reference held by property Test::$x of type string|int and property Test::$y of type string|float, as this would result in an inconsistent type conversion +string(6) "foobar" +int(42) +Cannot assign float to reference held by property Test::$x of type string|int and property Test::$y of type string|float, as this would result in an inconsistent type conversion +string(6) "foobar" +float(42) +Reference with value of type int held by property Test::$x of type string|int is not compatible with property Test::$y of type string|float +Reference with value of type float held by property Test::$y of type string|float is not compatible with property Test::$x of type string|int diff --git a/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt new file mode 100644 index 0000000000000..91d2d940f63df --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/bool_and_false.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both bool and false in a union +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type false is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt new file mode 100644 index 0000000000000..5739f699e72d9 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_class_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Duplicate class type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type FOO is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt new file mode 100644 index 0000000000000..f9cd3e01fa13d --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/duplicate_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using a type twice in a union +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type int is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt new file mode 100644 index 0000000000000..5b65a33de1a59 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both iterable and Traversable +--FILE-- + +--EXPECTF-- +Fatal error: Type Traversable|iterable contains both iterable and Traversable, which is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt new file mode 100644 index 0000000000000..e3f7c5858b400 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_Traversable_2.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both iterable and Traversable, with extra classes +--FILE-- + +--EXPECTF-- +Fatal error: Type Traversable|ArrayAccess|iterable contains both iterable and Traversable, which is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt new file mode 100644 index 0000000000000..c6b0949418890 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/iterable_and_array.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both iterable and array +--FILE-- + +--EXPECTF-- +Fatal error: Type iterable|array contains both iterable and array, which is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt new file mode 100644 index 0000000000000..5597794c86719 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/nullable_null.phpt @@ -0,0 +1,11 @@ +--TEST-- +Combining nullability with null +--FILE-- + +--EXPECTF-- +Fatal error: Null can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt b/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt new file mode 100644 index 0000000000000..e9f785ed17173 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/redundant_types/object_and_class_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Using both object and a class type +--FILE-- + +--EXPECTF-- +Fatal error: Type Test|object contains both object and a class type, which is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_false.phpt b/Zend/tests/type_declarations/union_types/standalone_false.phpt new file mode 100644 index 0000000000000..3932f929e511c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_false.phpt @@ -0,0 +1,10 @@ +--TEST-- +False cannot be used as a standalone type +--FILE-- + +--EXPECTF-- +Fatal error: False can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_null.phpt b/Zend/tests/type_declarations/union_types/standalone_null.phpt new file mode 100644 index 0000000000000..7a25f9cd05f58 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_null.phpt @@ -0,0 +1,10 @@ +--TEST-- +Null cannot be used as a standalone type +--FILE-- + +--EXPECTF-- +Fatal error: Null can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt new file mode 100644 index 0000000000000..1e680249b0cd6 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/standalone_nullable_false.phpt @@ -0,0 +1,10 @@ +--TEST-- +Nullable false cannot be used as a standalone type +--FILE-- + +--EXPECTF-- +Fatal error: False can not be used as a standalone type in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/type_checking_strict.phpt b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt new file mode 100644 index 0000000000000..f098b638dcfe3 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/type_checking_strict.phpt @@ -0,0 +1,211 @@ +--TEST-- +Behavior of union type checks (strict) +--SKIPIF-- + +--FILE-- + '; + + try { + error_clear_last(); + $value = @$fn($value); + echo dump($value); + if ($e = error_get_last()) { + echo ' (', $e['message'], ')'; + } + } catch (TypeError $e) { + $msg = $e->getMessage(); + $msg = strstr($msg, ', called in', true); + $msg = str_replace('1 passed to {closure}()', '...', $msg); + echo $msg; + } + echo "\n"; + } +} + +class WithToString { + public function __toString() { + return "__toString()"; + } +} + +$values = [ + 42, 42.0, INF, "42", "42.0", "42x", "x", "", + true, false, null, [], new stdClass, new WithToString, +]; +test('int|float', $values); +test('int|float|false', $values); +test('int|float|bool', $values); +test('int|bool', $values); +test('int|string|null', $values); +test('string|bool', $values); +test('float|array', $values); +test('string|array', $values); +test('bool|array', $values); + +?> +--EXPECT-- +Type int|float: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type int|float, string given +"42.0" => Argument ... must be of type int|float, string given +"42x" => Argument ... must be of type int|float, string given +"x" => Argument ... must be of type int|float, string given +"" => Argument ... must be of type int|float, string given +true => Argument ... must be of type int|float, bool given +false => Argument ... must be of type int|float, bool given +null => Argument ... must be of type int|float, null given +[] => Argument ... must be of type int|float, array given +new stdClass => Argument ... must be of type int|float, object given +new WithToString => Argument ... must be of type int|float, object given + +Type int|float|false: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type int|float|false, string given +"42.0" => Argument ... must be of type int|float|false, string given +"42x" => Argument ... must be of type int|float|false, string given +"x" => Argument ... must be of type int|float|false, string given +"" => Argument ... must be of type int|float|false, string given +true => Argument ... must be of type int|float|false, bool given +false => false +null => Argument ... must be of type int|float|false, null given +[] => Argument ... must be of type int|float|false, array given +new stdClass => Argument ... must be of type int|float|false, object given +new WithToString => Argument ... must be of type int|float|false, object given + +Type int|float|bool: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type int|float|bool, string given +"42.0" => Argument ... must be of type int|float|bool, string given +"42x" => Argument ... must be of type int|float|bool, string given +"x" => Argument ... must be of type int|float|bool, string given +"" => Argument ... must be of type int|float|bool, string given +true => true +false => false +null => Argument ... must be of type int|float|bool, null given +[] => Argument ... must be of type int|float|bool, array given +new stdClass => Argument ... must be of type int|float|bool, object given +new WithToString => Argument ... must be of type int|float|bool, object given + +Type int|bool: +42 => 42 +42.0 => Argument ... must be of type int|bool, float given +INF => Argument ... must be of type int|bool, float given +"42" => Argument ... must be of type int|bool, string given +"42.0" => Argument ... must be of type int|bool, string given +"42x" => Argument ... must be of type int|bool, string given +"x" => Argument ... must be of type int|bool, string given +"" => Argument ... must be of type int|bool, string given +true => true +false => false +null => Argument ... must be of type int|bool, null given +[] => Argument ... must be of type int|bool, array given +new stdClass => Argument ... must be of type int|bool, object given +new WithToString => Argument ... must be of type int|bool, object given + +Type int|string|null: +42 => 42 +42.0 => Argument ... must be of type string|int|null, float given +INF => Argument ... must be of type string|int|null, float given +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => Argument ... must be of type string|int|null, bool given +false => Argument ... must be of type string|int|null, bool given +null => null +[] => Argument ... must be of type string|int|null, array given +new stdClass => Argument ... must be of type string|int|null, object given +new WithToString => Argument ... must be of type string|int|null, object given + +Type string|bool: +42 => Argument ... must be of type string|bool, int given +42.0 => Argument ... must be of type string|bool, float given +INF => Argument ... must be of type string|bool, float given +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => true +false => false +null => Argument ... must be of type string|bool, null given +[] => Argument ... must be of type string|bool, array given +new stdClass => Argument ... must be of type string|bool, object given +new WithToString => Argument ... must be of type string|bool, object given + +Type float|array: +42 => 42.0 +42.0 => 42.0 +INF => INF +"42" => Argument ... must be of type array|float, string given +"42.0" => Argument ... must be of type array|float, string given +"42x" => Argument ... must be of type array|float, string given +"x" => Argument ... must be of type array|float, string given +"" => Argument ... must be of type array|float, string given +true => Argument ... must be of type array|float, bool given +false => Argument ... must be of type array|float, bool given +null => Argument ... must be of type array|float, null given +[] => [] +new stdClass => Argument ... must be of type array|float, object given +new WithToString => Argument ... must be of type array|float, object given + +Type string|array: +42 => Argument ... must be of type array|string, int given +42.0 => Argument ... must be of type array|string, float given +INF => Argument ... must be of type array|string, float given +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => Argument ... must be of type array|string, bool given +false => Argument ... must be of type array|string, bool given +null => Argument ... must be of type array|string, null given +[] => [] +new stdClass => Argument ... must be of type array|string, object given +new WithToString => Argument ... must be of type array|string, object given + +Type bool|array: +42 => Argument ... must be of type array|bool, int given +42.0 => Argument ... must be of type array|bool, float given +INF => Argument ... must be of type array|bool, float given +"42" => Argument ... must be of type array|bool, string given +"42.0" => Argument ... must be of type array|bool, string given +"42x" => Argument ... must be of type array|bool, string given +"x" => Argument ... must be of type array|bool, string given +"" => Argument ... must be of type array|bool, string given +true => true +false => false +null => Argument ... must be of type array|bool, null given +[] => [] +new stdClass => Argument ... must be of type array|bool, object given +new WithToString => Argument ... must be of type array|bool, object given diff --git a/Zend/tests/type_declarations/union_types/type_checking_weak.phpt b/Zend/tests/type_declarations/union_types/type_checking_weak.phpt new file mode 100644 index 0000000000000..441eaab854804 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/type_checking_weak.phpt @@ -0,0 +1,209 @@ +--TEST-- +Behavior of union type checks (weak) +--SKIPIF-- + +--FILE-- + '; + + try { + error_clear_last(); + $value = @$fn($value); + echo dump($value); + if ($e = error_get_last()) { + echo ' (', $e['message'], ')'; + } + } catch (TypeError $e) { + $msg = $e->getMessage(); + $msg = strstr($msg, ', called in', true); + $msg = str_replace('1 passed to {closure}()', '...', $msg); + echo $msg; + } + echo "\n"; + } +} + +class WithToString { + public function __toString() { + return "__toString()"; + } +} + +$values = [ + 42, 42.0, INF, "42", "42.0", "42x", "x", "", + true, false, null, [], new stdClass, new WithToString, +]; +test('int|float', $values); +test('int|float|false', $values); +test('int|float|bool', $values); +test('int|bool', $values); +test('int|string|null', $values); +test('string|bool', $values); +test('float|array', $values); +test('string|array', $values); +test('bool|array', $values); + +?> +--EXPECT-- +Type int|float: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => 42 +"42.0" => 42.0 +"42x" => 42 (A non well formed numeric value encountered) +"x" => Argument ... must be of type int|float, string given +"" => Argument ... must be of type int|float, string given +true => 1 +false => 0 +null => Argument ... must be of type int|float, null given +[] => Argument ... must be of type int|float, array given +new stdClass => Argument ... must be of type int|float, object given +new WithToString => Argument ... must be of type int|float, object given + +Type int|float|false: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => 42 +"42.0" => 42.0 +"42x" => 42 (A non well formed numeric value encountered) +"x" => Argument ... must be of type int|float|false, string given +"" => Argument ... must be of type int|float|false, string given +true => 1 +false => false +null => Argument ... must be of type int|float|false, null given +[] => Argument ... must be of type int|float|false, array given +new stdClass => Argument ... must be of type int|float|false, object given +new WithToString => Argument ... must be of type int|float|false, object given + +Type int|float|bool: +42 => 42 +42.0 => 42.0 +INF => INF +"42" => 42 +"42.0" => 42.0 +"42x" => 42 (A non well formed numeric value encountered) +"x" => true +"" => false +true => true +false => false +null => Argument ... must be of type int|float|bool, null given +[] => Argument ... must be of type int|float|bool, array given +new stdClass => Argument ... must be of type int|float|bool, object given +new WithToString => Argument ... must be of type int|float|bool, object given + +Type int|bool: +42 => 42 +42.0 => 42 +INF => true +"42" => 42 +"42.0" => 42 +"42x" => 42 (A non well formed numeric value encountered) +"x" => true +"" => false +true => true +false => false +null => Argument ... must be of type int|bool, null given +[] => Argument ... must be of type int|bool, array given +new stdClass => Argument ... must be of type int|bool, object given +new WithToString => Argument ... must be of type int|bool, object given + +Type int|string|null: +42 => 42 +42.0 => 42 +INF => "INF" +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => 1 +false => 0 +null => null +[] => Argument ... must be of type string|int|null, array given +new stdClass => Argument ... must be of type string|int|null, object given +new WithToString => "__toString()" + +Type string|bool: +42 => "42" +42.0 => "42" +INF => "INF" +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => true +false => false +null => Argument ... must be of type string|bool, null given +[] => Argument ... must be of type string|bool, array given +new stdClass => Argument ... must be of type string|bool, object given +new WithToString => "__toString()" + +Type float|array: +42 => 42.0 +42.0 => 42.0 +INF => INF +"42" => 42.0 +"42.0" => 42.0 +"42x" => 42.0 (A non well formed numeric value encountered) +"x" => Argument ... must be of type array|float, string given +"" => Argument ... must be of type array|float, string given +true => 1.0 +false => 0.0 +null => Argument ... must be of type array|float, null given +[] => [] +new stdClass => Argument ... must be of type array|float, object given +new WithToString => Argument ... must be of type array|float, object given + +Type string|array: +42 => "42" +42.0 => "42" +INF => "INF" +"42" => "42" +"42.0" => "42.0" +"42x" => "42x" +"x" => "x" +"" => "" +true => "1" +false => "" +null => Argument ... must be of type array|string, null given +[] => [] +new stdClass => Argument ... must be of type array|string, object given +new WithToString => "__toString()" + +Type bool|array: +42 => true +42.0 => true +INF => true +"42" => true +"42.0" => true +"42x" => true +"x" => true +"" => false +true => true +false => false +null => Argument ... must be of type array|bool, null given +[] => [] +new stdClass => Argument ... must be of type array|bool, object given +new WithToString => Argument ... must be of type array|bool, object given diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt new file mode 100644 index 0000000000000..9e1dcaefa16c4 --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/invalid_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid union type variance: Adding extra return type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::method(): int|float must be compatible with A::method(): int in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt new file mode 100644 index 0000000000000..4448114fceb6c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/invalid_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Invalid union type variance: Removing argument union type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::method(int $a) must be compatible with A::method(int|float $a) in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt b/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt new file mode 100644 index 0000000000000..26d9ae3eb430e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/invalid_003.phpt @@ -0,0 +1,18 @@ +--TEST-- +Invalid union type variance: Using parent of class in return type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::method(): X|string must be compatible with A::method(): Y|string in %s on line %d diff --git a/Zend/tests/type_declarations/union_types/variance/valid.phpt b/Zend/tests/type_declarations/union_types/variance/valid.phpt new file mode 100644 index 0000000000000..f9e5cc498056c --- /dev/null +++ b/Zend/tests/type_declarations/union_types/variance/valid.phpt @@ -0,0 +1,31 @@ +--TEST-- +Valid union type variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/union_types/void_with_class.phpt b/Zend/tests/type_declarations/union_types/void_with_class.phpt new file mode 100644 index 0000000000000..6e1f439e3193e --- /dev/null +++ b/Zend/tests/type_declarations/union_types/void_with_class.phpt @@ -0,0 +1,10 @@ +--TEST-- +Combining void with class type +--FILE-- + +--EXPECTF-- +Fatal error: Void can only be used as a standalone type in %s on line %d diff --git a/Zend/zend.c b/Zend/zend.c index ce98f50025ffa..d85135d28713f 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -592,12 +592,20 @@ static void function_copy_ctor(zval *zv) /* {{{ */ new_arg_info = pemalloc(sizeof(zend_arg_info) * num_args, 1); memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args); for (i = 0 ; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { + if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) { + zend_type_list *old_list = ZEND_TYPE_LIST(arg_info[i].type); + zend_type_list *new_list = pemalloc(ZEND_TYPE_LIST_SIZE(old_list->num_types), 1); + memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, new_list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(new_list, entry) { + zend_string *name = zend_string_dup(ZEND_TYPE_LIST_GET_NAME(*entry), 1); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), 1); - - new_arg_info[i].type = - ZEND_TYPE_ENCODE_CLASS( - name, ZEND_TYPE_ALLOW_NULL(arg_info[i].type)); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, name); } } func->common.arg_info = new_arg_info + 1; @@ -950,6 +958,15 @@ void zend_register_standard_ini_entries(void) /* {{{ */ } /* }}} */ +static zend_class_entry *resolve_type_name(zend_string *type_name) { + zend_string *lc_type_name = zend_string_tolower(type_name); + zend_class_entry *ce = zend_hash_find_ptr(CG(class_table), lc_type_name); + + ZEND_ASSERT(ce && ce->type == ZEND_INTERNAL_CLASS); + zend_string_release(lc_type_name); + return ce; +} + static void zend_resolve_property_types(void) /* {{{ */ { zend_class_entry *ce; @@ -962,14 +979,18 @@ static void zend_resolve_property_types(void) /* {{{ */ if (UNEXPECTED(ZEND_CLASS_HAS_TYPE_HINTS(ce))) { ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) { - if (ZEND_TYPE_IS_NAME(prop_info->type)) { + if (ZEND_TYPE_HAS_LIST(prop_info->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + *entry = ZEND_TYPE_LIST_ENCODE_CE(resolve_type_name(type_name)); + zend_string_release(type_name); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) { zend_string *type_name = ZEND_TYPE_NAME(prop_info->type); - zend_string *lc_type_name = zend_string_tolower(type_name); - zend_class_entry *prop_ce = zend_hash_find_ptr(CG(class_table), lc_type_name); - - ZEND_ASSERT(prop_ce && prop_ce->type == ZEND_INTERNAL_CLASS); - prop_info->type = ZEND_TYPE_ENCODE_CE(prop_ce, ZEND_TYPE_ALLOW_NULL(prop_info->type)); - zend_string_release(lc_type_name); + ZEND_TYPE_SET_CE(prop_info->type, resolve_type_name(type_name)); zend_string_release(type_name); } } ZEND_HASH_FOREACH_END(); diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 387f6e1a9822e..87cfd5f1394bc 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2045,21 +2045,17 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio } else { internal_function->required_num_args = info->required_num_args; } - if (info->return_reference) { + if (ZEND_ARG_SEND_MODE(info)) { internal_function->fn_flags |= ZEND_ACC_RETURN_REFERENCE; } - if (ptr->arg_info[ptr->num_args].is_variadic) { + if (ZEND_ARG_IS_VARIADIC(&ptr->arg_info[ptr->num_args])) { internal_function->fn_flags |= ZEND_ACC_VARIADIC; /* Don't count the variadic argument */ internal_function->num_args--; } if (ZEND_TYPE_IS_SET(info->type)) { - if (ZEND_TYPE_IS_CLASS(info->type)) { - const char *type_name = (const char*)info->type; - - if (type_name[0] == '?') { - type_name++; - } + if (ZEND_TYPE_HAS_NAME(info->type)) { + const char *type_name = ZEND_TYPE_LITERAL_NAME(info->type); if (!scope && (!strcasecmp(type_name, "self") || !strcasecmp(type_name, "parent"))) { zend_error_noreturn(E_CORE_ERROR, "Cannot declare a return type of %s outside of a class scope", type_name); } @@ -2139,17 +2135,12 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio memcpy(new_arg_info, arg_info, sizeof(zend_arg_info) * num_args); reg_function->common.arg_info = new_arg_info + 1; for (i = 0; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(new_arg_info[i].type)) { - const char *class_name = (const char*)new_arg_info[i].type; - zend_bool allow_null = 0; - zend_string *str; - - if (class_name[0] == '?') { - class_name++; - allow_null = 1; - } - str = zend_string_init_interned(class_name, strlen(class_name), 1); - new_arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(str, allow_null); + if (ZEND_TYPE_HAS_CLASS(new_arg_info[i].type)) { + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type) + && "Only simple classes are currently supported"); + const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type); + ZEND_TYPE_SET_PTR(new_arg_info[i].type, + zend_string_init_interned(class_name, strlen(class_name), 1)); } } } @@ -3715,7 +3706,7 @@ ZEND_API int zend_try_assign_typed_ref_zval_ex(zend_reference *ref, zval *zv, ze ZEND_API int zend_declare_property_ex(zend_class_entry *ce, zend_string *name, zval *property, int access_type, zend_string *doc_comment) /* {{{ */ { - return zend_declare_typed_property(ce, name, property, access_type, doc_comment, ZEND_TYPE_ENCODE_NONE()); + return zend_declare_typed_property(ce, name, property, access_type, doc_comment, (zend_type) ZEND_TYPE_INIT_NONE(0)); } /* }}} */ diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 1389f0a6d0f1a..2801b08266897 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -96,34 +96,45 @@ typedef struct _zend_fcall_info_cache { #define ZEND_FE_END { NULL, NULL, NULL, 0, 0 } -#define ZEND_ARG_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 0}, -#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, pass_by_ref, 0}, -#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_ENCODE_CLASS_CONST(#classname, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(IS_ARRAY, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(IS_CALLABLE, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(type_hint, allow_null), pass_by_ref, 0 }, -#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, 0, pass_by_ref, 1 }, -#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, ZEND_TYPE_ENCODE_CODE(type_hint, allow_null), pass_by_ref, 1 }, -#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, ZEND_TYPE_ENCODE_CLASS_CONST(#classname, allow_null), pass_by_ref, 1 }, +#define _ZEND_ARG_INFO_FLAGS(pass_by_ref, is_variadic) \ + (((pass_by_ref) << _ZEND_SEND_MODE_SHIFT) | ((is_variadic) ? _ZEND_IS_VARIADIC_BIT : 0)) + +#define ZEND_ARG_INFO(pass_by_ref, name) \ + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0))}, +#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) \ + { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0)) }, +#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) \ + { #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1)) }, +#define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \ + { #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1)) }, +#define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, classname, allow_null) \ + { #name, ZEND_TYPE_INIT_CLASS_CONST(#classname, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1)) }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE_CLASS_CONST(#class_name, allow_null), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), \ + ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0)) }, #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO(name, class_name, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, 0, -1, class_name, allow_null) #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_ENCODE_CODE(type, allow_null), return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0)) }, #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(name, type, allow_null) \ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, 0, -1, type, allow_null) #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \ static const zend_internal_arg_info name[] = { \ - { (const char*)(zend_uintptr_t)(required_num_args), 0, return_reference, 0 }, + { (const char*)(zend_uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0)) }, #define ZEND_BEGIN_ARG_INFO(name, _unused) \ - ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1) + ZEND_BEGIN_ARG_INFO_EX(name, {}, ZEND_RETURN_VALUE, -1) #define ZEND_END_ARG_INFO() }; /* Name macros */ diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index f0b524b30e829..980e23d055537 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1314,6 +1314,23 @@ static ZEND_COLD void zend_ast_export_class_no_header(smart_str *str, zend_ast_d smart_str_appends(str, "}"); } +static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int indent) { + if (ast->kind == ZEND_AST_TYPE_UNION) { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + if (i != 0) { + smart_str_appendc(str, '|'); + } + zend_ast_export_type(str, list->child[i], indent); + } + return; + } + if (ast->attr & ZEND_TYPE_NULLABLE) { + smart_str_appendc(str, '?'); + } + zend_ast_export_ns_name(str, ast, 0, indent); +} + #define BINARY_OP(_op, _p, _pl, _pr) do { \ op = _op; \ p = _p; \ @@ -1423,10 +1440,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio zend_ast_export_ex(str, decl->child[1], 0, indent); if (decl->child[3]) { smart_str_appends(str, ": "); - if (decl->child[3]->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } - zend_ast_export_ns_name(str, decl->child[3], 0, indent); + zend_ast_export_type(str, decl->child[3], indent); } if (decl->child[2]) { if (decl->kind == ZEND_AST_ARROW_FUNC) { @@ -1516,11 +1530,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } if (type_ast) { - if (type_ast->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } - zend_ast_export_ns_name( - str, type_ast, 0, indent); + zend_ast_export_type(str, type_ast, indent); smart_str_appendc(str, ' '); } @@ -1990,10 +2000,7 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio break; case ZEND_AST_PARAM: if (ast->child[0]) { - if (ast->child[0]->attr & ZEND_TYPE_NULLABLE) { - smart_str_appendc(str, '?'); - } - zend_ast_export_ns_name(str, ast->child[0], 0, indent); + zend_ast_export_type(str, ast->child[0], indent); smart_str_appendc(str, ' '); } if (ast->attr & ZEND_PARAM_REF) { diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index fd6dd1677a450..5b8aae6f96c25 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -61,6 +61,7 @@ enum _zend_ast_kind { ZEND_AST_NAME_LIST, ZEND_AST_TRAIT_ADAPTATIONS, ZEND_AST_USE, + ZEND_AST_TYPE_UNION, /* 0 child nodes */ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index bb469558a1aec..63b91aacbda2b 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -557,16 +557,16 @@ static HashTable *zend_closure_get_debug_info(zend_object *object, int *is_temp) if (arg_info->name) { if (zstr_args) { name = zend_strpprintf(0, "%s$%s", - arg_info->pass_by_reference ? "&" : "", + ZEND_ARG_SEND_MODE(arg_info) ? "&" : "", ZSTR_VAL(arg_info->name)); } else { name = zend_strpprintf(0, "%s$%s", - arg_info->pass_by_reference ? "&" : "", + ZEND_ARG_SEND_MODE(arg_info) ? "&" : "", ((zend_internal_arg_info*)arg_info)->name); } } else { name = zend_strpprintf(0, "%s$param%d", - arg_info->pass_by_reference ? "&" : "", + ZEND_ARG_SEND_MODE(arg_info) ? "&" : "", i + 1); } ZVAL_NEW_STR(&info, zend_strpprintf(0, "%s", i >= required ? "" : "")); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 3800c664e1728..53fe9afde8c77 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -60,6 +60,10 @@ typedef struct _zend_loop_var { } zend_loop_var; static inline uint32_t zend_alloc_cache_slots(unsigned count) { + if (count == 0) { + return (uint32_t) -1; + } + zend_op_array *op_array = CG(active_op_array); uint32_t ret = op_array->cache_size; op_array->cache_size += count * sizeof(void*); @@ -123,6 +127,7 @@ static void zend_destroy_property_info_internal(zval *zv) /* {{{ */ zend_property_info *property_info = Z_PTR_P(zv); zend_string_release_ex(property_info->name, 1); + zend_type_release(property_info->type, /* persistent */ 1); free(property_info); } /* }}} */ @@ -211,6 +216,8 @@ typedef struct _builtin_type_info { } builtin_type_info; static const builtin_type_info builtin_types[] = { + {ZEND_STRL("null"), IS_NULL}, + {ZEND_STRL("false"), IS_FALSE}, {ZEND_STRL("int"), IS_LONG}, {ZEND_STRL("float"), IS_DOUBLE}, {ZEND_STRL("string"), IS_STRING}, @@ -1119,62 +1126,94 @@ ZEND_API int do_bind_class(zval *lcname, zend_string *lc_parent_name) /* {{{ */ } /* }}} */ +static zend_string *add_type_string(zend_string *type, zend_string *new_type) { + zend_string *result; + if (type == NULL) { + return zend_string_copy(new_type); + } + + // TODO: Switch to smart_str? + result = zend_string_alloc(ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1, 0); + memcpy(ZSTR_VAL(result), ZSTR_VAL(type), ZSTR_LEN(type)); + ZSTR_VAL(result)[ZSTR_LEN(type)] = '|'; + memcpy(ZSTR_VAL(result) + ZSTR_LEN(type) + 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + ZSTR_VAL(result)[ZSTR_LEN(type) + ZSTR_LEN(new_type) + 1] = '\0'; + zend_string_release(type); + return result; +} + +static zend_string *resolve_class_name(zend_string *name, zend_class_entry *scope) { + if (scope) { + if (zend_string_equals_literal_ci(name, "self")) { + name = scope->name; + } else if (zend_string_equals_literal_ci(name, "parent") && scope->parent) { + name = scope->parent->name; + } + } + return name; +} + zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scope) { - zend_bool nullable = ZEND_TYPE_ALLOW_NULL(type); - zend_string *str; - if (ZEND_TYPE_IS_NAME(type)) { - zend_string *name = ZEND_TYPE_NAME(type); - if (scope) { - if (zend_string_equals_literal_ci(name, "self")) { - name = scope->name; - } else if (zend_string_equals_literal_ci(name, "parent") && scope->parent) { - name = scope->parent->name; - } - } - str = zend_string_copy(name); - } else if (ZEND_TYPE_IS_CE(type)) { + zend_string *str = NULL; + if (ZEND_TYPE_HAS_LIST(type)) { + void *elem; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), elem) { + if (ZEND_TYPE_LIST_IS_CE(elem)) { + str = add_type_string(str, ZEND_TYPE_LIST_GET_CE(elem)->name); + } else { + str = add_type_string(str, + resolve_class_name(ZEND_TYPE_LIST_GET_NAME(elem), scope)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { + str = zend_string_copy(resolve_class_name(ZEND_TYPE_NAME(type), scope)); + } else if (ZEND_TYPE_HAS_CE(type)) { str = zend_string_copy(ZEND_TYPE_CE(type)->name); - } else { - uint32_t type_mask = ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(type)); - switch (type_mask) { - case MAY_BE_FALSE|MAY_BE_TRUE: - str = ZSTR_KNOWN(ZEND_STR_BOOL); - break; - case MAY_BE_LONG: - str = ZSTR_KNOWN(ZEND_STR_INT); - break; - case MAY_BE_DOUBLE: - str = ZSTR_KNOWN(ZEND_STR_FLOAT); - break; - case MAY_BE_STRING: - str = ZSTR_KNOWN(ZEND_STR_STRING); - break; - case MAY_BE_ARRAY: - str = ZSTR_KNOWN(ZEND_STR_ARRAY); - break; - case MAY_BE_OBJECT: - str = ZSTR_KNOWN(ZEND_STR_OBJECT); - break; - case MAY_BE_CALLABLE: - str = ZSTR_KNOWN(ZEND_STR_CALLABLE); - break; - case MAY_BE_ITERABLE: - str = ZSTR_KNOWN(ZEND_STR_ITERABLE); - break; - case MAY_BE_VOID: - str = ZSTR_KNOWN(ZEND_STR_VOID); - break; - EMPTY_SWITCH_DEFAULT_CASE() - } } - if (nullable) { - zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0); - ZSTR_VAL(nullable_str)[0] = '?'; - memcpy(ZSTR_VAL(nullable_str) + 1, ZSTR_VAL(str), ZSTR_LEN(str)); - ZSTR_VAL(nullable_str)[ZSTR_LEN(nullable_str)] = '\0'; - zend_string_release(str); - return nullable_str; + uint32_t type_mask = ZEND_TYPE_FULL_MASK(type); + if (type_mask & MAY_BE_CALLABLE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE)); + } + if (type_mask & MAY_BE_ITERABLE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE)); + } + if (type_mask & MAY_BE_OBJECT) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT)); + } + if (type_mask & MAY_BE_ARRAY) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY)); + } + if (type_mask & MAY_BE_STRING) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING)); + } + if (type_mask & MAY_BE_LONG) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT)); + } + if (type_mask & MAY_BE_DOUBLE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT)); + } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL)); + } else if (type_mask & MAY_BE_FALSE) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE)); + } + if (type_mask & MAY_BE_VOID) { + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID)); + } + + if (type_mask & MAY_BE_NULL) { + zend_bool is_union = !str || memchr(ZSTR_VAL(str), '|', ZSTR_LEN(str)) != NULL; + if (!is_union) { + zend_string *nullable_str = zend_string_alloc(ZSTR_LEN(str) + 1, 0); + ZSTR_VAL(nullable_str)[0] = '?'; + memcpy(ZSTR_VAL(nullable_str) + 1, ZSTR_VAL(str), ZSTR_LEN(str)); + ZSTR_VAL(nullable_str)[ZSTR_LEN(nullable_str)] = '\0'; + zend_string_release(str); + return nullable_str; + } + + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); } return str; } @@ -1183,6 +1222,12 @@ zend_string *zend_type_to_string(zend_type type) { return zend_type_to_string_resolved(type, NULL); } +static zend_bool is_generator_compatible_class_type(zend_string *name) { + return zend_string_equals_literal_ci(name, "Traversable") + || zend_string_equals_literal_ci(name, "Iterator") + || zend_string_equals_literal_ci(name, "Generator"); +} + static void zend_mark_function_as_generator() /* {{{ */ { if (!CG(active_op_array)->function_name) { @@ -1191,21 +1236,30 @@ static void zend_mark_function_as_generator() /* {{{ */ } if (CG(active_op_array)->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { - zend_arg_info return_info = CG(active_op_array)->arg_info[-1]; - zend_bool valid_type; - if (ZEND_TYPE_IS_CLASS(return_info.type)) { - zend_string *name = ZEND_TYPE_NAME(return_info.type); - valid_type = zend_string_equals_literal_ci(name, "Traversable") - || zend_string_equals_literal_ci(name, "Iterator") - || zend_string_equals_literal_ci(name, "Generator"); + zend_type return_type = CG(active_op_array)->arg_info[-1].type; + zend_bool valid_type = 0; + if (ZEND_TYPE_HAS_CLASS(return_type)) { + if (ZEND_TYPE_HAS_LIST(return_type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(return_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + if (is_generator_compatible_class_type(ZEND_TYPE_LIST_GET_NAME(entry))) { + valid_type = 1; + break; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else { + ZEND_ASSERT(ZEND_TYPE_HAS_NAME(return_type)); + valid_type = is_generator_compatible_class_type(ZEND_TYPE_NAME(return_type)); + } } else { - valid_type = (ZEND_TYPE_MASK(return_info.type) & MAY_BE_ITERABLE) != 0; + valid_type = (ZEND_TYPE_FULL_MASK(return_type) & MAY_BE_ITERABLE) != 0; } if (!valid_type) { - zend_string *str = zend_type_to_string(return_info.type); + zend_string *str = zend_type_to_string(return_type); zend_error_noreturn(E_COMPILE_ERROR, - "Generators may only declare a return type of " \ + "Generators may only declare a return type containing " \ "Generator, Iterator, Traversable, or iterable, %s is not permitted", ZSTR_VAL(str)); } @@ -2167,6 +2221,16 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr) /* {{{ */ } /* }}} */ +static size_t zend_type_get_num_classes(zend_type type) { + if (!ZEND_TYPE_HAS_CLASS(type)) { + return 0; + } + if (ZEND_TYPE_HAS_LIST(type)) { + return ZEND_TYPE_LIST(type)->num_types; + } + return 1; +} + static void zend_emit_return_type_check( znode *expr, zend_arg_info *return_info, zend_bool implicit) /* {{{ */ { @@ -2175,7 +2239,7 @@ static void zend_emit_return_type_check( zend_op *opline; /* `return ...;` is illegal in a void function (but `return;` isn't) */ - if (ZEND_TYPE_IS_MASK(type) && ZEND_TYPE_CONTAINS_CODE(type, IS_VOID)) { + if (ZEND_TYPE_CONTAINS_CODE(type, IS_VOID)) { if (expr) { if (expr->op_type == IS_CONST && Z_TYPE(expr->u.constant) == IS_NULL) { zend_error_noreturn(E_COMPILE_ERROR, @@ -2201,8 +2265,7 @@ static void zend_emit_return_type_check( } if (expr && expr->op_type == IS_CONST) { - if (ZEND_TYPE_IS_MASK(type) - && ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE(expr->u.constant))) { + if (ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE(expr->u.constant))) { /* we don't need run-time check */ return; } @@ -2213,12 +2276,8 @@ static void zend_emit_return_type_check( opline->result_type = expr->op_type = IS_TMP_VAR; opline->result.var = expr->u.op.var = get_temporary_variable(); } - if (ZEND_TYPE_IS_CLASS(return_info->type)) { - opline->op2.num = CG(active_op_array)->cache_size; - CG(active_op_array)->cache_size += sizeof(void*); - } else { - opline->op2.num = -1; - } + + opline->op2.num = zend_alloc_cache_slots(zend_type_get_num_classes(return_info->type)); } } /* }}} */ @@ -5359,11 +5418,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ n = MIN(func->common.num_args, MAX_ARG_FLAG_NUM); i = 0; while (i < n) { - ZEND_SET_ARG_FLAG(func, i + 1, func->common.arg_info[i].pass_by_reference); + ZEND_SET_ARG_FLAG(func, i + 1, ZEND_ARG_SEND_MODE(&func->common.arg_info[i])); i++; } - if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_VARIADIC && func->common.arg_info[i].pass_by_reference)) { - uint32_t pass_by_reference = func->common.arg_info[i].pass_by_reference; + if (UNEXPECTED(func->common.fn_flags & ZEND_ACC_VARIADIC && ZEND_ARG_SEND_MODE(&func->common.arg_info[i]))) { + uint32_t pass_by_reference = ZEND_ARG_SEND_MODE(&func->common.arg_info[i]); while (i < MAX_ARG_FLAG_NUM) { ZEND_SET_ARG_FLAG(func, i + 1, pass_by_reference); i++; @@ -5373,16 +5432,11 @@ ZEND_API void zend_set_function_arg_flags(zend_function *func) /* {{{ */ } /* }}} */ -static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null) /* {{{ */ +static zend_type zend_compile_single_typename(zend_ast *ast) { - zend_bool allow_null = force_allow_null; - if (ast->attr & ZEND_TYPE_NULLABLE) { - allow_null = 1; - ast->attr &= ~ZEND_TYPE_NULLABLE; - } - + ZEND_ASSERT(!(ast->attr & ZEND_TYPE_NULLABLE)); if (ast->kind == ZEND_AST_TYPE) { - return ZEND_TYPE_ENCODE_CODE(ast->attr, allow_null); + return (zend_type) ZEND_TYPE_INIT_CODE(ast->attr, 0, 0); } else { zend_string *class_name = zend_ast_get_str(ast); zend_uchar type = zend_lookup_builtin_type_by_name(class_name); @@ -5393,10 +5447,7 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null "Type declaration '%s' must be unqualified", ZSTR_VAL(zend_string_tolower(class_name))); } - if (type == IS_VOID && allow_null) { - zend_error_noreturn(E_COMPILE_ERROR, "Void type cannot be nullable"); - } - return ZEND_TYPE_ENCODE_CODE(type, allow_null); + return (zend_type) ZEND_TYPE_INIT_CODE(type, 0, 0); } else { const char *correct_name; zend_string *orig_name = zend_ast_get_str(ast); @@ -5428,32 +5479,157 @@ static zend_type zend_compile_typename(zend_ast *ast, zend_bool force_allow_null } } - return ZEND_TYPE_ENCODE_CLASS(class_name, allow_null); + return (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 0, 0); } } } + +static zend_bool zend_type_contains_traversable(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + if (zend_string_equals_literal_ci(ZEND_TYPE_LIST_GET_NAME(entry), "Traversable")) { + return 1; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { + return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "Traversable"); + } + return 0; +} + +// TODO: Ideally we'd canonicalize "iterable" into "array|Traversable" and essentially +// treat it as a built-in type alias. +static zend_type zend_compile_typename( + zend_ast *ast, zend_bool force_allow_null, zend_bool use_arena) /* {{{ */ +{ + zend_bool allow_null = force_allow_null; + zend_type type = ZEND_TYPE_INIT_NONE(0); + if (ast->attr & ZEND_TYPE_NULLABLE) { + allow_null = 1; + ast->attr &= ~ZEND_TYPE_NULLABLE; + } + + if (ast->kind == ZEND_AST_TYPE_UNION) { + zend_ast_list *list = zend_ast_get_list(ast); + for (uint32_t i = 0; i < list->children; i++) { + zend_ast *type_ast = list->child[i]; + zend_type single_type = zend_compile_single_typename(type_ast); + uint32_t type_mask_overlap = + ZEND_TYPE_PURE_MASK(type) & ZEND_TYPE_PURE_MASK(single_type); + if (type_mask_overlap) { + zend_type overlap_type = ZEND_TYPE_INIT_MASK(type_mask_overlap); + zend_string *overlap_type_str = zend_type_to_string(overlap_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Duplicate type %s is redundant", ZSTR_VAL(overlap_type_str)); + } + ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); + + if (ZEND_TYPE_HAS_CLASS(single_type)) { + if (!ZEND_TYPE_HAS_CLASS(type)) { + /* The first class type can be stored directly as the type ptr payload. */ + ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type)); + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT; + } else { + zend_type_list *list; + if (ZEND_TYPE_HAS_LIST(type)) { + /* Add name to existing name list. */ + zend_type_list *old_list = ZEND_TYPE_LIST(type); + if (use_arena) { + // TODO: Add a zend_arena_realloc API? + list = zend_arena_alloc( + &CG(arena), ZEND_TYPE_LIST_SIZE(old_list->num_types + 1)); + memcpy(list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + } else { + list = erealloc(old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types + 1)); + } + list->types[list->num_types++] = ZEND_TYPE_NAME(single_type); + } else { + /* Switch from single name to name list. */ + size_t size = ZEND_TYPE_LIST_SIZE(2); + list = use_arena ? zend_arena_alloc(&CG(arena), size) : emalloc(size); + list->num_types = 2; + list->types[0] = ZEND_TYPE_NAME(type); + list->types[1] = ZEND_TYPE_NAME(single_type); + } + ZEND_TYPE_SET_LIST(type, list); + if (use_arena) { + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; + } + + /* Check for trivially redundant class types */ + for (size_t i = 0; i < list->num_types - 1; i++) { + if (zend_string_equals_ci( + ZEND_TYPE_LIST_GET_NAME(list->types[i]), + ZEND_TYPE_NAME(single_type))) { + zend_string *single_type_str = zend_type_to_string(single_type); + zend_error_noreturn(E_COMPILE_ERROR, + "Duplicate type %s is redundant", ZSTR_VAL(single_type_str)); + } + } + } + } + } + } else { + type = zend_compile_single_typename(ast); + } + + if (allow_null) { + ZEND_TYPE_FULL_MASK(type) |= MAY_BE_NULL; + } + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + if ((type_mask & (MAY_BE_ARRAY|MAY_BE_ITERABLE)) == (MAY_BE_ARRAY|MAY_BE_ITERABLE)) { + zend_string *type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s contains both iterable and array, which is redundant", ZSTR_VAL(type_str)); + } + + if ((type_mask & MAY_BE_ITERABLE) && zend_type_contains_traversable(type)) { + zend_string *type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s contains both iterable and Traversable, which is redundant", + ZSTR_VAL(type_str)); + } + + if ((type_mask & MAY_BE_OBJECT) && ZEND_TYPE_HAS_CLASS(type)) { + zend_string *type_str = zend_type_to_string(type); + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s contains both object and a class type, which is redundant", + ZSTR_VAL(type_str)); + } + + if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_VOID)) { + zend_error_noreturn(E_COMPILE_ERROR, "Void can only be used as a standalone type"); + } + + if ((type_mask & (MAY_BE_NULL|MAY_BE_FALSE)) + && !ZEND_TYPE_HAS_CLASS(type) && !(type_mask & ~(MAY_BE_NULL|MAY_BE_FALSE))) { + if (type_mask == MAY_BE_NULL) { + zend_error_noreturn(E_COMPILE_ERROR, "Null can not be used as a standalone type"); + } else { + zend_error_noreturn(E_COMPILE_ERROR, "False can not be used as a standalone type"); + } + } + + return type; +} /* }}} */ /* May convert value from int to float. */ static zend_bool zend_is_valid_default_value(zend_type type, zval *value) { ZEND_ASSERT(ZEND_TYPE_IS_SET(type)); - if (Z_TYPE_P(value) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type)) { - return 1; - } - - if (ZEND_TYPE_IS_CLASS(type)) { - return 0; - } if (ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(value))) { return 1; } - if ((ZEND_TYPE_MASK(type) & MAY_BE_DOUBLE) && Z_TYPE_P(value) == IS_LONG) { + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_DOUBLE) && Z_TYPE_P(value) == IS_LONG) { /* Integers are allowed as initializers for floating-point values. */ convert_to_double(value); return 1; } - if ((ZEND_TYPE_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) { + if ((ZEND_TYPE_FULL_MASK(type) & MAY_BE_ITERABLE) && Z_TYPE_P(value) == IS_ARRAY) { return 1; } return 0; @@ -5470,9 +5646,10 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ /* Use op_array->arg_info[-1] for return type */ arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0); arg_infos->name = NULL; - arg_infos->pass_by_reference = (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0; - arg_infos->is_variadic = 0; - arg_infos->type = zend_compile_typename(return_type_ast, 0); + arg_infos->type = zend_compile_typename( + return_type_ast, /* force_allow_null */ 0, /* use_arena */ 0); + ZEND_TYPE_FULL_MASK(arg_infos->type) |= _ZEND_ARG_INFO_FLAGS( + (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) != 0, /* is_variadic */ 0); arg_infos++; op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; } else { @@ -5540,23 +5717,16 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ arg_info = &arg_infos[i]; arg_info->name = zend_string_copy(name); - arg_info->pass_by_reference = is_ref; - arg_info->is_variadic = is_variadic; - arg_info->type = ZEND_TYPE_ENCODE_NONE(); + arg_info->type = (zend_type) ZEND_TYPE_INIT_NONE(0); if (type_ast) { uint32_t default_type = default_ast ? Z_TYPE(default_node.u.constant) : IS_UNDEF; - uint32_t arg_type; - zend_bool is_class; - op_array->fn_flags |= ZEND_ACC_HAS_TYPE_HINTS; - arg_info->type = zend_compile_typename(type_ast, default_type == IS_NULL); + arg_info->type = zend_compile_typename( + type_ast, default_type == IS_NULL, /* use_arena */ 0); - is_class = ZEND_TYPE_IS_CLASS(arg_info->type); - arg_type = !is_class ? ZEND_TYPE_MASK(arg_info->type) : 0; - - if (arg_type & MAY_BE_VOID) { + if (ZEND_TYPE_FULL_MASK(arg_info->type) & MAY_BE_VOID) { zend_error_noreturn(E_COMPILE_ERROR, "void cannot be used as a parameter type"); } @@ -5576,10 +5746,11 @@ void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) /* {{{ */ if (type_ast) { /* Allocate cache slot to speed-up run-time class resolution */ - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { - opline->extended_value = zend_alloc_cache_slot(); - } + opline->extended_value = + zend_alloc_cache_slots(zend_type_get_num_classes(arg_info->type)); } + + ZEND_TYPE_FULL_MASK(arg_info->type) |= _ZEND_ARG_INFO_FLAGS(is_ref, is_variadic); } /* These are assigned at the end to avoid uninitialized memory in case of an error */ @@ -6079,13 +6250,12 @@ void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t flags) / zend_string *name = zval_make_interned_string(zend_ast_get_zval(name_ast)); zend_string *doc_comment = NULL; zval value_zv; - zend_type type = ZEND_TYPE_ENCODE_NONE(); + zend_type type = ZEND_TYPE_INIT_NONE(0); if (type_ast) { - type = zend_compile_typename(type_ast, 0); + type = zend_compile_typename(type_ast, /* force_allow_null */ 0, /* use_arena */ 1); - if (ZEND_TYPE_IS_MASK(type) - && (ZEND_TYPE_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE))) { + if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_CALLABLE)) { zend_string *str = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Property %s::$%s cannot have type %s", diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index e4096a05a5693..a87204d26753b 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -383,16 +383,12 @@ typedef struct _zend_class_constant { typedef struct _zend_internal_arg_info { const char *name; zend_type type; - zend_uchar pass_by_reference; - zend_bool is_variadic; } zend_internal_arg_info; /* arg_info for user functions */ typedef struct _zend_arg_info { zend_string *name; zend_type type; - zend_uchar pass_by_reference; - zend_bool is_variadic; } zend_arg_info; /* the following structure repeats the layout of zend_internal_arg_info, @@ -403,8 +399,6 @@ typedef struct _zend_arg_info { typedef struct _zend_internal_function_info { zend_uintptr_t required_num_args; zend_type type; - zend_bool return_reference; - zend_bool _is_variadic; } zend_internal_function_info; struct _zend_op_array { @@ -789,6 +783,8 @@ ZEND_API void destroy_op_array(zend_op_array *op_array); ZEND_API void zend_destroy_file_handle(zend_file_handle *file_handle); ZEND_API void zend_cleanup_internal_class_data(zend_class_entry *ce); ZEND_API void zend_cleanup_internal_classes(void); +ZEND_API void zend_type_release(zend_type type, zend_bool persistent); + ZEND_API ZEND_COLD void zend_user_exception_handler(void); @@ -934,6 +930,14 @@ zend_string *zend_type_to_string(zend_type type); #define ZEND_SEND_BY_REF 1u #define ZEND_SEND_PREFER_REF 2u +/* The send mode and is_variadic flag are stored as part of zend_type */ +#define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT +#define _ZEND_IS_VARIADIC_BIT (1 << (_ZEND_TYPE_EXTRA_FLAGS_SHIFT + 2)) +#define ZEND_ARG_SEND_MODE(arg_info) \ + ((ZEND_TYPE_FULL_MASK((arg_info)->type) >> _ZEND_SEND_MODE_SHIFT) & 3) +#define ZEND_ARG_IS_VARIADIC(arg_info) \ + ((ZEND_TYPE_FULL_MASK((arg_info)->type) & _ZEND_IS_VARIADIC_BIT) != 0) + #define ZEND_DIM_IS (1 << 0) /* isset fetch needed for null coalesce */ #define ZEND_DIM_ALTERNATIVE_SYNTAX (1 << 1) /* deprecated curly brace usage */ @@ -950,7 +954,7 @@ static zend_always_inline int zend_check_arg_send_type(const zend_function *zf, } arg_num = zf->common.num_args; } - return UNEXPECTED((zf->common.arg_info[arg_num].pass_by_reference & mask) != 0); + return UNEXPECTED((ZEND_ARG_SEND_MODE(&zf->common.arg_info[arg_num]) & mask) != 0); } #define ARG_MUST_BE_SENT_BY_REF(zf, arg_num) \ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 8278f698cdda4..153d8e3bc4ea9 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -39,6 +39,7 @@ #include "zend_dtrace.h" #include "zend_inheritance.h" #include "zend_type_info.h" +#include "zend_smart_str.h" /* Virtual current working directory support */ #include "zend_virtual_cwd.h" @@ -642,14 +643,30 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_throw_non_object_erro } } +/* Test used to preserve old error messages for non-union types. + * We might want to canonicalize all type errors instead. */ +static zend_bool is_union_type(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + return 1; + } + uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); + if (ZEND_TYPE_HAS_CLASS(type)) { + return type_mask_without_null != 0; + } + if (type_mask_without_null == MAY_BE_BOOL) { + return 0; + } + /* Check that only one bit is set. */ + return (type_mask_without_null & (type_mask_without_null - 1)) != 0; +} + static ZEND_COLD void zend_verify_type_error_common( const zend_function *zf, const zend_arg_info *arg_info, void **cache_slot, zval *value, const char **fname, const char **fsep, const char **fclass, - const char **need_msg, const char **need_kind, const char **need_or_null, - const char **given_msg, const char **given_kind) + zend_string **need_msg, const char **given_msg, const char **given_kind) { - zend_bool is_interface = 0; + smart_str str = {0}; *fname = ZSTR_VAL(zf->common.function_name); if (zf->common.scope) { *fsep = "::"; @@ -659,54 +676,69 @@ static ZEND_COLD void zend_verify_type_error_common( *fclass = ""; } - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { + if (is_union_type(arg_info->type)) { + zend_string *type_str = zend_type_to_string(arg_info->type); + smart_str_appends(&str, "be of type "); + smart_str_append(&str, type_str); + zend_string_release(type_str); + } else if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + zend_bool is_interface = 0; zend_class_entry *ce = *cache_slot; + if (!ce) { + ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), + (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + } if (ce) { if (ce->ce_flags & ZEND_ACC_INTERFACE) { - *need_msg = "implement interface "; + smart_str_appends(&str, "implement interface "); is_interface = 1; } else { - *need_msg = "be an instance of "; + smart_str_appends(&str, "be an instance of "); } - *need_kind = ZSTR_VAL(ce->name); + smart_str_append(&str, ce->name); } else { /* We don't know whether it's a class or interface, assume it's a class */ + smart_str_appends(&str, "be an instance of "); + smart_str_append(&str, ZEND_TYPE_NAME(arg_info->type)); + } - *need_msg = "be an instance of "; - *need_kind = ZSTR_VAL(ZEND_TYPE_NAME(arg_info->type)); + if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { + smart_str_appends(&str, is_interface ? " or be null" : " or null"); } } else { - zend_type type = ZEND_TYPE_WITHOUT_NULL(arg_info->type); - switch (ZEND_TYPE_MASK(type)) { + uint32_t type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(arg_info->type); + switch (type_mask) { case MAY_BE_OBJECT: - *need_msg = "be an "; - *need_kind = "object"; + smart_str_appends(&str, "be an object"); break; case MAY_BE_CALLABLE: - *need_msg = "be callable"; - *need_kind = ""; + smart_str_appends(&str, "be callable"); break; case MAY_BE_ITERABLE: - *need_msg = "be iterable"; - *need_kind = ""; + smart_str_appends(&str, "be iterable"); break; default: - /* TODO: The zend_type_to_string() result is guaranteed interned here. - * It would be beter to switch all this code to use zend_string though. */ - *need_msg = "be of the type "; - *need_kind = ZSTR_VAL(zend_type_to_string(type)); + { + /* Hack to print the type without null */ + zend_type type = arg_info->type; + ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; + zend_string *type_str = zend_type_to_string(type); + smart_str_appends(&str, "be of the type "); + smart_str_append(&str, type_str); + zend_string_release(type_str); break; + } } - } - if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - *need_or_null = is_interface ? " or be null" : " or null"; - } else { - *need_or_null = ""; + if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { + smart_str_appends(&str, " or null"); + } } + *need_msg = smart_str_extract(&str); + if (value) { - if (ZEND_TYPE_IS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) { + if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(value) == IS_OBJECT) { *given_msg = "instance of "; *given_kind = ZSTR_VAL(Z_OBJCE_P(value)->name); } else { @@ -725,7 +757,8 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( { zend_execute_data *ptr = EG(current_execute_data)->prev_execute_data; const char *fname, *fsep, *fclass; - const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind; + zend_string *need_msg; + const char *given_msg, *given_kind; if (EG(exception)) { /* The type verification itself might have already thrown an exception @@ -736,19 +769,21 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( if (value) { zend_verify_type_error_common( zf, arg_info, cache_slot, value, - &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind); + &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind); if (zf->common.type == ZEND_USER_FUNCTION) { if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) { - zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given, called in %s on line %d", - arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind, + zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given, called in %s on line %d", + arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind, ZSTR_VAL(ptr->func->op_array.filename), ptr->opline->lineno); } else { - zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given", arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); } } else { - zend_type_error("Argument %d passed to %s%s%s() must %s%s%s, %s%s given", arg_num, fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_type_error("Argument %d passed to %s%s%s() must %s, %s%s given", arg_num, fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); } + + zend_string_release(need_msg); } else { zend_missing_arg_error(ptr); } @@ -756,42 +791,47 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg) { - if (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) { - zend_bool dest; + zend_long lval; + double dval; + zend_string *str; + zend_bool bval; - if (!zend_parse_arg_bool_weak(arg, &dest)) { - return 0; + /* Type preference order: int -> float -> string -> bool */ + if (type_mask & MAY_BE_LONG) { + /* For an int|float union type and string value, + * determine chosen type by is_numeric_string() semantics. */ + if ((type_mask & MAY_BE_DOUBLE) && Z_TYPE_P(arg) == IS_STRING) { + zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), &lval, &dval, -1); + if (type == IS_LONG) { + zend_string_release(Z_STR_P(arg)); + ZVAL_LONG(arg, lval); + return 1; + } + if (type == IS_DOUBLE) { + zend_string_release(Z_STR_P(arg)); + ZVAL_DOUBLE(arg, dval); + return 1; + } + } else if (zend_parse_arg_long_weak(arg, &lval)) { + zval_ptr_dtor(arg); + ZVAL_LONG(arg, lval); + return 1; } + } + if ((type_mask & MAY_BE_DOUBLE) && zend_parse_arg_double_weak(arg, &dval)) { zval_ptr_dtor(arg); - ZVAL_BOOL(arg, dest); + ZVAL_DOUBLE(arg, dval); return 1; } - if (type_mask & MAY_BE_LONG) { - zend_long dest; - - if (!zend_parse_arg_long_weak(arg, &dest)) { - return 0; - } - zval_ptr_dtor(arg); - ZVAL_LONG(arg, dest); + if ((type_mask & MAY_BE_STRING) && zend_parse_arg_str_weak(arg, &str)) { + /* on success "arg" is converted to IS_STRING */ return 1; } - if (type_mask & MAY_BE_DOUBLE) { - double dest; - - if (!zend_parse_arg_double_weak(arg, &dest)) { - return 0; - } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) { zval_ptr_dtor(arg); - ZVAL_DOUBLE(arg, dest); + ZVAL_BOOL(arg, bval); return 1; } - if (type_mask & MAY_BE_STRING) { - zend_string *dest; - - /* on success "arg" is converted to IS_STRING */ - return zend_parse_arg_str_weak(arg, &dest); - } return 0; } @@ -799,40 +839,48 @@ static zend_bool zend_verify_weak_scalar_type_hint(uint32_t type_mask, zval *arg /* Used to sanity-check internal arginfo types without performing any actual type conversions. */ static zend_bool zend_verify_weak_scalar_type_hint_no_sideeffect(uint32_t type_mask, zval *arg) { - if (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE)) { - zend_bool dest; - return zend_parse_arg_bool_weak(arg, &dest); - } + zend_long lval; + double dval; + zend_bool bval; + if (type_mask & MAY_BE_LONG) { - zend_long dest; if (Z_TYPE_P(arg) == IS_STRING) { /* Handle this case separately to avoid the "non well-formed" warning */ - double dval; zend_uchar type = is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, &dval, 1); if (type == IS_LONG) { return 1; } if (type == IS_DOUBLE) { - return !zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval); + if ((type_mask & MAY_BE_DOUBLE) + || (!zend_isnan(dval) && ZEND_DOUBLE_FITS_LONG(dval))) { + return 1; + } } - return 0; } - return zend_parse_arg_long_weak(arg, &dest); + if (zend_parse_arg_long_weak(arg, &lval)) { + return 1; + } } if (type_mask & MAY_BE_DOUBLE) { - double dest; if (Z_TYPE_P(arg) == IS_STRING) { /* Handle this case separately to avoid the "non well-formed" warning */ - return is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 1) != 0; + if (is_numeric_string(Z_STRVAL_P(arg), Z_STRLEN_P(arg), NULL, NULL, 1) != 0) { + return 1; + } + } + if (zend_parse_arg_double_weak(arg, &dval)) { + return 1; } - return zend_parse_arg_double_weak(arg, &dest); } - if (type_mask & MAY_BE_STRING) { - /* We don't call cast_object here, because this check must be side-effect free. As this - * is only used for a sanity check of arginfo/zpp consistency, it's okay if we accept - * more than actually allowed here. */ - return Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT; + /* We don't call cast_object here, because this check must be side-effect free. As this + * is only used for a sanity check of arginfo/zpp consistency, it's okay if we accept + * more than actually allowed here. */ + if ((type_mask & MAY_BE_STRING) && (Z_TYPE_P(arg) < IS_STRING || Z_TYPE_P(arg) == IS_OBJECT)) { + return 1; + } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL && zend_parse_arg_bool_weak(arg, &bval)) { + return 1; } return 0; } @@ -846,12 +894,10 @@ ZEND_API zend_bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, z return 0; } } else if (UNEXPECTED(Z_TYPE_P(arg) == IS_NULL)) { - /* NULL may be accepted only by nullable hints (this is already checked) */ - if (is_internal_arg && (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) { - /* As an exception, null is allowed for scalar types in weak mode. */ - return 1; - } - return 0; + /* NULL may be accepted only by nullable hints (this is already checked). + * As an exception for internal functions, null is allowed for scalar types in weak mode. */ + return is_internal_arg + && (type_mask & (MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)); } #if ZEND_DEBUG if (is_internal_arg) { @@ -879,59 +925,77 @@ ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_i zend_string_release(type_str); } -static zend_bool zend_resolve_class_type(zend_type *type, zend_class_entry *self_ce) { - zend_class_entry *ce; - zend_string *name = ZEND_TYPE_NAME(*type); +static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class_entry *self_ce) { if (zend_string_equals_literal_ci(name, "self")) { /* We need to explicitly check for this here, to avoid updating the type in the trait and * later using the wrong "self" when the trait is used in a class. */ if (UNEXPECTED((self_ce->ce_flags & ZEND_ACC_TRAIT) != 0)) { - zend_throw_error(NULL, "Cannot write a%s value to a 'self' typed static property of a trait", ZEND_TYPE_ALLOW_NULL(*type) ? " non-null" : ""); - return 0; + return NULL; } - ce = self_ce; + return self_ce; } else if (zend_string_equals_literal_ci(name, "parent")) { - if (UNEXPECTED(!self_ce->parent)) { - zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent"); - return 0; - } - ce = self_ce->parent; + return self_ce->parent; } else { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - return 0; - } + return zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); } - - zend_string_release(name); - *type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(*type)); - return 1; } +static zend_bool zend_check_and_resolve_property_class_type( + zend_property_info *info, zend_class_entry *object_ce) { + zend_class_entry *ce; + if (ZEND_TYPE_HAS_LIST(info->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(info->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry); + ce = resolve_single_class_type(name, info->ce); + if (!ce) { + continue; + } + zend_string_release(name); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } else { + ce = ZEND_TYPE_LIST_GET_CE(*entry); + } + if (instanceof_function(object_ce, ce)) { + return 1; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return 0; + } else { + if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) { + zend_string *name = ZEND_TYPE_NAME(info->type); + ce = resolve_single_class_type(name, info->ce); + if (UNEXPECTED(!ce)) { + return 0; + } + + zend_string_release(name); + ZEND_TYPE_SET_CE(info->type, ce); + } else { + ce = ZEND_TYPE_CE(info->type); + } + return instanceof_function(object_ce, ce); + } +} static zend_always_inline zend_bool i_zend_check_property_type(zend_property_info *info, zval *property, zend_bool strict) { ZEND_ASSERT(!Z_ISREF_P(property)); - if (ZEND_TYPE_IS_CLASS(info->type)) { - if (UNEXPECTED(Z_TYPE_P(property) != IS_OBJECT)) { - return Z_TYPE_P(property) == IS_NULL && ZEND_TYPE_ALLOW_NULL(info->type); - } - - if (UNEXPECTED(!ZEND_TYPE_IS_CE(info->type)) && UNEXPECTED(!zend_resolve_class_type(&info->type, info->ce))) { - return 0; - } + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { + return 1; + } - return instanceof_function(Z_OBJCE_P(property), ZEND_TYPE_CE(info->type)); + if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT + && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) { + return 1; } - ZEND_ASSERT(!(ZEND_TYPE_MASK(info->type) & MAY_BE_CALLABLE)); - if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE_P(property)))) { + ZEND_ASSERT(!(ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_CALLABLE)); + if ((ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_ITERABLE) && zend_is_iterable(property)) { return 1; - } else if (ZEND_TYPE_MASK(info->type) & MAY_BE_ITERABLE) { - return zend_is_iterable(property); - } else { - return zend_verify_scalar_type_hint(ZEND_TYPE_MASK(info->type), property, strict, 0); } + return zend_verify_scalar_type_hint(ZEND_TYPE_FULL_MASK(info->type), property, strict, 0); } static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict) @@ -977,43 +1041,70 @@ static zend_always_inline zend_bool zend_check_type( arg = Z_REFVAL_P(arg); } - if (ZEND_TYPE_IS_CLASS(type)) { + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) { + return 1; + } + + if (ZEND_TYPE_HAS_CLASS(type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *) *cache_slot; + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (*cache_slot) { + ce = *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_LIST_GET_NAME(entry), + (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + if (!ce) { + cache_slot++; + continue; + } + *cache_slot = ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return 1; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); } else { - ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (UNEXPECTED(!ce)) { - return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type); + if (EXPECTED(*cache_slot)) { + ce = (zend_class_entry *) *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + if (UNEXPECTED(!ce)) { + goto builtin_types; + } + *cache_slot = (void *) ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return 1; } - *cache_slot = (void *) ce; - } - if (EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { - return instanceof_function(Z_OBJCE_P(arg), ce); } - return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type); - } else if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, Z_TYPE_P(arg)))) { - return 1; } - type_mask = ZEND_TYPE_MASK(type); - if (type_mask & MAY_BE_CALLABLE) { - return zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL); - } else if (type_mask & MAY_BE_ITERABLE) { - return zend_is_iterable(arg); - } else if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { - return 0; /* we cannot have conversions for typed refs */ - } else if (is_internal && is_return_type) { +builtin_types: + type_mask = ZEND_TYPE_FULL_MASK(type); + if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + return 1; + } + if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { + return 1; + } + if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) { + /* We cannot have conversions for typed refs. */ + return 0; + } + if (is_internal && is_return_type) { /* For internal returns, the type has to match exactly, because we're not * going to check it for non-debug builds, and there will be no chance to * apply coercions. */ return 0; - } else { - return zend_verify_scalar_type_hint(type_mask, arg, - is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(), - is_internal); } + return zend_verify_scalar_type_hint(type_mask, arg, + is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES(), + is_internal); + /* Special handling for IS_VOID is not necessary (for return types), * because this case is already checked at compile-time. */ } @@ -1135,14 +1226,17 @@ static ZEND_COLD void zend_verify_return_error( { const zend_arg_info *arg_info = &zf->common.arg_info[-1]; const char *fname, *fsep, *fclass; - const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind; + zend_string *need_msg; + const char *given_msg, *given_kind; zend_verify_type_error_common( zf, arg_info, cache_slot, value, - &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind); + &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind); - zend_type_error("Return value of %s%s%s() must %s%s%s, %s%s returned", - fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_type_error("Return value of %s%s%s() must %s, %s%s returned", + fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); + + zend_string_release(need_msg); } #if ZEND_DEBUG @@ -1151,14 +1245,15 @@ static ZEND_COLD void zend_verify_internal_return_error( { const zend_arg_info *arg_info = &zf->common.arg_info[-1]; const char *fname, *fsep, *fclass; - const char *need_msg, *need_kind, *need_or_null, *given_msg, *given_kind; + zend_string *need_msg; + const char *given_msg, *given_kind; zend_verify_type_error_common( zf, arg_info, cache_slot, value, - &fname, &fsep, &fclass, &need_msg, &need_kind, &need_or_null, &given_msg, &given_kind); + &fname, &fsep, &fclass, &need_msg, &given_msg, &given_kind); - zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s%s%s, %s%s returned", - fclass, fsep, fname, need_msg, need_kind, need_or_null, given_msg, given_kind); + zend_error_noreturn(E_CORE_ERROR, "Return value of %s%s%s() must %s, %s%s returned", + fclass, fsep, fname, ZSTR_VAL(need_msg), given_msg, given_kind); } static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, const char *returned_msg, const char *returned_kind) @@ -1184,7 +1279,7 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret) zend_internal_arg_info *ret_info = zf->internal_function.arg_info - 1; void *dummy_cache_slot = NULL; - if (ZEND_TYPE_IS_MASK(ret_info->type) && (ZEND_TYPE_MASK(ret_info->type) & MAY_BE_VOID)) { + if (ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_VOID) { if (UNEXPECTED(Z_TYPE_P(ret) != IS_NULL)) { zend_verify_void_return_error(zf, zend_zval_type_name(ret), ""); return 0; @@ -1213,17 +1308,6 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval * static ZEND_COLD int zend_verify_missing_return_type(const zend_function *zf, void **cache_slot) { /* VERIFY_RETURN_TYPE is not emitted for "void" functions, so this is always an error. */ - zend_arg_info *ret_info = zf->common.arg_info - 1; - - // TODO: Eliminate this! - if (ZEND_TYPE_IS_CLASS(ret_info->type)) { - if (UNEXPECTED(!*cache_slot)) { - zend_class_entry *ce = zend_fetch_class(ZEND_TYPE_NAME(ret_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (ce) { - *cache_slot = (void *) ce; - } - } - } zend_verify_return_error(zf, cache_slot, NULL); return 0; } @@ -1560,32 +1644,32 @@ static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *re { zend_property_info *prop; ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - if (!ZEND_TYPE_IS_MASK(prop->type) || !(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { + if (!(ZEND_TYPE_FULL_MASK(prop->type) & MAY_BE_DOUBLE)) { return prop; } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); return NULL; } -static ZEND_COLD zend_long zend_throw_incdec_ref_error(zend_reference *ref OPLINE_DC) +static ZEND_COLD zend_long zend_throw_incdec_ref_error( + zend_reference *ref, zend_property_info *error_prop OPLINE_DC) { - zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref); - /* Currently there should be no way for a typed reference to accept both int and double. - * Generalize this and the related property code once this becomes possible. */ - ZEND_ASSERT(error_prop); + zend_string *type_str = zend_type_to_string(error_prop->type); if (ZEND_IS_INCREMENT(opline->opcode)) { zend_type_error( - "Cannot increment a reference held by property %s::$%s of type %sint past its maximal value", + "Cannot increment a reference held by property %s::$%s of type %s past its maximal value", ZSTR_VAL(error_prop->ce->name), zend_get_unmangled_property_name(error_prop->name), - ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : ""); + ZSTR_VAL(type_str)); + zend_string_release(type_str); return ZEND_LONG_MAX; } else { zend_type_error( - "Cannot decrement a reference held by property %s::$%s of type %sint past its minimal value", + "Cannot decrement a reference held by property %s::$%s of type %s past its minimal value", ZSTR_VAL(error_prop->ce->name), zend_get_unmangled_property_name(error_prop->name), - ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : ""); + ZSTR_VAL(type_str)); + zend_string_release(type_str); return ZEND_LONG_MIN; } } @@ -1627,8 +1711,11 @@ static void zend_incdec_typed_ref(zend_reference *ref, zval *copy OPLINE_DC EXEC } if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(copy) == IS_LONG) { - zend_long val = zend_throw_incdec_ref_error(ref OPLINE_CC); - ZVAL_LONG(var_ptr, val); + zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref); + if (UNEXPECTED(error_prop)) { + zend_long val = zend_throw_incdec_ref_error(ref, error_prop OPLINE_CC); + ZVAL_LONG(var_ptr, val); + } } else if (UNEXPECTED(!zend_verify_ref_assignable_zval(ref, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, copy); @@ -1655,8 +1742,10 @@ static void zend_incdec_typed_prop(zend_property_info *prop_info, zval *var_ptr, } if (UNEXPECTED(Z_TYPE_P(var_ptr) == IS_DOUBLE) && Z_TYPE_P(copy) == IS_LONG) { - zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); - ZVAL_LONG(var_ptr, val); + if (!(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { + zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); + ZVAL_LONG(var_ptr, val); + } } else if (UNEXPECTED(!zend_verify_property_type(prop_info, var_ptr, EX_USES_STRICT_TYPES()))) { zval_ptr_dtor(var_ptr); ZVAL_COPY_VALUE(var_ptr, copy); @@ -1674,7 +1763,8 @@ static void zend_pre_incdec_property_zval(zval *prop, zend_property_info *prop_i } else { fast_long_decrement_function(prop); } - if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) { + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); ZVAL_LONG(prop, val); } @@ -1712,7 +1802,8 @@ static void zend_post_incdec_property_zval(zval *prop, zend_property_info *prop_ } else { fast_long_decrement_function(prop); } - if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) { + if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info) + && !(ZEND_TYPE_FULL_MASK(prop_info->type) & MAY_BE_DOUBLE)) { zend_long val = zend_throw_incdec_prop_error(prop_info OPLINE_CC); ZVAL_LONG(prop, val); } @@ -2528,7 +2619,7 @@ static zend_always_inline zend_bool check_type_array_assignable(zend_type type) if (!ZEND_TYPE_IS_SET(type)) { return 1; } - return ZEND_TYPE_IS_MASK(type) && (ZEND_TYPE_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)); + return (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_ITERABLE|MAY_BE_ARRAY)) != 0; } /* Checks whether an array can be assigned to the reference. Throws error if not assignable. */ @@ -2937,29 +3028,22 @@ ZEND_API ZEND_COLD void zend_throw_conflicting_coercion_error(zend_property_info /* 1: valid, 0: invalid, -1: may be valid after type coercion */ static zend_always_inline int i_zend_verify_type_assignable_zval( - zend_type *type_ptr, zend_class_entry *self_ce, zval *zv, zend_bool strict) { - zend_type type = *type_ptr; + zend_property_info *info, zval *zv, zend_bool strict) { + zend_type type = info->type; uint32_t type_mask; zend_uchar zv_type = Z_TYPE_P(zv); - if (ZEND_TYPE_IS_CLASS(type)) { - if (ZEND_TYPE_ALLOW_NULL(type) && zv_type == IS_NULL) { - return 1; - } - if (!ZEND_TYPE_IS_CE(type)) { - if (!zend_resolve_class_type(type_ptr, self_ce)) { - return 0; - } - type = *type_ptr; - } - return zv_type == IS_OBJECT && instanceof_function(Z_OBJCE_P(zv), ZEND_TYPE_CE(type)); + if (EXPECTED(ZEND_TYPE_CONTAINS_CODE(type, zv_type))) { + return 1; } - if (ZEND_TYPE_CONTAINS_CODE(type, zv_type)) { + if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT + && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) { return 1; } - type_mask = ZEND_TYPE_MASK(type); + type_mask = ZEND_TYPE_FULL_MASK(type); + ZEND_ASSERT(!(type_mask & MAY_BE_CALLABLE)); if (type_mask & MAY_BE_ITERABLE) { return zend_is_iterable(zv); } @@ -2972,13 +3056,14 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( return 0; } - /* No weak conversions for arrays and objects */ - if (type_mask & (MAY_BE_ARRAY|MAY_BE_OBJECT)) { + /* NULL may be accepted only by nullable hints (this is already checked) */ + if (zv_type == IS_NULL) { return 0; } - /* NULL may be accepted only by nullable hints (this is already checked) */ - if (zv_type == IS_NULL) { + /* Does not contain any type to which a coercion is possible */ + if (!(type_mask & (MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING)) + && (type_mask & MAY_BE_BOOL) != MAY_BE_BOOL) { return 0; } @@ -2991,39 +3076,62 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_ref_assignable_zval(zend_reference zend_property_info *prop; /* The value must satisfy each property type, and coerce to the same value for each property - * type. Right now, the latter rule means that *if* coercion is necessary, then all types - * must be the same (modulo nullability). To handle this, remember the first type we see and - * compare against it when coercion becomes necessary. */ - zend_property_info *seen_prop = NULL; - uint32_t seen_type_mask; - zend_bool needs_coercion = 0; + * type. Remember the first coerced type and value we've seen for this purpose. */ + zend_property_info *first_prop = NULL; + zval coerced_value; + ZVAL_UNDEF(&coerced_value); ZEND_ASSERT(Z_TYPE_P(zv) != IS_REFERENCE); ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - int result = i_zend_verify_type_assignable_zval(&prop->type, prop->ce, zv, strict); + int result = i_zend_verify_type_assignable_zval(prop, zv, strict); if (result == 0) { +type_error: zend_throw_ref_type_error_zval(prop, zv); + zval_ptr_dtor(&coerced_value); return 0; } if (result < 0) { - needs_coercion = 1; - } - - if (!seen_prop) { - seen_prop = prop; - seen_type_mask = ZEND_TYPE_IS_CLASS(prop->type) - ? MAY_BE_OBJECT : ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(prop->type)); - } else if (needs_coercion - && seen_type_mask != ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(prop->type))) { - zend_throw_conflicting_coercion_error(seen_prop, prop, zv); - return 0; + if (!first_prop) { + first_prop = prop; + ZVAL_COPY(&coerced_value, zv); + if (!zend_verify_weak_scalar_type_hint( + ZEND_TYPE_FULL_MASK(prop->type), &coerced_value)) { + goto type_error; + } + } else if (Z_ISUNDEF(coerced_value)) { + /* A previous property did not require coercion, but this one does, + * so they are incompatible. */ + goto conflicting_coercion_error; + } else { + zval tmp; + ZVAL_COPY(&tmp, zv); + if (!zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop->type), &tmp)) { + zval_ptr_dtor(&tmp); + goto type_error; + } + if (!zend_is_identical(&coerced_value, &tmp)) { + zval_ptr_dtor(&tmp); + goto conflicting_coercion_error; + } + } + } else { + if (!first_prop) { + first_prop = prop; + } else if (!Z_ISUNDEF(coerced_value)) { + /* A previous property required coercion, but this one doesn't, + * so they are incompatible. */ +conflicting_coercion_error: + zend_throw_conflicting_coercion_error(first_prop, prop, zv); + zval_ptr_dtor(&coerced_value); + return 0; + } } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); - if (UNEXPECTED(needs_coercion && !zend_verify_weak_scalar_type_hint(seen_type_mask, zv))) { - zend_throw_ref_type_error_zval(seen_prop, zv); - return 0; + if (!Z_ISUNDEF(coerced_value)) { + zval_ptr_dtor(zv); + ZVAL_COPY_VALUE(zv, &coerced_value); } return 1; @@ -3075,22 +3183,23 @@ ZEND_API zend_bool ZEND_FASTCALL zend_verify_prop_assignable_by_ref(zend_propert int result; val = Z_REFVAL_P(val); - result = i_zend_verify_type_assignable_zval(&prop_info->type, prop_info->ce, val, strict); + result = i_zend_verify_type_assignable_zval(prop_info, val, strict); if (result > 0) { return 1; } if (result < 0) { - zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val)); - if (ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(prop_info->type)) - != ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(ref_prop->type))) { - /* Invalid due to conflicting coercion */ + /* This is definitely an error, but we still need to determined why: Either because + * the value is simply illegal for the type, or because or a conflicting coercion. */ + zval tmp; + ZVAL_COPY(&tmp, val); + if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_FULL_MASK(prop_info->type), &tmp)) { + zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val)); zend_throw_ref_type_error_type(ref_prop, prop_info, val); + zval_ptr_dtor(&tmp); return 0; } - if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_MASK(prop_info->type), val)) { - return 1; - } + zval_ptr_dtor(&tmp); } } else { ZVAL_DEREF(val); diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index a1ecb9e2b70f3..9b8a47f365ba5 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -40,14 +40,31 @@ static void overridden_ptr_dtor(zval *zv) /* {{{ */ } /* }}} */ +static void zend_type_copy_ctor(zend_type *type, zend_bool persistent) { + if (ZEND_TYPE_HAS_LIST(*type)) { + zend_type_list *old_list = ZEND_TYPE_LIST(*type); + size_t size = ZEND_TYPE_LIST_SIZE(old_list->num_types); + zend_type_list *new_list = ZEND_TYPE_USES_ARENA(*type) + ? zend_arena_alloc(&CG(arena), size) : pemalloc(size, persistent); + memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); + ZEND_TYPE_SET_PTR(*type, new_list); + + void *entry; + ZEND_TYPE_LIST_FOREACH(new_list, entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry)); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string_addref(ZEND_TYPE_NAME(*type)); + } +} + static zend_property_info *zend_duplicate_property_info_internal(zend_property_info *property_info) /* {{{ */ { zend_property_info* new_property_info = pemalloc(sizeof(zend_property_info), 1); memcpy(new_property_info, property_info, sizeof(zend_property_info)); zend_string_addref(new_property_info->name); - if (ZEND_TYPE_IS_NAME(new_property_info->type)) { - zend_string_addref(ZEND_TYPE_NAME(new_property_info->type)); - } + zend_type_copy_ctor(&new_property_info->type, /* persistent */ 1); return new_property_info; } @@ -219,7 +236,8 @@ static zend_bool class_visible(zend_class_entry *ce) { } } -static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name) { +static zend_class_entry *lookup_class( + zend_class_entry *scope, zend_string *name, zend_bool register_unresolved) { zend_class_entry *ce; if (!CG(in_compilation)) { uint32_t flags = ZEND_FETCH_CLASS_ALLOW_UNLINKED | ZEND_FETCH_CLASS_NO_AUTOLOAD; @@ -228,12 +246,14 @@ static zend_class_entry *lookup_class(zend_class_entry *scope, zend_string *name return ce; } - /* We'll autoload this class and process delayed variance obligations later. */ - if (!CG(delayed_autoloads)) { - ALLOC_HASHTABLE(CG(delayed_autoloads)); - zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0); + if (register_unresolved) { + /* We'll autoload this class and process delayed variance obligations later. */ + if (!CG(delayed_autoloads)) { + ALLOC_HASHTABLE(CG(delayed_autoloads)); + zend_hash_init(CG(delayed_autoloads), 0, NULL, NULL, 0); + } + zend_hash_add_empty_element(CG(delayed_autoloads), name); } - zend_hash_add_empty_element(CG(delayed_autoloads), name); } else { ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); if (ce && class_visible(ce)) { @@ -302,6 +322,23 @@ static zend_bool unlinked_instanceof(zend_class_entry *ce1, zend_class_entry *ce return 0; } +static zend_bool zend_type_contains_traversable(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + if (zend_string_equals_literal_ci(ZEND_TYPE_LIST_GET_NAME(entry), "Traversable")) { + return 1; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return 0; + } + if (ZEND_TYPE_HAS_NAME(type)) { + return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "Traversable"); + } + return 0; +} + /* Unresolved means that class declarations that are currently not available are needed to * determine whether the inheritance is valid or not. At runtime UNRESOLVED should be treated * as an ERROR. */ @@ -311,86 +348,150 @@ typedef enum { INHERITANCE_SUCCESS = 1, } inheritance_status; -static inheritance_status zend_perform_covariant_type_check( - zend_string **unresolved_class, - const zend_function *fe, zend_arg_info *fe_arg_info, - const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ -{ - zend_type fe_type = fe_arg_info->type, proto_type = proto_arg_info->type; - ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); - - if (ZEND_TYPE_ALLOW_NULL(fe_type) && !ZEND_TYPE_ALLOW_NULL(proto_type)) { - return INHERITANCE_ERROR; +static inheritance_status zend_perform_covariant_class_type_check( + zend_class_entry *fe_scope, zend_string *fe_class_name, + zend_class_entry *proto_scope, zend_type proto_type, + zend_bool register_unresolved) { + zend_bool have_unresolved = 0; + zend_class_entry *fe_ce = NULL; + if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_OBJECT) { + /* Currently, any class name would be allowed here. We still perform a class lookup + * for forward-compatibility reasons, as we may have named types in the future that + * are not classes (such as enums or typedefs). */ + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); + if (!fe_ce) { + have_unresolved = 1; + } else { + return INHERITANCE_SUCCESS; + } } - - if (ZEND_TYPE_IS_CLASS(proto_type)) { - zend_string *fe_class_name, *proto_class_name; - zend_class_entry *fe_ce, *proto_ce; - if (!ZEND_TYPE_IS_CLASS(fe_type)) { - return INHERITANCE_ERROR; + if (ZEND_TYPE_FULL_MASK(proto_type) & MAY_BE_ITERABLE) { + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); + if (!fe_ce) { + have_unresolved = 1; + } else if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { + return INHERITANCE_SUCCESS; } - - fe_class_name = resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); - proto_class_name = resolve_class_name(proto->common.scope, ZEND_TYPE_NAME(proto_type)); + } + if (ZEND_TYPE_HAS_NAME(proto_type)) { + zend_string *proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(proto_type)); if (zend_string_equals_ci(fe_class_name, proto_class_name)) { return INHERITANCE_SUCCESS; } /* Make sure to always load both classes, to avoid only registering one of them as * a delayed autoload. */ - fe_ce = lookup_class(fe->common.scope, fe_class_name); - proto_ce = lookup_class(proto->common.scope, proto_class_name); - if (!fe_ce) { - *unresolved_class = fe_class_name; - return INHERITANCE_UNRESOLVED; - } - if (!proto_ce) { - *unresolved_class = proto_class_name; - return INHERITANCE_UNRESOLVED; + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); + zend_class_entry *proto_ce = + lookup_class(proto_scope, proto_class_name, register_unresolved); + if (!fe_ce || !proto_ce) { + have_unresolved = 1; + } else if (unlinked_instanceof(fe_ce, proto_ce)) { + return INHERITANCE_SUCCESS; } + } + if (ZEND_TYPE_HAS_LIST(proto_type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(proto_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + zend_string *proto_class_name = + resolve_class_name(proto_scope, ZEND_TYPE_LIST_GET_NAME(entry)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + return INHERITANCE_SUCCESS; + } - return unlinked_instanceof(fe_ce, proto_ce) ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else if (ZEND_TYPE_MASK(proto_type) & MAY_BE_ITERABLE) { - if (ZEND_TYPE_IS_CLASS(fe_type)) { - zend_string *fe_class_name = - resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); - zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name); - if (!fe_ce) { - *unresolved_class = fe_class_name; - return INHERITANCE_UNRESOLVED; + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); + zend_class_entry *proto_ce = + lookup_class(proto_scope, proto_class_name, register_unresolved); + if (!fe_ce || !proto_ce) { + have_unresolved = 1; + } else if (unlinked_instanceof(fe_ce, proto_ce)) { + return INHERITANCE_SUCCESS; } - return unlinked_instanceof(fe_ce, zend_ce_traversable) - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + } ZEND_TYPE_LIST_FOREACH_END(); + } + return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; +} + +static inheritance_status zend_perform_covariant_type_check( + zend_class_entry *fe_scope, zend_type fe_type, + zend_class_entry *proto_scope, zend_type proto_type) /* {{{ */ +{ + ZEND_ASSERT(ZEND_TYPE_IS_SET(fe_type) && ZEND_TYPE_IS_SET(proto_type)); + + /* Builtin types may be removed, but not added */ + uint32_t fe_type_mask = ZEND_TYPE_PURE_MASK(fe_type); + uint32_t proto_type_mask = ZEND_TYPE_PURE_MASK(proto_type); + uint32_t added_types = fe_type_mask & ~proto_type_mask; + if (added_types) { + // TODO: Make "iterable" an alias of "array|Traversable" instead, + // so these special cases will be handled automatically. + if (added_types == MAY_BE_ITERABLE + && (proto_type_mask & MAY_BE_ARRAY) + && zend_type_contains_traversable(proto_type)) { + /* Replacing array|Traversable with iterable is okay */ + } else if (added_types == MAY_BE_ARRAY && (proto_type_mask & MAY_BE_ITERABLE)) { + /* Replacing iterable with array is okay */ + } else { + /* Otherwise adding new types is illegal */ + return INHERITANCE_ERROR; + } + } + + if (ZEND_TYPE_HAS_NAME(fe_type)) { + zend_string *fe_class_name = resolve_class_name(fe_scope, ZEND_TYPE_NAME(fe_type)); + inheritance_status status = zend_perform_covariant_class_type_check( + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 0); + if (status != INHERITANCE_UNRESOLVED) { + return status; } - return ZEND_TYPE_MASK(fe_type) & (MAY_BE_ARRAY|MAY_BE_ITERABLE) - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else if (ZEND_TYPE_MASK(proto_type) & MAY_BE_OBJECT) { - if (ZEND_TYPE_IS_CLASS(fe_type)) { - /* Currently, any class name would be allowed here. We still perform a class lookup - * for forward-compatibility reasons, as we may have named types in the future that - * are not classes (such as enums or typedefs). */ + zend_perform_covariant_class_type_check( + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 1); + return INHERITANCE_UNRESOLVED; + } + + if (ZEND_TYPE_HAS_LIST(fe_type)) { + void *entry; + zend_bool all_success = 1; + + /* First try to check whether we can succeed without resolving anything */ + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); zend_string *fe_class_name = - resolve_class_name(fe->common.scope, ZEND_TYPE_NAME(fe_type)); - zend_class_entry *fe_ce = lookup_class(fe->common.scope, fe_class_name); - if (!fe_ce) { - *unresolved_class = fe_class_name; - return INHERITANCE_UNRESOLVED; + resolve_class_name(fe_scope, ZEND_TYPE_LIST_GET_NAME(entry)); + inheritance_status status = zend_perform_covariant_class_type_check( + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 0); + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; + } + + if (status != INHERITANCE_SUCCESS) { + all_success = 0; } + } ZEND_TYPE_LIST_FOREACH_END(); + + /* All individual checks suceeded, overall success */ + if (all_success) { return INHERITANCE_SUCCESS; } - return ZEND_TYPE_MASK(fe_type) & MAY_BE_OBJECT ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; - } else { - return ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(fe_type)) - == ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(proto_type)) - ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + /* Register all classes that may have to be resolved */ + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(fe_type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(entry)); + zend_string *fe_class_name = + resolve_class_name(fe_scope, ZEND_TYPE_LIST_GET_NAME(entry)); + zend_perform_covariant_class_type_check( + fe_scope, fe_class_name, proto_scope, proto_type, /* register_unresolved */ 1); + } ZEND_TYPE_LIST_FOREACH_END(); + return INHERITANCE_UNRESOLVED; } + + return INHERITANCE_SUCCESS; } /* }}} */ static inheritance_status zend_do_perform_arg_type_hint_check( - zend_string **unresolved_class, const zend_function *fe, zend_arg_info *fe_arg_info, const zend_function *proto, zend_arg_info *proto_arg_info) /* {{{ */ { @@ -407,12 +508,12 @@ static inheritance_status zend_do_perform_arg_type_hint_check( /* Contravariant type check is performed as a covariant type check with swapped * argument order. */ return zend_perform_covariant_type_check( - unresolved_class, proto, proto_arg_info, fe, fe_arg_info); + proto->common.scope, proto_arg_info->type, fe->common.scope, fe_arg_info->type); } /* }}} */ static inheritance_status zend_do_perform_implementation_check( - zend_string **unresolved_class, const zend_function *fe, const zend_function *proto) /* {{{ */ + const zend_function *fe, const zend_function *proto) /* {{{ */ { uint32_t i, num_args; inheritance_status status, local_status; @@ -478,8 +579,7 @@ static inheritance_status zend_do_perform_implementation_check( proto_arg_info = &proto->common.arg_info[proto->common.num_args]; } - local_status = zend_do_perform_arg_type_hint_check( - unresolved_class, fe, fe_arg_info, proto, proto_arg_info); + local_status = zend_do_perform_arg_type_hint_check(fe, fe_arg_info, proto, proto_arg_info); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { @@ -490,7 +590,7 @@ static inheritance_status zend_do_perform_implementation_check( } /* by-ref constraints on arguments are invariant */ - if (fe_arg_info->pass_by_reference != proto_arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(fe_arg_info) != ZEND_ARG_SEND_MODE(proto_arg_info)) { return INHERITANCE_ERROR; } } @@ -504,7 +604,8 @@ static inheritance_status zend_do_perform_implementation_check( } local_status = zend_perform_covariant_type_check( - unresolved_class, fe, fe->common.arg_info - 1, proto, proto->common.arg_info - 1); + fe->common.scope, fe->common.arg_info[-1].type, + proto->common.scope, proto->common.arg_info[-1].type); if (UNEXPECTED(local_status != INHERITANCE_SUCCESS)) { if (UNEXPECTED(local_status == INHERITANCE_ERROR)) { @@ -561,11 +662,11 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function for (i = 0; i < num_args;) { zend_append_type_hint(&str, fptr, arg_info, 0); - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(&str, '&'); } - if (arg_info->is_variadic) { + if (ZEND_ARG_IS_VARIADIC(arg_info)) { smart_str_appends(&str, "..."); } @@ -582,7 +683,7 @@ static ZEND_COLD zend_string *zend_get_function_declaration(const zend_function smart_str_append_unsigned(&str, i); } - if (i >= required && !arg_info->is_variadic) { + if (i >= required && !ZEND_ARG_IS_VARIADIC(arg_info)) { smart_str_appends(&str, " = "); if (fptr->type == ZEND_USER_FUNCTION) { zend_op *precv = NULL; @@ -663,10 +764,17 @@ static zend_always_inline uint32_t func_lineno(const zend_function *fn) { static void ZEND_COLD emit_incompatible_method_error( const zend_function *child, const zend_function *parent, - inheritance_status status, zend_string *unresolved_class) { + inheritance_status status) { zend_string *parent_prototype = zend_get_function_declaration(parent); zend_string *child_prototype = zend_get_function_declaration(child); if (status == INHERITANCE_UNRESOLVED) { + /* Fetch the first unresolved class from registered autoloads */ + zend_string *unresolved_class = NULL; + ZEND_HASH_FOREACH_STR_KEY(CG(delayed_autoloads), unresolved_class) { + break; + } ZEND_HASH_FOREACH_END(); + ZEND_ASSERT(unresolved_class); + zend_error_at(E_COMPILE_ERROR, NULL, func_lineno(child), "Could not check compatibility between %s and %s, because class %s is not available", ZSTR_VAL(child_prototype), ZSTR_VAL(parent_prototype), ZSTR_VAL(unresolved_class)); @@ -683,17 +791,13 @@ static void perform_delayable_implementation_check( zend_class_entry *ce, const zend_function *fe, const zend_function *proto) { - zend_string *unresolved_class; - inheritance_status status = zend_do_perform_implementation_check( - &unresolved_class, fe, proto); - + inheritance_status status = zend_do_perform_implementation_check(fe, proto); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { add_compatibility_obligation(ce, fe, proto); } else { ZEND_ASSERT(status == INHERITANCE_ERROR); - emit_incompatible_method_error( - fe, proto, status, unresolved_class); + emit_incompatible_method_error(fe, proto, status); } } } @@ -792,10 +896,7 @@ static zend_always_inline inheritance_status do_inheritance_check_on_method_ex(z if (!checked) { if (check_only) { - zend_string *unresolved_class; - - return zend_do_perform_implementation_check( - &unresolved_class, child, parent); + return zend_do_perform_implementation_check(child, parent); } perform_delayable_implementation_check(ce, child, parent); } @@ -845,38 +946,28 @@ static zend_always_inline void do_inherit_method(zend_string *key, zend_function inheritance_status property_types_compatible( const zend_property_info *parent_info, const zend_property_info *child_info) { - zend_string *parent_name, *child_name; - zend_class_entry *parent_type_ce, *child_type_ce; - if (parent_info->type == child_info->type) { + if (ZEND_TYPE_PURE_MASK(parent_info->type) == ZEND_TYPE_PURE_MASK(child_info->type) + && ZEND_TYPE_NAME(parent_info->type) == ZEND_TYPE_NAME(child_info->type)) { return INHERITANCE_SUCCESS; } - if (!ZEND_TYPE_IS_CLASS(parent_info->type) || !ZEND_TYPE_IS_CLASS(child_info->type) || - ZEND_TYPE_ALLOW_NULL(parent_info->type) != ZEND_TYPE_ALLOW_NULL(child_info->type)) { + if (ZEND_TYPE_IS_SET(parent_info->type) != ZEND_TYPE_IS_SET(child_info->type)) { return INHERITANCE_ERROR; } - parent_name = ZEND_TYPE_IS_CE(parent_info->type) - ? ZEND_TYPE_CE(parent_info->type)->name - : resolve_class_name(parent_info->ce, ZEND_TYPE_NAME(parent_info->type)); - child_name = ZEND_TYPE_IS_CE(child_info->type) - ? ZEND_TYPE_CE(child_info->type)->name - : resolve_class_name(child_info->ce, ZEND_TYPE_NAME(child_info->type)); - if (zend_string_equals_ci(parent_name, child_name)) { + /* Perform a covariant type check in both directions to determined invariance. */ + inheritance_status status1 = zend_perform_covariant_type_check( + child_info->ce, child_info->type, parent_info->ce, parent_info->type); + inheritance_status status2 = zend_perform_covariant_type_check( + parent_info->ce, parent_info->type, child_info->ce, child_info->type); + if (status1 == INHERITANCE_SUCCESS && status2 == INHERITANCE_SUCCESS) { return INHERITANCE_SUCCESS; } - - /* Check for class aliases */ - parent_type_ce = ZEND_TYPE_IS_CE(parent_info->type) - ? ZEND_TYPE_CE(parent_info->type) - : lookup_class(parent_info->ce, parent_name); - child_type_ce = ZEND_TYPE_IS_CE(child_info->type) - ? ZEND_TYPE_CE(child_info->type) - : lookup_class(child_info->ce, child_name); - if (!parent_type_ce || !child_type_ce) { - return INHERITANCE_UNRESOLVED; + if (status1 == INHERITANCE_ERROR || status2 == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; } - return parent_type_ce == child_type_ce ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; + ZEND_ASSERT(status1 == INHERITANCE_UNRESOLVED && status2 == INHERITANCE_UNRESOLVED); + return INHERITANCE_UNRESOLVED; } static void emit_incompatible_property_error( @@ -1957,9 +2048,7 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent Z_TRY_ADDREF_P(prop_value); doc_comment = property_info->doc_comment ? zend_string_copy(property_info->doc_comment) : NULL; - if (ZEND_TYPE_IS_NAME(property_info->type)) { - zend_string_addref(ZEND_TYPE_NAME(property_info->type)); - } + zend_type_copy_ctor(&property_info->type, /* persistent */ 0); zend_declare_typed_property(ce, prop_name, prop_value, flags, doc_comment, property_info->type); zend_string_release_ex(prop_name, 0); } ZEND_HASH_FOREACH_END(); @@ -2224,16 +2313,14 @@ static int check_variance_obligation(zval *zv) { return ZEND_HASH_APPLY_KEEP; } } else if (obligation->type == OBLIGATION_COMPATIBILITY) { - zend_string *unresolved_class; inheritance_status status = zend_do_perform_implementation_check( - &unresolved_class, obligation->child_fn, obligation->parent_fn); + obligation->child_fn, obligation->parent_fn); if (UNEXPECTED(status != INHERITANCE_SUCCESS)) { if (EXPECTED(status == INHERITANCE_UNRESOLVED)) { return ZEND_HASH_APPLY_KEEP; } ZEND_ASSERT(status == INHERITANCE_ERROR); - emit_incompatible_method_error( - obligation->child_fn, obligation->parent_fn, status, unresolved_class); + emit_incompatible_method_error(obligation->child_fn, obligation->parent_fn, status); } /* Either the compatibility check was successful or only threw a warning. */ } else { @@ -2296,16 +2383,14 @@ static void report_variance_errors(zend_class_entry *ce) { ZEND_ASSERT(obligations != NULL); ZEND_HASH_FOREACH_PTR(obligations, obligation) { - inheritance_status status; - zend_string *unresolved_class; - if (obligation->type == OBLIGATION_COMPATIBILITY) { - /* Just used to fetch the unresolved_class in this case. */ - status = zend_do_perform_implementation_check( - &unresolved_class, obligation->child_fn, obligation->parent_fn); + /* Just used to populate the delayed_autoloads table, + * which will be used when printing the "unresolved" error. */ + inheritance_status status = zend_do_perform_implementation_check( + obligation->child_fn, obligation->parent_fn); ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); emit_incompatible_method_error( - obligation->child_fn, obligation->parent_fn, status, unresolved_class); + obligation->child_fn, obligation->parent_fn, status); } else if (obligation->type == OBLIGATION_PROPERTY_COMPATIBILITY) { emit_incompatible_property_error(obligation->child_prop, obligation->parent_prop); } else { diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 0f35b69adf93f..57e88e630dee6 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -255,7 +255,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair %type isset_variable type return_type type_expr %type identifier -%type inline_function +%type inline_function union_type %type returns_ref function fn is_reference is_variadic variable_modifiers %type method_modifiers non_empty_member_modifiers member_modifier @@ -660,6 +660,7 @@ optional_type: type_expr: type { $$ = $1; } | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } + | union_type { $$ = $1; } ; type: @@ -668,6 +669,11 @@ type: | name { $$ = $1; } ; +union_type: + type '|' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_UNION, $1, $3); } + | union_type '|' type { $$ = zend_ast_list_add($1, $3); } +; + return_type: /* empty */ { $$ = NULL; } | ':' type_expr { $$ = $2; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 626eacd35d199..e197e9bf16ab5 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -102,6 +102,22 @@ ZEND_API void destroy_zend_function(zend_function *function) zend_function_dtor(&tmp); } +ZEND_API void zend_type_release(zend_type type, zend_bool persistent) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + if (!ZEND_TYPE_USES_ARENA(type)) { + pefree(ZEND_TYPE_LIST(type), persistent); + } + } else if (ZEND_TYPE_HAS_NAME(type)) { + zend_string_release(ZEND_TYPE_NAME(type)); + } +} + void zend_free_internal_arg_info(zend_internal_function *function) { if ((function->fn_flags & (ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_HAS_TYPE_HINTS)) && function->arg_info) { @@ -114,9 +130,7 @@ void zend_free_internal_arg_info(zend_internal_function *function) { num_args++; } for (i = 0 ; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string_release_ex(ZEND_TYPE_NAME(arg_info[i].type), 1); - } + zend_type_release(arg_info[i].type, /* persistent */ 1); } free(arg_info); } @@ -303,9 +317,7 @@ ZEND_API void destroy_zend_class(zval *zv) if (prop_info->doc_comment) { zend_string_release_ex(prop_info->doc_comment, 0); } - if (ZEND_TYPE_IS_NAME(prop_info->type)) { - zend_string_release(ZEND_TYPE_NAME(prop_info->type)); - } + zend_type_release(prop_info->type, /* persistent */ 0); } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(&ce->properties_info); @@ -496,9 +508,7 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) if (arg_info[i].name) { zend_string_release_ex(arg_info[i].name, 0); } - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string_release_ex(ZEND_TYPE_NAME(arg_info[i].type), 0); - } + zend_type_release(arg_info[i].type, /* persistent */ 0); } efree(arg_info); } diff --git a/Zend/zend_string.h b/Zend/zend_string.h index a38c1cae8c552..f2076beee10c8 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -512,6 +512,8 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_CALLABLE, "callable") \ _(ZEND_STR_ITERABLE, "iterable") \ _(ZEND_STR_VOID, "void") \ + _(ZEND_STR_FALSE, "false") \ + _(ZEND_STR_NULL_LOWERCASE, "null") \ typedef enum _zend_known_string_id { diff --git a/Zend/zend_type_info.h b/Zend/zend_type_info.h index 9479d5aad2c03..ef2c6a19bc8e5 100644 --- a/Zend/zend_type_info.h +++ b/Zend/zend_type_info.h @@ -25,6 +25,7 @@ #define MAY_BE_NULL (1 << IS_NULL) #define MAY_BE_FALSE (1 << IS_FALSE) #define MAY_BE_TRUE (1 << IS_TRUE) +#define MAY_BE_BOOL (MAY_BE_FALSE|MAY_BE_TRUE) #define MAY_BE_LONG (1 << IS_LONG) #define MAY_BE_DOUBLE (1 << IS_DOUBLE) #define MAY_BE_STRING (1 << IS_STRING) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index b381eeff14674..4bfe335e0a667 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -105,88 +105,178 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * zend_type - is an abstraction layer to represent information about type hint. * It shouldn't be used directly. Only through ZEND_TYPE_* macros. * - * ZEND_TYPE_IS_SET() - checks if type-hint exists - * ZEND_TYPE_IS_MASK() - checks if type-hint refer to standard type - * ZEND_TYPE_IS_CLASS() - checks if type-hint refer to some class - * ZEND_TYPE_IS_CE() - checks if type-hint refer to some class by zend_class_entry * - * ZEND_TYPE_IS_NAME() - checks if type-hint refer to some class by zend_string * + * ZEND_TYPE_IS_SET() - checks if there is a type-hint + * ZEND_TYPE_HAS_ONLY_MASK() - checks if type-hint refer to standard type only + * ZEND_TYPE_HAS_CLASS() - checks if type-hint contains some class + * ZEND_TYPE_HAS_CE() - checks if type-hint contains some class as zend_class_entry * + * ZEND_TYPE_HAS_NAME() - checks if type-hint contains some class as zend_string * * * ZEND_TYPE_NAME() - returns referenced class name * ZEND_TYPE_CE() - returns referenced class entry - * ZEND_TYPE_MASK() - returns MAY_BE_* type mask + * ZEND_TYPE_PURE_MASK() - returns MAY_BE_* type mask + * ZEND_TYPE_FULL_MASK() - returns MAY_BE_* type mask together with other flags * * ZEND_TYPE_ALLOW_NULL() - checks if NULL is allowed * - * ZEND_TYPE_ENCODE_*() should be used for construction. + * ZEND_TYPE_INIT_*() should be used for construction. */ -typedef uintptr_t zend_type; +typedef struct { + /* Not using a union here, because there's no good way to initialize them + * in a way that is supported in both C and C++ (designated initializers + * are only supported since C++20). */ + void *ptr; + uint32_t type_mask; + /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ +} zend_type; -#define _ZEND_TYPE_CODE_MAX ((Z_L(1)<<(IS_VOID+1))-1) -#define _ZEND_TYPE_FLAG_MASK Z_L(0x3) -#define _ZEND_TYPE_CE_BIT Z_L(0x1) +typedef struct { + size_t num_types; + void *types[1]; +} zend_type_list; + +#define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 24 +#define _ZEND_TYPE_MASK ((1u << 24) - 1) +#define _ZEND_TYPE_MAY_BE_MASK ((1u << (IS_VOID+1)) - 1) +/* Only one of these bits may be set. */ +#define _ZEND_TYPE_NAME_BIT (1u << 23) +#define _ZEND_TYPE_CE_BIT (1u << 22) +#define _ZEND_TYPE_LIST_BIT (1u << 21) +#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_CE_BIT|_ZEND_TYPE_NAME_BIT) +/* Whether the type list is arena allocated */ +#define _ZEND_TYPE_ARENA_BIT (1u << 20) /* Must have same value as MAY_BE_NULL */ -#define _ZEND_TYPE_NULLABLE_BIT Z_L(0x2) +#define _ZEND_TYPE_NULLABLE_BIT 0x2 #define ZEND_TYPE_IS_SET(t) \ - ((t) != 0) + (((t).type_mask & _ZEND_TYPE_MASK) != 0) + +#define ZEND_TYPE_HAS_CLASS(t) \ + ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) + +#define ZEND_TYPE_HAS_CE(t) \ + ((((t).type_mask) & _ZEND_TYPE_CE_BIT) != 0) -#define ZEND_TYPE_IS_MASK(t) \ - ((t) != 0 && (t) <= _ZEND_TYPE_CODE_MAX) +#define ZEND_TYPE_HAS_NAME(t) \ + ((((t).type_mask) & _ZEND_TYPE_NAME_BIT) != 0) -#define ZEND_TYPE_IS_CLASS(t) \ - ((t) > _ZEND_TYPE_CODE_MAX) +#define ZEND_TYPE_HAS_LIST(t) \ + ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) -#define ZEND_TYPE_IS_CE(t) \ - (((t) & _ZEND_TYPE_CE_BIT) != 0) +#define ZEND_TYPE_USES_ARENA(t) \ + ((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0) -#define ZEND_TYPE_IS_NAME(t) \ - (ZEND_TYPE_IS_CLASS(t) && !ZEND_TYPE_IS_CE(t)) +#define ZEND_TYPE_IS_ONLY_MASK(t) \ + (ZEND_TYPE_IS_SET(t) && (t).ptr == NULL) #define ZEND_TYPE_NAME(t) \ - ((zend_string*)((t) & ~_ZEND_TYPE_FLAG_MASK)) + ((zend_string *) (t).ptr) + +#define ZEND_TYPE_LITERAL_NAME(t) \ + ((const char *) (t).ptr) #define ZEND_TYPE_CE(t) \ - ((zend_class_entry*)((t) & ~_ZEND_TYPE_FLAG_MASK)) + ((zend_class_entry *) (t).ptr) + +#define ZEND_TYPE_LIST(t) \ + ((zend_type_list *) (t).ptr) + +/* Type lists use the low bit to distinguish NAME and CE entries, + * both of which may exist in the same list. */ +#define ZEND_TYPE_LIST_IS_CE(entry) \ + (((uintptr_t) (entry)) & 1) + +#define ZEND_TYPE_LIST_IS_NAME(entry) \ + !ZEND_TYPE_LIST_IS_CE(entry) + +#define ZEND_TYPE_LIST_GET_NAME(entry) \ + ((zend_string *) (entry)) + +#define ZEND_TYPE_LIST_GET_CE(entry) \ + ((zend_class_entry *) ((uintptr_t) (entry) & ~1)) + +#define ZEND_TYPE_LIST_ENCODE_NAME(name) \ + ((void *) (name)) + +#define ZEND_TYPE_LIST_ENCODE_CE(ce) \ + ((void *) (((uintptr_t) ce) | 1)) + +#define ZEND_TYPE_LIST_SIZE(num_types) \ + (sizeof(zend_type_list) + ((num_types) - 1) * sizeof(void *)) + +#define ZEND_TYPE_LIST_FOREACH_PTR(list, entry_ptr) do { \ + void **_list = (list)->types; \ + void **_end = _list + (list)->num_types; \ + for (; _list < _end; _list++) { \ + entry_ptr = _list; + +#define ZEND_TYPE_LIST_FOREACH(list, entry) do { \ + void **_list = (list)->types; \ + void **_end = _list + (list)->num_types; \ + for (; _list < _end; _list++) { \ + entry = *_list; + +#define ZEND_TYPE_LIST_FOREACH_END() \ + } \ +} while (0) + +#define ZEND_TYPE_SET_PTR(t, _ptr) \ + ((t).ptr = (_ptr)) + +#define ZEND_TYPE_SET_PTR_AND_KIND(t, _ptr, kind_bit) do { \ + (t).ptr = (_ptr); \ + (t).type_mask &= ~_ZEND_TYPE_KIND_MASK; \ + (t).type_mask |= (kind_bit); \ +} while (0) + +#define ZEND_TYPE_SET_CE(t, ce) \ + ZEND_TYPE_SET_PTR_AND_KIND(t, ce, _ZEND_TYPE_CE_BIT) + +#define ZEND_TYPE_SET_LIST(t, list) \ + ZEND_TYPE_SET_PTR_AND_KIND(t, list, _ZEND_TYPE_LIST_BIT) + +/* FULL_MASK() includes the MAY_BE_* type mask, the CE/NAME bits, as well as extra reserved bits. + * The PURE_MASK() only includes the MAY_BE_* type mask. */ +#define ZEND_TYPE_FULL_MASK(t) \ + ((t).type_mask) + +#define ZEND_TYPE_PURE_MASK(t) \ + ((t).type_mask & _ZEND_TYPE_MAY_BE_MASK) + +#define ZEND_TYPE_FULL_MASK_WITHOUT_NULL(t) \ + ((t).type_mask & ~_ZEND_TYPE_NULLABLE_BIT) -#define ZEND_TYPE_MASK(t) \ - (t) +#define ZEND_TYPE_PURE_MASK_WITHOUT_NULL(t) \ + ((t).type_mask & _ZEND_TYPE_MAY_BE_MASK & ~_ZEND_TYPE_NULLABLE_BIT) #define ZEND_TYPE_CONTAINS_CODE(t, code) \ - (((t) & (1 << (code))) != 0) + (((t).type_mask & (1u << (code))) != 0) #define ZEND_TYPE_ALLOW_NULL(t) \ - (((t) & _ZEND_TYPE_NULLABLE_BIT) != 0) + (((t).type_mask & _ZEND_TYPE_NULLABLE_BIT) != 0) -#define ZEND_TYPE_WITHOUT_NULL(t) \ - ((t) & ~_ZEND_TYPE_NULLABLE_BIT) +#define ZEND_TYPE_INIT_NONE(extra_flags) \ + { NULL, (extra_flags) } -#define ZEND_TYPE_ENCODE_NONE() \ - (0) +#define ZEND_TYPE_INIT_MASK(_type_mask) \ + { NULL, (_type_mask) } -#define ZEND_TYPE_ENCODE_MASK(maybe_code) \ - (maybe_code) +#define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ + ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : (1 << (code))) \ + | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)) -#define ZEND_TYPE_ENCODE_CODE(code, allow_null) \ - (((code) == _IS_BOOL ? (MAY_BE_FALSE|MAY_BE_TRUE) : (1 << (code))) \ - | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : Z_L(0x0))) +#define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \ + { (void *) (ptr), \ + (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } -#define ZEND_TYPE_ENCODE_CE(ce, allow_null) \ - (((uintptr_t)(ce)) | _ZEND_TYPE_CE_BIT | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : Z_L(0x0))) +#define ZEND_TYPE_INIT_CE(_ce, allow_null, extra_flags) \ + ZEND_TYPE_INIT_PTR(_ce, _ZEND_TYPE_CE_BIT, allow_null, extra_flags) -#define ZEND_TYPE_ENCODE_CLASS(class_name, allow_null) \ - (((uintptr_t)(class_name)) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : Z_L(0x0))) +#define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ + ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) -#define ZEND_TYPE_ENCODE_CLASS_CONST_0(class_name) \ - ((zend_type) class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST_1(class_name) \ - ((zend_type) "?" class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST_Q2(macro, class_name) \ - macro(class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST_Q1(allow_null, class_name) \ - ZEND_TYPE_ENCODE_CLASS_CONST_Q2(ZEND_TYPE_ENCODE_CLASS_CONST_ ##allow_null, class_name) -#define ZEND_TYPE_ENCODE_CLASS_CONST(class_name, allow_null) \ - ZEND_TYPE_ENCODE_CLASS_CONST_Q1(allow_null, class_name) +#define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \ + ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) typedef union _zend_value { zend_long lval; /* long value */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3ba0c41d630d2..01b3bcf9f2d72 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4108,8 +4108,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f754348bac9dd..a5b1e2e75244e 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -8738,8 +8738,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -18668,8 +18667,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -26095,8 +26093,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -32717,8 +32714,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) @@ -44160,8 +44156,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU ZVAL_DEREF(retval_ptr); } - if (UNEXPECTED(!ZEND_TYPE_IS_CLASS(ret_info->type) - && !(ZEND_TYPE_MASK(ret_info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) + if (UNEXPECTED((ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_ANY) && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(retval_ptr)) && !(EX(func)->op_array.fn_flags & ZEND_ACC_RETURN_REFERENCE) && retval_ref != retval_ptr) diff --git a/ext/com_dotnet/com_com.c b/ext/com_dotnet/com_com.c index 2d3f6c8e5cb36..526ccf79773c9 100644 --- a/ext/com_dotnet/com_com.c +++ b/ext/com_dotnet/com_com.c @@ -496,7 +496,7 @@ int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function * if (f->arg_info) { for (i = 0; i < nargs; i++) { - if (f->arg_info[nargs - i - 1].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) { byref_count++; } } @@ -505,7 +505,7 @@ int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function * if (byref_count) { byref_vals = (VARIANT*)safe_emalloc(sizeof(VARIANT), byref_count, 0); for (j = 0, i = 0; i < nargs; i++) { - if (f->arg_info[nargs - i - 1].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) { /* put the value into byref_vals instead */ php_com_variant_from_zval(&byref_vals[j], &args[nargs - i - 1], obj->code_page); @@ -552,7 +552,7 @@ int php_com_do_invoke_byref(php_com_dotnet_object *obj, zend_internal_function * if (f && f->arg_info) { for (i = 0, j = 0; i < nargs; i++) { /* if this was byref, update the zval */ - if (f->arg_info[nargs - i - 1].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&f->arg_info[nargs - i - 1])) { zval *arg = &args[nargs - i - 1]; ZVAL_DEREF(arg); diff --git a/ext/com_dotnet/com_handlers.c b/ext/com_dotnet/com_handlers.c index 1a5d9c30461b8..ed08494d85721 100644 --- a/ext/com_dotnet/com_handlers.c +++ b/ext/com_dotnet/com_handlers.c @@ -327,10 +327,8 @@ static zend_function *com_method_get(zend_object **object_ptr, zend_string *name f.arg_info = ecalloc(bindptr.lpfuncdesc->cParams, sizeof(zend_arg_info)); for (i = 0; i < bindptr.lpfuncdesc->cParams; i++) { - f.arg_info[i].type = ZEND_TYPE_ENCODE_NONE(); - if (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) { - f.arg_info[i].pass_by_reference = ZEND_SEND_BY_REF; - } + zend_bool by_ref = (bindptr.lpfuncdesc->lprgelemdescParam[i].paramdesc.wParamFlags & PARAMFLAG_FOUT) != 0; + f.arg_info[i].type = (zend_type) ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(by_ref, 0)); } f.num_args = bindptr.lpfuncdesc->cParams; diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c index 0a99ac4140f8e..4aabe04c6e120 100644 --- a/ext/opcache/Optimizer/compact_literals.c +++ b/ext/opcache/Optimizer/compact_literals.c @@ -56,25 +56,31 @@ typedef struct _literal_info { info[n].flags = ((kind) | (related)); \ } while (0) -static zend_bool class_name_type_hint(const zend_op_array *op_array, uint32_t arg_num) +static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num) { zend_arg_info *arg_info; - if (arg_num > 0) { - if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) { - if (EXPECTED(arg_num <= op_array->num_args)) { - arg_info = &op_array->arg_info[arg_num-1]; - } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) { - arg_info = &op_array->arg_info[op_array->num_args]; - } else { - return 0; - } - return ZEND_TYPE_IS_CLASS(arg_info->type); + if (!(op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS)) { + return 0; + } + if (EXPECTED(arg_num <= op_array->num_args)) { + arg_info = &op_array->arg_info[arg_num-1]; + } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) { + arg_info = &op_array->arg_info[op_array->num_args]; + } else { + return 0; } } else { arg_info = op_array->arg_info - 1; - return ZEND_TYPE_IS_CLASS(arg_info->type); } + + if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (ZEND_TYPE_HAS_LIST(arg_info->type)) { + return ZEND_TYPE_LIST(arg_info->type)->num_types; + } + return 1; + } + return 0; } @@ -505,17 +511,23 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx case ZEND_RECV_INIT: case ZEND_RECV: case ZEND_RECV_VARIADIC: - if (class_name_type_hint(op_array, opline->op1.num)) { + { + size_t num_classes = type_num_classes(op_array, opline->op1.num); + if (num_classes) { opline->extended_value = cache_size; - cache_size += sizeof(void *); + cache_size += num_classes * sizeof(void *); } break; + } case ZEND_VERIFY_RETURN_TYPE: - if (class_name_type_hint(op_array, 0)) { + { + size_t num_classes = type_num_classes(op_array, 0); + if (num_classes) { opline->op2.num = cache_size; - cache_size += sizeof(void *); + cache_size += num_classes * sizeof(void *); } break; + } case ZEND_ASSIGN_STATIC_PROP_OP: if (opline->op1_type == IS_CONST) { // op1 static property diff --git a/ext/opcache/Optimizer/dfa_pass.c b/ext/opcache/Optimizer/dfa_pass.c index f4c5dee916209..8802577154269 100644 --- a/ext/opcache/Optimizer/dfa_pass.c +++ b/ext/opcache/Optimizer/dfa_pass.c @@ -307,12 +307,11 @@ static inline zend_bool can_elide_return_type_check( } /* These types are not represented exactly */ - if (ZEND_TYPE_IS_MASK(info->type) - && (ZEND_TYPE_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE))) { + if (ZEND_TYPE_FULL_MASK(info->type) & (MAY_BE_CALLABLE|MAY_BE_ITERABLE)) { return 0; } - if (ZEND_TYPE_IS_CLASS(info->type)) { + if (ZEND_TYPE_HAS_CLASS(info->type)) { if (!use_info->ce || !def_info->ce || !safe_instanceof(use_info->ce, def_info->ce)) { return 0; } diff --git a/ext/opcache/Optimizer/optimize_func_calls.c b/ext/opcache/Optimizer/optimize_func_calls.c index ea2b904a0f4e6..2894ca89f4d54 100644 --- a/ext/opcache/Optimizer/optimize_func_calls.c +++ b/ext/opcache/Optimizer/optimize_func_calls.c @@ -116,7 +116,7 @@ static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_o for (i = 0; i < num_args; i++) { /* Don't inline functions with by-reference arguments. This would require * correct handling of INDIRECT arguments. */ - if (func->op_array.arg_info[i].pass_by_reference) { + if (ZEND_ARG_SEND_MODE(&func->op_array.arg_info[i])) { return; } } diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c index 51872dcc611b5..0ff64b0a92f09 100644 --- a/ext/opcache/Optimizer/zend_inference.c +++ b/ext/opcache/Optimizer/zend_inference.c @@ -1420,21 +1420,19 @@ int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int } else if (op_array->arg_info && opline->op1.num <= op_array->num_args) { zend_type type = op_array->arg_info[opline->op1.num-1].type; - if (ZEND_TYPE_IS_MASK(type)) { - uint32_t mask = ZEND_TYPE_MASK(ZEND_TYPE_WITHOUT_NULL(type)); - if (mask == MAY_BE_LONG) { - tmp->underflow = 0; - tmp->min = ZEND_LONG_MIN; - tmp->max = ZEND_LONG_MAX; - tmp->overflow = 0; - return 1; - } else if (mask == (MAY_BE_FALSE|MAY_BE_TRUE)) { - tmp->underflow = 0; - tmp->min = 0; - tmp->max = 1; - tmp->overflow = 0; - return 1; - } + uint32_t mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); + if (mask == MAY_BE_LONG) { + tmp->underflow = 0; + tmp->min = ZEND_LONG_MIN; + tmp->max = ZEND_LONG_MAX; + tmp->overflow = 0; + return 1; + } else if (mask == (MAY_BE_FALSE|MAY_BE_TRUE)) { + tmp->underflow = 0; + tmp->min = 0; + tmp->max = 1; + tmp->overflow = 0; + return 1; } } } @@ -2231,42 +2229,39 @@ static inline zend_class_entry *get_class_entry(const zend_script *script, zend_ } static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) { + uint32_t result_mask = type_mask & MAY_BE_ANY; if (type_mask & MAY_BE_VOID) { - type_mask &= ~MAY_BE_VOID; - type_mask |= MAY_BE_NULL; + result_mask |= MAY_BE_NULL; } if (type_mask & MAY_BE_CALLABLE) { - type_mask &= ~MAY_BE_CALLABLE; - type_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } if (type_mask & MAY_BE_ITERABLE) { - type_mask &= ~MAY_BE_ITERABLE; - type_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } if (type_mask & MAY_BE_ARRAY) { - type_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + result_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; } - return type_mask; + return result_mask; } uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce) { - uint32_t tmp = 0; + uint32_t tmp; + if (!ZEND_TYPE_IS_SET(arg_info->type)) { + return MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN; + } + tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type)); *pce = NULL; - if (ZEND_TYPE_IS_CLASS(arg_info->type)) { - // class type hinting... - zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type)); + if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { tmp |= MAY_BE_OBJECT; - *pce = get_class_entry(script, lcname); - zend_string_release_ex(lcname, 0); - } else if (ZEND_TYPE_IS_MASK(arg_info->type)) { - tmp |= zend_convert_type_declaration_mask(ZEND_TYPE_MASK(arg_info->type)); - } else { - tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; - } - if (ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - tmp |= MAY_BE_NULL; + /* As we only have space to store one CE, we use a plain object type for class unions. */ + if (ZEND_TYPE_HAS_NAME(arg_info->type)) { + zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type)); + *pce = get_class_entry(script, lcname); + zend_string_release_ex(lcname, 0); + } } if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { tmp |= MAY_BE_RC1 | MAY_BE_RCN; @@ -2362,33 +2357,29 @@ static zend_property_info *zend_fetch_static_prop_info(const zend_script *script static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce) { + if (pce) { + *pce = NULL; + } if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { - uint32_t type = ZEND_TYPE_IS_CLASS(prop_info->type) - ? MAY_BE_OBJECT - : zend_convert_type_declaration_mask(ZEND_TYPE_MASK(prop_info->type)); + uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type)); - if (ZEND_TYPE_ALLOW_NULL(prop_info->type)) { - type |= MAY_BE_NULL; - } if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { type |= MAY_BE_RC1 | MAY_BE_RCN; } - if (pce) { - if (ZEND_TYPE_IS_CE(prop_info->type)) { - *pce = ZEND_TYPE_CE(prop_info->type); - } else if (ZEND_TYPE_IS_NAME(prop_info->type)) { - zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type)); - *pce = get_class_entry(script, lcname); - zend_string_release(lcname); - } else { - *pce = NULL; + if (ZEND_TYPE_HAS_CLASS(prop_info->type)) { + type |= MAY_BE_OBJECT; + if (pce) { + if (ZEND_TYPE_HAS_CE(prop_info->type)) { + *pce = ZEND_TYPE_CE(prop_info->type); + } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) { + zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type)); + *pce = get_class_entry(script, lcname); + zend_string_release(lcname); + } } } return type; } - if (pce) { - *pce = NULL; - } return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN; } @@ -3097,7 +3088,7 @@ static int zend_update_type_info(const zend_op_array *op_array, ce = NULL; if (arg_info) { tmp = zend_fetch_arg_info_type(script, arg_info, &ce); - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { tmp |= MAY_BE_REF; } } else { diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c index aa638032cae2a..08879734a871c 100644 --- a/ext/opcache/Optimizer/zend_optimizer.c +++ b/ext/opcache/Optimizer/zend_optimizer.c @@ -652,9 +652,7 @@ int zend_optimizer_replace_by_const(zend_op_array *op_array, } case ZEND_VERIFY_RETURN_TYPE: { zend_arg_info *ret_info = op_array->arg_info - 1; - if (ZEND_TYPE_IS_CLASS(ret_info->type) - || (ZEND_TYPE_IS_MASK(ret_info->type) - && !ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(val))) + if (!ZEND_TYPE_CONTAINS_CODE(ret_info->type, Z_TYPE_P(val)) || (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE)) { return 0; } diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 12dd8aba7aee6..08fca1e3fa90f 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -601,9 +601,15 @@ static void accel_copy_permanent_strings(zend_new_interned_string_func_t new_int num_args++; } for (i = 0 ; i < num_args; i++) { - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(arg_info[i].type); - arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(new_interned_string(ZEND_TYPE_NAME(arg_info[i].type)), allow_null); + if (ZEND_TYPE_HAS_LIST(arg_info[i].type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(arg_info[i].type), entry) { + ZEND_ASSERT(ZEND_TYPE_LIST_IS_NAME(*entry)); + *entry = zend_new_interned_string(ZEND_TYPE_LIST_GET_NAME(*entry)); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { + ZEND_TYPE_SET_PTR(arg_info[i].type, + new_interned_string(ZEND_TYPE_NAME(arg_info[i].type))); } } } @@ -3539,6 +3545,32 @@ static zend_bool preload_try_resolve_constants(zend_class_entry *ce) return ok; } +static zend_class_entry *preload_fetch_resolved_ce(zend_string *name, zend_class_entry *self_ce) { + zend_string *lcname = zend_string_tolower(name); + zend_class_entry *ce = zend_hash_find_ptr(EG(class_table), lcname); + zend_string_release(lcname); + if (!ce) { + return NULL; + } + if (ce == self_ce) { + /* Ignore the following requirements if this is the class referring to itself */ + return ce; + } +#ifdef ZEND_WIN32 + /* On Windows we can't link with internal class, because of ASLR */ + if (ce->type == ZEND_INTERNAL_CLASS) { + return NULL; + } +#endif + if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + return NULL; + } + if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + return NULL; + } + return ce; +} + static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) { zend_bool ok = 1; @@ -3547,66 +3579,61 @@ static zend_bool preload_try_resolve_property_types(zend_class_entry *ce) if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - zend_string *name, *lcname; - - if (!ZEND_TYPE_IS_NAME(prop->type)) { - continue; - } - - name = ZEND_TYPE_NAME(prop->type); - lcname = zend_string_tolower(name); - p = zend_hash_find_ptr(EG(class_table), lcname); - zend_string_release(lcname); - if (!p) { - ok = 0; - continue; - } - if (p != ce) { -#ifdef ZEND_WIN32 - /* On Windows we can't link with internal class, because of ASLR */ - if (p->type == ZEND_INTERNAL_CLASS) { - ok = 0; - continue; - } -#endif - if (!(p->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { - ok = 0; - continue; - } - if (!(p->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + if (ZEND_TYPE_HAS_LIST(prop->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + p = preload_fetch_resolved_ce(ZEND_TYPE_LIST_GET_NAME(*entry), ce); + if (!p) { + ok = 0; + continue; + } + *entry = ZEND_TYPE_LIST_ENCODE_CE(p); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(prop->type)) { + p = preload_fetch_resolved_ce(ZEND_TYPE_NAME(prop->type), ce); + if (!p) { ok = 0; continue; } + ZEND_TYPE_SET_CE(prop->type, p); } - - zend_string_release(name); - prop->type = ZEND_TYPE_ENCODE_CE(p, ZEND_TYPE_ALLOW_NULL(prop->type)); } ZEND_HASH_FOREACH_END(); } return ok; } -static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) { - zend_string *name, *lcname; - zend_bool known; - if (!ZEND_TYPE_IS_NAME(type)) { - return 1; - } - - name = ZEND_TYPE_NAME(type); +static zend_bool preload_is_class_type_known(zend_class_entry *ce, zend_string *name) { if (zend_string_equals_literal_ci(name, "self") || zend_string_equals_literal_ci(name, "parent") || zend_string_equals_ci(name, ce->name)) { return 1; } - lcname = zend_string_tolower(name); - known = zend_hash_exists(EG(class_table), lcname); + zend_string *lcname = zend_string_tolower(name); + zend_bool known = zend_hash_exists(EG(class_table), lcname); zend_string_release(lcname); return known; } +static zend_bool preload_is_type_known(zend_class_entry *ce, zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry) + && !preload_is_class_type_known(ce, ZEND_TYPE_LIST_GET_NAME(entry))) { + return 0; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } + if (ZEND_TYPE_HAS_NAME(type)) { + return preload_is_class_type_known(ce, ZEND_TYPE_NAME(type)); + } + return 1; +} + static zend_bool preload_is_method_maybe_override(zend_class_entry *ce, zend_string *lcname) { zend_class_entry *p; if (ce->trait_aliases || ce->trait_precedences) { diff --git a/ext/opcache/jit/zend_jit_disasm_x86.c b/ext/opcache/jit/zend_jit_disasm_x86.c index 70708729b49ed..df6c4ab1bab01 100644 --- a/ext/opcache/jit/zend_jit_disasm_x86.c +++ b/ext/opcache/jit/zend_jit_disasm_x86.c @@ -424,7 +424,6 @@ static int zend_jit_disasm_init(void) REGISTER_HELPER(zend_jit_zval_copy_deref_helper) REGISTER_HELPER(zend_jit_new_ref_helper); REGISTER_HELPER(zend_jit_fetch_global_helper); - REGISTER_HELPER(zend_jit_verify_arg_object); REGISTER_HELPER(zend_jit_verify_arg_slow); REGISTER_HELPER(zend_jit_fetch_obj_r_slow); REGISTER_HELPER(zend_jit_fetch_obj_r_dynamic); diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index f581841aab5ce..0efd07c3dce6c 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1136,64 +1136,59 @@ static zval* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_execute_data *execu return value; } -static void ZEND_FASTCALL zend_jit_verify_arg_object(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot) -{ - zend_class_entry *ce; - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *)*cache_slot; - } else { - ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (UNEXPECTED(!ce)) { - zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg); - return; - } - *cache_slot = (void *)ce; - } - if (UNEXPECTED(!instanceof_function(Z_OBJCE_P(arg), ce))) { - zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg); - } -} - static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, const zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot) { uint32_t type_mask; - if (UNEXPECTED(ZEND_TYPE_IS_CLASS(arg_info->type))) { + if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; - if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(arg_info->type)) { - /* Null passed to nullable type */ - return; - } - - /* This is always an error - we fetch the class name for the error message here */ - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *) *cache_slot; + if (ZEND_TYPE_HAS_LIST(arg_info->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), entry) { + if (*cache_slot) { + ce = *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_LIST_GET_NAME(entry), + (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + if (!ce) { + cache_slot++; + continue; + } + *cache_slot = ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); } else { - ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); - if (ce) { - *cache_slot = (void *)ce; + if (EXPECTED(*cache_slot)) { + ce = (zend_class_entry *) *cache_slot; + } else { + ce = zend_fetch_class(ZEND_TYPE_NAME(arg_info->type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD)); + if (UNEXPECTED(!ce)) { + goto builtin_types; + } + *cache_slot = (void *) ce; + } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return; } } - goto err; } - type_mask = ZEND_TYPE_MASK(arg_info->type); - if (type_mask & MAY_BE_CALLABLE) { - if (zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL) == 0) { - goto err; - } - } else if (type_mask & MAY_BE_ITERABLE) { - if (zend_is_iterable(arg) == 0) { - goto err; - } - } else { - if (Z_ISUNDEF_P(arg) || - zend_verify_scalar_type_hint(ZEND_TYPE_MASK(arg_info->type), arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0) == 0) { - goto err; - } +builtin_types: + type_mask = ZEND_TYPE_FULL_MASK(arg_info->type); + if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, IS_CALLABLE_CHECK_SILENT, NULL)) { + return; } - return; -err: + if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { + return; + } + if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) { + return; + } + zend_verify_arg_error((zend_function*)op_array, arg_info, arg_num, cache_slot, arg); } @@ -1345,7 +1340,7 @@ static zend_property_info *zend_jit_get_prop_not_accepting_double(zend_reference { zend_property_info *prop; ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) { - if (!ZEND_TYPE_IS_MASK(prop->type) || !(ZEND_TYPE_MASK(prop->type) & MAY_BE_DOUBLE)) { + if (!(ZEND_TYPE_FULL_MASK(prop->type) & MAY_BE_DOUBLE)) { return prop; } } ZEND_REF_FOREACH_TYPE_SOURCES_END(); diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index 02b6f606aebca..6f1913a1a1e0d 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -7102,8 +7102,8 @@ static uint32_t skip_valid_arguments(const zend_op_array *op_array, zend_ssa *ss zend_arg_info *arg_info = func->op_array.arg_info + num_args; if (ZEND_TYPE_IS_SET(arg_info->type)) { - if (ZEND_TYPE_IS_MASK(arg_info->type)) { - uint32_t type_mask = ZEND_TYPE_MASK(arg_info->type); + if (ZEND_TYPE_IS_ONLY_MASK(arg_info->type)) { + uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); uint32_t info = _ssa_op1_info(op_array, ssa, call_info->arg_info[num_args].opline); if ((info & (MAY_BE_ANY|MAY_BE_UNDEF)) & ~type_mask) { break; @@ -9032,58 +9032,27 @@ static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, const zend_op_ zend_jit_addr res_addr = zend_jit_decode_op(op_array, opline->result_type, opline->result, opline, NULL, -1); | LOAD_ZVAL_ADDR r0, res_addr - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { | GET_Z_PTR r0, r0 | add r0, offsetof(zend_reference, val) } - if (!ZEND_TYPE_IS_CLASS(type)) { - uint32_t type_mask = ZEND_TYPE_MASK(type); - if (is_power_of_two(type_mask)) { - uint32_t type_code = concrete_type(type_mask); - | cmp byte [r0 + 8], type_code - | jne >8 - } else { - | mov edx, 1 - | mov cl, byte [r0 + 8] - | shl edx, cl - | test edx, type_mask - | je >8 - } + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); + if (is_power_of_two(type_mask)) { + uint32_t type_code = concrete_type(type_mask); + | cmp byte [r0 + 8], type_code + | jne >8 } else { - | SAVE_VALID_OPLINE opline - | cmp byte [r0 + 8], IS_OBJECT - | jne >9 - | mov FCARG1a, r0 - | mov r0, EX->run_time_cache - | add r0, opline->extended_value - | LOAD_ADDR FCARG2a, (ptrdiff_t)op_array - |.if X64WIN - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov aword A5, r0 - | EXT_CALL zend_jit_verify_arg_object, r0 - |.elif X64 - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov CARG5, r0 - | EXT_CALL zend_jit_verify_arg_object, r0 - |.else - | sub r4, 4 - | push r0 - | push (ptrdiff_t)arg_info - | push arg_num - | EXT_CALL zend_jit_verify_arg_object, r0 - | add r4, 4 - |.endif - if (!zend_jit_check_exception(Dst)) { - return 0; - } + | mov edx, 1 + | mov cl, byte [r0 + 8] + | shl edx, cl + | test edx, type_mask + | je >8 } |.cold_code |8: | SAVE_VALID_OPLINE opline - |9: | mov FCARG1a, r0 | mov r0, EX->run_time_cache | add r0, opline->extended_value @@ -9185,47 +9154,18 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, const zen has_slow += 2; | LOAD_ZVAL_ADDR r0, res_addr | ZVAL_DEREF r0, MAY_BE_REF - if (!ZEND_TYPE_IS_CLASS(arg_info->type)) { - uint32_t type_mask = ZEND_TYPE_MASK(arg_info->type); - if (is_power_of_two(type_mask)) { - uint32_t type_code = concrete_type(type_mask); - | cmp byte [r0 + 8], type_code - | jne >8 - } else { - | mov edx, 1 - | mov cl, byte [r0 + 8] - | shl edx, cl - | test edx, type_mask - | je >8 - } + + uint32_t type_mask = ZEND_TYPE_PURE_MASK(arg_info->type); + if (is_power_of_two(type_mask)) { + uint32_t type_code = concrete_type(type_mask); + | cmp byte [r0 + 8], type_code + | jne >8 } else { - | cmp byte [r0 + 8], IS_OBJECT - | jne >8 - | mov FCARG1a, r0 - | mov r0, EX->run_time_cache - | lea r0, [r0 + opline->extended_value] - | LOAD_ADDR FCARG2a, (ptrdiff_t)op_array - |.if X64WIN - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov aword A5, r0 - | SAVE_VALID_OPLINE opline - | EXT_CALL zend_jit_verify_arg_object, r0 - |.elif X64 - | mov CARG3, arg_num - | LOAD_ADDR CARG4, (ptrdiff_t)arg_info - | mov CARG5, r0 - | SAVE_VALID_OPLINE opline - | EXT_CALL zend_jit_verify_arg_object, r0 - |.else - | sub r4, 4 - | push r0 - | push (ptrdiff_t)arg_info - | push arg_num - | SAVE_VALID_OPLINE opline - | EXT_CALL zend_jit_verify_arg_object, r0 - | add r4, 4 - |.endif + | mov edx, 1 + | mov cl, byte [r0 + 8] + | shl edx, cl + | test edx, type_mask + | je >8 } } while (0); } diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index dc7a76b32610f..9eb6745478f65 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -233,11 +233,28 @@ static void zend_hash_clone_prop_info(HashTable *ht) prop_info->ce = ARENA_REALLOC(prop_info->ce); } - if (ZEND_TYPE_IS_CE(prop_info->type)) { + if (ZEND_TYPE_HAS_LIST(prop_info->type)) { + zend_type_list *list = ZEND_TYPE_LIST(prop_info->type); + if (IN_ARENA(list)) { + list = ARENA_REALLOC(list); + ZEND_TYPE_SET_PTR(prop_info->type, list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop_info->type), entry) { + if (ZEND_TYPE_LIST_IS_CE(*entry)) { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + if (IN_ARENA(ce)) { + ce = ARENA_REALLOC(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } + } ZEND_TYPE_LIST_FOREACH_END(); + } + } else if (ZEND_TYPE_HAS_CE(prop_info->type)) { zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type); if (IN_ARENA(ce)) { ce = ARENA_REALLOC(ce); - prop_info->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop_info->type)); + ZEND_TYPE_SET_PTR(prop_info->type, ce); } } } diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 697bb10b0d749..049b0e9b48780 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -371,6 +371,38 @@ static void zend_file_cache_serialize_zval(zval *zv, } } +static void zend_file_cache_serialize_type( + zend_type *type, zend_persistent_script *script, zend_file_cache_metainfo *info, void *buf) +{ + if (ZEND_TYPE_HAS_LIST(*type)) { + zend_type_list *list = ZEND_TYPE_LIST(*type); + SERIALIZE_PTR(list); + ZEND_TYPE_SET_PTR(*type, list); + UNSERIALIZE_PTR(list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry); + SERIALIZE_STR(name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(name); + } else { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + SERIALIZE_PTR(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + SERIALIZE_STR(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } else if (ZEND_TYPE_HAS_CE(*type)) { + zend_class_entry *ce = ZEND_TYPE_CE(*type); + SERIALIZE_PTR(ce); + ZEND_TYPE_SET_PTR(*type, ce); + } +} + static void zend_file_cache_serialize_op_array(zend_op_array *op_array, zend_persistent_script *script, zend_file_cache_metainfo *info, @@ -498,16 +530,7 @@ static void zend_file_cache_serialize_op_array(zend_op_array *op_arra if (!IS_SERIALIZED(p->name)) { SERIALIZE_STR(p->name); } - if (ZEND_TYPE_IS_CLASS(p->type)) { - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(p->type); - zend_string *type_name = ZEND_TYPE_NAME(p->type); - - SERIALIZE_STR(type_name); - p->type = - (Z_UL(1) << (sizeof(zend_type)*8-1)) | /* type is class */ - (allow_null ? (Z_UL(1) << (sizeof(zend_type)*8-2)) : Z_UL(0)) | /* type allow null */ - (zend_type)type_name; - } + zend_file_cache_serialize_type(&p->type, script, info, buf); p++; } } @@ -576,17 +599,7 @@ static void zend_file_cache_serialize_prop_info(zval *zv, if (prop->doc_comment) { SERIALIZE_STR(prop->doc_comment); } - } - if (prop->type) { - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *name = ZEND_TYPE_NAME(prop->type); - SERIALIZE_STR(name); - prop->type = ZEND_TYPE_ENCODE_CLASS(name, ZEND_TYPE_ALLOW_NULL(prop->type)); - } else if (ZEND_TYPE_IS_CE(prop->type)) { - zend_class_entry *ce = ZEND_TYPE_CE(prop->type); - SERIALIZE_PTR(ce); - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); - } + zend_file_cache_serialize_type(&prop->type, script, info, buf); } } } @@ -1087,6 +1100,37 @@ static void zend_file_cache_unserialize_zval(zval *zv, } } +static void zend_file_cache_unserialize_type( + zend_type *type, zend_persistent_script *script, void *buf) +{ + if (ZEND_TYPE_HAS_LIST(*type)) { + zend_type_list *list = ZEND_TYPE_LIST(*type); + UNSERIALIZE_PTR(list); + ZEND_TYPE_SET_PTR(*type, list); + + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { + if (ZEND_TYPE_LIST_IS_NAME(*entry)) { + zend_string *name = ZEND_TYPE_LIST_GET_NAME(*entry); + UNSERIALIZE_STR(name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(name); + } else { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + UNSERIALIZE_PTR(ce); + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + UNSERIALIZE_STR(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } else if (ZEND_TYPE_HAS_CE(*type)) { + zend_class_entry *ce = ZEND_TYPE_CE(*type); + UNSERIALIZE_PTR(ce); + ZEND_TYPE_SET_PTR(*type, ce); + } +} + static void zend_file_cache_unserialize_op_array(zend_op_array *op_array, zend_persistent_script *script, void *buf) @@ -1202,13 +1246,7 @@ static void zend_file_cache_unserialize_op_array(zend_op_array *op_arr if (!IS_UNSERIALIZED(p->name)) { UNSERIALIZE_STR(p->name); } - if (p->type & (Z_UL(1) << (sizeof(zend_type)*8-1))) { /* type is class */ - zend_bool allow_null = (p->type & (Z_UL(1) << (sizeof(zend_type)*8-2))) != 0; /* type allow null */ - zend_string *type_name = (zend_string*)(p->type & ~(((Z_UL(1) << (sizeof(zend_type)*8-1))) | ((Z_UL(1) << (sizeof(zend_type)*8-2))))); - - UNSERIALIZE_STR(type_name); - p->type = ZEND_TYPE_ENCODE_CLASS(type_name, allow_null); - } + zend_file_cache_unserialize_type(&p->type, script, buf); p++; } } @@ -1277,17 +1315,7 @@ static void zend_file_cache_unserialize_prop_info(zval *zv, if (prop->doc_comment) { UNSERIALIZE_STR(prop->doc_comment); } - } - if (prop->type) { - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *name = ZEND_TYPE_NAME(prop->type); - UNSERIALIZE_STR(name); - prop->type = ZEND_TYPE_ENCODE_CLASS(name, ZEND_TYPE_ALLOW_NULL(prop->type)); - } else if (ZEND_TYPE_IS_CE(prop->type)) { - zend_class_entry *ce = ZEND_TYPE_CE(prop->type); - UNSERIALIZE_PTR(ce); - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); - } + zend_file_cache_unserialize_type(&prop->type, script, buf); } } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 60c7620ee9d99..ec5b9d417a1a0 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -258,6 +258,35 @@ static void zend_persist_zval(zval *z) } } +static void zend_persist_type(zend_type *type) { + if (ZEND_TYPE_HAS_LIST(*type)) { + void **entry; + zend_type_list *list = ZEND_TYPE_LIST(*type); + if (ZEND_TYPE_USES_ARENA(*type)) { + if (!ZCG(is_immutable_class)) { + list = zend_shared_memdup_arena_put(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + } else { + /* Moved from arena to SHM because type list was fully resolved. */ + list = zend_shared_memdup_put(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + ZEND_TYPE_FULL_MASK(*type) &= ~_ZEND_TYPE_ARENA_BIT; + } + } else { + list = zend_shared_memdup_put_free(list, ZEND_TYPE_LIST_SIZE(list->num_types)); + } + ZEND_TYPE_SET_PTR(*type, list); + + ZEND_TYPE_LIST_FOREACH_PTR(list, entry) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + zend_accel_store_interned_string(type_name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + zend_accel_store_interned_string(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } +} + static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_script* main_persistent_script) { zend_op *persist_ptr; @@ -499,13 +528,7 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc if (arg_info[i].name) { zend_accel_store_interned_string(arg_info[i].name); } - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type); - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(arg_info[i].type); - - zend_accel_store_interned_string(type_name); - arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(type_name, allow_null); - } + zend_persist_type(&arg_info[i].type); } if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { arg_info++; @@ -660,12 +683,7 @@ static void zend_persist_property_info(zval *zv) prop->doc_comment = NULL; } } - - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *class_name = ZEND_TYPE_NAME(prop->type); - zend_accel_store_interned_string(class_name); - prop->type = ZEND_TYPE_ENCODE_CLASS(class_name, ZEND_TYPE_ALLOW_NULL(prop->type)); - } + zend_persist_type(&prop->type); } static void zend_persist_class_constant(zval *zv) @@ -939,12 +957,25 @@ static void zend_update_parent_ce(zend_class_entry *ce) if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { zend_property_info *prop; ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - if (ZEND_TYPE_IS_CE(prop->type)) { + if (ZEND_TYPE_HAS_LIST(prop->type)) { + void **entry; + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(prop->type), entry) { + if (ZEND_TYPE_LIST_IS_CE(*entry)) { + zend_class_entry *ce = ZEND_TYPE_LIST_GET_CE(*entry); + if (ce->type == ZEND_USER_CLASS) { + ce = zend_shared_alloc_get_xlat_entry(ce); + if (ce) { + *entry = ZEND_TYPE_LIST_ENCODE_CE(ce); + } + } + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_CE(prop->type)) { zend_class_entry *ce = ZEND_TYPE_CE(prop->type); if (ce->type == ZEND_USER_CLASS) { ce = zend_shared_alloc_get_xlat_entry(ce); if (ce) { - prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); + ZEND_TYPE_SET_PTR(prop->type, ce); } } } diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index cc798b27de050..c4aa4adf5eaca 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -148,6 +148,27 @@ static void zend_persist_zval_calc(zval *z) } } +static void zend_persist_type_calc(zend_type *type) +{ + if (ZEND_TYPE_HAS_LIST(*type)) { + void **entry; + if (ZEND_TYPE_USES_ARENA(*type) && !ZCG(is_immutable_class)) { + ADD_ARENA_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types)); + } else { + ADD_SIZE(ZEND_TYPE_LIST_SIZE(ZEND_TYPE_LIST(*type)->num_types)); + } + ZEND_TYPE_LIST_FOREACH_PTR(ZEND_TYPE_LIST(*type), entry) { + zend_string *type_name = ZEND_TYPE_LIST_GET_NAME(*entry); + ADD_INTERNED_STRING(type_name); + *entry = ZEND_TYPE_LIST_ENCODE_NAME(type_name); + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(*type)) { + zend_string *type_name = ZEND_TYPE_NAME(*type); + ADD_INTERNED_STRING(type_name); + ZEND_TYPE_SET_PTR(*type, type_name); + } +} + static void zend_persist_op_array_calc_ex(zend_op_array *op_array) { if (op_array->scope && zend_shared_alloc_get_xlat_entry(op_array->opcodes)) { @@ -222,13 +243,7 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) if (arg_info[i].name) { ADD_INTERNED_STRING(arg_info[i].name); } - if (ZEND_TYPE_IS_CLASS(arg_info[i].type)) { - zend_string *type_name = ZEND_TYPE_NAME(arg_info[i].type); - zend_bool allow_null = ZEND_TYPE_ALLOW_NULL(arg_info[i].type); - - ADD_INTERNED_STRING(type_name); - arg_info[i].type = ZEND_TYPE_ENCODE_CLASS(type_name, allow_null); - } + zend_persist_type_calc(&arg_info[i].type); } } @@ -304,11 +319,7 @@ static void zend_persist_property_info_calc(zval *zv) zend_shared_alloc_register_xlat_entry(prop, prop); ADD_SIZE_EX(sizeof(zend_property_info)); ADD_INTERNED_STRING(prop->name); - if (ZEND_TYPE_IS_NAME(prop->type)) { - zend_string *class_name = ZEND_TYPE_NAME(prop->type); - ADD_INTERNED_STRING(class_name); - prop->type = ZEND_TYPE_ENCODE_CLASS(class_name, ZEND_TYPE_ALLOW_NULL(prop->type)); - } + zend_persist_type_calc(&prop->type); if (ZCG(accel_directives).save_comments && prop->doc_comment) { ADD_STRING(prop->doc_comment); } @@ -338,7 +349,14 @@ static void check_property_type_resolution(zend_class_entry *ce) { if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { - if (ZEND_TYPE_IS_NAME(prop->type)) { + if (ZEND_TYPE_HAS_LIST(prop->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(prop->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + return; + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(prop->type)) { return; } } ZEND_HASH_FOREACH_END(); diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index e5a740d9d7704..28cca1e11b1ff 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -1292,10 +1292,10 @@ int pdo_hash_methods(pdo_dbh_object_t *dbh_obj, int kind) } else { func.required_num_args = info->required_num_args; } - if (info->return_reference) { + if (ZEND_ARG_SEND_MODE(info)) { func.fn_flags |= ZEND_ACC_RETURN_REFERENCE; } - if (funcs->arg_info[funcs->num_args].is_variadic) { + if (ZEND_ARG_IS_VARIADIC(&funcs->arg_info[funcs->num_args])) { func.fn_flags |= ZEND_ACC_VARIADIC; /* Don't count the variadic argument */ func.num_args--; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 5bfbb5f6f9a60..4b3851e9e0361 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -75,6 +75,7 @@ PHPAPI zend_class_entry *reflection_generator_ptr; PHPAPI zend_class_entry *reflection_parameter_ptr; PHPAPI zend_class_entry *reflection_type_ptr; PHPAPI zend_class_entry *reflection_named_type_ptr; +PHPAPI zend_class_entry *reflection_union_type_ptr; PHPAPI zend_class_entry *reflection_class_ptr; PHPAPI zend_class_entry *reflection_object_ptr; PHPAPI zend_class_entry *reflection_method_ptr; @@ -127,6 +128,8 @@ typedef struct _parameter_reference { /* Struct for type hints */ typedef struct _type_reference { zend_type type; + /* Whether to use backwards compatible null representation */ + zend_bool legacy_behavior; } type_reference; typedef enum { @@ -228,7 +231,14 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */ case REF_TYPE_TYPE: { type_reference *type_ref = intern->ptr; - if (ZEND_TYPE_IS_NAME(type_ref->type)) { + if (ZEND_TYPE_HAS_LIST(type_ref->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type_ref->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + zend_string_release(ZEND_TYPE_LIST_GET_NAME(entry)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type_ref->type)) { zend_string_release(ZEND_TYPE_NAME(type_ref->type)); } efree(type_ref); @@ -595,10 +605,10 @@ static void _parameter_string(smart_str *str, zend_function *fptr, struct _zend_ smart_str_append_printf(str, "%s ", ZSTR_VAL(type_str)); zend_string_release(type_str); } - if (arg_info->pass_by_reference) { + if (ZEND_ARG_SEND_MODE(arg_info)) { smart_str_appendc(str, '&'); } - if (arg_info->is_variadic) { + if (ZEND_ARG_IS_VARIADIC(arg_info)) { smart_str_appends(str, "..."); } if (arg_info->name) { @@ -1130,22 +1140,50 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje } /* }}} */ +/* For backwards compatibility reasons, we need to return T|null style unions + * as a ReflectionNamedType. Here we determine what counts as a union type and + * what doesn't. */ +static zend_bool is_union_type(zend_type type) { + if (ZEND_TYPE_HAS_LIST(type)) { + return 1; + } + uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); + if (ZEND_TYPE_HAS_CLASS(type)) { + return type_mask_without_null != 0; + } + if (type_mask_without_null == MAY_BE_BOOL) { + return 0; + } + /* Check that only one bit is set. */ + return (type_mask_without_null & (type_mask_without_null - 1)) != 0; +} + /* {{{ reflection_type_factory */ -static void reflection_type_factory(zend_type type, zval *object) +static void reflection_type_factory(zend_type type, zval *object, zend_bool legacy_behavior) { reflection_object *intern; type_reference *reference; + zend_bool is_union = is_union_type(type); - reflection_instantiate(reflection_named_type_ptr, object); + reflection_instantiate( + is_union ? reflection_union_type_ptr : reflection_named_type_ptr, object); intern = Z_REFLECTION_P(object); reference = (type_reference*) emalloc(sizeof(type_reference)); reference->type = type; + reference->legacy_behavior = legacy_behavior && !is_union; intern->ptr = reference; intern->ref_type = REF_TYPE_TYPE; /* Property types may be resolved during the lifetime of the ReflectionType, * so we need to make sure that the strings we reference are not released. */ - if (ZEND_TYPE_IS_NAME(type)) { + if (ZEND_TYPE_HAS_LIST(type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + zend_string_addref(ZEND_TYPE_LIST_GET_NAME(entry)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(type)) { zend_string_addref(ZEND_TYPE_NAME(type)); } } @@ -2482,7 +2520,8 @@ ZEND_METHOD(reflection_parameter, getClass) } GET_REFLECTION_OBJECT_PTR(param); - if (ZEND_TYPE_IS_CLASS(param->arg_info->type)) { + // TODO: This is going to return null for union types, which is rather odd. + if (ZEND_TYPE_HAS_NAME(param->arg_info->type)) { /* Class name is stored as a string, we might also get "self" or "parent" * - For "self", simply use the function scope. If scope is NULL then * the function is global and thus self does not make any sense @@ -2562,7 +2601,7 @@ ZEND_METHOD(reflection_parameter, getType) if (!ZEND_TYPE_IS_SET(param->arg_info->type)) { RETURN_NULL(); } - reflection_type_factory(param->arg_info->type, return_value); + reflection_type_factory(param->arg_info->type, return_value, 1); } /* }}} */ @@ -2572,15 +2611,15 @@ ZEND_METHOD(reflection_parameter, isArray) { reflection_object *intern; parameter_reference *param; - zend_type type; + uint32_t type_mask; if (zend_parse_parameters_none() == FAILURE) { return; } GET_REFLECTION_OBJECT_PTR(param); - type = ZEND_TYPE_WITHOUT_NULL(param->arg_info->type); - RETVAL_BOOL(ZEND_TYPE_MASK(type) == MAY_BE_ARRAY); + type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(param->arg_info->type); + RETVAL_BOOL(type_mask == MAY_BE_ARRAY); } /* }}} */ @@ -2590,15 +2629,15 @@ ZEND_METHOD(reflection_parameter, isCallable) { reflection_object *intern; parameter_reference *param; - zend_type type; + uint32_t type_mask; if (zend_parse_parameters_none() == FAILURE) { return; } GET_REFLECTION_OBJECT_PTR(param); - type = ZEND_TYPE_WITHOUT_NULL(param->arg_info->type); - RETVAL_BOOL(ZEND_TYPE_MASK(type) == MAY_BE_CALLABLE); + type_mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(param->arg_info->type); + RETVAL_BOOL(type_mask == MAY_BE_CALLABLE); } /* }}} */ @@ -2631,7 +2670,7 @@ ZEND_METHOD(reflection_parameter, isPassedByReference) } GET_REFLECTION_OBJECT_PTR(param); - RETVAL_BOOL(param->arg_info->pass_by_reference); + RETVAL_BOOL(ZEND_ARG_SEND_MODE(param->arg_info)); } /* }}} */ @@ -2648,7 +2687,7 @@ ZEND_METHOD(reflection_parameter, canBePassedByValue) GET_REFLECTION_OBJECT_PTR(param); /* true if it's ZEND_SEND_BY_VAL or ZEND_SEND_PREFER_REF */ - RETVAL_BOOL(param->arg_info->pass_by_reference != ZEND_SEND_BY_REF); + RETVAL_BOOL(ZEND_ARG_SEND_MODE(param->arg_info) != ZEND_SEND_BY_REF); } /* }}} */ @@ -2809,7 +2848,7 @@ ZEND_METHOD(reflection_parameter, isVariadic) } GET_REFLECTION_OBJECT_PTR(param); - RETVAL_BOOL(param->arg_info->is_variadic); + RETVAL_BOOL(ZEND_ARG_IS_VARIADIC(param->arg_info)); } /* }}} */ @@ -2829,6 +2868,11 @@ ZEND_METHOD(reflection_type, allowsNull) } /* }}} */ +static zend_string *zend_type_to_string_without_null(zend_type type) { + ZEND_TYPE_FULL_MASK(type) &= ~MAY_BE_NULL; + return zend_type_to_string(type); +} + /* {{{ proto public string ReflectionType::__toString() Return the text of the type hint */ ZEND_METHOD(reflection_type, __toString) @@ -2857,7 +2901,10 @@ ZEND_METHOD(reflection_named_type, getName) } GET_REFLECTION_OBJECT_PTR(param); - RETURN_STR(zend_type_to_string(ZEND_TYPE_WITHOUT_NULL(param->type))); + if (param->legacy_behavior) { + RETURN_STR(zend_type_to_string_without_null(param->type)); + } + RETURN_STR(zend_type_to_string(param->type)); } /* }}} */ @@ -2873,7 +2920,84 @@ ZEND_METHOD(reflection_named_type, isBuiltin) } GET_REFLECTION_OBJECT_PTR(param); - RETVAL_BOOL(ZEND_TYPE_IS_MASK(param->type)); + RETVAL_BOOL(ZEND_TYPE_IS_ONLY_MASK(param->type)); +} +/* }}} */ + +static void append_type(zval *return_value, zend_type type) { + zval reflection_type; + reflection_type_factory(type, &reflection_type, 0); + zend_hash_next_index_insert(Z_ARRVAL_P(return_value), &reflection_type); +} + +static void append_type_mask(zval *return_value, uint32_t type_mask) { + append_type(return_value, (zend_type) ZEND_TYPE_INIT_MASK(type_mask)); +} + +/* {{{ proto public string ReflectionUnionType::getTypes() + Returns the types that are part of this union type */ +ZEND_METHOD(reflection_union_type, getTypes) +{ + reflection_object *intern; + type_reference *param; + uint32_t type_mask; + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + GET_REFLECTION_OBJECT_PTR(param); + + array_init(return_value); + if (ZEND_TYPE_HAS_LIST(param->type)) { + void *entry; + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), entry) { + if (ZEND_TYPE_LIST_IS_NAME(entry)) { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_LIST_GET_NAME(entry), 0, 0)); + } else { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_LIST_GET_CE(entry), 0, 0)); + } + } ZEND_TYPE_LIST_FOREACH_END(); + } else if (ZEND_TYPE_HAS_NAME(param->type)) { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CLASS(ZEND_TYPE_NAME(param->type), 0, 0)); + } else if (ZEND_TYPE_HAS_CE(param->type)) { + append_type(return_value, + (zend_type) ZEND_TYPE_INIT_CE(ZEND_TYPE_CE(param->type), 0, 0)); + } + + type_mask = ZEND_TYPE_PURE_MASK(param->type); + ZEND_ASSERT(!(type_mask & MAY_BE_VOID)); + if (type_mask & MAY_BE_CALLABLE) { + append_type_mask(return_value, MAY_BE_CALLABLE); + } + if (type_mask & MAY_BE_ITERABLE) { + append_type_mask(return_value, MAY_BE_ITERABLE); + } + if (type_mask & MAY_BE_OBJECT) { + append_type_mask(return_value, MAY_BE_OBJECT); + } + if (type_mask & MAY_BE_ARRAY) { + append_type_mask(return_value, MAY_BE_ARRAY); + } + if (type_mask & MAY_BE_STRING) { + append_type_mask(return_value, MAY_BE_STRING); + } + if (type_mask & MAY_BE_LONG) { + append_type_mask(return_value, MAY_BE_LONG); + } + if (type_mask & MAY_BE_DOUBLE) { + append_type_mask(return_value, MAY_BE_DOUBLE); + } + if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { + append_type_mask(return_value, MAY_BE_BOOL); + } else if (type_mask & MAY_BE_FALSE) { + append_type_mask(return_value, MAY_BE_FALSE); + } + if (type_mask & MAY_BE_NULL) { + append_type_mask(return_value, MAY_BE_NULL); + } } /* }}} */ @@ -3342,7 +3466,7 @@ ZEND_METHOD(reflection_function, getReturnType) RETURN_NULL(); } - reflection_type_factory(fptr->common.arg_info[-1].type, return_value); + reflection_type_factory(fptr->common.arg_info[-1].type, return_value, 1); } /* }}} */ @@ -5570,7 +5694,7 @@ ZEND_METHOD(reflection_property, getType) RETURN_NULL(); } - reflection_type_factory(ref->prop->type, return_value); + reflection_type_factory(ref->prop->type, return_value, 1); } /* }}} */ @@ -6424,6 +6548,11 @@ static const zend_function_entry reflection_named_type_functions[] = { PHP_FE_END }; +static const zend_function_entry reflection_union_type_functions[] = { + ZEND_ME(reflection_union_type, getTypes, arginfo_class_ReflectionUnionType_getTypes, 0) + PHP_FE_END +}; + static const zend_function_entry reflection_extension_functions[] = { ZEND_ME(reflection, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL) ZEND_DEP_ME(reflection_extension, export, arginfo_class_ReflectionExtension_export, ZEND_ACC_STATIC|ZEND_ACC_PUBLIC) @@ -6547,6 +6676,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_init_class_handlers(&_reflection_entry); reflection_named_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionUnionType", reflection_union_type_functions); + reflection_init_class_handlers(&_reflection_entry); + reflection_union_type_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_type_ptr); + INIT_CLASS_ENTRY(_reflection_entry, "ReflectionMethod", reflection_method_functions); reflection_init_class_handlers(&_reflection_entry); reflection_method_ptr = zend_register_internal_class_ex(&_reflection_entry, reflection_function_abstract_ptr); diff --git a/ext/reflection/reflection.stub.php b/ext/reflection/reflection.stub.php index bfd5f0caf952b..b9cb156e7da56 100644 --- a/ext/reflection/reflection.stub.php +++ b/ext/reflection/reflection.stub.php @@ -552,6 +552,11 @@ public function getName() {} public function isBuiltin() {} } +class ReflectionUnionType extends ReflectionType +{ + public function getTypes(): array {} +} + class ReflectionExtension implements Reflector { final private function __clone() {} diff --git a/ext/reflection/reflection_arginfo.h b/ext/reflection/reflection_arginfo.h index 517668bd629d2..d5404d9b3157a 100644 --- a/ext/reflection/reflection_arginfo.h +++ b/ext/reflection/reflection_arginfo.h @@ -428,6 +428,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionNamedType_isBuiltin arginfo_class_Reflector___toString +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_ReflectionUnionType_getTypes, 0, 0, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_ReflectionExtension___clone arginfo_class_Reflector___toString #define arginfo_class_ReflectionExtension_export arginfo_class_ReflectionFunction_export diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 5877f88e27076..5f3b6166b5af9 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -9,7 +9,7 @@ var_dump($ext->getClasses()); ?> ==DONE== --EXPECT-- -array(17) { +array(18) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -55,43 +55,48 @@ array(17) { ["name"]=> string(19) "ReflectionNamedType" } - ["ReflectionMethod"]=> + ["ReflectionUnionType"]=> object(ReflectionClass)#11 (1) { + ["name"]=> + string(19) "ReflectionUnionType" + } + ["ReflectionMethod"]=> + object(ReflectionClass)#12 (1) { ["name"]=> string(16) "ReflectionMethod" } ["ReflectionClass"]=> - object(ReflectionClass)#12 (1) { + object(ReflectionClass)#13 (1) { ["name"]=> string(15) "ReflectionClass" } ["ReflectionObject"]=> - object(ReflectionClass)#13 (1) { + object(ReflectionClass)#14 (1) { ["name"]=> string(16) "ReflectionObject" } ["ReflectionProperty"]=> - object(ReflectionClass)#14 (1) { + object(ReflectionClass)#15 (1) { ["name"]=> string(18) "ReflectionProperty" } ["ReflectionClassConstant"]=> - object(ReflectionClass)#15 (1) { + object(ReflectionClass)#16 (1) { ["name"]=> string(23) "ReflectionClassConstant" } ["ReflectionExtension"]=> - object(ReflectionClass)#16 (1) { + object(ReflectionClass)#17 (1) { ["name"]=> string(19) "ReflectionExtension" } ["ReflectionZendExtension"]=> - object(ReflectionClass)#17 (1) { + object(ReflectionClass)#18 (1) { ["name"]=> string(23) "ReflectionZendExtension" } ["ReflectionReference"]=> - object(ReflectionClass)#18 (1) { + object(ReflectionClass)#19 (1) { ["name"]=> string(19) "ReflectionReference" } diff --git a/ext/reflection/tests/union_types.phpt b/ext/reflection/tests/union_types.phpt new file mode 100644 index 0000000000000..36773e55cadcf --- /dev/null +++ b/ext/reflection/tests/union_types.phpt @@ -0,0 +1,114 @@ +--TEST-- +Union types in reflection +--INI-- +error_reporting=E_ALL&~E_DEPRECATED +--FILE-- +allowsNull() ? "true" : "false") . "\n"; + foreach ($rt->getTypes() as $type) { + echo " Name: " . $type->getName() . "\n"; + echo " String: " . (string) $type . "\n"; + echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n"; + } +} + +function test1(): X|Y|int|float|false|null { } +function test2(): X|iterable|bool { } + +class Test { + public X|Y|int $prop; +} + +dumpType((new ReflectionFunction('test1'))->getReturnType()); +dumpType((new ReflectionFunction('test2'))->getReturnType()); + +$rc = new ReflectionClass(Test::class); +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +/* Force CE resolution of the property type */ + +class x {} +$test = new Test; +$test->prop = new x; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +class y {} +$test->prop = new y; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +?> +--EXPECT-- +Type X|Y|int|float|false|null: +Allows null: true + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: int + String: int + Allows Null: false + Name: float + String: float + Allows Null: false + Name: false + String: false + Allows Null: false + Name: null + String: null + Allows Null: true +Type X|iterable|bool: +Allows null: false + Name: X + String: X + Allows Null: false + Name: iterable + String: iterable + Allows Null: false + Name: bool + String: bool + Allows Null: false +Type X|Y|int: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: int + String: int + Allows Null: false +Type x|Y|int: +Allows null: false + Name: x + String: x + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: int + String: int + Allows Null: false +Type x|y|int: +Allows null: false + Name: x + String: x + Allows Null: false + Name: y + String: y + Allows Null: false + Name: int + String: int + Allows Null: false diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index cdff844ea9eb8..318419ba3e37b 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -237,7 +237,8 @@ PHP_MINIT_FUNCTION(zend_test) zval val; ZVAL_LONG(&val, 123); zend_declare_typed_property( - zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, ZEND_TYPE_ENCODE_CODE(IS_LONG, 0)); + zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, + (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0)); zend_string_release(name); } @@ -248,7 +249,22 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_NULL(&val); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, - ZEND_TYPE_ENCODE_CLASS(class_name, 1)); + (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 1, 0)); + zend_string_release(name); + } + + { + zend_string *name = zend_string_init("classUnionProp", sizeof("classUnionProp") - 1, 1); + zend_string *class_name1 = zend_string_init("stdClass", sizeof("stdClass") - 1, 1); + zend_string *class_name2 = zend_string_init("Iterator", sizeof("Iterator") - 1, 1); + zend_type_list *type_list = malloc(ZEND_TYPE_LIST_SIZE(2)); + type_list->num_types = 2; + type_list->types[0] = ZEND_TYPE_LIST_ENCODE_NAME(class_name1); + type_list->types[1] = ZEND_TYPE_LIST_ENCODE_NAME(class_name2); + zend_type type = ZEND_TYPE_INIT_PTR(type_list, _ZEND_TYPE_LIST_BIT, 1, 0); + zval val; + ZVAL_NULL(&val); + zend_declare_typed_property(zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, type); zend_string_release(name); } @@ -258,7 +274,7 @@ PHP_MINIT_FUNCTION(zend_test) ZVAL_LONG(&val, 123); zend_declare_typed_property( zend_test_class, name, &val, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, NULL, - ZEND_TYPE_ENCODE_CODE(IS_LONG, 0)); + (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0)); zend_string_release(name); } diff --git a/sapi/phpdbg/phpdbg_frame.c b/sapi/phpdbg/phpdbg_frame.c index d8c7d3941a8db..453a0d74ba374 100644 --- a/sapi/phpdbg/phpdbg_frame.c +++ b/sapi/phpdbg/phpdbg_frame.c @@ -229,7 +229,7 @@ static void phpdbg_dump_prototype(zval *tmp) /* {{{ */ } if (!is_variadic) { - is_variadic = arginfo ? arginfo[j].is_variadic : 0; + is_variadic = arginfo ? ZEND_ARG_IS_VARIADIC(&arginfo[j]) : 0; } phpdbg_xml(" variadic=\"%s\" name=\"%s\">", is_variadic ? "variadic" : "", arg_name ? arg_name : "");