Skip to content

Add support for class union types for internal functions #6581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
}
}
}
}
}
Expand Down
35 changes: 23 additions & 12 deletions Zend/zend_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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)) {
Expand All @@ -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++;
Expand Down Expand Up @@ -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), "");
Expand All @@ -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;
}
Expand Down
60 changes: 34 additions & 26 deletions build/gen_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -293,20 +293,17 @@ public function tryToSimpleType(): ?SimpleType {
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 {
Expand Down Expand Up @@ -339,18 +336,32 @@ function ($type) { return $type->name; },
}
}

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));
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down
14 changes: 4 additions & 10 deletions ext/ffi/ffi.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,11 @@ public static function arrayType(FFI\CType $type, array $dimensions): FFI\CType
/** @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
Expand Down
4 changes: 2 additions & 2 deletions ext/ffi/ffi_arginfo.h
Original file line number Diff line number Diff line change
@@ -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, "\"\"")
Expand Down Expand Up @@ -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
Expand Down