mirror of
https://github.com/php/php-src.git
synced 2024-11-24 10:24:11 +08:00
Add support for union types for internal functions
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.
This commit is contained in:
parent
bc0f78a2da
commit
973138f39d
@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
/** @var SimpleType[] $builtinTypes */
|
||||
public $builtinTypes;
|
||||
class ArginfoType {
|
||||
/** @var ClassType[] $classTypes */
|
||||
public $classTypes;
|
||||
|
||||
public function __construct(?SimpleType $classType, array $builtinTypes) {
|
||||
$this->classType = $classType;
|
||||
/** @var SimpleType[] $builtinTypes */
|
||||
private $builtinTypes;
|
||||
|
||||
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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user