Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

3819 lines (3196 sloc) 95.162 kB
/*
* Copyright (c) 2008, Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <Foundation/Foundation.h>
#include "ruby/ruby.h"
#include "ruby/node.h"
#include "ruby/encoding.h"
#include "ruby/objc.h"
#include <unistd.h>
#include <dlfcn.h>
#include <mach-o/dyld.h>
#include <sys/mman.h>
#if HAVE_BRIDGESUPPORT_FRAMEWORK
# include <BridgeSupport/BridgeSupport.h>
#else
# include "bs.h"
#endif
#include "vm_core.h"
#include "vm.h"
#include "eval_intern.h"
void native_mutex_lock(pthread_mutex_t *lock);
void native_mutex_unlock(pthread_mutex_t *lock);
rb_thread_t *rb_thread_wrap_existing_native_thread(rb_thread_id_t id);
extern VALUE rb_cMutex;
typedef struct {
bs_element_type_t type;
void *value;
VALUE klass;
ffi_type *ffi_type;
} bs_element_boxed_t;
typedef struct {
char *name;
struct st_table *cmethods;
struct st_table *imethods;
} bs_element_indexed_class_t;
static VALUE rb_cBoxed;
static ID rb_ivar_type;
static VALUE bs_const_magic_cookie = Qnil;
static struct st_table *bs_constants;
struct st_table *bs_functions;
static struct st_table *bs_boxeds;
static struct st_table *bs_classes;
static struct st_table *bs_inf_prot_cmethods;
static struct st_table *bs_inf_prot_imethods;
static struct st_table *bs_cftypes;
VALUE rb_cPointer;
struct RPointer
{
void *ptr;
const char *type;
};
static inline const char *
rb_objc_skip_octype_modifiers(const char *octype)
{
while (true) {
switch (*octype) {
case _C_CONST:
case 'O': /* bycopy */
case 'n': /* in */
case 'o': /* out */
case 'N': /* inout */
case 'V': /* oneway */
octype++;
break;
default:
return octype;
}
}
}
static inline const char *
__iterate_until(const char *type, char end)
{
char begin;
unsigned nested;
begin = *type;
nested = 0;
do {
type++;
if (*type == begin) {
nested++;
}
else if (*type == end) {
if (nested == 0) {
return type;
}
nested--;
}
}
while (YES);
return NULL;
}
static const char *
rb_objc_get_first_type(const char *type, char *buf, size_t buf_len)
{
const char *orig_type;
const char *p;
orig_type = type;
type = rb_objc_skip_octype_modifiers(type);
switch (*type) {
case '\0':
return NULL;
case _C_ARY_B:
type = __iterate_until(type, _C_ARY_E);
break;
case _C_STRUCT_B:
type = __iterate_until(type, _C_STRUCT_E);
break;
case _C_UNION_B:
type = __iterate_until(type, _C_UNION_E);
break;
case _C_PTR:
type++;
buf[0] = _C_PTR;
buf_len -= 1;
return rb_objc_get_first_type(type, &buf[1], buf_len);
}
type++;
p = type;
while (*p >= '0' && *p <= '9') { p++; }
if (buf != NULL) {
size_t len = (long)(type - orig_type);
assert(len < buf_len);
strncpy(buf, orig_type, len);
buf[len] = '\0';
}
return p;
}
static ffi_type *
fake_ary_ffi_type(size_t size, size_t align)
{
static struct st_table *ary_ffi_types = NULL;
ffi_type *type;
unsigned i;
assert(size > 0);
if (ary_ffi_types == NULL) {
ary_ffi_types = st_init_numtable();
GC_ROOT(&ary_ffi_types);
}
if (st_lookup(ary_ffi_types, (st_data_t)size, (st_data_t *)&type)) {
return type;
}
type = (ffi_type *)malloc(sizeof(ffi_type));
type->size = size;
type->alignment = align;
type->type = FFI_TYPE_STRUCT;
type->elements = malloc(size * sizeof(ffi_type *));
for (i = 0; i < size; i++) {
type->elements[i] = &ffi_type_uchar;
}
st_insert(ary_ffi_types, (st_data_t)size, (st_data_t)type);
return type;
}
static size_t
get_ffi_struct_size(ffi_type *type)
{
ffi_type **p;
size_t s;
if (type->size > 0)
return type->size;
assert(type->type == FFI_TYPE_STRUCT);
for (s = 0, p = &type->elements[0]; *p != NULL; p++)
s += get_ffi_struct_size(*p);
return s;
}
static ffi_type *
rb_objc_octype_to_ffitype(const char *octype)
{
octype = rb_objc_skip_octype_modifiers(octype);
if (bs_cftypes != NULL && st_lookup(bs_cftypes, (st_data_t)octype, NULL))
octype = "@";
switch (*octype) {
case _C_ID:
case _C_CLASS:
case _C_SEL:
case _C_CHARPTR:
case _C_PTR:
return &ffi_type_pointer;
case _C_BOOL:
case _C_UCHR:
return &ffi_type_uchar;
case _C_CHR:
return &ffi_type_schar;
case _C_SHT:
return &ffi_type_sshort;
case _C_USHT:
return &ffi_type_ushort;
case _C_INT:
return &ffi_type_sint;
case _C_UINT:
return &ffi_type_uint;
case _C_LNG:
return sizeof(int) == sizeof(long)
? &ffi_type_sint : &ffi_type_slong;
#if defined(_C_LNG_LNG)
case _C_LNG_LNG:
return &ffi_type_sint64;
#endif
case _C_ULNG:
return sizeof(unsigned int) == sizeof(unsigned long)
? &ffi_type_uint : &ffi_type_ulong;
#if defined(_C_ULNG_LNG)
case _C_ULNG_LNG:
return &ffi_type_uint64;
#endif
case _C_FLT:
return &ffi_type_float;
case _C_DBL:
return &ffi_type_double;
case _C_ARY_B:
{
NSUInteger size, align;
@try {
NSGetSizeAndAlignment(octype, &size, &align);
}
@catch (id exception) {
rb_raise(rb_eRuntimeError, "can't get size of type `%s': %s",
octype, [[exception description] UTF8String]);
}
if (size > 0)
return fake_ary_ffi_type(size, align);
break;
}
case _C_BFLD:
{
char *type;
long lng;
size_t size;
type = (char *)octype;
lng = strtol(type, &type, 10);
/* while next type is a bit field */
while (*type == _C_BFLD) {
long next_lng;
/* skip over _C_BFLD */
type++;
/* get next bit field length */
next_lng = strtol(type, &type, 10);
/* if spans next word then align to next word */
if ((lng & ~31) != ((lng + next_lng) & ~31))
lng = (lng + 31) & ~31;
/* increment running length */
lng += next_lng;
}
size = (lng + 7) / 8;
if (size > 0) {
if (size == 1)
return &ffi_type_uchar;
else if (size == 2)
return &ffi_type_ushort;
else if (size <= 4)
return &ffi_type_uint;
return fake_ary_ffi_type(size, 0);
}
break;
}
case _C_STRUCT_B:
{
bs_element_boxed_t *bs_boxed;
if (st_lookup(bs_boxeds, (st_data_t)octype,
(st_data_t *)&bs_boxed)) {
bs_element_struct_t *bs_struct =
(bs_element_struct_t *)bs_boxed->value;
unsigned i;
assert(bs_boxed->type == BS_ELEMENT_STRUCT);
if (bs_boxed->ffi_type != NULL)
return bs_boxed->ffi_type;
bs_boxed->ffi_type = (ffi_type *)malloc(sizeof(ffi_type));
bs_boxed->ffi_type->size = 0;
bs_boxed->ffi_type->alignment = 0;
bs_boxed->ffi_type->type = FFI_TYPE_STRUCT;
bs_boxed->ffi_type->elements = malloc(
(bs_struct->fields_count + 1) * sizeof(ffi_type *));
for (i = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *field = &bs_struct->fields[i];
bs_boxed->ffi_type->elements[i] =
rb_objc_octype_to_ffitype(field->type);
}
bs_boxed->ffi_type->elements[bs_struct->fields_count] = NULL;
{
/* Prepare a fake cif, to make sure critical things such
* as the ffi_type size is set.
*/
ffi_cif cif;
ffi_prep_cif(&cif, FFI_DEFAULT_ABI, 0, bs_boxed->ffi_type,
NULL);
assert(bs_boxed->ffi_type->size > 0);
}
return bs_boxed->ffi_type;
}
break;
}
case _C_VOID:
return &ffi_type_void;
}
rb_raise(rb_eRuntimeError, "unrecognized octype `%s'", octype);
return NULL;
}
static size_t
rb_objc_octype_size(const char *octype)
{
ffi_type *t = rb_objc_octype_to_ffitype(octype);
if (t == NULL)
rb_bug("can't determine size of type `%s'", octype);
return t->size;
}
static bool
rb_objc_rval_to_ocsel(VALUE rval, void **ocval)
{
const char *cstr;
if (NIL_P(rval)) {
*(SEL *)ocval = NULL;
return true;
}
switch (TYPE(rval)) {
case T_STRING:
cstr = StringValuePtr(rval);
break;
case T_SYMBOL:
cstr = rb_sym2name(rval);
break;
default:
return false;
}
*(SEL *)ocval = sel_registerName(cstr);
return true;
}
static void
rb_bs_boxed_assert_ffitype_ok(bs_element_boxed_t *bs_boxed)
{
if (bs_boxed->ffi_type == NULL && bs_boxed->type == BS_ELEMENT_STRUCT) {
/* Make sure the ffi_type is set before use. */
rb_objc_octype_to_ffitype(
((bs_element_struct_t *)bs_boxed->value)->type);
}
assert(bs_boxed->ffi_type != NULL);
}
static void rb_objc_ocval_to_rval(void **ocval, const char *octype, VALUE *rbval);
static VALUE
rb_bs_boxed_new_from_ocdata(bs_element_boxed_t *bs_boxed, void *ocval)
{
if (ocval == NULL)
return Qnil;
if (bs_boxed->type == BS_ELEMENT_OPAQUE && *(void **)ocval == NULL)
return Qnil;
rb_bs_boxed_assert_ffitype_ok(bs_boxed);
if (bs_boxed->type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
VALUE *data;
int i;
size_t pos;
data = (VALUE *)xmalloc(bs_struct->fields_count * sizeof(VALUE));
for (i = 0, pos = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *bs_field =
(bs_element_struct_field_t *)&bs_struct->fields[i];
VALUE fval;
rb_objc_ocval_to_rval(ocval + pos, bs_field->type, &fval);
GC_WB(&data[i], fval);
pos += rb_objc_octype_to_ffitype(bs_field->type)->size;
}
return Data_Wrap_Struct(bs_boxed->klass, NULL, NULL, data);
}
else {
void *data;
data = xmalloc(bs_boxed->ffi_type->size);
memcpy(data, ocval, bs_boxed->ffi_type->size);
return Data_Wrap_Struct(bs_boxed->klass, NULL, NULL, data);
}
}
static long
rebuild_new_struct_ary(ffi_type **elements, VALUE orig, VALUE new)
{
long n = 0;
while ((*elements) != NULL) {
if ((*elements)->type == FFI_TYPE_STRUCT) {
long i, n2;
VALUE tmp;
n2 = rebuild_new_struct_ary((*elements)->elements, orig, new);
tmp = rb_ary_new();
for (i = 0; i < n2; i++) {
if (RARRAY_LEN(orig) == 0)
return 0;
rb_ary_push(tmp, rb_ary_shift(orig));
}
rb_ary_push(new, tmp);
}
elements++;
n++;
}
return n;
}
static VALUE
rb_pointer_create(void *ptr, const char *type)
{
struct RPointer *data;
data = (struct RPointer *)xmalloc(sizeof(struct RPointer ));
data->ptr = ptr;
data->type = type;
return Data_Wrap_Struct(rb_cPointer, NULL, NULL, data);
}
static void rb_objc_rval_to_ocval(VALUE, const char *, void **);
static VALUE
rb_pointer_new_with_type(VALUE recv, VALUE type)
{
const char *ctype;
ffi_type *ffitype;
struct RPointer *data;
Check_Type(type, T_STRING);
ctype = RSTRING_PTR(type);
ffitype = rb_objc_octype_to_ffitype(ctype);
data = (struct RPointer *)xmalloc(sizeof(struct RPointer ));
GC_WB(&data->ptr, xmalloc(ffitype->size));
GC_WB(&data->type, xmalloc(strlen(ctype) + 1));
strcpy((char *)data->type, ctype);
return Data_Wrap_Struct(rb_cPointer, NULL, NULL, data);
}
static VALUE
rb_pointer_assign(VALUE recv, VALUE val)
{
struct RPointer *data;
Data_Get_Struct(recv, struct RPointer, data);
assert(data != NULL);
assert(data->ptr != NULL);
assert(data->type != NULL);
rb_objc_rval_to_ocval(val, data->type, data->ptr);
return val;
}
static VALUE
rb_pointer_aref(VALUE recv, VALUE i)
{
struct RPointer *data;
int idx;
VALUE ret;
Data_Get_Struct(recv, struct RPointer, data);
assert(data != NULL);
assert(data->ptr != NULL);
assert(data->type != NULL);
idx = FIX2INT(i);
rb_objc_ocval_to_rval(data->ptr + (idx * rb_objc_octype_size(data->type)),
data->type, &ret);
return ret;
}
static bool
rb_objc_rval_copy_boxed_data(VALUE rval, bs_element_boxed_t *bs_boxed, void *ocval)
{
rb_bs_boxed_assert_ffitype_ok(bs_boxed);
if (bs_boxed->type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
bool is_ary;
long i, n;
size_t pos;
VALUE *data = NULL;
is_ary = TYPE(rval) == T_ARRAY;
if (is_ary) {
n = RARRAY_LEN(rval);
if (n < bs_struct->fields_count)
rb_raise(rb_eArgError,
"not enough elements in array `%s' to create " \
"structure `%s' (%ld for %d)",
RSTRING_PTR(rb_inspect(rval)), bs_struct->name, n,
bs_struct->fields_count);
if (n > bs_struct->fields_count) {
VALUE new_rval = rb_ary_new();
VALUE orig = rval;
rval = rb_ary_dup(rval);
rebuild_new_struct_ary(bs_boxed->ffi_type->elements, rval,
new_rval);
n = RARRAY_LEN(new_rval);
if (RARRAY_LEN(rval) != 0 || n != bs_struct->fields_count) {
rb_raise(rb_eArgError,
"too much elements in array `%s' to create " \
"structure `%s' (%ld for %d)",
RSTRING_PTR(rb_inspect(orig)),
bs_struct->name, RARRAY_LEN(orig),
bs_struct->fields_count);
}
rval = new_rval;
}
}
else {
if (TYPE(rval) != T_DATA)
return false;
Data_Get_Struct(rval, VALUE, data);
}
for (i = 0, pos = 0; i < bs_struct->fields_count; i++) {
char *field_type;
VALUE o;
field_type = bs_struct->fields[i].type;
o = is_ary ? RARRAY_AT(rval, i) : data[i];
rb_objc_rval_to_ocval(o, field_type, ocval + pos);
pos += rb_objc_octype_to_ffitype(field_type)->size;
}
}
else {
void *data;
if (rval == Qnil) {
*(void **)ocval = NULL;
}
else {
Data_Get_Struct(rval, void, data);
if (data == NULL) {
*(void **)ocval = NULL;
}
else {
memcpy(ocval, data, bs_boxed->ffi_type->size);
}
}
}
return true;
}
static void
rb_objc_rval_to_ocval(VALUE rval, const char *octype, void **ocval)
{
bs_element_boxed_t *bs_boxed;
bool ok = true;
octype = rb_objc_skip_octype_modifiers(octype);
DLOG("CONV", "ruby obj=%p type=%s dest=%p", (void *)rval, octype, ocval);
if (*octype == _C_VOID)
return;
if (bs_boxeds != NULL
&& st_lookup(bs_boxeds, (st_data_t)octype, (st_data_t *)&bs_boxed)) {
ok = rb_objc_rval_copy_boxed_data(rval, bs_boxed, (void *)ocval);
goto bails;
}
if (bs_cftypes != NULL && st_lookup(bs_cftypes, (st_data_t)octype, NULL))
octype = "@";
if (*octype != _C_BOOL && *octype != _C_ID) {
if (rval == Qtrue)
rval = INT2FIX(1);
else if (rval == Qfalse)
rval = INT2FIX(0);
}
switch (*octype) {
case _C_ID:
case _C_CLASS:
*(id *)ocval = rval == Qnil ? NULL : RB2OC(rval);
ok = true;
break;
case _C_SEL:
ok = rb_objc_rval_to_ocsel(rval, ocval);
break;
case _C_PTR:
switch (TYPE(rval)) {
case T_NIL:
*(void **)ocval = NULL;
break;
case T_STRING:
*(char **)ocval = StringValuePtr(rval);
break;
case T_ARRAY:
{
int i, count = RARRAY_LEN(rval);
void *buf = NULL;
if (count > 0) {
size_t subs = rb_objc_octype_size(octype + 1);
void *p;
p = buf = xmalloc(subs * count);
for (i = 0; i < count; i++) {
rb_objc_rval_to_ocval(RARRAY_AT(rval, i), octype + 1, p);
p += subs;
}
}
*(void **)ocval = buf;
}
break;
default:
if (SPECIAL_CONST_P(rval)) {
ok = false;
}
else if (*(VALUE *)rval == rb_cPointer) {
struct RPointer *data;
Data_Get_Struct(rval, struct RPointer, data);
*(void **)ocval = data->ptr;
}
else if (strcmp(octype, "^v") == 0) {
*(void **)ocval = (void *)rval;
}
else if (st_lookup(bs_boxeds, (st_data_t)octype + 1,
(st_data_t *)&bs_boxed)) {
*(void **)ocval = xmalloc(bs_boxed->ffi_type->size);
ok = rb_objc_rval_copy_boxed_data(rval, bs_boxed, *(void **)ocval);
}
else {
ok = false;
}
break;
}
break;
case _C_UCHR:
*(unsigned char *)ocval = (unsigned char)
NUM2UINT(rb_Integer(rval));
break;
case _C_BOOL:
{
unsigned char v;
switch (TYPE(rval)) {
case T_FALSE:
case T_NIL:
v = 0;
break;
case T_TRUE:
/* All other types should be converted as true, to
* follow the Ruby semantics (where for example any
* integer is always true, even 0)
*/
default:
v = 1;
break;
}
*(unsigned char *)ocval = v;
}
break;
case _C_CHR:
if (TYPE(rval) == T_STRING && RSTRING_LEN(rval) == 1) {
*(char *)ocval = RSTRING_PTR(rval)[0];
}
else {
*(char *)ocval = (char) NUM2INT(rb_Integer(rval));
}
break;
case _C_SHT:
*(short *)ocval = (short) NUM2INT(rb_Integer(rval));
break;
case _C_USHT:
*(unsigned short *)ocval =
(unsigned short)NUM2UINT(rb_Integer(rval));
break;
case _C_INT:
*(int *)ocval = (int) NUM2INT(rb_Integer(rval));
break;
case _C_UINT:
*(unsigned int *)ocval = (unsigned int) NUM2UINT(rb_Integer(rval));
break;
case _C_LNG:
*(long *)ocval = (long) NUM2LONG(rb_Integer(rval));
break;
case _C_ULNG:
*(unsigned long *)ocval = (unsigned long)
NUM2ULONG(rb_Integer(rval));
break;
#if HAVE_LONG_LONG
case _C_LNG_LNG:
*(long long *)ocval = (long long) NUM2LL(rb_Integer(rval));
break;
case _C_ULNG_LNG:
*(unsigned long long *)ocval =
(unsigned long long) NUM2ULL(rb_Integer(rval));
break;
#endif
case _C_FLT:
*(float *)ocval = (float) RFLOAT_VALUE(rb_Float(rval));
break;
case _C_DBL:
*(double *)ocval = RFLOAT_VALUE(rb_Float(rval));
break;
case _C_CHARPTR:
{
VALUE str = rb_obj_as_string(rval);
*(char **)ocval = StringValuePtr(str);
}
break;
default:
ok = false;
}
bails:
if (!ok)
rb_raise(rb_eArgError, "can't convert Ruby object `%s' to " \
"Objective-C value of type `%s'",
RSTRING_PTR(rb_inspect(rval)), octype);
}
static inline bool
rb_objc_ocid_to_rval(void **ocval, VALUE *rbval)
{
id ocid = *(id *)ocval;
if (ocid == NULL) {
*rbval = Qnil;
}
else {
*rbval = (VALUE)ocid;
}
return true;
}
static void
rb_objc_ocval_to_rval(void **ocval, const char *octype, VALUE *rbval)
{
bool ok;
octype = rb_objc_skip_octype_modifiers(octype);
ok = true;
DLOG("CONV", "objc obj=%p type=%s dest=%p", ocval, octype, rbval);
{
bs_element_boxed_t *bs_boxed;
if (bs_boxeds != NULL
&& st_lookup(bs_boxeds, (st_data_t)octype,
(st_data_t *)&bs_boxed)) {
*rbval = rb_bs_boxed_new_from_ocdata(bs_boxed, ocval);
goto bails;
}
if (bs_cftypes != NULL
&& st_lookup(bs_cftypes, (st_data_t)octype, NULL))
octype = "@";
}
switch (*octype) {
case _C_ID:
{
id obj = *(id *)ocval;
if (obj == NULL) {
*rbval = Qnil;
}
else if (*(Class *)obj == (Class)rb_cFixnum) {
*rbval = LONG2FIX(RFIXNUM(obj)->value);
}
else {
*rbval = *(VALUE *)ocval;
}
ok = true;
break;
}
case _C_CLASS:
*rbval = *(void **)ocval == NULL ? Qnil : *(VALUE *)ocval;
ok = true;
break;
case _C_BOOL:
*rbval = *(bool *)ocval ? Qtrue : Qfalse;
break;
case _C_CHR:
*rbval = INT2NUM(*(char *)ocval);
break;
case _C_UCHR:
*rbval = UINT2NUM(*(unsigned char *)ocval);
break;
case _C_SHT:
*rbval = INT2NUM(*(short *)ocval);
break;
case _C_USHT:
*rbval = UINT2NUM(*(unsigned short *)ocval);
break;
case _C_INT:
*rbval = INT2NUM(*(int *)ocval);
break;
case _C_UINT:
*rbval = UINT2NUM(*(unsigned int *)ocval);
break;
case _C_LNG:
*rbval = INT2NUM(*(long *)ocval);
break;
case _C_ULNG:
*rbval = UINT2NUM(*(unsigned long *)ocval);
break;
case _C_LNG_LNG:
*rbval = LL2NUM(*(long long *)ocval);
break;
case _C_ULNG_LNG:
*rbval = ULL2NUM(*(unsigned long long *)ocval);
break;
case _C_FLT:
*rbval = rb_float_new((double)(*(float *)ocval));
break;
case _C_DBL:
*rbval = rb_float_new(*(double *)ocval);
break;
case _C_SEL:
{
const char *selname = sel_getName(*(SEL *)ocval);
*rbval = rb_str_new2(selname);
}
break;
case _C_CHARPTR:
*rbval = *(void **)ocval == NULL
? Qnil
: rb_str_new2(*(char **)ocval);
break;
case _C_PTR:
if (*(void **)ocval == NULL) {
*rbval = Qnil;
}
else {
*rbval = rb_pointer_create(*(void **)ocval, octype + 1);
}
break;
default:
ok = false;
}
bails:
if (!ok)
rb_raise(rb_eArgError, "can't convert C/Objective-C value `%p' " \
"of type `%s' to Ruby object", ocval, octype);
}
static void
rb_objc_exc_raise(id exception)
{
const char *name;
const char *desc;
name = [[exception name] UTF8String];
desc = [[exception reason] UTF8String];
rb_raise(rb_eRuntimeError, "%s: %s", name, desc);
}
bs_element_method_t *
rb_bs_find_method(Class klass, SEL sel)
{
if (bs_classes == NULL)
return NULL;
do {
bs_element_indexed_class_t *bs_class;
bs_element_method_t *bs_method;
if (st_lookup(bs_classes, (st_data_t)class_getName(klass),
(st_data_t *)&bs_class)) {
struct st_table *t = class_isMetaClass(klass)
? bs_class->cmethods : bs_class->imethods;
if (t != NULL
&& st_lookup(t, (st_data_t)sel, (st_data_t *)&bs_method))
return bs_method;
}
klass = class_getSuperclass(klass);
}
while (klass != NULL);
return NULL;
}
static const char *
rb_objc_method_get_type(Method method, unsigned count,
bs_element_method_t *bs_method, int n,
char *type, size_t type_len)
{
if (bs_method != NULL) {
if (n == -1) {
if (bs_method->retval != NULL) {
return bs_method->retval->type;
}
}
else {
unsigned i;
for (i = 0; i < bs_method->args_count; i++) {
if (bs_method->args[i].index == n
&& bs_method->args[i].type != NULL) {
return bs_method->args[i].type;
}
}
}
}
if (n == -1) {
method_getReturnType(method, type, type_len);
}
else {
if (n + 2 < count) {
method_getArgumentType(method, n + 2, type, type_len);
}
else {
assert(bs_method->variadic);
return "@"; /* FIXME: should parse the format string if any */
}
}
return type;
}
static inline int
SubtypeUntil(const char *type, char end)
{
int level = 0;
const char *head = type;
while (*type)
{
if (!*type || (!level && (*type == end)))
return (int)(type - head);
switch (*type)
{
case ']': case '}': case ')': level--; break;
case '[': case '{': case '(': level += 1; break;
}
type += 1;
}
rb_bug ("Object: SubtypeUntil: end of type encountered prematurely\n");
return 0;
}
static inline const char *
SkipStackSize(const char *type)
{
while ((*type >= '0') && (*type <= '9'))
type += 1;
return type;
}
static inline const char *
SkipFirstType(const char *type)
{
while (1)
{
switch (*type++)
{
case 'O': /* bycopy */
case 'n': /* in */
case 'o': /* out */
case 'N': /* inout */
case 'r': /* const */
case 'V': /* oneway */
case '^': /* pointers */
break;
/* arrays */
case '[':
return type + SubtypeUntil (type, ']') + 1;
/* structures */
case '{':
return type + SubtypeUntil (type, '}') + 1;
/* unions */
case '(':
return type + SubtypeUntil (type, ')') + 1;
/* basic types */
default:
return type;
}
}
}
static inline void
rb_method_setTypeEncoding(Method method, const char *types)
{
char **types_p = ((void *)method + sizeof(SEL));
free(*types_p);
*types_p = strdup(types);
}
static void *rb_ruby_to_objc_closure(const char *octype, unsigned arity, NODE *node);
static inline void
rb_overwrite_method_signature(Class klass, SEL sel, const char *types, bool raise_if_error)
{
Method method;
IMP imp;
NODE *node;
method = class_getInstanceMethod(klass, sel);
if (method == NULL) {
if (raise_if_error) {
rb_raise(rb_eArgError, "%c[%s %s] not found",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel));
}
return;
}
if (strcmp(method_getTypeEncoding(method), types) == 0) {
return;
}
imp = method_getImplementation(method);
node = rb_objc_method_node3(imp);
if (node == NULL) {
if (raise_if_error) {
rb_raise(rb_eArgError, "%c[%s %s] is a pure Objective-C method",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel));
}
else {
return;
}
}
DLOG("OCALL", "overwrite %c[%s %s] type encoding to %s",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel),
types);
/* re-generate the FFI closure with the right types */
imp = rb_ruby_to_objc_closure(types, method_getNumberOfArguments(method) - 2, node);
method_setImplementation(method, imp);
/* change the method signature */
rb_method_setTypeEncoding(method, types);
}
VALUE
rb_objc_call2(VALUE recv, VALUE klass, SEL sel, IMP imp,
struct rb_objc_method_sig *sig, bs_element_method_t *bs_method,
int argc, VALUE *argv)
{
unsigned i, real_count, count;
ffi_type *ffi_rettype, **ffi_argtypes;
void *ffi_ret, **ffi_args;
ffi_cif *cif;
const char *type;
char *rettype, buf[100];
id ocrcv;
/* XXX very special exceptions! */
if (recv == rb_cNSMutableHash && sel == @selector(new)) {
/* because Hash.new can accept a block */
return rb_class_new_instance(0, NULL, recv);
}
else if (sel == @selector(class)) {
if (RCLASS_META(klass)) {
/* because +[NSObject class] returns self */
return RCLASS_MODULE(recv) ? rb_cModule : rb_cClass;
}
/* because the CF classes should be hidden */
else if (klass == rb_cCFString) {
bool __CFStringIsMutable(void *);
return __CFStringIsMutable((void *)recv)
? rb_cNSMutableString : rb_cNSString;
}
else if (klass == rb_cCFArray) {
bool _CFArrayIsMutable(void *);
return _CFArrayIsMutable((void *)recv)
? rb_cNSMutableArray : rb_cNSArray;
}
else if (klass == rb_cCFHash) {
bool _CFDictionaryIsMutable(void *);
return _CFDictionaryIsMutable((void *)recv)
? rb_cNSMutableHash : rb_cNSHash;
}
else if (klass == rb_cCFSet) {
bool _CFSetIsMutable(void *);
return _CFSetIsMutable((void *)recv)
? rb_cNSMutableSet : rb_cNSSet;
}
}
ocrcv = RB2OC(recv);
DLOG("OCALL", "%c[<%s %p> %s] types=%s bs_method=%p", class_isMetaClass((Class)klass) ? '+' : '-', class_getName((Class)klass), (void *)ocrcv, (char *)sel, sig->types, bs_method);
count = sig->argc;
assert(count >= 2);
real_count = count;
if (bs_method != NULL && bs_method->variadic) {
if (argc < count - 2)
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, count - 2);
count = argc + 2;
}
else if (argc != count - 2) {
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, count - 2);
}
#define UNLOCK_GIL() \
do { if (rb_cMutex != 0) { native_mutex_unlock(&GET_THREAD()->vm->global_interpreter_lock); }; } while (0)
#define LOCK_GIL() \
do { if (rb_cMutex != 0) { native_mutex_lock(&GET_THREAD()->vm->global_interpreter_lock); }; } while (0)
if (count == 2) {
if (sig->types[0] == '@' || sig->types[0] == '#' || sig->types[0] == 'v') {
/* Easy case! */
id exception = nil;
//UNLOCK_GIL();
@try {
if (klass == *(VALUE *)ocrcv) {
ffi_ret = objc_msgSend(ocrcv, sel);
}
else {
struct objc_super s;
s.receiver = ocrcv;
#if defined(__LP64__)
s.super_class = (Class)klass;
#else
s.class = (Class)klass;
#endif
ffi_ret = objc_msgSendSuper(&s, sel);
}
}
@catch (id e) {
exception = e;
}
//LOCK_GIL();
if (exception != nil) {
rb_objc_exc_raise(exception);
}
if (sig->types[0] == '@' || sig->types[0] == '#') {
VALUE retval;
buf[0] = sig->types[0];
buf[1] = '\0';
rb_objc_ocval_to_rval(&ffi_ret, buf, &retval);
return retval;
}
return Qnil;
}
}
const size_t s = sizeof(ffi_type *) * (count + 1);
ffi_argtypes = bs_method != NULL && bs_method->variadic
? (ffi_type **)alloca(s) : (ffi_type **)malloc(s);
ffi_argtypes[0] = &ffi_type_pointer;
ffi_argtypes[1] = &ffi_type_pointer;
ffi_args = (void **)alloca(sizeof(void *) * (count + 1));
ffi_args[0] = &ocrcv;
ffi_args[1] = &sel;
type = SkipFirstType(sig->types);
rettype = alloca(type - sig->types + 1);
strncpy(rettype, sig->types, type - sig->types);
rettype[type - sig->types] = '\0';
ffi_rettype = rb_objc_octype_to_ffitype(rettype);
type = SkipStackSize(type);
type = SkipFirstType(type); /* skip receiver */
type = SkipStackSize(type);
type = SkipFirstType(type); /* skip selector */
bs_element_arg_t *bs_args;
bs_args = bs_method == NULL ? NULL : bs_method->args;
for (i = 0; i < argc; i++) {
bs_element_arg_t *bs_arg;
ffi_type *ffi_argtype;
bs_arg = NULL;
if (*type == '\0') {
if (bs_method != NULL && bs_method->variadic) {
buf[0] = '@';
buf[1] = '\0';
}
else {
rb_bug("incomplete method signature `%s' for argc %d",
sig->types, argc);
}
}
else {
const char *type2;
type = SkipStackSize(type);
type2 = SkipFirstType(type);
strncpy(buf, type, MIN(sizeof buf, type2 - type));
buf[MIN(sizeof buf, type2 - type)] = '\0';
type = type2;
if (bs_args != NULL) {
while ((bs_arg = bs_args)->index < i) {
bs_args++;
}
}
}
ffi_argtypes[i + 2] = rb_objc_octype_to_ffitype(buf);
assert(ffi_argtypes[i + 2]->size > 0);
ffi_argtype = ffi_argtypes[i + 2];
ffi_args[i + 2] = (void *)alloca(ffi_argtype->size);
rb_objc_rval_to_ocval(argv[i], buf, ffi_args[i + 2]);
if (buf[0] == _C_SEL && bs_arg != NULL && bs_arg->sel_of_type != NULL) {
SEL arg_sel;
int j;
arg_sel = *(SEL *)ffi_args[i + 2];
/* XXX BridgeSupport tells us that this argument contains a
* selector of the given type, but we don't have any information
* regarding the target. RubyCocoa and the other ObjC bridges do
* not really require it since they use the NSObject message
* forwarding mechanism, but MacRuby registers all methods in the
* runtime.
*
* Therefore, we apply here a naive heuristic by assuming that
* either the receiver or one of the arguments of this call is the
* future target.
*/
rb_overwrite_method_signature(*(Class *)ocrcv, arg_sel,
bs_arg->sel_of_type, false);
for (j = 0; j < argc; j++) {
if (j != i && !SPECIAL_CONST_P(argv[j])) {
rb_overwrite_method_signature(*(Class *)argv[j], arg_sel,
bs_arg->sel_of_type, false);
}
}
}
}
ffi_argtypes[count] = NULL;
ffi_args[count] = NULL;
cif = (ffi_cif *)alloca(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, count, ffi_rettype,
ffi_argtypes) != FFI_OK) {
rb_fatal("can't prepare cif for objc method type `%s'",
sig->types);
}
if (ffi_rettype != &ffi_type_void) {
ffi_ret = (void *)alloca(ffi_rettype->size);
memset(ffi_ret, 0, ffi_rettype->size);
}
else {
ffi_ret = NULL;
}
@try {
ffi_call(cif, FFI_FN(imp), ffi_ret, ffi_args);
}
@catch (id e) {
rb_objc_exc_raise(e);
}
if (ffi_rettype != &ffi_type_void) {
VALUE resp;
rb_objc_ocval_to_rval(ffi_ret, rettype, &resp);
return resp;
}
else {
return Qnil;
}
}
VALUE
rb_objc_call(VALUE recv, SEL sel, int argc, VALUE *argv)
{
VALUE klass;
Method method;
struct rb_objc_method_sig sig;
klass = CLASS_OF(recv);
method = class_getInstanceMethod((Class)klass, sel);
assert(rb_objc_fill_sig(recv, (Class)klass, sel, &sig, NULL));
return rb_objc_call2(recv, klass, sel, method_getImplementation(method),
&sig, NULL, argc, argv);
}
static inline const char *
rb_get_bs_method_type(bs_element_method_t *bs_method, int arg)
{
if (bs_method != NULL) {
if (arg == -1) {
if (bs_method->retval != NULL)
return bs_method->retval->type;
}
else {
int i;
for (i = 0; i < bs_method->args_count; i++) {
if (bs_method->args[i].index == arg)
return bs_method->args[i].type;
}
}
}
return NULL;
}
bool
rb_objc_fill_sig(VALUE recv, Class klass, SEL sel, struct rb_objc_method_sig *sig, bs_element_method_t *bs_method)
{
Method method;
const char *type;
char buf[100];
unsigned i;
method = class_getInstanceMethod(klass, sel);
if (method != NULL) {
if (bs_method == NULL) {
sig->types = method_getTypeEncoding(method);
sig->argc = method_getNumberOfArguments(method);
}
else {
char buf2[100];
type = rb_get_bs_method_type(bs_method, -1);
if (type != NULL) {
strlcpy(buf, type, sizeof buf);
}
else {
method_getReturnType(method, buf2, sizeof buf2);
strlcpy(buf, buf2, sizeof buf);
}
sig->argc = method_getNumberOfArguments(method);
for (i = 0; i < sig->argc; i++) {
if (i >= 2 && (type = rb_get_bs_method_type(bs_method, i - 2)) != NULL) {
strlcat(buf, type, sizeof buf);
}
else {
method_getArgumentType(method, i, buf2, sizeof(buf2));
strlcat(buf, buf2, sizeof buf);
}
}
sig->types = (char *)sel_registerName(buf); /* unify the string */
}
return true;
}
else if (!SPECIAL_CONST_P(recv)) {
NSMethodSignature *msig = [(id)recv methodSignatureForSelector:sel];
if (msig != NULL) {
char buf[100];
unsigned i;
type = rb_get_bs_method_type(bs_method, -1);
if (type == NULL)
type = [msig methodReturnType];
strlcpy(buf, type, sizeof buf);
sig->argc = [msig numberOfArguments];
for (i = 0; i < sig->argc; i++) {
if (i < 2 || (type = rb_get_bs_method_type(bs_method, i - 2)) == NULL) {
type = [msig getArgumentTypeAtIndex:i];
}
strlcat(buf, type, sizeof buf);
}
sig->types = (char *)sel_registerName(buf); /* unify the string */
return true;
}
}
return false;
}
void
rb_objc_alias(VALUE klass, ID name, ID def)
{
const char *name_str, *def_str;
SEL name_sel, def_sel;
Method method, dest_method;
bool redo = false;
VALUE included_in_classes;
int included_in_classes_count = -1;
name_str = rb_id2name(name);
def_str = rb_id2name(def);
name_sel = sel_registerName(name_str);
def_sel = sel_registerName(def_str);
included_in_classes = RCLASS_VERSION(klass) & RCLASS_IS_INCLUDED
? rb_attr_get(klass, idIncludedInClasses) : Qnil;
method = class_getInstanceMethod((Class)klass, def_sel);
if (method == NULL) {
size_t len = strlen(def_str);
if (def_str[len - 1] != ':') {
char buf[100];
snprintf(buf, sizeof buf, "%s:", def_str);
def_sel = sel_registerName(buf);
method = class_getInstanceMethod((Class)klass, def_sel);
if (method == NULL) {
rb_print_undef(klass, def, 0);
}
len = strlen(name_str);
if (name_str[len - 1] != ':') {
snprintf(buf, sizeof buf, "%s:", name_str);
name_sel = sel_registerName(buf);
}
}
}
alias_method:
#define forward_method_definition(sel,imp,types) \
do { \
if (included_in_classes != Qnil) { \
int i; \
if (included_in_classes_count == -1) \
included_in_classes_count = RARRAY_LEN(included_in_classes); \
for (i = 0; i < included_in_classes_count; i++) { \
VALUE k = RARRAY_AT(included_in_classes, i); \
Method m = class_getInstanceMethod((Class)k, sel); \
DLOG("DEFI", "-[%s %s]", class_getName((Class)k), (char *)sel); \
if (m != NULL) { \
Method m2 = class_getInstanceMethod((Class)RCLASS_SUPER(k), sel); \
if (m != m2) { \
method_setImplementation(m, imp); \
break; \
} \
} \
assert(class_addMethod((Class)k, sel, imp, types)); \
} \
} \
} \
while (0)
dest_method = class_getInstanceMethod((Class)klass, name_sel);
DLOG("ALIAS", "%c[%s %s -> %s] types=%s direct_override=%d orig_node=%p",
class_isMetaClass((Class)klass) ? '+' : '-', class_getName((Class)klass), (char *)name_sel, (char *)def_sel, method_getTypeEncoding(method), dest_method != NULL, rb_objc_method_node3(method_getImplementation(method)));
if (dest_method != NULL
&& dest_method != class_getInstanceMethod((Class)RCLASS_SUPER(klass), name_sel)) {
method_setImplementation(dest_method, method_getImplementation(method));
}
else {
assert(class_addMethod((Class)klass, name_sel,
method_getImplementation(method),
method_getTypeEncoding(method)));
}
forward_method_definition(name_sel, method_getImplementation(method), method_getTypeEncoding(method));
if (!redo && name_str[strlen(name_str) - 1] != ':') {
char buf[100];
snprintf(buf, sizeof buf, "%s:", def_str);
def_sel = sel_registerName(buf);
method = class_getInstanceMethod((Class)klass, def_sel);
if (method != NULL) {
snprintf(buf, sizeof buf, "%s:", name_str);
name_sel = sel_registerName(buf);
redo = true;
goto alias_method;
}
}
#undef forward_method_definition
}
static VALUE
rb_super_objc_send(int argc, VALUE *argv, VALUE rcv)
{
return Qnil;
#if 0
struct objc_ruby_closure_context fake_ctx;
id ocrcv;
ID mid;
Class klass;
if (argc < 1)
rb_raise(rb_eArgError, "expected at least one argument");
mid = rb_to_id(argv[0]);
argv++;
argc--;
ocrcv = RB2OC(rcv);
klass = class_getSuperclass(*(Class *)ocrcv);
fake_ctx.selector = sel_registerName(rb_id2name(mid));
fake_ctx.method = class_getInstanceMethod(klass, fake_ctx.selector);
assert(fake_ctx.method != NULL);
fake_ctx.bs_method = NULL;
fake_ctx.cif = NULL;
fake_ctx.imp = NULL;
fake_ctx.klass = NULL;
return rb_objc_call_objc(argc, argv, ocrcv, klass, true, &fake_ctx);
#endif
}
#define IGNORE_PRIVATE_OBJC_METHODS 1
struct rb_ruby_to_objc_closure_handler_main_ctx {
ffi_cif *cif;
void *resp;
void **args;
void *userdata;
};
VALUE rb_vm_call(rb_thread_t * th, VALUE klass, VALUE recv, VALUE id,
ID oid, int argc, const VALUE *argv, const NODE *body,
int nosuper);
static VALUE
rb_ruby_to_objc_closure_handler_main(void *ctx)
{
struct rb_ruby_to_objc_closure_handler_main_ctx *_ctx =
(struct rb_ruby_to_objc_closure_handler_main_ctx *)ctx;
ffi_cif *cif = _ctx->cif;
void *resp = _ctx->resp;
void **args = _ctx->args;
void *userdata = _ctx->userdata;
void *rcv;
SEL sel;
ID mid;
VALUE rrcv, ret;
Method method;
const char *type;
char buf[128];
long i, argc;
VALUE *argv, klass;
NODE *body, *node;
bs_element_method_t *bs_method;
rcv = *(id *)args[0];
sel = *(SEL *)args[1];
body = (NODE *)userdata;
node = body->nd_body;
method = class_getInstanceMethod(*(Class *)rcv, sel);
assert(method != NULL);
bs_method = rb_bs_find_method(*(Class *)rcv, sel);
argc = method_getNumberOfArguments(method) - 2;
if (argc > 0) {
argv = (VALUE *)alloca(sizeof(VALUE) * argc);
for (i = 0; i < argc; i++) {
VALUE val;
type = rb_objc_method_get_type(method, cif->nargs, bs_method,
i, buf, sizeof buf);
rb_objc_ocval_to_rval(args[i + 2], type, &val);
argv[i] = val;
}
}
else {
argv = NULL;
}
rrcv = rcv == NULL ? Qnil : (VALUE)rcv;
mid = rb_intern((const char *)sel);
klass = CLASS_OF(rrcv);
DLOG("RCALL", "%c[<%s %p> %s] node=%p", class_isMetaClass((Class)klass) ? '+' : '-', class_getName((Class)klass), (void *)rrcv, (char *)sel, body);
ret = rb_vm_call(GET_THREAD(), klass, rrcv, mid, Qnil,
argc, argv, node, 0);
type = rb_objc_method_get_type(method, cif->nargs, bs_method,
-1, buf, sizeof buf);
rb_objc_rval_to_ocval(ret, type, resp);
return Qnil;
}
static void
rb_ruby_to_objc_closure_handler(ffi_cif *cif, void *resp, void **args,
void *userdata)
{
struct rb_ruby_to_objc_closure_handler_main_ctx ctx;
ctx.cif = cif;
ctx.resp = resp;
ctx.args = args;
ctx.userdata = userdata;
if (rb_cMutex == 0) {
/* GL not initialized yet! */
rb_ruby_to_objc_closure_handler_main(&ctx);
}
else {
if (GET_THREAD()->thread_id == pthread_self()) {
rb_ruby_to_objc_closure_handler_main(&ctx);
}
else {
rb_thread_t *rb_thread_wrap_existing_native_thread(rb_thread_id_t id);
rb_thread_t *th, *old;
th = rb_thread_wrap_existing_native_thread(pthread_self());
native_mutex_lock(&GET_THREAD()->vm->global_interpreter_lock);
old = ruby_current_thread;
ruby_current_thread = th;
rb_ruby_to_objc_closure_handler_main(&ctx);
native_mutex_unlock(&GET_THREAD()->vm->global_interpreter_lock);
ruby_current_thread = old;
}
}
}
static void *
rb_ruby_to_objc_closure(const char *octype, unsigned arity, NODE *node)
{
const char *p;
char buf[128];
ffi_type *ret, **args;
ffi_cif *cif;
ffi_closure *closure;
unsigned i;
p = octype;
assert((p = rb_objc_get_first_type(p, buf, sizeof buf)) != NULL);
ret = rb_objc_octype_to_ffitype(buf);
args = (ffi_type **)malloc(sizeof(ffi_type *) * (arity + 2));
i = 0;
while ((p = rb_objc_get_first_type(p, buf, sizeof buf)) != NULL) {
args[i] = rb_objc_octype_to_ffitype(buf);
assert(++i <= arity + 2);
}
cif = (ffi_cif *)malloc(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, arity + 2, ret, args) != FFI_OK) {
rb_fatal("can't prepare ruby to objc cif");
}
/* XXX mmap() and mprotect() are 2 expensive calls, maybe we should try to
* mmap() and mprotect() a large memory page and reuse it for closures?
* XXX currently overwriting a closure leaks the previous one!
*/
if ((closure = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0)) == (void *)-1) {
rb_fatal("can't allocate ruby to objc closure");
}
if (ffi_prep_closure(closure, cif, rb_ruby_to_objc_closure_handler, node)
!= FFI_OK) {
rb_fatal("can't prepare ruby to objc closure");
}
if (mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1) {
rb_fatal("can't mprotect the ruby to objc closure");
}
rb_objc_retain(node);
return closure;
}
NODE *
rb_objc_method_node3(IMP imp)
{
if (imp == NULL || ((ffi_closure *)imp)->fun != rb_ruby_to_objc_closure_handler)
return NULL;
return ((ffi_closure *)imp)->user_data;
}
extern id _objc_msgForward(id receiver, SEL sel, ...);
static void *_objc_msgForward_addr = NULL;
NODE *
rb_objc_method_node2(VALUE mod, SEL sel, IMP *pimp)
{
IMP imp;
if (pimp != NULL)
*pimp = NULL;
imp = class_getMethodImplementation((Class)mod, sel);
if (imp == (IMP)_objc_msgForward_addr)
imp = NULL;
if (pimp != NULL)
*pimp = imp;
if (imp == NULL || ((ffi_closure *)imp)->fun != rb_ruby_to_objc_closure_handler)
return NULL;
return ((ffi_closure *)imp)->user_data;
}
NODE *
rb_objc_method_node(VALUE mod, ID mid, IMP *pimp, SEL *psel)
{
SEL sel;
IMP imp;
NODE *node;
sel = mid == ID_ALLOCATOR
? @selector(alloc)
: sel_registerName(rb_id2name(mid));
if (psel != NULL) {
*psel = sel;
}
node = rb_objc_method_node2(mod, sel, &imp);
if (pimp != NULL) {
*pimp = imp;
}
if (imp == NULL) {
char buf[100];
size_t slen;
slen = strlen((char *)sel);
if (((char *)sel)[slen - 1] == ':') {
return NULL;
}
strlcpy(buf, (char *)sel, sizeof buf);
strlcat(buf, ":", sizeof buf);
sel = sel_registerName(buf);
if (psel != NULL) {
*psel = sel;
}
return rb_objc_method_node2(mod, sel, pimp);
}
return node;
}
void
rb_objc_register_ruby_method(VALUE mod, ID mid, NODE *body)
{
SEL sel;
Method method;
char *types;
int arity, oc_arity;
IMP imp;
bool direct_override;
NODE *node;
VALUE included_in_classes;
int included_in_classes_count = - 1;
#define forward_method_definition(sel,imp,types) \
do { \
if (included_in_classes != Qnil) { \
int i; \
if (included_in_classes_count == -1) \
included_in_classes_count = RARRAY_LEN(included_in_classes); \
for (i = 0; i < included_in_classes_count; i++) { \
VALUE k = RARRAY_AT(included_in_classes, i); \
Method m = class_getInstanceMethod((Class)k, sel); \
DLOG("DEFI", "-[%s %s]", class_getName((Class)k), (char *)sel); \
if (m != NULL) { \
Method m2 = class_getInstanceMethod((Class)RCLASS_SUPER(k), sel); \
if (m != m2) { \
method_setImplementation(m, imp); \
break; \
} \
} \
assert(class_addMethod((Class)k, sel, imp, types)); \
} \
} \
} \
while (0)
if (body != NULL) {
if (nd_type(body) != NODE_METHOD)
rb_bug("non-method node (%d)", nd_type(body));
node = body->nd_body;
arity = oc_arity = rb_node_arity(node);
}
else {
node = NULL;
arity = oc_arity = 0;
}
if (mid == ID_ALLOCATOR) {
sel = @selector(alloc);
}
else {
char *mid_str;
size_t mid_str_len;
char buf[100];
mid_str = (char *)rb_id2name(mid);
mid_str_len = strlen(mid_str);
if ((arity < 0 || arity > 0) && mid_str[mid_str_len - 1] != ':') {
assert(sizeof(buf) > mid_str_len + 1);
snprintf(buf, sizeof buf, "%s:", mid_str);
sel = sel_registerName(buf);
oc_arity = 1;
}
else {
sel = sel_registerName(mid_str);
if (sel == sel_ignored || sel == sel_zone) {
assert(sizeof(buf) > mid_str_len + 7);
snprintf(buf, sizeof buf, "__rb_%s__", mid_str);
sel = sel_registerName(buf);
}
}
}
included_in_classes = RCLASS_VERSION(mod) & RCLASS_IS_INCLUDED
? rb_attr_get(mod, idIncludedInClasses) : Qnil;
direct_override = false;
method = class_getInstanceMethod((Class)mod, sel);
if (method != NULL) {
Class klass;
if (oc_arity + 2 != method_getNumberOfArguments(method)) {
rb_warn("cannot override Objective-C method `%s' in " \
"class `%s' because of an arity mismatch (%d for %d)",
(char *)method_getName(method),
class_getName((Class)mod),
oc_arity + 2,
method_getNumberOfArguments(method));
return;
}
types = (char *)method_getTypeEncoding(method);
klass = (Class)RCLASS_SUPER(mod);
direct_override =
klass == NULL || class_getInstanceMethod(klass, sel) != method;
}
else {
struct st_table *t = class_isMetaClass((Class)mod)
? bs_inf_prot_cmethods
: bs_inf_prot_imethods;
if (t == NULL || !st_lookup(t, (st_data_t)sel, (st_data_t *)&types)) {
if (oc_arity == 0) {
types = "@@:";
}
else if (oc_arity == 1) {
types = "@@:@";
}
else {
int i;
types = alloca(3 + oc_arity + 1);
types[0] = '@';
types[1] = '@';
types[2] = ':';
for (i = 0; i < oc_arity; i++)
types[3 + i] = '@';
types[3 + oc_arity] = '\0';
}
}
}
DLOG("DEFM", "%c[%s %s] types=%s arity=%d body=%p override=%d direct_override=%d",
class_isMetaClass((Class)mod) ? '+' : '-', class_getName((Class)mod), (char *)sel, types, arity, body, method != NULL, direct_override);
imp = body == NULL ? NULL : rb_ruby_to_objc_closure(types, oc_arity, body);
if (method != NULL && direct_override) {
method_setImplementation(method, imp);
}
else {
assert(class_addMethod((Class)mod, sel, imp, types));
}
forward_method_definition(sel, imp, types);
if (node != NULL) {
const char *sel_str = (const char *)sel;
const size_t sel_len = strlen(sel_str);
SEL new_sel;
char *new_types;
bool override;
if (sel_str[sel_len - 1] == ':') {
char buf[100];
strlcpy(buf, sel_str, sizeof buf);
assert(sizeof buf > sel_len);
buf[sel_len - 1] = '\0';
new_sel = sel_registerName(buf);
new_types = "@@:";
override = arity == -1 || arity == -2;
arity = 0;
}
else {
char buf[100];
strlcpy(buf, sel_str, sizeof buf);
strlcat(buf, ":", sizeof buf);
new_sel = sel_registerName(buf);
new_types = "@@:@";
override = false;
arity = -1;
}
method = class_getInstanceMethod((Class)mod, new_sel);
direct_override = false;
if (method != NULL || override) {
direct_override = method != NULL && class_getInstanceMethod((Class)RCLASS_SUPER(mod), new_sel) != method;
DLOG("DEFM", "%c[%s %s] types=%s arity=%d body=%p override=%d direct_override=%d",
class_isMetaClass((Class)mod) ? '+' : '-', class_getName((Class)mod), (char *)new_sel, new_types, arity, body, method != NULL, direct_override);
if (method != NULL && direct_override) {
method_setImplementation(method, imp);
}
else {
assert(class_addMethod((Class)mod, new_sel, imp, new_types));
}
forward_method_definition(new_sel, imp, new_types);
}
}
#undef forward_method_definition
}
void
rb_objc_change_ruby_method_signature(VALUE mod, VALUE mid, VALUE sig)
{
SEL sel = sel_registerName(rb_id2name(rb_to_id(mid)));
char *types = StringValuePtr(sig);
rb_overwrite_method_signature((Class)mod, sel, types, true);
}
static inline bool
rb_objc_resourceful(VALUE obj)
{
/* TODO we should export this function in the runtime
* Object#__resourceful__? perhaps?
*/
extern CFTypeID __CFGenericTypeID(void *);
CFTypeID t = __CFGenericTypeID((void *)obj);
if (t > 0) {
extern void *_CFRuntimeGetClassWithTypeID(CFTypeID);
long *d = (long *)_CFRuntimeGetClassWithTypeID(t);
/* first long is version, 4 means resourceful */
if (d != NULL && *d & 4)
return true;
}
return false;
}
VALUE
rb_bsfunc_call(bs_element_function_t *bs_func, void *sym, int argc, VALUE *argv)
{
unsigned i;
ffi_type *ffi_rettype, **ffi_argtypes;
void *ffi_ret, **ffi_args;
ffi_cif *cif;
VALUE resp;
if (argc != bs_func->args_count)
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, bs_func->args_count);
DLOG("FCALL", "%s() sym=%p argc=%d", bs_func->name, sym, argc);
ffi_argtypes = (ffi_type **)alloca(sizeof(ffi_type *) * argc + 1);
ffi_args = (void **)alloca(sizeof(void *) * argc + 1);
for (i = 0; i < argc; i++) {
char *type = bs_func->args[i].type;
ffi_argtypes[i] = rb_objc_octype_to_ffitype(type);
ffi_args[i] = (void *)alloca(ffi_argtypes[i]->size);
rb_objc_rval_to_ocval(argv[i], type, ffi_args[i]);
}
ffi_argtypes[argc] = NULL;
ffi_args[argc] = NULL;
ffi_rettype = bs_func->retval == NULL
? &ffi_type_void
: rb_objc_octype_to_ffitype(bs_func->retval->type);
cif = (ffi_cif *)alloca(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, argc, ffi_rettype, ffi_argtypes)
!= FFI_OK)
rb_fatal("can't prepare cif for function `%s'", bs_func->name);
if (ffi_rettype != &ffi_type_void) {
ffi_ret = (void *)alloca(ffi_rettype->size);
}
else {
ffi_ret = NULL;
}
@try {
ffi_call(cif, FFI_FN(sym), ffi_ret, ffi_args);
}
@catch (id e) {
rb_objc_exc_raise(e);
}
resp = Qnil;
if (ffi_rettype != &ffi_type_void) {
rb_objc_ocval_to_rval(ffi_ret, bs_func->retval->type, &resp);
if (bs_func->retval->already_retained && !rb_objc_resourceful(resp))
CFMakeCollectable((void *)resp);
}
return resp;
}
VALUE
rb_objc_resolve_const_value(VALUE v, VALUE klass, ID id)
{
void *sym;
bs_element_constant_t *bs_const;
if (v == bs_const_magic_cookie) {
if (!st_lookup(bs_constants, (st_data_t)id, (st_data_t *)&bs_const))
rb_bug("unresolved bridgesupport constant `%s' not in cache",
rb_id2name(id));
sym = dlsym(RTLD_DEFAULT, bs_const->name);
if (sym == NULL)
rb_bug("cannot locate symbol for unresolved bridgesupport " \
"constant `%s'", bs_const->name);
rb_objc_ocval_to_rval(sym, bs_const->type, &v);
CFMutableDictionaryRef iv_dict = rb_class_ivar_dict(rb_cObject);
assert(iv_dict != NULL);
CFDictionarySetValue(iv_dict, (const void *)id, (const void *)v);
}
return v;
}
static bs_element_boxed_t *
rb_klass_get_bs_boxed(VALUE recv)
{
bs_element_boxed_t *bs_boxed;
VALUE type;
type = rb_ivar_get(recv, rb_ivar_type);
if (NIL_P(type))
rb_bug("cannot get boxed objc type of class `%s'",
rb_class2name(recv));
assert(TYPE(type) == T_STRING);
if (st_lookup(bs_boxeds, (st_data_t)StringValuePtr(type),
(st_data_t *)&bs_boxed)) {
rb_bs_boxed_assert_ffitype_ok(bs_boxed);
return bs_boxed;
}
return NULL;
}
static VALUE
rb_bs_struct_new(int argc, VALUE *argv, VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(recv);
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
VALUE *data;
unsigned i;
if (argc > 0 && argc != bs_struct->fields_count)
rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)",
argc, bs_struct->fields_count);
data = (VALUE *)xmalloc(bs_struct->fields_count * sizeof(VALUE));
for (i = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *bs_field =
(bs_element_struct_field_t *)&bs_struct->fields[i];
size_t fdata_size;
void *fdata;
VALUE fval;
fdata_size = rb_objc_octype_to_ffitype(bs_field->type)->size;
fdata = alloca(fdata_size);
if (i < argc) {
rb_objc_rval_to_ocval(argv[i], bs_field->type, fdata);
}
else {
memset(fdata, 0, fdata_size);
}
rb_objc_ocval_to_rval(fdata, bs_field->type, &fval);
GC_WB(&data[i], fval);
}
return Data_Wrap_Struct(recv, NULL, NULL, data);
}
static VALUE
rb_bs_struct_to_a(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
VALUE *data;
VALUE ary;
unsigned i;
Data_Get_Struct(recv, VALUE, data);
assert(data != NULL);
ary = rb_ary_new();
for (i = 0; i < bs_struct->fields_count; i++) {
rb_ary_push(ary, data[i]);
}
return ary;
}
static VALUE
rb_bs_boxed_is_equal(VALUE recv, VALUE other)
{
bs_element_boxed_t *bs_boxed;
if (recv == other)
return Qtrue;
if (rb_obj_is_kind_of(other, rb_cBoxed) == Qfalse)
return Qfalse;
bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
if (bs_boxed != rb_klass_get_bs_boxed(CLASS_OF(other)))
return Qfalse;
return CFEqual((CFTypeRef)recv, (CFTypeRef)other) ? Qtrue : Qfalse;
}
static VALUE
rb_bs_struct_dup(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
VALUE *data, *new_data;
int i;
static ID idDup = 0;
if (idDup == 0) {
idDup = rb_intern("dup");
}
Data_Get_Struct(recv, VALUE, data);
new_data = (VALUE *)xmalloc(bs_struct->fields_count * sizeof(VALUE));
for (i = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *bs_field =
(bs_element_struct_field_t *)&bs_struct->fields[i];
size_t fdata_size;
void *fdata;
VALUE fval;
fdata_size = rb_objc_octype_to_ffitype(bs_field->type)->size;
fdata = alloca(fdata_size);
rb_objc_rval_to_ocval(data[i], bs_field->type, fdata);
rb_objc_ocval_to_rval(fdata, bs_field->type, &fval);
GC_WB(&new_data[i], fval);
}
return Data_Wrap_Struct(CLASS_OF(recv), NULL, NULL, new_data);
}
static VALUE
rb_bs_struct_inspect(VALUE recv)
{
bs_element_boxed_t *bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(recv));
bs_element_struct_t *bs_struct = (bs_element_struct_t *)bs_boxed->value;
unsigned i;
VALUE str;
str = rb_str_new2("#<");
rb_str_cat2(str, rb_obj_classname(recv));
if (!bs_struct->opaque) {
VALUE *data;
Data_Get_Struct(recv, VALUE, data);
for (i = 0; i < bs_struct->fields_count; i++) {
rb_str_cat2(str, " ");
rb_str_cat2(str, bs_struct->fields[i].name);
rb_str_cat2(str, "=");
rb_str_append(str, rb_inspect(data[i]));
}
}
rb_str_cat2(str, ">");
return str;
}
static VALUE
rb_boxed_objc_type(VALUE recv)
{
char *type;
bs_element_boxed_t *bs_boxed;
bs_boxed = rb_klass_get_bs_boxed(recv);
type = bs_boxed->type == BS_ELEMENT_OPAQUE
? ((bs_element_opaque_t *)bs_boxed->value)->type
: ((bs_element_struct_t *)bs_boxed->value)->type;
return rb_str_new2(type);
}
static VALUE
rb_boxed_is_opaque(VALUE recv)
{
bs_element_boxed_t *bs_boxed;
bs_boxed = rb_klass_get_bs_boxed(recv);
if (bs_boxed->type == BS_ELEMENT_OPAQUE)
return Qtrue;
return ((bs_element_struct_t *)bs_boxed->value)->opaque ? Qtrue : Qfalse;
}
static VALUE
rb_boxed_fields(VALUE recv)
{
bs_element_boxed_t *bs_boxed;
VALUE ary;
unsigned i;
bs_boxed = rb_klass_get_bs_boxed(recv);
ary = rb_ary_new();
if (bs_boxed->type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct;
bs_struct = (bs_element_struct_t *)bs_boxed->value;
for (i = 0; i < bs_struct->fields_count; i++) {
rb_ary_push(ary, ID2SYM(rb_intern(bs_struct->fields[i].name)));
}
}
return ary;
}
static ffi_cif *struct_reader_cif = NULL;
static ffi_cif *struct_writer_cif = NULL;
struct rb_struct_accessor_context {
bs_element_struct_field_t *field;
int num;
};
static void
rb_struct_reader_closure_handler(ffi_cif *cif, void *resp, void **args,
void *userdata)
{
struct rb_struct_accessor_context *ctx;
VALUE recv, *data;
recv = *(VALUE *)args[0];
Data_Get_Struct(recv, VALUE, data);
assert(data != NULL);
ctx = (struct rb_struct_accessor_context *)userdata;
*(VALUE *)resp = data[ctx->num];
}
static void
rb_struct_writer_closure_handler(ffi_cif *cif, void *resp, void **args,
void *userdata)
{
struct rb_struct_accessor_context *ctx;
VALUE recv, value, *data, fval;
size_t fdata_size;
void *fdata;
recv = *(VALUE *)args[0];
value = *(VALUE *)args[1];
Data_Get_Struct(recv, VALUE, data);
assert(data != NULL);
ctx = (struct rb_struct_accessor_context *)userdata;
fdata_size = rb_objc_octype_to_ffitype(ctx->field->type)->size;
fdata = alloca(fdata_size);
rb_objc_rval_to_ocval(value, ctx->field->type, fdata);
rb_objc_ocval_to_rval(fdata, ctx->field->type, &fval);
GC_WB(&data[ctx->num], fval);
*(VALUE *)resp = fval;
}
static void
rb_struct_gen_accessors(VALUE klass, bs_element_struct_field_t *field, int num)
{
ffi_closure *closure;
struct rb_struct_accessor_context *ctx;
char buf[100];
ctx = (struct rb_struct_accessor_context *)
malloc(sizeof(struct rb_struct_accessor_context));
ctx->field = field;
ctx->num = num;
if (struct_reader_cif == NULL) {
ffi_type **args;
struct_reader_cif = (ffi_cif *)malloc(sizeof(ffi_cif));
args = (ffi_type **)malloc(sizeof(ffi_type *) * 1);
args[0] = &ffi_type_pointer;
if (ffi_prep_cif(struct_reader_cif, FFI_DEFAULT_ABI, 1,
&ffi_type_pointer, args) != FFI_OK) {
rb_fatal("can't prepare struct_reader_cif");
}
}
if ((closure = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0)) == (void *)-1) {
rb_fatal("can't allocate struct reader closure");
}
if (ffi_prep_closure(closure, struct_reader_cif,
rb_struct_reader_closure_handler, ctx) != FFI_OK) {
rb_fatal("can't prepare struct reader closure");
}
if (mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1) {
rb_fatal("can't mprotect struct reader closure");
}
rb_define_method(klass, field->name, (VALUE(*)(ANYARGS))closure, 0);
if (struct_writer_cif == NULL) {
ffi_type **args;
struct_writer_cif = (ffi_cif *)malloc(sizeof(ffi_cif));
args = (ffi_type **)malloc(sizeof(ffi_type *) * 2);
args[0] = &ffi_type_pointer;
args[1] = &ffi_type_pointer;
if (ffi_prep_cif(struct_writer_cif, FFI_DEFAULT_ABI, 2,
&ffi_type_pointer, args) != FFI_OK) {
rb_fatal("can't prepare struct_writer_cif");
}
}
if ((closure = mmap(NULL, sizeof(ffi_closure), PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE, -1, 0)) == (void *)-1) {
rb_fatal("can't allocate struct writer closure");
}
if (ffi_prep_closure(closure, struct_writer_cif,
rb_struct_writer_closure_handler, ctx) != FFI_OK) {
rb_fatal("can't prepare struct writer closure");
}
if (mprotect(closure, sizeof(closure), PROT_READ | PROT_EXEC) == -1) {
rb_fatal("can't mprotect struct writer closure");
}
snprintf(buf, sizeof buf, "%s=", field->name);
rb_define_method(klass, buf, (VALUE(*)(ANYARGS))closure, 1);
}
static void
setup_bs_boxed_type(bs_element_type_t type, void *value)
{
bs_element_boxed_t *bs_boxed;
VALUE klass;
struct __bs_boxed {
char *name;
char *type;
} *p;
ffi_type *bs_ffi_type;
p = (struct __bs_boxed *)value;
klass = rb_define_class(p->name, rb_cBoxed);
assert(!NIL_P(klass));
rb_ivar_set(klass, rb_ivar_type, rb_str_new2(p->type));
if (type == BS_ELEMENT_STRUCT) {
bs_element_struct_t *bs_struct = (bs_element_struct_t *)value;
int i;
/* Needs to be lazily created, because the type of some fields
* may not be registered yet.
*/
bs_ffi_type = NULL;
if (!bs_struct->opaque) {
for (i = 0; i < bs_struct->fields_count; i++) {
bs_element_struct_field_t *field = &bs_struct->fields[i];
rb_struct_gen_accessors(klass, field, i);
}
rb_define_method(klass, "to_a", rb_bs_struct_to_a, 0);
}
rb_define_singleton_method(klass, "new", rb_bs_struct_new, -1);
rb_define_method(klass, "dup", rb_bs_struct_dup, 0);
rb_define_alias(klass, "clone", "dup");
rb_define_method(klass, "inspect", rb_bs_struct_inspect, 0);
rb_define_alias(klass, "to_s", "inspect");
}
else {
rb_undef_alloc_func(klass);
rb_undef_method(CLASS_OF(klass), "new");
bs_ffi_type = &ffi_type_pointer;
}
rb_define_method(klass, "==", rb_bs_boxed_is_equal, 1);
bs_boxed = (bs_element_boxed_t *)malloc(sizeof(bs_element_boxed_t));
bs_boxed->type = type;
bs_boxed->value = value;
bs_boxed->klass = klass;
bs_boxed->ffi_type = bs_ffi_type;
st_insert(bs_boxeds, (st_data_t)p->type, (st_data_t)bs_boxed);
}
static inline ID
generate_const_name(char *name)
{
ID id;
if (islower(name[0])) {
name[0] = toupper(name[0]);
id = rb_intern(name);
name[0] = tolower(name[0]);
return id;
}
else {
return rb_intern(name);
}
}
static void
bs_parse_cb(bs_parser_t *parser, const char *path, bs_element_type_t type,
void *value, void *ctx)
{
bool do_not_free = false;
CFMutableDictionaryRef rb_cObject_dict = (CFMutableDictionaryRef)ctx;
switch (type) {
case BS_ELEMENT_ENUM:
{
bs_element_enum_t *bs_enum = (bs_element_enum_t *)value;
ID name = generate_const_name(bs_enum->name);
if (!CFDictionaryGetValueIfPresent(
(CFDictionaryRef)rb_cObject_dict, (const void *)name, NULL)) {
VALUE val = strchr(bs_enum->value, '.') != NULL
? rb_float_new(rb_cstr_to_dbl(bs_enum->value, 1))
: rb_cstr_to_inum(bs_enum->value, 10, 1);
CFDictionarySetValue(rb_cObject_dict, (const void *)name,
(const void *)val);
}
else {
rb_warning("bs: enum `%s' already defined", rb_id2name(name));
}
break;
}
case BS_ELEMENT_CONSTANT:
{
bs_element_constant_t *bs_const = (bs_element_constant_t *)value;
ID name = generate_const_name(bs_const->name);
if (!CFDictionaryGetValueIfPresent(
(CFDictionaryRef)rb_cObject_dict, (const void *)name, NULL)) {
st_insert(bs_constants, (st_data_t)name, (st_data_t)bs_const);
CFDictionarySetValue(rb_cObject_dict, (const void *)name,
(const void *)bs_const_magic_cookie);
do_not_free = true;
}
else {
rb_warning("bs: constant `%s' already defined",
rb_id2name(name));
}
break;
}
case BS_ELEMENT_STRING_CONSTANT:
{
bs_element_string_constant_t *bs_strconst =
(bs_element_string_constant_t *)value;
ID name = generate_const_name(bs_strconst->name);
if (!CFDictionaryGetValueIfPresent(
(CFDictionaryRef)rb_cObject_dict, (const void *)name, NULL)) {
VALUE val;
if (bs_strconst->nsstring) {
CFStringRef string;
string = CFStringCreateWithCString(
NULL, bs_strconst->value, kCFStringEncodingUTF8);
val = (VALUE)string;
}
else {
val = rb_str_new2(bs_strconst->value);
}
CFDictionarySetValue(rb_cObject_dict, (const void *)name,
(const void *)val);
}
else {
rb_warning("bs: string constant `%s' already defined",
rb_id2name(name));
}
break;
}
case BS_ELEMENT_FUNCTION:
{
bs_element_function_t *bs_func = (bs_element_function_t *)value;
ID name = rb_intern(bs_func->name);
if (!st_lookup(bs_functions, (st_data_t)name, NULL)) {
st_insert(bs_functions, (st_data_t)name, (st_data_t)bs_func);
do_not_free = true;
}
else {
rb_warning("bs: function `%s' already defined", bs_func->name);
}
break;
}
case BS_ELEMENT_FUNCTION_ALIAS:
{
bs_element_function_alias_t *bs_func_alias =
(bs_element_function_alias_t *)value;
bs_element_function_t *bs_func_original;
if (st_lookup(bs_functions,
(st_data_t)rb_intern(bs_func_alias->original),
(st_data_t *)&bs_func_original)) {
st_insert(bs_functions,
(st_data_t)rb_intern(bs_func_alias->name),
(st_data_t)bs_func_original);
}
else {
rb_raise(rb_eRuntimeError,
"cannot alias '%s' to '%s' because it doesn't exist",
bs_func_alias->name, bs_func_alias->original);
}
break;
}
case BS_ELEMENT_OPAQUE:
case BS_ELEMENT_STRUCT:
{
setup_bs_boxed_type(type, value);
do_not_free = true;
break;
}
case BS_ELEMENT_CLASS:
{
bs_element_class_t *bs_class = (bs_element_class_t *)value;
bs_element_indexed_class_t *bs_class_new;
unsigned i;
if (!st_lookup(bs_classes, (st_data_t)bs_class->name,
(st_data_t *)&bs_class_new)) {
bs_class_new = (bs_element_indexed_class_t *)
malloc(sizeof(bs_element_indexed_class_t));
bs_class_new->name = bs_class->name;
bs_class_new->cmethods = bs_class_new->imethods = NULL;
st_insert(bs_classes, (st_data_t)bs_class_new->name,
(st_data_t)bs_class_new);
}
#define INDEX_METHODS(table, ary, len) \
do { \
if (len > 0) { \
if (table == NULL) { \
table = st_init_numtable(); \
rb_objc_retain(table); \
} \
for (i = 0; i < len; i++) { \
bs_element_method_t *method = &ary[i]; \
st_insert(table, (st_data_t)method->name, (st_data_t)method); \
} \
} \
} \
while (0)
INDEX_METHODS(bs_class_new->cmethods, bs_class->class_methods,
bs_class->class_methods_count);
INDEX_METHODS(bs_class_new->imethods, bs_class->instance_methods,
bs_class->instance_methods_count);
#undef INDEX_METHODS
free(bs_class);
do_not_free = true;
break;
}
case BS_ELEMENT_INFORMAL_PROTOCOL_METHOD:
{
bs_element_informal_protocol_method_t *bs_inf_prot_method =
(bs_element_informal_protocol_method_t *)value;
struct st_table *t = bs_inf_prot_method->class_method
? bs_inf_prot_cmethods
: bs_inf_prot_imethods;
st_insert(t, (st_data_t)bs_inf_prot_method->name,
(st_data_t)bs_inf_prot_method->type);
free(bs_inf_prot_method->protocol_name);
free(bs_inf_prot_method);
do_not_free = true;
break;
}
case BS_ELEMENT_CFTYPE:
{
bs_element_cftype_t *bs_cftype = (bs_element_cftype_t *)value;
st_insert(bs_cftypes, (st_data_t)bs_cftype->type,
(st_data_t)bs_cftype);
do_not_free = true;
break;
}
}
if (!do_not_free)
bs_element_free(type, value);
}
extern VALUE enable_method_added;
static bs_parser_t *bs_parser = NULL;
static void
rb_objc_load_bridge_support(const char *path, const char *framework_path,
int options)
{
char *error;
bool ok;
CFMutableDictionaryRef rb_cObject_dict;
if (bs_parser == NULL) {
bs_parser = bs_parser_new();
}
rb_cObject_dict = rb_class_ivar_dict(rb_cObject);
assert(rb_cObject_dict != NULL);
enable_method_added = Qfalse;
ok = bs_parser_parse(bs_parser, path, framework_path, options,
bs_parse_cb, rb_cObject_dict, &error);
enable_method_added = Qtrue;
if (!ok) {
rb_raise(rb_eRuntimeError, "%s", error);
}
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 1060
/* XXX we should introduce the possibility to write prelude scripts per
* frameworks where this kind of changes could be located.
*/
#if defined(__LP64__)
static bool R6399046_fixed = false;
/* XXX work around for <rdar://problem/6399046> NSNotFound 64-bit value is incorrect */
if (!R6399046_fixed) {
ID nsnotfound = rb_intern("NSNotFound");
VALUE val =
(VALUE)CFDictionaryGetValue(rb_cObject_dict, (void *)nsnotfound);
if ((VALUE)val == INT2FIX(-1)) {
CFDictionarySetValue(rb_cObject_dict,
(const void *)nsnotfound,
(const void *)ULL2NUM(NSNotFound));
R6399046_fixed = true;
DLOG("XXX", "applied work-around for rdar://problem/6399046");
}
}
#endif
static bool R6401816_fixed = false;
/* XXX work around for <rdar://problem/6401816> -[NSObject performSelector:withObject:] has wrong sel_of_type attributes*/
if (!R6401816_fixed) {
bs_element_method_t *bs_method =
rb_bs_find_method((Class)rb_cNSObject,
@selector(performSelector:withObject:));
if (bs_method != NULL) {
bs_element_arg_t *arg = bs_method->args;
while (arg != NULL) {
if (arg->index == 0
&& arg->sel_of_type != NULL
&& arg->sel_of_type[0] != '@') {
arg->sel_of_type[0] = '@';
R6401816_fixed = true;
DLOG("XXX", "applied work-around for rdar://problem/6401816");
break;
}
arg++;
}
}
}
#endif
}
static VALUE
rb_objc_load_bs(VALUE recv, VALUE path)
{
rb_objc_load_bridge_support(StringValuePtr(path), NULL, 0);
return recv;
}
static void
rb_objc_search_and_load_bridge_support(const char *framework_path)
{
char path[PATH_MAX];
if (bs_find_path(framework_path, path, sizeof path)) {
rb_objc_load_bridge_support(path, framework_path,
BS_PARSE_OPTIONS_LOAD_DYLIBS);
}
}
static void
reload_protocols(void)
{
Protocol **prots;
unsigned int i, prots_count;
prots = objc_copyProtocolList(&prots_count);
for (i = 0; i < prots_count; i++) {
Protocol *p;
struct objc_method_description *methods;
unsigned j, methods_count;
p = prots[i];
#define REGISTER_MDESCS(t) \
do { \
for (j = 0; j < methods_count; j++) { \
if (methods[j].name == sel_ignored) \
continue; \
st_insert(t, (st_data_t)methods[j].name, \
(st_data_t)strdup(methods[j].types)); \
} \
free(methods); \
} \
while (0)
methods = protocol_copyMethodDescriptionList(p, true, true, &methods_count);
REGISTER_MDESCS(bs_inf_prot_imethods);
methods = protocol_copyMethodDescriptionList(p, false, true, &methods_count);
REGISTER_MDESCS(bs_inf_prot_imethods);
methods = protocol_copyMethodDescriptionList(p, true, false, &methods_count);
REGISTER_MDESCS(bs_inf_prot_cmethods);
methods = protocol_copyMethodDescriptionList(p, false, false, &methods_count);
REGISTER_MDESCS(bs_inf_prot_cmethods);
#undef REGISTER_MDESCS
}
free(prots);
}
static void
reload_class_constants(void)
{
static int class_count = 0;
int i, count;
Class *buf;
count = objc_getClassList(NULL, 0);
if (count == class_count)
return;
buf = (Class *)alloca(sizeof(Class) * count);
objc_getClassList(buf, count);
for (i = 0; i < count; i++) {
const char *name = class_getName(buf[i]);
if (name[0] != '_') {
ID id = rb_intern(name);
if (!rb_const_defined(rb_cObject, id))
rb_const_set(rb_cObject, id, (VALUE)buf[i]);
}
}
class_count = count;
}
VALUE
rb_require_framework(int argc, VALUE *argv, VALUE recv)
{
VALUE framework;
VALUE search_network;
const char *cstr;
NSFileManager *fileManager;
NSString *path;
NSBundle *bundle;
NSError *error;
rb_scan_args(argc, argv, "11", &framework, &search_network);
Check_Type(framework, T_STRING);
cstr = RSTRING_PTR(framework);
fileManager = [NSFileManager defaultManager];
path = [fileManager stringWithFileSystemRepresentation:cstr
length:strlen(cstr)];
if (![fileManager fileExistsAtPath:path]) {
/* framework name is given */
NSSearchPathDomainMask pathDomainMask;
NSString *frameworkName;
NSArray *dirs;
NSUInteger i, count;
cstr = NULL;
#define FIND_LOAD_PATH_IN_LIBRARY(dir) \
do { \
path = [[dir stringByAppendingPathComponent:@"Frameworks"] \
stringByAppendingPathComponent:frameworkName]; \
if ([fileManager fileExistsAtPath:path]) \
goto success; \
path = [[dir stringByAppendingPathComponent:@"PrivateFrameworks"] \
stringByAppendingPathComponent:frameworkName]; \
if ([fileManager fileExistsAtPath:path]) \
goto success; \
} \
while(0)
pathDomainMask = RTEST(search_network)
? NSAllDomainsMask
: NSUserDomainMask | NSLocalDomainMask | NSSystemDomainMask;
frameworkName = [path stringByAppendingPathExtension:@"framework"];
path = [[[[NSBundle mainBundle] bundlePath]
stringByAppendingPathComponent:@"Contents/Frameworks"]
stringByAppendingPathComponent:frameworkName];
if ([fileManager fileExistsAtPath:path])
goto success;
dirs = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
pathDomainMask, YES);
for (i = 0, count = [dirs count]; i < count; i++) {
NSString *dir = [dirs objectAtIndex:i];
FIND_LOAD_PATH_IN_LIBRARY(dir);
}
dirs = NSSearchPathForDirectoriesInDomains(NSDeveloperDirectory,
pathDomainMask, YES);
for (i = 0, count = [dirs count]; i < count; i++) {
NSString *dir = [[dirs objectAtIndex:i]
stringByAppendingPathComponent:@"Library"];
FIND_LOAD_PATH_IN_LIBRARY(dir);
}
#undef FIND_LOAD_PATH_IN_LIBRARY
rb_raise(rb_eRuntimeError, "framework `%s' not found",
RSTRING_PTR(framework));
}
success:
if (cstr == NULL)
cstr = [path fileSystemRepresentation];
bundle = [NSBundle bundleWithPath:path];
if (bundle == nil)
rb_raise(rb_eRuntimeError,
"framework at path `%s' cannot be located",
cstr);
if ([bundle isLoaded])
return Qfalse;
if (![bundle loadAndReturnError:&error]) {
rb_raise(rb_eRuntimeError,
"framework at path `%s' cannot be loaded: %s",
cstr,
[[error description] UTF8String]);
}
rb_objc_search_and_load_bridge_support(cstr);
reload_class_constants();
reload_protocols();
return Qtrue;
}
static const char *
imp_rb_boxed_objCType(void *rcv, SEL sel)
{
VALUE klass, type;
klass = CLASS_OF(rcv);
type = rb_boxed_objc_type(klass);
return StringValuePtr(type);
}
static void
imp_rb_boxed_getValue(void *rcv, SEL sel, void *buffer)
{
bs_element_boxed_t *bs_boxed;
bs_boxed = rb_klass_get_bs_boxed(CLASS_OF(rcv));
assert(rb_objc_rval_copy_boxed_data((VALUE)rcv, bs_boxed, buffer));
}
static void
rb_install_boxed_primitives(void)
{
Class klass;
/* Boxed */
klass = (Class)rb_cBoxed;
rb_objc_install_method(klass, @selector(objCType),
(IMP)imp_rb_boxed_objCType);
rb_objc_install_method(klass, @selector(getValue:),
(IMP)imp_rb_boxed_getValue);
}
static const char *
resources_path(char *path, size_t len)
{
CFBundleRef bundle;
CFURLRef url;
bundle = CFBundleGetMainBundle();
assert(bundle != NULL);
url = CFBundleCopyResourcesDirectoryURL(bundle);
*path = '-';
*(path+1) = 'I';
assert(CFURLGetFileSystemRepresentation(
url, true, (UInt8 *)&path[2], len - 2));
CFRelease(url);
return path;
}
int
macruby_main(const char *path, int argc, char **argv)
{
char **newargv;
char *p1, *p2;
int n, i;
newargv = (char **)malloc(sizeof(char *) * (argc + 2));
for (i = n = 0; i < argc; i++) {
if (!strncmp(argv[i], "-psn_", 5) == 0)
newargv[n++] = argv[i];
}
p1 = (char *)malloc(PATH_MAX);
newargv[n++] = (char *)resources_path(p1, PATH_MAX);
p2 = (char *)malloc(PATH_MAX);
snprintf(p2, PATH_MAX, "%s/%s", (path[0] != '/') ? &p1[2] : "", path);
newargv[n++] = p2;
argv = newargv;
argc = n;
ruby_sysinit(&argc, &argv);
{
void *tree;
RUBY_INIT_STACK;
ruby_init();
tree = ruby_options(argc, argv);
free(newargv);
free(p1);
free(p2);
return ruby_run_node(tree);
}
}
static void *
rb_objc_kvo_setter_imp(void *recv, SEL sel, void *value)
{
const char *selname;
char buf[128];
size_t s;
selname = sel_getName(sel);
buf[0] = '@';
buf[1] = tolower(selname[3]);
s = strlcpy(&buf[2], &selname[4], sizeof buf - 2);
buf[s + 1] = '\0';
rb_ivar_set((VALUE)recv, rb_intern(buf), value == NULL ? Qnil : OC2RB(value));
return NULL; /* we explicitely return NULL because otherwise a special constant may stay on the stack and be returned to Objective-C, and do some very nasty crap, especially if called via -[performSelector:]. */
}
/*
Defines an attribute writer method which conforms to Key-Value Coding.
(See http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/KeyValueCoding.html)
attr_accessor :foo
Will create the normal accessor methods, plus <tt>setFoo</tt>
TODO: Does not handle the case were the user might override #foo=
*/
void
rb_objc_define_kvo_setter(VALUE klass, ID mid)
{
char buf[100];
const char *mid_name;
buf[0] = 's'; buf[1] = 'e'; buf[2] = 't';
mid_name = rb_id2name(mid);
buf[3] = toupper(mid_name[0]);
buf[4] = '\0';
strlcat(buf, &mid_name[1], sizeof buf);
strlcat(buf, ":", sizeof buf);
if (!class_addMethod((Class)klass, sel_registerName(buf),
(IMP)rb_objc_kvo_setter_imp, "v@:@")) {
rb_warn("can't register `%s' as an KVO setter (method `%s')",
mid_name, buf);
}
}
VALUE
rb_mod_objc_ib_outlet(int argc, VALUE *argv, VALUE recv)
{
int i;
rb_warn("ib_outlet has been deprecated, please use attr_writer instead");
for (i = 0; i < argc; i++) {
VALUE sym = argv[i];
Check_Type(sym, T_SYMBOL);
rb_objc_define_kvo_setter(recv, SYM2ID(sym));
}
return recv;
}
#define FLAGS_AS_ASSOCIATIVE_REF 1
static CFMutableDictionaryRef __obj_flags;
long
rb_objc_flag_get_mask(const void *obj)
{
#if FLAGS_AS_ASSOCIATIVE_REF
return (long)rb_objc_get_associative_ref((void *)obj, &__obj_flags);
#else
if (__obj_flags == NULL)
return 0;
return (long)CFDictionaryGetValue(__obj_flags, obj);
#endif
}
bool
rb_objc_flag_check(const void *obj, int flag)
{
long v;
v = rb_objc_flag_get_mask(obj);
if (v == 0)
return false;
return (v & flag) == flag;
}
void
rb_objc_flag_set(const void *obj, int flag, bool val)
{
#if FLAGS_AS_ASSOCIATIVE_REF
long v = (long)rb_objc_get_associative_ref((void *)obj, &__obj_flags);
if (val) {
v |= flag;
}
else {
v ^= flag;
}
rb_objc_set_associative_ref((void *)obj, &__obj_flags, (void *)v);
#else
long v;
if (__obj_flags == NULL) {
__obj_flags = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
}
v = (long)CFDictionaryGetValue(__obj_flags, obj);
if (val) {
v |= flag;
}
else {
v ^= flag;
}
CFDictionarySetValue(__obj_flags, obj, (void *)v);
#endif
}
long
rb_objc_remove_flags(const void *obj)
{
#if FLAGS_AS_ASSOCIATIVE_REF
long flag = (long)rb_objc_get_associative_ref((void *)obj, &__obj_flags);
//rb_objc_set_associative_ref((void *)obj, &__obj_flags, (void *)0);
return flag;
#else
long flag;
if (CFDictionaryGetValueIfPresent(__obj_flags, obj,
(const void **)&flag)) {
CFDictionaryRemoveValue(__obj_flags, obj);
return flag;
}
return 0;
#endif
}
static void
rb_objc_get_types_for_format_str(char **octypes, const int len, VALUE *args,
const char *format_str, char **new_fmt)
{
unsigned i, j, format_str_len;
format_str_len = strlen(format_str);
i = j = 0;
while (i < format_str_len) {
bool sharp_modifier = false;
bool star_modifier = false;
if (format_str[i++] != '%')
continue;
if (i < format_str_len && format_str[i] == '%') {
i++;
continue;
}
while (i < format_str_len) {
char *type = NULL;
switch (format_str[i]) {
case '#':
sharp_modifier = true;
break;
case '*':
star_modifier = true;
type = "i"; // C_INT;
break;
case 'd':
case 'i':
case 'o':
case 'u':
case 'x':
case 'X':
type = "i"; // _C_INT;
break;
case 'c':
case 'C':
type = "c"; // _C_CHR;
break;
case 'D':
case 'O':
case 'U':
type = "l"; // _C_LNG;
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
type = "d"; // _C_DBL;
break;
case 's':
case 'S':
{
if (i - 1 > 0) {
long k = i - 1;
while (k > 0 && format_str[k] == '0')
k--;
if (k < i && format_str[k] == '.')
args[j] = (VALUE)CFSTR("");
}
type = "*"; // _C_CHARPTR;
}
break;
case 'p':
type = "^"; // _C_PTR;
break;
case '@':
type = "@"; // _C_ID;
break;
case 'B':
case 'b':
{
VALUE arg = args[j];
switch (TYPE(arg)) {
case T_STRING:
arg = rb_str_to_inum(arg, 0, Qtrue);
break;
}
arg = rb_big2str(arg, 2);
if (sharp_modifier) {
VALUE prefix = format_str[i] == 'B'
? (VALUE)CFSTR("0B") : (VALUE)CFSTR("0b");
rb_str_update(arg, 0, 0, prefix);
}
if (*new_fmt == NULL)
*new_fmt = strdup(format_str);
(*new_fmt)[i] = '@';
args[j] = arg;
type = "@";
}
break;
}
i++;
if (type != NULL) {
if (len == 0 || j >= len)
rb_raise(rb_eArgError,
"Too much tokens in the format string `%s' "\
"for the given %d argument(s)", format_str, len);
octypes[j++] = type;
if (!star_modifier)
break;
}
}
}
for (; j < len; j++)
octypes[j] = "@"; // _C_ID;
}
VALUE
rb_str_format(int argc, const VALUE *argv, VALUE fmt)
{
char **types;
ffi_type *ffi_rettype, **ffi_argtypes;
void *ffi_ret, **ffi_args;
ffi_cif *cif;
int i;
void *null;
char *new_fmt;
if (argc == 0)
return fmt;
types = (char **)alloca(sizeof(char *) * argc);
ffi_argtypes = (ffi_type **)alloca(sizeof(ffi_type *) * (argc + 4));
ffi_args = (void **)alloca(sizeof(void *) * (argc + 4));
null = NULL;
new_fmt = NULL;
rb_objc_get_types_for_format_str(types, argc, (VALUE *)argv,
RSTRING_PTR(fmt), &new_fmt);
if (new_fmt != NULL) {
fmt = (VALUE)CFStringCreateWithCString(NULL, new_fmt,
kCFStringEncodingUTF8);
xfree(new_fmt);
CFMakeCollectable((void *)fmt);
}
for (i = 0; i < argc; i++) {
ffi_argtypes[i + 3] = rb_objc_octype_to_ffitype(types[i]);
ffi_args[i + 3] = (void *)alloca(ffi_argtypes[i + 3]->size);
rb_objc_rval_to_ocval(argv[i], types[i], ffi_args[i + 3]);
}
ffi_argtypes[0] = &ffi_type_pointer;
ffi_args[0] = &null;
ffi_argtypes[1] = &ffi_type_pointer;
ffi_args[1] = &null;
ffi_argtypes[2] = &ffi_type_pointer;
ffi_args[2] = &fmt;
ffi_argtypes[argc + 4] = NULL;
ffi_args[argc + 4] = NULL;
ffi_rettype = &ffi_type_pointer;
cif = (ffi_cif *)alloca(sizeof(ffi_cif));
if (ffi_prep_cif(cif, FFI_DEFAULT_ABI, argc + 3, ffi_rettype, ffi_argtypes)
!= FFI_OK)
rb_fatal("can't prepare cif for CFStringCreateWithFormat");
ffi_ret = NULL;
ffi_call(cif, FFI_FN(CFStringCreateWithFormat), &ffi_ret, ffi_args);
if (ffi_ret != NULL) {
CFMakeCollectable((CFTypeRef)ffi_ret);
return (VALUE)ffi_ret;
}
return Qnil;
}
extern bool __CFStringIsMutable(void *);
extern bool _CFArrayIsMutable(void *);
extern bool _CFDictionaryIsMutable(void *);
bool
rb_objc_is_immutable(VALUE v)
{
switch(TYPE(v)) {
case T_STRING:
return !__CFStringIsMutable((void *)v);
case T_ARRAY:
return !_CFArrayIsMutable((void *)v);
case T_HASH:
return !_CFDictionaryIsMutable((void *)v);
}
return false;
}
static void
timer_cb(CFRunLoopTimerRef timer, void *ctx)
{
RUBY_VM_CHECK_INTS();
}
static IMP old_imp_isaForAutonotifying;
static Class
rb_obj_imp_isaForAutonotifying(void *rcv, SEL sel)
{
Class ret;
long ret_version;
#define KVO_CHECK_DONE 0x100000
ret = ((Class (*)(void *, SEL)) old_imp_isaForAutonotifying)(rcv, sel);
if (ret != NULL && ((ret_version = RCLASS_VERSION(ret)) & KVO_CHECK_DONE) == 0) {
const char *name = class_getName(ret);
if (strncmp(name, "NSKVONotifying_", 15) == 0) {
Class ret_orig;
name += 15;
ret_orig = objc_getClass(name);
if (ret_orig != NULL && RCLASS_VERSION(ret_orig) & RCLASS_IS_OBJECT_SUBCLASS) {
DLOG("XXX", "marking KVO generated klass %p (%s) as RObject", ret, class_getName(ret));
ret_version |= RCLASS_IS_OBJECT_SUBCLASS;
}
}
ret_version |= KVO_CHECK_DONE;
RCLASS_SET_VERSION(ret, ret_version);
}
return ret;
}
void
Init_ObjC(void)
{
rb_objc_retain(bs_constants = st_init_numtable());
rb_objc_retain(bs_functions = st_init_numtable());
rb_objc_retain(bs_boxeds = st_init_strtable());
rb_objc_retain(bs_classes = st_init_strtable());
rb_objc_retain(bs_inf_prot_cmethods = st_init_numtable());
rb_objc_retain(bs_inf_prot_imethods = st_init_numtable());
rb_objc_retain(bs_cftypes = st_init_strtable());
rb_objc_retain((const void *)(
bs_const_magic_cookie = rb_str_new2("bs_const_magic_cookie")));
rb_cBoxed = rb_define_class("Boxed", (VALUE)objc_getClass("NSValue"));
RCLASS_SET_VERSION_FLAG(rb_cBoxed, RCLASS_IS_OBJECT_SUBCLASS);
rb_define_singleton_method(rb_cBoxed, "type", rb_boxed_objc_type, 0);
rb_define_singleton_method(rb_cBoxed, "opaque?", rb_boxed_is_opaque, 0);
rb_define_singleton_method(rb_cBoxed, "fields", rb_boxed_fields, 0);
rb_install_boxed_primitives();
rb_cPointer = rb_define_class("Pointer", rb_cObject);
rb_undef_alloc_func(rb_cPointer);
rb_define_singleton_method(rb_cPointer, "new_with_type", rb_pointer_new_with_type, 1);
rb_define_method(rb_cPointer, "assign", rb_pointer_assign, 1);
rb_define_method(rb_cPointer, "[]", rb_pointer_aref, 1);
rb_ivar_type = rb_intern("@__objc_type__");
rb_define_global_function("load_bridge_support_file", rb_objc_load_bs, 1);
{
/* XXX timer_cb should acquires the GL or not be triggered when
* MacRuby.framework is loaded in an existing Objective-C app.
*/
CFRunLoopTimerRef timer;
timer = CFRunLoopTimerCreate(NULL,
CFAbsoluteTimeGetCurrent(), 0.1, 0, 0, timer_cb, NULL);
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopDefaultMode);
}
rb_define_method(rb_cNSObject, "__super_objc_send__", rb_super_objc_send, -1);
Method m = class_getInstanceMethod(objc_getClass("NSKeyValueUnnestedProperty"), sel_registerName("isaForAutonotifying"));
assert(m != NULL);
old_imp_isaForAutonotifying = method_getImplementation(m);
method_setImplementation(m, (IMP)rb_obj_imp_isaForAutonotifying);
{
VALUE klass;
NODE *node, *body;
void *closure;
klass = rb_singleton_class(rb_cNSObject);
node = NEW_CFUNC(rb_class_new_instance, -1);
body = NEW_FBODY(NEW_METHOD(node, klass, NOEX_PUBLIC), 0);
closure = rb_ruby_to_objc_closure("@@:@", 1, body->nd_body);
assert(class_addMethod((Class)klass, @selector(new:), (IMP)closure, "@@:@"));
}
_objc_msgForward_addr = &_objc_msgForward;
}
// for debug in gdb
int __rb_type(VALUE v) { return TYPE(v); }
int __rb_native(VALUE v) { return NATIVE(v); }
@interface Protocol
@end
@implementation Protocol (MRFindProtocol)
+(id)protocolWithName:(NSString *)name
{
return (id)objc_getProtocol([name UTF8String]);
}
@end
extern int ruby_initialized; /* eval.c */
@implementation MacRuby
+ (MacRuby *)sharedRuntime
{
static MacRuby *runtime = nil;
if (runtime == nil) {
runtime = [[MacRuby alloc] init];
if (ruby_initialized == 0) {
int argc = 0;
char **argv = NULL;
ruby_sysinit(&argc, &argv);
RUBY_INIT_STACK;
ruby_init();
}
}
return runtime;
}
+ (MacRuby *)runtimeAttachedToProcessIdentifier:(pid_t)pid
{
[NSException raise:NSGenericException format:@"not implemented yet"];
return nil;
}
static void
rb_raise_ruby_exc_in_objc(VALUE ex)
{
VALUE ex_name, ex_message, ex_backtrace;
NSException *ocex;
static ID name_id = 0;
static ID message_id = 0;
static ID backtrace_id = 0;
if (name_id == 0) {
name_id = rb_intern("name");
message_id = rb_intern("message");
backtrace_id = rb_intern("backtrace");
}
ex_name = rb_funcall(CLASS_OF(ex), name_id, 0);
ex_message = rb_funcall(ex, message_id, 0);
ex_backtrace = rb_funcall(ex, backtrace_id, 0);
ocex = [NSException exceptionWithName:(id)ex_name reason:(id)ex_message
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
(id)ex, @"object",
(id)ex_backtrace, @"backtrace",
NULL]];
[ocex raise];
}
static VALUE
evaluateString_safe(VALUE context)
{
VALUE *argv = (VALUE *)context;
const int argc = NIL_P(argv[2]) ? 2 : 3;
return rb_f_eval(argc, argv, rb_vm_top_self());
}
static VALUE
evaluateString_rescue(void)
{
rb_raise_ruby_exc_in_objc(rb_errinfo());
return Qnil; /* not reached */
}
static id
__evaluate_string_at_path(VALUE string, VALUE path)
{
VALUE ret;
VALUE args[3];
args[0] = string;
args[1] = Qnil;
args[2] = path;
ret = rb_rescue2(evaluateString_safe, (VALUE)args,
evaluateString_rescue, Qnil,
rb_eException, (VALUE)0);
return RB2OC(ret);
}
- (id)evaluateString:(NSString *)expression
{
return __evaluate_string_at_path((VALUE)expression, Qnil);
}
- (id)evaluateFileAtPath:(NSString *)path
{
VALUE expression = (VALUE)[NSString stringWithContentsOfFile:path usedEncoding:nil error:nil];
return __evaluate_string_at_path(expression, (VALUE)path);
}
- (id)evaluateFileAtURL:(NSURL *)URL
{
if (![URL isFileURL]) {
[NSException raise:NSInvalidArgumentException format:@"given URL is not a file URL"];
}
return [self evaluateFileAtPath:[URL relativePath]];
}
- (void)loadBridgeSupportFileAtPath:(NSString *)path
{
rb_objc_load_bridge_support([path fileSystemRepresentation], NULL, 0);
}
- (void)loadBridgeSupportFileAtURL:(NSURL *)URL
{
if (![URL isFileURL]) {
[NSException raise:NSInvalidArgumentException format:@"given URL is not a file URL"];
}
[self loadBridgeSupportFileAtPath:[URL relativePath]];
}
@end
@implementation NSObject (MacRubyAdditions)
- (id)performRubySelector:(SEL)sel
{
return [self performRubySelector:sel withArguments:NULL];
}
struct performRuby_context
{
VALUE rcv;
ID mid;
int argc;
VALUE *argv;
NODE *node;
};
static VALUE
performRuby_safe(VALUE arg)
{
struct performRuby_context *ud = (struct performRuby_context *)arg;
return rb_vm_call(GET_THREAD(), *(VALUE *)ud->rcv, ud->rcv, ud->mid, Qnil,
ud->argc, ud->argv, ud->node, 0);
}
static VALUE
performRuby_rescue(VALUE arg)
{
if (arg != 0) {
ruby_current_thread = (rb_thread_t *)arg;
}
rb_raise_ruby_exc_in_objc(rb_errinfo());
return Qnil; /* not reached */
}
- (id)performRubySelector:(SEL)sel withArguments:(id *)argv count:(int)argc
{
const bool need_protection = GET_THREAD()->thread_id != pthread_self();
NODE *node;
IMP imp;
VALUE *rargs, ret;
ID mid;
imp = NULL;
node = rb_objc_method_node2(*(VALUE *)self, sel, &imp);
if (node == NULL) {
if (imp != NULL) {
[NSException raise:NSInvalidArgumentException format:
@"-[%@ %s] is not a pure Ruby method", self, (char *)sel];
}
else {
[NSException raise:NSInvalidArgumentException format:
@"receiver %@ does not respond to %s", (char *)sel];
}
}
if (argc == 0) {
rargs = NULL;
}
else {
int i;
rargs = (VALUE *)alloca(sizeof(VALUE) * argc);
for (i = 0; i < argc; i++) {
rargs[i] = OC2RB(argv[i]);
}
}
rb_thread_t *th, *old = NULL;
if (need_protection) {
th = rb_thread_wrap_existing_native_thread(pthread_self());
native_mutex_lock(&GET_THREAD()->vm->global_interpreter_lock);
old = ruby_current_thread;
ruby_current_thread = th;
}
mid = rb_intern((char *)sel);
struct performRuby_context ud;
ud.rcv = (VALUE)self;
ud.mid = mid;
ud.argc = argc;
ud.argv = rargs;
ud.node = node->nd_body;
ret = rb_rescue2(performRuby_safe, (VALUE)&ud,
performRuby_rescue, (VALUE)old,
rb_eException, (VALUE)0);
if (need_protection) {
native_mutex_unlock(&GET_THREAD()->vm->global_interpreter_lock);
ruby_current_thread = old;
}
return RB2OC(ret);
}
- (id)performRubySelector:(SEL)sel withArguments:firstArg, ...
{
va_list args;
int argc;
id *argv;
if (firstArg != nil) {
int i;
argc = 1;
va_start(args, firstArg);
while (va_arg(args, id)) {
argc++;
}
va_end(args);
argv = alloca(sizeof(id) * argc);
va_start(args, firstArg);
argv[0] = firstArg;
for (i = 1; i < argc; i++) {
argv[i] = va_arg(args, id);
}
va_end(args);
}
else {
argc = 0;
argv = NULL;
}
return [self performRubySelector:sel withArguments:argv count:argc];
}
@end
Jump to Line
Something went wrong with that request. Please try again.