Skip to content

Commit 973138f

Browse files
committed
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 phpGH-6581.
1 parent bc0f78a commit 973138f

File tree

5 files changed

+96
-53
lines changed

5 files changed

+96
-53
lines changed

Zend/zend_API.c

+33-3
Original file line numberDiff line numberDiff line change
@@ -2455,10 +2455,40 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
24552455
for (i = 0; i < num_args; i++) {
24562456
if (ZEND_TYPE_HAS_CLASS(new_arg_info[i].type)) {
24572457
ZEND_ASSERT(ZEND_TYPE_HAS_NAME(new_arg_info[i].type)
2458-
&& "Only simple classes are currently supported");
2458+
&& "Should be stored as simple name");
24592459
const char *class_name = ZEND_TYPE_LITERAL_NAME(new_arg_info[i].type);
2460-
ZEND_TYPE_SET_PTR(new_arg_info[i].type,
2461-
zend_string_init_interned(class_name, strlen(class_name), 1));
2460+
2461+
size_t num_types = 1;
2462+
const char *p = class_name;
2463+
while ((p = strchr(p, '|'))) {
2464+
num_types++;
2465+
p++;
2466+
}
2467+
2468+
if (num_types == 1) {
2469+
/* Simple class type */
2470+
ZEND_TYPE_SET_PTR(new_arg_info[i].type,
2471+
zend_string_init_interned(class_name, strlen(class_name), 1));
2472+
} else {
2473+
/* Union type */
2474+
zend_type_list *list = malloc(ZEND_TYPE_LIST_SIZE(num_types));
2475+
list->num_types = num_types;
2476+
ZEND_TYPE_SET_LIST(new_arg_info[i].type, list);
2477+
2478+
const char *start = class_name;
2479+
uint32_t j = 0;
2480+
while (true) {
2481+
const char *end = strchr(start, '|');
2482+
zend_string *str =
2483+
zend_string_init(start, end ? end - start : strlen(start), 1);
2484+
list->types[j] = (zend_type) ZEND_TYPE_INIT_CLASS(str, 0, 0);
2485+
if (!end) {
2486+
break;
2487+
}
2488+
start = end + 1;
2489+
j++;
2490+
}
2491+
}
24622492
}
24632493
}
24642494
}

Zend/zend_execute.c

+23-12
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,13 @@ ZEND_API zend_bool zend_value_instanceof_static(zval *zv) {
951951
return instanceof_function(Z_OBJCE_P(zv), called_scope);
952952
}
953953

954+
/* The cache_slot may only be NULL in debug builds, where arginfo verification of
955+
* internal functions is enabled. Avoid unnecessary checks in release builds. */
956+
#if ZEND_DEBUG
957+
# define HAVE_CACHE_SLOT (cache_slot != NULL)
958+
#else
959+
# define HAVE_CACHE_SLOT 1
960+
#endif
954961

955962
static zend_always_inline zend_bool zend_check_type_slow(
956963
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(
962969
if (ZEND_TYPE_HAS_LIST(type)) {
963970
zend_type *list_type;
964971
ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(type), list_type) {
965-
if (*cache_slot) {
972+
if (HAVE_CACHE_SLOT && *cache_slot) {
966973
ce = *cache_slot;
967974
} else {
968975
ce = zend_fetch_class(ZEND_TYPE_NAME(*list_type),
969976
(ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
970977
if (!ce) {
971-
cache_slot++;
978+
if (HAVE_CACHE_SLOT) {
979+
cache_slot++;
980+
}
972981
continue;
973982
}
974-
*cache_slot = ce;
983+
if (HAVE_CACHE_SLOT) {
984+
*cache_slot = ce;
985+
}
975986
}
976987
if (instanceof_function(Z_OBJCE_P(arg), ce)) {
977988
return 1;
978989
}
979-
cache_slot++;
990+
if (HAVE_CACHE_SLOT) {
991+
cache_slot++;
992+
}
980993
} ZEND_TYPE_LIST_FOREACH_END();
981994
} else {
982-
if (EXPECTED(*cache_slot)) {
995+
if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
983996
ce = (zend_class_entry *) *cache_slot;
984997
} else {
985998
ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
986999
if (UNEXPECTED(!ce)) {
9871000
goto builtin_types;
9881001
}
989-
*cache_slot = (void *) ce;
1002+
if (HAVE_CACHE_SLOT) {
1003+
*cache_slot = (void *) ce;
1004+
}
9901005
}
9911006
if (instanceof_function(Z_OBJCE_P(arg), ce)) {
9921007
return 1;
@@ -1079,8 +1094,6 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ
10791094

10801095
for (i = 0; i < num_args; ++i) {
10811096
zend_arg_info *cur_arg_info;
1082-
void *dummy_cache_slot = NULL;
1083-
10841097
if (EXPECTED(i < fbc->common.num_args)) {
10851098
cur_arg_info = &fbc->common.arg_info[i];
10861099
} 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
10901103
}
10911104

10921105
if (ZEND_TYPE_IS_SET(cur_arg_info->type)
1093-
&& UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &dummy_cache_slot, fbc->common.scope, 0, /* is_internal */ 1))) {
1106+
&& UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) {
10941107
return 0;
10951108
}
10961109
arg++;
@@ -1215,8 +1228,6 @@ static ZEND_COLD void zend_verify_void_return_error(const zend_function *zf, con
12151228
static bool zend_verify_internal_return_type(zend_function *zf, zval *ret)
12161229
{
12171230
zend_internal_arg_info *ret_info = zf->internal_function.arg_info - 1;
1218-
void *dummy_cache_slot = NULL;
1219-
12201231
if (ZEND_TYPE_FULL_MASK(ret_info->type) & MAY_BE_VOID) {
12211232
if (UNEXPECTED(Z_TYPE_P(ret) != IS_NULL)) {
12221233
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)
12251236
return 1;
12261237
}
12271238

1228-
if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &dummy_cache_slot, NULL, 1, /* is_internal */ 1))) {
1239+
if (UNEXPECTED(!zend_check_type(ret_info->type, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) {
12291240
zend_verify_internal_return_error(zf, ret);
12301241
return 0;
12311242
}

build/gen_stub.php

+34-26
Original file line numberDiff line numberDiff line change
@@ -293,20 +293,17 @@ public function tryToSimpleType(): ?SimpleType {
293293
return null;
294294
}
295295

296-
public function tryToRepresentableType(): ?RepresentableType {
297-
$classType = null;
296+
public function toArginfoType(): ?ArginfoType {
297+
$classTypes = [];
298298
$builtinTypes = [];
299299
foreach ($this->types as $type) {
300300
if ($type->isBuiltin) {
301301
$builtinTypes[] = $type;
302-
} else if ($classType === null) {
303-
$classType = $type;
304302
} else {
305-
// We can only represent a single class type.
306-
return null;
303+
$classTypes[] = $type;
307304
}
308305
}
309-
return new RepresentableType($classType, $builtinTypes);
306+
return new ArginfoType($classTypes, $builtinTypes);
310307
}
311308

312309
public static function equals(?Type $a, ?Type $b): bool {
@@ -339,18 +336,32 @@ function ($type) { return $type->name; },
339336
}
340337
}
341338

342-
class RepresentableType {
343-
/** @var ?SimpleType $classType */
344-
public $classType;
339+
class ArginfoType {
340+
/** @var ClassType[] $classTypes */
341+
public $classTypes;
342+
345343
/** @var SimpleType[] $builtinTypes */
346-
public $builtinTypes;
344+
private $builtinTypes;
347345

348-
public function __construct(?SimpleType $classType, array $builtinTypes) {
349-
$this->classType = $classType;
346+
public function __construct(array $classTypes, array $builtinTypes) {
347+
$this->classTypes = $classTypes;
350348
$this->builtinTypes = $builtinTypes;
351349
}
352350

351+
public function hasClassType(): bool {
352+
return !empty($this->classTypes);
353+
}
354+
355+
public function toClassTypeString(): string {
356+
return implode('|', array_map(function(SimpleType $type) {
357+
return $type->toEscapedName();
358+
}, $this->classTypes));
359+
}
360+
353361
public function toTypeMask(): string {
362+
if (empty($this->builtinTypes)) {
363+
return '0';
364+
}
354365
return implode('|', array_map(function(SimpleType $type) {
355366
return $type->toTypeMask();
356367
}, $this->builtinTypes));
@@ -1362,24 +1373,23 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
13621373
$simpleReturnType->toEscapedName(), $returnType->isNullable()
13631374
);
13641375
}
1365-
} else if (null !== $representableType = $returnType->tryToRepresentableType()) {
1366-
if ($representableType->classType !== null) {
1376+
} else {
1377+
$arginfoType = $returnType->toArginfoType();
1378+
if ($arginfoType->hasClassType()) {
13671379
$code .= sprintf(
13681380
"ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(%s, %d, %d, %s, %s)\n",
13691381
$funcInfo->getArgInfoName(), $funcInfo->return->byRef,
13701382
$funcInfo->numRequiredArgs,
1371-
$representableType->classType->toEscapedName(), $representableType->toTypeMask()
1383+
$arginfoType->toClassTypeString(), $arginfoType->toTypeMask()
13721384
);
13731385
} else {
13741386
$code .= sprintf(
13751387
"ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(%s, %d, %d, %s)\n",
13761388
$funcInfo->getArgInfoName(), $funcInfo->return->byRef,
13771389
$funcInfo->numRequiredArgs,
1378-
$representableType->toTypeMask()
1390+
$arginfoType->toTypeMask()
13791391
);
13801392
}
1381-
} else {
1382-
throw new Exception('Unimplemented');
13831393
}
13841394
} else {
13851395
$code .= sprintf(
@@ -1409,25 +1419,23 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
14091419
$argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
14101420
);
14111421
}
1412-
} else if (null !== $representableType = $argType->tryToRepresentableType()) {
1413-
if ($representableType->classType !== null) {
1422+
} else {
1423+
$arginfoType = $argType->toArginfoType();
1424+
if ($arginfoType->hasClassType()) {
14141425
$code .= sprintf(
14151426
"\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s, %s)\n",
14161427
$argKind, $argInfo->getSendByString(), $argInfo->name,
1417-
$representableType->classType->toEscapedName(),
1418-
$representableType->toTypeMask(),
1428+
$arginfoType->toClassTypeString(), $arginfoType->toTypeMask(),
14191429
$argInfo->getDefaultValueAsArginfoString()
14201430
);
14211431
} else {
14221432
$code .= sprintf(
14231433
"\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n",
14241434
$argKind, $argInfo->getSendByString(), $argInfo->name,
1425-
$representableType->toTypeMask(),
1435+
$arginfoType->toTypeMask(),
14261436
$argInfo->getDefaultValueAsArginfoString()
14271437
);
14281438
}
1429-
} else {
1430-
throw new Exception('Unimplemented');
14311439
}
14321440
} else {
14331441
$code .= sprintf(

ext/ffi/ffi.stub.php

+4-10
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,11 @@ public static function arrayType(FFI\CType $type, array $dimensions): FFI\CType
3333
/** @prefer-ref $ptr */
3434
public static function addr(FFI\CData $ptr): FFI\CData {}
3535

36-
/**
37-
* @param FFI\CData|FFI\CType $ptr
38-
* @prefer-ref $ptr
39-
*/
40-
public static function sizeof($ptr): int {}
36+
/** @prefer-ref $ptr */
37+
public static function sizeof(FFI\CData|FFI\CType $ptr): int {}
4138

42-
/**
43-
* @param FFI\CData|FFI\CType $ptr
44-
* @prefer-ref $ptr
45-
*/
46-
public static function alignof($ptr): int {}
39+
/** @prefer-ref $ptr */
40+
public static function alignof(FFI\CData|FFI\CType $ptr): int {}
4741

4842
/**
4943
* @param FFI\CData|string $from

ext/ffi/ffi_arginfo.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 0b4215e4686f4184b2eef0de7d60e01855725924 */
2+
* Stub hash: 5aeec68fea7a94cd643464acfb10bf4cfcc863da */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_class_FFI_cdef, 0, 0, FFI, 0)
55
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,
4747
ZEND_END_ARG_INFO()
4848

4949
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_FFI_sizeof, 0, 1, IS_LONG, 0)
50-
ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr)
50+
ZEND_ARG_OBJ_TYPE_MASK(ZEND_SEND_PREFER_REF, ptr, FFI\\CData|FFI\\CType, 0, NULL)
5151
ZEND_END_ARG_INFO()
5252

5353
#define arginfo_class_FFI_alignof arginfo_class_FFI_sizeof

0 commit comments

Comments
 (0)