/* +----------------------------------------------------------------------+ | PHP Version 7 | +----------------------------------------------------------------------+ | Copyright (c) The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Author: Dmitry Stogov | +----------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "php.h" #include "php_ffi.h" #include "ext/standard/info.h" #include "php_scandir.h" #include "zend_exceptions.h" #include "zend_interfaces.h" #include "zend_closures.h" #include "main/SAPI.h" #include #include #include #include #ifdef HAVE_GLOB #ifdef PHP_WIN32 #include "win32/glob.h" #else #include #endif #endif #ifndef __BIGGEST_ALIGNMENT__ /* XXX need something better, perhaps with regard to SIMD, etc. */ # define __BIGGEST_ALIGNMENT__ sizeof(size_t) #endif ZEND_DECLARE_MODULE_GLOBALS(ffi) typedef enum _zend_ffi_tag_kind { ZEND_FFI_TAG_ENUM, ZEND_FFI_TAG_STRUCT, ZEND_FFI_TAG_UNION } zend_ffi_tag_kind; static const char *zend_ffi_tag_kind_name[3] = {"enum", "struct", "union"}; typedef struct _zend_ffi_tag { zend_ffi_tag_kind kind; zend_ffi_type *type; } zend_ffi_tag; typedef enum _zend_ffi_type_kind { ZEND_FFI_TYPE_VOID, ZEND_FFI_TYPE_FLOAT, ZEND_FFI_TYPE_DOUBLE, #ifdef HAVE_LONG_DOUBLE ZEND_FFI_TYPE_LONGDOUBLE, #endif ZEND_FFI_TYPE_UINT8, ZEND_FFI_TYPE_SINT8, ZEND_FFI_TYPE_UINT16, ZEND_FFI_TYPE_SINT16, ZEND_FFI_TYPE_UINT32, ZEND_FFI_TYPE_SINT32, ZEND_FFI_TYPE_UINT64, ZEND_FFI_TYPE_SINT64, ZEND_FFI_TYPE_ENUM, ZEND_FFI_TYPE_BOOL, ZEND_FFI_TYPE_CHAR, ZEND_FFI_TYPE_POINTER, ZEND_FFI_TYPE_FUNC, ZEND_FFI_TYPE_ARRAY, ZEND_FFI_TYPE_STRUCT, } zend_ffi_type_kind; typedef enum _zend_ffi_flags { ZEND_FFI_FLAG_CONST = (1 << 0), ZEND_FFI_FLAG_OWNED = (1 << 1), ZEND_FFI_FLAG_PERSISTENT = (1 << 2), } zend_ffi_flags; struct _zend_ffi_type { zend_ffi_type_kind kind; size_t size; uint32_t align; uint32_t attr; union { struct { zend_string *tag_name; zend_ffi_type_kind kind; } enumeration; struct { zend_ffi_type *type; zend_long length; } array; struct { zend_ffi_type *type; } pointer; struct { zend_string *tag_name; HashTable fields; } record; struct { zend_ffi_type *ret_type; HashTable *args; ffi_abi abi; } func; }; }; typedef struct _zend_ffi_field { size_t offset; zend_bool is_const; zend_bool is_nested; /* part of nested anonymous struct */ uint8_t first_bit; uint8_t bits; zend_ffi_type *type; } zend_ffi_field; typedef enum _zend_ffi_symbol_kind { ZEND_FFI_SYM_TYPE, ZEND_FFI_SYM_CONST, ZEND_FFI_SYM_VAR, ZEND_FFI_SYM_FUNC } zend_ffi_symbol_kind; typedef struct _zend_ffi_symbol { zend_ffi_symbol_kind kind; zend_bool is_const; zend_ffi_type *type; union { void *addr; int64_t value; }; } zend_ffi_symbol; typedef struct _zend_ffi_scope { HashTable *symbols; HashTable *tags; } zend_ffi_scope; typedef struct _zend_ffi { zend_object std; DL_HANDLE lib; HashTable *symbols; HashTable *tags; zend_bool persistent; } zend_ffi; #define ZEND_FFI_TYPE_OWNED (1<<0) #define ZEND_FFI_TYPE(t) \ ((zend_ffi_type*)(((uintptr_t)(t)) & ~ZEND_FFI_TYPE_OWNED)) #define ZEND_FFI_TYPE_IS_OWNED(t) \ (((uintptr_t)(t)) & ZEND_FFI_TYPE_OWNED) #define ZEND_FFI_TYPE_MAKE_OWNED(t) \ ((zend_ffi_type*)(((uintptr_t)(t)) | ZEND_FFI_TYPE_OWNED)) #define ZEND_FFI_SIZEOF_ARG \ MAX(FFI_SIZEOF_ARG, sizeof(double)) typedef struct _zend_ffi_cdata { zend_object std; zend_ffi_type *type; void *ptr; void *ptr_holder; zend_ffi_flags flags; } zend_ffi_cdata; typedef struct _zend_ffi_ctype { zend_object std; zend_ffi_type *type; } zend_ffi_ctype; static zend_class_entry *zend_ffi_exception_ce; static zend_class_entry *zend_ffi_parser_exception_ce; static zend_class_entry *zend_ffi_ce; static zend_class_entry *zend_ffi_cdata_ce; static zend_class_entry *zend_ffi_ctype_ce; static zend_object_handlers zend_ffi_handlers; static zend_object_handlers zend_ffi_cdata_handlers; static zend_object_handlers zend_ffi_cdata_value_handlers; static zend_object_handlers zend_ffi_cdata_free_handlers; static zend_object_handlers zend_ffi_ctype_handlers; static zend_internal_function zend_ffi_new_fn; static zend_internal_function zend_ffi_cast_fn; static zend_internal_function zend_ffi_type_fn; /* forward declarations */ static void _zend_ffi_type_dtor(zend_ffi_type *type); static void zend_ffi_finalize_type(zend_ffi_dcl *dcl); static int zend_ffi_is_same_type(zend_ffi_type *type1, zend_ffi_type *type2); static zend_ffi_type *zend_ffi_remember_type(zend_ffi_type *type); static char *zend_ffi_parse_directives(const char *filename, char *code_pos, char **scope_name, char **lib, zend_bool preload); static ZEND_FUNCTION(ffi_trampoline); static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type); static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type); static ZEND_COLD void zend_ffi_assign_incompatible(zval *arg, zend_ffi_type *type); #if FFI_CLOSURES static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value); #endif static zend_always_inline void zend_ffi_type_dtor(zend_ffi_type *type) /* {{{ */ { if (UNEXPECTED(ZEND_FFI_TYPE_IS_OWNED(type))) { _zend_ffi_type_dtor(type); return; } } /* }}} */ static zend_always_inline void zend_ffi_object_init(zend_object *object, zend_class_entry *ce) /* {{{ */ { GC_SET_REFCOUNT(object, 1); GC_TYPE_INFO(object) = IS_OBJECT | ((GC_COLLECTABLE | IS_OBJ_DESTRUCTOR_CALLED) << GC_FLAGS_SHIFT); object->ce = ce; object->properties = NULL; zend_objects_store_put(object); } /* }}} */ static zend_object *zend_ffi_cdata_new(zend_class_entry *class_type) /* {{{ */ { zend_ffi_cdata *cdata; cdata = emalloc(sizeof(zend_ffi_cdata)); zend_ffi_object_init(&cdata->std, class_type); cdata->std.handlers = &zend_ffi_cdata_handlers; cdata->type = NULL; cdata->ptr = NULL; cdata->flags = 0; return &cdata->std; } /* }}} */ static int zend_ffi_is_compatible_type(zend_ffi_type *dst_type, zend_ffi_type *src_type) /* {{{ */ { while (1) { if (dst_type == src_type) { return 1; } else if (dst_type->kind == src_type->kind) { if (dst_type->kind < ZEND_FFI_TYPE_POINTER) { return 1; } else if (dst_type->kind == ZEND_FFI_TYPE_POINTER) { dst_type = ZEND_FFI_TYPE(dst_type->pointer.type); src_type = ZEND_FFI_TYPE(src_type->pointer.type); if (dst_type->kind == ZEND_FFI_TYPE_VOID || src_type->kind == ZEND_FFI_TYPE_VOID) { return 1; } } else if (dst_type->kind == ZEND_FFI_TYPE_ARRAY && (dst_type->array.length == src_type->array.length || dst_type->array.length == 0)) { dst_type = ZEND_FFI_TYPE(dst_type->array.type); src_type = ZEND_FFI_TYPE(src_type->array.type); } else { break; } } else if (dst_type->kind == ZEND_FFI_TYPE_POINTER && src_type->kind == ZEND_FFI_TYPE_ARRAY) { dst_type = ZEND_FFI_TYPE(dst_type->pointer.type); src_type = ZEND_FFI_TYPE(src_type->array.type); if (dst_type->kind == ZEND_FFI_TYPE_VOID) { return 1; } } else { break; } } return 0; } /* }}} */ static ffi_type *zend_ffi_make_fake_struct_type(zend_ffi_type *type) /* {{{ */ { ffi_type *t = emalloc(sizeof(ffi_type) + sizeof(ffi_type*) * (zend_hash_num_elements(&type->record.fields) + 1)); int i; zend_ffi_field *field; t->size = type->size; t->alignment = type->align; t->type = FFI_TYPE_STRUCT; t->elements = (ffi_type**)(t + 1); i = 0; ZEND_HASH_FOREACH_PTR(&type->record.fields, field) { switch (ZEND_FFI_TYPE(field->type)->kind) { case ZEND_FFI_TYPE_FLOAT: t->elements[i] = &ffi_type_float; break; case ZEND_FFI_TYPE_DOUBLE: t->elements[i] = &ffi_type_double; break; #ifndef PHP_WIN32 case ZEND_FFI_TYPE_LONGDOUBLE: t->elements[i] = &ffi_type_longdouble; break; #endif case ZEND_FFI_TYPE_SINT8: case ZEND_FFI_TYPE_UINT8: case ZEND_FFI_TYPE_BOOL: case ZEND_FFI_TYPE_CHAR: t->elements[i] = &ffi_type_uint8; break; case ZEND_FFI_TYPE_SINT16: case ZEND_FFI_TYPE_UINT16: t->elements[i] = &ffi_type_uint16; break; case ZEND_FFI_TYPE_SINT32: case ZEND_FFI_TYPE_UINT32: t->elements[i] = &ffi_type_uint32; break; case ZEND_FFI_TYPE_SINT64: case ZEND_FFI_TYPE_UINT64: t->elements[i] = &ffi_type_uint64; break; case ZEND_FFI_TYPE_POINTER: t->elements[i] = &ffi_type_pointer; break; default: efree(t); return NULL; } i++; } ZEND_HASH_FOREACH_END(); t->elements[i] = NULL; return t; } /* }}} */ static ffi_type *zend_ffi_get_type(zend_ffi_type *type) /* {{{ */ { zend_ffi_type_kind kind = type->kind; again: switch (kind) { case ZEND_FFI_TYPE_FLOAT: return &ffi_type_float; case ZEND_FFI_TYPE_DOUBLE: return &ffi_type_double; #ifndef PHP_WIN32 case ZEND_FFI_TYPE_LONGDOUBLE: return &ffi_type_longdouble; #endif case ZEND_FFI_TYPE_UINT8: return &ffi_type_uint8; case ZEND_FFI_TYPE_SINT8: return &ffi_type_sint8; case ZEND_FFI_TYPE_UINT16: return &ffi_type_uint16; case ZEND_FFI_TYPE_SINT16: return &ffi_type_sint16; case ZEND_FFI_TYPE_UINT32: return &ffi_type_uint32; case ZEND_FFI_TYPE_SINT32: return &ffi_type_sint32; case ZEND_FFI_TYPE_UINT64: return &ffi_type_uint64; case ZEND_FFI_TYPE_SINT64: return &ffi_type_sint64; case ZEND_FFI_TYPE_POINTER: return &ffi_type_pointer; case ZEND_FFI_TYPE_VOID: return &ffi_type_void; case ZEND_FFI_TYPE_BOOL: return &ffi_type_uint8; case ZEND_FFI_TYPE_CHAR: return &ffi_type_sint8; case ZEND_FFI_TYPE_ENUM: kind = type->enumeration.kind; goto again; case ZEND_FFI_TYPE_STRUCT: if (!(type->attr & ZEND_FFI_ATTR_UNION)) { ffi_type *t = zend_ffi_make_fake_struct_type(type); return t; } break; default: break; } return NULL; } /* }}} */ static zend_never_inline zend_ffi_cdata *zend_ffi_cdata_to_zval_slow(void *ptr, zend_ffi_type *type, zend_ffi_flags flags) /* {{{ */ { zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata)); zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce); cdata->std.handlers = (type->kind < ZEND_FFI_TYPE_POINTER) ? &zend_ffi_cdata_value_handlers : &zend_ffi_cdata_handlers; cdata->type = type; cdata->flags = flags; cdata->ptr = ptr; return cdata; } /* }}} */ static zend_never_inline zend_ffi_cdata *zend_ffi_cdata_to_zval_slow_ptr(void *ptr, zend_ffi_type *type, zend_ffi_flags flags) /* {{{ */ { zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata)); zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce); cdata->std.handlers = &zend_ffi_cdata_handlers; cdata->type = type; cdata->flags = flags; cdata->ptr = (void*)&cdata->ptr_holder; *(void**)cdata->ptr = *(void**)ptr; return cdata; } /* }}} */ static zend_never_inline zend_ffi_cdata *zend_ffi_cdata_to_zval_slow_ret(void *ptr, zend_ffi_type *type, zend_ffi_flags flags) /* {{{ */ { zend_ffi_cdata *cdata = emalloc(sizeof(zend_ffi_cdata)); zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce); cdata->std.handlers = (type->kind < ZEND_FFI_TYPE_POINTER) ? &zend_ffi_cdata_value_handlers : &zend_ffi_cdata_handlers; cdata->type = type; cdata->flags = flags; if (type->kind == ZEND_FFI_TYPE_POINTER) { cdata->ptr = (void*)&cdata->ptr_holder; *(void**)cdata->ptr = *(void**)ptr; } else if (type->kind == ZEND_FFI_TYPE_STRUCT) { cdata->ptr = emalloc(type->size); cdata->flags |= ZEND_FFI_FLAG_OWNED; memcpy(cdata->ptr, ptr, type->size); } else { cdata->ptr = ptr; } return cdata; } /* }}} */ static zend_always_inline void zend_ffi_cdata_to_zval(zend_ffi_cdata *cdata, void *ptr, zend_ffi_type *type, int read_type, zval *rv, zend_ffi_flags flags, zend_bool is_ret, zend_bool debug_union) /* {{{ */ { if (read_type == BP_VAR_R) { zend_ffi_type_kind kind = type->kind; again: switch (kind) { case ZEND_FFI_TYPE_FLOAT: ZVAL_DOUBLE(rv, *(float*)ptr); return; case ZEND_FFI_TYPE_DOUBLE: ZVAL_DOUBLE(rv, *(double*)ptr); return; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: ZVAL_DOUBLE(rv, *(long double*)ptr); return; #endif case ZEND_FFI_TYPE_UINT8: ZVAL_LONG(rv, *(uint8_t*)ptr); return; case ZEND_FFI_TYPE_SINT8: ZVAL_LONG(rv, *(int8_t*)ptr); return; case ZEND_FFI_TYPE_UINT16: ZVAL_LONG(rv, *(uint16_t*)ptr); return; case ZEND_FFI_TYPE_SINT16: ZVAL_LONG(rv, *(int16_t*)ptr); return; case ZEND_FFI_TYPE_UINT32: ZVAL_LONG(rv, *(uint32_t*)ptr); return; case ZEND_FFI_TYPE_SINT32: ZVAL_LONG(rv, *(int32_t*)ptr); return; case ZEND_FFI_TYPE_UINT64: ZVAL_LONG(rv, *(uint64_t*)ptr); return; case ZEND_FFI_TYPE_SINT64: ZVAL_LONG(rv, *(int64_t*)ptr); return; case ZEND_FFI_TYPE_BOOL: ZVAL_BOOL(rv, *(uint8_t*)ptr); return; case ZEND_FFI_TYPE_CHAR: ZVAL_INTERNED_STR(rv, ZSTR_CHAR(*(unsigned char*)ptr)); return; case ZEND_FFI_TYPE_ENUM: kind = type->enumeration.kind; goto again; case ZEND_FFI_TYPE_POINTER: if (*(void**)ptr == NULL) { ZVAL_NULL(rv); return; } else if (debug_union) { ZVAL_STR(rv, zend_strpprintf(0, "%p", *(void**)ptr)); return; } else if ((type->attr & ZEND_FFI_ATTR_CONST) && ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { ZVAL_STRING(rv, *(char**)ptr); return; } if (!cdata) { if (is_ret) { cdata = zend_ffi_cdata_to_zval_slow_ret(ptr, type, flags); } else { cdata = zend_ffi_cdata_to_zval_slow_ptr(ptr, type, flags); } } else { GC_ADDREF(&cdata->std); } ZVAL_OBJ(rv, &cdata->std); return; default: break; } } if (!cdata) { if (is_ret) { cdata = zend_ffi_cdata_to_zval_slow_ret(ptr, type, flags); } else { cdata = zend_ffi_cdata_to_zval_slow(ptr, type, flags); } } else { GC_ADDREF(&cdata->std); } ZVAL_OBJ(rv, &cdata->std); } /* }}} */ static uint64_t zend_ffi_bit_field_read(void *ptr, zend_ffi_field *field) /* {{{ */ { size_t bit = field->first_bit; size_t last_bit = bit + field->bits - 1; uint8_t *p = (uint8_t *) ptr + bit / 8; uint8_t *last_p = (uint8_t *) ptr + last_bit / 8; size_t pos = bit % 8; size_t insert_pos = 0; uint8_t mask; uint64_t val = 0; /* Bitfield fits into a single byte */ if (p == last_p) { mask = (1U << field->bits) - 1U; return (*p >> pos) & mask; } /* Read partial prefix byte */ if (pos != 0) { size_t num_bits = 8 - pos; mask = ((1U << num_bits) - 1U) << pos; val = (*p++ >> pos) & mask; insert_pos += num_bits; } /* Read full bytes */ while (p < last_p) { val |= *p++ << insert_pos; insert_pos += 8; } /* Read partial suffix byte */ if (p == last_p) { size_t num_bits = last_bit % 8 + 1; mask = (1U << num_bits) - 1U; val |= (*p & mask) << insert_pos; } return val; } /* }}} */ static void zend_ffi_bit_field_to_zval(void *ptr, zend_ffi_field *field, zval *rv) /* {{{ */ { uint64_t val = zend_ffi_bit_field_read(ptr, field); if (ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_CHAR || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT8 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT16 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT32 || ZEND_FFI_TYPE(field->type)->kind == ZEND_FFI_TYPE_SINT64) { /* Sign extend */ uint64_t shift = 64 - (field->bits % 64); if (shift != 0) { val = (int64_t)(val << shift) >> shift; } } ZVAL_LONG(rv, val); } /* }}} */ static int zend_ffi_zval_to_bit_field(void *ptr, zend_ffi_field *field, zval *value) /* {{{ */ { uint64_t val = zval_get_long(value); size_t bit = field->first_bit; size_t last_bit = bit + field->bits - 1; uint8_t *p = (uint8_t *) ptr + bit / 8; uint8_t *last_p = (uint8_t *) ptr + last_bit / 8; size_t pos = bit % 8; uint8_t mask; /* Bitfield fits into a single byte */ if (p == last_p) { mask = ((1U << field->bits) - 1U) << pos; *p = (*p & ~mask) | ((val << pos) & mask); return SUCCESS; } /* Write partial prefix byte */ if (pos != 0) { size_t num_bits = 8 - pos; mask = ((1U << num_bits) - 1U) << pos; *p = (*p & ~mask) | ((val << pos) & mask); p++; val >>= num_bits; } /* Write full bytes */ while (p < last_p) { *p++ = val; val >>= 8; } /* Write partial suffix byte */ if (p == last_p) { size_t num_bits = last_bit % 8 + 1; mask = (1U << num_bits) - 1U; *p = (*p & ~mask) | (val & mask); } return SUCCESS; } /* }}} */ static zend_always_inline int zend_ffi_zval_to_cdata(void *ptr, zend_ffi_type *type, zval *value) /* {{{ */ { zend_long lval; double dval; zend_string *tmp_str; zend_string *str; zend_ffi_type_kind kind = type->kind; again: switch (kind) { case ZEND_FFI_TYPE_FLOAT: dval = zval_get_double(value); *(float*)ptr = dval; break; case ZEND_FFI_TYPE_DOUBLE: dval = zval_get_double(value); *(double*)ptr = dval; break; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: dval = zval_get_double(value); *(long double*)ptr = dval; break; #endif case ZEND_FFI_TYPE_UINT8: lval = zval_get_long(value); *(uint8_t*)ptr = lval; break; case ZEND_FFI_TYPE_SINT8: lval = zval_get_long(value); *(int8_t*)ptr = lval; break; case ZEND_FFI_TYPE_UINT16: lval = zval_get_long(value); *(uint16_t*)ptr = lval; break; case ZEND_FFI_TYPE_SINT16: lval = zval_get_long(value); *(int16_t*)ptr = lval; break; case ZEND_FFI_TYPE_UINT32: lval = zval_get_long(value); *(uint32_t*)ptr = lval; break; case ZEND_FFI_TYPE_SINT32: lval = zval_get_long(value); *(int32_t*)ptr = lval; break; case ZEND_FFI_TYPE_UINT64: lval = zval_get_long(value); *(uint64_t*)ptr = lval; break; case ZEND_FFI_TYPE_SINT64: lval = zval_get_long(value); *(int64_t*)ptr = lval; break; case ZEND_FFI_TYPE_BOOL: *(uint8_t*)ptr = zend_is_true(value); break; case ZEND_FFI_TYPE_CHAR: str = zval_get_tmp_string(value, &tmp_str); if (ZSTR_LEN(str) == 1) { *(char*)ptr = ZSTR_VAL(str)[0]; } else { zend_ffi_assign_incompatible(value, type); return FAILURE; } zend_tmp_string_release(tmp_str); break; case ZEND_FFI_TYPE_ENUM: kind = type->enumeration.kind; goto again; case ZEND_FFI_TYPE_POINTER: if (Z_TYPE_P(value) == IS_NULL) { *(void**)ptr = NULL; break; } else if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value); if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type))) { if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) { *(void**)ptr = *(void**)cdata->ptr; } else { if (cdata->flags & ZEND_FFI_FLAG_OWNED) { zend_throw_error(zend_ffi_exception_ce, "Attempt to perform assign of owned C pointer"); return FAILURE; } *(void**)ptr = cdata->ptr; } return SUCCESS; /* Allow transparent assignment of not-owned CData to compatible pointers */ } else if (ZEND_FFI_TYPE(cdata->type)->kind != ZEND_FFI_TYPE_POINTER && zend_ffi_is_compatible_type(ZEND_FFI_TYPE(type->pointer.type), ZEND_FFI_TYPE(cdata->type))) { if (cdata->flags & ZEND_FFI_FLAG_OWNED) { zend_throw_error(zend_ffi_exception_ce, "Attempt to perform assign pointer to owned C data"); return FAILURE; } *(void**)ptr = cdata->ptr; return SUCCESS; } #if FFI_CLOSURES } else if (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_FUNC) { void *callback = zend_ffi_create_callback(ZEND_FFI_TYPE(type->pointer.type), value); if (callback) { *(void**)ptr = callback; break; } else { return FAILURE; } #endif } zend_ffi_assign_incompatible(value, type); return FAILURE; case ZEND_FFI_TYPE_STRUCT: case ZEND_FFI_TYPE_ARRAY: default: if (Z_TYPE_P(value) == IS_OBJECT && Z_OBJCE_P(value) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(value); if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type)) && type->size == ZEND_FFI_TYPE(cdata->type)->size) { memcpy(ptr, cdata->ptr, type->size); return SUCCESS; } } zend_ffi_assign_incompatible(value, type); return FAILURE; } return SUCCESS; } /* }}} */ #if defined(ZEND_WIN32) && (defined(HAVE_FFI_FASTCALL) || defined(HAVE_FFI_STDCALL) || defined(HAVE_FFI_VECTORCALL_PARTIAL)) static size_t zend_ffi_arg_size(zend_ffi_type *type) /* {{{ */ { zend_ffi_type *arg_type; size_t arg_size = 0; ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) { arg_size += MAX(ZEND_FFI_TYPE(arg_type)->size, sizeof(size_t)); } ZEND_HASH_FOREACH_END(); return arg_size; } /* }}} */ #endif static zend_always_inline zend_string *zend_ffi_mangled_func_name(zend_string *name, zend_ffi_type *type) /* {{{ */ { #ifdef ZEND_WIN32 switch (type->func.abi) { # ifdef HAVE_FFI_FASTCALL case FFI_FASTCALL: return strpprintf(0, "@%s@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type)); # endif # ifdef HAVE_FFI_STDCALL case FFI_STDCALL: return strpprintf(0, "_%s@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type)); # endif # ifdef HAVE_FFI_VECTORCALL_PARTIAL case FFI_VECTORCALL_PARTIAL: return strpprintf(0, "%s@@%zu", ZSTR_VAL(name), zend_ffi_arg_size(type)); # endif } #endif return zend_string_copy(name); } /* }}} */ #if FFI_CLOSURES typedef struct _zend_ffi_callback_data { zend_fcall_info_cache fcc; zend_ffi_type *type; void *code; void *callback; ffi_cif cif; uint32_t arg_count; ffi_type *ret_type; ffi_type *arg_types[0]; } zend_ffi_callback_data; static void zend_ffi_callback_hash_dtor(zval *zv) /* {{{ */ { zend_ffi_callback_data *callback_data = Z_PTR_P(zv); ffi_closure_free(callback_data->callback); if (callback_data->fcc.function_handler->common.fn_flags & ZEND_ACC_CLOSURE) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(callback_data->fcc.function_handler)); } efree(callback_data); } /* }}} */ static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, void* data) /* {{{ */ { zend_ffi_callback_data *callback_data = (zend_ffi_callback_data*)data; zend_fcall_info fci; zend_ffi_type *ret_type; zval retval; ALLOCA_FLAG(use_heap) fci.size = sizeof(zend_fcall_info); ZVAL_UNDEF(&fci.function_name); fci.retval = &retval; fci.params = do_alloca(sizeof(zval) *callback_data->arg_count, use_heap); fci.object = NULL; fci.no_separation = 1; fci.param_count = callback_data->arg_count; if (callback_data->type->func.args) { int n = 0; zend_ffi_type *arg_type; ZEND_HASH_FOREACH_PTR(callback_data->type->func.args, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); zend_ffi_cdata_to_zval(NULL, args[n], arg_type, BP_VAR_R, &fci.params[n], (zend_ffi_flags)(arg_type->attr & ZEND_FFI_ATTR_CONST), 0, 0); n++; } ZEND_HASH_FOREACH_END(); } ZVAL_UNDEF(&retval); if (zend_call_function(&fci, &callback_data->fcc) != SUCCESS) { zend_throw_error(zend_ffi_exception_ce, "Cannot call callback"); } if (callback_data->arg_count) { int n = 0; for (n = 0; n < callback_data->arg_count; n++) { zval_ptr_dtor(&fci.params[n]); } } free_alloca(fci.params, use_heap); if (EG(exception)) { zend_error(E_ERROR, "Throwing from FFI callbacks is not allowed"); } ret_type = ZEND_FFI_TYPE(callback_data->type->func.ret_type); if (ret_type->kind != ZEND_FFI_TYPE_VOID) { zend_ffi_zval_to_cdata(ret, ret_type, &retval); } } /* }}} */ static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value) /* {{{ */ { zend_fcall_info_cache fcc; char *error = NULL; uint32_t arg_count; void *code; void *callback; zend_ffi_callback_data *callback_data; if (type->attr & ZEND_FFI_ATTR_VARIADIC) { zend_throw_error(zend_ffi_exception_ce, "Variadic function closures are not supported"); return NULL; } if (!zend_is_callable_ex(value, NULL, 0, NULL, &fcc, &error)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign an invalid callback, %s", error); return NULL; } arg_count = type->func.args ? zend_hash_num_elements(type->func.args) : 0; if (arg_count < fcc.function_handler->common.required_num_args) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign an invalid callback, insufficient number of arguments"); return NULL; } callback = ffi_closure_alloc(sizeof(ffi_closure), &code); if (!callback) { zend_throw_error(zend_ffi_exception_ce, "Cannot allocate callback"); return NULL; } callback_data = emalloc(sizeof(zend_ffi_callback_data) + sizeof(ffi_type*) * arg_count); memcpy(&callback_data->fcc, &fcc, sizeof(zend_fcall_info_cache)); callback_data->type = type; callback_data->callback = callback; callback_data->code = code; callback_data->arg_count = arg_count; if (type->func.args) { int n = 0; zend_ffi_type *arg_type; ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); callback_data->arg_types[n] = zend_ffi_get_type(arg_type); if (!callback_data->arg_types[n]) { zend_ffi_pass_unsupported(arg_type); efree(callback_data); ffi_closure_free(callback); return NULL; } n++; } ZEND_HASH_FOREACH_END(); } callback_data->ret_type = zend_ffi_get_type(ZEND_FFI_TYPE(type->func.ret_type)); if (!callback_data->ret_type) { zend_ffi_return_unsupported(type->func.ret_type); efree(callback_data); ffi_closure_free(callback); return NULL; } if (ffi_prep_cif(&callback_data->cif, type->func.abi, callback_data->arg_count, callback_data->ret_type, callback_data->arg_types) != FFI_OK) { zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback CIF"); efree(callback_data); ffi_closure_free(callback); return NULL; } if (ffi_prep_closure_loc(callback, &callback_data->cif, zend_ffi_callback_trampoline, callback_data, code) != FFI_OK) { zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback"); efree(callback_data); ffi_closure_free(callback); return NULL; } if (!FFI_G(callbacks)) { FFI_G(callbacks) = emalloc(sizeof(HashTable)); zend_hash_init(FFI_G(callbacks), 0, NULL, zend_ffi_callback_hash_dtor, 0); } zend_hash_next_index_insert_ptr(FFI_G(callbacks), callback_data); if (fcc.function_handler->common.fn_flags & ZEND_ACC_CLOSURE) { GC_ADDREF(ZEND_CLOSURE_OBJECT(fcc.function_handler)); } return code; } /* }}} */ #endif static zval *zend_ffi_cdata_get(zval *object, zval *member, int read_type, void **cache_slot, zval *rv) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); #if 0 if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return &EG(uninitialized_zval); } #endif if (UNEXPECTED(Z_TYPE_P(member) != IS_STRING) || UNEXPECTED(!zend_string_equals_literal(Z_STR_P(member), "cdata"))) { zend_throw_error(zend_ffi_exception_ce, "only 'cdata' property may be read"); return &EG(uninitialized_zval);; } zend_ffi_cdata_to_zval(cdata, cdata->ptr, type, BP_VAR_R, rv, 0, 0, 0); return rv; } /* }}} */ static zval *zend_ffi_cdata_set(zval *object, zval *member, zval *value, void **cache_slot) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); #if 0 if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return &EG(uninitialized_zval);; } #endif if (UNEXPECTED(Z_TYPE_P(member) != IS_STRING) || UNEXPECTED(!zend_string_equals_literal(Z_STR_P(member), "cdata"))) { zend_throw_error(zend_ffi_exception_ce, "only 'cdata' property may be set"); return &EG(uninitialized_zval);; } zend_ffi_zval_to_cdata(cdata->ptr, type, value); return value; } /* }}} */ static int zend_ffi_cdata_cast_object(zval *readobj, zval *writeobj, int type) /* {{{ */ { if (type == IS_STRING) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(readobj); zend_ffi_type *ctype = ZEND_FFI_TYPE(cdata->type); void *ptr = cdata->ptr; zend_ffi_type_kind kind = ctype->kind; again: switch (kind) { case ZEND_FFI_TYPE_FLOAT: ZVAL_DOUBLE(writeobj, *(float*)ptr); break; case ZEND_FFI_TYPE_DOUBLE: ZVAL_DOUBLE(writeobj, *(double*)ptr); break; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: ZVAL_DOUBLE(writeobj, *(long double*)ptr); break; #endif case ZEND_FFI_TYPE_UINT8: ZVAL_LONG(writeobj, *(uint8_t*)ptr); break; case ZEND_FFI_TYPE_SINT8: ZVAL_LONG(writeobj, *(int8_t*)ptr); break; case ZEND_FFI_TYPE_UINT16: ZVAL_LONG(writeobj, *(uint16_t*)ptr); break; case ZEND_FFI_TYPE_SINT16: ZVAL_LONG(writeobj, *(int16_t*)ptr); break; case ZEND_FFI_TYPE_UINT32: ZVAL_LONG(writeobj, *(uint32_t*)ptr); break; case ZEND_FFI_TYPE_SINT32: ZVAL_LONG(writeobj, *(int32_t*)ptr); break; case ZEND_FFI_TYPE_UINT64: ZVAL_LONG(writeobj, *(uint64_t*)ptr); break; case ZEND_FFI_TYPE_SINT64: ZVAL_LONG(writeobj, *(int64_t*)ptr); break; case ZEND_FFI_TYPE_BOOL: ZVAL_BOOL(writeobj, *(uint8_t*)ptr); break; case ZEND_FFI_TYPE_CHAR: ZVAL_INTERNED_STR(writeobj, ZSTR_CHAR(*(unsigned char*)ptr)); return SUCCESS; case ZEND_FFI_TYPE_ENUM: kind = ctype->enumeration.kind; goto again; case ZEND_FFI_TYPE_POINTER: if (*(void**)ptr == NULL) { ZVAL_NULL(writeobj); break; } else if ((ctype->attr & ZEND_FFI_ATTR_CONST) && ZEND_FFI_TYPE(ctype->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { ZVAL_STRING(writeobj, *(char**)ptr); return SUCCESS; } return FAILURE; default: return FAILURE; } convert_to_string(writeobj); return SUCCESS; } return FAILURE; } /* }}} */ static zval *zend_ffi_cdata_read_field(zval *object, zval *member, int read_type, void **cache_slot, zval *rv) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); void *ptr = cdata->ptr; zend_ffi_field *field; if (cache_slot && *cache_slot == type) { field = *(cache_slot + 1); } else { zend_string *tmp_field_name; zend_string *field_name = zval_get_tmp_string(member, &tmp_field_name); if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) { if (type->kind == ZEND_FFI_TYPE_POINTER) { /* transparently dereference the pointer */ if (UNEXPECTED(!ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); zend_tmp_string_release(tmp_field_name); return &EG(uninitialized_zval); } ptr = (void*)(*(char**)ptr); if (UNEXPECTED(!ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); zend_tmp_string_release(tmp_field_name); return &EG(uninitialized_zval); } type = ZEND_FFI_TYPE(type->pointer.type); } if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to read field '%s' of non C struct/union", ZSTR_VAL(field_name)); zend_tmp_string_release(tmp_field_name); return &EG(uninitialized_zval); } } field = zend_hash_find_ptr(&type->record.fields, field_name); if (UNEXPECTED(!field)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to read undefined field '%s' of C struct/union", ZSTR_VAL(field_name)); zend_tmp_string_release(tmp_field_name); return &EG(uninitialized_zval); } zend_tmp_string_release(tmp_field_name); if (cache_slot) { *cache_slot = type; *(cache_slot + 1) = field; } } #if 0 if (UNEXPECTED(!ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return &EG(uninitialized_zval); } #endif if (EXPECTED(!field->bits)) { zend_ffi_type *field_type = field->type; if (ZEND_FFI_TYPE_IS_OWNED(field_type)) { field_type = ZEND_FFI_TYPE(field_type); if (!(field_type->attr & ZEND_FFI_ATTR_STORED) && field_type->kind == ZEND_FFI_TYPE_POINTER) { field->type = field_type = zend_ffi_remember_type(field_type); } } ptr = (void*)(((char*)ptr) + field->offset); zend_ffi_cdata_to_zval(NULL, ptr, field_type, read_type, rv, (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)field->is_const, 0, 0); } else { zend_ffi_bit_field_to_zval(ptr, field, rv); } return rv; } /* }}} */ static zval *zend_ffi_cdata_write_field(zval *object, zval *member, zval *value, void **cache_slot) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); void *ptr = cdata->ptr; zend_ffi_field *field; if (cache_slot && *cache_slot == type) { field = *(cache_slot + 1); } else { zend_string *tmp_field_name; zend_string *field_name = zval_get_tmp_string(member, &tmp_field_name); if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) { if (type->kind == ZEND_FFI_TYPE_POINTER) { /* transparently dereference the pointer */ if (UNEXPECTED(!ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); zend_tmp_string_release(tmp_field_name); return value; } ptr = (void*)(*(char**)ptr); if (UNEXPECTED(!ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); zend_tmp_string_release(tmp_field_name); return value; } type = ZEND_FFI_TYPE(type->pointer.type); } if (UNEXPECTED(type->kind != ZEND_FFI_TYPE_STRUCT)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign field '%s' of non C struct/union", ZSTR_VAL(field_name)); zend_tmp_string_release(tmp_field_name); return value; } } field = zend_hash_find_ptr(&type->record.fields, field_name); if (UNEXPECTED(!field)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign undefined field '%s' of C struct/union", ZSTR_VAL(field_name)); zend_tmp_string_release(tmp_field_name); return value; } zend_tmp_string_release(tmp_field_name); if (cache_slot) { *cache_slot = type; *(cache_slot + 1) = field; } } #if 0 if (UNEXPECTED(!ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return value; } #endif if (UNEXPECTED(cdata->flags & ZEND_FFI_FLAG_CONST)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only location"); return value; } else if (UNEXPECTED(field->is_const)) { zend_string *tmp_field_name; zend_string *field_name = zval_get_tmp_string(member, &tmp_field_name); zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only field '%s'", ZSTR_VAL(field_name)); zend_tmp_string_release(tmp_field_name); return value; } if (EXPECTED(!field->bits)) { ptr = (void*)(((char*)ptr) + field->offset); zend_ffi_zval_to_cdata(ptr, ZEND_FFI_TYPE(field->type), value); } else { zend_ffi_zval_to_bit_field(ptr, field, value); } return value; } /* }}} */ static zval *zend_ffi_cdata_read_dim(zval *object, zval *offset, int read_type, zval *rv) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); zend_long dim = zval_get_long(offset); zend_ffi_type *dim_type; void *ptr; zend_ffi_flags is_const; if (EXPECTED(type->kind == ZEND_FFI_TYPE_ARRAY)) { if (UNEXPECTED((zend_ulong)(dim) >= (zend_ulong)type->array.length) && (UNEXPECTED(dim < 0) || UNEXPECTED(type->array.length != 0))) { zend_throw_error(zend_ffi_exception_ce, "C array index out of bounds"); return &EG(uninitialized_zval); } is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST); dim_type = type->array.type; if (ZEND_FFI_TYPE_IS_OWNED(dim_type)) { dim_type = ZEND_FFI_TYPE(dim_type); if (!(dim_type->attr & ZEND_FFI_ATTR_STORED) && dim_type->kind == ZEND_FFI_TYPE_POINTER) { type->array.type = dim_type = zend_ffi_remember_type(dim_type); } } #if 0 if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return &EG(uninitialized_zval); } #endif ptr = (void*)(((char*)cdata->ptr) + dim_type->size * dim); } else if (EXPECTED(type->kind == ZEND_FFI_TYPE_POINTER)) { is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST); dim_type = type->pointer.type; if (ZEND_FFI_TYPE_IS_OWNED(dim_type)) { dim_type = ZEND_FFI_TYPE(dim_type); if (!(dim_type->attr & ZEND_FFI_ATTR_STORED) && dim_type->kind == ZEND_FFI_TYPE_POINTER) { type->pointer.type = dim_type = zend_ffi_remember_type(dim_type); } } if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return &EG(uninitialized_zval); } ptr = (void*)((*(char**)cdata->ptr) + dim_type->size * dim); } else { zend_throw_error(zend_ffi_exception_ce, "Attempt to read element of non C array"); return &EG(uninitialized_zval); } zend_ffi_cdata_to_zval(NULL, ptr, dim_type, read_type, rv, is_const, 0, 0); return rv; } /* }}} */ static void zend_ffi_cdata_write_dim(zval *object, zval *offset, zval *value) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); zend_long dim; void *ptr; zend_ffi_flags is_const; if (offset == NULL) { zend_throw_error(zend_ffi_exception_ce, "Cannot add next element to object of type FFI\\CData"); return; } dim = zval_get_long(offset); if (EXPECTED(type->kind == ZEND_FFI_TYPE_ARRAY)) { if (UNEXPECTED((zend_ulong)(dim) >= (zend_ulong)type->array.length) && (UNEXPECTED(dim < 0) || UNEXPECTED(type->array.length != 0))) { zend_throw_error(zend_ffi_exception_ce, "C array index out of bounds"); return; } is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST); type = ZEND_FFI_TYPE(type->array.type); #if 0 if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return; } #endif ptr = (void*)(((char*)cdata->ptr) + type->size * dim); } else if (EXPECTED(type->kind == ZEND_FFI_TYPE_POINTER)) { is_const = (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST); type = ZEND_FFI_TYPE(type->pointer.type); if (UNEXPECTED(!cdata->ptr)) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return; } ptr = (void*)((*(char**)cdata->ptr) + type->size * dim); } else { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign element of non C array"); return; } if (UNEXPECTED(is_const)) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only location"); return; } zend_ffi_zval_to_cdata(ptr, type, value); } /* }}} */ #define MAX_TYPE_NAME_LEN 256 typedef struct _zend_ffi_ctype_name_buf { char *start; char *end; char buf[MAX_TYPE_NAME_LEN]; } zend_ffi_ctype_name_buf; static int zend_ffi_ctype_name_prepend(zend_ffi_ctype_name_buf *buf, const char *str, size_t len) /* {{{ */ { buf->start -= len; if (buf->start < buf->buf) { return 0; } memcpy(buf->start, str, len); return 1; } /* }}} */ static int zend_ffi_ctype_name_append(zend_ffi_ctype_name_buf *buf, const char *str, size_t len) /* {{{ */ { if (buf->end + len > buf->buf + MAX_TYPE_NAME_LEN) { return 0; } memcpy(buf->end, str, len); buf->end += len; return 1; } /* }}} */ static int zend_ffi_ctype_name(zend_ffi_ctype_name_buf *buf, const zend_ffi_type *type) /* {{{ */ { const char *name = NULL; int is_ptr = 0; while (1) { switch (type->kind) { case ZEND_FFI_TYPE_VOID: name = "void"; break; case ZEND_FFI_TYPE_FLOAT: name = "float"; break; case ZEND_FFI_TYPE_DOUBLE: name = "double"; break; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: name = "long double"; break; #endif case ZEND_FFI_TYPE_UINT8: name = "uint8_t"; break; case ZEND_FFI_TYPE_SINT8: name = "int8_t"; break; case ZEND_FFI_TYPE_UINT16: name = "uint16_t"; break; case ZEND_FFI_TYPE_SINT16: name = "int16_t"; break; case ZEND_FFI_TYPE_UINT32: name = "uint32_t"; break; case ZEND_FFI_TYPE_SINT32: name = "int32_t"; break; case ZEND_FFI_TYPE_UINT64: name = "uint64_t"; break; case ZEND_FFI_TYPE_SINT64: name = "int64_t"; break; case ZEND_FFI_TYPE_ENUM: if (type->enumeration.tag_name) { zend_ffi_ctype_name_prepend(buf, ZSTR_VAL(type->enumeration.tag_name), ZSTR_LEN(type->enumeration.tag_name)); } else { zend_ffi_ctype_name_prepend(buf, "", sizeof("")-1); } name = "enum "; break; case ZEND_FFI_TYPE_BOOL: name = "bool"; break; case ZEND_FFI_TYPE_CHAR: name = "char"; break; case ZEND_FFI_TYPE_POINTER: if (!zend_ffi_ctype_name_prepend(buf, "*", 1)) { return 0; } is_ptr = 1; type = ZEND_FFI_TYPE(type->pointer.type); break; case ZEND_FFI_TYPE_FUNC: if (is_ptr) { is_ptr = 0; if (!zend_ffi_ctype_name_prepend(buf, "(", 1) || !zend_ffi_ctype_name_append(buf, ")", 1)) { return 0; } } if (!zend_ffi_ctype_name_append(buf, "(", 1) || !zend_ffi_ctype_name_append(buf, ")", 1)) { return 0; } type = ZEND_FFI_TYPE(type->func.ret_type); break; case ZEND_FFI_TYPE_ARRAY: if (is_ptr) { is_ptr = 0; if (!zend_ffi_ctype_name_prepend(buf, "(", 1) || !zend_ffi_ctype_name_append(buf, ")", 1)) { return 0; } } if (!zend_ffi_ctype_name_append(buf, "[", 1)) { return 0; } if (type->attr & ZEND_FFI_ATTR_VLA) { if (!zend_ffi_ctype_name_append(buf, "*", 1)) { return 0; } } else if (!(type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) { char str[MAX_LENGTH_OF_LONG + 1]; char *s = zend_print_long_to_buf(str + sizeof(str) - 1, type->array.length); if (!zend_ffi_ctype_name_append(buf, s, strlen(s))) { return 0; } } if (!zend_ffi_ctype_name_append(buf, "]", 1)) { return 0; } type = ZEND_FFI_TYPE(type->array.type); break; case ZEND_FFI_TYPE_STRUCT: if (type->attr & ZEND_FFI_ATTR_UNION) { if (type->record.tag_name) { zend_ffi_ctype_name_prepend(buf, ZSTR_VAL(type->record.tag_name), ZSTR_LEN(type->record.tag_name)); } else { zend_ffi_ctype_name_prepend(buf, "", sizeof("")-1); } name = "union "; } else { if (type->record.tag_name) { zend_ffi_ctype_name_prepend(buf, ZSTR_VAL(type->record.tag_name), ZSTR_LEN(type->record.tag_name)); } else { zend_ffi_ctype_name_prepend(buf, "", sizeof("")-1); } name = "struct "; } break; default: ZEND_ASSERT(0); } if (name) { break; } } // if (buf->start != buf->end && *buf->start != '[') { // if (!zend_ffi_ctype_name_prepend(buf, " ", 1)) { // return 0; // } // } return zend_ffi_ctype_name_prepend(buf, name, strlen(name)); } /* }}} */ static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type) /* {{{ */ { type = ZEND_FFI_TYPE(type); if (type->kind == ZEND_FFI_TYPE_STRUCT) { zend_throw_error(zend_ffi_exception_ce, "FFI return struct/union is not implemented"); } else if (type->kind == ZEND_FFI_TYPE_ARRAY) { zend_throw_error(zend_ffi_exception_ce, "FFI return array is not implemented"); } else { zend_throw_error(zend_ffi_exception_ce, "FFI internal error. Unsupported return type"); } } /* }}} */ static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type) /* {{{ */ { type = ZEND_FFI_TYPE(type); if (type->kind == ZEND_FFI_TYPE_STRUCT) { zend_throw_error(zend_ffi_exception_ce, "FFI passing struct/union is not implemented"); } else if (type->kind == ZEND_FFI_TYPE_ARRAY) { zend_throw_error(zend_ffi_exception_ce, "FFI passing array is not implemented"); } else { zend_throw_error(zend_ffi_exception_ce, "FFI internal error. Unsupported parameter type"); } } /* }}} */ static ZEND_COLD void zend_ffi_pass_incompatible(zval *arg, zend_ffi_type *type, uint32_t n, zend_execute_data *execute_data) /* {{{ */ { zend_ffi_ctype_name_buf buf1, buf2; buf1.start = buf1.end = buf1.buf + ((MAX_TYPE_NAME_LEN * 3) / 4); if (!zend_ffi_ctype_name(&buf1, type)) { zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name)); } else { *buf1.end = 0; if (Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg); type = ZEND_FFI_TYPE(cdata->type); buf2.start = buf2.end = buf2.buf + ((MAX_TYPE_NAME_LEN * 3) / 4); if (!zend_ffi_ctype_name(&buf2, type)) { zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s', expecting '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name), buf1.start); } else { *buf2.end = 0; zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s', expecting '%s', found '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name), buf1.start, buf2.start); } } else { zend_throw_error(zend_ffi_exception_ce, "Passing incompatible argument %d of C function '%s', expecting '%s', found PHP '%s'", n + 1, ZSTR_VAL(EX(func)->internal_function.function_name), buf1.start, zend_get_type_by_const(Z_TYPE_P(arg))); } } } /* }}} */ static ZEND_COLD void zend_ffi_assign_incompatible(zval *arg, zend_ffi_type *type) /* {{{ */ { zend_ffi_ctype_name_buf buf1, buf2; buf1.start = buf1.end = buf1.buf + ((MAX_TYPE_NAME_LEN * 3) / 4); if (!zend_ffi_ctype_name(&buf1, type)) { zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning"); } else { *buf1.end = 0; if (Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg); type = ZEND_FFI_TYPE(cdata->type); buf2.start = buf2.end = buf2.buf + ((MAX_TYPE_NAME_LEN * 3) / 4); if (!zend_ffi_ctype_name(&buf2, type)) { zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning to type '%s'", buf1.start); } else { *buf2.end = 0; zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning to type '%s' from type '%s'", buf1.start, buf2.start); } } else { zend_throw_error(zend_ffi_exception_ce, "Incompatible types when assigning to type '%s' from PHP '%s'", buf1.start, zend_get_type_by_const(Z_TYPE_P(arg))); } } } /* }}} */ static zend_string *zend_ffi_get_class_name(zend_string *prefix, const zend_ffi_type *type) /* {{{ */ { zend_ffi_ctype_name_buf buf; buf.start = buf.end = buf.buf + ((MAX_TYPE_NAME_LEN * 3) / 4); if (!zend_ffi_ctype_name(&buf, type)) { return zend_string_copy(prefix); } else { zend_string *name = zend_string_alloc(ZSTR_LEN(prefix) + 1 + buf.end - buf.start, 0); memcpy(ZSTR_VAL(name), ZSTR_VAL(prefix), ZSTR_LEN(prefix)); ZSTR_VAL(name)[ZSTR_LEN(prefix)] = ':'; memcpy(ZSTR_VAL(name) + ZSTR_LEN(prefix) + 1, buf.start, buf.end - buf.start); ZSTR_VAL(name)[ZSTR_LEN(name)] = 0; return name; } } /* }}} */ static zend_string *zend_ffi_cdata_get_class_name(const zend_object *zobj) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)zobj; return zend_ffi_get_class_name(zobj->ce->name, ZEND_FFI_TYPE(cdata->type)); } /* }}} */ static int zend_ffi_cdata_compare_objects(zval *o1, zval *o2) /* {{{ */ { if (Z_TYPE_P(o1) == IS_OBJECT && Z_OBJCE_P(o1) == zend_ffi_cdata_ce && Z_TYPE_P(o2) == IS_OBJECT && Z_OBJCE_P(o2) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata1 = (zend_ffi_cdata*)Z_OBJ_P(o1); zend_ffi_cdata *cdata2 = (zend_ffi_cdata*)Z_OBJ_P(o2); zend_ffi_type *type1 = ZEND_FFI_TYPE(cdata1->type); zend_ffi_type *type2 = ZEND_FFI_TYPE(cdata2->type); if (type1->kind == ZEND_FFI_TYPE_POINTER && type2->kind == ZEND_FFI_TYPE_POINTER) { void *ptr1 = *(void**)cdata1->ptr; void *ptr2 = *(void**)cdata2->ptr; if (!ptr1 || !ptr2) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return 0; } return ptr1 == ptr2 ? 0 : (ptr1 < ptr2 ? -1 : 1); } } zend_throw_error(zend_ffi_exception_ce, "Comparison of incompatible C types"); return 0; } /* }}} */ static int zend_ffi_cdata_count_elements(zval *object, zend_long *count) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); if (type->kind != ZEND_FFI_TYPE_ARRAY) { zend_throw_error(zend_ffi_exception_ce, "Attempt to count() on non C array"); return FAILURE; } else { *count = type->array.length; return SUCCESS; } } /* }}} */ static zend_object* zend_ffi_add(zend_ffi_cdata *base_cdata, zend_ffi_type *base_type, zend_long offset) /* {{{ */ { char *ptr; zend_ffi_type *ptr_type; zend_ffi_cdata *cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); if (base_type->kind == ZEND_FFI_TYPE_POINTER) { if (ZEND_FFI_TYPE_IS_OWNED(base_cdata->type)) { if (!(base_type->attr & ZEND_FFI_ATTR_STORED)) { if (GC_REFCOUNT(&base_cdata->std) == 1) { /* transfer type ownership */ base_cdata->type = base_type; base_type = ZEND_FFI_TYPE_MAKE_OWNED(base_type); } else { base_cdata->type = base_type = zend_ffi_remember_type(base_type); } } } cdata->type = base_type; ptr = (char*)(*(void**)base_cdata->ptr); ptr_type = ZEND_FFI_TYPE(base_type)->pointer.type; } else { zend_ffi_type *new_type = emalloc(sizeof(zend_ffi_type)); new_type->kind = ZEND_FFI_TYPE_POINTER; new_type->attr = 0; new_type->size = sizeof(void*); new_type->align = _Alignof(void*); ptr_type = base_type->array.type; if (ZEND_FFI_TYPE_IS_OWNED(ptr_type)) { ptr_type = ZEND_FFI_TYPE(ptr_type); if (!(ptr_type->attr & ZEND_FFI_ATTR_STORED)) { if (GC_REFCOUNT(&base_cdata->std) == 1) { /* transfer type ownership */ base_type->array.type = ptr_type; ptr_type = ZEND_FFI_TYPE_MAKE_OWNED(ptr_type); } else { base_type->array.type = ptr_type = zend_ffi_remember_type(ptr_type); } } } new_type->pointer.type = ptr_type; cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); ptr = (char*)base_cdata->ptr; } cdata->ptr = &cdata->ptr_holder; cdata->ptr_holder = ptr + (ptrdiff_t) (offset * ZEND_FFI_TYPE(ptr_type)->size); cdata->flags = base_cdata->flags & ZEND_FFI_FLAG_CONST; return &cdata->std; } /* }}} */ static int zend_ffi_cdata_do_operation(zend_uchar opcode, zval *result, zval *op1, zval *op2) /* {{{ */ { zend_long offset; ZVAL_DEREF(op1); ZVAL_DEREF(op2); if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJCE_P(op1) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata1 = (zend_ffi_cdata*)Z_OBJ_P(op1); zend_ffi_type *type1 = ZEND_FFI_TYPE(cdata1->type); if (type1->kind == ZEND_FFI_TYPE_POINTER || type1->kind == ZEND_FFI_TYPE_ARRAY) { if (opcode == ZEND_ADD) { offset = zval_get_long(op2); ZVAL_OBJ(result, zend_ffi_add(cdata1, type1, offset)); if (result == op1) { OBJ_RELEASE(&cdata1->std); } return SUCCESS; } else if (opcode == ZEND_SUB) { if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJCE_P(op2) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata2 = (zend_ffi_cdata*)Z_OBJ_P(op2); zend_ffi_type *type2 = ZEND_FFI_TYPE(cdata2->type); if (type2->kind == ZEND_FFI_TYPE_POINTER || type2->kind == ZEND_FFI_TYPE_ARRAY) { zend_ffi_type *t1, *t2; char *p1, *p2; if (type1->kind == ZEND_FFI_TYPE_POINTER) { t1 = ZEND_FFI_TYPE(type1->pointer.type); p1 = (char*)(*(void**)cdata1->ptr); } else { t1 = ZEND_FFI_TYPE(type1->array.type); p1 = cdata1->ptr; } if (type2->kind == ZEND_FFI_TYPE_POINTER) { t2 = ZEND_FFI_TYPE(type2->pointer.type); p2 = (char*)(*(void**)cdata2->ptr); } else { t2 = ZEND_FFI_TYPE(type2->array.type); p2 = cdata2->ptr; } if (zend_ffi_is_same_type(t1, t2)) { ZVAL_LONG(result, (zend_long)(p1 - p2) / (zend_long)t1->size); return SUCCESS; } } } offset = zval_get_long(op2); ZVAL_OBJ(result, zend_ffi_add(cdata1, type1, -offset)); if (result == op1) { OBJ_RELEASE(&cdata1->std); } return SUCCESS; } } } else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJCE_P(op2) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata2 = (zend_ffi_cdata*)Z_OBJ_P(op2); zend_ffi_type *type2 = ZEND_FFI_TYPE(cdata2->type); if (type2->kind == ZEND_FFI_TYPE_POINTER || type2->kind == ZEND_FFI_TYPE_ARRAY) { if (opcode == ZEND_ADD) { offset = zval_get_long(op1); ZVAL_OBJ(result, zend_ffi_add(cdata2, type2, offset)); return SUCCESS; } } } return FAILURE; } /* }}} */ typedef struct _zend_ffi_cdata_iterator { zend_object_iterator it; zend_long key; zval value; zend_bool by_ref; } zend_ffi_cdata_iterator; static void zend_ffi_cdata_it_dtor(zend_object_iterator *iter) /* {{{ */ { zval_ptr_dtor(&((zend_ffi_cdata_iterator*)iter)->value); zval_ptr_dtor(&iter->data); } /* }}} */ static int zend_ffi_cdata_it_valid(zend_object_iterator *it) /* {{{ */ { zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it; zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ(iter->it.data); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); return (iter->key >= 0 && iter->key < type->array.length) ? SUCCESS : FAILURE; } /* }}} */ static zval *zend_ffi_cdata_it_get_current_data(zend_object_iterator *it) /* {{{ */ { zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it; zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ(iter->it.data); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); zend_ffi_type *dim_type; void *ptr; if (!cdata->ptr) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return &EG(uninitialized_zval); } dim_type = type->array.type; if (ZEND_FFI_TYPE_IS_OWNED(dim_type)) { dim_type = ZEND_FFI_TYPE(dim_type); if (!(dim_type->attr & ZEND_FFI_ATTR_STORED) && dim_type->kind == ZEND_FFI_TYPE_POINTER) { type->array.type = dim_type = zend_ffi_remember_type(dim_type); } } ptr = (void*)((char*)cdata->ptr + dim_type->size * iter->it.index); zval_ptr_dtor(&iter->value); zend_ffi_cdata_to_zval(NULL, ptr, dim_type, iter->by_ref ? BP_VAR_RW : BP_VAR_R, &iter->value, (cdata->flags & ZEND_FFI_FLAG_CONST) | (zend_ffi_flags)(type->attr & ZEND_FFI_ATTR_CONST), 0, 0); return &iter->value; } /* }}} */ static void zend_ffi_cdata_it_get_current_key(zend_object_iterator *it, zval *key) /* {{{ */ { zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it; ZVAL_LONG(key, iter->key); } /* }}} */ static void zend_ffi_cdata_it_move_forward(zend_object_iterator *it) /* {{{ */ { zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it; iter->key++; } /* }}} */ static void zend_ffi_cdata_it_rewind(zend_object_iterator *it) /* {{{ */ { zend_ffi_cdata_iterator *iter = (zend_ffi_cdata_iterator*)it; iter->key = 0; } /* }}} */ static const zend_object_iterator_funcs zend_ffi_cdata_it_funcs = { zend_ffi_cdata_it_dtor, zend_ffi_cdata_it_valid, zend_ffi_cdata_it_get_current_data, zend_ffi_cdata_it_get_current_key, zend_ffi_cdata_it_move_forward, zend_ffi_cdata_it_rewind, NULL }; static zend_object_iterator *zend_ffi_cdata_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); zend_ffi_cdata_iterator *iter; if (type->kind != ZEND_FFI_TYPE_ARRAY) { zend_throw_error(zend_ffi_exception_ce, "Attempt to iterate on non C array"); return NULL; } iter = emalloc(sizeof(zend_ffi_cdata_iterator)); zend_iterator_init(&iter->it); Z_ADDREF_P(object); ZVAL_OBJ(&iter->it.data, Z_OBJ_P(object)); iter->it.funcs = &zend_ffi_cdata_it_funcs; iter->key = 0; iter->by_ref = by_ref; ZVAL_UNDEF(&iter->value); return &iter->it; } /* }}} */ static HashTable *zend_ffi_cdata_get_debug_info(zval *object, int *is_temp) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(object); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); void *ptr = cdata->ptr; HashTable *ht = NULL; zend_string *key; zend_ffi_field *f; zend_long n; zval tmp; if (!cdata->ptr) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return NULL; } switch (type->kind) { case ZEND_FFI_TYPE_BOOL: case ZEND_FFI_TYPE_CHAR: case ZEND_FFI_TYPE_ENUM: case ZEND_FFI_TYPE_FLOAT: case ZEND_FFI_TYPE_DOUBLE: #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: #endif case ZEND_FFI_TYPE_UINT8: case ZEND_FFI_TYPE_SINT8: case ZEND_FFI_TYPE_UINT16: case ZEND_FFI_TYPE_SINT16: case ZEND_FFI_TYPE_UINT32: case ZEND_FFI_TYPE_SINT32: case ZEND_FFI_TYPE_UINT64: case ZEND_FFI_TYPE_SINT64: zend_ffi_cdata_to_zval(cdata, ptr, type, BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0, 0); ht = zend_new_array(1); zend_hash_str_add(ht, "cdata", sizeof("cdata")-1, &tmp); *is_temp = 1; return ht; break; case ZEND_FFI_TYPE_POINTER: if (*(void**)ptr == NULL) { ZVAL_NULL(&tmp); ht = zend_new_array(1); zend_hash_index_add_new(ht, 0, &tmp); *is_temp = 1; return ht; } else if (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_VOID) { ZVAL_LONG(&tmp, (uintptr_t)*(void**)ptr); ht = zend_new_array(1); zend_hash_index_add_new(ht, 0, &tmp); *is_temp = 1; return ht; } else { zend_ffi_cdata_to_zval(NULL, *(void**)ptr, ZEND_FFI_TYPE(type->pointer.type), BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0, 0); ht = zend_new_array(1); zend_hash_index_add_new(ht, 0, &tmp); *is_temp = 1; return ht; } break; case ZEND_FFI_TYPE_STRUCT: ht = zend_new_array(zend_hash_num_elements(&type->record.fields)); ZEND_HASH_FOREACH_STR_KEY_PTR(&type->record.fields, key, f) { if (key) { if (!f->bits) { void *f_ptr = (void*)(((char*)ptr) + f->offset); zend_ffi_cdata_to_zval(NULL, f_ptr, ZEND_FFI_TYPE(f->type), BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0, type->attr & ZEND_FFI_ATTR_UNION); zend_hash_add(ht, key, &tmp); } else { zend_ffi_bit_field_to_zval(ptr, f, &tmp); zend_hash_add(ht, key, &tmp); } } } ZEND_HASH_FOREACH_END(); *is_temp = 1; return ht; case ZEND_FFI_TYPE_ARRAY: ht = zend_new_array(type->array.length); for (n = 0; n < type->array.length; n++) { zend_ffi_cdata_to_zval(NULL, ptr, ZEND_FFI_TYPE(type->array.type), BP_VAR_R, &tmp, ZEND_FFI_FLAG_CONST, 0, 0); zend_hash_index_add(ht, n, &tmp); ptr = (void*)(((char*)ptr) + ZEND_FFI_TYPE(type->array.type)->size); } *is_temp = 1; return ht; case ZEND_FFI_TYPE_FUNC: ht = zend_new_array(0); // TODO: function name ??? *is_temp = 1; return ht; break; default: ZEND_ASSERT(0); break; } return NULL; } /* }}} */ static int zend_ffi_cdata_get_closure(zval *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(obj); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); zend_function *func; if (type->kind != ZEND_FFI_TYPE_POINTER) { zend_throw_error(zend_ffi_exception_ce, "Attempt to call non C function pointer"); return FAILURE; } type = ZEND_FFI_TYPE(type->pointer.type); if (type->kind != ZEND_FFI_TYPE_FUNC) { zend_throw_error(zend_ffi_exception_ce, "Attempt to call non C function pointer"); return FAILURE; } if (!cdata->ptr) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return FAILURE; } if (EXPECTED(EG(trampoline).common.function_name == NULL)) { func = &EG(trampoline); } else { func = ecalloc(sizeof(zend_internal_function), 1); } func->type = ZEND_INTERNAL_FUNCTION; func->common.arg_flags[0] = 0; func->common.arg_flags[1] = 0; func->common.arg_flags[2] = 0; func->common.fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE; func->common.function_name = ZSTR_KNOWN(ZEND_STR_MAGIC_INVOKE); /* set to 0 to avoid arg_info[] allocation, because all values are passed by value anyway */ func->common.num_args = 0; func->common.required_num_args = type->func.args ? zend_hash_num_elements(type->func.args) : 0; func->common.scope = NULL; func->common.prototype = NULL; func->common.arg_info = NULL; func->internal_function.handler = ZEND_FN(ffi_trampoline); func->internal_function.module = NULL; func->internal_function.reserved[0] = type; func->internal_function.reserved[1] = *(void**)cdata->ptr; *ce_ptr = NULL; *fptr_ptr= func; *obj_ptr = NULL; return SUCCESS; } /* }}} */ static zend_object *zend_ffi_ctype_new(zend_class_entry *class_type) /* {{{ */ { zend_ffi_ctype *ctype; ctype = emalloc(sizeof(zend_ffi_ctype)); zend_ffi_object_init(&ctype->std, class_type); ctype->std.handlers = &zend_ffi_ctype_handlers; ctype->type = NULL; return &ctype->std; } /* }}} */ static void zend_ffi_ctype_free_obj(zend_object *object) /* {{{ */ { zend_ffi_ctype *ctype = (zend_ffi_ctype*)object; zend_ffi_type_dtor(ctype->type); } /* }}} */ static int zend_ffi_is_same_type(zend_ffi_type *type1, zend_ffi_type *type2) /* {{{ */ { while (1) { if (type1 == type2) { return 1; } else if (type1->kind == type2->kind) { if (type1->kind < ZEND_FFI_TYPE_POINTER) { return 1; } else if (type1->kind == ZEND_FFI_TYPE_POINTER) { type1 = ZEND_FFI_TYPE(type1->pointer.type); type2 = ZEND_FFI_TYPE(type2->pointer.type); if (type1->kind == ZEND_FFI_TYPE_VOID || type2->kind == ZEND_FFI_TYPE_VOID) { return 1; } } else if (type1->kind == ZEND_FFI_TYPE_ARRAY && type1->array.length == type2->array.length) { type1 = ZEND_FFI_TYPE(type1->array.type); type2 = ZEND_FFI_TYPE(type2->array.type); } else { break; } } else { break; } } return 0; } /* }}} */ static zend_string *zend_ffi_ctype_get_class_name(const zend_object *zobj) /* {{{ */ { zend_ffi_ctype *ctype = (zend_ffi_ctype*)zobj; return zend_ffi_get_class_name(zobj->ce->name, ZEND_FFI_TYPE(ctype->type)); } /* }}} */ static int zend_ffi_ctype_compare_objects(zval *o1, zval *o2) /* {{{ */ { if (Z_TYPE_P(o1) == IS_OBJECT && Z_OBJCE_P(o1) == zend_ffi_ctype_ce && Z_TYPE_P(o2) == IS_OBJECT && Z_OBJCE_P(o2) == zend_ffi_ctype_ce) { zend_ffi_ctype *ctype1 = (zend_ffi_ctype*)Z_OBJ_P(o1); zend_ffi_ctype *ctype2 = (zend_ffi_ctype*)Z_OBJ_P(o2); zend_ffi_type *type1 = ZEND_FFI_TYPE(ctype1->type); zend_ffi_type *type2 = ZEND_FFI_TYPE(ctype2->type); if (zend_ffi_is_same_type(type1, type2)) { return 0; } else { return 1; } } zend_throw_error(zend_ffi_exception_ce, "Comparison of incompatible C types"); return 0; } /* }}} */ static HashTable *zend_ffi_ctype_get_debug_info(zval *object, int *is_temp) /* {{{ */ { return NULL; } /* }}} */ static zend_object *zend_ffi_new(zend_class_entry *class_type) /* {{{ */ { zend_ffi *ffi; ffi = emalloc(sizeof(zend_ffi)); zend_ffi_object_init(&ffi->std, class_type); ffi->std.handlers = &zend_ffi_handlers; ffi->lib = NULL; ffi->symbols = NULL; ffi->tags = NULL; ffi->persistent = 0; return &ffi->std; } /* }}} */ static void _zend_ffi_type_dtor(zend_ffi_type *type) /* {{{ */ { type = ZEND_FFI_TYPE(type); switch (type->kind) { case ZEND_FFI_TYPE_ENUM: if (type->enumeration.tag_name) { zend_string_release(type->enumeration.tag_name); } break; case ZEND_FFI_TYPE_STRUCT: if (type->record.tag_name) { zend_string_release(type->record.tag_name); } zend_hash_destroy(&type->record.fields); break; case ZEND_FFI_TYPE_POINTER: zend_ffi_type_dtor(type->pointer.type); break; case ZEND_FFI_TYPE_ARRAY: zend_ffi_type_dtor(type->array.type); break; case ZEND_FFI_TYPE_FUNC: if (type->func.args) { zend_hash_destroy(type->func.args); pefree(type->func.args, type->attr & ZEND_FFI_ATTR_PERSISTENT); } zend_ffi_type_dtor(type->func.ret_type); break; default: break; } pefree(type, type->attr & ZEND_FFI_ATTR_PERSISTENT); } /* }}} */ static void zend_ffi_type_hash_dtor(zval *zv) /* {{{ */ { zend_ffi_type *type = Z_PTR_P(zv); zend_ffi_type_dtor(type); } /* }}} */ static void zend_ffi_field_hash_dtor(zval *zv) /* {{{ */ { zend_ffi_field *field = Z_PTR_P(zv); zend_ffi_type_dtor(field->type); efree(field); } /* }}} */ static void zend_ffi_field_hash_persistent_dtor(zval *zv) /* {{{ */ { zend_ffi_field *field = Z_PTR_P(zv); zend_ffi_type_dtor(field->type); free(field); } /* }}} */ static void zend_ffi_symbol_hash_dtor(zval *zv) /* {{{ */ { zend_ffi_symbol *sym = Z_PTR_P(zv); zend_ffi_type_dtor(sym->type); efree(sym); } /* }}} */ static void zend_ffi_symbol_hash_persistent_dtor(zval *zv) /* {{{ */ { zend_ffi_symbol *sym = Z_PTR_P(zv); zend_ffi_type_dtor(sym->type); free(sym); } /* }}} */ static void zend_ffi_tag_hash_dtor(zval *zv) /* {{{ */ { zend_ffi_tag *tag = Z_PTR_P(zv); zend_ffi_type_dtor(tag->type); efree(tag); } /* }}} */ static void zend_ffi_tag_hash_persistent_dtor(zval *zv) /* {{{ */ { zend_ffi_tag *tag = Z_PTR_P(zv); zend_ffi_type_dtor(tag->type); free(tag); } /* }}} */ static void zend_ffi_cdata_dtor(zend_ffi_cdata *cdata) /* {{{ */ { zend_ffi_type_dtor(cdata->type); if (cdata->flags & ZEND_FFI_FLAG_OWNED) { if (cdata->ptr != (void*)&cdata->ptr_holder) { pefree(cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT); } else { pefree(cdata->ptr_holder, cdata->flags & ZEND_FFI_FLAG_PERSISTENT); } } } /* }}} */ static void zend_ffi_scope_hash_dtor(zval *zv) /* {{{ */ { zend_ffi_scope *scope = Z_PTR_P(zv); if (scope->symbols) { zend_hash_destroy(scope->symbols); free(scope->symbols); } if (scope->tags) { zend_hash_destroy(scope->tags); free(scope->tags); } free(scope); } /* }}} */ static void zend_ffi_free_obj(zend_object *object) /* {{{ */ { zend_ffi *ffi = (zend_ffi*)object; if (ffi->persistent) { return; } if (ffi->lib) { DL_UNLOAD(ffi->lib); ffi->lib = NULL; } if (ffi->symbols) { zend_hash_destroy(ffi->symbols); efree(ffi->symbols); } if (ffi->tags) { zend_hash_destroy(ffi->tags); efree(ffi->tags); } } /* }}} */ static void zend_ffi_cdata_free_obj(zend_object *object) /* {{{ */ { zend_ffi_cdata *cdata = (zend_ffi_cdata*)object; zend_ffi_cdata_dtor(cdata); } /* }}} */ static zend_object *zend_ffi_cdata_clone_obj(zval *zobject) /* {{{ */ { zend_ffi_cdata *old_cdata = (zend_ffi_cdata*)Z_OBJ_P(zobject); zend_ffi_type *type = ZEND_FFI_TYPE(old_cdata->type); zend_ffi_cdata *new_cdata; new_cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); if (type->kind < ZEND_FFI_TYPE_POINTER) { new_cdata->std.handlers = &zend_ffi_cdata_value_handlers; } new_cdata->type = type; new_cdata->ptr = emalloc(type->size); memcpy(new_cdata->ptr, old_cdata->ptr, type->size); new_cdata->flags |= ZEND_FFI_FLAG_OWNED; return &new_cdata->std; } /* }}} */ static zval *zend_ffi_read_var(zval *object, zval *member, int read_type, void **cache_slot, zval *rv) /* {{{ */ { zend_ffi *ffi = (zend_ffi*)Z_OBJ_P(object); zend_string *tmp_var_name; zend_string *var_name = zval_get_tmp_string(member, &tmp_var_name); zend_ffi_symbol *sym = NULL; if (ffi->symbols) { sym = zend_hash_find_ptr(ffi->symbols, var_name); if (sym && sym->kind != ZEND_FFI_SYM_VAR && sym->kind != ZEND_FFI_SYM_CONST && sym->kind != ZEND_FFI_SYM_FUNC) { sym = NULL; } } if (!sym) { zend_throw_error(zend_ffi_exception_ce, "Attempt to read undefined C variable '%s'", ZSTR_VAL(var_name)); zend_tmp_string_release(tmp_var_name); return &EG(uninitialized_zval); } zend_tmp_string_release(tmp_var_name); if (sym->kind == ZEND_FFI_SYM_VAR) { zend_ffi_cdata_to_zval(NULL, sym->addr, ZEND_FFI_TYPE(sym->type), read_type, rv, (zend_ffi_flags)sym->is_const, 0, 0); } else if (sym->kind == ZEND_FFI_SYM_FUNC) { zend_ffi_cdata *cdata; zend_ffi_type *new_type = emalloc(sizeof(zend_ffi_type)); new_type->kind = ZEND_FFI_TYPE_POINTER; new_type->attr = 0; new_type->size = sizeof(void*); new_type->align = _Alignof(void*); new_type->pointer.type = ZEND_FFI_TYPE(sym->type); cdata = emalloc(sizeof(zend_ffi_cdata)); zend_ffi_object_init(&cdata->std, zend_ffi_cdata_ce); cdata->std.handlers = &zend_ffi_cdata_handlers; cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); cdata->flags = ZEND_FFI_FLAG_CONST; cdata->ptr_holder = sym->addr; cdata->ptr = &cdata->ptr_holder; ZVAL_OBJ(rv, &cdata->std); } else { ZVAL_LONG(rv, sym->value); } return rv; } /* }}} */ static zval *zend_ffi_write_var(zval *object, zval *member, zval *value, void **cache_slot) /* {{{ */ { zend_ffi *ffi = (zend_ffi*)Z_OBJ_P(object); zend_string *tmp_var_name; zend_string *var_name = zval_get_tmp_string(member, &tmp_var_name); zend_ffi_symbol *sym = NULL; if (ffi->symbols) { sym = zend_hash_find_ptr(ffi->symbols, var_name); if (sym && sym->kind != ZEND_FFI_SYM_VAR) { sym = NULL; } } if (!sym) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign undefined C variable '%s'", ZSTR_VAL(var_name)); zend_tmp_string_release(tmp_var_name); return value; } zend_tmp_string_release(tmp_var_name); if (sym->is_const) { zend_throw_error(zend_ffi_exception_ce, "Attempt to assign read-only C variable '%s'", ZSTR_VAL(var_name)); return value; } zend_ffi_zval_to_cdata(sym->addr, ZEND_FFI_TYPE(sym->type), value); return value; } /* }}} */ static int zend_ffi_pass_arg(zval *arg, zend_ffi_type *type, ffi_type **pass_type, void **arg_values, uint32_t n, zend_execute_data *execute_data) /* {{{ */ { zend_long lval; double dval; zend_string *str, *tmp_str; zend_ffi_type_kind kind = type->kind; ZVAL_DEREF(arg); again: switch (kind) { case ZEND_FFI_TYPE_FLOAT: dval = zval_get_double(arg); *pass_type = &ffi_type_float; *(float*)arg_values[n] = (float)dval; break; case ZEND_FFI_TYPE_DOUBLE: dval = zval_get_double(arg); *pass_type = &ffi_type_double; *(double*)arg_values[n] = dval; break; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: dval = zval_get_double(arg); *pass_type = &ffi_type_double; *(long double*)arg_values[n] = (long double)dval; break; #endif case ZEND_FFI_TYPE_UINT8: lval = zval_get_long(arg); *pass_type = &ffi_type_uint8; *(uint8_t*)arg_values[n] = (uint8_t)lval; break; case ZEND_FFI_TYPE_SINT8: lval = zval_get_long(arg); *pass_type = &ffi_type_sint8; *(int8_t*)arg_values[n] = (int8_t)lval; break; case ZEND_FFI_TYPE_UINT16: lval = zval_get_long(arg); *pass_type = &ffi_type_uint16; *(uint16_t*)arg_values[n] = (uint16_t)lval; break; case ZEND_FFI_TYPE_SINT16: lval = zval_get_long(arg); *pass_type = &ffi_type_sint16; *(int16_t*)arg_values[n] = (int16_t)lval; break; case ZEND_FFI_TYPE_UINT32: lval = zval_get_long(arg); *pass_type = &ffi_type_uint32; *(uint32_t*)arg_values[n] = (uint32_t)lval; break; case ZEND_FFI_TYPE_SINT32: lval = zval_get_long(arg); *pass_type = &ffi_type_sint32; *(int32_t*)arg_values[n] = (int32_t)lval; break; case ZEND_FFI_TYPE_UINT64: lval = zval_get_long(arg); *pass_type = &ffi_type_uint64; *(uint64_t*)arg_values[n] = (uint64_t)lval; break; case ZEND_FFI_TYPE_SINT64: lval = zval_get_long(arg); *pass_type = &ffi_type_sint64; *(int64_t*)arg_values[n] = (int64_t)lval; break; case ZEND_FFI_TYPE_POINTER: *pass_type = &ffi_type_pointer; if (Z_TYPE_P(arg) == IS_NULL) { *(void**)arg_values[n] = NULL; return SUCCESS; } else if (Z_TYPE_P(arg) == IS_STRING && ((ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) || (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_VOID))) { *(void**)arg_values[n] = Z_STRVAL_P(arg); return SUCCESS; } else if (Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg); if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type))) { if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) { if (!cdata->ptr) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return FAILURE; } *(void**)arg_values[n] = *(void**)cdata->ptr; } else { *(void**)arg_values[n] = cdata->ptr; } return SUCCESS; } #if FFI_CLOSURES } else if (ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_FUNC) { void *callback = zend_ffi_create_callback(ZEND_FFI_TYPE(type->pointer.type), arg); if (callback) { *(void**)arg_values[n] = callback; break; } else { return FAILURE; } #endif } zend_ffi_pass_incompatible(arg, type, n, execute_data); return FAILURE; case ZEND_FFI_TYPE_BOOL: *pass_type = &ffi_type_uint8; *(uint8_t*)arg_values[n] = zend_is_true(arg); break; case ZEND_FFI_TYPE_CHAR: str = zval_get_tmp_string(arg, &tmp_str); *pass_type = &ffi_type_sint8; *(char*)arg_values[n] = ZSTR_VAL(str)[0]; if (ZSTR_LEN(str) != 1) { zend_ffi_pass_incompatible(arg, type, n, execute_data); } zend_tmp_string_release(tmp_str); break; case ZEND_FFI_TYPE_ENUM: kind = type->enumeration.kind; goto again; case ZEND_FFI_TYPE_STRUCT: if (!(type->attr & ZEND_FFI_ATTR_UNION) && Z_TYPE_P(arg) == IS_OBJECT && Z_OBJCE_P(arg) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg); if (zend_ffi_is_compatible_type(type, ZEND_FFI_TYPE(cdata->type))) { /* Create a fake structure type */ ffi_type *t = zend_ffi_make_fake_struct_type(type); if (t) { *pass_type = t; arg_values[n] = cdata->ptr; break; } } } zend_ffi_pass_incompatible(arg, type, n, execute_data); return FAILURE; default: zend_ffi_pass_unsupported(type); return FAILURE; } return SUCCESS; } /* }}} */ static int zend_ffi_pass_var_arg(zval *arg, ffi_type **pass_type, void **arg_values, uint32_t n, zend_execute_data *execute_data) /* {{{ */ { ZVAL_DEREF(arg); switch (Z_TYPE_P(arg)) { case IS_NULL: *pass_type = &ffi_type_pointer; *(void**)arg_values[n] = NULL; break; case IS_FALSE: *pass_type = &ffi_type_uint8; *(uint8_t*)arg_values[n] = 0; break; case IS_TRUE: *pass_type = &ffi_type_uint8; *(uint8_t*)arg_values[n] = 1; break; case IS_LONG: if (sizeof(zend_long) == 4) { *pass_type = &ffi_type_sint32; *(int32_t*)arg_values[n] = Z_LVAL_P(arg); } else { *pass_type = &ffi_type_sint64; *(int64_t*)arg_values[n] = Z_LVAL_P(arg); } break; case IS_DOUBLE: *pass_type = &ffi_type_double; *(double*)arg_values[n] = Z_DVAL_P(arg); break; case IS_STRING: *pass_type = &ffi_type_pointer; *(char**)arg_values[n] = Z_STRVAL_P(arg); break; case IS_OBJECT: if (Z_OBJCE_P(arg) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(arg); zend_ffi_type *type = ZEND_FFI_TYPE(cdata->type); return zend_ffi_pass_arg(arg, type, pass_type, arg_values, n, execute_data); } /* break missing intentionally */ default: zend_throw_error(zend_ffi_exception_ce, "Unsupported argument type"); return FAILURE; } return SUCCESS; } /* }}} */ static ZEND_FUNCTION(ffi_trampoline) /* {{{ */ { zend_ffi_type *type = EX(func)->internal_function.reserved[0]; void *addr = EX(func)->internal_function.reserved[1]; ffi_cif cif; ffi_type *ret_type = NULL; ffi_type **arg_types = NULL; void **arg_values = NULL; uint32_t n, arg_count; void *ret; zend_ffi_type *arg_type; ALLOCA_FLAG(arg_types_use_heap = 0) ALLOCA_FLAG(arg_values_use_heap = 0) ALLOCA_FLAG(ret_use_heap = 0) ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC); arg_count = type->func.args ? zend_hash_num_elements(type->func.args) : 0; if (type->attr & ZEND_FFI_ATTR_VARIADIC) { if (arg_count > EX_NUM_ARGS()) { zend_throw_error(zend_ffi_exception_ce, "Incorrect number of arguments for C function '%s', expecting at least %d parameter%s", ZSTR_VAL(EX(func)->internal_function.function_name), arg_count, (arg_count != 1) ? "s" : ""); goto exit; } if (EX_NUM_ARGS()) { arg_types = do_alloca( sizeof(ffi_type*) * EX_NUM_ARGS(), arg_types_use_heap); arg_values = do_alloca( (sizeof(void*) + ZEND_FFI_SIZEOF_ARG) * EX_NUM_ARGS(), arg_values_use_heap); n = 0; if (type->func.args) { ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); arg_values[n] = ((char*)arg_values) + (sizeof(void*) * EX_NUM_ARGS()) + (ZEND_FFI_SIZEOF_ARG * n); if (zend_ffi_pass_arg(EX_VAR_NUM(n), arg_type, &arg_types[n], arg_values, n, execute_data) != SUCCESS) { free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } n++; } ZEND_HASH_FOREACH_END(); } for (; n < EX_NUM_ARGS(); n++) { arg_values[n] = ((char*)arg_values) + (sizeof(void*) * EX_NUM_ARGS()) + (ZEND_FFI_SIZEOF_ARG * n); if (zend_ffi_pass_var_arg(EX_VAR_NUM(n), &arg_types[n], arg_values, n, execute_data) != SUCCESS) { free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } } } ret_type = zend_ffi_get_type(ZEND_FFI_TYPE(type->func.ret_type)); if (!ret_type) { zend_ffi_return_unsupported(type->func.ret_type); free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } if (ffi_prep_cif_var(&cif, type->func.abi, arg_count, EX_NUM_ARGS(), ret_type, arg_types) != FFI_OK) { zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback CIF"); free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } } else { if (arg_count != EX_NUM_ARGS()) { zend_throw_error(zend_ffi_exception_ce, "Incorrect number of arguments for C function '%s', expecting exactly %d parameter%s", ZSTR_VAL(EX(func)->internal_function.function_name), arg_count, (arg_count != 1) ? "s" : ""); goto exit; } if (EX_NUM_ARGS()) { arg_types = do_alloca( (sizeof(ffi_type*) + sizeof(ffi_type)) * EX_NUM_ARGS(), arg_types_use_heap); arg_values = do_alloca( (sizeof(void*) + ZEND_FFI_SIZEOF_ARG) * EX_NUM_ARGS(), arg_values_use_heap); n = 0; if (type->func.args) { ZEND_HASH_FOREACH_PTR(type->func.args, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); arg_values[n] = ((char*)arg_values) + (sizeof(void*) * EX_NUM_ARGS()) + (ZEND_FFI_SIZEOF_ARG * n); if (zend_ffi_pass_arg(EX_VAR_NUM(n), arg_type, &arg_types[n], arg_values, n, execute_data) != SUCCESS) { free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } n++; } ZEND_HASH_FOREACH_END(); } } ret_type = zend_ffi_get_type(ZEND_FFI_TYPE(type->func.ret_type)); if (!ret_type) { zend_ffi_return_unsupported(type->func.ret_type); free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } if (ffi_prep_cif(&cif, type->func.abi, arg_count, ret_type, arg_types) != FFI_OK) { zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback CIF"); free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); goto exit; } } ret = do_alloca(MAX(ret_type->size, sizeof(ffi_arg)), ret_use_heap); ffi_call(&cif, addr, ret, arg_values); for (n = 0; n < arg_count; n++) { if (arg_types[n]->type == FFI_TYPE_STRUCT) { efree(arg_types[n]); } } if (ret_type->type == FFI_TYPE_STRUCT) { efree(ret_type); } if (EX_NUM_ARGS()) { free_alloca(arg_types, arg_types_use_heap); free_alloca(arg_values, arg_values_use_heap); } zend_ffi_cdata_to_zval(NULL, ret, ZEND_FFI_TYPE(type->func.ret_type), BP_VAR_R, return_value, 0, 1, 0); free_alloca(ret, ret_use_heap); exit: zend_string_release(EX(func)->common.function_name); if (EX(func)->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) { zend_free_trampoline(EX(func)); EX(func) = NULL; } } /* }}} */ static zend_function *zend_ffi_get_func(zend_object **obj, zend_string *name, const zval *key) /* {{{ */ { zend_ffi *ffi = (zend_ffi*)*obj; zend_ffi_symbol *sym = NULL; zend_function *func; zend_ffi_type *type; if (ZSTR_LEN(name) == sizeof("new") -1 && (ZSTR_VAL(name)[0] == 'n' || ZSTR_VAL(name)[0] == 'N') && (ZSTR_VAL(name)[1] == 'e' || ZSTR_VAL(name)[1] == 'E') && (ZSTR_VAL(name)[2] == 'w' || ZSTR_VAL(name)[2] == 'W')) { return (zend_function*)&zend_ffi_new_fn; } else if (ZSTR_LEN(name) == sizeof("cast") -1 && (ZSTR_VAL(name)[0] == 'c' || ZSTR_VAL(name)[0] == 'C') && (ZSTR_VAL(name)[1] == 'a' || ZSTR_VAL(name)[1] == 'A') && (ZSTR_VAL(name)[2] == 's' || ZSTR_VAL(name)[2] == 'S') && (ZSTR_VAL(name)[3] == 't' || ZSTR_VAL(name)[3] == 'T')) { return (zend_function*)&zend_ffi_cast_fn; } else if (ZSTR_LEN(name) == sizeof("type") -1 && (ZSTR_VAL(name)[0] == 't' || ZSTR_VAL(name)[0] == 'T') && (ZSTR_VAL(name)[1] == 'y' || ZSTR_VAL(name)[1] == 'Y') && (ZSTR_VAL(name)[2] == 'p' || ZSTR_VAL(name)[2] == 'P') && (ZSTR_VAL(name)[3] == 'e' || ZSTR_VAL(name)[3] == 'E')) { return (zend_function*)&zend_ffi_type_fn; } if (ffi->symbols) { sym = zend_hash_find_ptr(ffi->symbols, name); if (sym && sym->kind != ZEND_FFI_SYM_FUNC) { sym = NULL; } } if (!sym) { zend_throw_error(zend_ffi_exception_ce, "Attempt to call undefined C function '%s'", ZSTR_VAL(name)); return NULL; } type = ZEND_FFI_TYPE(sym->type); ZEND_ASSERT(type->kind == ZEND_FFI_TYPE_FUNC); if (EXPECTED(EG(trampoline).common.function_name == NULL)) { func = &EG(trampoline); } else { func = ecalloc(sizeof(zend_internal_function), 1); } func->common.type = ZEND_INTERNAL_FUNCTION; func->common.arg_flags[0] = 0; func->common.arg_flags[1] = 0; func->common.arg_flags[2] = 0; func->common.fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE; func->common.function_name = zend_string_copy(name); /* set to 0 to avoid arg_info[] allocation, because all values are passed by value anyway */ func->common.num_args = 0; func->common.required_num_args = type->func.args ? zend_hash_num_elements(type->func.args) : 0; func->common.scope = NULL; func->common.prototype = NULL; func->common.arg_info = NULL; func->internal_function.handler = ZEND_FN(ffi_trampoline); func->internal_function.module = NULL; func->internal_function.reserved[0] = type; func->internal_function.reserved[1] = sym->addr; return func; } /* }}} */ static zend_never_inline int zend_ffi_disabled(void) /* {{{ */ { zend_throw_error(zend_ffi_exception_ce, "FFI API is restricted by \"ffi.enable\" configuration directive"); return 0; } /* }}} */ static zend_always_inline int zend_ffi_validate_api_restriction(zend_execute_data *execute_data) /* {{{ */ { if (EXPECTED(FFI_G(restriction) > ZEND_FFI_ENABLED)) { ZEND_ASSERT(FFI_G(restriction) == ZEND_FFI_PRELOAD); if (FFI_G(is_cli) || (execute_data->prev_execute_data && (execute_data->prev_execute_data->func->common.fn_flags & ZEND_ACC_PRELOADED)) || (CG(compiler_options) & ZEND_COMPILE_PRELOAD)) { return 1; } } else if (EXPECTED(FFI_G(restriction) == ZEND_FFI_ENABLED)) { return 1; } return zend_ffi_disabled(); } /* }}} */ #define ZEND_FFI_VALIDATE_API_RESTRICTION() do { \ if (UNEXPECTED(!zend_ffi_validate_api_restriction(execute_data))) { \ return; \ } \ } while (0) ZEND_METHOD(FFI, cdef) /* {{{ */ { zend_string *code = NULL; zend_string *lib = NULL; zend_ffi *ffi = NULL; DL_HANDLE handle = NULL; void *addr; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(0, 2) Z_PARAM_OPTIONAL Z_PARAM_STR(code) Z_PARAM_STR(lib) ZEND_PARSE_PARAMETERS_END(); if (lib) { handle = DL_LOAD(ZSTR_VAL(lib)); if (!handle) { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s'", ZSTR_VAL(lib)); return; } #ifdef RTLD_DEFAULT } else if (1) { // TODO: this might need to be disabled or protected ??? handle = RTLD_DEFAULT; #endif } FFI_G(symbols) = NULL; FFI_G(tags) = NULL; if (code) { /* Parse C definitions */ FFI_G(default_type_attr) = ZEND_FFI_ATTR_STORED; if (zend_ffi_parse_decl(ZSTR_VAL(code), ZSTR_LEN(code)) != SUCCESS) { if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } if (FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); efree(FFI_G(tags)); FFI_G(tags) = NULL; } return; } if (FFI_G(symbols)) { zend_string *name; zend_ffi_symbol *sym; ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), name, sym) { if (sym->kind == ZEND_FFI_SYM_VAR) { addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(name)); if (!addr) { zend_throw_error(zend_ffi_exception_ce, "Failed resolving C variable '%s'", ZSTR_VAL(name)); } sym->addr = addr; } else if (sym->kind == ZEND_FFI_SYM_FUNC) { zend_string *mangled_name = zend_ffi_mangled_func_name(name, ZEND_FFI_TYPE(sym->type)); addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(mangled_name)); zend_string_release(mangled_name); if (!addr) { zend_throw_error(zend_ffi_exception_ce, "Failed resolving C function '%s'", ZSTR_VAL(name)); } sym->addr = addr; } } ZEND_HASH_FOREACH_END(); } } ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); ffi->lib = handle; ffi->symbols = FFI_G(symbols); ffi->tags = FFI_G(tags); FFI_G(symbols) = NULL; FFI_G(tags) = NULL; RETURN_OBJ(&ffi->std); } /* }}} */ static int zend_ffi_same_types(zend_ffi_type *old, zend_ffi_type *type) /* {{{ */ { if (old == type) { return 1; } if (old->kind != type->kind || old->size != type->size || old->align != type->align || old->attr != type->attr) { return 0; } switch (old->kind) { case ZEND_FFI_TYPE_ENUM: return old->enumeration.kind == type->enumeration.kind; case ZEND_FFI_TYPE_ARRAY: return old->array.length == type->array.length && zend_ffi_same_types(ZEND_FFI_TYPE(old->array.type), ZEND_FFI_TYPE(type->array.type)); case ZEND_FFI_TYPE_POINTER: return zend_ffi_same_types(ZEND_FFI_TYPE(old->pointer.type), ZEND_FFI_TYPE(type->pointer.type)); case ZEND_FFI_TYPE_STRUCT: if (zend_hash_num_elements(&old->record.fields) != zend_hash_num_elements(&type->record.fields)) { return 0; } else { zend_ffi_field *old_field, *field; zend_string *key; Bucket *b = type->record.fields.arData; ZEND_HASH_FOREACH_STR_KEY_PTR(&old->record.fields, key, old_field) { while (Z_TYPE(b->val) == IS_UNDEF) { b++; } if (key) { if (!b->key || !zend_string_equals(key, b->key)) { return 0; } } else if (b->key) { return 0; } field = Z_PTR(b->val); if (old_field->offset != field->offset || old_field->is_const != field->is_const || old_field->is_nested != field->is_nested || old_field->first_bit != field->first_bit || old_field->bits != field->bits || !zend_ffi_same_types(ZEND_FFI_TYPE(old_field->type), ZEND_FFI_TYPE(field->type))) { return 0; } b++; } ZEND_HASH_FOREACH_END(); } break; case ZEND_FFI_TYPE_FUNC: if (old->func.abi != type->func.abi || ((old->func.args ? zend_hash_num_elements(old->func.args) : 0) != (type->func.args ? zend_hash_num_elements(type->func.args) : 0)) || !zend_ffi_same_types(ZEND_FFI_TYPE(old->func.ret_type), ZEND_FFI_TYPE(type->func.ret_type))) { return 0; } else if (old->func.args) { zend_ffi_type *arg_type; Bucket *b = type->func.args->arData; ZEND_HASH_FOREACH_PTR(old->func.args, arg_type) { while (Z_TYPE(b->val) == IS_UNDEF) { b++; } if (!zend_ffi_same_types(ZEND_FFI_TYPE(arg_type), ZEND_FFI_TYPE(Z_PTR(b->val)))) { return 0; } b++; } ZEND_HASH_FOREACH_END(); } break; default: break; } return 1; } /* }}} */ static int zend_ffi_same_symbols(zend_ffi_symbol *old, zend_ffi_symbol *sym) /* {{{ */ { if (old->kind != sym->kind || old->is_const != sym->is_const) { return 0; } if (old->kind == ZEND_FFI_SYM_CONST) { if (old->value != sym->value) { return 0; } } return zend_ffi_same_types(ZEND_FFI_TYPE(old->type), ZEND_FFI_TYPE(sym->type)); } /* }}} */ static int zend_ffi_same_tags(zend_ffi_tag *old, zend_ffi_tag *tag) /* {{{ */ { if (old->kind != tag->kind) { return 0; } return zend_ffi_same_types(ZEND_FFI_TYPE(old->type), ZEND_FFI_TYPE(tag->type)); } /* }}} */ static int zend_ffi_subst_old_type(zend_ffi_type **dcl, zend_ffi_type *old, zend_ffi_type *type) /* {{{ */ { zend_ffi_type *dcl_type; zend_ffi_field *field; if (ZEND_FFI_TYPE(*dcl) == type) { *dcl = old; return 1; } dcl_type = *dcl; switch (dcl_type->kind) { case ZEND_FFI_TYPE_POINTER: return zend_ffi_subst_old_type(&dcl_type->pointer.type, old, type); case ZEND_FFI_TYPE_ARRAY: return zend_ffi_subst_old_type(&dcl_type->array.type, old, type); case ZEND_FFI_TYPE_FUNC: if (zend_ffi_subst_old_type(&dcl_type->func.ret_type, old, type)) { return 1; } if (dcl_type->func.args) { zval *zv; ZEND_HASH_FOREACH_VAL(dcl_type->func.args, zv) { if (zend_ffi_subst_old_type((zend_ffi_type**)&Z_PTR_P(zv), old, type)) { return 1; } } ZEND_HASH_FOREACH_END(); } break; case ZEND_FFI_TYPE_STRUCT: ZEND_HASH_FOREACH_PTR(&dcl_type->record.fields, field) { if (zend_ffi_subst_old_type(&field->type, old, type)) { return 1; } } ZEND_HASH_FOREACH_END(); break; default: break; } return 0; } /* }}} */ static void zend_ffi_cleanup_type(zend_ffi_type *old, zend_ffi_type *type) /* {{{ */ { zend_ffi_symbol *sym; zend_ffi_tag *tag; if (FFI_G(symbols)) { ZEND_HASH_FOREACH_PTR(FFI_G(symbols), sym) { zend_ffi_subst_old_type(&sym->type, old, type); } ZEND_HASH_FOREACH_END(); } if (FFI_G(tags)) { ZEND_HASH_FOREACH_PTR(FFI_G(tags), tag) { zend_ffi_subst_old_type(&tag->type, old, type); } ZEND_HASH_FOREACH_END(); } } /* }}} */ static zend_ffi_type *zend_ffi_remember_type(zend_ffi_type *type) /* {{{ */ { if (!FFI_G(weak_types)) { FFI_G(weak_types) = emalloc(sizeof(HashTable)); zend_hash_init(FFI_G(weak_types), 0, NULL, zend_ffi_type_hash_dtor, 0); } // TODO: avoid dups ??? type->attr |= ZEND_FFI_ATTR_STORED; zend_hash_next_index_insert_ptr(FFI_G(weak_types), ZEND_FFI_TYPE_MAKE_OWNED(type)); return type; } /* }}} */ static zend_ffi *zend_ffi_load(const char *filename, zend_bool preload) /* {{{ */ { struct stat buf; int fd; char *code, *code_pos, *scope_name, *lib; size_t code_size, scope_name_len; zend_ffi *ffi; DL_HANDLE handle = NULL; zend_ffi_scope *scope = NULL; zend_string *name; zend_ffi_symbol *sym; zend_ffi_tag *tag; void *addr; if (stat(filename, &buf) != 0) { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', file doesn't exist", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', file doesn't exist", filename); } return NULL; } if ((buf.st_mode & S_IFMT) != S_IFREG) { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', not a regular file", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', not a regular file", filename); } return NULL; } code_size = buf.st_size; code = emalloc(code_size + 1); fd = open(filename, O_RDONLY, 0); if (fd < 0 || read(fd, code, code_size) != code_size) { if (preload) { zend_error(E_WARNING, "FFI: Failed pre-loading '%s', cannot read_file", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', cannot read_file", filename); } efree(code); close(fd); return NULL; } close(fd); code[code_size] = 0; FFI_G(symbols) = NULL; FFI_G(tags) = NULL; FFI_G(persistent) = preload; FFI_G(default_type_attr) = preload ? ZEND_FFI_ATTR_STORED | ZEND_FFI_ATTR_PERSISTENT : ZEND_FFI_ATTR_STORED; scope_name = NULL; scope_name_len = 0; lib = NULL; code_pos = zend_ffi_parse_directives(filename, code, &scope_name, &lib, preload); if (!code_pos) { efree(code); FFI_G(persistent) = 0; return NULL; } code_size -= code_pos - code; if (zend_ffi_parse_decl(code_pos, code_size) != SUCCESS) { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s'", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s'", filename); } goto cleanup; } if (lib) { handle = DL_LOAD(lib); if (!handle) { if (preload) { zend_error(E_WARNING, "FFI: Failed pre-loading '%s'", lib); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s'", lib); } goto cleanup; } #ifdef RTLD_DEFAULT } else if (1) { // TODO: this might need to be disabled or protected ??? handle = RTLD_DEFAULT; #endif } if (preload) { if (!scope_name) { scope_name = "C"; } scope_name_len = strlen(scope_name); if (FFI_G(scopes)) { scope = zend_hash_str_find_ptr(FFI_G(scopes), scope_name, scope_name_len); } } if (FFI_G(symbols)) { ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), name, sym) { if (sym->kind == ZEND_FFI_SYM_VAR) { addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(name)); if (!addr) { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', cannot resolve C variable '%s'", filename, ZSTR_VAL(name)); } else { zend_throw_error(zend_ffi_exception_ce, "Failed resolving C variable '%s'", ZSTR_VAL(name)); } if (lib) { DL_UNLOAD(handle); } goto cleanup; } sym->addr = addr; } else if (sym->kind == ZEND_FFI_SYM_FUNC) { zend_string *mangled_name = zend_ffi_mangled_func_name(name, ZEND_FFI_TYPE(sym->type)); addr = DL_FETCH_SYMBOL(handle, ZSTR_VAL(mangled_name)); zend_string_release(mangled_name); if (!addr) { if (preload) { zend_error(E_WARNING, "failed pre-loading '%s', cannot resolve C function '%s'", filename, ZSTR_VAL(name)); } else { zend_throw_error(zend_ffi_exception_ce, "Failed resolving C function '%s'", ZSTR_VAL(name)); } if (lib) { DL_UNLOAD(handle); } goto cleanup; } sym->addr = addr; } if (scope && scope->symbols) { zend_ffi_symbol *old_sym = zend_hash_find_ptr(scope->symbols, name); if (old_sym) { if (zend_ffi_same_symbols(old_sym, sym)) { if (ZEND_FFI_TYPE_IS_OWNED(sym->type) && ZEND_FFI_TYPE(old_sym->type) != ZEND_FFI_TYPE(sym->type)) { zend_ffi_type *type = ZEND_FFI_TYPE(sym->type); zend_ffi_cleanup_type(ZEND_FFI_TYPE(old_sym->type), ZEND_FFI_TYPE(type)); zend_ffi_type_dtor(type); } } else { zend_error(E_WARNING, "FFI: failed pre-loading '%s', redefinition of '%s'", filename, ZSTR_VAL(name)); if (lib) { DL_UNLOAD(handle); } goto cleanup; } } } } ZEND_HASH_FOREACH_END(); } if (preload) { if (scope && scope->tags && FFI_G(tags)) { ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(tags), name, tag) { zend_ffi_tag *old_tag = zend_hash_find_ptr(scope->tags, name); if (old_tag) { if (zend_ffi_same_tags(old_tag, tag)) { if (ZEND_FFI_TYPE_IS_OWNED(tag->type) && ZEND_FFI_TYPE(old_tag->type) != ZEND_FFI_TYPE(tag->type)) { zend_ffi_type *type = ZEND_FFI_TYPE(tag->type); zend_ffi_cleanup_type(ZEND_FFI_TYPE(old_tag->type), ZEND_FFI_TYPE(type)); zend_ffi_type_dtor(type); } } else { zend_error(E_WARNING, "FFI: failed pre-loading '%s', redefinition of '%s %s'", filename, zend_ffi_tag_kind_name[tag->kind], ZSTR_VAL(name)); if (lib) { DL_UNLOAD(handle); } goto cleanup; } } } ZEND_HASH_FOREACH_END(); } if (!scope) { scope = malloc(sizeof(zend_ffi_scope)); scope->symbols = FFI_G(symbols); scope->tags = FFI_G(tags); if (!FFI_G(scopes)) { FFI_G(scopes) = malloc(sizeof(HashTable)); zend_hash_init(FFI_G(scopes), 0, NULL, zend_ffi_scope_hash_dtor, 1); } zend_hash_str_add_ptr(FFI_G(scopes), scope_name, scope_name_len, scope); } else { if (FFI_G(symbols)) { if (!scope->symbols) { scope->symbols = FFI_G(symbols); FFI_G(symbols) = NULL; } else { ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), name, sym) { if (!zend_hash_add_ptr(scope->symbols, name, sym)) { zend_ffi_type_dtor(sym->type); free(sym); } } ZEND_HASH_FOREACH_END(); FFI_G(symbols)->pDestructor = NULL; zend_hash_destroy(FFI_G(symbols)); } } if (FFI_G(tags)) { if (!scope->tags) { scope->tags = FFI_G(tags); FFI_G(tags) = NULL; } else { ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(tags), name, tag) { if (!zend_hash_add_ptr(scope->tags, name, tag)) { zend_ffi_type_dtor(tag->type); free(tag); } } ZEND_HASH_FOREACH_END(); FFI_G(tags)->pDestructor = NULL; zend_hash_destroy(FFI_G(tags)); } } } if (EG(objects_store).object_buckets) { ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); } else { ffi = ecalloc(1, sizeof(zend_ffi)); } ffi->symbols = scope->symbols; ffi->tags = scope->tags; ffi->persistent = 1; } else { ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); ffi->lib = handle; ffi->symbols = FFI_G(symbols); ffi->tags = FFI_G(tags); } efree(code); FFI_G(symbols) = NULL; FFI_G(tags) = NULL; FFI_G(persistent) = 0; return ffi; cleanup: efree(code); if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); pefree(FFI_G(symbols), preload); FFI_G(symbols) = NULL; } if (FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); pefree(FFI_G(tags), preload); FFI_G(tags) = NULL; } FFI_G(persistent) = 0; return NULL; } /* }}} */ ZEND_METHOD(FFI, load) /* {{{ */ { zend_string *fn; zend_ffi *ffi; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(fn) ZEND_PARSE_PARAMETERS_END(); if (CG(compiler_options) & ZEND_COMPILE_PRELOAD_IN_CHILD) { zend_throw_error(zend_ffi_exception_ce, "FFI::load() doesn't work in conjunction with \"opcache.preload_user\". Use \"ffi.preload\" instead."); return; } ffi = zend_ffi_load(ZSTR_VAL(fn), (CG(compiler_options) & ZEND_COMPILE_PRELOAD) != 0); if (ffi) { RETURN_OBJ(&ffi->std); } } /* }}} */ ZEND_METHOD(FFI, scope) /* {{{ */ { zend_string *scope_name; zend_ffi_scope *scope = NULL; zend_ffi *ffi; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(scope_name) ZEND_PARSE_PARAMETERS_END(); if (FFI_G(scopes)) { scope = zend_hash_find_ptr(FFI_G(scopes), scope_name); } if (!scope) { zend_throw_error(zend_ffi_exception_ce, "Failed loading scope '%s'", ZSTR_VAL(scope_name)); return; } ffi = (zend_ffi*)zend_ffi_new(zend_ffi_ce); ffi->symbols = scope->symbols; ffi->tags = scope->tags; ffi->persistent = 1; RETURN_OBJ(&ffi->std); } /* }}} */ static void zend_ffi_cleanup_dcl(zend_ffi_dcl *dcl) /* {{{ */ { if (dcl) { zend_ffi_type_dtor(dcl->type); dcl->type = NULL; } } /* }}} */ static void zend_ffi_throw_parser_error(const char *format, ...) /* {{{ */ { va_list va; char *message = NULL; va_start(va, format); zend_vspprintf(&message, 0, format, va); if (EG(current_execute_data)) { zend_throw_exception(zend_ffi_parser_exception_ce, message, 0); } else { zend_error(E_WARNING, "FFI Parser: %s", message); } efree(message); va_end(va); } /* }}} */ static int zend_ffi_validate_vla(zend_ffi_type *type) /* {{{ */ { if (!FFI_G(allow_vla) && (type->attr & ZEND_FFI_ATTR_VLA)) { zend_ffi_throw_parser_error("'[*]' not allowed in other than function prototype scope at line %d", FFI_G(line)); return FAILURE; } return SUCCESS; } /* }}} */ static int zend_ffi_validate_incomplete_type(zend_ffi_type *type, zend_bool allow_incomplete_tag, zend_bool allow_incomplete_array) /* {{{ */ { if (!allow_incomplete_tag && (type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) { if (FFI_G(tags)) { zend_string *key; zend_ffi_tag *tag; ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(tags), key, tag) { if (ZEND_FFI_TYPE(tag->type) == type) { if (type->kind == ZEND_FFI_TYPE_ENUM) { zend_ffi_throw_parser_error("incomplete 'enum %s' at line %d", ZSTR_VAL(key), FFI_G(line)); } else if (type->attr & ZEND_FFI_ATTR_UNION) { zend_ffi_throw_parser_error("incomplete 'union %s' at line %d", ZSTR_VAL(key), FFI_G(line)); } else { zend_ffi_throw_parser_error("incomplete 'struct %s' at line %d", ZSTR_VAL(key), FFI_G(line)); } return FAILURE; } } ZEND_HASH_FOREACH_END(); } if (FFI_G(symbols)) { zend_string *key; zend_ffi_symbol *sym; ZEND_HASH_FOREACH_STR_KEY_PTR(FFI_G(symbols), key, sym) { if (type == ZEND_FFI_TYPE(sym->type)) { zend_ffi_throw_parser_error("incomplete C type '%s' at line %d", ZSTR_VAL(key), FFI_G(line)); return FAILURE; } } ZEND_HASH_FOREACH_END(); } zend_ffi_throw_parser_error("incomplete type at line %d", FFI_G(line)); return FAILURE; } else if (!allow_incomplete_array && type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY) { zend_ffi_throw_parser_error("'[]' not allowed at line %d", FFI_G(line)); return FAILURE; } else if (!FFI_G(allow_vla) && (type->attr & ZEND_FFI_ATTR_VLA)) { zend_ffi_throw_parser_error("'[*]' not allowed in other than function prototype scope at line %d", FFI_G(line)); return FAILURE; } return SUCCESS; } /* }}} */ static int zend_ffi_validate_type(zend_ffi_type *type, zend_bool allow_incomplete_tag, zend_bool allow_incomplete_array) /* {{{ */ { if (type->kind == ZEND_FFI_TYPE_VOID) { zend_ffi_throw_parser_error("'void' type is not allowed at line %d", FFI_G(line)); return FAILURE; } return zend_ffi_validate_incomplete_type(type, allow_incomplete_tag, allow_incomplete_array); } /* }}} */ static int zend_ffi_validate_var_type(zend_ffi_type *type, zend_bool allow_incomplete_array) /* {{{ */ { if (type->kind == ZEND_FFI_TYPE_FUNC) { zend_ffi_throw_parser_error("'function' type is not allowed at line %d", FFI_G(line)); return FAILURE; } return zend_ffi_validate_type(type, 0, allow_incomplete_array); } /* }}} */ void zend_ffi_validate_type_name(zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_finalize_type(dcl); if (zend_ffi_validate_var_type(ZEND_FFI_TYPE(dcl->type), 0) != SUCCESS) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } } /* }}} */ static int zend_ffi_subst_type(zend_ffi_type **dcl, zend_ffi_type *type) /* {{{ */ { zend_ffi_type *dcl_type; zend_ffi_field *field; if (*dcl == type) { *dcl = ZEND_FFI_TYPE_MAKE_OWNED(type); return 1; } dcl_type = *dcl; switch (dcl_type->kind) { case ZEND_FFI_TYPE_POINTER: return zend_ffi_subst_type(&dcl_type->pointer.type, type); case ZEND_FFI_TYPE_ARRAY: return zend_ffi_subst_type(&dcl_type->array.type, type); case ZEND_FFI_TYPE_FUNC: if (zend_ffi_subst_type(&dcl_type->func.ret_type, type)) { return 1; } if (dcl_type->func.args) { zval *zv; ZEND_HASH_FOREACH_VAL(dcl_type->func.args, zv) { if (zend_ffi_subst_type((zend_ffi_type**)&Z_PTR_P(zv), type)) { return 1; } } ZEND_HASH_FOREACH_END(); } break; case ZEND_FFI_TYPE_STRUCT: ZEND_HASH_FOREACH_PTR(&dcl_type->record.fields, field) { if (zend_ffi_subst_type(&field->type, type)) { return 1; } } ZEND_HASH_FOREACH_END(); break; default: break; } return 0; } /* }}} */ static void zend_ffi_tags_cleanup(zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_tag *tag; ZEND_HASH_FOREACH_PTR(FFI_G(tags), tag) { if (ZEND_FFI_TYPE_IS_OWNED(tag->type)) { zend_ffi_type *type = ZEND_FFI_TYPE(tag->type); zend_ffi_subst_type(&dcl->type, type); tag->type = type; } } ZEND_HASH_FOREACH_END(); zend_hash_destroy(FFI_G(tags)); efree(FFI_G(tags)); } /* }}} */ ZEND_METHOD(FFI, new) /* {{{ */ { zend_string *type_def = NULL; zval *ztype = NULL; zend_ffi_type *type, *type_ptr; zend_ffi_cdata *cdata; void *ptr; zend_bool owned = 1; zend_bool persistent = 0; zend_bool is_const = 0; zend_ffi_flags flags = ZEND_FFI_FLAG_OWNED; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 3) if (Z_TYPE_P(EX_VAR_NUM(0)) == IS_STRING) { Z_PARAM_STR(type_def) } else { Z_PARAM_OBJECT_OF_CLASS(ztype, zend_ffi_ctype_ce) } Z_PARAM_OPTIONAL Z_PARAM_BOOL(owned) Z_PARAM_BOOL(persistent) ZEND_PARSE_PARAMETERS_END(); if (!owned) { flags &= ~ZEND_FFI_FLAG_OWNED; } if (persistent) { flags |= ZEND_FFI_FLAG_PERSISTENT; } if (type_def) { zend_ffi_dcl dcl = ZEND_FFI_ATTR_INIT; if (Z_TYPE(EX(This)) == IS_OBJECT) { zend_ffi *ffi = (zend_ffi*)Z_OBJ(EX(This)); FFI_G(symbols) = ffi->symbols; FFI_G(tags) = ffi->tags; } else { FFI_G(symbols) = NULL; FFI_G(tags) = NULL; } FFI_G(default_type_attr) = 0; if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) != SUCCESS) { zend_ffi_type_dtor(dcl.type); if (Z_TYPE(EX(This)) != IS_OBJECT) { if (FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); efree(FFI_G(tags)); FFI_G(tags) = NULL; } if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } } return; } type = ZEND_FFI_TYPE(dcl.type); if (dcl.attr & ZEND_FFI_ATTR_CONST) { is_const = 1; } if (Z_TYPE(EX(This)) != IS_OBJECT) { if (FFI_G(tags)) { zend_ffi_tags_cleanup(&dcl); } if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } } FFI_G(symbols) = NULL; FFI_G(tags) = NULL; type_ptr = dcl.type; } else { zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(ztype); type_ptr = type = ctype->type; if (ZEND_FFI_TYPE_IS_OWNED(type)) { type = ZEND_FFI_TYPE(type); if (!(type->attr & ZEND_FFI_ATTR_STORED)) { if (GC_REFCOUNT(&ctype->std) == 1) { /* transfer type ownership */ ctype->type = type; } else { ctype->type = type_ptr = type = zend_ffi_remember_type(type); } } } } if (type->size == 0) { zend_throw_error(zend_ffi_exception_ce, "Cannot instantiate FFI\\CData of zero size"); zend_ffi_type_dtor(type_ptr); return; } ptr = pemalloc(type->size, flags & ZEND_FFI_FLAG_PERSISTENT); memset(ptr, 0, type->size); cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); if (type->kind < ZEND_FFI_TYPE_POINTER) { cdata->std.handlers = &zend_ffi_cdata_value_handlers; } cdata->type = type_ptr; cdata->ptr = ptr; cdata->flags = flags; if (is_const) { cdata->flags |= ZEND_FFI_FLAG_CONST; } RETURN_OBJ(&cdata->std); } /* }}} */ ZEND_METHOD(FFI, free) /* {{{ */ { zval *zv; zend_ffi_cdata *cdata; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_OBJECT_OF_CLASS_EX2(zv, zend_ffi_cdata_ce, 0, 1, 0); ZEND_PARSE_PARAMETERS_END(); cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); if (ZEND_FFI_TYPE(cdata->type)->kind == ZEND_FFI_TYPE_POINTER) { if (!cdata->ptr) { zend_throw_error(zend_ffi_exception_ce, "NULL pointer dereference"); return; } if (cdata->ptr != (void*)&cdata->ptr_holder) { pefree(*(void**)cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT); } else { pefree(cdata->ptr_holder, (cdata->flags & ZEND_FFI_FLAG_PERSISTENT) || !is_zend_ptr(cdata->ptr_holder)); } *(void**)cdata->ptr = NULL; } else if (!(cdata->flags & ZEND_FFI_FLAG_OWNED)) { pefree(cdata->ptr, cdata->flags & ZEND_FFI_FLAG_PERSISTENT); cdata->ptr = NULL; cdata->flags &= ~(ZEND_FFI_FLAG_OWNED|ZEND_FFI_FLAG_PERSISTENT); cdata->std.handlers = &zend_ffi_cdata_free_handlers; } else { zend_throw_error(zend_ffi_exception_ce, "free() non a C pointer"); } } /* }}} */ ZEND_METHOD(FFI, cast) /* {{{ */ { zend_string *type_def = NULL; zval *ztype = NULL; zend_ffi_type *old_type, *type, *type_ptr; zend_ffi_cdata *old_cdata, *cdata; zend_bool is_const = 0; zval *zv, *arg; void *ptr; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(2, 2) if (Z_TYPE_P(EX_VAR_NUM(0)) == IS_STRING) { Z_PARAM_STR(type_def) } else { Z_PARAM_OBJECT_OF_CLASS(ztype, zend_ffi_ctype_ce) } Z_PARAM_ZVAL(zv); ZEND_PARSE_PARAMETERS_END(); arg = zv; ZVAL_DEREF(zv); if (type_def) { zend_ffi_dcl dcl = ZEND_FFI_ATTR_INIT; if (Z_TYPE(EX(This)) == IS_OBJECT) { zend_ffi *ffi = (zend_ffi*)Z_OBJ(EX(This)); FFI_G(symbols) = ffi->symbols; FFI_G(tags) = ffi->tags; } else { FFI_G(symbols) = NULL; FFI_G(tags) = NULL; } FFI_G(default_type_attr) = 0; if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) != SUCCESS) { zend_ffi_type_dtor(dcl.type); if (Z_TYPE(EX(This)) != IS_OBJECT) { if (FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); efree(FFI_G(tags)); FFI_G(tags) = NULL; } if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } } return; } type = ZEND_FFI_TYPE(dcl.type); if (dcl.attr & ZEND_FFI_ATTR_CONST) { is_const = 1; } if (Z_TYPE(EX(This)) != IS_OBJECT) { if (FFI_G(tags)) { zend_ffi_tags_cleanup(&dcl); } if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } } FFI_G(symbols) = NULL; FFI_G(tags) = NULL; type_ptr = dcl.type; } else { zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(ztype); type_ptr = type = ctype->type; if (ZEND_FFI_TYPE_IS_OWNED(type)) { type = ZEND_FFI_TYPE(type); if (!(type->attr & ZEND_FFI_ATTR_STORED)) { if (GC_REFCOUNT(&ctype->std) == 1) { /* transfer type ownership */ ctype->type = type; } else { ctype->type = type_ptr = type = zend_ffi_remember_type(type); } } } } if (Z_TYPE_P(zv) != IS_OBJECT || Z_OBJCE_P(zv) != zend_ffi_cdata_ce) { if (type->kind < ZEND_FFI_TYPE_POINTER && Z_TYPE_P(zv) < IS_STRING) { /* numeric conversion */ cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); cdata->std.handlers = &zend_ffi_cdata_value_handlers; cdata->type = type_ptr; cdata->ptr = emalloc(type->size); zend_ffi_zval_to_cdata(cdata->ptr, type, zv); cdata->flags = ZEND_FFI_FLAG_OWNED; if (is_const) { cdata->flags |= ZEND_FFI_FLAG_CONST; } RETURN_OBJ(&cdata->std); } else if (type->kind == ZEND_FFI_TYPE_POINTER && Z_TYPE_P(zv) == IS_LONG) { /* number to pointer conversion */ cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); cdata->type = type_ptr; cdata->ptr = &cdata->ptr_holder; cdata->ptr_holder = (void*)(intptr_t)Z_LVAL_P(zv); if (is_const) { cdata->flags |= ZEND_FFI_FLAG_CONST; } RETURN_OBJ(&cdata->std); } else if (type->kind == ZEND_FFI_TYPE_POINTER && Z_TYPE_P(zv) == IS_NULL) { /* null -> pointer */ cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); cdata->type = type_ptr; cdata->ptr = &cdata->ptr_holder; cdata->ptr_holder = NULL; if (is_const) { cdata->flags |= ZEND_FFI_FLAG_CONST; } RETURN_OBJ(&cdata->std); } else { zend_wrong_parameter_class_error(2, "FFI\\CData", zv); return; } } old_cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); old_type = ZEND_FFI_TYPE(old_cdata->type); ptr = old_cdata->ptr; cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); if (type->kind < ZEND_FFI_TYPE_POINTER) { cdata->std.handlers = &zend_ffi_cdata_value_handlers; } cdata->type = type_ptr; if (old_type->kind == ZEND_FFI_TYPE_POINTER && type->kind != ZEND_FFI_TYPE_POINTER && ZEND_FFI_TYPE(old_type->pointer.type)->kind == ZEND_FFI_TYPE_VOID) { /* automatically dereference void* pointers ??? */ cdata->ptr = *(void**)ptr; } else if (old_type->kind == ZEND_FFI_TYPE_ARRAY && type->kind == ZEND_FFI_TYPE_POINTER) { cdata->ptr = &cdata->ptr_holder; cdata->ptr_holder = old_cdata->ptr; } else if (type->size > old_type->size) { zend_object_release(&cdata->std); zend_throw_error(zend_ffi_exception_ce, "attempt to cast to larger type"); return; } else if (ptr != &old_cdata->ptr_holder) { cdata->ptr = ptr; } else { cdata->ptr = &cdata->ptr_holder; cdata->ptr_holder = old_cdata->ptr_holder; } if (is_const) { cdata->flags |= ZEND_FFI_FLAG_CONST; } if (old_cdata->flags & ZEND_FFI_FLAG_OWNED) { if (GC_REFCOUNT(&old_cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) { /* transfer ownership */ old_cdata->flags &= ~ZEND_FFI_FLAG_OWNED; cdata->flags |= ZEND_FFI_FLAG_OWNED; } else { //???zend_throw_error(zend_ffi_exception_ce, "Attempt to cast owned C pointer"); } } RETURN_OBJ(&cdata->std); } /* }}} */ ZEND_METHOD(FFI, type) /* {{{ */ { zend_ffi_ctype *ctype; zend_ffi_dcl dcl = ZEND_FFI_ATTR_INIT; zend_string *type_def; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(type_def); ZEND_PARSE_PARAMETERS_END(); if (Z_TYPE(EX(This)) == IS_OBJECT) { zend_ffi *ffi = (zend_ffi*)Z_OBJ(EX(This)); FFI_G(symbols) = ffi->symbols; FFI_G(tags) = ffi->tags; } else { FFI_G(symbols) = NULL; FFI_G(tags) = NULL; } FFI_G(default_type_attr) = 0; if (zend_ffi_parse_type(ZSTR_VAL(type_def), ZSTR_LEN(type_def), &dcl) != SUCCESS) { zend_ffi_type_dtor(dcl.type); if (Z_TYPE(EX(This)) != IS_OBJECT) { if (FFI_G(tags)) { zend_hash_destroy(FFI_G(tags)); efree(FFI_G(tags)); FFI_G(tags) = NULL; } if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } } return; } if (Z_TYPE(EX(This)) != IS_OBJECT) { if (FFI_G(tags)) { zend_ffi_tags_cleanup(&dcl); } if (FFI_G(symbols)) { zend_hash_destroy(FFI_G(symbols)); efree(FFI_G(symbols)); FFI_G(symbols) = NULL; } } FFI_G(symbols) = NULL; FFI_G(tags) = NULL; ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce); ctype->type = dcl.type; RETURN_OBJ(&ctype->std); } /* }}} */ ZEND_METHOD(FFI, typeof) /* {{{ */ { zval *zv, *arg; zend_ffi_ctype *ctype; zend_ffi_type *type; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zv); ZEND_PARSE_PARAMETERS_END(); arg = zv; ZVAL_DEREF(zv); if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = cdata->type; if (ZEND_FFI_TYPE_IS_OWNED(type)) { type = ZEND_FFI_TYPE(type); if (!(type->attr & ZEND_FFI_ATTR_STORED)) { if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) { /* transfer type ownership */ cdata->type = type; type = ZEND_FFI_TYPE_MAKE_OWNED(type); } else { cdata->type = type = zend_ffi_remember_type(type); } } } } else { zend_wrong_parameter_class_error(1, "FFI\\CData", zv); return; } ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce); ctype->type = type; RETURN_OBJ(&ctype->std); } /* }}} */ ZEND_METHOD(FFI, arrayType) /* {{{ */ { zval *ztype; zend_ffi_ctype *ctype; zend_ffi_type *type; HashTable *dims; zval *val; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(2, 2) Z_PARAM_OBJECT_OF_CLASS(ztype, zend_ffi_ctype_ce) Z_PARAM_ARRAY_HT(dims) ZEND_PARSE_PARAMETERS_END(); ctype = (zend_ffi_ctype*)Z_OBJ_P(ztype); type = ZEND_FFI_TYPE(ctype->type); if (type->kind == ZEND_FFI_TYPE_FUNC) { zend_throw_error(zend_ffi_exception_ce, "array of functions is not allowed"); return; } else if (type->kind == ZEND_FFI_TYPE_ARRAY && (type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) { zend_throw_error(zend_ffi_exception_ce, "only the leftmost array can be undimensioned"); return; } else if (type->kind == ZEND_FFI_TYPE_VOID) { zend_throw_error(zend_ffi_exception_ce, "array of 'void' is not allowed"); return; } else if (type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG) { zend_throw_error(zend_ffi_exception_ce, "array of incomplete type is not allowed"); return; } if (ZEND_FFI_TYPE_IS_OWNED(ctype->type)) { if (!(type->attr & ZEND_FFI_ATTR_STORED)) { if (GC_REFCOUNT(&ctype->std) == 1) { /* transfer type ownership */ ctype->type = type; type = ZEND_FFI_TYPE_MAKE_OWNED(type); } else { ctype->type = type = zend_ffi_remember_type(type); } } } ZEND_HASH_REVERSE_FOREACH_VAL(dims, val) { zend_long n = zval_get_long(val); zend_ffi_type *new_type; if (n < 0) { zend_throw_error(zend_ffi_exception_ce, "negative array index"); zend_ffi_type_dtor(type); return; } else if (ZEND_FFI_TYPE(type)->kind == ZEND_FFI_TYPE_ARRAY && (ZEND_FFI_TYPE(type)->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) { zend_throw_error(zend_ffi_exception_ce, "only the leftmost array can be undimensioned"); zend_ffi_type_dtor(type); return; } new_type = emalloc(sizeof(zend_ffi_type)); new_type->kind = ZEND_FFI_TYPE_ARRAY; new_type->attr = 0; new_type->size = n * ZEND_FFI_TYPE(type)->size; new_type->align = ZEND_FFI_TYPE(type)->align; new_type->array.type = type; new_type->array.length = n; if (n == 0) { new_type->attr |= ZEND_FFI_ATTR_INCOMPLETE_ARRAY; } type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); } ZEND_HASH_FOREACH_END(); ctype = (zend_ffi_ctype*)zend_ffi_ctype_new(zend_ffi_ctype_ce); ctype->type = type; RETURN_OBJ(&ctype->std); } /* }}} */ ZEND_METHOD(FFI, addr) /* {{{ */ { zend_ffi_type *type, *new_type; zend_ffi_cdata *cdata, *new_cdata; zval *zv, *arg; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zv) ZEND_PARSE_PARAMETERS_END(); arg = zv; ZVAL_DEREF(zv); if (Z_TYPE_P(zv) != IS_OBJECT || Z_OBJCE_P(zv) != zend_ffi_cdata_ce) { zend_wrong_parameter_class_error(1, "FFI\\CData", zv); return; } cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(cdata->type); new_type = emalloc(sizeof(zend_ffi_type)); new_type->kind = ZEND_FFI_TYPE_POINTER; new_type->attr = 0; new_type->size = sizeof(void*); new_type->align = _Alignof(void*); /* life-time (source must relive the resulting pointer) ??? */ new_type->pointer.type = type; new_cdata = (zend_ffi_cdata*)zend_ffi_cdata_new(zend_ffi_cdata_ce); new_cdata->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); new_cdata->ptr_holder = cdata->ptr; new_cdata->ptr = &new_cdata->ptr_holder; if (GC_REFCOUNT(&cdata->std) == 1 && Z_REFCOUNT_P(arg) == 1) { if (ZEND_FFI_TYPE_IS_OWNED(cdata->type)) { /* transfer type ownership */ cdata->type = type; new_type->pointer.type = ZEND_FFI_TYPE_MAKE_OWNED(type); } if (cdata->flags & ZEND_FFI_FLAG_OWNED) { /* transfer ownership */ cdata->flags &= ~ZEND_FFI_FLAG_OWNED; new_cdata->flags |= ZEND_FFI_FLAG_OWNED; } } RETURN_OBJ(&new_cdata->std); } /* }}} */ ZEND_METHOD(FFI, sizeof) /* {{{ */ { zval *zv; zend_ffi_type *type; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zv); ZEND_PARSE_PARAMETERS_END(); ZVAL_DEREF(zv); if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(cdata->type); } else if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_ctype_ce) { zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(ctype->type); } else { zend_wrong_parameter_class_error(1, "FFI\\CData or FFI\\CType", zv); return; } RETURN_LONG(type->size); } /* }}} */ ZEND_METHOD(FFI, alignof) /* {{{ */ { zval *zv; zend_ffi_type *type; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zv); ZEND_PARSE_PARAMETERS_END(); ZVAL_DEREF(zv); if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_cdata_ce) { zend_ffi_cdata *cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(cdata->type); } else if (Z_TYPE_P(zv) == IS_OBJECT && Z_OBJCE_P(zv) == zend_ffi_ctype_ce) { zend_ffi_ctype *ctype = (zend_ffi_ctype*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(ctype->type); } else { zend_wrong_parameter_class_error(1, "FFI\\CData or FFI\\CType", zv); return; } RETURN_LONG(type->align); } /* }}} */ ZEND_METHOD(FFI, memcpy) /* {{{ */ { zval *zv1, *zv2; zend_ffi_cdata *cdata1, *cdata2; zend_ffi_type *type1, *type2; void *ptr1, *ptr2; zend_long size; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_OBJECT_OF_CLASS_EX2(zv1, zend_ffi_cdata_ce, 0, 1, 0); Z_PARAM_ZVAL(zv2) Z_PARAM_LONG(size) ZEND_PARSE_PARAMETERS_END(); cdata1 = (zend_ffi_cdata*)Z_OBJ_P(zv1); type1 = ZEND_FFI_TYPE(cdata1->type); if (type1->kind == ZEND_FFI_TYPE_POINTER) { ptr1 = *(void**)cdata1->ptr; } else { ptr1 = cdata1->ptr; if (type1->kind != ZEND_FFI_TYPE_POINTER && size > type1->size) { zend_throw_error(zend_ffi_exception_ce, "attempt to write over data boundary"); return; } } ZVAL_DEREF(zv2); if (Z_TYPE_P(zv2) == IS_STRING) { ptr2 = Z_STRVAL_P(zv2); if (size > Z_STRLEN_P(zv2)) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over string boundary"); return; } } else if (Z_TYPE_P(zv2) == IS_OBJECT && Z_OBJCE_P(zv2) == zend_ffi_cdata_ce) { cdata2 = (zend_ffi_cdata*)Z_OBJ_P(zv2); type2 = ZEND_FFI_TYPE(cdata2->type); if (type2->kind == ZEND_FFI_TYPE_POINTER) { ptr2 = *(void**)cdata2->ptr; } else { ptr2 = cdata2->ptr; if (type2->kind != ZEND_FFI_TYPE_POINTER && size > type2->size) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary"); return; } } } else { zend_wrong_parameter_class_error(2, "FFI\\CData or string", zv2); return; } memcpy(ptr1, ptr2, size); } /* }}} */ ZEND_METHOD(FFI, memcmp) /* {{{ */ { zval *zv1, *zv2; zend_ffi_cdata *cdata1, *cdata2; zend_ffi_type *type1, *type2; void *ptr1, *ptr2; zend_long size; int ret; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_ZVAL(zv1); Z_PARAM_ZVAL(zv2); Z_PARAM_LONG(size) ZEND_PARSE_PARAMETERS_END(); ZVAL_DEREF(zv1); if (Z_TYPE_P(zv1) == IS_STRING) { ptr1 = Z_STRVAL_P(zv1); if (size > Z_STRLEN_P(zv1)) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over string boundary"); return; } } else if (Z_TYPE_P(zv1) == IS_OBJECT && Z_OBJCE_P(zv1) == zend_ffi_cdata_ce) { cdata1 = (zend_ffi_cdata*)Z_OBJ_P(zv1); type1 = ZEND_FFI_TYPE(cdata1->type); if (type1->kind == ZEND_FFI_TYPE_POINTER) { ptr1 = *(void**)cdata1->ptr; } else { ptr1 = cdata1->ptr; if (type1->kind != ZEND_FFI_TYPE_POINTER && size > type1->size) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary"); return; } } } else { zend_wrong_parameter_class_error(1, "FFI\\CData or string", zv1); return; } ZVAL_DEREF(zv2); if (Z_TYPE_P(zv2) == IS_STRING) { ptr2 = Z_STRVAL_P(zv2); if (size > Z_STRLEN_P(zv2)) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over string boundary"); return; } } else if (Z_TYPE_P(zv2) == IS_OBJECT && Z_OBJCE_P(zv2) == zend_ffi_cdata_ce) { cdata2 = (zend_ffi_cdata*)Z_OBJ_P(zv2); type2 = ZEND_FFI_TYPE(cdata2->type); if (type2->kind == ZEND_FFI_TYPE_POINTER) { ptr2 = *(void**)cdata2->ptr; } else { ptr2 = cdata2->ptr; if (type2->kind != ZEND_FFI_TYPE_POINTER && size > type2->size) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary"); return; } } } else { zend_wrong_parameter_class_error(2, "FFI\\CData or string", zv2); return; } ret = memcmp(ptr1, ptr2, size); if (ret == 0) { RETVAL_LONG(0); } else if (ret < 0) { RETVAL_LONG(-1); } else { RETVAL_LONG(1); } } /* }}} */ ZEND_METHOD(FFI, memset) /* {{{ */ { zval *zv; zend_ffi_cdata *cdata; zend_ffi_type *type; void *ptr; zend_long ch, size; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(3, 3) Z_PARAM_OBJECT_OF_CLASS_EX2(zv, zend_ffi_cdata_ce, 0, 1, 0); Z_PARAM_LONG(ch) Z_PARAM_LONG(size) ZEND_PARSE_PARAMETERS_END(); cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(cdata->type); if (type->kind == ZEND_FFI_TYPE_POINTER) { ptr = *(void**)cdata->ptr; } else { ptr = cdata->ptr; if (type->kind != ZEND_FFI_TYPE_POINTER && size > type->size) { zend_throw_error(zend_ffi_exception_ce, "attempt to write over data boundary"); return; } } memset(ptr, ch, size); } /* }}} */ ZEND_METHOD(FFI, string) /* {{{ */ { zval *zv; zend_ffi_cdata *cdata; zend_ffi_type *type; void *ptr; zend_long size = 0; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 2) Z_PARAM_OBJECT_OF_CLASS_EX2(zv, zend_ffi_cdata_ce, 0, 1, 0); Z_PARAM_OPTIONAL Z_PARAM_LONG(size) ZEND_PARSE_PARAMETERS_END(); cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(cdata->type); if (EX_NUM_ARGS() == 2) { if (type->kind == ZEND_FFI_TYPE_POINTER) { ptr = *(void**)cdata->ptr; } else { ptr = cdata->ptr; if (type->kind != ZEND_FFI_TYPE_POINTER && size > type->size) { zend_throw_error(zend_ffi_exception_ce, "attempt to read over data boundary"); return; } } RETURN_STRINGL((char*)ptr, size); } else { if (type->kind == ZEND_FFI_TYPE_POINTER && ZEND_FFI_TYPE(type->pointer.type)->kind == ZEND_FFI_TYPE_CHAR) { ptr = *(void**)cdata->ptr; } else if (type->kind == ZEND_FFI_TYPE_ARRAY && ZEND_FFI_TYPE(type->array.type)->kind == ZEND_FFI_TYPE_CHAR) { ptr = cdata->ptr; } else { zend_throw_error(zend_ffi_exception_ce, "FFI\\Cdata is not a C string"); return; } RETURN_STRING((char*)ptr); } } /* }}} */ ZEND_METHOD(FFI, isNull) /* {{{ */ { zval *zv; zend_ffi_cdata *cdata; zend_ffi_type *type; ZEND_FFI_VALIDATE_API_RESTRICTION(); ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(zv); ZEND_PARSE_PARAMETERS_END(); ZVAL_DEREF(zv); if (Z_TYPE_P(zv) != IS_OBJECT || Z_OBJCE_P(zv) != zend_ffi_cdata_ce) { zend_wrong_parameter_class_error(1, "FFI\\CData", zv); return; } cdata = (zend_ffi_cdata*)Z_OBJ_P(zv); type = ZEND_FFI_TYPE(cdata->type); if (type->kind != ZEND_FFI_TYPE_POINTER){ zend_throw_error(zend_ffi_exception_ce, "FFI\\Cdata is not a pointer"); return; } RETURN_BOOL(*(void**)cdata->ptr == NULL); } /* }}} */ ZEND_BEGIN_ARG_INFO_EX(arginfo_func_cdef, 0, 0, 0) ZEND_ARG_INFO(0, code) ZEND_ARG_INFO(0, lib) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_load, 0, 0, 1) ZEND_ARG_INFO(0, filename) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_scope, 0, 0, 1) ZEND_ARG_INFO(0, scope_name) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_new, 0, 0, 1) ZEND_ARG_INFO(0, type) ZEND_ARG_INFO(0, owned) ZEND_ARG_INFO(0, persistent) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_free, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_cast, 0, 0, 2) ZEND_ARG_INFO(0, type) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_type, 0, 0, 1) ZEND_ARG_INFO(0, type) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_typeof, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_array, 0, 0, 2) ZEND_ARG_INFO(0, type) ZEND_ARG_INFO(0, dims) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_addr, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_sizeof, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_alignof, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_memcpy, 0, 0, 3) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, dst) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, src) ZEND_ARG_INFO(0, size) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_memcmp, 0, 0, 3) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr2) ZEND_ARG_INFO(0, size) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_memset, 0, 0, 3) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_ARG_INFO(0, ch) ZEND_ARG_INFO(0, size) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_string, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_ARG_INFO(0, size) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_func_isnull, 0, 0, 1) ZEND_ARG_INFO(ZEND_SEND_PREFER_REF, ptr) ZEND_END_ARG_INFO() static const zend_function_entry zend_ffi_functions[] = { ZEND_ME(FFI, cdef, arginfo_func_cdef, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, load, arginfo_func_load, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, scope, arginfo_func_scope, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, new, arginfo_func_new, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, free, arginfo_func_free, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, cast, arginfo_func_cast, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, type, arginfo_func_type, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, typeof, arginfo_func_typeof, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, arrayType, arginfo_func_array, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, addr, arginfo_func_addr, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, sizeof, arginfo_func_sizeof, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, alignof, arginfo_func_alignof, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, memcpy, arginfo_func_memcpy, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, memcmp, arginfo_func_memcmp, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, memset, arginfo_func_memset, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, string, arginfo_func_string, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(FFI, isNull, arginfo_func_isnull, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_FE_END }; static char *zend_ffi_parse_directives(const char *filename, char *code_pos, char **scope_name, char **lib, zend_bool preload) /* {{{ */ { char *p; *scope_name = NULL; *lib = NULL; while (*code_pos == '#') { if (strncmp(code_pos, "#define FFI_SCOPE", sizeof("#define FFI_SCOPE") - 1) == 0 && (code_pos[sizeof("#define FFI_SCOPE") - 1] == ' ' || code_pos[sizeof("#define FFI_SCOPE") - 1] == '\t')) { p = code_pos + sizeof("#define FFI_SCOPE"); while (*p == ' ' || *p == '\t') { p++; } if (*p != '"') { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_SCOPE define", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_SCOPE define", filename); } return NULL; } p++; if (*scope_name) { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', FFI_SCOPE defined twice", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', FFI_SCOPE defined twice", filename); } return NULL; } *scope_name = p; while (1) { if (*p == '\"') { *p = 0; p++; break; } else if (*p <= ' ') { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_SCOPE define", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_SCOPE define", filename); } return NULL; } p++; } while (*p == ' ' || *p == '\t') { p++; } while (*p == '\r' || *p == '\n') { p++; } code_pos = p; } else if (strncmp(code_pos, "#define FFI_LIB", sizeof("#define FFI_LIB") - 1) == 0 && (code_pos[sizeof("#define FFI_LIB") - 1] == ' ' || code_pos[sizeof("#define FFI_LIB") - 1] == '\t')) { p = code_pos + sizeof("#define FFI_LIB"); while (*p == ' ' || *p == '\t') { p++; } if (*p != '"') { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_LIB define", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_LIB define", filename); } return NULL; } p++; if (*lib) { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', FFI_LIB defined twice", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', FFI_LIB defined twice", filename); } return NULL; } *lib = p; while (1) { if (*p == '\"') { *p = 0; p++; break; } else if (*p <= ' ') { if (preload) { zend_error(E_WARNING, "FFI: failed pre-loading '%s', bad FFI_LIB define", filename); } else { zend_throw_error(zend_ffi_exception_ce, "Failed loading '%s', bad FFI_LIB define", filename); } return NULL; } p++; } while (*p == ' ' || *p == '\t') { p++; } while (*p == '\r' || *p == '\n') { p++; } code_pos = p; } else { break; } } return code_pos; } /* }}} */ static ZEND_COLD zend_function *zend_fake_get_constructor(zend_object *object) /* {{{ */ { zend_throw_error(NULL, "Instantiation of '%s' is not allowed", ZSTR_VAL(object->ce->name)); return NULL; } /* }}} */ static ZEND_COLD zend_never_inline void zend_bad_array_access(zend_class_entry *ce) /* {{{ */ { zend_throw_error(NULL, "Cannot use object of type %s as array", ZSTR_VAL(ce->name)); } /* }}} */ static ZEND_COLD zval *zend_fake_read_dimension(zval *object, zval *offset, int type, zval *rv) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); return NULL; } /* }}} */ static ZEND_COLD void zend_fake_write_dimension(zval *object, zval *offset, zval *value) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); } /* }}} */ static ZEND_COLD int zend_fake_has_dimension(zval *object, zval *offset, int check_empty) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); return 0; } /* }}} */ static ZEND_COLD void zend_fake_unset_dimension(zval *object, zval *offset) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); } /* }}} */ static ZEND_COLD zend_never_inline void zend_bad_property_access(zend_class_entry *ce) /* {{{ */ { zend_throw_error(NULL, "Cannot access property of object of type %s", ZSTR_VAL(ce->name)); } /* }}} */ static ZEND_COLD zval *zend_fake_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_property_access(ce); return &EG(uninitialized_zval); } /* }}} */ static ZEND_COLD zval *zend_fake_write_property(zval *object, zval *member, zval *value, void **cache_slot) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); return value; } /* }}} */ static ZEND_COLD int zend_fake_has_property(zval *object, zval *member, int has_set_exists, void **cache_slot) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); return 0; } /* }}} */ static ZEND_COLD void zend_fake_unset_property(zval *object, zval *member, void **cache_slot) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(object); zend_bad_array_access(ce); } /* }}} */ static zval *zend_fake_get_property_ptr_ptr(zval *object, zval *member, int type, void **cache_slot) /* {{{ */ { return NULL; } /* }}} */ static ZEND_COLD zend_function *zend_fake_get_method(zend_object **obj_ptr, zend_string *method_name, const zval *key) /* {{{ */ { zend_class_entry *ce = (*obj_ptr)->ce; zend_throw_error(NULL, "Object of type %s does not support method calls", ZSTR_VAL(ce->name)); return NULL; } /* }}} */ static HashTable *zend_fake_get_properties(zval *object) /* {{{ */ { return (HashTable*)&zend_empty_array; } /* }}} */ static HashTable *zend_fake_get_gc(zval *object, zval **table, int *n) /* {{{ */ { *table = NULL; *n = 0; return NULL; } /* }}} */ static ZEND_COLD zend_never_inline void zend_ffi_use_after_free(void) /* {{{ */ { zend_throw_error(zend_ffi_exception_ce, "Use after free()"); } /* }}} */ static zend_object *zend_ffi_free_clone_obj(zval *zobject) /* {{{ */ { zend_ffi_use_after_free(); return NULL; } /* }}} */ static ZEND_COLD zval *zend_ffi_free_read_dimension(zval *object, zval *offset, int type, zval *rv) /* {{{ */ { zend_ffi_use_after_free(); return NULL; } /* }}} */ static ZEND_COLD void zend_ffi_free_write_dimension(zval *object, zval *offset, zval *value) /* {{{ */ { zend_ffi_use_after_free(); } /* }}} */ static ZEND_COLD int zend_ffi_free_has_dimension(zval *object, zval *offset, int check_empty) /* {{{ */ { zend_ffi_use_after_free(); return 0; } /* }}} */ static ZEND_COLD void zend_ffi_free_unset_dimension(zval *object, zval *offset) /* {{{ */ { zend_ffi_use_after_free(); } /* }}} */ static ZEND_COLD zval *zend_ffi_free_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv) /* {{{ */ { zend_ffi_use_after_free(); return &EG(uninitialized_zval); } /* }}} */ static ZEND_COLD zval *zend_ffi_free_write_property(zval *object, zval *member, zval *value, void **cache_slot) /* {{{ */ { zend_ffi_use_after_free(); return value; } /* }}} */ static ZEND_COLD int zend_ffi_free_has_property(zval *object, zval *member, int has_set_exists, void **cache_slot) /* {{{ */ { zend_ffi_use_after_free(); return 0; } /* }}} */ static ZEND_COLD void zend_ffi_free_unset_property(zval *object, zval *member, void **cache_slot) /* {{{ */ { zend_ffi_use_after_free(); } /* }}} */ static zval* zend_ffi_free_get(zval *object, zval *rv) /* {{{ */ { zend_ffi_use_after_free(); return NULL; } /* }}} */ static HashTable *zend_ffi_free_get_debug_info(zval *object, int *is_temp) /* {{{ */ { zend_ffi_use_after_free(); return NULL; } /* }}} */ static ZEND_INI_MH(OnUpdateFFIEnable) /* {{{ */ { if (zend_string_equals_literal_ci(new_value, "preload")) { FFI_G(restriction) = ZEND_FFI_PRELOAD; } else { FFI_G(restriction) = (zend_ffi_api_restriction)zend_ini_parse_bool(new_value); } return SUCCESS; } /* }}} */ static ZEND_INI_DISP(zend_ffi_enable_displayer_cb) /* {{{ */ { if (FFI_G(restriction) == ZEND_FFI_PRELOAD) { ZEND_PUTS("preload"); } else if (FFI_G(restriction) == ZEND_FFI_ENABLED) { ZEND_PUTS("On"); } else { ZEND_PUTS("Off"); } } /* }}} */ ZEND_INI_BEGIN() ZEND_INI_ENTRY3_EX("ffi.enable", "preload", ZEND_INI_SYSTEM, OnUpdateFFIEnable, NULL, NULL, NULL, zend_ffi_enable_displayer_cb) STD_ZEND_INI_ENTRY("ffi.preload", NULL, ZEND_INI_SYSTEM, OnUpdateString, preload, zend_ffi_globals, ffi_globals) ZEND_INI_END() static int zend_ffi_preload_glob(const char *filename) /* {{{ */ { #ifdef HAVE_GLOB glob_t globbuf; int ret; unsigned int i; memset(&globbuf, 0, sizeof(glob_t)); ret = glob(filename, 0, NULL, &globbuf); #ifdef GLOB_NOMATCH if (ret == GLOB_NOMATCH || !globbuf.gl_pathc) { #else if (!globbuf.gl_pathc) { #endif /* pass */ } else { for(i=0 ; ice_flags |= ZEND_ACC_FINAL; INIT_CLASS_ENTRY(ce, "FFI", zend_ffi_functions); zend_ffi_ce = zend_register_internal_class(&ce); zend_ffi_ce->ce_flags |= ZEND_ACC_FINAL; zend_ffi_ce->create_object = zend_ffi_new; zend_ffi_ce->serialize = zend_class_serialize_deny; zend_ffi_ce->unserialize = zend_class_unserialize_deny; memcpy(&zend_ffi_new_fn, zend_hash_str_find_ptr(&zend_ffi_ce->function_table, "new", sizeof("new")-1), sizeof(zend_internal_function)); zend_ffi_new_fn.fn_flags &= ~ZEND_ACC_STATIC; memcpy(&zend_ffi_cast_fn, zend_hash_str_find_ptr(&zend_ffi_ce->function_table, "cast", sizeof("cast")-1), sizeof(zend_internal_function)); zend_ffi_cast_fn.fn_flags &= ~ZEND_ACC_STATIC; memcpy(&zend_ffi_type_fn, zend_hash_str_find_ptr(&zend_ffi_ce->function_table, "type", sizeof("type")-1), sizeof(zend_internal_function)); zend_ffi_type_fn.fn_flags &= ~ZEND_ACC_STATIC; memcpy(&zend_ffi_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_ffi_handlers.get_constructor = zend_fake_get_constructor; zend_ffi_handlers.free_obj = zend_ffi_free_obj; zend_ffi_handlers.clone_obj = NULL; zend_ffi_handlers.read_property = zend_ffi_read_var; zend_ffi_handlers.write_property = zend_ffi_write_var; zend_ffi_handlers.read_dimension = zend_fake_read_dimension; zend_ffi_handlers.write_dimension = zend_fake_write_dimension; zend_ffi_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_handlers.has_property = zend_fake_has_property; zend_ffi_handlers.unset_property = zend_fake_unset_property; zend_ffi_handlers.has_dimension = zend_fake_has_dimension; zend_ffi_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_handlers.get_method = zend_ffi_get_func; zend_ffi_handlers.compare_objects = NULL; zend_ffi_handlers.cast_object = NULL; zend_ffi_handlers.get_debug_info = NULL; zend_ffi_handlers.get_closure = NULL; zend_ffi_handlers.get_properties = zend_fake_get_properties; zend_ffi_handlers.get_gc = zend_fake_get_gc; zend_declare_class_constant_long(zend_ffi_ce, "__BIGGEST_ALIGNMENT__", sizeof("__BIGGEST_ALIGNMENT__")-1, __BIGGEST_ALIGNMENT__); INIT_NS_CLASS_ENTRY(ce, "FFI", "CData", NULL); zend_ffi_cdata_ce = zend_register_internal_class(&ce); zend_ffi_cdata_ce->ce_flags |= ZEND_ACC_FINAL; zend_ffi_cdata_ce->create_object = zend_ffi_cdata_new; zend_ffi_cdata_ce->get_iterator = zend_ffi_cdata_get_iterator; zend_ffi_cdata_ce->serialize = zend_class_serialize_deny; zend_ffi_cdata_ce->unserialize = zend_class_unserialize_deny; memcpy(&zend_ffi_cdata_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_ffi_cdata_handlers.get_constructor = zend_fake_get_constructor; zend_ffi_cdata_handlers.free_obj = zend_ffi_cdata_free_obj; zend_ffi_cdata_handlers.clone_obj = zend_ffi_cdata_clone_obj; zend_ffi_cdata_handlers.read_property = zend_ffi_cdata_read_field; zend_ffi_cdata_handlers.write_property = zend_ffi_cdata_write_field; zend_ffi_cdata_handlers.read_dimension = zend_ffi_cdata_read_dim; zend_ffi_cdata_handlers.write_dimension = zend_ffi_cdata_write_dim; zend_ffi_cdata_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_cdata_handlers.has_property = zend_fake_has_property; zend_ffi_cdata_handlers.unset_property = zend_fake_unset_property; zend_ffi_cdata_handlers.has_dimension = zend_fake_has_dimension; zend_ffi_cdata_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_cdata_handlers.get_method = zend_fake_get_method; zend_ffi_cdata_handlers.get_class_name = zend_ffi_cdata_get_class_name; zend_ffi_cdata_handlers.do_operation = zend_ffi_cdata_do_operation; zend_ffi_cdata_handlers.compare_objects = zend_ffi_cdata_compare_objects; zend_ffi_cdata_handlers.cast_object = zend_ffi_cdata_cast_object; zend_ffi_cdata_handlers.count_elements = zend_ffi_cdata_count_elements; zend_ffi_cdata_handlers.get_debug_info = zend_ffi_cdata_get_debug_info; zend_ffi_cdata_handlers.get_closure = zend_ffi_cdata_get_closure; zend_ffi_cdata_handlers.get_properties = zend_fake_get_properties; zend_ffi_cdata_handlers.get_gc = zend_fake_get_gc; memcpy(&zend_ffi_cdata_value_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_ffi_cdata_value_handlers.get_constructor = zend_fake_get_constructor; zend_ffi_cdata_value_handlers.free_obj = zend_ffi_cdata_free_obj; zend_ffi_cdata_value_handlers.clone_obj = zend_ffi_cdata_clone_obj; zend_ffi_cdata_value_handlers.read_property = zend_ffi_cdata_get; zend_ffi_cdata_value_handlers.write_property = zend_ffi_cdata_set; zend_ffi_cdata_value_handlers.read_dimension = zend_fake_read_dimension; zend_ffi_cdata_value_handlers.write_dimension = zend_fake_write_dimension; zend_ffi_cdata_value_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_cdata_value_handlers.has_property = zend_fake_has_property; zend_ffi_cdata_value_handlers.unset_property = zend_fake_unset_property; zend_ffi_cdata_value_handlers.has_dimension = zend_fake_has_dimension; zend_ffi_cdata_value_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_cdata_value_handlers.get_method = zend_fake_get_method; zend_ffi_cdata_value_handlers.get_class_name = zend_ffi_cdata_get_class_name; zend_ffi_cdata_value_handlers.compare_objects = zend_ffi_cdata_compare_objects; zend_ffi_cdata_value_handlers.cast_object = zend_ffi_cdata_cast_object; zend_ffi_cdata_value_handlers.count_elements = NULL; zend_ffi_cdata_value_handlers.get_debug_info = zend_ffi_cdata_get_debug_info; zend_ffi_cdata_value_handlers.get_closure = NULL; zend_ffi_cdata_value_handlers.get_properties = zend_fake_get_properties; zend_ffi_cdata_value_handlers.get_gc = zend_fake_get_gc; memcpy(&zend_ffi_cdata_free_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_ffi_cdata_free_handlers.get_constructor = zend_fake_get_constructor; zend_ffi_cdata_free_handlers.free_obj = zend_ffi_cdata_free_obj; zend_ffi_cdata_free_handlers.clone_obj = zend_ffi_free_clone_obj; zend_ffi_cdata_free_handlers.read_property = zend_ffi_free_read_property; zend_ffi_cdata_free_handlers.write_property = zend_ffi_free_write_property; zend_ffi_cdata_free_handlers.read_dimension = zend_ffi_free_read_dimension; zend_ffi_cdata_free_handlers.write_dimension = zend_ffi_free_write_dimension; zend_ffi_cdata_free_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_cdata_free_handlers.get = zend_ffi_free_get; zend_ffi_cdata_free_handlers.has_property = zend_ffi_free_has_property; zend_ffi_cdata_free_handlers.unset_property = zend_ffi_free_unset_property; zend_ffi_cdata_free_handlers.has_dimension = zend_ffi_free_has_dimension; zend_ffi_cdata_free_handlers.unset_dimension = zend_ffi_free_unset_dimension; zend_ffi_cdata_free_handlers.get_method = zend_fake_get_method; zend_ffi_cdata_free_handlers.get_class_name = zend_ffi_cdata_get_class_name; zend_ffi_cdata_free_handlers.compare_objects = zend_ffi_cdata_compare_objects; zend_ffi_cdata_free_handlers.cast_object = NULL; zend_ffi_cdata_free_handlers.count_elements = NULL; zend_ffi_cdata_free_handlers.get_debug_info = zend_ffi_free_get_debug_info; zend_ffi_cdata_free_handlers.get_closure = NULL; zend_ffi_cdata_free_handlers.get_properties = zend_fake_get_properties; zend_ffi_cdata_free_handlers.get_gc = zend_fake_get_gc; INIT_NS_CLASS_ENTRY(ce, "FFI", "CType", NULL); zend_ffi_ctype_ce = zend_register_internal_class(&ce); zend_ffi_ctype_ce->ce_flags |= ZEND_ACC_FINAL; zend_ffi_ctype_ce->create_object = zend_ffi_ctype_new; zend_ffi_ctype_ce->serialize = zend_class_serialize_deny; zend_ffi_ctype_ce->unserialize = zend_class_unserialize_deny; memcpy(&zend_ffi_ctype_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); zend_ffi_ctype_handlers.get_constructor = zend_fake_get_constructor; zend_ffi_ctype_handlers.free_obj = zend_ffi_ctype_free_obj; zend_ffi_ctype_handlers.clone_obj = NULL; zend_ffi_ctype_handlers.read_property = zend_fake_read_property; zend_ffi_ctype_handlers.write_property = zend_fake_write_property; zend_ffi_ctype_handlers.read_dimension = zend_fake_read_dimension; zend_ffi_ctype_handlers.write_dimension = zend_fake_write_dimension; zend_ffi_ctype_handlers.get_property_ptr_ptr = zend_fake_get_property_ptr_ptr; zend_ffi_ctype_handlers.has_property = zend_fake_has_property; zend_ffi_ctype_handlers.unset_property = zend_fake_unset_property; zend_ffi_ctype_handlers.has_dimension = zend_fake_has_dimension; zend_ffi_ctype_handlers.unset_dimension = zend_fake_unset_dimension; zend_ffi_ctype_handlers.get_method = zend_fake_get_method; zend_ffi_ctype_handlers.get_class_name = zend_ffi_ctype_get_class_name; zend_ffi_ctype_handlers.compare_objects = zend_ffi_ctype_compare_objects; zend_ffi_ctype_handlers.cast_object = NULL; zend_ffi_ctype_handlers.count_elements = NULL; zend_ffi_ctype_handlers.get_debug_info = zend_ffi_ctype_get_debug_info; zend_ffi_ctype_handlers.get_closure = NULL; zend_ffi_ctype_handlers.get_properties = zend_fake_get_properties; zend_ffi_ctype_handlers.get_gc = zend_fake_get_gc; if (FFI_G(preload)) { if (zend_ffi_preload(FFI_G(preload)) != SUCCESS) { return FAILURE; } } return SUCCESS; } /* }}} */ /* {{{ ZEND_RSHUTDOWN_FUNCTION */ ZEND_RSHUTDOWN_FUNCTION(ffi) { if (FFI_G(callbacks)) { zend_hash_destroy(FFI_G(callbacks)); efree(FFI_G(callbacks)); FFI_G(callbacks) = NULL; } if (FFI_G(weak_types)) { #if 0 fprintf(stderr, "WeakTypes: %d\n", zend_hash_num_elements(FFI_G(weak_types))); #endif zend_hash_destroy(FFI_G(weak_types)); efree(FFI_G(weak_types)); FFI_G(weak_types) = NULL; } return SUCCESS; } /* }}} */ /* {{{ ZEND_MINFO_FUNCTION */ ZEND_MINFO_FUNCTION(ffi) { php_info_print_table_start(); php_info_print_table_header(2, "FFI support", "enabled"); php_info_print_table_end(); DISPLAY_INI_ENTRIES(); } /* }}} */ static const zend_ffi_type zend_ffi_type_void = {.kind=ZEND_FFI_TYPE_VOID, .size=1, .align=1}; static const zend_ffi_type zend_ffi_type_char = {.kind=ZEND_FFI_TYPE_CHAR, .size=1, .align=_Alignof(char)}; static const zend_ffi_type zend_ffi_type_bool = {.kind=ZEND_FFI_TYPE_BOOL, .size=1, .align=_Alignof(uint8_t)}; static const zend_ffi_type zend_ffi_type_sint8 = {.kind=ZEND_FFI_TYPE_SINT8, .size=1, .align=_Alignof(int8_t)}; static const zend_ffi_type zend_ffi_type_uint8 = {.kind=ZEND_FFI_TYPE_UINT8, .size=1, .align=_Alignof(uint8_t)}; static const zend_ffi_type zend_ffi_type_sint16 = {.kind=ZEND_FFI_TYPE_SINT16, .size=2, .align=_Alignof(int16_t)}; static const zend_ffi_type zend_ffi_type_uint16 = {.kind=ZEND_FFI_TYPE_UINT16, .size=2, .align=_Alignof(uint16_t)}; static const zend_ffi_type zend_ffi_type_sint32 = {.kind=ZEND_FFI_TYPE_SINT32, .size=4, .align=_Alignof(int32_t)}; static const zend_ffi_type zend_ffi_type_uint32 = {.kind=ZEND_FFI_TYPE_UINT32, .size=4, .align=_Alignof(uint32_t)}; static const zend_ffi_type zend_ffi_type_sint64 = {.kind=ZEND_FFI_TYPE_SINT64, .size=8, .align=_Alignof(int64_t)}; static const zend_ffi_type zend_ffi_type_uint64 = {.kind=ZEND_FFI_TYPE_UINT64, .size=8, .align=_Alignof(uint64_t)}; static const zend_ffi_type zend_ffi_type_float = {.kind=ZEND_FFI_TYPE_FLOAT, .size=sizeof(float), .align=_Alignof(float)}; static const zend_ffi_type zend_ffi_type_double = {.kind=ZEND_FFI_TYPE_DOUBLE, .size=sizeof(double), .align=_Alignof(double)}; #ifdef HAVE_LONG_DOUBLE static const zend_ffi_type zend_ffi_type_long_double = {.kind=ZEND_FFI_TYPE_LONGDOUBLE, .size=sizeof(long double), .align=_Alignof(long double)}; #endif static const zend_ffi_type zend_ffi_type_ptr = {.kind=ZEND_FFI_TYPE_POINTER, .size=sizeof(void*), .align=_Alignof(void*), .pointer.type = (zend_ffi_type*)&zend_ffi_type_void}; const struct { const char *name; const zend_ffi_type *type; } zend_ffi_types[] = { {"void", &zend_ffi_type_void}, {"char", &zend_ffi_type_char}, {"bool", &zend_ffi_type_bool}, {"int8_t", &zend_ffi_type_sint8}, {"uint8_t", &zend_ffi_type_uint8}, {"int16_t", &zend_ffi_type_sint16}, {"uint16_t", &zend_ffi_type_uint16}, {"int32_t", &zend_ffi_type_sint32}, {"uint32_t", &zend_ffi_type_uint32}, {"int64_t", &zend_ffi_type_sint64}, {"uint64_t", &zend_ffi_type_uint64}, {"float", &zend_ffi_type_float}, {"double", &zend_ffi_type_double}, #ifdef HAVE_LONG_DOUBLE {"long double", &zend_ffi_type_long_double}, #endif #if SIZEOF_SIZE_T == 4 {"uintptr_t", &zend_ffi_type_uint32}, {"intptr_t", &zend_ffi_type_sint32}, {"size_t", &zend_ffi_type_uint32}, {"ssize_t", &zend_ffi_type_sint32}, {"ptrdiff_t", &zend_ffi_type_sint32}, #else {"uintptr_t", &zend_ffi_type_uint64}, {"intptr_t", &zend_ffi_type_sint64}, {"size_t", &zend_ffi_type_uint64}, {"ssize_t", &zend_ffi_type_sint64}, {"ptrdiff_t", &zend_ffi_type_sint64}, #endif #if SIZEOF_OFF_T == 4 {"off_t", &zend_ffi_type_sint32}, #else {"off_t", &zend_ffi_type_sint64}, #endif {"va_list", &zend_ffi_type_ptr}, {"__builtin_va_list", &zend_ffi_type_ptr}, {"__gnuc_va_list", &zend_ffi_type_ptr}, }; /* {{{ ZEND_GINIT_FUNCTION */ static ZEND_GINIT_FUNCTION(ffi) { size_t i; #if defined(COMPILE_DL_FFI) && defined(ZTS) ZEND_TSRMLS_CACHE_UPDATE(); #endif memset(ffi_globals, 0, sizeof(*ffi_globals)); zend_hash_init(&ffi_globals->types, 0, NULL, NULL, 1); for (i = 0; i < sizeof(zend_ffi_types)/sizeof(zend_ffi_types[0]); i++) { zend_hash_str_add_new_ptr(&ffi_globals->types, zend_ffi_types[i].name, strlen(zend_ffi_types[i].name), (void*)zend_ffi_types[i].type); } } /* }}} */ /* {{{ ZEND_GINIT_FUNCTION */ static ZEND_GSHUTDOWN_FUNCTION(ffi) { if (ffi_globals->scopes) { zend_hash_destroy(ffi_globals->scopes); free(ffi_globals->scopes); } zend_hash_destroy(&ffi_globals->types); } /* }}} */ /* {{{ ffi_module_entry */ zend_module_entry ffi_module_entry = { STANDARD_MODULE_HEADER, "FFI", /* Extension name */ NULL, /* zend_function_entry */ ZEND_MINIT(ffi), /* ZEND_MINIT - Module initialization */ NULL, /* ZEND_MSHUTDOWN - Module shutdown */ NULL, /* ZEND_RINIT - Request initialization */ ZEND_RSHUTDOWN(ffi), /* ZEND_RSHUTDOWN - Request shutdown */ ZEND_MINFO(ffi), /* ZEND_MINFO - Module info */ PHP_VERSION, /* Version */ ZEND_MODULE_GLOBALS(ffi), ZEND_GINIT(ffi), ZEND_GSHUTDOWN(ffi), NULL, STANDARD_MODULE_PROPERTIES_EX }; /* }}} */ #ifdef COMPILE_DL_FFI # ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE() # endif ZEND_GET_MODULE(ffi) #endif /* parser callbacks */ void zend_ffi_parser_error(const char *format, ...) /* {{{ */ { va_list va; char *message = NULL; va_start(va, format); zend_vspprintf(&message, 0, format, va); if (EG(current_execute_data)) { zend_throw_exception(zend_ffi_parser_exception_ce, message, 0); } else { zend_error(E_WARNING, "FFI Parser: %s", message); } efree(message); va_end(va); LONGJMP(FFI_G(bailout), FAILURE); } /* }}} */ static void zend_ffi_finalize_type(zend_ffi_dcl *dcl) /* {{{ */ { if (!dcl->type) { switch (dcl->flags & ZEND_FFI_DCL_TYPE_SPECIFIERS) { case ZEND_FFI_DCL_VOID: dcl->type = (zend_ffi_type*)&zend_ffi_type_void; break; case ZEND_FFI_DCL_CHAR: dcl->type = (zend_ffi_type*)&zend_ffi_type_char; break; case ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SIGNED: dcl->type = (zend_ffi_type*)&zend_ffi_type_sint8; break; case ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_UNSIGNED: case ZEND_FFI_DCL_BOOL: dcl->type = (zend_ffi_type*)&zend_ffi_type_uint8; break; case ZEND_FFI_DCL_SHORT: case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_SIGNED: case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT: case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT: dcl->type = (zend_ffi_type*)&zend_ffi_type_sint16; break; case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_UNSIGNED: case ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT: dcl->type = (zend_ffi_type*)&zend_ffi_type_uint16; break; case ZEND_FFI_DCL_INT: case ZEND_FFI_DCL_SIGNED: case ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT: dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32; break; case ZEND_FFI_DCL_UNSIGNED: case ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT: dcl->type = (zend_ffi_type*)&zend_ffi_type_uint32; break; case ZEND_FFI_DCL_LONG: case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED: case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_INT: case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT: if (sizeof(long) == 4) { dcl->type = (zend_ffi_type*)&zend_ffi_type_sint32; } else { dcl->type = (zend_ffi_type*)&zend_ffi_type_sint64; } break; case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED: case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT: if (sizeof(long) == 4) { dcl->type = (zend_ffi_type*)&zend_ffi_type_uint32; } else { dcl->type = (zend_ffi_type*)&zend_ffi_type_uint64; } break; case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG: case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED: case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_INT: case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_INT: dcl->type = (zend_ffi_type*)&zend_ffi_type_sint64; break; case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED: case ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_UNSIGNED|ZEND_FFI_DCL_INT: dcl->type = (zend_ffi_type*)&zend_ffi_type_uint64; break; case ZEND_FFI_DCL_FLOAT: dcl->type = (zend_ffi_type*)&zend_ffi_type_float; break; case ZEND_FFI_DCL_DOUBLE: dcl->type = (zend_ffi_type*)&zend_ffi_type_double; break; case ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_DOUBLE: #ifdef _WIN32 dcl->type = (zend_ffi_type*)&zend_ffi_type_double; #else dcl->type = (zend_ffi_type*)&zend_ffi_type_long_double; #endif break; case ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_COMPLEX: case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_COMPLEX: case ZEND_FFI_DCL_DOUBLE|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_COMPLEX: zend_ffi_parser_error("unsupported type '_Complex' at line %d", FFI_G(line)); break; default: zend_ffi_parser_error("unsupported type specifier combination at line %d", FFI_G(line)); break; } dcl->flags &= ~ZEND_FFI_DCL_TYPE_SPECIFIERS; dcl->flags |= ZEND_FFI_DCL_TYPEDEF_NAME; } } /* }}} */ int zend_ffi_is_typedef_name(const char *name, size_t name_len) /* {{{ */ { zend_ffi_symbol *sym; zend_ffi_type *type; if (FFI_G(symbols)) { sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len); if (sym) { return (sym->kind == ZEND_FFI_SYM_TYPE); } } type = zend_hash_str_find_ptr(&FFI_G(types), name, name_len); if (type) { return 1; } return 0; } /* }}} */ void zend_ffi_resolve_typedef(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_symbol *sym; zend_ffi_type *type; if (FFI_G(symbols)) { sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len); if (sym && sym->kind == ZEND_FFI_SYM_TYPE) { dcl->type = ZEND_FFI_TYPE(sym->type);; if (sym->is_const) { dcl->attr |= ZEND_FFI_ATTR_CONST; } return; } } type = zend_hash_str_find_ptr(&FFI_G(types), name, name_len); if (type) { dcl->type = type; return; } zend_ffi_parser_error("undefined C type '%.*s' at line %d", name_len, name, FFI_G(line)); } /* }}} */ void zend_ffi_resolve_const(const char *name, size_t name_len, zend_ffi_val *val) /* {{{ */ { zend_ffi_symbol *sym; if (UNEXPECTED(FFI_G(attribute_parsing))) { val->kind = ZEND_FFI_VAL_NAME; val->str = name; val->len = name_len; return; } else if (FFI_G(symbols)) { sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len); if (sym && sym->kind == ZEND_FFI_SYM_CONST) { val->i64 = sym->value; switch (sym->type->kind) { case ZEND_FFI_TYPE_SINT8: case ZEND_FFI_TYPE_SINT16: case ZEND_FFI_TYPE_SINT32: val->kind = ZEND_FFI_VAL_INT32; break; case ZEND_FFI_TYPE_SINT64: val->kind = ZEND_FFI_VAL_INT64; break; case ZEND_FFI_TYPE_UINT8: case ZEND_FFI_TYPE_UINT16: case ZEND_FFI_TYPE_UINT32: val->kind = ZEND_FFI_VAL_UINT32; break; case ZEND_FFI_TYPE_UINT64: val->kind = ZEND_FFI_VAL_UINT64; break; default: ZEND_ASSERT(0); } return; } } val->kind = ZEND_FFI_VAL_ERROR; } /* }}} */ void zend_ffi_make_enum_type(zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); type->kind = ZEND_FFI_TYPE_ENUM; type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_ENUM_ATTRS); type->enumeration.tag_name = NULL; if (type->attr & ZEND_FFI_ATTR_PACKED) { type->size = zend_ffi_type_uint8.size; type->align = zend_ffi_type_uint8.align; type->enumeration.kind = ZEND_FFI_TYPE_UINT8; } else { type->size = zend_ffi_type_uint32.size; type->align = zend_ffi_type_uint32.align; type->enumeration.kind = ZEND_FFI_TYPE_UINT32; } dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); dcl->attr &= ~ZEND_FFI_ENUM_ATTRS; } /* }}} */ void zend_ffi_add_enum_val(zend_ffi_dcl *enum_dcl, const char *name, size_t name_len, zend_ffi_val *val, int64_t *min, int64_t *max, int64_t *last) /* {{{ */ { zend_ffi_symbol *sym; const zend_ffi_type *sym_type; int64_t value; zend_ffi_type *enum_type = ZEND_FFI_TYPE(enum_dcl->type); zend_bool overflow = 0; zend_bool is_signed = (enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT8 || enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT16 || enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT32 || enum_type->enumeration.kind == ZEND_FFI_TYPE_SINT64); ZEND_ASSERT(enum_type && enum_type->kind == ZEND_FFI_TYPE_ENUM); if (val->kind == ZEND_FFI_VAL_EMPTY) { if (is_signed) { if (*last == 0x7FFFFFFFFFFFFFFFLL) { overflow = 1; } } else { if ((*min != 0 || *max != 0) && (uint64_t)*last == 0xFFFFFFFFFFFFFFFFULL) { overflow = 1; } } value = *last + 1; } else if (val->kind == ZEND_FFI_VAL_CHAR) { if (!is_signed && val->ch < 0) { if ((uint64_t)*max > 0x7FFFFFFFFFFFFFFFULL) { overflow = 1; } else { is_signed = 1; } } value = val->ch; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { if (!is_signed && val->i64 < 0) { if ((uint64_t)*max > 0x7FFFFFFFFFFFFFFFULL) { overflow = 1; } else { is_signed = 1; } } value = val->i64; } else if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { if (is_signed && val->u64 > 0x7FFFFFFFFFFFFFFFULL) { overflow = 1; } value = val->u64; } else { zend_ffi_parser_error("enumerator value '%.*s' must be an integer at line %d", name_len, name, FFI_G(line)); return; } if (overflow) { zend_ffi_parser_error("overflow in enumeration values '%.*s' at line %d", name_len, name, FFI_G(line)); return; } if (is_signed) { *min = MIN(*min, value); *max = MAX(*max, value); if ((enum_type->attr & ZEND_FFI_ATTR_PACKED) && *min >= -0x7FLL-1 && *max <= 0x7FLL) { sym_type = &zend_ffi_type_sint8; } else if ((enum_type->attr & ZEND_FFI_ATTR_PACKED) && *min >= -0x7FFFLL-1 && *max <= 0x7FFFLL) { sym_type = &zend_ffi_type_sint16; } else if (*min >= -0x7FFFFFFFLL-1 && *max <= 0x7FFFFFFFLL) { sym_type = &zend_ffi_type_sint32; } else { sym_type = &zend_ffi_type_sint64; } } else { *min = MIN((uint64_t)*min, (uint64_t)value); *max = MAX((uint64_t)*max, (uint64_t)value); if ((enum_type->attr & ZEND_FFI_ATTR_PACKED) && (uint64_t)*max <= 0xFFULL) { sym_type = &zend_ffi_type_uint8; } else if ((enum_type->attr & ZEND_FFI_ATTR_PACKED) && (uint64_t)*max <= 0xFFFFULL) { sym_type = &zend_ffi_type_uint16; } else if ((uint64_t)*max <= 0xFFFFFFFFULL) { sym_type = &zend_ffi_type_uint32; } else { sym_type = &zend_ffi_type_uint64; } } enum_type->enumeration.kind = sym_type->kind; enum_type->size = sym_type->size; enum_type->align = sym_type->align; *last = value; if (!FFI_G(symbols)) { FFI_G(symbols) = pemalloc(sizeof(HashTable), FFI_G(persistent)); zend_hash_init(FFI_G(symbols), 0, NULL, FFI_G(persistent) ? zend_ffi_symbol_hash_persistent_dtor : zend_ffi_symbol_hash_dtor, FFI_G(persistent)); } sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len); if (sym) { zend_ffi_parser_error("redeclaration of '%.*s' at line %d", name_len, name, FFI_G(line)); } else { sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent)); sym->kind = ZEND_FFI_SYM_CONST; sym->type = (zend_ffi_type*)sym_type; sym->value = value; zend_hash_str_add_new_ptr(FFI_G(symbols), name, name_len, sym); } } /* }}} */ void zend_ffi_make_struct_type(zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); type->kind = ZEND_FFI_TYPE_STRUCT; type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_STRUCT_ATTRS); type->size = 0; type->align = dcl->align > 1 ? dcl->align : 1; if (dcl->flags & ZEND_FFI_DCL_UNION) { type->attr |= ZEND_FFI_ATTR_UNION; } dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); type->record.tag_name = NULL; zend_hash_init(&type->record.fields, 0, NULL, FFI_G(persistent) ? zend_ffi_field_hash_persistent_dtor :zend_ffi_field_hash_dtor, FFI_G(persistent)); dcl->attr &= ~ZEND_FFI_STRUCT_ATTRS; dcl->align = 0; } /* }}} */ static int zend_ffi_validate_prev_field_type(zend_ffi_type *struct_type) /* {{{ */ { if (zend_hash_num_elements(&struct_type->record.fields) > 0) { zend_ffi_field *field = NULL; ZEND_HASH_REVERSE_FOREACH_PTR(&struct_type->record.fields, field) { break; } ZEND_HASH_FOREACH_END(); if (ZEND_FFI_TYPE(field->type)->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY) { zend_ffi_throw_parser_error("flexible array member not at end of struct at line %d", FFI_G(line)); return FAILURE; } } return SUCCESS; } /* }}} */ static int zend_ffi_validate_field_type(zend_ffi_type *type, zend_ffi_type *struct_type) /* {{{ */ { if (type == struct_type) { zend_ffi_throw_parser_error("struct/union can't contain an instance of itself at line %d", FFI_G(line)); return FAILURE; } else if (zend_ffi_validate_var_type(type, 1) != SUCCESS) { return FAILURE; } else if (struct_type->attr & ZEND_FFI_ATTR_UNION) { if (type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY) { zend_ffi_throw_parser_error("flexible array member in union at line %d", FFI_G(line)); return FAILURE; } } return zend_ffi_validate_prev_field_type(struct_type); } /* }}} */ void zend_ffi_add_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl) /* {{{ */ { zend_ffi_field *field; zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type); zend_ffi_type *field_type; ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT); zend_ffi_finalize_type(field_dcl); field_type = ZEND_FFI_TYPE(field_dcl->type); if (zend_ffi_validate_field_type(field_type, struct_type) != SUCCESS) { zend_ffi_cleanup_dcl(field_dcl); LONGJMP(FFI_G(bailout), FAILURE); } field = pemalloc(sizeof(zend_ffi_field), FFI_G(persistent)); if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) { struct_type->align = MAX(struct_type->align, MAX(field_type->align, field_dcl->align)); } if (struct_type->attr & ZEND_FFI_ATTR_UNION) { field->offset = 0; struct_type->size = MAX(struct_type->size, field_type->size); } else { if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) { uint32_t field_align = MAX(field_type->align, field_dcl->align); struct_type->size = ((struct_type->size + (field_align - 1)) / field_align) * field_align; } field->offset = struct_type->size; struct_type->size += field_type->size; } field->type = field_dcl->type; field->is_const = (zend_bool)(field_dcl->attr & ZEND_FFI_ATTR_CONST); field->is_nested = 0; field->first_bit = 0; field->bits = 0; field_dcl->type = field_type; /* reset "owned" flag */ if (!zend_hash_str_add_ptr(&struct_type->record.fields, name, name_len, field)) { zend_ffi_type_dtor(field->type); pefree(field, FFI_G(persistent)); zend_ffi_parser_error("duplicate field name '%.*s' at line %d", name_len, name, FFI_G(line)); } } /* }}} */ void zend_ffi_add_anonymous_field(zend_ffi_dcl *struct_dcl, zend_ffi_dcl *field_dcl) /* {{{ */ { zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type); zend_ffi_type *field_type; zend_ffi_field *field; zend_string *key; ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT); zend_ffi_finalize_type(field_dcl); field_type = ZEND_FFI_TYPE(field_dcl->type); if (field_type->kind != ZEND_FFI_TYPE_STRUCT) { zend_ffi_cleanup_dcl(field_dcl); zend_ffi_parser_error("declaration does not declare anything at line %d", FFI_G(line)); return; } if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) { struct_type->align = MAX(struct_type->align, MAX(field_type->align, field_dcl->align)); } if (!(struct_type->attr & ZEND_FFI_ATTR_UNION)) { if (zend_ffi_validate_prev_field_type(struct_type) != SUCCESS) { zend_ffi_cleanup_dcl(field_dcl); LONGJMP(FFI_G(bailout), FAILURE); } if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED) && !(field_dcl->attr & ZEND_FFI_ATTR_PACKED)) { uint32_t field_align = MAX(field_type->align, field_dcl->align); struct_type->size = ((struct_type->size + (field_align - 1)) / field_align) * field_align; } } ZEND_HASH_FOREACH_STR_KEY_PTR(&field_type->record.fields, key, field) { zend_ffi_field *new_field = pemalloc(sizeof(zend_ffi_field), FFI_G(persistent)); if (struct_type->attr & ZEND_FFI_ATTR_UNION) { new_field->offset = field->offset; } else { new_field->offset = struct_type->size + field->offset; } new_field->type = field->type; new_field->is_const = field->is_const; new_field->is_nested = 1; new_field->first_bit = field->first_bit; new_field->bits = field->bits; field->type = ZEND_FFI_TYPE(field->type); /* reset "owned" flag */ if (key) { if (!zend_hash_add_ptr(&struct_type->record.fields, key, new_field)) { zend_ffi_type_dtor(new_field->type); pefree(new_field, FFI_G(persistent)); zend_ffi_parser_error("duplicate field name '%s' at line %d", ZSTR_VAL(key), FFI_G(line)); return; } } else { zend_hash_next_index_insert_ptr(&struct_type->record.fields, field); } } ZEND_HASH_FOREACH_END(); if (struct_type->attr & ZEND_FFI_ATTR_UNION) { struct_type->size = MAX(struct_type->size, field_type->size); } else { struct_type->size += field_type->size; } zend_ffi_type_dtor(field_dcl->type); field_dcl->type = NULL; } /* }}} */ void zend_ffi_add_bit_field(zend_ffi_dcl *struct_dcl, const char *name, size_t name_len, zend_ffi_dcl *field_dcl, zend_ffi_val *bits) /* {{{ */ { zend_ffi_type *struct_type = ZEND_FFI_TYPE(struct_dcl->type); zend_ffi_type *field_type; zend_ffi_field *field; ZEND_ASSERT(struct_type && struct_type->kind == ZEND_FFI_TYPE_STRUCT); zend_ffi_finalize_type(field_dcl); field_type = ZEND_FFI_TYPE(field_dcl->type); if (zend_ffi_validate_field_type(field_type, struct_type) != SUCCESS) { zend_ffi_cleanup_dcl(field_dcl); LONGJMP(FFI_G(bailout), FAILURE); } if (field_type->kind < ZEND_FFI_TYPE_UINT8 || field_type->kind > ZEND_FFI_TYPE_BOOL) { zend_ffi_cleanup_dcl(field_dcl); zend_ffi_parser_error("wrong type of bit field '%.*s' at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } if (bits->kind == ZEND_FFI_VAL_INT32 || bits->kind == ZEND_FFI_VAL_INT64) { if (bits->i64 < 0) { zend_ffi_cleanup_dcl(field_dcl); zend_ffi_parser_error("negative width in bit-field '%.*s' at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } else if (bits->i64 == 0) { zend_ffi_cleanup_dcl(field_dcl); if (name) { zend_ffi_parser_error("zero width in bit-field '%.*s' at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } return; } else if (bits->i64 > field_type->size * 8) { zend_ffi_cleanup_dcl(field_dcl); zend_ffi_parser_error("width of '%.*s' exceeds its type at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } } else if (bits->kind == ZEND_FFI_VAL_UINT32 || bits->kind == ZEND_FFI_VAL_UINT64) { if (bits->u64 == 0) { zend_ffi_cleanup_dcl(field_dcl); if (name) { zend_ffi_parser_error("zero width in bit-field '%.*s' at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } return; } else if (bits->u64 > field_type->size * 8) { zend_ffi_cleanup_dcl(field_dcl); zend_ffi_parser_error("width of '%.*s' exceeds its type at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } } else { zend_ffi_cleanup_dcl(field_dcl); zend_ffi_parser_error("bit field '%.*s' width not an integer constant at line %d", name ? name_len : sizeof("")-1, name ? name : "", FFI_G(line)); } field = pemalloc(sizeof(zend_ffi_field), FFI_G(persistent)); if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED)) { struct_type->align = MAX(struct_type->align, sizeof(uint32_t)); } if (struct_type->attr & ZEND_FFI_ATTR_UNION) { field->offset = 0; field->first_bit = 0; field->bits = bits->u64; if (struct_type->attr & ZEND_FFI_ATTR_PACKED) { struct_type->size = MAX(struct_type->size, (bits->u64 + 7) / 8); } else { struct_type->size = MAX(struct_type->size, ((bits->u64 + 31) / 32) * 4); } } else { zend_ffi_field *prev_field = NULL; if (zend_hash_num_elements(&struct_type->record.fields) > 0) { ZEND_HASH_REVERSE_FOREACH_PTR(&struct_type->record.fields, prev_field) { break; } ZEND_HASH_FOREACH_END(); } if (prev_field && prev_field->bits) { field->offset = prev_field->offset; field->first_bit = prev_field->first_bit + prev_field->bits; field->bits = bits->u64; } else { field->offset = struct_type->size; field->first_bit = 0; field->bits = bits->u64; } if (struct_type->attr & ZEND_FFI_ATTR_PACKED) { struct_type->size = field->offset + ((field->first_bit + field->bits) + 7) / 8; } else { struct_type->size = field->offset + (((field->first_bit + field->bits) + 31) / 32) * 4; } } field->type = field_dcl->type; field->is_const = (zend_bool)(field_dcl->attr & ZEND_FFI_ATTR_CONST); field->is_nested = 0; field_dcl->type = field_type; /* reset "owned" flag */ if (name) { if (!zend_hash_str_add_ptr(&struct_type->record.fields, name, name_len, field)) { zend_ffi_type_dtor(field->type); pefree(field, FFI_G(persistent)); zend_ffi_parser_error("duplicate field name '%.*s' at line %d", name_len, name, FFI_G(line)); } } else { zend_hash_next_index_insert_ptr(&struct_type->record.fields, field); } } /* }}} */ void zend_ffi_adjust_struct_size(zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_type *struct_type = ZEND_FFI_TYPE(dcl->type); ZEND_ASSERT(struct_type->kind == ZEND_FFI_TYPE_STRUCT); if (dcl->align > struct_type->align) { struct_type->align = dcl->align; } if (!(struct_type->attr & ZEND_FFI_ATTR_PACKED)) { struct_type->size = ((struct_type->size + (struct_type->align - 1)) / struct_type->align) * struct_type->align; } dcl->align = 0; } /* }}} */ void zend_ffi_make_pointer_type(zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); type->kind = ZEND_FFI_TYPE_POINTER; type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_POINTER_ATTRS); type->size = sizeof(void*); type->align = _Alignof(void*); zend_ffi_finalize_type(dcl); if (zend_ffi_validate_vla(ZEND_FFI_TYPE(dcl->type)) != SUCCESS) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } type->pointer.type = dcl->type; dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); dcl->flags &= ~ZEND_FFI_DCL_TYPE_QUALIFIERS; dcl->attr &= ~ZEND_FFI_POINTER_ATTRS; dcl->align = 0; } /* }}} */ static int zend_ffi_validate_array_element_type(zend_ffi_type *type) /* {{{ */ { if (type->kind == ZEND_FFI_TYPE_FUNC) { zend_ffi_throw_parser_error("array of functions is not allowed at line %d", FFI_G(line)); return FAILURE; } else if (type->kind == ZEND_FFI_TYPE_ARRAY && (type->attr & ZEND_FFI_ATTR_INCOMPLETE_ARRAY)) { zend_ffi_throw_parser_error("only the leftmost array can be undimensioned at line %d", FFI_G(line)); return FAILURE; } return zend_ffi_validate_type(type, 0, 1); } /* }}} */ void zend_ffi_make_array_type(zend_ffi_dcl *dcl, zend_ffi_val *len) /* {{{ */ { int length = 0; zend_ffi_type *element_type; zend_ffi_type *type; zend_ffi_finalize_type(dcl); element_type = ZEND_FFI_TYPE(dcl->type); if (len->kind == ZEND_FFI_VAL_EMPTY) { length = 0; } else if (len->kind == ZEND_FFI_VAL_UINT32 || len->kind == ZEND_FFI_VAL_UINT64) { length = len->u64; } else if (len->kind == ZEND_FFI_VAL_INT32 || len->kind == ZEND_FFI_VAL_INT64) { length = len->i64; } else if (len->kind == ZEND_FFI_VAL_CHAR) { length = len->ch; } else { zend_ffi_cleanup_dcl(dcl); zend_ffi_parser_error("unsupported array index type at line %d", FFI_G(line)); return; } if (length < 0) { zend_ffi_cleanup_dcl(dcl); zend_ffi_parser_error("negative array index at line %d", FFI_G(line)); return; } if (zend_ffi_validate_array_element_type(element_type) != SUCCESS) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); type->kind = ZEND_FFI_TYPE_ARRAY; type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_ARRAY_ATTRS); type->size = length * element_type->size; type->align = element_type->align; type->array.type = dcl->type; type->array.length = length; dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); dcl->flags &= ~ZEND_FFI_DCL_TYPE_QUALIFIERS; dcl->attr &= ~ZEND_FFI_ARRAY_ATTRS; dcl->align = 0; } /* }}} */ static int zend_ffi_validate_func_ret_type(zend_ffi_type *type) /* {{{ */ { if (type->kind == ZEND_FFI_TYPE_FUNC) { zend_ffi_throw_parser_error("function returning function is not allowed at line %d", FFI_G(line)); return FAILURE; } else if (type->kind == ZEND_FFI_TYPE_ARRAY) { zend_ffi_throw_parser_error("function returning array is not allowed at line %d", FFI_G(line)); return FAILURE; } return zend_ffi_validate_incomplete_type(type, 1, 0); } /* }}} */ void zend_ffi_make_func_type(zend_ffi_dcl *dcl, HashTable *args, zend_ffi_dcl *nested_dcl) /* {{{ */ { zend_ffi_type *type; zend_ffi_type *ret_type; zend_ffi_finalize_type(dcl); ret_type = ZEND_FFI_TYPE(dcl->type); if (args) { int no_args = 0; zend_ffi_type *arg_type; ZEND_HASH_FOREACH_PTR(args, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); if (arg_type->kind == ZEND_FFI_TYPE_VOID) { if (zend_hash_num_elements(args) != 1) { zend_ffi_cleanup_dcl(nested_dcl); zend_ffi_cleanup_dcl(dcl); zend_hash_destroy(args); pefree(args, FFI_G(persistent)); zend_ffi_parser_error("'void' type is not allowed at line %d", FFI_G(line)); return; } else { no_args = 1; } } } ZEND_HASH_FOREACH_END(); if (no_args) { zend_hash_destroy(args); pefree(args, FFI_G(persistent)); args = NULL; } } #ifdef HAVE_FFI_VECTORCALL_PARTIAL if (dcl->abi == ZEND_FFI_ABI_VECTORCALL && args) { zend_ulong i; zend_ffi_type *arg_type; ZEND_HASH_FOREACH_NUM_KEY_PTR(args, i, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); # ifdef _WIN64 if (i >= 4 && i <= 5 && (arg_type->kind == ZEND_FFI_TYPE_FLOAT || arg_type->kind == ZEND_FFI_TYPE_DOUBLE)) { # else if (i < 6 && (arg_type->kind == ZEND_FFI_TYPE_FLOAT || arg_type->kind == ZEND_FFI_TYPE_DOUBLE)) { # endif zend_ffi_cleanup_dcl(nested_dcl); zend_ffi_cleanup_dcl(dcl); zend_hash_destroy(args); pefree(args, FFI_G(persistent)); zend_ffi_parser_error("'float'/'double' type not allowed at position " ZEND_ULONG_FMT " with __vectorcall at line %d", i+1, FFI_G(line)); return; } } ZEND_HASH_FOREACH_END(); } #endif if (zend_ffi_validate_func_ret_type(ret_type) != SUCCESS) { zend_ffi_cleanup_dcl(nested_dcl); zend_ffi_cleanup_dcl(dcl); if (args) { zend_hash_destroy(args); pefree(args, FFI_G(persistent)); } LONGJMP(FFI_G(bailout), FAILURE); } type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); type->kind = ZEND_FFI_TYPE_FUNC; type->attr = FFI_G(default_type_attr) | (dcl->attr & ZEND_FFI_FUNC_ATTRS); type->size = sizeof(void*); type->align = 1; type->func.ret_type = dcl->type; switch (dcl->abi) { case ZEND_FFI_ABI_DEFAULT: case ZEND_FFI_ABI_CDECL: type->func.abi = FFI_DEFAULT_ABI; break; #ifdef HAVE_FFI_FASTCALL case ZEND_FFI_ABI_FASTCALL: type->func.abi = FFI_FASTCALL; break; #endif #ifdef HAVE_FFI_THISCALL case ZEND_FFI_ABI_THISCALL: type->func.abi = FFI_THISCALL; break; #endif #ifdef HAVE_FFI_STDCALL case ZEND_FFI_ABI_STDCALL: type->func.abi = FFI_STDCALL; break; #endif #ifdef HAVE_FFI_PASCAL case ZEND_FFI_ABI_PASCAL: type->func.abi = FFI_PASCAL; break; #endif #ifdef HAVE_FFI_REGISTER case ZEND_FFI_ABI_REGISTER: type->func.abi = FFI_REGISTER; break; #endif #ifdef HAVE_FFI_MS_CDECL case ZEND_FFI_ABI_MS: type->func.abi = FFI_MS_CDECL; break; #endif #ifdef HAVE_FFI_SYSV case ZEND_FFI_ABI_SYSV: type->func.abi = FFI_SYSV; break; #endif #ifdef HAVE_FFI_VECTORCALL_PARTIAL case ZEND_FFI_ABI_VECTORCALL: type->func.abi = FFI_VECTORCALL_PARTIAL; break; #endif default: type->func.abi = FFI_DEFAULT_ABI; zend_ffi_cleanup_dcl(nested_dcl); if (args) { zend_hash_destroy(args); pefree(args, FFI_G(persistent)); } type->func.args = NULL; _zend_ffi_type_dtor(type); zend_ffi_parser_error("unsupported calling convention line %d", FFI_G(line)); break; } type->func.args = args; dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); dcl->attr &= ~ZEND_FFI_FUNC_ATTRS; dcl->align = 0; dcl->abi = 0; } /* }}} */ void zend_ffi_add_arg(HashTable **args, const char *name, size_t name_len, zend_ffi_dcl *arg_dcl) /* {{{ */ { zend_ffi_type *type; if (!*args) { *args = pemalloc(sizeof(HashTable), FFI_G(persistent)); zend_hash_init(*args, 0, NULL, zend_ffi_type_hash_dtor, FFI_G(persistent)); } zend_ffi_finalize_type(arg_dcl); type = ZEND_FFI_TYPE(arg_dcl->type); if (type->kind == ZEND_FFI_TYPE_ARRAY) { if (ZEND_FFI_TYPE_IS_OWNED(arg_dcl->type)) { type->kind = ZEND_FFI_TYPE_POINTER; type->size = sizeof(void*); } else { zend_ffi_type *new_type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); new_type->kind = ZEND_FFI_TYPE_POINTER; new_type->attr = FFI_G(default_type_attr) | (type->attr & ZEND_FFI_POINTER_ATTRS); new_type->size = sizeof(void*); new_type->align = _Alignof(void*); new_type->pointer.type = ZEND_FFI_TYPE(type->array.type); arg_dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); } } else if (type->kind == ZEND_FFI_TYPE_FUNC) { zend_ffi_type *new_type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); new_type->kind = ZEND_FFI_TYPE_POINTER; new_type->attr = FFI_G(default_type_attr); new_type->size = sizeof(void*); new_type->align = _Alignof(void*); new_type->pointer.type = arg_dcl->type; arg_dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(new_type); } if (zend_ffi_validate_incomplete_type(type, 1, 1) != SUCCESS) { zend_ffi_cleanup_dcl(arg_dcl); zend_hash_destroy(*args); pefree(*args, FFI_G(persistent)); *args = NULL; LONGJMP(FFI_G(bailout), FAILURE); } zend_hash_next_index_insert_ptr(*args, (void*)arg_dcl->type); } /* }}} */ void zend_ffi_declare(const char *name, size_t name_len, zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_symbol *sym; if (!FFI_G(symbols)) { FFI_G(symbols) = pemalloc(sizeof(HashTable), FFI_G(persistent)); zend_hash_init(FFI_G(symbols), 0, NULL, FFI_G(persistent) ? zend_ffi_symbol_hash_persistent_dtor : zend_ffi_symbol_hash_dtor, FFI_G(persistent)); } zend_ffi_finalize_type(dcl); sym = zend_hash_str_find_ptr(FFI_G(symbols), name, name_len); if (sym) { if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_TYPEDEF && sym->kind == ZEND_FFI_SYM_TYPE && zend_ffi_is_same_type(ZEND_FFI_TYPE(sym->type), ZEND_FFI_TYPE(dcl->type)) && sym->is_const == (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST)) { /* allowed redeclaration */ zend_ffi_type_dtor(dcl->type); return; } else if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == 0 || (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN) { zend_ffi_type *type = ZEND_FFI_TYPE(dcl->type); if (type->kind == ZEND_FFI_TYPE_FUNC) { if (sym->kind == ZEND_FFI_SYM_FUNC && zend_ffi_same_types(ZEND_FFI_TYPE(sym->type), type)) { /* allowed redeclaration */ zend_ffi_type_dtor(dcl->type); return; } } else { if (sym->kind == ZEND_FFI_SYM_VAR && zend_ffi_is_same_type(ZEND_FFI_TYPE(sym->type), type) && sym->is_const == (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST)) { /* allowed redeclaration */ zend_ffi_type_dtor(dcl->type); return; } } } zend_ffi_parser_error("redeclaration of '%.*s' at line %d", name_len, name, FFI_G(line)); } else { if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_TYPEDEF) { if (zend_ffi_validate_vla(ZEND_FFI_TYPE(dcl->type)) != SUCCESS) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } if (dcl->align && dcl->align > ZEND_FFI_TYPE(dcl->type)->align) { if (ZEND_FFI_TYPE_IS_OWNED(dcl->type)) { ZEND_FFI_TYPE(dcl->type)->align = dcl->align; } else { zend_ffi_type *type = pemalloc(sizeof(zend_ffi_type), FFI_G(persistent)); memcpy(type, ZEND_FFI_TYPE(dcl->type), sizeof(zend_ffi_type)); type->attr |= FFI_G(default_type_attr); type->align = dcl->align; dcl->type = ZEND_FFI_TYPE_MAKE_OWNED(type); } } sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent)); sym->kind = ZEND_FFI_SYM_TYPE; sym->type = dcl->type; sym->is_const = (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST); dcl->type = ZEND_FFI_TYPE(dcl->type); /* reset "owned" flag */ zend_hash_str_add_new_ptr(FFI_G(symbols), name, name_len, sym); } else { zend_ffi_type *type; type = ZEND_FFI_TYPE(dcl->type); if (zend_ffi_validate_type(type, (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN, 1) != SUCCESS) { zend_ffi_cleanup_dcl(dcl); LONGJMP(FFI_G(bailout), FAILURE); } if ((dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == 0 || (dcl->flags & ZEND_FFI_DCL_STORAGE_CLASS) == ZEND_FFI_DCL_EXTERN) { sym = pemalloc(sizeof(zend_ffi_symbol), FFI_G(persistent)); sym->kind = (type->kind == ZEND_FFI_TYPE_FUNC) ? ZEND_FFI_SYM_FUNC : ZEND_FFI_SYM_VAR; sym->type = dcl->type; sym->is_const = (zend_bool)(dcl->attr & ZEND_FFI_ATTR_CONST); dcl->type = type; /* reset "owned" flag */ zend_hash_str_add_new_ptr(FFI_G(symbols), name, name_len, sym); } else { /* useless declarartion */ zend_ffi_type_dtor(dcl->type); } } } } /* }}} */ void zend_ffi_declare_tag(const char *name, size_t name_len, zend_ffi_dcl *dcl, zend_bool incomplete) /* {{{ */ { zend_ffi_tag *tag; zend_ffi_type *type; if (!FFI_G(tags)) { FFI_G(tags) = pemalloc(sizeof(HashTable), FFI_G(persistent)); zend_hash_init(FFI_G(tags), 0, NULL, FFI_G(persistent) ? zend_ffi_tag_hash_persistent_dtor : zend_ffi_tag_hash_dtor, FFI_G(persistent)); } tag = zend_hash_str_find_ptr(FFI_G(tags), name, name_len); if (tag) { zend_ffi_type *type = ZEND_FFI_TYPE(tag->type); if (dcl->flags & ZEND_FFI_DCL_STRUCT) { if (tag->kind != ZEND_FFI_TAG_STRUCT) { zend_ffi_parser_error("'%.*s' defined as wrong kind of tag at line %d", name_len, name, FFI_G(line)); return; } else if (!incomplete && !(type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) { zend_ffi_parser_error("redefinition of 'struct %.*s' at line %d", name_len, name, FFI_G(line)); return; } } else if (dcl->flags & ZEND_FFI_DCL_UNION) { if (tag->kind != ZEND_FFI_TAG_UNION) { zend_ffi_parser_error("'%.*s' defined as wrong kind of tag at line %d", name_len, name, FFI_G(line)); return; } else if (!incomplete && !(type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) { zend_ffi_parser_error("redefinition of 'union %.*s' at line %d", name_len, name, FFI_G(line)); return; } } else if (dcl->flags & ZEND_FFI_DCL_ENUM) { if (tag->kind != ZEND_FFI_TAG_ENUM) { zend_ffi_parser_error("'%.*s' defined as wrong kind of tag at line %d", name_len, name, FFI_G(line)); return; } else if (!incomplete && !(type->attr & ZEND_FFI_ATTR_INCOMPLETE_TAG)) { zend_ffi_parser_error("redefinition of 'enum %.*s' at line %d", name_len, name, FFI_G(line)); return; } } else { ZEND_ASSERT(0); return; } dcl->type = type; if (!incomplete) { type->attr &= ~ZEND_FFI_ATTR_INCOMPLETE_TAG; } } else { zend_ffi_tag *tag = pemalloc(sizeof(zend_ffi_tag), FFI_G(persistent)); zend_string *tag_name = zend_string_init(name, name_len, FFI_G(persistent)); if (dcl->flags & ZEND_FFI_DCL_STRUCT) { tag->kind = ZEND_FFI_TAG_STRUCT; zend_ffi_make_struct_type(dcl); type = ZEND_FFI_TYPE(dcl->type); type->record.tag_name = zend_string_copy(tag_name); } else if (dcl->flags & ZEND_FFI_DCL_UNION) { tag->kind = ZEND_FFI_TAG_UNION; zend_ffi_make_struct_type(dcl); type = ZEND_FFI_TYPE(dcl->type); type->record.tag_name = zend_string_copy(tag_name); } else if (dcl->flags & ZEND_FFI_DCL_ENUM) { tag->kind = ZEND_FFI_TAG_ENUM; zend_ffi_make_enum_type(dcl); type = ZEND_FFI_TYPE(dcl->type); type->enumeration.tag_name = zend_string_copy(tag_name); } else { ZEND_ASSERT(0); } tag->type = ZEND_FFI_TYPE_MAKE_OWNED(dcl->type); dcl->type = ZEND_FFI_TYPE(dcl->type); if (incomplete) { dcl->type->attr |= ZEND_FFI_ATTR_INCOMPLETE_TAG; } zend_hash_add_new_ptr(FFI_G(tags), tag_name, tag); zend_string_release(tag_name); } } /* }}} */ void zend_ffi_set_abi(zend_ffi_dcl *dcl, uint16_t abi) /* {{{ */ { if (dcl->abi != ZEND_FFI_ABI_DEFAULT) { zend_ffi_parser_error("multiple calling convention specifiers at line %d", FFI_G(line)); } else { dcl->abi = abi; } } /* }}} */ #define SIMPLE_ATTRIBUTES(_) \ _(cdecl) \ _(fastcall) \ _(thiscall) \ _(stdcall) \ _(ms_abi) \ _(sysv_abi) \ _(vectorcall) \ _(aligned) \ _(packed) \ _(ms_struct) \ _(gcc_struct) \ _(const) \ _(malloc) \ _(deprecated) \ _(nothrow) \ _(leaf) \ _(pure) \ _(noreturn) \ _(warn_unused_result) #define ATTR_ID(name) attr_ ## name, #define ATTR_NAME(name) {sizeof(#name)-1, #name}, void zend_ffi_add_attribute(zend_ffi_dcl *dcl, const char *name, size_t name_len) /* {{{ */ { enum { SIMPLE_ATTRIBUTES(ATTR_ID) attr_unsupported }; static const struct { size_t len; const char * const name; } names[] = { SIMPLE_ATTRIBUTES(ATTR_NAME) {0, NULL} }; int id; if (name_len > 4 && name[0] == '_' && name[1] == '_' && name[name_len-2] == '_' && name[name_len-1] == '_') { name += 2; name_len -= 4; } for (id = 0; names[id].len != 0; id++) { if (name_len == names[id].len) { if (memcmp(name, names[id].name, name_len) == 0) { break; } } } switch (id) { case attr_cdecl: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_CDECL); break; case attr_fastcall: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_FASTCALL); break; case attr_thiscall: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_THISCALL); break; case attr_stdcall: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_STDCALL); break; case attr_ms_abi: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_MS); break; case attr_sysv_abi: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_SYSV); break; case attr_vectorcall: zend_ffi_set_abi(dcl, ZEND_FFI_ABI_VECTORCALL); break; case attr_aligned: dcl->align = __BIGGEST_ALIGNMENT__; break; case attr_packed: dcl->attr |= ZEND_FFI_ATTR_PACKED; break; case attr_ms_struct: dcl->attr |= ZEND_FFI_ATTR_MS_STRUCT; break; case attr_gcc_struct: dcl->attr |= ZEND_FFI_ATTR_GCC_STRUCT; break; case attr_unsupported: zend_ffi_parser_error("unsupported attribute '%.*s' at line %d", name_len, name, FFI_G(line)); break; default: /* ignore */ break; } } /* }}} */ #define VALUE_ATTRIBUTES(_) \ _(regparam) \ _(aligned) \ _(mode) \ _(nonnull) \ _(alloc_size) \ _(format) \ _(deprecated) void zend_ffi_add_attribute_value(zend_ffi_dcl *dcl, const char *name, size_t name_len, int n, zend_ffi_val *val) /* {{{ */ { enum { VALUE_ATTRIBUTES(ATTR_ID) attr_unsupported }; static const struct { size_t len; const char * const name; } names[] = { VALUE_ATTRIBUTES(ATTR_NAME) {0, NULL} }; int id; if (name_len > 4 && name[0] == '_' && name[1] == '_' && name[name_len-2] == '_' && name[name_len-1] == '_') { name += 2; name_len -= 4; } for (id = 0; names[id].len != 0; id++) { if (name_len == names[id].len) { if (memcmp(name, names[id].name, name_len) == 0) { break; } } } switch (id) { case attr_regparam: if (n == 0 && (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT64 || val->kind == ZEND_FFI_VAL_UINT64) && val->i64 == 3) { zend_ffi_set_abi(dcl, ZEND_FFI_ABI_REGISTER); } else { zend_ffi_parser_error("incorrect 'regparam' value at line %d", FFI_G(line)); } break; case attr_aligned: if (n == 0 && (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT64 || val->kind == ZEND_FFI_VAL_UINT64) && val->i64 > 0 && val->i64 <= 0x80000000 && (val->i64 & (val->i64 - 1)) == 0) { dcl->align = val->i64; } else { zend_ffi_parser_error("incorrect 'alignment' value at line %d", FFI_G(line)); } break; case attr_mode: if (n == 0 && (val->kind == ZEND_FFI_VAL_NAME)) { const char *str = val->str; size_t len = val->len; if (len > 4 && str[0] == '_' && str[1] == '_' && str[len-2] == '_' && str[len-1] == '_') { str += 2; len -= 4; } // TODO: Add support for vector type 'VnXX' ??? if (len == 2) { if (str[1] == 'I') { if (dcl->flags & (ZEND_FFI_DCL_TYPE_SPECIFIERS-(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG|ZEND_FFI_DCL_SIGNED|ZEND_FFI_DCL_UNSIGNED))) { /* inappropriate type */ } else if (str[0] == 'Q') { dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG); dcl->flags |= ZEND_FFI_DCL_CHAR; break; } else if (str[0] == 'H') { dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG); dcl->flags |= ZEND_FFI_DCL_SHORT; break; } else if (str[0] == 'S') { dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG); dcl->flags |= ZEND_FFI_DCL_INT; break; } else if (str[0] == 'D') { dcl->flags &= ~(ZEND_FFI_DCL_CHAR|ZEND_FFI_DCL_SHORT|ZEND_FFI_DCL_INT|ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG); if (sizeof(long) == 8) { dcl->flags |= ZEND_FFI_DCL_LONG; } else { dcl->flags |= ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_LONG_LONG; } break; } } else if (str[1] == 'F') { if (dcl->flags & (ZEND_FFI_DCL_TYPE_SPECIFIERS-(ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_DOUBLE))) { /* inappropriate type */ } else if (str[0] == 'S') { dcl->flags &= ~(ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_DOUBLE); dcl->flags |= ZEND_FFI_DCL_FLOAT; break; } else if (str[0] == 'D') { dcl->flags &= ~(ZEND_FFI_DCL_LONG|ZEND_FFI_DCL_FLOAT|ZEND_FFI_DCL_DOUBLE); dcl->flags |= ZEND_FFI_DCL_DOUBLE; break; } } } } zend_ffi_parser_error("unsupported 'mode' value at line %d", FFI_G(line)); // TODO: ??? case attr_unsupported: zend_ffi_parser_error("unsupported attribute '%.*s' at line %d", name_len, name, FFI_G(line)); break; default: /* ignore */ break; } } /* }}} */ void zend_ffi_add_msvc_attribute_value(zend_ffi_dcl *dcl, const char *name, size_t name_len, zend_ffi_val *val) /* {{{ */ { if (name_len == sizeof("align")-1 && memcmp(name, "align", sizeof("align")-1) == 0) { if ((val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT64 || val->kind == ZEND_FFI_VAL_UINT64) && val->i64 > 0 && val->i64 <= 0x80000000 && (val->i64 & (val->i64 - 1)) == 0) { dcl->align = val->i64; } else { zend_ffi_parser_error("incorrect 'alignment' value at line %d", FFI_G(line)); } } else { /* ignore */ } } /* }}} */ static int zend_ffi_nested_type(zend_ffi_type *type, zend_ffi_type *nested_type) /* {{{ */ { nested_type = ZEND_FFI_TYPE(nested_type); switch (nested_type->kind) { case ZEND_FFI_TYPE_POINTER: /* "char" is used as a terminator of nested declaration */ if (nested_type->pointer.type == &zend_ffi_type_char) { nested_type->pointer.type = type; return zend_ffi_validate_vla(ZEND_FFI_TYPE(type)); } else { return zend_ffi_nested_type(type, nested_type->pointer.type); } break; case ZEND_FFI_TYPE_ARRAY: /* "char" is used as a terminator of nested declaration */ if (nested_type->array.type == &zend_ffi_type_char) { nested_type->array.type = type; if (zend_ffi_validate_array_element_type(ZEND_FFI_TYPE(type)) != SUCCESS) { return FAILURE; } } else { if (zend_ffi_nested_type(type, nested_type->array.type) != SUCCESS) { return FAILURE; } } nested_type->size = nested_type->array.length * ZEND_FFI_TYPE(nested_type->array.type)->size; nested_type->align = ZEND_FFI_TYPE(nested_type->array.type)->align; return SUCCESS; break; case ZEND_FFI_TYPE_FUNC: /* "char" is used as a terminator of nested declaration */ if (nested_type->func.ret_type == &zend_ffi_type_char) { nested_type->func.ret_type = type; return zend_ffi_validate_func_ret_type(ZEND_FFI_TYPE(type)); } else { return zend_ffi_nested_type(type, nested_type->func.ret_type); } break; default: ZEND_ASSERT(0); } } /* }}} */ void zend_ffi_nested_declaration(zend_ffi_dcl *dcl, zend_ffi_dcl *nested_dcl) /* {{{ */ { /* "char" is used as a terminator of nested declaration */ zend_ffi_finalize_type(dcl); if (!nested_dcl->type || nested_dcl->type == &zend_ffi_type_char) { nested_dcl->type = dcl->type; } else { if (zend_ffi_nested_type(dcl->type, nested_dcl->type) != SUCCESS) { zend_ffi_cleanup_dcl(nested_dcl); LONGJMP(FFI_G(bailout), FAILURE); } } dcl->type = nested_dcl->type; } /* }}} */ void zend_ffi_align_as_type(zend_ffi_dcl *dcl, zend_ffi_dcl *align_dcl) /* {{{ */ { zend_ffi_finalize_type(align_dcl); dcl->align = MAX(align_dcl->align, ZEND_FFI_TYPE(align_dcl->type)->align); } /* }}} */ void zend_ffi_align_as_val(zend_ffi_dcl *dcl, zend_ffi_val *align_val) /* {{{ */ { switch (align_val->kind) { case ZEND_FFI_VAL_INT32: case ZEND_FFI_VAL_UINT32: dcl->align = zend_ffi_type_uint32.align; break; case ZEND_FFI_VAL_INT64: case ZEND_FFI_VAL_UINT64: dcl->align = zend_ffi_type_uint64.align; break; case ZEND_FFI_VAL_FLOAT: dcl->align = zend_ffi_type_float.align; break; case ZEND_FFI_VAL_DOUBLE: dcl->align = zend_ffi_type_double.align; break; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_VAL_LONG_DOUBLE: dcl->align = zend_ffi_type_long_double.align; break; #endif case ZEND_FFI_VAL_CHAR: case ZEND_FFI_VAL_STRING: dcl->align = zend_ffi_type_char.align; break; default: break; } } /* }}} */ #define zend_ffi_expr_bool(val) do { \ if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = !!val->u64; \ } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = !!val->i64; \ } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = !!val->d; \ } else if (val->kind == ZEND_FFI_VAL_CHAR) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = !!val->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } while (0) #define zend_ffi_expr_math(val, op2, OP) do { \ if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = MAX(val->kind, op2->kind); \ val->u64 = val->u64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32) { \ val->u64 = val->u64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_INT64) { \ val->u64 = val->u64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = op2->kind; \ val->d = (zend_ffi_double)val->u64 OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->u64 = val->u64 OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \ if (op2->kind == ZEND_FFI_VAL_UINT32) { \ val->i64 = val->i64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_UINT64) { \ val->i64 = val->i64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = MAX(val->kind, op2->kind); \ val->i64 = val->i64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = op2->kind; \ val->d = (zend_ffi_double)val->i64 OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->i64 = val->i64 OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->d = val->d OP (zend_ffi_double)op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 ||op2->kind == ZEND_FFI_VAL_INT64) { \ val->d = val->d OP (zend_ffi_double)op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = MAX(val->kind, op2->kind); \ val->d = val->d OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->d = val->d OP (zend_ffi_double)op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_CHAR) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = op2->kind; \ val->u64 = val->ch OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = ZEND_FFI_VAL_INT64; \ val->i64 = val->ch OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = op2->kind; \ val->d = (zend_ffi_double)val->ch OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->ch = val->ch OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } while (0) #define zend_ffi_expr_int_math(val, op2, OP) do { \ if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = MAX(val->kind, op2->kind); \ val->u64 = val->u64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32) { \ val->u64 = val->u64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_INT64) { \ val->u64 = val->u64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->u64 = val->u64 OP (uint64_t)op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->u64 = val->u64 OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \ if (op2->kind == ZEND_FFI_VAL_UINT32) { \ val->i64 = val->i64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_UINT64) { \ val->i64 = val->i64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = MAX(val->kind, op2->kind); \ val->i64 = val->i64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->u64 = val->u64 OP (int64_t)op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->i64 = val->i64 OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = op2->kind; \ val->u64 = (uint64_t)val->d OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = op2->kind; \ val->i64 = (int64_t)val->d OP op2->i64; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_CHAR) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = op2->kind; \ val->u64 = (uint64_t)val->ch OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = op2->kind; \ val->i64 = (int64_t)val->ch OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->ch = val->ch OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } while (0) #define zend_ffi_expr_cmp(val, op2, OP) do { \ if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->u64 OP op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->u64 OP op2->u64; /*signed/unsigned */ \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = (zend_ffi_double)val->u64 OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->u64 OP op2->d; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->i64 OP op2->i64; /* signed/unsigned */ \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->i64 OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = (zend_ffi_double)val->i64 OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->i64 OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->d OP (zend_ffi_double)op2->u64; \ } else if (op2->kind == ZEND_FFI_VAL_INT32 ||op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->d OP (zend_ffi_double)op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->d OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->d OP (zend_ffi_double)op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else if (val->kind == ZEND_FFI_VAL_CHAR) { \ if (op2->kind == ZEND_FFI_VAL_UINT32 || op2->kind == ZEND_FFI_VAL_UINT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->ch OP op2->i64; /* signed/unsigned */ \ } else if (op2->kind == ZEND_FFI_VAL_INT32 || op2->kind == ZEND_FFI_VAL_INT64) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->ch OP op2->i64; \ } else if (op2->kind == ZEND_FFI_VAL_FLOAT || op2->kind == ZEND_FFI_VAL_DOUBLE || op2->kind == ZEND_FFI_VAL_LONG_DOUBLE) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = (zend_ffi_double)val->ch OP op2->d; \ } else if (op2->kind == ZEND_FFI_VAL_CHAR) { \ val->kind = ZEND_FFI_VAL_INT32; \ val->i64 = val->ch OP op2->ch; \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } else { \ val->kind = ZEND_FFI_VAL_ERROR; \ } \ } while (0) void zend_ffi_expr_conditional(zend_ffi_val *val, zend_ffi_val *op2, zend_ffi_val *op3) /* {{{ */ { zend_ffi_expr_bool(val); if (val->kind == ZEND_FFI_VAL_INT32) { if (val->i64) { *val = *op2; } else { *val = *op3; } } } /* }}} */ void zend_ffi_expr_bool_or(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_bool(val); zend_ffi_expr_bool(op2); if (val->kind == ZEND_FFI_VAL_INT32 && op2->kind == ZEND_FFI_VAL_INT32) { val->i64 = val->i64 || op2->i64; } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_bool_and(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_bool(val); zend_ffi_expr_bool(op2); if (val->kind == ZEND_FFI_VAL_INT32 && op2->kind == ZEND_FFI_VAL_INT32) { val->i64 = val->i64 && op2->i64; } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_bw_or(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_int_math(val, op2, |); } /* }}} */ void zend_ffi_expr_bw_xor(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_int_math(val, op2, ^); } /* }}} */ void zend_ffi_expr_bw_and(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_int_math(val, op2, &); } /* }}} */ void zend_ffi_expr_is_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_cmp(val, op2, ==); } /* }}} */ void zend_ffi_expr_is_not_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_cmp(val, op2, !=); } /* }}} */ void zend_ffi_expr_is_less(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_cmp(val, op2, <); } /* }}} */ void zend_ffi_expr_is_greater(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_cmp(val, op2, >); } /* }}} */ void zend_ffi_expr_is_less_or_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_cmp(val, op2, <=); } /* }}} */ void zend_ffi_expr_is_greater_or_equal(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_cmp(val, op2, >=); } /* }}} */ void zend_ffi_expr_shift_left(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_int_math(val, op2, <<); } /* }}} */ void zend_ffi_expr_shift_right(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_int_math(val, op2, >>); } /* }}} */ void zend_ffi_expr_add(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_math(val, op2, +); } /* }}} */ void zend_ffi_expr_sub(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_math(val, op2, -); } /* }}} */ void zend_ffi_expr_mul(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_math(val, op2, *); } /* }}} */ void zend_ffi_expr_div(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_math(val, op2, /); } /* }}} */ void zend_ffi_expr_mod(zend_ffi_val *val, zend_ffi_val *op2) /* {{{ */ { zend_ffi_expr_int_math(val, op2, %); // ??? } /* }}} */ void zend_ffi_expr_cast(zend_ffi_val *val, zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_finalize_type(dcl); switch (ZEND_FFI_TYPE(dcl->type)->kind) { case ZEND_FFI_TYPE_FLOAT: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { val->kind = ZEND_FFI_VAL_FLOAT; val->d = val->u64; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_FLOAT; val->d = val->i64; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_FLOAT; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_FLOAT; val->d = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; case ZEND_FFI_TYPE_DOUBLE: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { val->kind = ZEND_FFI_VAL_DOUBLE; val->d = val->u64; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_DOUBLE; val->d = val->i64; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_DOUBLE; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_DOUBLE; val->d = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; #ifdef HAVE_LONG_DOUBLE case ZEND_FFI_TYPE_LONGDOUBLE: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { val->kind = ZEND_FFI_VAL_LONG_DOUBLE; val->d = val->u64; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_LONG_DOUBLE; val->d = val->i64; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_LONG_DOUBLE; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_LONG_DOUBLE; val->d = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; #endif case ZEND_FFI_TYPE_UINT8: case ZEND_FFI_TYPE_UINT16: case ZEND_FFI_TYPE_UINT32: case ZEND_FFI_TYPE_BOOL: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_UINT32; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = val->d; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; case ZEND_FFI_TYPE_SINT8: case ZEND_FFI_TYPE_SINT16: case ZEND_FFI_TYPE_SINT32: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_INT32; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_INT32; val->i64 = val->d; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_INT32; val->i64 = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; case ZEND_FFI_TYPE_UINT64: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_UINT64; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT64; val->u64 = val->d; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_UINT64; val->u64 = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; case ZEND_FFI_TYPE_SINT64: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { val->kind = ZEND_FFI_VAL_CHAR; val->ch = val->u64; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_CHAR; val->ch = val->i64; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_CHAR; val->ch = val->d; } else if (val->kind == ZEND_FFI_VAL_CHAR) { } else { val->kind = ZEND_FFI_VAL_ERROR; } break; case ZEND_FFI_TYPE_CHAR: if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_UINT32; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = val->d; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } break; default: val->kind = ZEND_FFI_VAL_ERROR; break; } zend_ffi_type_dtor(dcl->type); } /* }}} */ void zend_ffi_expr_plus(zend_ffi_val *val) /* {{{ */ { if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { } else if (val->kind == ZEND_FFI_VAL_CHAR) { } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_neg(zend_ffi_val *val) /* {{{ */ { if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { val->u64 = -val->u64; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->i64 = -val->i64; } else if (val->kind == ZEND_FFI_VAL_FLOAT || val->kind == ZEND_FFI_VAL_DOUBLE || val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->d = -val->d; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->ch = -val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_bw_not(zend_ffi_val *val) /* {{{ */ { if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_UINT64) { val->u64 = ~val->u64; } else if (val->kind == ZEND_FFI_VAL_INT32 || val->kind == ZEND_FFI_VAL_INT64) { val->i64 = ~val->i64; } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->ch = ~val->ch; } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_bool_not(zend_ffi_val *val) /* {{{ */ { zend_ffi_expr_bool(val); if (val->kind == ZEND_FFI_VAL_INT32) { val->i64 = !val->i64; } } /* }}} */ void zend_ffi_expr_sizeof_val(zend_ffi_val *val) /* {{{ */ { if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT32) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_uint32.size; } else if (val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_uint64.size; } else if (val->kind == ZEND_FFI_VAL_FLOAT) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_float.size; } else if (val->kind == ZEND_FFI_VAL_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_double.size; } else if (val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT32; #ifdef _WIN32 val->u64 = zend_ffi_type_double.size; #else val->u64 = zend_ffi_type_long_double.size; #endif } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_char.size; } else if (val->kind == ZEND_FFI_VAL_STRING) { if (memchr(val->str, '\\', val->len)) { // TODO: support for escape sequences ??? val->kind = ZEND_FFI_VAL_ERROR; } else { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = val->len + 1; } } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_sizeof_type(zend_ffi_val *val, zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_type *type; zend_ffi_finalize_type(dcl); type = ZEND_FFI_TYPE(dcl->type); val->kind = (type->size > 0xffffffff) ? ZEND_FFI_VAL_UINT64 : ZEND_FFI_VAL_UINT32; val->u64 = type->size; zend_ffi_type_dtor(dcl->type); } /* }}} */ void zend_ffi_expr_alignof_val(zend_ffi_val *val) /* {{{ */ { if (val->kind == ZEND_FFI_VAL_UINT32 || val->kind == ZEND_FFI_VAL_INT32) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_uint32.align; } else if (val->kind == ZEND_FFI_VAL_UINT64 || val->kind == ZEND_FFI_VAL_INT64) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_uint64.align; } else if (val->kind == ZEND_FFI_VAL_FLOAT) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_float.align; } else if (val->kind == ZEND_FFI_VAL_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_double.align; #ifdef HAVE_LONG_DOUBLE } else if (val->kind == ZEND_FFI_VAL_LONG_DOUBLE) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_long_double.align; #endif } else if (val->kind == ZEND_FFI_VAL_CHAR) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = zend_ffi_type_char.size; } else if (val->kind == ZEND_FFI_VAL_STRING) { val->kind = ZEND_FFI_VAL_UINT32; val->u64 = _Alignof(char*); } else { val->kind = ZEND_FFI_VAL_ERROR; } } /* }}} */ void zend_ffi_expr_alignof_type(zend_ffi_val *val, zend_ffi_dcl *dcl) /* {{{ */ { zend_ffi_finalize_type(dcl); val->kind = ZEND_FFI_VAL_UINT32; val->u64 = ZEND_FFI_TYPE(dcl->type)->align; zend_ffi_type_dtor(dcl->type); } /* }}} */ void zend_ffi_val_number(zend_ffi_val *val, int base, const char *str, size_t str_len) /* {{{ */ { int u = 0; int l = 0; if (str[str_len-1] == 'u' || str[str_len-1] == 'U') { u = 1; if (str[str_len-2] == 'l' || str[str_len-2] == 'L') { l = 1; if (str[str_len-3] == 'l' || str[str_len-3] == 'L') { l = 2; } } } else if (str[str_len-1] == 'l' || str[str_len-1] == 'L') { l = 1; if (str[str_len-2] == 'l' || str[str_len-2] == 'L') { l = 2; if (str[str_len-3] == 'u' || str[str_len-3] == 'U') { u = 1; } } else if (str[str_len-2] == 'u' || str[str_len-2] == 'U') { u = 1; } } if (u) { val->u64 = strtoull(str, NULL, base); if (l == 0) { val->kind = ZEND_FFI_VAL_UINT32; } else if (l == 1) { val->kind = (sizeof(long) == 4) ? ZEND_FFI_VAL_UINT32 : ZEND_FFI_VAL_UINT64; } else if (l == 2) { val->kind = ZEND_FFI_VAL_UINT64; } } else { val->i64 = strtoll(str, NULL, base); if (l == 0) { val->kind = ZEND_FFI_VAL_INT32; } else if (l == 1) { val->kind = (sizeof(long) == 4) ? ZEND_FFI_VAL_INT32 : ZEND_FFI_VAL_INT64; } else if (l == 2) { val->kind = ZEND_FFI_VAL_INT64; } } } /* }}} */ void zend_ffi_val_float_number(zend_ffi_val *val, const char *str, size_t str_len) /* {{{ */ { val->d = strtold(str, NULL); if (str[str_len-1] == 'f' || str[str_len-1] == 'F') { val->kind = ZEND_FFI_VAL_FLOAT; } else if (str[str_len-1] == 'l' || str[str_len-1] == 'L') { val->kind = ZEND_FFI_VAL_LONG_DOUBLE; } else { val->kind = ZEND_FFI_VAL_DOUBLE; } } /* }}} */ void zend_ffi_val_string(zend_ffi_val *val, const char *str, size_t str_len) /* {{{ */ { if (str[0] != '\"') { val->kind = ZEND_FFI_VAL_ERROR; } else { val->kind = ZEND_FFI_VAL_STRING; val->str = str + 1; val->len = str_len - 2; } } /* }}} */ void zend_ffi_val_character(zend_ffi_val *val, const char *str, size_t str_len) /* {{{ */ { int n; if (str[0] != '\'') { val->kind = ZEND_FFI_VAL_ERROR; } else { val->kind = ZEND_FFI_VAL_CHAR; if (str_len == 3) { val->ch = str[1]; } else if (str[1] == '\\') { if (str[2] == 'a') { } else if (str[2] == 'b' && str_len == 4) { val->ch = '\b'; } else if (str[2] == 'f' && str_len == 4) { val->ch = '\f'; } else if (str[2] == 'n' && str_len == 4) { val->ch = '\n'; } else if (str[2] == 'r' && str_len == 4) { val->ch = '\r'; } else if (str[2] == 't' && str_len == 4) { val->ch = '\t'; } else if (str[2] == 'v' && str_len == 4) { val->ch = '\v'; } else if (str[2] >= '0' && str[2] <= '7') { n = str[2] - '0'; if (str[3] >= '0' && str[3] <= '7') { n = n * 8 + (str[3] - '0'); if ((str[4] >= '0' && str[4] <= '7') && str_len == 6) { n = n * 8 + (str[4] - '0'); } else if (str_len != 5) { val->kind = ZEND_FFI_VAL_ERROR; } } else if (str_len != 4) { val->kind = ZEND_FFI_VAL_ERROR; } if (n <= 0xff) { val->ch = n; } else { val->kind = ZEND_FFI_VAL_ERROR; } } else if (str[2] == 'x') { if (str[3] >= '0' && str[3] <= '9') { n = str[3] - '0'; } else if (str[3] >= 'A' && str[3] <= 'F') { n = str[3] - 'A'; } else if (str[3] >= 'a' && str[3] <= 'f') { n = str[3] - 'a'; } else { val->kind = ZEND_FFI_VAL_ERROR; return; } if ((str[4] >= '0' && str[4] <= '9') && str_len == 6) { n = n * 16 + (str[4] - '0'); } else if ((str[4] >= 'A' && str[4] <= 'F') && str_len == 6) { n = n * 16 + (str[4] - 'A'); } else if ((str[4] >= 'a' && str[4] <= 'f') && str_len == 6) { n = n * 16 + (str[4] - 'a'); } else if (str_len != 5) { val->kind = ZEND_FFI_VAL_ERROR; return; } val->ch = n; } else if (str_len == 4) { val->ch = str[2]; } else { val->kind = ZEND_FFI_VAL_ERROR; } } else { val->kind = ZEND_FFI_VAL_ERROR; } } } /* }}} */