From 941d55ca9e341375f83a575f4e2c8b375fc4d877 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sun, 14 Mar 2021 14:12:05 +0000 Subject: [PATCH 01/40] Start implementing intersection types --- Zend/Optimizer/compact_literals.c | 2 +- Zend/Optimizer/dfa_pass.c | 2 +- Zend/Optimizer/zend_inference.c | 2 +- .../added_interface_intersection_type.phpt | 53 +++++ .../assigning_intersection_types.phpt | 57 ++++++ .../invalid_types/invalid_array_type.phpt | 10 + .../invalid_types/invalid_bool_type.phpt | 10 + .../invalid_types/invalid_callable_type.phpt | 10 + .../invalid_types/invalid_false_type.phpt | 10 + .../invalid_types/invalid_float_type.phpt | 10 + .../invalid_types/invalid_int_type.phpt | 10 + .../invalid_types/invalid_iterable_type.phpt | 10 + .../invalid_types/invalid_mixed_type.phpt | 10 + .../invalid_types/invalid_never_type.phpt | 10 + .../invalid_types/invalid_null_type.phpt | 10 + .../invalid_types/invalid_nullable_type.phpt | 10 + .../invalid_types/invalid_object_type.phpt | 10 + .../invalid_types/invalid_parent_type.phpt | 14 ++ .../invalid_types/invalid_self_type.phpt | 12 ++ .../invalid_types/invalid_static_type.phpt | 12 ++ .../invalid_types/invalid_string_type.phpt | 10 + .../invalid_types/invalid_void_type.phpt | 10 + .../missing_interface_intersection_type.phpt | 50 +++++ .../intersection_types/parameter.phpt | 28 +++ .../duplicate_class_alias_type.phpt | 11 + .../duplicate_class_alias_type_runtime.phpt | 14 ++ .../redundant_types/duplicate_class_type.phpt | 11 + .../redundant_types/inheritence.phpt | 15 ++ .../intersection_types/typed_reference.phpt | 30 +++ .../intersection_types/variance/invalid1.phpt | 27 +++ .../intersection_types/variance/invalid2.phpt | 26 +++ .../invalid_covariance_drop_type1.phpt | 26 +++ .../invalid_covariance_drop_type2.phpt | 27 +++ .../variance/invalid_invariance1.phpt | 18 ++ .../variance/invalid_invariance2.phpt | 20 ++ .../intersection_types/variance/valid1.phpt | 40 ++++ .../intersection_types/variance/valid2.phpt | 22 ++ .../intersection_types/variance/valid3.phpt | 29 +++ .../intersection_types/variance/valid4.phpt | 19 ++ .../intersection_types/variance/valid5.phpt | 45 +++++ Zend/zend_API.c | 8 +- Zend/zend_ast.c | 10 + Zend/zend_ast.h | 1 + Zend/zend_compile.c | 118 ++++++++--- Zend/zend_execute.c | 190 +++++++++++++----- Zend/zend_inheritance.c | 136 ++++++++++--- Zend/zend_language_parser.y | 10 +- Zend/zend_types.h | 18 +- ext/opcache/jit/zend_jit_helpers.c | 82 +++++--- ext/reflection/php_reflection.c | 77 ++++++- ext/reflection/php_reflection.stub.php | 5 + ext/reflection/php_reflection_arginfo.h | 21 +- .../ReflectionExtension_getClasses_basic.phpt | 29 +-- ext/reflection/tests/intersection_types.phpt | 80 ++++++++ ext/tokenizer/tests/002.phpt | 9 +- 55 files changed, 1381 insertions(+), 165 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/parameter.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/inheritence.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/typed_reference.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid1.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid2.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid3.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid4.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid5.phpt create mode 100644 ext/reflection/tests/intersection_types.phpt diff --git a/Zend/Optimizer/compact_literals.c b/Zend/Optimizer/compact_literals.c index bda09ded025e4..6e935bc60c8c1 100644 --- a/Zend/Optimizer/compact_literals.c +++ b/Zend/Optimizer/compact_literals.c @@ -73,7 +73,7 @@ static size_t type_num_classes(const zend_op_array *op_array, uint32_t arg_num) arg_info = op_array->arg_info - 1; } - if (ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (ZEND_TYPE_IS_COMPLEX(arg_info->type)) { if (ZEND_TYPE_HAS_LIST(arg_info->type)) { return ZEND_TYPE_LIST(arg_info->type)->num_types; } diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index ff9335dec8a6a..fb1e3248700fb 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -310,7 +310,7 @@ static inline bool can_elide_return_type_check( return true; } - if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_HAS_CLASS(arg_info->type)) { + if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_IS_COMPLEX(arg_info->type)) { zend_type *single_type; ZEND_TYPE_FOREACH(arg_info->type, single_type) { if (ZEND_TYPE_HAS_NAME(*single_type)) { diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index b4efbeabb050d..32b6414d7985f 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2221,7 +2221,7 @@ static uint32_t zend_convert_type(const zend_script *script, zend_type type, zen } uint32_t tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(type)); - if (ZEND_TYPE_HAS_CLASS(type)) { + if (ZEND_TYPE_IS_COMPLEX(type)) { tmp |= MAY_BE_OBJECT; if (pce) { /* As we only have space to store one CE, diff --git a/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt new file mode 100644 index 0000000000000..3d7334ccd44ad --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt @@ -0,0 +1,53 @@ +--TEST-- +Added element of intersection type +--FILE-- +getMessage(), "\n"; +} + +$c = new Collection(); +$a = new A(); + +try { + $c->intersect = $a; + echo 'OK', \PHP_EOL; +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + bar($a); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +object(A)#1 (0) { +} +OK +object(A)#3 (0) { +} diff --git a/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt new file mode 100644 index 0000000000000..568e6e25afade --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/assigning_intersection_types.phpt @@ -0,0 +1,57 @@ +--TEST-- +Assigning values to intersection types +--FILE-- +prop = $tp; +} catch (TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +$o->prop = $tc; + +$r = $o->method1($tp); +var_dump($r); +$r = $o->method2($tp); +var_dump($r); +$r = $o->method1($tc); +var_dump($r); +$r = $o->method2($tc); +var_dump($r); + + +?> +--EXPECTF-- +Cannot assign TestParent to property A::$prop of type X&Y&Z +object(TestChild)#%d (0) { +} +object(TestParent)#%d (0) { +} +object(TestChild)#%d (0) { +} +object(TestParent)#%d (0) { +} diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt new file mode 100644 index 0000000000000..cd276dfb27b3e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_array_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +array type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type array cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt new file mode 100644 index 0000000000000..345793b4d5be8 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_bool_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +bool type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type bool cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt new file mode 100644 index 0000000000000..c78e5245a88bf --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_callable_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +callable type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type callable cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt new file mode 100644 index 0000000000000..52e9159681670 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_false_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +false type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type false cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt new file mode 100644 index 0000000000000..3a61181767621 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_float_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +float type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type float cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt new file mode 100644 index 0000000000000..9e4dc56a5be8a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_int_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +int type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type int cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt new file mode 100644 index 0000000000000..fc4ee2d5607d0 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_iterable_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +iterable type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type iterable cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt new file mode 100644 index 0000000000000..b980b8436e79a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_mixed_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +mixed type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type mixed cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt new file mode 100644 index 0000000000000..2bd8fd356b2d1 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_never_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +never type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type never cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt new file mode 100644 index 0000000000000..c062bc323bf19 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_null_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +null type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type null cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt new file mode 100644 index 0000000000000..1c35dfdf91c70 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +Intersection type cannot be nullable +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt new file mode 100644 index 0000000000000..0f6b4d0304e2e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_object_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +object type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type object cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt new file mode 100644 index 0000000000000..e3a86771a2189 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt @@ -0,0 +1,14 @@ +--TEST-- +parent type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type parent cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt new file mode 100644 index 0000000000000..66c7ec79325dc --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +self type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type self cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt new file mode 100644 index 0000000000000..7d2f44f572ee1 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +static type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type static cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt new file mode 100644 index 0000000000000..406a34510ae9a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_string_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +string type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type string cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt new file mode 100644 index 0000000000000..9fb9325972d63 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_void_type.phpt @@ -0,0 +1,10 @@ +--TEST-- +void type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type void cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt b/Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt new file mode 100644 index 0000000000000..e724e1b15f50a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/missing_interface_intersection_type.phpt @@ -0,0 +1,50 @@ +--TEST-- +Missing one element of intersection type +--FILE-- +getMessage(), "\n"; +} + +$c = new Collection(); +$a = new A(); + +try { + $c->intersect = $a; +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +try { + bar($a); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +foo(): Return value must be of type X&Y, A returned +Cannot assign A to property Collection::$intersect of type X&Y +bar(): Argument #1 ($o) must be of type X&Y, A given, called in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/parameter.phpt b/Zend/tests/type_declarations/intersection_types/parameter.phpt new file mode 100644 index 0000000000000..d1c7de2243654 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/parameter.phpt @@ -0,0 +1,28 @@ +--TEST-- +Intersection types in parameters +--FILE-- +getMessage(), \PHP_EOL; +} + +?> +--EXPECTF-- +object(Foo)#1 (0) { +} +foo(): Argument #1 ($bar) must be of type A&B, Bar given, called in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt new file mode 100644 index 0000000000000..ff53b9f593014 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type.phpt @@ -0,0 +1,11 @@ +--TEST-- +Duplicate class alias type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type A is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt new file mode 100644 index 0000000000000..b2866fb8b8b5d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_alias_type_runtime.phpt @@ -0,0 +1,14 @@ +--TEST-- +Duplicate class alias type at runtime +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_class_type.phpt new file mode 100644 index 0000000000000..41d59c7a8dcb9 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_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/intersection_types/redundant_types/inheritence.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/inheritence.phpt new file mode 100644 index 0000000000000..f31c8d39dc0e6 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/inheritence.phpt @@ -0,0 +1,15 @@ +--TEST-- +Intersection with child class +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/typed_reference.phpt b/Zend/tests/type_declarations/intersection_types/typed_reference.phpt new file mode 100644 index 0000000000000..2ceb34f256ffd --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/typed_reference.phpt @@ -0,0 +1,30 @@ +--TEST-- +Intersection types and typed reference +--FILE-- +y =& $r; +$test->z =& $r; + +try { + $r = new B; +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECT-- +Cannot assign B to reference held by property Test::$z of type X&Z diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt new file mode 100644 index 0000000000000..f4b214fb90256 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid1.phpt @@ -0,0 +1,27 @@ +--TEST-- +Co-variance check failure for intersection type where child replace one of intersection type members with a supertype +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): A&C must be compatible with Foo::foo(): B&C in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt new file mode 100644 index 0000000000000..e69028882688e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid2.phpt @@ -0,0 +1,26 @@ +--TEST-- +Co-variance check failure for intersection type where child replaces it with standard type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): array must be compatible with Foo::foo(): A&B in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt new file mode 100644 index 0000000000000..d34c022175649 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type1.phpt @@ -0,0 +1,26 @@ +--TEST-- +Co-variance check failure for intersection type where child removes one of intersection type members +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): A must be compatible with Foo::foo(): A&B in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt new file mode 100644 index 0000000000000..cff3674ddf485 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_drop_type2.phpt @@ -0,0 +1,27 @@ +--TEST-- +Co-variance check failure for intersection type where child removes one of intersection type members +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of FooChild::foo(): A&B must be compatible with Foo::foo(): A&B&C in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt new file mode 100644 index 0000000000000..6f81d10b72f23 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance1.phpt @@ -0,0 +1,18 @@ +--TEST-- +Property types must be invariant +--FILE-- + +--EXPECTF-- +Fatal error: Type of B::$prop must be X&Y (as in class A) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt new file mode 100644 index 0000000000000..0c48e42790037 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_invariance2.phpt @@ -0,0 +1,20 @@ +--TEST-- +Intersection type reduction invalid invariant type check +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Type of Test2::$prop must be X&Y (as in class Test) in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid1.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid1.phpt new file mode 100644 index 0000000000000..3282b0f9dc04f --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid1.phpt @@ -0,0 +1,40 @@ +--TEST-- +Valid inheritence - co-variance +--FILE-- +foo()); +$o = new FooSecondChild(); +var_dump($o->foo()); + +?> +--EXPECTF-- +object(Test)#%d (0) { +} +object(Test)#%d (0) { +} diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid2.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid2.phpt new file mode 100644 index 0000000000000..e8db85eb5523d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid2.phpt @@ -0,0 +1,22 @@ +--TEST-- +Commutative intersection types +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid3.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid3.phpt new file mode 100644 index 0000000000000..d8166b49ec42b --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid3.phpt @@ -0,0 +1,29 @@ +--TEST-- +Valid intersection type variance +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt new file mode 100644 index 0000000000000..4761628e5b97f --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid4.phpt @@ -0,0 +1,19 @@ +--TEST-- +Intersection type reduction valid invariant type check +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid5.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid5.phpt new file mode 100644 index 0000000000000..de79f0586157d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid5.phpt @@ -0,0 +1,45 @@ +--TEST-- +Replacing union of classes respecting intersection type by intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_API.c b/Zend/zend_API.c index b726412f3a051..0df51b2f62413 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2424,14 +2424,14 @@ static void zend_check_magic_method_return_type(const zend_class_entry *ce, cons return; } - bool has_class_type = ZEND_TYPE_HAS_CLASS(fptr->common.arg_info[-1].type); + bool is_complex_type = ZEND_TYPE_IS_COMPLEX(fptr->common.arg_info[-1].type); uint32_t extra_types = ZEND_TYPE_PURE_MASK(fptr->common.arg_info[-1].type) & ~return_type; if (extra_types & MAY_BE_STATIC) { extra_types &= ~MAY_BE_STATIC; - has_class_type = 1; + is_complex_type = true; } - if (extra_types || (has_class_type && return_type != MAY_BE_OBJECT)) { + if (extra_types || (is_complex_type && return_type != MAY_BE_OBJECT)) { zend_error(error_type, "%s::%s(): Return type must be %s when declared", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name), ZSTR_VAL(zend_type_to_string((zend_type) ZEND_TYPE_INIT_MASK(return_type)))); @@ -2768,7 +2768,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend 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_HAS_CLASS(new_arg_info[i].type)) { + if (ZEND_TYPE_IS_COMPLEX(new_arg_info[i].type)) { ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type) && "Should be stored as simple name"); const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type); diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index 927fc23d95004..ba46b7feb6cba 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1457,6 +1457,16 @@ static ZEND_COLD void zend_ast_export_type(smart_str *str, zend_ast *ast, int in } return; } + if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { + 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, '?'); } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 0e3468ebde110..1bf8f263fa45e 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -62,6 +62,7 @@ enum _zend_ast_kind { ZEND_AST_TRAIT_ADAPTATIONS, ZEND_AST_USE, ZEND_AST_TYPE_UNION, + ZEND_AST_TYPE_INTERSECTION, ZEND_AST_ATTRIBUTE_LIST, ZEND_AST_ATTRIBUTE_GROUP, ZEND_AST_MATCH_ARM_LIST, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a762fe98f1b10..14cbcbb525acd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1149,15 +1149,21 @@ ZEND_API zend_result do_bind_class(zval *lcname, zend_string *lc_parent_name) /* } /* }}} */ -static zend_string *add_type_string(zend_string *type, zend_string *new_type) { +static zend_string *add_type_string(zend_string *type, zend_string *new_type, bool is_intersection) { zend_string *result; if (type == NULL) { return zend_string_copy(new_type); } - result = zend_string_concat3( - ZSTR_VAL(type), ZSTR_LEN(type), "|", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); - zend_string_release(type); + if (is_intersection) { + result = zend_string_concat3(ZSTR_VAL(type), ZSTR_LEN(type), + "&", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + zend_string_release(type); + } else { + result = zend_string_concat3( + ZSTR_VAL(type), ZSTR_LEN(type), "|", 1, ZSTR_VAL(new_type), ZSTR_LEN(new_type)); + zend_string_release(type); + } return result; } @@ -1185,9 +1191,10 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop if (ZEND_TYPE_HAS_LIST(type)) { zend_type *list_type; + bool is_intersection = ZEND_TYPE_IS_INTERSECTION(type); ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) { if (ZEND_TYPE_HAS_CE(*list_type)) { - str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name); + str = add_type_string(str, ZEND_TYPE_CE(*list_type)->name, is_intersection); } else { zend_string *name = ZEND_TYPE_NAME(*list_type); @@ -1196,13 +1203,13 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop zend_class_entry *ce = ZSTR_GET_CE_CACHE(name); if (ce->ce_flags & ZEND_ACC_ANON_CLASS) { zend_string *tmp = zend_string_init(ZSTR_VAL(ce->name), strlen(ZSTR_VAL(ce->name)), 0); - str = add_type_string(str, tmp); + str = add_type_string(str, tmp, is_intersection); } else { - str = add_type_string(str, ce->name); + str = add_type_string(str, ce->name, is_intersection); } } else { zend_string *resolved = resolve_class_name(name, scope); - str = add_type_string(str, resolved); + str = add_type_string(str, resolved, is_intersection); zend_string_release(resolved); } } @@ -1228,7 +1235,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); if (type_mask == MAY_BE_ANY) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_MIXED), /* is_intersection */ false); return str; } @@ -1240,39 +1247,39 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop name = called_scope->name; } } - str = add_type_string(str, name); + str = add_type_string(str, name, /* is_intersection */ false); } if (type_mask & MAY_BE_CALLABLE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false); } if (type_mask & MAY_BE_ITERABLE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ITERABLE), /* is_intersection */ false); } if (type_mask & MAY_BE_OBJECT) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_OBJECT), /* is_intersection */ false); } if (type_mask & MAY_BE_ARRAY) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_ARRAY), /* is_intersection */ false); } if (type_mask & MAY_BE_STRING) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_STRING), /* is_intersection */ false); } if (type_mask & MAY_BE_LONG) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_INT), /* is_intersection */ false); } if (type_mask & MAY_BE_DOUBLE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FLOAT), /* is_intersection */ false); } if ((type_mask & MAY_BE_BOOL) == MAY_BE_BOOL) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_BOOL), /* is_intersection */ false); } else if (type_mask & MAY_BE_FALSE) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_FALSE), /* is_intersection */ false); } if (type_mask & MAY_BE_VOID) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_VOID), /* is_intersection */ false); } if (type_mask & MAY_BE_NEVER) { - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NEVER)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NEVER), /* is_intersection */ false); } if (type_mask & MAY_BE_NULL) { @@ -1283,7 +1290,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop return nullable_str; } - str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); + str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE), /* is_intersection */ false); } return str; } @@ -2411,7 +2418,7 @@ 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)) { + if (!ZEND_TYPE_IS_COMPLEX(type)) { return 0; } if (ZEND_TYPE_HAS_LIST(type)) { @@ -6249,8 +6256,8 @@ static zend_type zend_compile_typename( ZEND_TYPE_FULL_MASK(type) |= ZEND_TYPE_PURE_MASK(single_type); ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; - if (ZEND_TYPE_HAS_CLASS(single_type)) { - if (!ZEND_TYPE_HAS_CLASS(type)) { + if (ZEND_TYPE_IS_COMPLEX(single_type)) { + if (!ZEND_TYPE_IS_COMPLEX(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; @@ -6284,9 +6291,60 @@ static zend_type zend_compile_typename( memcpy(list, type_list, ZEND_TYPE_LIST_SIZE(type_list->num_types)); ZEND_TYPE_SET_LIST(type, list); ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; + /* Inform that the type list is a union type */ + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_UNION_BIT; } free_alloca(type_list, use_heap); + } else if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { + zend_ast_list *list = zend_ast_get_list(ast); + zend_type_list *type_list; + + /* Allocate the type list directly on the arena as it must be a type + * list of the same number of elements as the AST list has children */ + type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children)); + type_list->num_types = 0; + + ZEND_ASSERT(list->children > 1); + + 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); + zend_string *standard_type_str = zend_type_to_string(single_type); + + /* An intersection of standard types cannot exist so invalidate it */ + if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + } + /* Check for "self" and "parent" too */ + if (zend_string_equals_literal_ci(standard_type_str, "self") + || zend_string_equals_literal_ci(standard_type_str, "parent")) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + } + zend_string_release_ex(standard_type_str, false); + + /* Add type to the type list */ + type_list->types[type_list->num_types++] = single_type; + + /* Check for trivially redundant class types */ + for (size_t i = 0; i < type_list->num_types - 1; i++) { + if (zend_string_equals_ci( + ZEND_TYPE_NAME(type_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)); + } + } + } + + ZEND_ASSERT(list->children == type_list->num_types); + + ZEND_TYPE_SET_LIST(type, type_list); + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; + /* Inform that the type list is an intersection type */ + ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_INTERSECTION_BIT; } else { type = zend_compile_single_typename(ast); } @@ -6313,23 +6371,23 @@ static zend_type zend_compile_typename( zend_error_noreturn(E_COMPILE_ERROR, "Type mixed cannot be marked as nullable since mixed already includes null"); } - if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_HAS_CLASS(type) || (type_mask & MAY_BE_STATIC))) { + if ((type_mask & MAY_BE_OBJECT) && (ZEND_TYPE_IS_COMPLEX(type) || (type_mask & MAY_BE_STATIC))) { 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)) { + if ((type_mask & MAY_BE_VOID) && (ZEND_TYPE_IS_COMPLEX(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_NEVER) && (ZEND_TYPE_HAS_CLASS(type) || type_mask != MAY_BE_NEVER)) { + if ((type_mask & MAY_BE_NEVER) && (ZEND_TYPE_IS_COMPLEX(type) || type_mask != MAY_BE_NEVER)) { zend_error_noreturn(E_COMPILE_ERROR, "never 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))) { + && !ZEND_TYPE_IS_COMPLEX(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 { @@ -7472,7 +7530,7 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ ZEND_ASSERT(ce->ce_flags & ZEND_ACC_ENUM); zend_type type = zend_compile_typename(enum_backing_type_ast, 0); uint32_t type_mask = ZEND_TYPE_PURE_MASK(type); - if (ZEND_TYPE_HAS_CLASS(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { + if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) { zend_string *type_string = zend_type_to_string(type); zend_error_noreturn(E_COMPILE_ERROR, "Enum backing type must be int or string, %s given", diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index aa07f9b41b036..1ae1c11c8abee 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -836,41 +836,79 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class } } +// TODO Handle complex types when added +// TODO Update to handle union and intersection in the same loop static 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)) { zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { - if (ZEND_TYPE_HAS_NAME(*list_type)) { - zend_string *name = ZEND_TYPE_NAME(*list_type); - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - continue; + if (ZEND_TYPE_IS_INTERSECTION(info->type)) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { + if (ZEND_TYPE_HAS_NAME(*list_type)) { + zend_string *name = ZEND_TYPE_NAME(*list_type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (UNEXPECTED(!ce)) { + /* Cannot resolve */ + return false; + } + } + } else { + ce = resolve_single_class_type(name, info->ce); + if (!ce) { + /* Cannot resolve */ + return false; + } + if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_string_release(name); + ZEND_TYPE_SET_CE(*list_type, ce); } } } else { - ce = resolve_single_class_type(name, info->ce); - if (!ce) { - continue; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(*list_type, ce); + ce = ZEND_TYPE_CE(*list_type); + } + if (!instanceof_function(object_ce, ce)) { + return false; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; + } else { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { + if (ZEND_TYPE_HAS_NAME(*list_type)) { + zend_string *name = ZEND_TYPE_NAME(*list_type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (UNEXPECTED(!ce)) { + continue; + } + } + } else { + ce = resolve_single_class_type(name, info->ce); + if (!ce) { + continue; + } + if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_string_release(name); + ZEND_TYPE_SET_CE(*list_type, ce); + } } + } else { + ce = ZEND_TYPE_CE(*list_type); } - } else { - ce = ZEND_TYPE_CE(*list_type); - } - if (instanceof_function(object_ce, ce)) { - return 1; - } - } ZEND_TYPE_LIST_FOREACH_END(); - return 0; + if (instanceof_function(object_ce, ce)) { + return true; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return false; + } } else { if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) { zend_string *name = ZEND_TYPE_NAME(info->type); @@ -907,7 +945,7 @@ static zend_always_inline bool i_zend_check_property_type(zend_property_info *in return 1; } - if (ZEND_TYPE_HAS_CLASS(info->type) && Z_TYPE_P(property) == IS_OBJECT + if (ZEND_TYPE_IS_COMPLEX(info->type) && Z_TYPE_P(property) == IS_OBJECT && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(property))) { return 1; } @@ -969,53 +1007,101 @@ ZEND_API bool zend_value_instanceof_static(zval *zv) { # define HAVE_CACHE_SLOT 1 #endif +// TODO Handle complex types when added +// TODO Update to handle union and intersection in the same loop static zend_always_inline bool zend_check_type_slow( zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal) { uint32_t type_mask; - if (ZEND_TYPE_HAS_CLASS(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { + if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) { zend_class_entry *ce; if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (HAVE_CACHE_SLOT && *cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); + if (ZEND_TYPE_IS_INTERSECTION(*type)) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { + if (HAVE_CACHE_SLOT && *cache_slot) { + ce = *cache_slot; + } else { + zend_string *name = ZEND_TYPE_NAME(*list_type); - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (!ce) { + if (HAVE_CACHE_SLOT) { + cache_slot++; + } + + /* Cannot resolve */ + return false; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); if (!ce) { if (HAVE_CACHE_SLOT) { cache_slot++; } - continue; + + /* Cannot resolve */ + return false; } } + } + + /* Perform actual type check */ + /* Instance of a single type part of a union is sufficient to pass the type check */ + if (!instanceof_function(Z_OBJCE_P(arg), ce)) { + return false; + } + if (HAVE_CACHE_SLOT) { + cache_slot++; + } + } ZEND_TYPE_LIST_FOREACH_END(); + return true; + } else { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { + if (HAVE_CACHE_SLOT && *cache_slot) { + ce = *cache_slot; } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; + zend_string *name = ZEND_TYPE_NAME(*list_type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (!ce) { + if (HAVE_CACHE_SLOT) { + cache_slot++; + } + continue; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (!ce) { + if (HAVE_CACHE_SLOT) { + cache_slot++; + } + continue; } - continue; } } + + /* Perform actual type check */ + /* Instance of a single type part of a union is sufficient to pass the type check */ + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; + } if (HAVE_CACHE_SLOT) { - *cache_slot = ce; + cache_slot++; } - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - } ZEND_TYPE_LIST_FOREACH_END(); + } ZEND_TYPE_LIST_FOREACH_END(); + } } else { if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { ce = (zend_class_entry *) *cache_slot; @@ -3129,7 +3215,7 @@ static zend_always_inline int i_zend_verify_type_assignable_zval( return 1; } - if (ZEND_TYPE_HAS_CLASS(type) && zv_type == IS_OBJECT + if (ZEND_TYPE_IS_COMPLEX(type) && zv_type == IS_OBJECT && zend_check_and_resolve_property_class_type(info, Z_OBJCE_P(zv))) { return 1; } diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 055f5fbc6d6bc..7e9ddde901b01 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -403,10 +403,58 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name zend_hash_add_ptr(ht, class_name, ce); } +static inheritance_status zend_perform_intersection_covariant_class_type_check( + zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce, + zend_class_entry *fe_scope, zend_type fe_type, + bool register_unresolved) +{ + bool have_unresolved = false; + zend_type *single_type; + + /* Traverse the list of child types and check if it is either a subtype + * of one of the parent types OR is not in the parent type list */ + ZEND_TYPE_FOREACH(fe_type, single_type) { + zend_class_entry *fe_ce; + zend_string *fe_class_name = NULL; + if (ZEND_TYPE_HAS_NAME(*single_type)) { + fe_class_name = + resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + return INHERITANCE_SUCCESS; + } + + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); + fe_ce = + lookup_class(fe_scope, fe_class_name, register_unresolved); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); + fe_ce = ZEND_TYPE_CE(*single_type); + } else { + /* standard type */ + ZEND_ASSERT(0 && "This shouldn't happen yet"); + continue; + } + + if (!fe_ce || !proto_ce) { + have_unresolved = true; + continue; + } + if (unlinked_instanceof(fe_ce, proto_ce)) { + track_class_dependency(fe_ce, fe_class_name); + track_class_dependency(proto_ce, proto_class_name); + return INHERITANCE_SUCCESS; + } + } ZEND_TYPE_FOREACH_END(); + + return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; +} + static inheritance_status zend_perform_covariant_class_type_check( zend_class_entry *fe_scope, zend_string *fe_class_name, zend_class_entry *fe_ce, zend_class_entry *proto_scope, zend_type proto_type) { bool have_unresolved = 0; + + /* If the parent has 'object' as a return type, any class satisfies the co-variant check */ 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 @@ -430,6 +478,9 @@ static inheritance_status zend_perform_covariant_class_type_check( } zend_type *single_type; + + /* Traverse the list of parent types and check if the current child (FE) + * class is the subtype of at least one of them */ ZEND_TYPE_FOREACH(proto_type, single_type) { zend_class_entry *proto_ce; zend_string *proto_class_name = NULL; @@ -446,6 +497,7 @@ static inheritance_status zend_perform_covariant_class_type_check( if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); proto_ce = ZEND_TYPE_CE(*single_type); } else { + /* standard type */ continue; } @@ -519,31 +571,69 @@ static inheritance_status zend_perform_covariant_type_check( } zend_type *single_type; - bool all_success = 1; + bool all_success = true; + + /* For intersection types loop over the parent types first as a child + * can add them */ + if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) { + /* First try to check whether we can succeed without resolving anything */ + ZEND_TYPE_FOREACH(proto_type, single_type) { + inheritance_status status; + zend_string *proto_class_name; + zend_class_entry *proto_ce = NULL; + + if (ZEND_TYPE_HAS_NAME(*single_type)) { + proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + proto_ce = ZEND_TYPE_CE(*single_type); + proto_class_name = proto_ce->name; + } else { + /* standard type */ + ZEND_ASSERT(0 && "This shouldn't happen yet"); + continue; + } - /* First try to check whether we can succeed without resolving anything */ - ZEND_TYPE_FOREACH(fe_type, single_type) { - inheritance_status status; - if (ZEND_TYPE_HAS_NAME(*single_type)) { - zend_string *fe_class_name = - resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); - status = zend_perform_covariant_class_type_check( - fe_scope, fe_class_name, NULL, proto_scope, proto_type); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type); - status = zend_perform_covariant_class_type_check( - fe_scope, fe_ce->name, fe_ce, proto_scope, proto_type); - } else { - continue; - } + status = zend_perform_intersection_covariant_class_type_check( + proto_scope, proto_class_name, proto_ce, + fe_scope, fe_type, /* register_unresolved */ false); - if (status == INHERITANCE_ERROR) { - return INHERITANCE_ERROR; - } - if (status != INHERITANCE_SUCCESS) { - all_success = 0; - } - } ZEND_TYPE_FOREACH_END(); + if (status == INHERITANCE_ERROR) { + return NHERITANCE_ERROR; + } + if (status != INHERITANCE_SUCCESS) { + ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); + all_success = false; + } + } ZEND_TYPE_FOREACH_END(); + } + /* Only union or single types both in parent and child */ + else { + /* First try to check whether we can succeed without resolving anything */ + ZEND_TYPE_FOREACH(fe_type, single_type) { + inheritance_status status; + if (ZEND_TYPE_HAS_NAME(*single_type)) { + zend_string *fe_class_name = + resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); + status = zend_perform_covariant_class_type_check( + fe_scope, fe_class_name, NULL, + proto_scope, proto_type, /* register_unresolved */ 0); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type); + status = zend_perform_covariant_class_type_check( + fe_scope, fe_ce->name, fe_ce, + proto_scope, proto_type, /* register_unresolved */ 0); + } else { + continue; + } + + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; + } + if (status != INHERITANCE_SUCCESS) { + all_success = 0; + } + } ZEND_TYPE_FOREACH_END(); + } /* All individual checks succeeded, overall success */ if (all_success) { diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e783a7d9aa1e7..887cb6e83939a 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -265,7 +265,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_without_static %type identifier type_expr_without_static union_type_without_static -%type inline_function union_type +%type inline_function union_type intersection_type %type attributed_statement attributed_class_statement attributed_parameter %type attribute_decl attribute attributes attribute_group namespace_declaration_name %type match match_arm_list non_empty_match_arm_list match_arm match_arm_cond_list @@ -785,6 +785,7 @@ type_expr: type { $$ = $1; } | '?' type { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type { $$ = $1; } + | intersection_type { $$ = $1; } ; type: @@ -797,6 +798,11 @@ union_type: | union_type '|' type { $$ = zend_ast_list_add($1, $3); } ; +intersection_type: + type '&' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); } + | intersection_type '&' type { $$ = zend_ast_list_add($1, $3); } +; + /* Duplicate the type rules without "static", * to avoid conflicts with "static" modifier for properties. */ @@ -819,6 +825,8 @@ union_type_without_static: { $$ = zend_ast_list_add($1, $3); } ; +// TODO Check if need to do intersection without static (seems weird) + return_type: %empty { $$ = NULL; } | ':' type_expr { $$ = $2; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 963362222d0eb..29ea3f5e9c17e 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -109,7 +109,7 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * It shouldn't be used directly. Only through ZEND_TYPE_* macros. * * 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_IS_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 * @@ -148,15 +148,21 @@ typedef struct { /* TODO: bit 21 is not used */ /* Whether the type list is arena allocated */ #define _ZEND_TYPE_ARENA_BIT (1u << 20) +/* Whether the type list is an intersection type */ +#define _ZEND_TYPE_INTERSECTION_BIT (1u << 19) +/* Whether the type is a union type */ +#define _ZEND_TYPE_UNION_BIT (1u << 18) /* Type mask excluding the flags above. */ -#define _ZEND_TYPE_MAY_BE_MASK ((1u << 20) - 1) +#define _ZEND_TYPE_MAY_BE_MASK ((1u << 18) - 1) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2u #define ZEND_TYPE_IS_SET(t) \ (((t).type_mask & _ZEND_TYPE_MASK) != 0) -#define ZEND_TYPE_HAS_CLASS(t) \ +/* If a type is complex it means it's either a list with a union or intersection, + * or the void pointer is a CE/Name */ +#define ZEND_TYPE_IS_COMPLEX(t) \ ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) #define ZEND_TYPE_HAS_CE(t) \ @@ -168,6 +174,12 @@ typedef struct { #define ZEND_TYPE_HAS_LIST(t) \ ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) +#define ZEND_TYPE_IS_INTERSECTION(t) \ + ((((t).type_mask) & _ZEND_TYPE_INTERSECTION_BIT) != 0) + +#define ZEND_TYPE_IS_UNION(t) \ + ((((t).type_mask) & _ZEND_TYPE_UNION_BIT) != 0) + #define ZEND_TYPE_USES_ARENA(t) \ ((((t).type_mask) & _ZEND_TYPE_ARENA_BIT) != 0) diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 5f1bf7b901bcb..28d36eaab2f53 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1324,40 +1324,74 @@ static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_i { uint32_t type_mask; - if (ZEND_TYPE_HAS_CLASS(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { + if (ZEND_TYPE_IS_COMPLEX(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { zend_class_entry *ce; if (ZEND_TYPE_HAS_LIST(arg_info->type)) { zend_type *list_type; - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { - if (*cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); + if (ZEND_TYPE_IS_INTERSECTION(arg_info->type)) { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { + if (*cache_slot) { + ce = *cache_slot; + } else { + zend_string *name = ZEND_TYPE_NAME(*list_type); - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); if (!ce) { - cache_slot++; - continue; + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (!ce) { + /* Cannot resolve */ + return false; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (!ce) { + /* Cannot resolve */ + return false; } } + *cache_slot = ce; + } + if (!instanceof_function(Z_OBJCE_P(arg), ce)) { + return false; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); + return true; + } else { + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { + if (*cache_slot) { + ce = *cache_slot; } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - cache_slot++; - continue; + zend_string *name = ZEND_TYPE_NAME(*list_type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (!ce) { + cache_slot++; + continue; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (!ce) { + cache_slot++; + continue; + } } + *cache_slot = ce; } - *cache_slot = ce; - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - cache_slot++; - } ZEND_TYPE_LIST_FOREACH_END(); + if (instanceof_function(Z_OBJCE_P(arg), ce)) { + return 1; + } + cache_slot++; + } ZEND_TYPE_LIST_FOREACH_END(); + } } else { if (EXPECTED(*cache_slot)) { ce = (zend_class_entry *) *cache_slot; diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index ff8b53cf8befe..90b76addcbc2f 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -79,6 +79,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_intersection_type_ptr; PHPAPI zend_class_entry *reflection_union_type_ptr; PHPAPI zend_class_entry *reflection_class_ptr; PHPAPI zend_class_entry *reflection_object_ptr; @@ -1319,22 +1320,40 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje } /* }}} */ +typedef enum reflection_type_kind { + NAMED_TYPE = 0, + UNION_TYPE = 1, + INTERSECTION_TYPE = 2 +} reflection_type_kind; + /* 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 bool is_union_type(zend_type type) { +static reflection_type_kind get_type_kind(zend_type type) { + uint32_t type_mask_without_null = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type); + if (ZEND_TYPE_HAS_LIST(type)) { - return 1; + if (ZEND_TYPE_IS_INTERSECTION(type)) { + return INTERSECTION_TYPE; + } + ZEND_ASSERT(ZEND_TYPE_IS_UNION(type)); + return UNION_TYPE; } - 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 (ZEND_TYPE_IS_COMPLEX(type)) { + if (type_mask_without_null != 0) { + return UNION_TYPE; + } + return NAMED_TYPE; } - if (type_mask_without_null == MAY_BE_BOOL) { - return 0; + if (type_mask_without_null == MAY_BE_BOOL || ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY) { + return NAMED_TYPE; } /* Check that only one bit is set. */ - return (type_mask_without_null & (type_mask_without_null - 1)) != 0; + if ((type_mask_without_null & (type_mask_without_null - 1)) != 0) { + return UNION_TYPE; + } + return NAMED_TYPE; } /* {{{ reflection_type_factory */ @@ -1342,14 +1361,26 @@ static void reflection_type_factory(zend_type type, zval *object, bool legacy_be { reflection_object *intern; type_reference *reference; - bool is_union = is_union_type(type); + reflection_type_kind type_kind = get_type_kind(type); bool is_mixed = ZEND_TYPE_PURE_MASK(type) == MAY_BE_ANY; - reflection_instantiate(is_union && !is_mixed ? reflection_union_type_ptr : reflection_named_type_ptr, object); + switch (type_kind) { + case INTERSECTION_TYPE: + reflection_instantiate(reflection_intersection_type_ptr, object); + break; + case UNION_TYPE: + reflection_instantiate(reflection_union_type_ptr, object); + break; + case NAMED_TYPE: + reflection_instantiate(reflection_named_type_ptr, object); + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + intern = Z_REFLECTION_P(object); reference = (type_reference*) emalloc(sizeof(type_reference)); reference->type = type; - reference->legacy_behavior = legacy_behavior && !is_union && !is_mixed; + reference->legacy_behavior = legacy_behavior && type_kind == NAMED_TYPE && !is_mixed; intern->ptr = reference; intern->ref_type = REF_TYPE_TYPE; @@ -3097,6 +3128,27 @@ ZEND_METHOD(ReflectionUnionType, getTypes) } /* }}} */ +/* {{{ Returns the types that are part of this intersection type */ +ZEND_METHOD(ReflectionIntersectionType, getTypes) +{ + reflection_object *intern; + type_reference *param; + zend_type *list_type; + + if (zend_parse_parameters_none() == FAILURE) { + RETURN_THROWS(); + } + GET_REFLECTION_OBJECT_PTR(param); + + ZEND_ASSERT(ZEND_TYPE_HAS_LIST(param->type)); + + array_init(return_value); + ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(param->type), list_type) { + append_type(return_value, *list_type); + } ZEND_TYPE_LIST_FOREACH_END(); +} +/* }}} */ + /* {{{ Constructor. Throws an Exception in case the given method does not exist */ ZEND_METHOD(ReflectionMethod, __construct) { @@ -7017,6 +7069,9 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_union_type_ptr = register_class_ReflectionUnionType(reflection_type_ptr); reflection_init_class_handlers(reflection_union_type_ptr); + reflection_intersection_type_ptr = register_class_ReflectionIntersectionType(reflection_type_ptr); + reflection_init_class_handlers(reflection_intersection_type_ptr); + reflection_method_ptr = register_class_ReflectionMethod(reflection_function_abstract_ptr); reflection_init_class_handlers(reflection_method_ptr); diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index fb2b2675ef2fa..3b883bb7e32ad 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -596,6 +596,11 @@ class ReflectionUnionType extends ReflectionType public function getTypes(): array {} } +class ReflectionIntersectionType extends ReflectionType +{ + public function getTypes(): array {} +} + class ReflectionExtension implements Reflector { public string $name; diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index fa0a532a24806..dc39e428719ff 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: e66c459f457f71cb677a93652364ab7e81be8b0e */ + * Stub hash: d8e686125cf213e019c1d706867e3c178fa057d2 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -468,6 +468,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionUnionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables +#define arginfo_class_ReflectionIntersectionType_getTypes arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables + #define arginfo_class_ReflectionExtension___clone arginfo_class_ReflectionFunctionAbstract___clone ZEND_BEGIN_ARG_INFO_EX(arginfo_class_ReflectionExtension___construct, 0, 0, 1) @@ -766,6 +768,7 @@ ZEND_METHOD(ReflectionType, __toString); ZEND_METHOD(ReflectionNamedType, getName); ZEND_METHOD(ReflectionNamedType, isBuiltin); ZEND_METHOD(ReflectionUnionType, getTypes); +ZEND_METHOD(ReflectionIntersectionType, getTypes); ZEND_METHOD(ReflectionExtension, __construct); ZEND_METHOD(ReflectionExtension, __toString); ZEND_METHOD(ReflectionExtension, getName); @@ -1070,6 +1073,12 @@ static const zend_function_entry class_ReflectionUnionType_methods[] = { }; +static const zend_function_entry class_ReflectionIntersectionType_methods[] = { + ZEND_ME(ReflectionIntersectionType, getTypes, arginfo_class_ReflectionIntersectionType_getTypes, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + + static const zend_function_entry class_ReflectionExtension_methods[] = { ZEND_MALIAS(ReflectionClass, __clone, __clone, arginfo_class_ReflectionExtension___clone, ZEND_ACC_PRIVATE) ZEND_ME(ReflectionExtension, __construct, arginfo_class_ReflectionExtension___construct, ZEND_ACC_PUBLIC) @@ -1367,6 +1376,16 @@ static zend_class_entry *register_class_ReflectionUnionType(zend_class_entry *cl return class_entry; } +static zend_class_entry *register_class_ReflectionIntersectionType(zend_class_entry *class_entry_ReflectionType) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "ReflectionIntersectionType", class_ReflectionIntersectionType_methods); + class_entry = zend_register_internal_class_ex(&ce, class_entry_ReflectionType); + + return class_entry; +} + static zend_class_entry *register_class_ReflectionExtension(zend_class_entry *class_entry_Reflector) { zend_class_entry ce, *class_entry; diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 88551fcd7932b..63bd4ef0265ea 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -59,63 +59,68 @@ array(23) { ["name"]=> string(19) "ReflectionUnionType" } - ["ReflectionMethod"]=> + ["ReflectionIntersectionType"]=> object(ReflectionClass)#12 (1) { + ["name"]=> + string(26) "ReflectionIntersectionType" + } + ["ReflectionMethod"]=> + object(ReflectionClass)#13 (1) { ["name"]=> string(16) "ReflectionMethod" } ["ReflectionClass"]=> - object(ReflectionClass)#13 (1) { + object(ReflectionClass)#14 (1) { ["name"]=> string(15) "ReflectionClass" } ["ReflectionObject"]=> - object(ReflectionClass)#14 (1) { + object(ReflectionClass)#15 (1) { ["name"]=> string(16) "ReflectionObject" } ["ReflectionProperty"]=> - object(ReflectionClass)#15 (1) { + object(ReflectionClass)#16 (1) { ["name"]=> string(18) "ReflectionProperty" } ["ReflectionClassConstant"]=> - object(ReflectionClass)#16 (1) { + object(ReflectionClass)#17 (1) { ["name"]=> string(23) "ReflectionClassConstant" } ["ReflectionExtension"]=> - object(ReflectionClass)#17 (1) { + object(ReflectionClass)#18 (1) { ["name"]=> string(19) "ReflectionExtension" } ["ReflectionZendExtension"]=> - object(ReflectionClass)#18 (1) { + object(ReflectionClass)#19 (1) { ["name"]=> string(23) "ReflectionZendExtension" } ["ReflectionReference"]=> - object(ReflectionClass)#19 (1) { + object(ReflectionClass)#20 (1) { ["name"]=> string(19) "ReflectionReference" } ["ReflectionAttribute"]=> - object(ReflectionClass)#20 (1) { + object(ReflectionClass)#21 (1) { ["name"]=> string(19) "ReflectionAttribute" } ["ReflectionEnum"]=> - object(ReflectionClass)#21 (1) { + object(ReflectionClass)#22 (1) { ["name"]=> string(14) "ReflectionEnum" } ["ReflectionEnumUnitCase"]=> - object(ReflectionClass)#22 (1) { + object(ReflectionClass)#23 (1) { ["name"]=> string(22) "ReflectionEnumUnitCase" } ["ReflectionEnumBackedCase"]=> - object(ReflectionClass)#23 (1) { + object(ReflectionClass)#24 (1) { ["name"]=> string(24) "ReflectionEnumBackedCase" } diff --git a/ext/reflection/tests/intersection_types.phpt b/ext/reflection/tests/intersection_types.phpt new file mode 100644 index 0000000000000..39557aaf1d796 --- /dev/null +++ b/ext/reflection/tests/intersection_types.phpt @@ -0,0 +1,80 @@ +--TEST-- +Intersection types in reflection +--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&Z&Traversable&Countable { } + +class Test { + public X&Y&Countable $prop; +} + +dumpType((new ReflectionFunction('test1'))->getReturnType()); + +$rc = new ReflectionClass(Test::class); +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +/* Force CE resolution of the property type */ + +interface y {} +class x implements Y, Countable { + public function count() {} +} +$test = new Test; +$test->prop = new x; + +$rp = $rc->getProperty('prop'); +dumpType($rp->getType()); + +?> +--EXPECT-- +Type X&Y&Z&Traversable&Countable: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: Z + String: Z + Allows Null: false + Name: Traversable + String: Traversable + Allows Null: false + Name: Countable + String: Countable + Allows Null: false +Type X&Y&Countable: +Allows null: false + Name: X + String: X + Allows Null: false + Name: Y + String: Y + Allows Null: false + Name: Countable + String: Countable + Allows Null: false +Type x&y&Countable: +Allows null: false + Name: x + String: x + Allows Null: false + Name: y + String: y + Allows Null: false + Name: Countable + String: Countable + Allows Null: false diff --git a/ext/tokenizer/tests/002.phpt b/ext/tokenizer/tests/002.phpt index 7cd65af19319f..4478d5c55c65d 100644 --- a/ext/tokenizer/tests/002.phpt +++ b/ext/tokenizer/tests/002.phpt @@ -917,7 +917,14 @@ array(47) { int(1) } [42]=> - string(1) "&" + array(3) { + [0]=> + int(%d) + [1]=> + string(1) "&" + [2]=> + int(1) + } [43]=> array(3) { [0]=> From f9c78be17ebe406ac8da0cfff353edf872cd1a4f Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 18 Mar 2021 20:28:24 +0100 Subject: [PATCH 02/40] Gross intersection type in parameter types syntax ambiguity workaround --- Zend/zend_language_parser.y | 40 +++++++++++++++++++++++----------- Zend/zend_language_scanner.l | 9 ++++++++ ext/tokenizer/tokenizer_data.c | 4 ++++ 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 887cb6e83939a..5718a57d92029 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -68,7 +68,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %left T_BOOLEAN_AND %left '|' %left '^' -%left '&' +%left T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG %nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP %nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL %left '.' @@ -231,6 +231,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_COALESCE "'??'" %token T_POW "'**'" %token T_POW_EQUAL "'**='" +%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "'&'" +%token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&''" %token T_BAD_CHARACTER "invalid character" /* Token used to force a parse error from the lexer */ @@ -264,7 +266,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type lexical_var_list encaps_list %type array_pair non_empty_array_pair_list array_pair_list possible_array_pair %type isset_variable type return_type type_expr type_without_static -%type identifier type_expr_without_static union_type_without_static +%type identifier type_expr_without_static union_type_without_static intersection_type_without_static %type inline_function union_type intersection_type %type attributed_statement attributed_class_statement attributed_parameter %type attribute_decl attribute attributes attribute_group namespace_declaration_name @@ -301,6 +303,11 @@ semi_reserved: | T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC ; +ampersand: + T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG + | T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG +; + identifier: T_STRING { $$ = $1; } | semi_reserved { @@ -555,7 +562,7 @@ function_declaration_statement: is_reference: %empty { $$ = 0; } - | '&' { $$ = ZEND_PARAM_REF; } + | T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG { $$ = ZEND_PARAM_REF; } ; is_variadic: @@ -633,7 +640,7 @@ implements_list: foreach_variable: variable { $$ = $1; } - | '&' variable { $$ = zend_ast_create(ZEND_AST_REF, $2); } + | ampersand variable { $$ = zend_ast_create(ZEND_AST_REF, $2); } | T_LIST '(' array_pair_list ')' { $$ = $3; $$->attr = ZEND_ARRAY_SYNTAX_LIST; } | '[' array_pair_list ']' { $$ = $2; $$->attr = ZEND_ARRAY_SYNTAX_SHORT; } ; @@ -799,8 +806,8 @@ union_type: ; intersection_type: - type '&' type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); } - | intersection_type '&' type { $$ = zend_ast_list_add($1, $3); } + type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); } + | intersection_type T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type { $$ = zend_ast_list_add($1, $3); } ; /* Duplicate the type rules without "static", @@ -810,6 +817,7 @@ type_expr_without_static: type_without_static { $$ = $1; } | '?' type_without_static { $$ = $2; $$->attr |= ZEND_TYPE_NULLABLE; } | union_type_without_static { $$ = $1; } + | intersection_type_without_static { $$ = $1; } ; type_without_static: @@ -825,7 +833,12 @@ union_type_without_static: { $$ = zend_ast_list_add($1, $3); } ; -// TODO Check if need to do intersection without static (seems weird) +intersection_type_without_static: + type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { $$ = zend_ast_create_list(2, ZEND_AST_TYPE_INTERSECTION, $1, $3); } + | intersection_type_without_static T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG type_without_static + { $$ = zend_ast_list_add($1, $3); } +; return_type: %empty { $$ = NULL; } @@ -1055,7 +1068,7 @@ expr: { $2->attr = ZEND_ARRAY_SYNTAX_SHORT; $$ = zend_ast_create(ZEND_AST_ASSIGN, $2, $5); } | variable '=' expr { $$ = zend_ast_create(ZEND_AST_ASSIGN, $1, $3); } - | variable '=' '&' variable + | variable '=' ampersand variable { $$ = zend_ast_create(ZEND_AST_ASSIGN_REF, $1, $4); } | T_CLONE expr { $$ = zend_ast_create(ZEND_AST_CLONE, $2); } | variable T_PLUS_EQUAL expr @@ -1099,7 +1112,8 @@ expr: | expr T_LOGICAL_XOR expr { $$ = zend_ast_create_binary_op(ZEND_BOOL_XOR, $1, $3); } | expr '|' expr { $$ = zend_ast_create_binary_op(ZEND_BW_OR, $1, $3); } - | expr '&' expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); } + | expr T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); } + | expr T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG expr { $$ = zend_ast_create_binary_op(ZEND_BW_AND, $1, $3); } | expr '^' expr { $$ = zend_ast_create_binary_op(ZEND_BW_XOR, $1, $3); } | expr '.' expr { $$ = zend_ast_create_binary_op(ZEND_CONCAT, $1, $3); } | expr '+' expr { $$ = zend_ast_create_binary_op(ZEND_ADD, $1, $3); } @@ -1209,7 +1223,7 @@ backup_lex_pos: returns_ref: %empty { $$ = 0; } - | '&' { $$ = ZEND_ACC_RETURN_REFERENCE; } + | ampersand { $$ = ZEND_ACC_RETURN_REFERENCE; } ; lexical_vars: @@ -1224,7 +1238,7 @@ lexical_var_list: lexical_var: T_VARIABLE { $$ = $1; } - | '&' T_VARIABLE { $$ = $2; $$->attr = ZEND_BIND_REF; } + | ampersand T_VARIABLE { $$ = $2; $$->attr = ZEND_BIND_REF; } ; function_call: @@ -1424,9 +1438,9 @@ array_pair: { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $3, $1); } | expr { $$ = zend_ast_create(ZEND_AST_ARRAY_ELEM, $1, NULL); } - | expr T_DOUBLE_ARROW '&' variable + | expr T_DOUBLE_ARROW ampersand variable { $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $4, $1); } - | '&' variable + | ampersand variable { $$ = zend_ast_create_ex(ZEND_AST_ARRAY_ELEM, 1, $2, NULL); } | T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index b127c3bc6b80c..1155f97d759c2 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1854,6 +1854,15 @@ NEWLINE ("\r"|"\n"|"\r\n") RETURN_TOKEN(T_SR); } +"&"{TABS_AND_SPACES}("$"|"...") { + yyless(1); + RETURN_TOKEN(T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG); +} + +"&" { + RETURN_TOKEN(T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG); +} + "]"|")" { /* Check that ] and ) match up properly with a preceding [ or ( */ RETURN_EXIT_NESTING_TOKEN(yytext[0]); diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index db5a425406ac6..a79112924f0c9 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -167,6 +167,8 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); } @@ -317,6 +319,8 @@ char *get_token_type_name(int token_type) case T_COALESCE: return "T_COALESCE"; case T_POW: return "T_POW"; case T_POW_EQUAL: return "T_POW_EQUAL"; + case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; + case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; } From 2d30485336f9f9dd5f81f8af788993bff4132970 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 23 Apr 2021 21:39:50 +0100 Subject: [PATCH 03/40] Fix variance when child is union and parent is intersection --- ...lid_covariance_intersection_to_union1.phpt | 24 +++++ ...lid_covariance_intersection_to_union2.phpt | 23 +++++ ...lid_covariance_intersection_to_union3.phpt | 25 ++++++ Zend/zend_inheritance.c | 87 ++++++++++++++++++- 4 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt new file mode 100644 index 0000000000000..bf5c45c2dd985 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union1.phpt @@ -0,0 +1,24 @@ +--TEST-- +Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 1 +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Y in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt new file mode 100644 index 0000000000000..1eafcb5d58718 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union2.phpt @@ -0,0 +1,23 @@ +--TEST-- +Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2 +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::foo(): TestOne|int must be compatible with A::foo(): X&Y in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt new file mode 100644 index 0000000000000..c4dd3449f976d --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid_covariance_intersection_to_union3.phpt @@ -0,0 +1,25 @@ +--TEST-- +Co-variance failure for intersection type where child is union, but not all members are a subtype of intersection 2 +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of B::foo(): TestOne|TestTwo must be compatible with A::foo(): X&Z in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 7e9ddde901b01..5c0fd05b5a966 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -513,6 +513,56 @@ static inheritance_status zend_perform_covariant_class_type_check( return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; } +/* checks that the child type (being unique) is a subtype of each member of the parent intersection */ +static inheritance_status zend_is_single_type_subtype_intersection( + zend_class_entry *fe_scope, zend_string *fe_class_name, + zend_class_entry *fe_ce, zend_class_entry *proto_scope, + zend_type proto_type, bool register_unresolved +) { + bool have_unresolved = false; + zend_type *single_type; + zend_type_list *parent_intersection_types; + + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(proto_type)); + + parent_intersection_types = ZEND_TYPE_LIST(proto_type); + + ZEND_TYPE_LIST_FOREACH(parent_intersection_types, single_type) { + zend_class_entry *proto_ce; + zend_string *proto_class_name = NULL; + if (ZEND_TYPE_HAS_NAME(*single_type)) { + proto_class_name = + resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); + if (zend_string_equals_ci(fe_class_name, proto_class_name)) { + continue; + } + + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); + proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); + proto_ce = ZEND_TYPE_CE(*single_type); + } else { + /* standard type cannot be part a subtype of an intersection type */ + ZEND_ASSERT(0 && "This shouldn't happen yet"); + continue; + } + + if (!fe_ce || !proto_ce) { + have_unresolved = true; + continue; + } + if (!unlinked_instanceof(fe_ce, proto_ce)) { + return INHERITANCE_ERROR; + } + + track_class_dependency(fe_ce, fe_class_name); + track_class_dependency(proto_ce, proto_class_name); + } ZEND_TYPE_LIST_FOREACH_END(); + + return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_SUCCESS; +} + static void register_unresolved_classes(zend_class_entry *scope, zend_type type) { zend_type *single_type; ZEND_TYPE_FOREACH(type, single_type) { @@ -575,7 +625,9 @@ static inheritance_status zend_perform_covariant_type_check( /* For intersection types loop over the parent types first as a child * can add them */ - if (ZEND_TYPE_IS_INTERSECTION(proto_type) || ZEND_TYPE_IS_INTERSECTION(fe_type)) { + if (ZEND_TYPE_IS_INTERSECTION(fe_type) + || (ZEND_TYPE_IS_INTERSECTION(proto_type) && !ZEND_TYPE_IS_UNION(fe_type)) + ) { /* First try to check whether we can succeed without resolving anything */ ZEND_TYPE_FOREACH(proto_type, single_type) { inheritance_status status; @@ -605,6 +657,39 @@ static inheritance_status zend_perform_covariant_type_check( all_success = false; } } ZEND_TYPE_FOREACH_END(); + } else if (ZEND_TYPE_IS_INTERSECTION(proto_type) && ZEND_TYPE_IS_UNION(fe_type)) { + /* Here each member of the child union must be a subtype of the intersection */ + + /* First try to check whether we can succeed without resolving anything */ + zend_type_list *child_union_types = ZEND_TYPE_LIST(fe_type); + + ZEND_TYPE_LIST_FOREACH(child_union_types, single_type) { + inheritance_status status; + zend_string *fe_class_name; + zend_class_entry *fe_ce = NULL; + + if (ZEND_TYPE_HAS_NAME(*single_type)) { + fe_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); + } else if (ZEND_TYPE_HAS_CE(*single_type)) { + fe_ce = ZEND_TYPE_CE(*single_type); + fe_class_name = fe_ce->name; + } else { + /* standard type */ + ZEND_ASSERT(0 && "This shouldn't happen yet"); + continue; + } + + status = zend_is_single_type_subtype_intersection(fe_scope, + fe_class_name, fe_ce, proto_scope, proto_type, + /* register_unresolved */ false); + + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; + } + if (status != INHERITANCE_SUCCESS) { + all_success = false; + } + } ZEND_TYPE_LIST_FOREACH_END(); } /* Only union or single types both in parent and child */ else { From 062d557dd42f5cac439b6c42f2369775c71cb963 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sat, 24 Apr 2021 02:18:32 +0100 Subject: [PATCH 04/40] Reorder execution and expand comment --- Zend/zend_inheritance.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 5c0fd05b5a966..eee9bf3dd5ab2 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -623,11 +623,11 @@ static inheritance_status zend_perform_covariant_type_check( zend_type *single_type; bool all_success = true; - /* For intersection types loop over the parent types first as a child - * can add them */ - if (ZEND_TYPE_IS_INTERSECTION(fe_type) - || (ZEND_TYPE_IS_INTERSECTION(proto_type) && !ZEND_TYPE_IS_UNION(fe_type)) - ) { + /* If the child type is an intersection type then we need to loop over + * the parents firstFor intersection types loop over the parent types first + * as the child can add types, however none of them can be a supertype of + * a parent type. */ + if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { /* First try to check whether we can succeed without resolving anything */ ZEND_TYPE_FOREACH(proto_type, single_type) { inheritance_status status; @@ -657,13 +657,12 @@ static inheritance_status zend_perform_covariant_type_check( all_success = false; } } ZEND_TYPE_FOREACH_END(); - } else if (ZEND_TYPE_IS_INTERSECTION(proto_type) && ZEND_TYPE_IS_UNION(fe_type)) { - /* Here each member of the child union must be a subtype of the intersection */ + } else if (ZEND_TYPE_IS_INTERSECTION(proto_type)) { + /* Here each member of the child union must be a subtype of the intersection + * Note: the union can be a single element */ /* First try to check whether we can succeed without resolving anything */ - zend_type_list *child_union_types = ZEND_TYPE_LIST(fe_type); - - ZEND_TYPE_LIST_FOREACH(child_union_types, single_type) { + ZEND_TYPE_FOREACH(fe_type, single_type) { inheritance_status status; zend_string *fe_class_name; zend_class_entry *fe_ce = NULL; @@ -689,7 +688,7 @@ static inheritance_status zend_perform_covariant_type_check( if (status != INHERITANCE_SUCCESS) { all_success = false; } - } ZEND_TYPE_LIST_FOREACH_END(); + } ZEND_TYPE_FOREACH_END(); } /* Only union or single types both in parent and child */ else { From 49f53a949b16a17531f633373618688e97c65734 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sat, 24 Apr 2021 02:39:34 +0100 Subject: [PATCH 05/40] Collapse branches together --- Zend/zend_inheritance.c | 43 ++++++++--------------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index eee9bf3dd5ab2..bad4050000068 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -657,10 +657,7 @@ static inheritance_status zend_perform_covariant_type_check( all_success = false; } } ZEND_TYPE_FOREACH_END(); - } else if (ZEND_TYPE_IS_INTERSECTION(proto_type)) { - /* Here each member of the child union must be a subtype of the intersection - * Note: the union can be a single element */ - + } else { /* First try to check whether we can succeed without resolving anything */ ZEND_TYPE_FOREACH(fe_type, single_type) { inheritance_status status; @@ -668,46 +665,22 @@ static inheritance_status zend_perform_covariant_type_check( zend_class_entry *fe_ce = NULL; if (ZEND_TYPE_HAS_NAME(*single_type)) { - fe_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); + fe_class_name = resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); } else if (ZEND_TYPE_HAS_CE(*single_type)) { fe_ce = ZEND_TYPE_CE(*single_type); fe_class_name = fe_ce->name; } else { - /* standard type */ - ZEND_ASSERT(0 && "This shouldn't happen yet"); continue; } - status = zend_is_single_type_subtype_intersection(fe_scope, + if (UNEXPECTED(ZEND_TYPE_IS_INTERSECTION(proto_type))) { + status = zend_is_single_type_subtype_intersection(fe_scope, + fe_class_name, fe_ce, proto_scope, proto_type, + /* register_unresolved */ false); + } else { + status = zend_perform_covariant_class_type_check(fe_scope, fe_class_name, fe_ce, proto_scope, proto_type, /* register_unresolved */ false); - - if (status == INHERITANCE_ERROR) { - return INHERITANCE_ERROR; - } - if (status != INHERITANCE_SUCCESS) { - all_success = false; - } - } ZEND_TYPE_FOREACH_END(); - } - /* Only union or single types both in parent and child */ - else { - /* First try to check whether we can succeed without resolving anything */ - ZEND_TYPE_FOREACH(fe_type, single_type) { - inheritance_status status; - if (ZEND_TYPE_HAS_NAME(*single_type)) { - zend_string *fe_class_name = - resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); - status = zend_perform_covariant_class_type_check( - fe_scope, fe_class_name, NULL, - proto_scope, proto_type, /* register_unresolved */ 0); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - zend_class_entry *fe_ce = ZEND_TYPE_CE(*single_type); - status = zend_perform_covariant_class_type_check( - fe_scope, fe_ce->name, fe_ce, - proto_scope, proto_type, /* register_unresolved */ 0); - } else { - continue; } if (status == INHERITANCE_ERROR) { From f98f402985e0b62db76cdcf02af531f9954ce28c Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 4 May 2021 19:15:35 +0100 Subject: [PATCH 06/40] Fix Reflection test after rebase (Fiber) --- .../tests/ReflectionExtension_getClasses_basic.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt index 63bd4ef0265ea..b7c10079d4dd3 100644 --- a/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt +++ b/ext/reflection/tests/ReflectionExtension_getClasses_basic.phpt @@ -8,7 +8,7 @@ $ext = new ReflectionExtension('reflection'); var_dump($ext->getClasses()); ?> --EXPECT-- -array(23) { +array(24) { ["ReflectionException"]=> object(ReflectionClass)#2 (1) { ["name"]=> @@ -125,7 +125,7 @@ array(23) { string(24) "ReflectionEnumBackedCase" } ["ReflectionFiber"]=> - object(ReflectionClass)#24 (1) { + object(ReflectionClass)#25 (1) { ["name"]=> string(15) "ReflectionFiber" } From 0824c47022a3a4d4f995dbaf1a611920acdb616b Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 18 May 2021 15:51:55 +0100 Subject: [PATCH 07/40] Fix after review --- Zend/zend_inheritance.c | 12 ++++++------ ext/reflection/php_reflection.c | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index bad4050000068..bec3cc42380b8 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -411,8 +411,8 @@ static inheritance_status zend_perform_intersection_covariant_class_type_check( bool have_unresolved = false; zend_type *single_type; - /* Traverse the list of child types and check if it is either a subtype - * of one of the parent types OR is not in the parent type list */ + /* Traverse the list of child types and check that at least one is + * a subtype of the parent type being checked */ ZEND_TYPE_FOREACH(fe_type, single_type) { zend_class_entry *fe_ce; zend_string *fe_class_name = NULL; @@ -431,7 +431,7 @@ static inheritance_status zend_perform_intersection_covariant_class_type_check( fe_ce = ZEND_TYPE_CE(*single_type); } else { /* standard type */ - ZEND_ASSERT(0 && "This shouldn't happen yet"); + ZEND_UNREACHABLE(); continue; } @@ -544,7 +544,7 @@ static inheritance_status zend_is_single_type_subtype_intersection( proto_ce = ZEND_TYPE_CE(*single_type); } else { /* standard type cannot be part a subtype of an intersection type */ - ZEND_ASSERT(0 && "This shouldn't happen yet"); + ZEND_UNREACHABLE(); continue; } @@ -624,7 +624,7 @@ static inheritance_status zend_perform_covariant_type_check( bool all_success = true; /* If the child type is an intersection type then we need to loop over - * the parents firstFor intersection types loop over the parent types first + * the parents first. For intersection types, loop over the parent types first * as the child can add types, however none of them can be a supertype of * a parent type. */ if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { @@ -641,7 +641,7 @@ static inheritance_status zend_perform_covariant_type_check( proto_class_name = proto_ce->name; } else { /* standard type */ - ZEND_ASSERT(0 && "This shouldn't happen yet"); + ZEND_UNREACHABLE(); continue; } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 90b76addcbc2f..f76b926f1941a 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -1320,7 +1320,7 @@ static void reflection_parameter_factory(zend_function *fptr, zval *closure_obje } /* }}} */ -typedef enum reflection_type_kind { +typedef enum { NAMED_TYPE = 0, UNION_TYPE = 1, INTERSECTION_TYPE = 2 From af47afcfaf57a16d2fb34b5bfc01d8fbf2ef10fe Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Sat, 29 May 2021 05:32:43 +0100 Subject: [PATCH 08/40] Alias T_AMPERSAND --- ext/tokenizer/tokenizer_data.c | 3 ++- ext/tokenizer/tokenizer_data_gen.php | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a79112924f0c9..f8ad59c13c967 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -171,6 +171,7 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_AMPERSAND", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); } char *get_token_type_name(int token_type) @@ -320,7 +321,7 @@ char *get_token_type_name(int token_type) case T_POW: return "T_POW"; case T_POW_EQUAL: return "T_POW_EQUAL"; case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; - case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; + case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; } diff --git a/ext/tokenizer/tokenizer_data_gen.php b/ext/tokenizer/tokenizer_data_gen.php index b9659e9f19abc..7ba814414b72d 100644 --- a/ext/tokenizer/tokenizer_data_gen.php +++ b/ext/tokenizer/tokenizer_data_gen.php @@ -58,6 +58,7 @@ $result .= "\tREGISTER_LONG_CONSTANT(\"$tokenName\", $tokenName, CONST_CS | CONST_PERSISTENT);\n"; } $result .= "\tREGISTER_LONG_CONSTANT(\"T_DOUBLE_COLON\", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT);\n"; +$result .= "\tREGISTER_LONG_CONSTANT(\"T_AMPERSAND\", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT);\n"; $result .= << Date: Tue, 1 Jun 2021 19:55:34 +0100 Subject: [PATCH 09/40] Revert "Alias T_AMPERSAND" This reverts commit bad3cab4afc34ba3117c0cea70dc3c8db37209ab. --- ext/tokenizer/tokenizer_data.c | 3 +-- ext/tokenizer/tokenizer_data_gen.php | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index f8ad59c13c967..a79112924f0c9 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -171,7 +171,6 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_AMPERSAND", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); } char *get_token_type_name(int token_type) @@ -321,7 +320,7 @@ char *get_token_type_name(int token_type) case T_POW: return "T_POW"; case T_POW_EQUAL: return "T_POW_EQUAL"; case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; - case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND"; + case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; } diff --git a/ext/tokenizer/tokenizer_data_gen.php b/ext/tokenizer/tokenizer_data_gen.php index 7ba814414b72d..b9659e9f19abc 100644 --- a/ext/tokenizer/tokenizer_data_gen.php +++ b/ext/tokenizer/tokenizer_data_gen.php @@ -58,7 +58,6 @@ $result .= "\tREGISTER_LONG_CONSTANT(\"$tokenName\", $tokenName, CONST_CS | CONST_PERSISTENT);\n"; } $result .= "\tREGISTER_LONG_CONSTANT(\"T_DOUBLE_COLON\", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT);\n"; -$result .= "\tREGISTER_LONG_CONSTANT(\"T_AMPERSAND\", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT);\n"; $result .= << Date: Tue, 1 Jun 2021 20:36:53 +0100 Subject: [PATCH 10/40] Fix an additional variance bug --- .../intersection_types/variance/valid6.phpt | 20 ++++++++++++ Zend/zend_inheritance.c | 32 +++++++++++++++---- 2 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid6.phpt diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid6.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid6.phpt new file mode 100644 index 0000000000000..bb6b50badcc62 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid6.phpt @@ -0,0 +1,20 @@ +--TEST-- +Replacing union type by intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index bec3cc42380b8..be4c1b8930d52 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -628,6 +628,7 @@ static inheritance_status zend_perform_covariant_type_check( * as the child can add types, however none of them can be a supertype of * a parent type. */ if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { + bool parent_union_has_unresolved = false; /* First try to check whether we can succeed without resolving anything */ ZEND_TYPE_FOREACH(proto_type, single_type) { inheritance_status status; @@ -649,14 +650,33 @@ static inheritance_status zend_perform_covariant_type_check( proto_scope, proto_class_name, proto_ce, fe_scope, fe_type, /* register_unresolved */ false); - if (status == INHERITANCE_ERROR) { - return NHERITANCE_ERROR; - } - if (status != INHERITANCE_SUCCESS) { - ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); - all_success = false; + /* If the parent is a union type then the intersection type must only be + * a subtype of one of them */ + if (ZEND_TYPE_IS_UNION(proto_type)) { + if (status == INHERITANCE_SUCCESS) { + return INHERITANCE_SUCCESS; + } + if (status == INHERITANCE_UNRESOLVED) { + all_success = false; + } + } else { + if (status == INHERITANCE_ERROR) { + return INHERITANCE_ERROR; + } + if (status != INHERITANCE_SUCCESS) { + ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); + parent_union_has_unresolved = true; + all_success = false; + } } } ZEND_TYPE_FOREACH_END(); + + /* Reaching this means either the child intersection type is a valid/unresolved + * subtype of a parent single/intersection type, either it is an INvalid subtype + * when the parent is a union or it is unresolved, we check which case this is */ + if (ZEND_TYPE_IS_UNION(proto_type) && !parent_union_has_unresolved) { + return tentative ? INHERITANCE_WARNING : INHERITANCE_ERROR; + } } else { /* First try to check whether we can succeed without resolving anything */ ZEND_TYPE_FOREACH(fe_type, single_type) { From 7c4f70c8f4d93470f3ac21a6eb6e762bc275311a Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 1 Jun 2021 20:40:10 +0100 Subject: [PATCH 11/40] Address review --- Zend/zend_execute.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 1ae1c11c8abee..a946e81d0dcee 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1030,10 +1030,6 @@ static zend_always_inline bool zend_check_type_slow( if (!ce) { ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - /* Cannot resolve */ return false; } @@ -1042,10 +1038,6 @@ static zend_always_inline bool zend_check_type_slow( ce = zend_fetch_class(name, ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - /* Cannot resolve */ return false; } @@ -1053,7 +1045,8 @@ static zend_always_inline bool zend_check_type_slow( } /* Perform actual type check */ - /* Instance of a single type part of a union is sufficient to pass the type check */ + /* If type is not an instance of one of the types taking part in the + * intersection it cannot be a valid instance of the whole intersection type. */ if (!instanceof_function(Z_OBJCE_P(arg), ce)) { return false; } From 93ca487939b8e4c55fdcdfc3a3b57b64e647832f Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Tue, 1 Jun 2021 21:01:38 +0100 Subject: [PATCH 12/40] Allow self and parent to be part of an intersection type --- .../invalid_types/invalid_parent_type.phpt | 14 ----- .../invalid_types/invalid_self_type.phpt | 12 ---- .../duplicate_parent_type.phpt | 14 +++++ .../redundant_types/duplicate_self_type.phpt | 13 +++++ .../self_parent_in_intersection.phpt | 57 +++++++++++++++++++ Zend/zend_compile.c | 11 +--- 6 files changed, 87 insertions(+), 34 deletions(-) delete mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt delete mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt deleted file mode 100644 index e3a86771a2189..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -parent type cannot take part in an intersection type ---FILE-- - ---EXPECTF-- -Fatal error: Type parent cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt deleted file mode 100644 index 66c7ec79325dc..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -self type cannot take part in an intersection type ---FILE-- - ---EXPECTF-- -Fatal error: Type self cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt new file mode 100644 index 0000000000000..f24cc7677595a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt @@ -0,0 +1,14 @@ +--TEST-- +Duplicate parent type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type parent is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt new file mode 100644 index 0000000000000..5db36b38e998b --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt @@ -0,0 +1,13 @@ +--TEST-- +Duplicate self type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type self is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt b/Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt new file mode 100644 index 0000000000000..f6c14f84a0986 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt @@ -0,0 +1,57 @@ +--TEST-- +Using self/parent/static part of an intersection +--FILE-- +test()); +try { + var_dump($be->test()); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +// Parent tests +var_dump($c->parentIntersection()); +try { + var_dump($d->parentIntersection()); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +object(B)#1 (0) { +} +A::test(): Return value must be of type A&X, BError returned +object(B)#6 (0) { +} +D::parentIntersection(): Return value must be of type B&X, A returned diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 14cbcbb525acd..9f6d2e6791f8b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6300,6 +6300,7 @@ static zend_type zend_compile_typename( zend_ast_list *list = zend_ast_get_list(ast); zend_type_list *type_list; + // TODO Is this still true if self/parent are accepted? /* Allocate the type list directly on the arena as it must be a type * list of the same number of elements as the AST list has children */ type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children)); @@ -6310,20 +6311,14 @@ static zend_type zend_compile_typename( 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); - zend_string *standard_type_str = zend_type_to_string(single_type); /* An intersection of standard types cannot exist so invalidate it */ if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { + zend_string *standard_type_str = zend_type_to_string(single_type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + zend_string_release_ex(standard_type_str, false); } - /* Check for "self" and "parent" too */ - if (zend_string_equals_literal_ci(standard_type_str, "self") - || zend_string_equals_literal_ci(standard_type_str, "parent")) { - zend_error_noreturn(E_COMPILE_ERROR, - "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); - } - zend_string_release_ex(standard_type_str, false); /* Add type to the type list */ type_list->types[type_list->num_types++] = single_type; From cff68c98d28c29d1e465685bd5f5240fe5901ca6 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 2 Jun 2021 01:23:22 +0100 Subject: [PATCH 13/40] Support static type in intersection Probably should not use the arena directly anymore Also variance tests need to be done --- .../invalid_types/invalid_static_type.phpt | 12 ------ .../duplicate_static_type.phpt | 13 ++++++ .../static_in_intersection.phpt | 41 +++++++++++++++++++ Zend/zend_compile.c | 23 +++++++++-- Zend/zend_execute.c | 5 +++ 5 files changed, 78 insertions(+), 16 deletions(-) delete mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt deleted file mode 100644 index 7d2f44f572ee1..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt +++ /dev/null @@ -1,12 +0,0 @@ ---TEST-- -static type cannot take part in an intersection type ---FILE-- - ---EXPECTF-- -Fatal error: Type static cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt new file mode 100644 index 0000000000000..2bb7926c1ce27 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt @@ -0,0 +1,13 @@ +--TEST-- +Duplicate static type +--FILE-- + +--EXPECTF-- +Fatal error: Duplicate type static is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt b/Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt new file mode 100644 index 0000000000000..a7f298f6b07f7 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt @@ -0,0 +1,41 @@ +--TEST-- +Using self/parent/static part of an intersection +--FILE-- +test()); +try { + var_dump($ce->test()); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +object(C)#3 (0) { +} +B::test(): Return value must be of type X&CError, B returned diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9f6d2e6791f8b..af493539e5517 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1247,7 +1247,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop name = called_scope->name; } } - str = add_type_string(str, name, /* is_intersection */ false); + str = add_type_string(str, name, ZEND_TYPE_IS_INTERSECTION(type)); } if (type_mask & MAY_BE_CALLABLE) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false); @@ -6299,8 +6299,9 @@ static zend_type zend_compile_typename( } else if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { zend_ast_list *list = zend_ast_get_list(ast); zend_type_list *type_list; + unsigned int has_static = 0; - // TODO Is this still true if self/parent are accepted? + // TODO static means that one slot may be unused /* Allocate the type list directly on the arena as it must be a type * list of the same number of elements as the AST list has children */ type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children)); @@ -6313,13 +6314,27 @@ static zend_type zend_compile_typename( zend_type single_type = zend_compile_single_typename(type_ast); /* An intersection of standard types cannot exist so invalidate it */ - if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { + /* Treat "static" as a class type. */ + if (ZEND_TYPE_IS_ONLY_MASK(single_type) && !(ZEND_TYPE_FULL_MASK(single_type) & MAY_BE_STATIC)) { zend_string *standard_type_str = zend_type_to_string(single_type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); zend_string_release_ex(standard_type_str, false); } + /* If the type is static */ + if (UNEXPECTED(!ZEND_TYPE_IS_COMPLEX(single_type))) { + /* Check that static doesn't overlap */ + uint32_t type_mask_overlap = ZEND_TYPE_PURE_MASK(type) & MAY_BE_STATIC; + if (type_mask_overlap) { + zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type static is redundant"); + } + ZEND_TYPE_FULL_MASK(type) |= MAY_BE_STATIC; + ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; + has_static = 1; + continue; + } + /* Add type to the type list */ type_list->types[type_list->num_types++] = single_type; @@ -6334,7 +6349,7 @@ static zend_type zend_compile_typename( } } - ZEND_ASSERT(list->children == type_list->num_types); + ZEND_ASSERT((list->children - has_static) == type_list->num_types); ZEND_TYPE_SET_LIST(type, type_list); ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index a946e81d0dcee..41d68eb54436b 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -845,6 +845,7 @@ static bool zend_check_and_resolve_property_class_type( zend_type *list_type; if (ZEND_TYPE_IS_INTERSECTION(info->type)) { + /* Property class can't have static as a type so need to check it here */ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { if (ZEND_TYPE_HAS_NAME(*list_type)) { zend_string *name = ZEND_TYPE_NAME(*list_type); @@ -1019,6 +1020,10 @@ static zend_always_inline bool zend_check_type_slow( if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { zend_type *list_type; if (ZEND_TYPE_IS_INTERSECTION(*type)) { + /* Check if intersection has static inside it */ + if ((ZEND_TYPE_FULL_MASK(*type) & MAY_BE_STATIC) && !zend_value_instanceof_static(arg)) { + return false; + } ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { if (HAVE_CACHE_SLOT && *cache_slot) { ce = *cache_slot; From b541b24040bf0b7967d4048f69c8feda6927a2c4 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 2 Jun 2021 13:21:01 +0100 Subject: [PATCH 14/40] Revert "Support static type in intersection" Let's not, this points more to a design bug then anything else This reverts commit 611f0e72ef66a6fbd3f438542e50847817f96bfc. --- .../invalid_types/invalid_static_type.phpt | 12 ++++++ .../duplicate_static_type.phpt | 13 ------ .../static_in_intersection.phpt | 41 ------------------- Zend/zend_compile.c | 23 ++--------- Zend/zend_execute.c | 5 --- 5 files changed, 16 insertions(+), 78 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt delete mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt delete mode 100644 Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt new file mode 100644 index 0000000000000..7d2f44f572ee1 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_static_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +static type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type static cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt deleted file mode 100644 index 2bb7926c1ce27..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_static_type.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Duplicate static type ---FILE-- - ---EXPECTF-- -Fatal error: Duplicate type static is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt b/Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt deleted file mode 100644 index a7f298f6b07f7..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/static_in_intersection.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -Using self/parent/static part of an intersection ---FILE-- -test()); -try { - var_dump($ce->test()); -} catch (\TypeError $e) { - echo $e->getMessage(), PHP_EOL; -} - -?> ---EXPECT-- -object(C)#3 (0) { -} -B::test(): Return value must be of type X&CError, B returned diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index af493539e5517..9f6d2e6791f8b 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1247,7 +1247,7 @@ zend_string *zend_type_to_string_resolved(zend_type type, zend_class_entry *scop name = called_scope->name; } } - str = add_type_string(str, name, ZEND_TYPE_IS_INTERSECTION(type)); + str = add_type_string(str, name, /* is_intersection */ false); } if (type_mask & MAY_BE_CALLABLE) { str = add_type_string(str, ZSTR_KNOWN(ZEND_STR_CALLABLE), /* is_intersection */ false); @@ -6299,9 +6299,8 @@ static zend_type zend_compile_typename( } else if (ast->kind == ZEND_AST_TYPE_INTERSECTION) { zend_ast_list *list = zend_ast_get_list(ast); zend_type_list *type_list; - unsigned int has_static = 0; - // TODO static means that one slot may be unused + // TODO Is this still true if self/parent are accepted? /* Allocate the type list directly on the arena as it must be a type * list of the same number of elements as the AST list has children */ type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children)); @@ -6314,27 +6313,13 @@ static zend_type zend_compile_typename( zend_type single_type = zend_compile_single_typename(type_ast); /* An intersection of standard types cannot exist so invalidate it */ - /* Treat "static" as a class type. */ - if (ZEND_TYPE_IS_ONLY_MASK(single_type) && !(ZEND_TYPE_FULL_MASK(single_type) & MAY_BE_STATIC)) { + if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { zend_string *standard_type_str = zend_type_to_string(single_type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); zend_string_release_ex(standard_type_str, false); } - /* If the type is static */ - if (UNEXPECTED(!ZEND_TYPE_IS_COMPLEX(single_type))) { - /* Check that static doesn't overlap */ - uint32_t type_mask_overlap = ZEND_TYPE_PURE_MASK(type) & MAY_BE_STATIC; - if (type_mask_overlap) { - zend_error_noreturn(E_COMPILE_ERROR, "Duplicate type static is redundant"); - } - ZEND_TYPE_FULL_MASK(type) |= MAY_BE_STATIC; - ZEND_TYPE_FULL_MASK(single_type) &= ~_ZEND_TYPE_MAY_BE_MASK; - has_static = 1; - continue; - } - /* Add type to the type list */ type_list->types[type_list->num_types++] = single_type; @@ -6349,7 +6334,7 @@ static zend_type zend_compile_typename( } } - ZEND_ASSERT((list->children - has_static) == type_list->num_types); + ZEND_ASSERT(list->children == type_list->num_types); ZEND_TYPE_SET_LIST(type, type_list); ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_ARENA_BIT; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 41d68eb54436b..a946e81d0dcee 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -845,7 +845,6 @@ static bool zend_check_and_resolve_property_class_type( zend_type *list_type; if (ZEND_TYPE_IS_INTERSECTION(info->type)) { - /* Property class can't have static as a type so need to check it here */ ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { if (ZEND_TYPE_HAS_NAME(*list_type)) { zend_string *name = ZEND_TYPE_NAME(*list_type); @@ -1020,10 +1019,6 @@ static zend_always_inline bool zend_check_type_slow( if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) { zend_type *list_type; if (ZEND_TYPE_IS_INTERSECTION(*type)) { - /* Check if intersection has static inside it */ - if ((ZEND_TYPE_FULL_MASK(*type) & MAY_BE_STATIC) && !zend_value_instanceof_static(arg)) { - return false; - } ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { if (HAVE_CACHE_SLOT && *cache_slot) { ce = *cache_slot; From 020b586a2e8578a731b5225f46baa422e2dde8fb Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 2 Jun 2021 13:21:31 +0100 Subject: [PATCH 15/40] Revert "Allow self and parent to be part of an intersection type" Let's not accept those either This reverts commit 386b104335c7205cb93ad18e40b27b1c0bd9fdc6. --- .../invalid_types/invalid_parent_type.phpt | 14 +++++ .../invalid_types/invalid_self_type.phpt | 12 ++++ .../duplicate_parent_type.phpt | 14 ----- .../redundant_types/duplicate_self_type.phpt | 13 ----- .../self_parent_in_intersection.phpt | 57 ------------------- Zend/zend_compile.c | 11 +++- 6 files changed, 34 insertions(+), 87 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt delete mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt delete mode 100644 Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt delete mode 100644 Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt new file mode 100644 index 0000000000000..e3a86771a2189 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_parent_type.phpt @@ -0,0 +1,14 @@ +--TEST-- +parent type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type parent cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt new file mode 100644 index 0000000000000..66c7ec79325dc --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_self_type.phpt @@ -0,0 +1,12 @@ +--TEST-- +self type cannot take part in an intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Type self cannot be part of an intersection type in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt deleted file mode 100644 index f24cc7677595a..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_parent_type.phpt +++ /dev/null @@ -1,14 +0,0 @@ ---TEST-- -Duplicate parent type ---FILE-- - ---EXPECTF-- -Fatal error: Duplicate type parent is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt b/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt deleted file mode 100644 index 5db36b38e998b..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/redundant_types/duplicate_self_type.phpt +++ /dev/null @@ -1,13 +0,0 @@ ---TEST-- -Duplicate self type ---FILE-- - ---EXPECTF-- -Fatal error: Duplicate type self is redundant in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt b/Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt deleted file mode 100644 index f6c14f84a0986..0000000000000 --- a/Zend/tests/type_declarations/intersection_types/self_parent_in_intersection.phpt +++ /dev/null @@ -1,57 +0,0 @@ ---TEST-- -Using self/parent/static part of an intersection ---FILE-- -test()); -try { - var_dump($be->test()); -} catch (\TypeError $e) { - echo $e->getMessage(), PHP_EOL; -} - -// Parent tests -var_dump($c->parentIntersection()); -try { - var_dump($d->parentIntersection()); -} catch (\TypeError $e) { - echo $e->getMessage(), PHP_EOL; -} - -?> ---EXPECT-- -object(B)#1 (0) { -} -A::test(): Return value must be of type A&X, BError returned -object(B)#6 (0) { -} -D::parentIntersection(): Return value must be of type B&X, A returned diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 9f6d2e6791f8b..14cbcbb525acd 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6300,7 +6300,6 @@ static zend_type zend_compile_typename( zend_ast_list *list = zend_ast_get_list(ast); zend_type_list *type_list; - // TODO Is this still true if self/parent are accepted? /* Allocate the type list directly on the arena as it must be a type * list of the same number of elements as the AST list has children */ type_list = zend_arena_alloc(&CG(arena), ZEND_TYPE_LIST_SIZE(list->children)); @@ -6311,14 +6310,20 @@ static zend_type zend_compile_typename( 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); + zend_string *standard_type_str = zend_type_to_string(single_type); /* An intersection of standard types cannot exist so invalidate it */ if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { - zend_string *standard_type_str = zend_type_to_string(single_type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); - zend_string_release_ex(standard_type_str, false); } + /* Check for "self" and "parent" too */ + if (zend_string_equals_literal_ci(standard_type_str, "self") + || zend_string_equals_literal_ci(standard_type_str, "parent")) { + zend_error_noreturn(E_COMPILE_ERROR, + "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + } + zend_string_release_ex(standard_type_str, false); /* Add type to the type list */ type_list->types[type_list->num_types++] = single_type; From b0c751957cae5aedeb7b8e68c6d850efadb9a79a Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 9 Jun 2021 19:24:13 +0100 Subject: [PATCH 16/40] Move common code into always inlined functions --- Zend/zend_execute.c | 224 +++++++++-------------------- Zend/zend_execute.h | 39 +++++ ext/opcache/jit/zend_jit_helpers.c | 83 +++-------- 3 files changed, 126 insertions(+), 220 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index a946e81d0dcee..0260060702e71 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -836,8 +836,39 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class } } -// TODO Handle complex types when added -// TODO Update to handle union and intersection in the same loop +// TODO better name +static zend_always_inline zend_class_entry* zend_resolve_ce( + zend_property_info *info, zend_type *type) { + zend_class_entry *ce; + if (UNEXPECTED(ZEND_TYPE_HAS_NAME(*type))) { + zend_string *name = ZEND_TYPE_NAME(*type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (UNEXPECTED(!ce)) { + /* Cannot resolve */ + return NULL; + } + } + } else { + ce = resolve_single_class_type(name, info->ce); + if (!ce) { + /* Cannot resolve */ + return NULL; + } + if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_string_release(name); + ZEND_TYPE_SET_CE(*type, ce); + } + } + } else { + ce = ZEND_TYPE_CE(*type); + } + return ce; +} + static bool zend_check_and_resolve_property_class_type( zend_property_info *info, zend_class_entry *object_ce) { zend_class_entry *ce; @@ -846,32 +877,14 @@ static bool zend_check_and_resolve_property_class_type( if (ZEND_TYPE_IS_INTERSECTION(info->type)) { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { - if (ZEND_TYPE_HAS_NAME(*list_type)) { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - /* Cannot resolve */ - return false; - } - } - } else { - ce = resolve_single_class_type(name, info->ce); - if (!ce) { - /* Cannot resolve */ - return false; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(*list_type, ce); - } - } - } else { - ce = ZEND_TYPE_CE(*list_type); + ce = zend_resolve_ce(info, list_type); + + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, fail. */ + if (ce == NULL) { + return false; } + if (!instanceof_function(object_ce, ce)) { return false; } @@ -879,29 +892,12 @@ static bool zend_check_and_resolve_property_class_type( return true; } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { - if (ZEND_TYPE_HAS_NAME(*list_type)) { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - continue; - } - } - } else { - ce = resolve_single_class_type(name, info->ce); - if (!ce) { - continue; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(*list_type, ce); - } - } - } else { - ce = ZEND_TYPE_CE(*list_type); + ce = zend_resolve_ce(info, list_type); + + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, check the next one. */ + if (ce == NULL) { + continue; } if (instanceof_function(object_ce, ce)) { return true; @@ -910,30 +906,14 @@ static bool zend_check_and_resolve_property_class_type( return false; } } else { - if (UNEXPECTED(ZEND_TYPE_HAS_NAME(info->type))) { - zend_string *name = ZEND_TYPE_NAME(info->type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - return 0; - } - } - } else { - ce = resolve_single_class_type(name, info->ce); - if (UNEXPECTED(!ce)) { - return 0; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(info->type, ce); - } - } - } else { - ce = ZEND_TYPE_CE(info->type); + ce = zend_resolve_ce(info, &info->type); + + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, fail. */ + if (ce == NULL) { + return false; } + return instanceof_function(object_ce, ce); } } @@ -999,16 +979,6 @@ ZEND_API bool zend_value_instanceof_static(zval *zv) { return instanceof_function(Z_OBJCE_P(zv), called_scope); } -/* The cache_slot may only be NULL in debug builds, where arginfo verification of - * internal functions is enabled. Avoid unnecessary checks in release builds. */ -#if ZEND_DEBUG -# define HAVE_CACHE_SLOT (cache_slot != NULL) -#else -# define HAVE_CACHE_SLOT 1 -#endif - -// TODO Handle complex types when added -// TODO Update to handle union and intersection in the same loop static zend_always_inline bool zend_check_type_slow( zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal) @@ -1020,28 +990,11 @@ static zend_always_inline bool zend_check_type_slow( zend_type *list_type; if (ZEND_TYPE_IS_INTERSECTION(*type)) { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (HAVE_CACHE_SLOT && *cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!ce) { - /* Cannot resolve */ - return false; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - /* Cannot resolve */ - return false; - } - } + ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, fail. */ + if (ce == NULL) { + return false; } /* Perform actual type check */ @@ -1057,32 +1010,14 @@ static zend_always_inline bool zend_check_type_slow( return true; } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { - if (HAVE_CACHE_SLOT && *cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - continue; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - continue; - } + ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, check the next one. */ + if (ce == NULL) { + if (HAVE_CACHE_SLOT) { + cache_slot++; } + continue; } /* Perform actual type check */ @@ -1096,30 +1031,13 @@ static zend_always_inline bool zend_check_type_slow( } ZEND_TYPE_LIST_FOREACH_END(); } } else { - if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { - ce = (zend_class_entry *) *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } - } - if (HAVE_CACHE_SLOT) { - *cache_slot = (void *) ce; - } + ce = zend_fetch_ce_from_cache_slot(cache_slot, type); + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, check if a standard type satisfies it. */ + if (ce == NULL) { + goto builtin_types; } + if (instanceof_function(Z_OBJCE_P(arg), ce)) { return 1; } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 13ef03d92fc0b..bab101b43f17b 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -459,6 +459,45 @@ ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *p } \ } while (0) +/* The cache_slot may only be NULL in debug builds, where arginfo verification of + * internal functions is enabled. Avoid unnecessary checks in release builds. */ +#if ZEND_DEBUG +# define HAVE_CACHE_SLOT (cache_slot != NULL) +#else +# define HAVE_CACHE_SLOT 1 +#endif + +static zend_always_inline zend_class_entry* zend_fetch_ce_from_cache_slot(void **cache_slot, zend_type *type) +{ + zend_class_entry *ce; + + if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { + ce = (zend_class_entry *) *cache_slot; + } else { + zend_string *name = ZEND_TYPE_NAME(*type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (UNEXPECTED(!ce)) { + /* Cannot resolve */ + return NULL; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (UNEXPECTED(!ce)) { + return NULL; + } + } + if (HAVE_CACHE_SLOT) { + *cache_slot = (void *) ce; + } + } + return ce; +} END_EXTERN_C() diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 28d36eaab2f53..8e5db440763f5 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1330,29 +1330,11 @@ static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_i zend_type *list_type; if (ZEND_TYPE_IS_INTERSECTION(arg_info->type)) { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { - if (*cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!ce) { - /* Cannot resolve */ - return false; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - /* Cannot resolve */ - return false; - } - } - *cache_slot = ce; + ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, fail. */ + if (ce == NULL) { + return false; } if (!instanceof_function(Z_OBJCE_P(arg), ce)) { return false; @@ -1362,29 +1344,12 @@ static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_i return true; } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { - if (*cache_slot) { - ce = *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*list_type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (!ce) { - cache_slot++; - continue; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (!ce) { - cache_slot++; - continue; - } - } - *cache_slot = ce; + ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, check the next one. */ + if (ce == NULL) { + cache_slot++; + continue; } if (instanceof_function(Z_OBJCE_P(arg), ce)) { return 1; @@ -1393,27 +1358,11 @@ static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_i } ZEND_TYPE_LIST_FOREACH_END(); } } else { - if (EXPECTED(*cache_slot)) { - ce = (zend_class_entry *) *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(arg_info->type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (UNEXPECTED(!ce)) { - goto builtin_types; - } - } - *cache_slot = (void *) ce; + ce = zend_fetch_ce_from_cache_slot(cache_slot, &arg_info->type); + /* If we cannot resolve the CE we cannot check if it satisfies + * the type constraint, check if a standard type satisfies it. */ + if (ce == NULL) { + goto builtin_types; } if (instanceof_function(Z_OBJCE_P(arg), ce)) { return 1; From 8ca34b305c3c622a090a4708d65e926ade9be104 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 9 Jun 2021 19:26:56 +0100 Subject: [PATCH 17/40] Comment nits --- Zend/zend_inheritance.c | 3 ++- Zend/zend_types.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index be4c1b8930d52..ee1e63f60d7d1 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -430,7 +430,8 @@ static inheritance_status zend_perform_intersection_covariant_class_type_check( if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); fe_ce = ZEND_TYPE_CE(*single_type); } else { - /* standard type */ + /* standard type in an intersection type is impossible, + * because it would be a fatal compile error */ ZEND_UNREACHABLE(); continue; } diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 29ea3f5e9c17e..b9d013fbc1979 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -109,7 +109,7 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * It shouldn't be used directly. Only through ZEND_TYPE_* macros. * * ZEND_TYPE_IS_SET() - checks if there is a type-hint - * ZEND_TYPE_IS_ONLY_MASK() - checks if type-hint refer to standard type only + * ZEND_TYPE_IS_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 * From b7c322e5c2f188c22672db8c6c9a24dade54303b Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Wed, 9 Jun 2021 19:28:55 +0100 Subject: [PATCH 18/40] Apply nit to reflection test --- ext/reflection/tests/intersection_types.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/reflection/tests/intersection_types.phpt b/ext/reflection/tests/intersection_types.phpt index 39557aaf1d796..9f091a8bc93fc 100644 --- a/ext/reflection/tests/intersection_types.phpt +++ b/ext/reflection/tests/intersection_types.phpt @@ -5,11 +5,11 @@ Intersection types in reflection function dumpType(ReflectionIntersectionType $rt) { echo "Type $rt:\n"; - echo "Allows null: " . ($rt->allowsNull() ? "true" : "false") . "\n"; + echo "Allows null: " . json_encode($rt->allowsNull()) . "\n"; foreach ($rt->getTypes() as $type) { echo " Name: " . $type->getName() . "\n"; echo " String: " . (string) $type . "\n"; - echo " Allows Null: " . ($type->allowsNull() ? "true" : "false") . "\n"; + echo " Allows Null: " . json_encode($type->allowsNull()) . "\n"; } } From 1bf0dd7b7036507ca9173c9e354f406e110e97a1 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 15:47:24 +0100 Subject: [PATCH 19/40] Adjust dfa pass --- Zend/Optimizer/dfa_pass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index fb1e3248700fb..4b9b9426ce556 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -317,7 +317,7 @@ static inline bool can_elide_return_type_check( zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type)); zend_class_entry *ce = zend_optimizer_get_class_entry(script, lcname); zend_string_release(lcname); - if (ce && safe_instanceof(use_info->ce, ce)) { + if (ce && ZEND_TYPE_IS_UNION(arg_info->type) && safe_instanceof(use_info->ce, ce)) { /* One of the class union types matched. */ return true; } From ac74551a62f54661ffd14b19fb9b8922b484a8d8 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 15:48:58 +0100 Subject: [PATCH 20/40] Drop unnecessary try/catch --- .../added_interface_intersection_type.phpt | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt index 3d7334ccd44ad..19ec4dae50919 100644 --- a/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/added_interface_intersection_type.phpt @@ -21,29 +21,15 @@ function bar(X&Y $o): void { var_dump($o); } -try { - $o = foo(); - var_dump($o); -} catch (\TypeError $e) { - echo $e->getMessage(), "\n"; -} +$o = foo(); +var_dump($o); $c = new Collection(); $a = new A(); -try { - $c->intersect = $a; - echo 'OK', \PHP_EOL; -} catch (\TypeError $e) { - echo $e->getMessage(), "\n"; -} - -try { - bar($a); -} catch (\TypeError $e) { - echo $e->getMessage(), "\n"; -} - +$c->intersect = $a; +echo 'OK', \PHP_EOL; +bar($a); ?> --EXPECT-- object(A)#1 (0) { From a0a1d902b4e5956734df041396de9680bf9d7be9 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 15:56:19 +0100 Subject: [PATCH 21/40] Refactor zend_resolve_ce() --- Zend/zend_execute.c | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 0260060702e71..5f2b2783084bb 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -840,31 +840,25 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class static zend_always_inline zend_class_entry* zend_resolve_ce( zend_property_info *info, zend_type *type) { zend_class_entry *ce; - if (UNEXPECTED(ZEND_TYPE_HAS_NAME(*type))) { - zend_string *name = ZEND_TYPE_NAME(*type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - /* Cannot resolve */ - return NULL; - } - } - } else { - ce = resolve_single_class_type(name, info->ce); - if (!ce) { - /* Cannot resolve */ - return NULL; - } - if (!(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { - zend_string_release(name); - ZEND_TYPE_SET_CE(*type, ce); - } + zend_string *name = NULL; + + if (UNEXPECTED(!ZEND_TYPE_HAS_NAME(*type))) { + return ZEND_TYPE_CE(*type); + } + + name = ZEND_TYPE_NAME(*type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); } } else { - ce = ZEND_TYPE_CE(*type); + ce = resolve_single_class_type(name, info->ce); + if (ce && !(info->ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_string_release(name); + ZEND_TYPE_SET_CE(*type, ce); + } } return ce; } From a96e95684475424f084e82046a8f1480776a1cec Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 16:01:42 +0100 Subject: [PATCH 22/40] Refactor zend_check_type_slow() --- Zend/zend_execute.c | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 5f2b2783084bb..23a14618f6d00 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1005,18 +1005,9 @@ static zend_always_inline bool zend_check_type_slow( } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, check the next one. */ - if (ce == NULL) { - if (HAVE_CACHE_SLOT) { - cache_slot++; - } - continue; - } - - /* Perform actual type check */ + /* Perform actual type check if we have a CE */ /* Instance of a single type part of a union is sufficient to pass the type check */ - if (instanceof_function(Z_OBJCE_P(arg), ce)) { + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { return true; } if (HAVE_CACHE_SLOT) { @@ -1026,19 +1017,14 @@ static zend_always_inline bool zend_check_type_slow( } } else { ce = zend_fetch_ce_from_cache_slot(cache_slot, type); - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, check if a standard type satisfies it. */ - if (ce == NULL) { - goto builtin_types; - } - - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; + /* If we have a CE we check if it satisfies the type constraint, + * otherwise it will check if a standard type satisfies it. */ + if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { + return true; } } } -builtin_types: type_mask = ZEND_TYPE_FULL_MASK(*type); if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) { return 1; From e334504d568b58abeee1bfba484dcc8f031423eb Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 16:29:57 +0100 Subject: [PATCH 23/40] Comment and reorder the parser hack Still need to fix parse error message generation --- .../invalid_types/invalid_nullable_type.phpt | 2 +- Zend/zend_language_parser.y | 7 +++++-- ext/tokenizer/tokenizer_data.c | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt index 1c35dfdf91c70..38969aea14931 100644 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt @@ -7,4 +7,4 @@ function foo(): ?Countable&Iterator {} ?> --EXPECTF-- -Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d +Parse error: syntax error, unexpected token "&'", expecting "{" in %s on line %d diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 5718a57d92029..204ef77cd5ec5 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -231,8 +231,11 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_COALESCE "'??'" %token T_POW "'**'" %token T_POW_EQUAL "'**='" -%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "'&'" -%token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&''" +/* We need to split the & token in two because otherwise Bison somehow lands + * in a shift/reduce conflict for parameter intersection types */ +%token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&'" +// TODO Fix parse error message +%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "'&''" %token T_BAD_CHARACTER "invalid character" /* Token used to force a parse error from the lexer */ diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a79112924f0c9..271184fb107d5 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -167,8 +167,8 @@ void tokenizer_register_constants(INIT_FUNC_ARGS) { REGISTER_LONG_CONSTANT("T_COALESCE", T_COALESCE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW", T_POW, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_POW_EQUAL", T_POW_EQUAL, CONST_CS | CONST_PERSISTENT); - REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG", T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BAD_CHARACTER", T_BAD_CHARACTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_COLON", T_PAAMAYIM_NEKUDOTAYIM, CONST_CS | CONST_PERSISTENT); } @@ -319,8 +319,8 @@ char *get_token_type_name(int token_type) case T_COALESCE: return "T_COALESCE"; case T_POW: return "T_POW"; case T_POW_EQUAL: return "T_POW_EQUAL"; - case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; case T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG"; + case T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG: return "T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG"; case T_BAD_CHARACTER: return "T_BAD_CHARACTER"; } From a02eb62a03cf1ec30f03d6a8f95f47cd7a1ef6ce Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 16:37:48 +0100 Subject: [PATCH 24/40] Refactor check for self/parent --- Zend/zend_compile.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 14cbcbb525acd..78a39fbc6cb02 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -6310,20 +6310,20 @@ static zend_type zend_compile_typename( 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); - zend_string *standard_type_str = zend_type_to_string(single_type); /* An intersection of standard types cannot exist so invalidate it */ if (ZEND_TYPE_IS_ONLY_MASK(single_type)) { + zend_string *standard_type_str = zend_type_to_string(single_type); zend_error_noreturn(E_COMPILE_ERROR, "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + zend_string_release_ex(standard_type_str, false); } /* Check for "self" and "parent" too */ - if (zend_string_equals_literal_ci(standard_type_str, "self") - || zend_string_equals_literal_ci(standard_type_str, "parent")) { + if (zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "self") + || zend_string_equals_literal_ci(ZEND_TYPE_NAME(single_type), "parent")) { zend_error_noreturn(E_COMPILE_ERROR, - "Type %s cannot be part of an intersection type", ZSTR_VAL(standard_type_str)); + "Type %s cannot be part of an intersection type", ZSTR_VAL(ZEND_TYPE_NAME(single_type))); } - zend_string_release_ex(standard_type_str, false); /* Add type to the type list */ type_list->types[type_list->num_types++] = single_type; From 9508af7a946a8e0e7cf8c24e456566c8fa156a7d Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Fri, 18 Jun 2021 17:41:33 +0100 Subject: [PATCH 25/40] Export common type checking code for JIT Also drop the unused scope arg --- Zend/zend_execute.c | 48 ++++++++++++++++++-- Zend/zend_execute.h | 43 +----------------- Zend/zend_vm_def.h | 2 +- Zend/zend_vm_execute.h | 10 ++-- ext/opcache/jit/zend_jit_helpers.c | 73 ++---------------------------- 5 files changed, 56 insertions(+), 120 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 23a14618f6d00..84c44b253d95f 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -961,7 +961,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf return zend_assign_to_variable(property_val, &tmp, IS_TMP_VAR, EX_USES_STRICT_TYPES()); } -ZEND_API bool zend_value_instanceof_static(zval *zv) { +static zend_always_inline bool zend_value_instanceof_static(zval *zv) { if (Z_TYPE_P(zv) != IS_OBJECT) { return 0; } @@ -973,8 +973,48 @@ ZEND_API bool zend_value_instanceof_static(zval *zv) { return instanceof_function(Z_OBJCE_P(zv), called_scope); } -static zend_always_inline bool zend_check_type_slow( - zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope, +/* The cache_slot may only be NULL in debug builds, where arginfo verification of + * internal functions is enabled. Avoid unnecessary checks in release builds. */ +#if ZEND_DEBUG +# define HAVE_CACHE_SLOT (cache_slot != NULL) +#else +# define HAVE_CACHE_SLOT 1 +#endif + +static zend_always_inline zend_class_entry* zend_fetch_ce_from_cache_slot(void **cache_slot, zend_type *type) +{ + zend_class_entry *ce; + + if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { + ce = (zend_class_entry *) *cache_slot; + } else { + zend_string *name = ZEND_TYPE_NAME(*type); + + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); + if (UNEXPECTED(!ce)) { + /* Cannot resolve */ + return NULL; + } + } + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (UNEXPECTED(!ce)) { + return NULL; + } + } + if (HAVE_CACHE_SLOT) { + *cache_slot = (void *) ce; + } + } + return ce; +} + +ZEND_API bool zend_check_type_slow( + zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) { uint32_t type_mask; @@ -1070,7 +1110,7 @@ static zend_always_inline bool zend_check_type( return 1; } - return zend_check_type_slow(type, arg, ref, cache_slot, scope, is_return_type, is_internal); + return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal); } static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index bab101b43f17b..254ae114fb27e 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -76,7 +76,8 @@ ZEND_API ZEND_COLD void zend_verify_return_error( ZEND_API ZEND_COLD void zend_verify_never_error( const zend_function *zf); ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); -ZEND_API bool zend_value_instanceof_static(zval *zv); +ZEND_API bool zend_check_type_slow(zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, + bool is_return_type, bool is_internal); #define ZEND_REF_TYPE_SOURCES(ref) \ @@ -459,46 +460,6 @@ ZEND_COLD void zend_verify_property_type_error(zend_property_info *info, zval *p } \ } while (0) -/* The cache_slot may only be NULL in debug builds, where arginfo verification of - * internal functions is enabled. Avoid unnecessary checks in release builds. */ -#if ZEND_DEBUG -# define HAVE_CACHE_SLOT (cache_slot != NULL) -#else -# define HAVE_CACHE_SLOT 1 -#endif - -static zend_always_inline zend_class_entry* zend_fetch_ce_from_cache_slot(void **cache_slot, zend_type *type) -{ - zend_class_entry *ce; - - if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { - ce = (zend_class_entry *) *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - /* Cannot resolve */ - return NULL; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); - if (UNEXPECTED(!ce)) { - return NULL; - } - } - if (HAVE_CACHE_SLOT) { - *cache_slot = (void *) ce; - } - } - return ce; -} - END_EXTERN_C() #endif /* ZEND_EXECUTE_H */ diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 29991429abe91..7b65efe579135 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4224,7 +4224,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index c698ad44967a4..6847f58f3c575 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -9995,7 +9995,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -20370,7 +20370,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -27901,7 +27901,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -35050,7 +35050,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } @@ -46789,7 +46789,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU } SAVE_OPLINE(); - if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, NULL, 1, 0))) { + if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) { zend_verify_return_error(EX(func), retval_ptr); HANDLE_EXCEPTION(); } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 8e5db440763f5..31e19703f4aff 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1320,73 +1320,6 @@ static zend_reference* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_string *v return ref; } -static zend_always_inline bool zend_jit_verify_type_common(zval *arg, zend_arg_info *arg_info, void **cache_slot) -{ - uint32_t type_mask; - - if (ZEND_TYPE_IS_COMPLEX(arg_info->type) && Z_TYPE_P(arg) == IS_OBJECT) { - zend_class_entry *ce; - if (ZEND_TYPE_HAS_LIST(arg_info->type)) { - zend_type *list_type; - if (ZEND_TYPE_IS_INTERSECTION(arg_info->type)) { - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { - ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, fail. */ - if (ce == NULL) { - return false; - } - if (!instanceof_function(Z_OBJCE_P(arg), ce)) { - return false; - } - cache_slot++; - } ZEND_TYPE_LIST_FOREACH_END(); - return true; - } else { - ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(arg_info->type), list_type) { - ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, check the next one. */ - if (ce == NULL) { - cache_slot++; - continue; - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - cache_slot++; - } ZEND_TYPE_LIST_FOREACH_END(); - } - } else { - ce = zend_fetch_ce_from_cache_slot(cache_slot, &arg_info->type); - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, check if a standard type satisfies it. */ - if (ce == NULL) { - goto builtin_types; - } - if (instanceof_function(Z_OBJCE_P(arg), ce)) { - return 1; - } - } - } - -builtin_types: - type_mask = ZEND_TYPE_FULL_MASK(arg_info->type); - if ((type_mask & MAY_BE_CALLABLE) && zend_is_callable(arg, 0, NULL)) { - return 1; - } - if ((type_mask & MAY_BE_ITERABLE) && zend_is_iterable(arg)) { - return 1; - } - if ((type_mask & MAY_BE_STATIC) && zend_value_instanceof_static(arg)) { - return 1; - } - if (zend_verify_scalar_type_hint(type_mask, arg, ZEND_ARG_USES_STRICT_TYPES(), /* is_internal */ 0)) { - return 1; - } - return 0; -} - static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg_info) { zend_execute_data *execute_data = EG(current_execute_data); @@ -1394,7 +1327,8 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg void **cache_slot = CACHE_ADDR(opline->extended_value); bool ret; - ret = zend_jit_verify_type_common(arg, arg_info, cache_slot); + ret = zend_check_type_slow(&arg_info->type, arg, /* ref */ NULL, cache_slot, + /* is_return_type */ false, /* is_internal */ false); if (UNEXPECTED(!ret)) { zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg); return 0; @@ -1404,7 +1338,8 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_array *op_array, zend_arg_info *arg_info, void **cache_slot) { - if (UNEXPECTED(!zend_jit_verify_type_common(arg, arg_info, cache_slot))) { + if (UNEXPECTED(!zend_check_type_slow(&arg_info->type, arg, /* ref */ NULL, cache_slot, + /* is_return_type */ true, /* is_internal */ false))) { zend_verify_return_error((zend_function*)op_array, arg); } } From 5ae1d2a06f223c65d77ab4eb8d4ffbc489fff00e Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 30 Jun 2021 12:05:55 +0200 Subject: [PATCH 26/40] Fix parse error message for "&" --- .../invalid_types/invalid_nullable_type.phpt | 2 +- .../intersection_types/parse_error.phpt | 8 ++++++++ Zend/zend_language_parser.y | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/parse_error.phpt diff --git a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt index 38969aea14931..1c35dfdf91c70 100644 --- a/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt +++ b/Zend/tests/type_declarations/intersection_types/invalid_types/invalid_nullable_type.phpt @@ -7,4 +7,4 @@ function foo(): ?Countable&Iterator {} ?> --EXPECTF-- -Parse error: syntax error, unexpected token "&'", expecting "{" in %s on line %d +Parse error: syntax error, unexpected token "&", expecting "{" in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/parse_error.phpt b/Zend/tests/type_declarations/intersection_types/parse_error.phpt new file mode 100644 index 0000000000000..2711754532d65 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/parse_error.phpt @@ -0,0 +1,8 @@ +--TEST-- +Parse error for & not followed by var or vararg +--FILE-- + +--EXPECTF-- +Parse error: syntax error, unexpected token "&" in %s on line %d diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index 204ef77cd5ec5..67a972c53bb9c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -231,11 +231,13 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_COALESCE "'??'" %token T_POW "'**'" %token T_POW_EQUAL "'**='" -/* We need to split the & token in two because otherwise Bison somehow lands - * in a shift/reduce conflict for parameter intersection types */ +/* We need to split the & token in two to avoid a shift/reduce conflict. For T1&$v and T1&T2, + * with only one token lookahead, bison does not know whether to reduce T1 as a complete type, + * or shift to continue parsing an intersection type. */ %token T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG "'&'" -// TODO Fix parse error message -%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "'&''" +/* Bison warns on duplicate token literals, so use a different dummy value here. + * It will be fixed up by zend_yytnamerr() later. */ +%token T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG "amp" %token T_BAD_CHARACTER "invalid character" /* Token used to force a parse error from the lexer */ @@ -1568,6 +1570,14 @@ static YYSIZE_T zend_yytnamerr(char *yyres, const char *yystr) return sizeof("token \"\\\"")-1; } + /* We used "amp" as a dummy label to avoid a duplicate token literal warning. */ + if (strcmp(toktype, "\"amp\"") == 0) { + if (yyres) { + yystpcpy(yyres, "token \"&\""); + } + return sizeof("token \"&\"")-1; + } + /* Avoid unreadable """ */ /* "'" would theoretically be just as bad, but is never currently parsed as a separate token */ if (strcmp(toktype, "'\"'") == 0) { From 3cbab3b509f1e48e02f78d6dfc01846096b83e40 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 30 Jun 2021 12:56:10 +0200 Subject: [PATCH 27/40] Merge zend_is_single_type_subtype_intersection() function Make zend_perform_intersection_covariant_class_type_check() a single function that checks whether a simple type is a subtype of a complex type. --- Zend/zend_inheritance.c | 88 +++++++++++------------------------------ 1 file changed, 24 insertions(+), 64 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index ee1e63f60d7d1..05d0c24474714 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -450,6 +450,7 @@ static inheritance_status zend_perform_intersection_covariant_class_type_check( return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; } +/* Check whether a single class proto type is a subtype of a potentially complex fe_type. */ static inheritance_status zend_perform_covariant_class_type_check( zend_class_entry *fe_scope, zend_string *fe_class_name, zend_class_entry *fe_ce, zend_class_entry *proto_scope, zend_type proto_type) { @@ -481,7 +482,8 @@ static inheritance_status zend_perform_covariant_class_type_check( zend_type *single_type; /* Traverse the list of parent types and check if the current child (FE) - * class is the subtype of at least one of them */ + * class is the subtype of at least one of them (union) or all of them (intersection). */ + bool is_intersection = ZEND_TYPE_IS_INTERSECTION(proto_type); ZEND_TYPE_FOREACH(proto_type, single_type) { zend_class_entry *proto_ce; zend_string *proto_class_name = NULL; @@ -489,7 +491,10 @@ static inheritance_status zend_perform_covariant_class_type_check( proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); if (zend_string_equals_ci(fe_class_name, proto_class_name)) { - return INHERITANCE_SUCCESS; + if (!is_intersection) { + return INHERITANCE_SUCCESS; + } + continue; } if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); @@ -499,69 +504,31 @@ static inheritance_status zend_perform_covariant_class_type_check( proto_ce = ZEND_TYPE_CE(*single_type); } else { /* standard type */ + ZEND_ASSERT(!is_intersection); continue; } if (!fe_ce || !proto_ce) { have_unresolved = 1; - } else if (unlinked_instanceof(fe_ce, proto_ce)) { + continue; + } + if (unlinked_instanceof(fe_ce, proto_ce)) { track_class_dependency(fe_ce, fe_class_name); track_class_dependency(proto_ce, proto_class_name); - return INHERITANCE_SUCCESS; - } - } ZEND_TYPE_FOREACH_END(); - - return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_ERROR; -} - -/* checks that the child type (being unique) is a subtype of each member of the parent intersection */ -static inheritance_status zend_is_single_type_subtype_intersection( - zend_class_entry *fe_scope, zend_string *fe_class_name, - zend_class_entry *fe_ce, zend_class_entry *proto_scope, - zend_type proto_type, bool register_unresolved -) { - bool have_unresolved = false; - zend_type *single_type; - zend_type_list *parent_intersection_types; - - ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(proto_type)); - - parent_intersection_types = ZEND_TYPE_LIST(proto_type); - - ZEND_TYPE_LIST_FOREACH(parent_intersection_types, single_type) { - zend_class_entry *proto_ce; - zend_string *proto_class_name = NULL; - if (ZEND_TYPE_HAS_NAME(*single_type)) { - proto_class_name = - resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); - if (zend_string_equals_ci(fe_class_name, proto_class_name)) { - continue; + if (!is_intersection) { + return INHERITANCE_SUCCESS; } - - if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); - proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name, register_unresolved); - proto_ce = ZEND_TYPE_CE(*single_type); } else { - /* standard type cannot be part a subtype of an intersection type */ - ZEND_UNREACHABLE(); - continue; - } - - if (!fe_ce || !proto_ce) { - have_unresolved = true; - continue; - } - if (!unlinked_instanceof(fe_ce, proto_ce)) { - return INHERITANCE_ERROR; + if (is_intersection) { + return INHERITANCE_ERROR; + } } + } ZEND_TYPE_FOREACH_END(); - track_class_dependency(fe_ce, fe_class_name); - track_class_dependency(proto_ce, proto_class_name); - } ZEND_TYPE_LIST_FOREACH_END(); - - return have_unresolved ? INHERITANCE_UNRESOLVED : INHERITANCE_SUCCESS; + if (have_unresolved) { + return INHERITANCE_UNRESOLVED; + } + return is_intersection ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } static void register_unresolved_classes(zend_class_entry *scope, zend_type type) { @@ -694,16 +661,9 @@ static inheritance_status zend_perform_covariant_type_check( continue; } - if (UNEXPECTED(ZEND_TYPE_IS_INTERSECTION(proto_type))) { - status = zend_is_single_type_subtype_intersection(fe_scope, - fe_class_name, fe_ce, proto_scope, proto_type, - /* register_unresolved */ false); - } else { - status = zend_perform_covariant_class_type_check(fe_scope, - fe_class_name, fe_ce, proto_scope, proto_type, - /* register_unresolved */ false); - } - + status = zend_perform_covariant_class_type_check(fe_scope, + fe_class_name, fe_ce, proto_scope, proto_type, + /* register_unresolved */ false); if (status == INHERITANCE_ERROR) { return INHERITANCE_ERROR; } From a47b77a1bde558e4689702969f63c24b80dc2235 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 30 Jun 2021 15:48:51 +0200 Subject: [PATCH 28/40] Rebase fixup --- Zend/zend_inheritance.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 05d0c24474714..c6437a527867a 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -643,7 +643,7 @@ static inheritance_status zend_perform_covariant_type_check( * subtype of a parent single/intersection type, either it is an INvalid subtype * when the parent is a union or it is unresolved, we check which case this is */ if (ZEND_TYPE_IS_UNION(proto_type) && !parent_union_has_unresolved) { - return tentative ? INHERITANCE_WARNING : INHERITANCE_ERROR; + return INHERITANCE_ERROR; } } else { /* First try to check whether we can succeed without resolving anything */ From c8b965a0c2b415758473f526cf892f967b08c533 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 30 Jun 2021 16:03:13 +0200 Subject: [PATCH 29/40] Unify early exit status handling, add comments --- Zend/zend_inheritance.c | 83 +++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index c6437a527867a..03937bbb2e44b 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -403,9 +403,11 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name zend_hash_add_ptr(ht, class_name, ce); } -static inheritance_status zend_perform_intersection_covariant_class_type_check( - zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce, +/* Check whether any type in fe_type is a subtype of the proto class. + * This is independently of whether fe_type is a union or intersection. */ +static inheritance_status zend_is_any_type_subtype_of_class( zend_class_entry *fe_scope, zend_type fe_type, + zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce, bool register_unresolved) { bool have_unresolved = false; @@ -451,7 +453,7 @@ static inheritance_status zend_perform_intersection_covariant_class_type_check( } /* Check whether a single class proto type is a subtype of a potentially complex fe_type. */ -static inheritance_status zend_perform_covariant_class_type_check( +static inheritance_status zend_is_class_subtype_of_type( zend_class_entry *fe_scope, zend_string *fe_class_name, zend_class_entry *fe_ce, zend_class_entry *proto_scope, zend_type proto_type) { bool have_unresolved = 0; @@ -589,15 +591,16 @@ static inheritance_status zend_perform_covariant_type_check( } zend_type *single_type; - bool all_success = true; + inheritance_status early_exit_status; + bool have_unresolved = false; - /* If the child type is an intersection type then we need to loop over - * the parents first. For intersection types, loop over the parent types first - * as the child can add types, however none of them can be a supertype of - * a parent type. */ if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { - bool parent_union_has_unresolved = false; - /* First try to check whether we can succeed without resolving anything */ + /* U_1&...&U_n < V_1&...&V_m if forall V_j. exists U_i. U_i < V_j. + * U_1&...&U_n < V_1|...|V_m if exists V_j. exists U_i. U_i < V_j. + * As such, we need to iterate over proto_type (V_j) first and use a different + * quantifier depending on whether fe_type is a union or an intersection. */ + early_exit_status = + ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS; ZEND_TYPE_FOREACH(proto_type, single_type) { inheritance_status status; zend_string *proto_class_name; @@ -614,39 +617,22 @@ static inheritance_status zend_perform_covariant_type_check( continue; } - status = zend_perform_intersection_covariant_class_type_check( - proto_scope, proto_class_name, proto_ce, - fe_scope, fe_type, /* register_unresolved */ false); - - /* If the parent is a union type then the intersection type must only be - * a subtype of one of them */ - if (ZEND_TYPE_IS_UNION(proto_type)) { - if (status == INHERITANCE_SUCCESS) { - return INHERITANCE_SUCCESS; - } - if (status == INHERITANCE_UNRESOLVED) { - all_success = false; - } - } else { - if (status == INHERITANCE_ERROR) { - return INHERITANCE_ERROR; - } - if (status != INHERITANCE_SUCCESS) { - ZEND_ASSERT(status == INHERITANCE_UNRESOLVED); - parent_union_has_unresolved = true; - all_success = false; - } + status = zend_is_any_type_subtype_of_class( + fe_scope, fe_type, proto_scope, proto_class_name, proto_ce, + /* register_unresolved */ false); + if (status == early_exit_status) { + return status; + } + if (status == INHERITANCE_UNRESOLVED) { + have_unresolved = true; } } ZEND_TYPE_FOREACH_END(); - - /* Reaching this means either the child intersection type is a valid/unresolved - * subtype of a parent single/intersection type, either it is an INvalid subtype - * when the parent is a union or it is unresolved, we check which case this is */ - if (ZEND_TYPE_IS_UNION(proto_type) && !parent_union_has_unresolved) { - return INHERITANCE_ERROR; - } } else { - /* First try to check whether we can succeed without resolving anything */ + /* U_1|...|U_n < V_1|...|V_m if forall U_i. exists V_j. U_i < V_j. + * U_1|...|U_n < V_1&...&V_m if forall U_i. forall V_j. U_i < V_j. + * We need to iterate over fe_type (U_i) first and the logic is independent of + * whether proto_type is a union or intersection (only the inner check differs). */ + early_exit_status = INHERITANCE_ERROR; ZEND_TYPE_FOREACH(fe_type, single_type) { inheritance_status status; zend_string *fe_class_name; @@ -661,21 +647,20 @@ static inheritance_status zend_perform_covariant_type_check( continue; } - status = zend_perform_covariant_class_type_check(fe_scope, - fe_class_name, fe_ce, proto_scope, proto_type, + status = zend_is_class_subtype_of_type( + fe_scope, fe_class_name, fe_ce, proto_scope, proto_type, /* register_unresolved */ false); - if (status == INHERITANCE_ERROR) { - return INHERITANCE_ERROR; + if (status == early_exit_status) { + return early_exit_status; } - if (status != INHERITANCE_SUCCESS) { - all_success = 0; + if (status == INHERITANCE_UNRESOLVED) { + have_unresolved = true; } } ZEND_TYPE_FOREACH_END(); } - /* All individual checks succeeded, overall success */ - if (all_success) { - return INHERITANCE_SUCCESS; + if (!have_unresolved) { + return early_exit_status == INHERITANCE_ERROR ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } register_unresolved_classes(fe_scope, fe_type); From 2070319116567d9dbad739e5cd5203a650f66b41 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 30 Jun 2021 17:02:27 +0200 Subject: [PATCH 30/40] Adjust misleading comment --- Zend/zend_inheritance.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 03937bbb2e44b..54996b9074d01 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -403,13 +403,13 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name zend_hash_add_ptr(ht, class_name, ce); } -/* Check whether any type in fe_type is a subtype of the proto class. - * This is independently of whether fe_type is a union or intersection. */ -static inheritance_status zend_is_any_type_subtype_of_class( +/* Check whether any type in the fe_type intersection type is a subtype of the proto class. */ +static inheritance_status zend_is_intersection_subtype_of_class( zend_class_entry *fe_scope, zend_type fe_type, zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce, bool register_unresolved) { + ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(fe_type)); bool have_unresolved = false; zend_type *single_type; @@ -617,7 +617,7 @@ static inheritance_status zend_perform_covariant_type_check( continue; } - status = zend_is_any_type_subtype_of_class( + status = zend_is_intersection_subtype_of_class( fe_scope, fe_type, proto_scope, proto_class_name, proto_ce, /* register_unresolved */ false); if (status == early_exit_status) { From 2c6d155a52becf82b0552ccf48dfa5d022cb74b1 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Wed, 30 Jun 2021 17:07:55 +0200 Subject: [PATCH 31/40] Add failing test --- .../intersection_types/variance/invalid3.phpt | 18 ++++++++++++++++++ .../intersection_types/variance/valid7.phpt | 19 +++++++++++++++++++ Zend/zend_inheritance.c | 13 +++++++++++-- 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid7.phpt diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt new file mode 100644 index 0000000000000..118393e8931cf --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid3.phpt @@ -0,0 +1,18 @@ +--TEST-- +Replacing int type with intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): int in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt new file mode 100644 index 0000000000000..8d5f267e0216e --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt @@ -0,0 +1,19 @@ +--TEST-- +Replacing object type with intersection type +--FILE-- + +===DONE=== +--EXPECTF-- +TODO diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 54996b9074d01..2256301839ce1 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -595,6 +595,17 @@ static inheritance_status zend_perform_covariant_type_check( bool have_unresolved = false; if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { + if (proto_type_mask & MAY_BE_OBJECT) { + /* TODO We can't just return success here, because the class must be loaded. */ + } + if (proto_type_mask & MAY_BE_ITERABLE) { + /* TODO */ + } + if (proto_type_mask) { + /* An intersection type cannot be a subtype of other builtin types. */ + return INHERITANCE_ERROR; + } + /* U_1&...&U_n < V_1&...&V_m if forall V_j. exists U_i. U_i < V_j. * U_1&...&U_n < V_1|...|V_m if exists V_j. exists U_i. U_i < V_j. * As such, we need to iterate over proto_type (V_j) first and use a different @@ -612,8 +623,6 @@ static inheritance_status zend_perform_covariant_type_check( proto_ce = ZEND_TYPE_CE(*single_type); proto_class_name = proto_ce->name; } else { - /* standard type */ - ZEND_UNREACHABLE(); continue; } From 681f1c4c40796f3ba7ce2f7a4ef639580e13da97 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 1 Jul 2021 09:42:56 +0200 Subject: [PATCH 32/40] Handle object/iterable, fixup rebase I think this is still not quite correct :/ --- .../intersection_types/variance/invalid4.phpt | 17 ++++ .../intersection_types/variance/invalid5.phpt | 18 ++++ .../intersection_types/variance/valid7.phpt | 10 ++- .../intersection_types/variance/valid8.phpt | 22 +++++ Zend/zend_inheritance.c | 89 ++++++++++--------- 5 files changed, 112 insertions(+), 44 deletions(-) create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt create mode 100644 Zend/tests/type_declarations/intersection_types/variance/valid8.phpt diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt new file mode 100644 index 0000000000000..be94fd2a1729a --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt @@ -0,0 +1,17 @@ +--TEST-- +Replacing object type with not-loadable intersection type +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Could not check compatibility between Test2::method(): X&Y and Test::method(): object, because class X is not available in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt new file mode 100644 index 0000000000000..b704f89b909f2 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid5.phpt @@ -0,0 +1,18 @@ +--TEST-- +Replacing iterable type with non-Traversable intersection type +--FILE-- + +--EXPECTF-- +Fatal error: Declaration of Test2::method(): X&Y must be compatible with Test::method(): iterable in %s on line %d diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt index 8d5f267e0216e..bc2f28eeff189 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/valid7.phpt @@ -3,17 +3,21 @@ Replacing object type with intersection type --FILE-- ===DONE=== ---EXPECTF-- -TODO +--EXPECT-- +===DONE=== diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid8.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid8.phpt new file mode 100644 index 0000000000000..a3b553b23e824 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/valid8.phpt @@ -0,0 +1,22 @@ +--TEST-- +Replacing iterable type with intersection type +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 2256301839ce1..4e254de4d72da 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -406,8 +406,7 @@ static void track_class_dependency(zend_class_entry *ce, zend_string *class_name /* Check whether any type in the fe_type intersection type is a subtype of the proto class. */ static inheritance_status zend_is_intersection_subtype_of_class( zend_class_entry *fe_scope, zend_type fe_type, - zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce, - bool register_unresolved) + zend_class_entry *proto_scope, zend_string *proto_class_name, zend_class_entry *proto_ce) { ZEND_ASSERT(ZEND_TYPE_IS_INTERSECTION(fe_type)); bool have_unresolved = false; @@ -425,11 +424,10 @@ static inheritance_status zend_is_intersection_subtype_of_class( return INHERITANCE_SUCCESS; } - if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); - fe_ce = - lookup_class(fe_scope, fe_class_name, register_unresolved); + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name); + fe_ce = lookup_class(fe_scope, fe_class_name); } else if (ZEND_TYPE_HAS_CE(*single_type)) { - if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name, register_unresolved); + if (!proto_ce) proto_ce = lookup_class(proto_scope, proto_class_name); fe_ce = ZEND_TYPE_CE(*single_type); } else { /* standard type in an intersection type is impossible, @@ -533,6 +531,19 @@ static inheritance_status zend_is_class_subtype_of_type( return is_intersection ? INHERITANCE_SUCCESS : INHERITANCE_ERROR; } +static zend_string *get_class_from_type( + zend_class_entry **ce, zend_class_entry *scope, zend_type single_type) { + if (ZEND_TYPE_HAS_NAME(single_type)) { + *ce = NULL; + return resolve_class_name(scope, ZEND_TYPE_NAME(single_type)); + } + if (ZEND_TYPE_HAS_CE(single_type)) { + *ce = ZEND_TYPE_CE(single_type); + return (*ce)->name; + } + return NULL; +} + static void register_unresolved_classes(zend_class_entry *scope, zend_type type) { zend_type *single_type; ZEND_TYPE_FOREACH(type, single_type) { @@ -595,15 +606,26 @@ static inheritance_status zend_perform_covariant_type_check( bool have_unresolved = false; if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { - if (proto_type_mask & MAY_BE_OBJECT) { - /* TODO We can't just return success here, because the class must be loaded. */ - } - if (proto_type_mask & MAY_BE_ITERABLE) { - /* TODO */ - } - if (proto_type_mask) { - /* An intersection type cannot be a subtype of other builtin types. */ - return INHERITANCE_ERROR; + if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { + bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; + ZEND_TYPE_FOREACH(fe_type, single_type) { + zend_class_entry *fe_ce; + zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type); + if (!fe_class_name) { + continue; + } + if (!fe_ce) { + fe_ce = lookup_class(fe_scope, fe_class_name); + } + if (fe_ce) { + if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) { + track_class_dependency(fe_ce, fe_class_name); + return INHERITANCE_SUCCESS; + } + } else { + have_unresolved = true; + } + } ZEND_TYPE_FOREACH_END(); } /* U_1&...&U_n < V_1&...&V_m if forall V_j. exists U_i. U_i < V_j. @@ -613,22 +635,15 @@ static inheritance_status zend_perform_covariant_type_check( early_exit_status = ZEND_TYPE_IS_INTERSECTION(proto_type) ? INHERITANCE_ERROR : INHERITANCE_SUCCESS; ZEND_TYPE_FOREACH(proto_type, single_type) { - inheritance_status status; - zend_string *proto_class_name; - zend_class_entry *proto_ce = NULL; - - if (ZEND_TYPE_HAS_NAME(*single_type)) { - proto_class_name = resolve_class_name(proto_scope, ZEND_TYPE_NAME(*single_type)); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - proto_ce = ZEND_TYPE_CE(*single_type); - proto_class_name = proto_ce->name; - } else { + zend_class_entry *proto_ce; + zend_string *proto_class_name = + get_class_from_type(&proto_ce, proto_scope, *single_type); + if (!proto_class_name) { continue; } - status = zend_is_intersection_subtype_of_class( - fe_scope, fe_type, proto_scope, proto_class_name, proto_ce, - /* register_unresolved */ false); + inheritance_status status = zend_is_intersection_subtype_of_class( + fe_scope, fe_type, proto_scope, proto_class_name, proto_ce); if (status == early_exit_status) { return status; } @@ -643,22 +658,14 @@ static inheritance_status zend_perform_covariant_type_check( * whether proto_type is a union or intersection (only the inner check differs). */ early_exit_status = INHERITANCE_ERROR; ZEND_TYPE_FOREACH(fe_type, single_type) { - inheritance_status status; - zend_string *fe_class_name; - zend_class_entry *fe_ce = NULL; - - if (ZEND_TYPE_HAS_NAME(*single_type)) { - fe_class_name = resolve_class_name(fe_scope, ZEND_TYPE_NAME(*single_type)); - } else if (ZEND_TYPE_HAS_CE(*single_type)) { - fe_ce = ZEND_TYPE_CE(*single_type); - fe_class_name = fe_ce->name; - } else { + zend_class_entry *fe_ce; + zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type); + if (!fe_class_name) { continue; } - status = zend_is_class_subtype_of_type( - fe_scope, fe_class_name, fe_ce, proto_scope, proto_type, - /* register_unresolved */ false); + inheritance_status status = zend_is_class_subtype_of_type( + fe_scope, fe_class_name, fe_ce, proto_scope, proto_type); if (status == early_exit_status) { return early_exit_status; } From 1446dec155323807660ba266302764d90991a0bb Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 1 Jul 2021 10:53:09 +0200 Subject: [PATCH 33/40] Handle intersection in can_elide_return_type_check() --- Zend/Optimizer/dfa_pass.c | 10 +++++++--- ext/opcache/tests/opt/verify_return_type.phpt | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 4b9b9426ce556..2967addd2f0bf 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -312,17 +312,21 @@ static inline bool can_elide_return_type_check( if (disallowed_types == MAY_BE_OBJECT && use_info->ce && ZEND_TYPE_IS_COMPLEX(arg_info->type)) { zend_type *single_type; + /* For intersection: result==false is failure, default is success. + * For union: result==true is success, default is failure. */ + bool is_intersection = ZEND_TYPE_IS_INTERSECTION(arg_info->type); ZEND_TYPE_FOREACH(arg_info->type, single_type) { if (ZEND_TYPE_HAS_NAME(*single_type)) { zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(*single_type)); zend_class_entry *ce = zend_optimizer_get_class_entry(script, lcname); zend_string_release(lcname); - if (ce && ZEND_TYPE_IS_UNION(arg_info->type) && safe_instanceof(use_info->ce, ce)) { - /* One of the class union types matched. */ - return true; + bool result = ce && safe_instanceof(use_info->ce, ce); + if (result == !is_intersection) { + return result; } } } ZEND_TYPE_FOREACH_END(); + return is_intersection; } return false; diff --git a/ext/opcache/tests/opt/verify_return_type.phpt b/ext/opcache/tests/opt/verify_return_type.phpt index e69fea2183f83..ef14d7630fe80 100644 --- a/ext/opcache/tests/opt/verify_return_type.phpt +++ b/ext/opcache/tests/opt/verify_return_type.phpt @@ -51,6 +51,10 @@ function getClassUnion(): stdClass|FooBar { return new stdClass; } +function getClassIntersection(): Traversable&Countable { + return new ArrayObject; +} + ?> --EXPECTF-- $_main: @@ -69,6 +73,16 @@ getClassUnion: LIVE RANGES: 0: 0001 - 0002 (new) +getClassIntersection: + ; (lines=3, args=0, vars=0, tmps=1) + ; (after optimizer) + ; %s +0000 V0 = NEW 0 string("ArrayObject") +0001 DO_FCALL +0002 RETURN V0 +LIVE RANGES: + 0: 0001 - 0002 (new) + Test1::getIntOrFloat: ; (lines=2, args=1, vars=1, tmps=0) ; (after optimizer) From 6166ed0f9b5ee2b355a368a451800e153273b861 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Thu, 1 Jul 2021 11:24:54 +0200 Subject: [PATCH 34/40] Code cleanup --- Zend/zend_execute.c | 105 +++++++++++------------------ Zend/zend_execute.h | 4 +- ext/opcache/jit/zend_jit_helpers.c | 10 ++- 3 files changed, 45 insertions(+), 74 deletions(-) diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 84c44b253d95f..60eb75347393f 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -836,18 +836,15 @@ static zend_class_entry *resolve_single_class_type(zend_string *name, zend_class } } -// TODO better name -static zend_always_inline zend_class_entry* zend_resolve_ce( +static zend_always_inline zend_class_entry *zend_ce_from_type( zend_property_info *info, zend_type *type) { - zend_class_entry *ce; - zend_string *name = NULL; - if (UNEXPECTED(!ZEND_TYPE_HAS_NAME(*type))) { + ZEND_ASSERT(ZEND_TYPE_HAS_CE(*type)); return ZEND_TYPE_CE(*type); } - name = ZEND_TYPE_NAME(*type); - + zend_string *name = ZEND_TYPE_NAME(*type); + zend_class_entry *ce; if (ZSTR_HAS_CE_CACHE(name)) { ce = ZSTR_GET_CE_CACHE(name); if (!ce) { @@ -865,50 +862,28 @@ static zend_always_inline zend_class_entry* zend_resolve_ce( static 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)) { zend_type *list_type; - if (ZEND_TYPE_IS_INTERSECTION(info->type)) { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { - ce = zend_resolve_ce(info, list_type); - - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, fail. */ - if (ce == NULL) { - return false; - } - - if (!instanceof_function(object_ce, ce)) { + zend_class_entry *ce = zend_ce_from_type(info, list_type); + if (!ce || !instanceof_function(object_ce, ce)) { return false; } } ZEND_TYPE_LIST_FOREACH_END(); return true; } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(info->type), list_type) { - ce = zend_resolve_ce(info, list_type); - - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, check the next one. */ - if (ce == NULL) { - continue; - } - if (instanceof_function(object_ce, ce)) { + zend_class_entry *ce = zend_ce_from_type(info, list_type); + if (ce && instanceof_function(object_ce, ce)) { return true; } } ZEND_TYPE_LIST_FOREACH_END(); return false; } } else { - ce = zend_resolve_ce(info, &info->type); - - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, fail. */ - if (ce == NULL) { - return false; - } - - return instanceof_function(object_ce, ce); + zend_class_entry *ce = zend_ce_from_type(info, &info->type); + return ce && instanceof_function(object_ce, ce); } } @@ -981,39 +956,38 @@ static zend_always_inline bool zend_value_instanceof_static(zval *zv) { # define HAVE_CACHE_SLOT 1 #endif -static zend_always_inline zend_class_entry* zend_fetch_ce_from_cache_slot(void **cache_slot, zend_type *type) +static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot( + void **cache_slot, zend_type *type) { - zend_class_entry *ce; - if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) { - ce = (zend_class_entry *) *cache_slot; - } else { - zend_string *name = ZEND_TYPE_NAME(*type); - - if (ZSTR_HAS_CE_CACHE(name)) { - ce = ZSTR_GET_CE_CACHE(name); - if (!ce) { - ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); - if (UNEXPECTED(!ce)) { - /* Cannot resolve */ - return NULL; - } - } - } else { - ce = zend_fetch_class(name, - ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + return (zend_class_entry *) *cache_slot; + } + + zend_string *name = ZEND_TYPE_NAME(*type); + zend_class_entry *ce; + if (ZSTR_HAS_CE_CACHE(name)) { + ce = ZSTR_GET_CE_CACHE(name); + if (!ce) { + ce = zend_lookup_class_ex(name, NULL, ZEND_FETCH_CLASS_NO_AUTOLOAD); if (UNEXPECTED(!ce)) { + /* Cannot resolve */ return NULL; } } - if (HAVE_CACHE_SLOT) { - *cache_slot = (void *) ce; + } else { + ce = zend_fetch_class(name, + ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD | ZEND_FETCH_CLASS_SILENT); + if (UNEXPECTED(!ce)) { + return NULL; } } + if (HAVE_CACHE_SLOT) { + *cache_slot = (void *) ce; + } return ce; } -ZEND_API bool zend_check_type_slow( +static zend_always_inline bool zend_check_type_slow( zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type, bool is_internal) { @@ -1025,16 +999,9 @@ ZEND_API bool zend_check_type_slow( if (ZEND_TYPE_IS_INTERSECTION(*type)) { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* If we cannot resolve the CE we cannot check if it satisfies - * the type constraint, fail. */ - if (ce == NULL) { - return false; - } - - /* Perform actual type check */ /* If type is not an instance of one of the types taking part in the * intersection it cannot be a valid instance of the whole intersection type. */ - if (!instanceof_function(Z_OBJCE_P(arg), ce)) { + if (!ce || !instanceof_function(Z_OBJCE_P(arg), ce)) { return false; } if (HAVE_CACHE_SLOT) { @@ -1045,7 +1012,6 @@ ZEND_API bool zend_check_type_slow( } else { ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) { ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type); - /* Perform actual type check if we have a CE */ /* Instance of a single type part of a union is sufficient to pass the type check */ if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) { return true; @@ -1113,6 +1079,13 @@ static zend_always_inline bool zend_check_type( return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal); } +ZEND_API bool zend_check_user_type_slow( + zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type) +{ + return zend_check_type_slow( + type, arg, ref, cache_slot, is_return_type, /* is_internal */ false); +} + static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) { zend_arg_info *cur_arg_info; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 254ae114fb27e..2bef567390725 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -76,8 +76,8 @@ ZEND_API ZEND_COLD void zend_verify_return_error( ZEND_API ZEND_COLD void zend_verify_never_error( const zend_function *zf); ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref); -ZEND_API bool zend_check_type_slow(zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, - bool is_return_type, bool is_internal); +ZEND_API bool zend_check_user_type_slow( + zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type); #define ZEND_REF_TYPE_SOURCES(ref) \ diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 31e19703f4aff..2c133a751e7f0 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1325,10 +1325,8 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg zend_execute_data *execute_data = EG(current_execute_data); const zend_op *opline = EX(opline); void **cache_slot = CACHE_ADDR(opline->extended_value); - bool ret; - - ret = zend_check_type_slow(&arg_info->type, arg, /* ref */ NULL, cache_slot, - /* is_return_type */ false, /* is_internal */ false); + bool ret = zend_check_user_type_slow( + &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false); if (UNEXPECTED(!ret)) { zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg); return 0; @@ -1338,8 +1336,8 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_array *op_array, zend_arg_info *arg_info, void **cache_slot) { - if (UNEXPECTED(!zend_check_type_slow(&arg_info->type, arg, /* ref */ NULL, cache_slot, - /* is_return_type */ true, /* is_internal */ false))) { + if (UNEXPECTED(!zend_check_user_type_slow( + &arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) { zend_verify_return_error((zend_function*)op_array, arg); } } From a6d28dc6ff10e7b44cc9353006eda932ff064048 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 5 Jul 2021 03:10:28 +0200 Subject: [PATCH 35/40] Return status like in branch above --- Zend/zend_inheritance.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 4e254de4d72da..f8ec7e86017c9 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -667,7 +667,7 @@ static inheritance_status zend_perform_covariant_type_check( inheritance_status status = zend_is_class_subtype_of_type( fe_scope, fe_class_name, fe_ce, proto_scope, proto_type); if (status == early_exit_status) { - return early_exit_status; + return status; } if (status == INHERITANCE_UNRESOLVED) { have_unresolved = true; From 129f1ad1ab6721fb529cd8fe6e998bd502f1d098 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 5 Jul 2021 03:50:26 +0200 Subject: [PATCH 36/40] Add a variance test It wasn't immediatly obvious to me while starring at the code that the case where a parent type is not loadable is handled. Just needed to understand that get_class_from_type() only returns NULL when it is not a class-type --- .../intersection_types/variance/invalid6.phpt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt new file mode 100644 index 0000000000000..b99b707862bf6 --- /dev/null +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid6.phpt @@ -0,0 +1,20 @@ +--TEST-- +Replacing not-loadable parent intersection type with loadable child intersection type +--FILE-- + +===DONE=== +--EXPECTF-- +Fatal error: Could not check compatibility between Test2::method(): Y&Z and Test::method(): X&Y, because class X is not available in %s on line %d From ea337680ab9d451b6b6aa4fa0ce07b2e2f42a6d3 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 5 Jul 2021 04:07:26 +0200 Subject: [PATCH 37/40] Don't load classes of an intersection if parent has object Refactor a bit the handling of iterable as a by product. --- .../variance/{invalid4.phpt => valid9.phpt} | 4 +-- Zend/zend_inheritance.c | 29 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) rename Zend/tests/type_declarations/intersection_types/variance/{invalid4.phpt => valid9.phpt} (55%) diff --git a/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt b/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt similarity index 55% rename from Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt rename to Zend/tests/type_declarations/intersection_types/variance/valid9.phpt index be94fd2a1729a..dfc39ed683a5c 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt @@ -13,5 +13,5 @@ class Test2 extends Test { ?> ===DONE=== ---EXPECTF-- -Fatal error: Could not check compatibility between Test2::method(): X&Y and Test::method(): object, because class X is not available in %s on line %d +--EXPECT-- +===DONE=== diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index f8ec7e86017c9..88748b1617a0c 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -606,24 +606,31 @@ static inheritance_status zend_perform_covariant_type_check( bool have_unresolved = false; if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { - if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { - bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; + /* As an intersection type can only be composed of class types (checked at compile stage), + * it is trivially covariant to the object type. + * Handle this case separately to ensure it never requires class loading. */ + if (proto_type_mask & MAY_BE_OBJECT) { + return INHERITANCE_SUCCESS; + } + + // TODO: Make "iterable" an alias of "array|Traversable" instead, + // so this special cases will be handled automatically. + if (proto_type_mask & MAY_BE_ITERABLE) { ZEND_TYPE_FOREACH(fe_type, single_type) { zend_class_entry *fe_ce; zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type); - if (!fe_class_name) { - continue; - } + + ZEND_ASSERT(fe_class_name); if (!fe_ce) { fe_ce = lookup_class(fe_scope, fe_class_name); } - if (fe_ce) { - if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) { - track_class_dependency(fe_ce, fe_class_name); - return INHERITANCE_SUCCESS; - } - } else { + if (!fe_ce) { have_unresolved = true; + continue; + } + if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { + track_class_dependency(fe_ce, fe_class_name); + return INHERITANCE_SUCCESS; } } ZEND_TYPE_FOREACH_END(); } From 8873527c457821143f4bd835e4dfc23e76f25c38 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 5 Jul 2021 12:33:10 +0200 Subject: [PATCH 38/40] Revert "Don't load classes of an intersection if parent has object" This reverts commit ea337680ab9d451b6b6aa4fa0ce07b2e2f42a6d3. --- .../variance/{valid9.phpt => invalid4.phpt} | 4 +-- Zend/zend_inheritance.c | 29 +++++++------------ 2 files changed, 13 insertions(+), 20 deletions(-) rename Zend/tests/type_declarations/intersection_types/variance/{valid9.phpt => invalid4.phpt} (55%) diff --git a/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt similarity index 55% rename from Zend/tests/type_declarations/intersection_types/variance/valid9.phpt rename to Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt index dfc39ed683a5c..be94fd2a1729a 100644 --- a/Zend/tests/type_declarations/intersection_types/variance/valid9.phpt +++ b/Zend/tests/type_declarations/intersection_types/variance/invalid4.phpt @@ -13,5 +13,5 @@ class Test2 extends Test { ?> ===DONE=== ---EXPECT-- -===DONE=== +--EXPECTF-- +Fatal error: Could not check compatibility between Test2::method(): X&Y and Test::method(): object, because class X is not available in %s on line %d diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 88748b1617a0c..f8ec7e86017c9 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -606,31 +606,24 @@ static inheritance_status zend_perform_covariant_type_check( bool have_unresolved = false; if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { - /* As an intersection type can only be composed of class types (checked at compile stage), - * it is trivially covariant to the object type. - * Handle this case separately to ensure it never requires class loading. */ - if (proto_type_mask & MAY_BE_OBJECT) { - return INHERITANCE_SUCCESS; - } - - // TODO: Make "iterable" an alias of "array|Traversable" instead, - // so this special cases will be handled automatically. - if (proto_type_mask & MAY_BE_ITERABLE) { + if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { + bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; ZEND_TYPE_FOREACH(fe_type, single_type) { zend_class_entry *fe_ce; zend_string *fe_class_name = get_class_from_type(&fe_ce, fe_scope, *single_type); - - ZEND_ASSERT(fe_class_name); + if (!fe_class_name) { + continue; + } if (!fe_ce) { fe_ce = lookup_class(fe_scope, fe_class_name); } - if (!fe_ce) { + if (fe_ce) { + if (any_class || unlinked_instanceof(fe_ce, zend_ce_traversable)) { + track_class_dependency(fe_ce, fe_class_name); + return INHERITANCE_SUCCESS; + } + } else { have_unresolved = true; - continue; - } - if (unlinked_instanceof(fe_ce, zend_ce_traversable)) { - track_class_dependency(fe_ce, fe_class_name); - return INHERITANCE_SUCCESS; } } ZEND_TYPE_FOREACH_END(); } From 891c59b76bb89e0eac80c67c986eae6aafd34ecc Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 5 Jul 2021 12:50:20 +0200 Subject: [PATCH 39/40] Add comment --- Zend/zend_inheritance.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index f8ec7e86017c9..06a25efdef796 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -606,6 +606,10 @@ static inheritance_status zend_perform_covariant_type_check( bool have_unresolved = false; if (ZEND_TYPE_IS_INTERSECTION(fe_type)) { + /* Currently, for object type 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 (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; ZEND_TYPE_FOREACH(fe_type, single_type) { From 799b0cb9b633931fa1e4589051b0ec1fc9944132 Mon Sep 17 00:00:00 2001 From: George Peter Banyard Date: Mon, 5 Jul 2021 13:48:42 +0200 Subject: [PATCH 40/40] [skip-ci] Add UPGRADING entry and fix comments --- UPGRADING | 7 +++++-- Zend/zend_inheritance.c | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/UPGRADING b/UPGRADING index 12e4b86539cab..de86fc31793e8 100644 --- a/UPGRADING +++ b/UPGRADING @@ -64,6 +64,9 @@ PHP 8.1 UPGRADE NOTES PHP cross-version compatibility concerns, a `#[ReturnTypeWillChange]` attribute can be added to silence the deprecation notice. RFC: https://wiki.php.net/rfc/internal_method_return_types + . Added support for intersection types. + They cannot be combined with union types. + RFC: https://wiki.php.net/rfc/pure-intersection-types - Fileinfo: . The fileinfo functions now accept and return, respectively, finfo objects @@ -307,7 +310,7 @@ PHP 8.1 UPGRADE NOTES Previously, -a without readline had the same behavior as calling php without any arguments, apart from printing an additional "Interactive mode enabled" message. This mode was not, in fact, interactive. - + - phpdbg: . Remote functionality from phpdbg has been removed. @@ -453,7 +456,7 @@ PHP 8.1 UPGRADE NOTES MYSQLI_REFRESH_SLAVE, in line with an upstream change in MySQL. The old constant is still available for backwards-compatibility reasons, but may be deprecated/removed in the future. - + - Sockets: . TCP_DEFER_ACCEPT socket option added where available. diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 06a25efdef796..f7ad6c5a33532 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -460,7 +460,7 @@ static inheritance_status zend_is_class_subtype_of_type( 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). */ + * are not classes (such as typedefs). */ if (!fe_ce) fe_ce = lookup_class(fe_scope, fe_class_name); if (!fe_ce) { have_unresolved = 1; @@ -609,7 +609,7 @@ static inheritance_status zend_perform_covariant_type_check( /* Currently, for object type 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). */ + * (such as typedefs). */ if (proto_type_mask & (MAY_BE_OBJECT|MAY_BE_ITERABLE)) { bool any_class = (proto_type_mask & MAY_BE_OBJECT) != 0; ZEND_TYPE_FOREACH(fe_type, single_type) {