1/* 2 * Copyright (c) 2006-2008, The RubyCocoa Project. 3 * Copyright (c) 2001-2006, FUJIMOTO Hisakuni. 4 * All Rights Reserved. 5 * 6 * RubyCocoa is free software, covered under either the Ruby's license or the 7 * LGPL. See the COPYRIGHT file for more information. 8 */ 9 10#import <Cocoa/Cocoa.h> 11#import <stdarg.h> 12#import <pthread.h> 13#import "OverrideMixin.h" 14#import "RBObject.h" 15#import "RBSlaveObject.h" 16#import "internal_macros.h" 17#import "RBClassUtils.h" 18#import "ocdata_conv.h" 19#import "BridgeSupport.h" 20#import "st.h" 21#import <objc/objc-runtime.h> 22#import "mdl_osxobjc.h" 23#import "objc_compat.h" 24 25#define OVMIX_LOG(fmt, args...) DLOG("OVMIX", fmt, ##args) 26 27static SEL super_selector(SEL a_sel) 28{ 29 char selName[1024]; 30 31 snprintf (selName, sizeof selName, "super:%s", sel_getName(a_sel)); 32 return sel_registerName(selName); 33} 34 35static IMP super_imp(id rcv, SEL a_sel, IMP origin_imp) 36{ 37 IMP ret = NULL; 38 Class klass = [rcv class]; 39 40 while ((klass = class_getSuperclass(klass)) != NULL) { 41 ret = [klass instanceMethodForSelector: a_sel]; 42 if (ret && ret != origin_imp) 43 return ret; 44 } 45 return NULL; 46} 47 48static inline id slave_obj_new(id rcv) 49{ 50 return [[RBObject alloc] initWithClass: [rcv class] masterObject: rcv]; 51} 52 53/** 54 * accessor for instance variables 55 **/ 56 57static inline void set_slave(id rcv, id slave) 58{ 59 object_setInstanceVariable(rcv, "m_slave", slave); 60} 61 62static inline id _get_slave(id rcv) 63{ 64 id ret; 65 object_getInstanceVariable(rcv, "m_slave", (void*)(&ret)); 66 return ret; 67} 68 69// FIXME: for now this is safe, but this should ultimately move as an ivar 70// of the receiver 71static BOOL __slave_just_created = NO; 72 73static inline id get_slave(id rcv) 74{ 75 id slave = _get_slave(rcv); 76 if (slave == nil) { 77 slave = slave_obj_new(rcv); 78 set_slave(rcv, slave); 79 __slave_just_created = YES; 80 } 81 else { 82 __slave_just_created = NO; 83 } 84 return slave; 85} 86 87void release_slave(id rcv) 88{ 89 id slave = _get_slave(rcv); 90 if (slave != nil) { 91 [slave release]; 92 set_slave(rcv, nil); 93 } 94} 95 96/** 97 * ruby method handler 98 **/ 99 100/* Implemented in RBObject.m for now, still private. */ 101VALUE rbobj_call_ruby(id rbobj, SEL selector, VALUE args); 102 103static void 104ovmix_ffi_closure_done(ffi_cif* cif, void* resp, void** args, void* userdata) 105{ 106 char *retval_octype = *(char **)userdata; 107 if (*retval_octype == _C_ID) 108 [*(id *)resp retain]; 109} 110 111static void 112ovmix_ffi_closure(ffi_cif* cif, void* resp, void** args, void* userdata) 113{ 114 char *retval_octype; 115 char **args_octypes; 116 volatile VALUE rb_args; 117 unsigned i; 118 VALUE retval; 119 120 retval_octype = *(char **)userdata; 121 122 if (!is_ruby_native_thread()) { 123 rb_warning("Closure `%s' called from another thread - forwarding it to the main thread", *(char **)args[1]); 124 ffi_dispatch_closure_in_main_thread(ovmix_ffi_closure, cif, resp, args, userdata, ovmix_ffi_closure_done); 125 if (*retval_octype == _C_ID) 126 [*(id *)resp autorelease]; 127 return; 128 } 129 130 args_octypes = ((char **)userdata) + 1; 131 rb_args = rb_ary_new2(cif->nargs - 2); 132 133 OVMIX_LOG("ffi_closure cif %p nargs %d sel '%s'", cif, cif->nargs, *(SEL *)args[1]); 134 135 for (i = 2; i < cif->nargs; i++) { 136 VALUE arg; 137 138 if (!ocdata_to_rbobj(Qnil, args_octypes[i - 2], args[i], &arg, NO)) 139 rb_raise(rb_eRuntimeError, "Can't convert Objective-C argument #%d of octype '%s' to Ruby value", i - 2, args_octypes[i - 2]); 140 141 OVMIX_LOG("converted arg #%d of type '%s' to Ruby value %p", i - 2, args_octypes[i - 2], arg); 142 143 if (!NIL_P(arg) 144 && rb_obj_is_kind_of(arg, objid_s_class()) == Qtrue 145 && !OBJCID_DATA_PTR(arg)->retained) { 146 OVMIX_LOG("retaining %p", OBJCID_ID(arg)); 147 [OBJCID_ID(arg) retain]; 148 OBJCID_DATA_PTR(arg)->retained = YES; 149 OBJCID_DATA_PTR(arg)->can_be_released = YES; 150 } 151 152 rb_ary_store(rb_args, i - 2, arg); 153 } 154 155 OVMIX_LOG("calling Ruby method `%s' on %@...", *(char **)args[1], *(id *)args[0]); 156 retval = rbobj_call_ruby(*(id *)args[0], *(SEL *)args[1], rb_args); 157 OVMIX_LOG("calling Ruby method done, retval %p", retval); 158 159 // Make sure to sync boxed pointer ivars. 160 for (i = 2; i < cif->nargs; i++) { 161 struct bsBoxed *bs_boxed; 162 if (is_boxed_ptr(args_octypes[i - 2], &bs_boxed)) { 163 VALUE arg = RARRAY(rb_args)->ptr[i - 2]; 164 rb_bs_boxed_get_data(arg, bs_boxed->encoding, NULL, NULL, NO); 165 } 166 } 167 168 if (*encoding_skip_to_first_type(retval_octype) != _C_VOID) { 169 if (!rbobj_to_ocdata(retval, retval_octype, resp, YES)) 170 rb_raise(rb_eRuntimeError, "Can't convert return Ruby value to Objective-C value of octype '%s'", retval_octype); 171 } 172} 173 174static struct st_table *ffi_imp_closures; 175static pthread_mutex_t ffi_imp_closures_lock; 176 177static IMP 178ovmix_imp_for_type(const char *type) 179{ 180 BOOL ok; 181 void *closure; 182 IMP imp; 183 unsigned i, argc; 184 char *retval_type; 185 char **arg_types; 186 char **octypes; 187 188 OVMIX_LOG("retrieving closure imp for method type '%s'", type); 189 190 pthread_mutex_lock(&ffi_imp_closures_lock); 191 imp = NULL; 192 ok = st_lookup(ffi_imp_closures, (st_data_t)type, (st_data_t *)&imp); 193 pthread_mutex_unlock(&ffi_imp_closures_lock); 194 if (ok) 195 return imp; 196 197 decode_method_encoding(type, nil, &argc, &retval_type, &arg_types, NO); 198 199 octypes = (char **)malloc(sizeof(char *) * (argc + 1)); /* first int is retval octype, then arg octypes */ 200 ASSERT_ALLOC(octypes); 201 for (i = 0; i < argc; i++) { 202 if (i >= 2) 203 octypes[i - 1] = arg_types[i]; 204 } 205 octypes[0] = retval_type; 206 207 closure = ffi_make_closure(retval_type, (const char **)arg_types, argc, ovmix_ffi_closure, octypes); 208 209 pthread_mutex_lock(&ffi_imp_closures_lock); 210 imp = NULL; 211 ok = st_lookup(ffi_imp_closures, (st_data_t)type, (st_data_t *)&imp); 212 if (!ok) 213 st_insert(ffi_imp_closures, (st_data_t)type, (st_data_t)closure); 214 pthread_mutex_unlock(&ffi_imp_closures_lock); 215 if (ok) { 216 if (arg_types != NULL) { 217 for (i = 0; i < argc; i++) 218 free(arg_types[i]); 219 free(arg_types); 220 } 221 free(retval_type); 222 free(octypes); 223 free(closure); 224 closure = imp; 225 } 226 227 return closure; 228} 229 230/** 231 * instance methods implementation 232 **/ 233 234static id imp_slave (id rcv, SEL method) 235{ 236 return get_slave(rcv); 237} 238 239@interface NSObject (AliasedOVMIXMethods) 240- (id)__copyWithZone:(NSZone *)zone; 241- (id)__retain; 242- (void)__release; 243@end 244 245static id imp_copyWithZone (id rcv, SEL method, NSZone *zone) 246{ 247 id copy = [rcv __copyWithZone:zone]; 248 set_slave(copy, nil); 249 return copy; 250} 251 252static void imp_trackSlaveRubyObject (id rcv, SEL method) 253{ 254 if (_get_slave(rcv) == NULL || __slave_just_created) { 255 id slave = get_slave(rcv); 256 [slave trackRetainReleaseOfRubyObject]; 257 [slave releaseRubyObject]; 258 } 259} 260 261static id imp_retain (id rcv, SEL method) 262{ 263 [get_slave(rcv) retainRubyObject]; 264 return [rcv __retain]; 265} 266 267static inline void release_slave_rbobj_if_needed (id rcv) 268{ 269 if ([rcv retainCount] == 2) 270 [get_slave(rcv) releaseRubyObject]; 271} 272 273static void imp_release (id rcv, SEL method) 274{ 275 release_slave_rbobj_if_needed(rcv); 276 [rcv __release]; 277} 278 279static id imp_rbobj (id rcv, SEL method) 280{ 281 return (id)[get_slave(rcv) __rbobj__]; 282} 283 284static BOOL imp_respondsToSelector (id rcv, SEL method, SEL arg0) 285{ 286 BOOL ret; 287 IMP simp = super_imp(rcv, method, (IMP)imp_respondsToSelector); 288 289 ret = ((BOOL (*)(id, SEL, SEL))simp)(rcv, method, arg0); 290 if (!ret) { 291 ret = [get_slave(rcv) respondsToSelector: arg0]; 292 } 293 return ret; 294} 295 296static id imp_methodSignatureForSelector (id rcv, SEL method, SEL arg0) 297{ 298 id ret; 299 IMP simp = super_imp(rcv, method, (IMP)imp_methodSignatureForSelector); 300 ret = (*simp)(rcv, method, arg0); 301 if (ret == nil) 302 ret = [get_slave(rcv) methodSignatureForSelector: arg0]; 303 return ret; 304} 305 306static id imp_forwardInvocation (id rcv, SEL method, NSInvocation* arg0) 307{ 308 IMP simp = super_imp(rcv, method, (IMP)imp_forwardInvocation); 309 id slave = get_slave(rcv); 310 311 if ([slave respondsToSelector: [arg0 selector]]) 312 [slave forwardInvocation: arg0]; 313 else 314 (*simp)(rcv, method, arg0); 315 return nil; 316} 317 318static id imp_valueForUndefinedKey (id rcv, SEL method, NSString* key) 319{ 320 id ret = nil; 321 id slave = get_slave(rcv); 322 323 if ([slave respondsToSelector: @selector(rbValueForKey:)]) 324 ret = (id)[rcv performSelector: @selector(rbValueForKey:) withObject: key]; 325 else 326 ret = [rcv performSelector: super_selector(method) withObject: key]; 327 return ret; 328} 329 330static void imp_setValue_forUndefinedKey (id rcv, SEL method, id value, NSString* key) 331{ 332 id slave = get_slave(rcv); 333 id dict; 334 335 /* In order to avoid ObjC values to be autorelease'd while they are still proxied in the 336 Ruby world, we keep them in an internal hash. */ 337 if (object_getInstanceVariable(rcv, "__rb_kvc_dict__", (void *)&dict) == NULL) { 338 dict = [[NSMutableDictionary alloc] init]; 339 object_setInstanceVariable(rcv, "__rb_kvc_dict__", dict); 340 } 341 342 if ([slave respondsToSelector: @selector(rbSetValue:forKey:)]) { 343 [slave performSelector: @selector(rbSetValue:forKey:) withObject: value withObject: key]; 344 if (value == nil) { 345 [dict removeObjectForKey:key]; 346 } 347 else { 348 [dict setObject:value forKey:key]; 349 } 350 } 351 else 352 [rcv performSelector: super_selector(method) withObject: value withObject: key]; 353} 354 355/** 356 * class methods implementation 357 **/ 358static id imp_c_alloc(Class klass, SEL method) 359{ 360 return class_createInstance(klass, 0); 361} 362 363static id imp_c_allocWithZone(Class klass, SEL method, NSZone* zone) 364{ 365 // XXX: use zone 366 return imp_c_alloc(klass, method); 367} 368 369void 370ovmix_register_ruby_method(Class klass, SEL method, BOOL direct_override) 371{ 372 Method me; 373 IMP me_imp, imp; 374 SEL me_name; 375 char *me_types; 376 377 me = class_getInstanceMethod(klass, method); 378 // warn if trying to override a method that isn't a member of the specified class 379 if (me == NULL) 380 rb_raise(rb_eRuntimeError, "could not add '%s' to class '%s': Objective-C cannot find it in the superclass", (char *)method, class_getName(klass)); 381 382 me_imp = method_getImplementation(me); 383 me_name = method_getName(me); 384 me_types = strdup(method_getTypeEncoding(me)); 385 386 // override method 387 OVMIX_LOG("Registering %sRuby method by selector '%s' types '%s'", direct_override ? "(direct override) " : "", (char *)method, me_types); 388 imp = ovmix_imp_for_type(me_types); 389 if (me_imp == imp) { 390 OVMIX_LOG("Already registered Ruby method by selector '%s' types '%s', skipping...", (char *)method, me_types); 391 return; 392 } 393 394#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4 395 if (direct_override) { 396 // It's only ok to use setImplementation if this method is in our own 397 // class--otherwise it will change the behavior of our ancestors. 398 Method *meth_list, *iter; 399 BOOL ok = NO; 400 unsigned int count = 0; 401 402 // Search our class' methods 403 iter = meth_list = class_copyMethodList(klass, &count); 404 for (; iter && count; ++iter, --count) { 405 if (sel_isEqual(method_getName(*iter), me_name)) { 406 ok = YES; 407 break; 408 } 409 } 410 if (!ok) 411 direct_override = NO; 412 free(meth_list); 413 } 414 415 if (direct_override) 416 method_setImplementation(me, imp); 417 else 418#endif 419 class_addMethod(klass, me_name, imp, me_types); 420 421 class_addMethod(klass, super_selector(me_name), me_imp, me_types); 422 423 OVMIX_LOG("Registered Ruby method by selector '%s' types '%s'", (char *)method, me_types); 424} 425 426static id imp_c_addRubyMethod(Class klass, SEL method, SEL arg0) 427{ 428 ovmix_register_ruby_method(klass, arg0, NO); 429 return nil; 430} 431 432static id imp_c_addRubyMethod_withType(Class klass, SEL method, SEL arg0, const char *type) 433{ 434 class_addMethod(klass, sel_registerName((const char*)arg0), ovmix_imp_for_type(type), strdup(type)); 435 OVMIX_LOG("Registered Ruby method by selector '%s' types '%s'", (char *)arg0, type); 436 return nil; 437} 438 439void install_ovmix_ivars(Class c) 440{ 441#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 442 struct objc_ivar_list* ivlp = NSZoneMalloc(NSDefaultMallocZone(), sizeof(struct objc_ivar)); 443 ivlp->ivar_count = 1; 444 ivlp->ivar_list[0].ivar_name = "m_slave"; 445 ivlp->ivar_list[0].ivar_type = "@"; 446 ivlp->ivar_list[0].ivar_offset = c->instance_size; 447 c->instance_size += ocdata_size("@"); 448#ifdef __LP64__ 449 ivlp->ivar_list[0].space = 0; 450#endif 451 c->ivars = ivlp; 452#else 453 class_addIvar(c, "m_slave", ocdata_size("@"), 0, "@"); 454#endif 455} 456 457void install_ovmix_methods(Class c) 458{ 459 class_addMethod(c, @selector(__trackSlaveRubyObject), (IMP)imp_trackSlaveRubyObject, "v@:"); 460 class_addMethod(c, @selector(__slave__), (IMP)imp_slave, "@4@4:8"); 461 class_addMethod(c, @selector(__rbobj__), (IMP)imp_rbobj, "L4@4:8"); 462 class_addMethod(c, @selector(respondsToSelector:), (IMP)imp_respondsToSelector, "c8@4:8:12"); 463 class_addMethod(c, @selector(methodSignatureForSelector:), (IMP)imp_methodSignatureForSelector, "@8@4:8:12"); 464 class_addMethod(c, @selector(forwardInvocation:), (IMP)imp_forwardInvocation, "v8@4:8@12"); 465 class_addMethod(c, @selector(valueForUndefinedKey:), (IMP)imp_valueForUndefinedKey, "@12@0:4@8"); 466 class_addMethod(c, @selector(setValue:forUndefinedKey:), (IMP)imp_setValue_forUndefinedKey, "v16@0:4@8@12"); 467} 468 469static inline void 470install_objc_hook(Class c, SEL orig, SEL new, IMP new_cb) 471{ 472 if (class_respondsToSelector(c, orig)) { 473 Method method = class_getInstanceMethod(c, orig); 474 if (method != NULL) { 475 IMP orig_cb = method_getImplementation(method); 476 if (orig_cb != new_cb) { 477 OVMIX_LOG("hooking [%s -%s]", class_getName(c), (char *)orig); 478 char *types = (char *)method_getTypeEncoding(method); 479 class_addMethod(c, new, method_getImplementation(method), types); 480 class_addMethod(c, orig, new_cb, types); 481 } 482 } 483 } 484} 485 486void install_ovmix_hooks(Class c) 487{ 488 install_objc_hook(c, @selector(copyWithZone:), @selector(__copyWithZone:), 489 (IMP)imp_copyWithZone); 490 install_objc_hook(c, @selector(retain), @selector(__retain), 491 (IMP)imp_retain); 492 install_objc_hook(c, @selector(release), @selector(__release), 493 (IMP)imp_release); 494} 495 496static inline void install_ovmix_pure_class_methods(Class c) 497{ 498 class_addMethod(c->isa, @selector(addRubyMethod:), (IMP)imp_c_addRubyMethod, "@4@4:8:12"); 499 class_addMethod(c->isa, @selector(addRubyMethod:withType:), (IMP)imp_c_addRubyMethod_withType, "@4@4:8:12*16"); 500} 501 502void install_ovmix_class_methods(Class c) 503{ 504 class_addMethod(c->isa, @selector(alloc), (IMP)imp_c_alloc, "@4@4:8"); 505 class_addMethod(c->isa, @selector(allocWithZone:), (IMP)imp_c_allocWithZone, "@8@4:8^{_NSZone=}12"); 506} 507 508void init_ovmix(void) 509{ 510 ffi_imp_closures = st_init_strtable(); 511 pthread_mutex_init(&ffi_imp_closures_lock, NULL); 512 install_ovmix_pure_class_methods(objc_lookUpClass("NSObject")); 513} 514 515@implementation NSObject (__rbobj__) 516 517+ (VALUE)__rbclass__ 518{ 519 return rb_const_get(osx_s_module(), rb_intern(class_getName((Class)self))); 520} 521 522@end 523