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 <Foundation/Foundation.h> 11#import <ctype.h> 12#import "RBObject.h" 13#import "mdl_osxobjc.h" 14#import "ocdata_conv.h" 15#import "BridgeSupport.h" 16#import "internal_macros.h" 17#import "OverrideMixin.h" 18 19#define RBOBJ_LOG(fmt, args...) DLOG("RBOBJ", fmt, ##args) 20 21extern ID _relaxed_syntax_ID; 22 23#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4 24// On MacOS X 10.4 or earlier, +signatureWithObjCTypes: is a SPI 25@interface NSMethodSignature (WarningKiller) 26+ (id) signatureWithObjCTypes:(const char*)types; 27@end 28#endif 29 30static RB_ID sel_to_mid(VALUE rcv, SEL a_sel) 31{ 32 int i, length; 33 const char *selName; 34 char mname[1024]; 35 36 selName = sel_getName(a_sel); 37 memset(mname, 0, sizeof(mname)); 38 strncpy(mname, selName, sizeof(mname) - 1); 39 40 // selstr.sub(/:/,'_') 41 length = strlen(selName); 42 for (i = 0; i < length; i++) 43 if (mname[i] == ':') mname[i] = '_'; 44 45 if (RTEST(rb_ivar_get(osx_s_module(), _relaxed_syntax_ID)) && !rb_respond_to(rcv, rb_intern(mname))) { 46 // sub(/^(.*)_$/, "\1") 47 for (i = length - 1; i >= 0; i--) { 48 if (mname[i] != '_') break; 49 mname[i] = '\0'; 50 } 51 } 52 53 return rb_intern(mname); 54} 55 56static RB_ID sel_to_mid_as_setter(SEL a_sel) 57{ 58 volatile VALUE str = rb_str_new2(sel_getName(a_sel)); 59 60 // if str.sub!(/^set([A-Z][^:]*):$/, '\1=') then 61 // str = str[0].chr.downcase + str[1..-1] 62 // end 63 const char* re_pattern = "^set([A-Z][^:]*):$"; 64 VALUE re = rb_reg_new(re_pattern, strlen(re_pattern), 0); 65 if (rb_funcall(str, rb_intern("sub!"), 2, re, rb_str_new2("\\1=")) != Qnil) { 66 int c = (int)(RSTRING(str)->ptr[0]); 67 c = tolower(c); 68 RSTRING(str)->ptr[0] = (char) c; 69 } 70 71 return rb_to_id(str); 72} 73 74static RB_ID rb_obj_sel_to_mid(VALUE rcv, SEL a_sel) 75{ 76 RB_ID mid = sel_to_mid(rcv, a_sel); 77 if (rb_respond_to(rcv, mid) == 0) 78 mid = sel_to_mid_as_setter(a_sel); 79 return mid; 80} 81 82static int rb_obj_arity_of_method(VALUE rcv, SEL a_sel, BOOL *ok) 83{ 84 VALUE mstr; 85 RB_ID mid; 86 VALUE method; 87 VALUE argc; 88 89 mid = rb_obj_sel_to_mid(rcv, a_sel); 90 if (rb_respond_to(rcv, mid) == 0) { 91 *ok = NO; 92 return 0; 93 } 94 mstr = rb_str_new2(rb_id2name(mid)); // mstr = sel_to_rbobj (a_sel); 95 method = rb_funcall(rcv, rb_intern("method"), 1, mstr); 96 *ok = YES; 97 argc = rb_funcall(method, rb_intern("arity"), 0); 98 return NUM2INT(argc); 99} 100 101@interface __RBObjectThreadDispatcher : NSObject 102{ 103 id _returned_ocid; 104 RBObject * _rbobj; 105 NSInvocation * _invocation; 106} 107+ (void)dispatchInvocation:(NSInvocation *)invocation toRBObject:(RBObject *)rbobj; 108@end 109 110@implementation RBObject 111 112// private methods 113 114- (BOOL)rbobjRespondsToSelector: (SEL)a_sel 115{ 116 BOOL ret; 117 RB_ID mid; 118 int state; 119 extern void Init_stack(VALUE*); 120 121 if (FREQUENTLY_INIT_STACK_FLAG) { 122 RBOBJ_LOG("rbobjRespondsToSelector(%s) w/Init_stack(%08lx)", a_sel, (void*)&state); 123 Init_stack((void*)&state); 124 } 125 mid = rb_obj_sel_to_mid(m_rbobj, a_sel); 126 ret = (rb_respond_to(m_rbobj, mid) != 0); 127 RBOBJ_LOG(" --> %d", ret); 128 return ret; 129} 130 131- (VALUE)fetchForwardArgumentsOf: (NSInvocation*)an_inv 132{ 133 int i; 134 NSMethodSignature* msig = [an_inv methodSignature]; 135 int arg_cnt = ([msig numberOfArguments] - 2); 136 VALUE args = rb_ary_new2(arg_cnt); 137 for (i = 0; i < arg_cnt; i++) { 138 VALUE arg_val; 139 const char* octstr = [msig getArgumentTypeAtIndex: (i+2)]; 140 void* ocdata = OCDATA_ALLOCA(octstr); 141 BOOL f_conv_success; 142 143 RBOBJ_LOG("arg[%d] of type '%s'", i, octstr); 144 [an_inv getArgument: ocdata atIndex: (i+2)]; 145 f_conv_success = ocdata_to_rbobj(Qnil, octstr, ocdata, &arg_val, NO); 146 if (f_conv_success == NO) { 147 arg_val = Qnil; 148 } 149 rb_ary_store(args, i, arg_val); 150 } 151 return args; 152} 153 154- (BOOL)stuffForwardResult: (VALUE)result to: (NSInvocation*)an_inv returnedOcid: (id *) returnedOcid 155{ 156 NSMethodSignature* msig = [an_inv methodSignature]; 157 const char* octype_str = encoding_skip_to_first_type([msig methodReturnType]); 158 BOOL f_success; 159 160 RBOBJ_LOG("stuff forward result of type '%s'", octype_str); 161 162 *returnedOcid = nil; 163 164 if (*octype_str == _C_VOID) { 165 f_success = true; 166 } 167 else if ((*octype_str == _C_ID) || (*octype_str == _C_CLASS)) { 168 id ocdata = rbobj_get_ocid(result); 169 if (ocdata == nil) { 170 if (result == m_rbobj) { 171 ocdata = self; 172 } 173 else { 174 rbobj_to_nsobj(result, &ocdata); 175 *returnedOcid = ocdata; 176 } 177 } 178 [an_inv setReturnValue: &ocdata]; 179 f_success = YES; 180 } 181 else { 182 void* ocdata = OCDATA_ALLOCA(octype_str); 183 f_success = rbobj_to_ocdata (result, octype_str, ocdata, YES); 184 if (f_success) [an_inv setReturnValue: ocdata]; 185 } 186 return f_success; 187} 188 189static void 190rbobjRaiseRubyException (void) 191{ 192 VALUE lasterr = rb_gv_get("$!"); 193 RB_ID mtd = rb_intern("nsexception"); 194 if (rb_respond_to(lasterr, mtd)) { 195 VALUE nso = rb_funcall(lasterr, mtd, 0); 196 NSException *exc = rbobj_get_ocid(nso); 197 [exc raise]; 198 return; // not reached 199 } 200 201 NSMutableDictionary *info = [NSMutableDictionary dictionary]; 202 203 id ocdata = rbobj_get_ocid(lasterr); 204 if (ocdata == nil) { 205 rbobj_to_nsobj(lasterr, &ocdata); 206 } 207 [info setObject: ocdata forKey: @"$!"]; 208 209 VALUE klass = rb_class_path(CLASS_OF(lasterr)); 210 NSString *rbclass = [NSString stringWithUTF8String:StringValuePtr(klass)]; 211 212 VALUE rbmessage = rb_obj_as_string(lasterr); 213 NSString *message = [NSString stringWithUTF8String:StringValuePtr(rbmessage)]; 214 215 NSMutableArray *backtraceArray = [NSMutableArray array]; 216 volatile VALUE ary = rb_funcall(ruby_errinfo, rb_intern("backtrace"), 0); 217 int c; 218 for (c=0; c<RARRAY(ary)->len; c++) { 219 const char *path = StringValuePtr(RARRAY(ary)->ptr[c]); 220 NSString *nspath = [NSString stringWithUTF8String:path]; 221 [backtraceArray addObject: nspath]; 222 } 223 224 [info setObject: backtraceArray forKey: @"backtrace"]; 225 226 NSException* myException = [NSException 227 exceptionWithName:[@"RBException_" stringByAppendingString: rbclass] 228 reason:message 229 userInfo:info]; 230 [myException raise]; 231} 232 233static VALUE rbobject_protected_apply(VALUE a) 234{ 235 VALUE *args = (VALUE*) a; 236 return rb_apply(args[0],(RB_ID)args[1],(VALUE)args[2]); 237} 238 239static void notify_error(VALUE rcv, RB_ID mid) 240{ 241 extern int RBNotifyException(const char* title, VALUE err); 242 char title[128]; 243 snprintf(title, sizeof(title), "%s#%s", rb_obj_classname(rcv), rb_id2name(mid)); 244 RBNotifyException(title, ruby_errinfo); 245} 246 247VALUE rbobj_call_ruby(id rbobj, SEL selector, VALUE args) 248{ 249 VALUE m_rbobj; 250 RB_ID mid; 251 VALUE stub_args[3]; 252 VALUE rb_result; 253 int err; 254 255 if ([rbobj respondsToSelector:@selector(__rbobj__)]) { 256 m_rbobj = [rbobj __rbobj__]; 257 } 258 else if ([rbobj respondsToSelector:@selector(__rbclass__)]) { 259 m_rbobj = [rbobj __rbclass__]; 260 } 261 else { 262 // Not an RBObject class, try to get the value from the cache. 263 m_rbobj = ocid_to_rbobj_cache_only(rbobj); 264 if (NIL_P(m_rbobj)) { 265 // Nothing in the cache, it means that this Objective-C object never 266 // crossed the bridge yet. Let's create the Ruby proxy. 267 m_rbobj = ocid_to_rbobj(Qnil, rbobj); 268 } 269 } 270 271 mid = rb_obj_sel_to_mid(m_rbobj, selector); 272 stub_args[0] = m_rbobj; 273 stub_args[1] = mid; 274 stub_args[2] = args; 275 276 RBOBJ_LOG("calling method %s on Ruby object %p with %d args", rb_id2name(mid), m_rbobj, RARRAY(args)->len); 277 278 if (rb_respond_to(m_rbobj, mid) == 0) { 279 VALUE str = rb_inspect(m_rbobj); 280 rb_raise(rb_eRuntimeError, "Ruby object `%s' doesn't respond to the ObjC selector `%s', the method either doesn't exist or is private", StringValuePtr(str), (char *)selector); 281 } 282 283 rb_result = rb_protect(rbobject_protected_apply, (VALUE)stub_args, &err); 284 if (err) { 285 notify_error(m_rbobj, mid); 286 RBOBJ_LOG("got Ruby exception, raising Objective-C exception"); 287 rbobjRaiseRubyException(); 288 return Qnil; /* to be sure */ 289 } 290 291 return rb_result; 292} 293 294- (id)rbobjForwardInvocation: (NSInvocation *)an_inv 295{ 296 VALUE rb_args; 297 VALUE rb_result; 298 VALUE rb_result_inspect; 299 id returned_ocid; 300 301 RBOBJ_LOG("rbobjForwardInvocation(%@)", an_inv); 302 rb_args = [self fetchForwardArgumentsOf: an_inv]; 303 rb_result = rbobj_call_ruby(self, [an_inv selector], rb_args); 304 [self stuffForwardResult: rb_result to: an_inv returnedOcid: &returned_ocid]; 305 rb_result_inspect = rb_inspect(rb_result); 306 RBOBJ_LOG(" --> rb_result=%s", StringValuePtr(rb_result_inspect)); 307 308 return returned_ocid; 309} 310 311// public class methods 312+ RBObjectWithRubyScriptCString: (const char*) cstr 313{ 314 return [[[self alloc] initWithRubyScriptCString: cstr] autorelease]; 315} 316 317+ RBObjectWithRubyScriptString: (NSString*) str 318{ 319 return [[[self alloc] initWithRubyScriptString: str] autorelease]; 320} 321 322// public methods 323 324- (VALUE) __rbobj__ { return m_rbobj; } 325 326- (void) trackRetainReleaseOfRubyObject 327{ 328 VALUE str = rb_inspect(m_rbobj); 329 RBOBJ_LOG("start tracking retain/release of Ruby object `%s'", 330 StringValuePtr(str)); 331 m_rbobj_retain_release_track = YES; 332} 333 334- (void) retainRubyObject 335{ 336 if (m_rbobj_retain_release_track && !m_rbobj_retained) { 337 VALUE str = rb_inspect(m_rbobj); 338 RBOBJ_LOG("retaining Ruby object `%s'", StringValuePtr(str)); 339 rb_gc_register_address(&m_rbobj); 340 m_rbobj_retained = YES; 341 } 342} 343 344- (void) releaseRubyObject 345{ 346 if (m_rbobj_retain_release_track && m_rbobj_retained) { 347 RBOBJ_LOG("releasing Ruby object `#<%s:%p>'", rb_obj_classname(m_rbobj), m_rbobj); 348 rb_gc_unregister_address(&m_rbobj); 349 m_rbobj_retained = NO; 350 } 351} 352 353- (void) dealloc 354{ 355 RBOBJ_LOG("deallocating RBObject %p", self); 356 remove_from_rb2oc_cache(m_rbobj); 357 [self releaseRubyObject]; 358 [super dealloc]; 359} 360 361- _initWithRubyObject: (VALUE)rbobj retains: (BOOL) flag 362{ 363 m_rbobj = rbobj; 364 m_rbobj_retained = flag; 365 m_rbobj_retain_release_track = NO; 366 oc_master = nil; 367 if (flag) 368 rb_gc_register_address(&m_rbobj); 369 return self; 370} 371 372- initWithRubyObject: (VALUE)rbobj 373{ 374 return [self _initWithRubyObject: rbobj retains: YES]; 375} 376 377- initWithRubyScriptCString: (const char*) cstr 378{ 379 return [self initWithRubyObject: rb_eval_string(cstr)]; 380} 381 382- initWithRubyScriptString: (NSString*) str 383{ 384 return [self initWithRubyScriptCString: [str UTF8String]]; 385} 386 387- (NSString*) _copyDescription 388{ 389 VALUE str = rb_inspect(m_rbobj); 390 return [[[NSString alloc] initWithUTF8String: StringValuePtr(str)] autorelease]; 391} 392 393- (BOOL)isKindOfClass: (Class)klass 394{ 395 BOOL ret; 396 RBOBJ_LOG("isKindOfClass(%@)", NSStringFromClass(klass)); 397 ret = NO; 398 RBOBJ_LOG(" --> %d", ret); 399 return ret; 400} 401 402- (BOOL)isRBObject 403{ 404 return YES; 405} 406 407- (void)forwardInvocation: (NSInvocation *)an_inv 408{ 409 RBOBJ_LOG("forwardInvocation(%@)", an_inv); 410 if ([self rbobjRespondsToSelector: [an_inv selector]]) { 411 RBOBJ_LOG(" -> forward to Ruby Object"); 412 if (is_ruby_native_thread()) { 413 [self rbobjForwardInvocation: an_inv]; 414 } 415 else { 416 rb_warning("Invocation `%s' received from another thread - forwarding it to the main thread", [[an_inv description] UTF8String]); 417 [__RBObjectThreadDispatcher dispatchInvocation:an_inv toRBObject:self]; 418 } 419 } 420 else { 421 RBOBJ_LOG(" -> forward to super Objective-C Object"); 422 [super forwardInvocation: an_inv]; 423 } 424} 425 426- (NSMethodSignature*)methodSignatureForSelector: (SEL)a_sel 427{ 428 NSMethodSignature* ret = nil; 429 RBOBJ_LOG("methodSignatureForSelector(%s)", a_sel); 430 if (a_sel == NULL) 431 return nil; 432 // Try the master object. 433 if (oc_master != nil) { 434 ret = [oc_master instanceMethodSignatureForSelector:a_sel]; 435 if (ret != nil) 436 RBOBJ_LOG("\tgot method signature from the master object"); 437 } 438 // Try the metadata. 439 if (ret == nil) { 440 struct bsInformalProtocolMethod *method; 441 442 method = find_bs_informal_protocol_method((const char *)a_sel, NO); 443 if (method != NULL) { 444 ret = [NSMethodSignature signatureWithObjCTypes:method->encoding]; 445 RBOBJ_LOG("\tgot method signature from metadata (types: '%s')", method->encoding); 446 } 447 } 448 // Ensure a dummy method signature ('id' for everything). 449 if (ret == nil) { 450 int argc; 451 BOOL ok; 452 453 argc = rb_obj_arity_of_method(m_rbobj, a_sel, &ok); 454 if (ok) { 455 char encoding[128], *p; 456 457 if (argc < 0) 458 argc = -1 - argc; 459 argc = MIN(sizeof(encoding) - 4, argc); 460 461 strcpy(encoding, "@@:"); 462 p = &encoding[3]; 463 while (argc-- > 0) { 464 *p++ = '@'; 465 } 466 *p = '\0'; 467 ret = [NSMethodSignature signatureWithObjCTypes:encoding]; 468 RBOBJ_LOG("\tgenerated dummy method signature"); 469 } 470 else { 471 VALUE str = rb_inspect(m_rbobj); 472 RBOBJ_LOG("\tcan't generate a dummy method signature because receiver %s doesn't respond to the selector", StringValuePtr(str)); 473 } 474 } 475 RBOBJ_LOG(" --> %@", ret); 476 return ret; 477} 478 479- (BOOL)respondsToSelector: (SEL)a_sel 480{ 481 BOOL ret; 482 if (a_sel == @selector(__rbobj__)) 483 return YES; 484 RBOBJ_LOG("respondsToSelector(%s)", a_sel); 485 ret = [self rbobjRespondsToSelector: a_sel]; 486 RBOBJ_LOG(" --> %d", ret); 487 return ret; 488} 489 490@end 491 492@implementation __RBObjectThreadDispatcher 493 494- (id)initWithInvocation:(NSInvocation *)invocation RBObject:(RBObject *)rbobj 495{ 496 self = [super init]; 497 if (self != NULL) { 498 _returned_ocid = nil; 499 _invocation = invocation; // no retain 500 _rbobj = rbobj; // no retain 501 } 502 return self; 503} 504 505extern NSThread *rubycocoaThread; 506 507- (void)dispatch 508{ 509 DISPATCH_ON_RUBYCOCOA_THREAD(self, @selector(syncDispatch)); 510 if (_returned_ocid != nil) 511 [_returned_ocid autorelease]; 512} 513 514- (void)syncDispatch 515{ 516 _returned_ocid = [_rbobj rbobjForwardInvocation:_invocation]; 517 if (_returned_ocid != nil) 518 [_returned_ocid retain]; 519} 520 521+ (void)dispatchInvocation:(NSInvocation *)invocation toRBObject:(RBObject *)rbobj 522{ 523 __RBObjectThreadDispatcher * dispatcher; 524 525 dispatcher = [[__RBObjectThreadDispatcher alloc] initWithInvocation:invocation RBObject:rbobj]; 526 [dispatcher dispatch]; 527 [dispatcher release]; 528} 529 530@end 531 532@implementation NSProxy (RubyCocoaEx) 533 534- (BOOL)isRBObject 535{ 536 return NO; 537} 538 539@end 540