]> granicus.if.org Git - php/commitdiff
Add support for union types for internal functions
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 6 Jan 2021 15:06:51 +0000 (16:06 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Tue, 12 Jan 2021 09:14:41 +0000 (10:14 +0100)
This closes the last hole in the supported types for internal
function arginfo types. It's now possible to represent unions of
multiple classes. This is done by storing them as TypeA|TypeB and
PHP will then convert this into an appropriate union type list.

Closes GH-6581.

Zend/zend_API.c
Zend/zend_execute.c
build/gen_stub.php
ext/ffi/ffi.stub.php
ext/ffi/ffi_arginfo.h

index 58b743e6419fa410ec63cbb680df79d8e278196b..11760d50f06ac5fb9642881c9adf28888fcdb8c4 100644 (file)
@@ -2455,10 +2455,40 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
                        for (i = 0; i < num_args; i++) {
                                if (ZEND_TYPE_HAS_CLASS(new_arg_info[i].type)) {
                                        ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type)
-                                               && "Only simple classes are currently supported");
+                                               && "Should be stored as simple name");
                                        const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
-                                       ZEND_TYPE_SET_PTR(new_arg_info[i].type,
-                                               zend_string_init_interned(class_name, strlen(class_name), 1));
+
+                                       size_t num_types = 1;
+                                       const char *p = class_name;
+                                       while ((p = strchr(p, '|'))) {
+                                               num_types++;
+                                               p++;
+                                       }
+
+                                       if (num_types == 1) {
+                                               /* Simple class type */
+                                               ZEND_TYPE_SET_PTR(new_arg_info[i].type,
+                                                       zend_string_init_interned(class_name, strlen(class_name), 1));
+                                       } else {
+                                               /* Union type */
+                                               zend_type_list *list = malloc(ZEND_TYPE_LIST_SIZE(num_types));
+                                               list->num_types = num_types;
+                                               ZEND_TYPE_SET_LIST(new_arg_info[i].type, list);
+
+                                               const char *start = class_name;
+                                               uint32_t j = 0;
+                                               while (true) {
+                                                       const char *end = strchr(start, '|');
+                                                       zend_string *str =
+                                                               zend_string_init(start, end ? end - start : strlen(start), 1);
+                                                       list->types[j] = (zend_type) ZEND_TYPE_INIT_CLASS(str, 0, 0);
+                                                       if (!end) {
+                                                               break;
+                                                       }
+                                                       start = end + 1;
+                                                       j++;
+                                               }
+                                       }
                                }
                        }
                }
index 16eafc39ba40107303c5dd58fd0328f2dd6f4bbd..14ebcec1b7aebd84fe56827ffae21678f88e63e4 100644 (file)
@@ -951,6 +951,13 @@ ZEND_API zend_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
 
 static zend_always_inline zend_bool zend_check_type_slow(
                zend_type type, zval *arg, zend_reference *ref, void **cache_slot, zend_class_entry *scope,
@@ -962,31 +969,39 @@ static zend_always_inline zend_bool zend_check_type_slow(
                if (ZEND_TYPE_HAS_LIST(type)) {
                        zend_type *list_type;
                        ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
-                               if (*cache_slot) {
+                               if (HAVE_CACHE_SLOT && *cache_slot) {
                                        ce = *cache_slot;
                                } else {
                                        ce = zend_fetch_class(ZEND_TYPE_NAME(*list_type),
                                                (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
                                        if (!ce) {
-                                               cache_slot++;
+                                               if (HAVE_CACHE_SLOT) {
+                                                       cache_slot++;
+                                               }
                                                continue;
                                        }
-                                       *cache_slot = ce;
+                                       if (HAVE_CACHE_SLOT) {
+                                               *cache_slot = ce;
+                                       }
                                }
                                if (instanceof_function(Z_OBJCE_P(arg), ce)) {
                                        return 1;
                                }
-                               cache_slot++;
+                               if (HAVE_CACHE_SLOT) {
+                                       cache_slot++;
+                               }
                        } ZEND_TYPE_LIST_FOREACH_END();
                } else {
-                       if (EXPECTED(*cache_slot)) {
+                       if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
                                ce = (zend_class_entry *) *cache_slot;
                        } else {
                                ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
                                if (UNEXPECTED(!ce)) {
                                        goto builtin_types;
                                }
-                               *cache_slot = (void *) ce;
+                               if (HAVE_CACHE_SLOT) {
+                                       *cache_slot = (void *) ce;
+                               }
                        }
                        if (instanceof_function(Z_OBJCE_P(arg), ce)) {
                                return 1;
@@ -1079,8 +1094,6 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ
 
        for (i = 0; i < num_args; ++i) {
                zend_arg_info *cur_arg_info;
-               void *dummy_cache_slot = NULL;
-
                if (EXPECTED(i < fbc->common.num_args)) {
                        cur_arg_info = &fbc->common.arg_info[i];
                } else if (UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_VARIADIC)) {
@@ -1090,7 +1103,7 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ
                }
 
                if (ZEND_TYPE_IS_SET(cur_arg_info->type)
-                               && UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &dummy_cache_slot, fbc->common.scope, 0, /* is_internal */ 1))) {
+                               && UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) {
                        return 0;
                }
                arg++;
@@ -1215,8 +1228,6 @@ static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, con
 static bool zend_verify_internal_return_type(zend_function *zf, zval *ret)
 {
        zend_internal_arg_info *ret_info = zf->internal_function.arg_info - 1;
-       void *dummy_cache_slot = NULL;
-
        if (ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_VOID) {
                if (UNEXPECTED(Z_TYPE_P(ret) != IS_NULL)) {
                        zend_verify_void_return_error(zf, zend_zval_type_name(ret), "");
@@ -1225,7 +1236,7 @@ static bool zend_verify_internal_return_type(zend_function *zf, zval *ret)
                return 1;
        }
 
-       if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &dummy_cache_slot, NULL, 1, /* is_internal */ 1))) {
+       if (UNEXPECTED(!zend_check_type(ret_info->type, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) {
                zend_verify_internal_return_error(zf, ret);
                return 0;
        }
index 5d651daaf27355786f0f85d24ed08a0dc518ee2b..6d1ae99ebc837166594f9a39d1095dac6d1a073a 100755 (executable)
@@ -293,20 +293,17 @@ class Type {
         return null;
     }
 
-    public function tryToRepresentableType(): ?RepresentableType {
-        $classType = null;
+    public function toArginfoType(): ?ArginfoType {
+        $classTypes = [];
         $builtinTypes = [];
         foreach ($this->types as $type) {
             if ($type->isBuiltin) {
                 $builtinTypes[] = $type;
-            } else if ($classType === null) {
-                $classType = $type;
             } else {
-                // We can only represent a single class type.
-                return null;
+                $classTypes[] = $type;
             }
         }
-        return new RepresentableType($classType, $builtinTypes);
+        return new ArginfoType($classTypes, $builtinTypes);
     }
 
     public static function equals(?Type $a, ?Type $b): bool {
@@ -339,18 +336,32 @@ class Type {
     }
 }
 
-class RepresentableType {
-    /** @var ?SimpleType $classType */
-    public $classType;
+class ArginfoType {
+    /** @var ClassType[] $classTypes */
+    public $classTypes;
+
     /** @var SimpleType[] $builtinTypes */
-    public $builtinTypes;
+    private $builtinTypes;
 
-    public function __construct(?SimpleType $classType, array $builtinTypes) {
-        $this->classType = $classType;
+    public function __construct(array $classTypes, array $builtinTypes) {
+        $this->classTypes = $classTypes;
         $this->builtinTypes = $builtinTypes;
     }
 
+    public function hasClassType(): bool {
+        return !empty($this->classTypes);
+    }
+
+    public function toClassTypeString(): string {
+        return implode('|', array_map(function(SimpleType $type) {
+            return $type->toEscapedName();
+        }, $this->classTypes));
+    }
+
     public function toTypeMask(): string {
+        if (empty($this->builtinTypes)) {
+            return '0';
+        }
         return implode('|', array_map(function(SimpleType $type) {
             return $type->toTypeMask();
         }, $this->builtinTypes));
@@ -1362,24 +1373,23 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
                     $simpleReturnType->toEscapedName(), $returnType->isNullable()
                 );
             }
-        } else if (null !== $representableType = $returnType->tryToRepresentableType()) {
-            if ($representableType->classType !== null) {
+        } else {
+            $arginfoType = $returnType->toArginfoType();
+            if ($arginfoType->hasClassType()) {
                 $code .= sprintf(
                     "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(%s, %d, %d, %s, %s)\n",
                     $funcInfo->getArgInfoName(), $funcInfo->return->byRef,
                     $funcInfo->numRequiredArgs,
-                    $representableType->classType->toEscapedName(), $representableType->toTypeMask()
+                    $arginfoType->toClassTypeString(), $arginfoType->toTypeMask()
                 );
             } else {
                 $code .= sprintf(
                     "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(%s, %d, %d, %s)\n",
                     $funcInfo->getArgInfoName(), $funcInfo->return->byRef,
                     $funcInfo->numRequiredArgs,
-                    $representableType->toTypeMask()
+                    $arginfoType->toTypeMask()
                 );
             }
-        } else {
-            throw new Exception('Unimplemented');
         }
     } else {
         $code .= sprintf(
@@ -1409,25 +1419,23 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
                         $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
                     );
                 }
-            } else if (null !== $representableType = $argType->tryToRepresentableType()) {
-                if ($representableType->classType !== null) {
+            } else {
+                $arginfoType = $argType->toArginfoType();
+                if ($arginfoType->hasClassType()) {
                     $code .= sprintf(
                         "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s, %s)\n",
                         $argKind, $argInfo->getSendByString(), $argInfo->name,
-                        $representableType->classType->toEscapedName(),
-                        $representableType->toTypeMask(),
+                        $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(),
                         $argInfo->getDefaultValueAsArginfoString()
                     );
                 } else {
                     $code .= sprintf(
                         "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n",
                         $argKind, $argInfo->getSendByString(), $argInfo->name,
-                        $representableType->toTypeMask(),
+                        $arginfoType->toTypeMask(),
                         $argInfo->getDefaultValueAsArginfoString()
                     );
                 }
-            } else {
-                throw new Exception('Unimplemented');
             }
         } else {
             $code .= sprintf(
index 4b345b91476501828282058f2c3f1e30b3f0d1cd..dba2dcc30810689b190a26d721ff21c7bb2e86e5 100644 (file)
@@ -33,17 +33,11 @@ final class FFI
     /** @prefer-ref $ptr */
     public static function addr(FFI\CData $ptr): FFI\CData {}
 
-    /**
-     * @param FFI\CData|FFI\CType $ptr
-     * @prefer-ref $ptr
-     */
-    public static function sizeof($ptr): int {}
+    /** @prefer-ref $ptr */
+    public static function sizeof(FFI\CData|FFI\CType $ptr): int {}
 
-    /**
-     * @param FFI\CData|FFI\CType $ptr
-     * @prefer-ref $ptr
-     */
-    public static function alignof($ptr): int {}
+    /** @prefer-ref $ptr */
+    public static function alignof(FFI\CData|FFI\CType $ptr): int {}
 
     /**
      * @param FFI\CData|string $from
index cbeb1983fc26b3772269db7e88be57fc0601895e..d9e9dec3d07b44e4b179e7588182799f2a9f11ee 100644 (file)
@@ -1,5 +1,5 @@
 /* This is a generated file, edit the .stub.php file instead.
- * Stub hash: 0b4215e4686f4184b2eef0de7d60e01855725924 */
+ * Stub hash: 5aeec68fea7a94cd643464acfb10bf4cfcc863da */
 
 ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_FFI_cdef, 0, 0, FFI, 0)
        ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, code, IS_STRING, 0, "\"\"")
@@ -47,7 +47,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_FFI_addr, 0, 1, FFI\\CData,
 ZEND_END_ARG_INFO()
 
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_FFI_sizeof, 0, 1, IS_LONG, 0)
-       ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr)
+       ZEND_ARG_OBJ_TYPE_MASK(ZEND_SEND_PREFER_REF, ptr, FFI\\CData|FFI\\CType, 0, NULL)
 ZEND_END_ARG_INFO()
 
 #define arginfo_class_FFI_alignof arginfo_class_FFI_sizeof