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