Method class_getInstanceMethod(Class aClass, SEL aSelector) { CHECK_ARG(aClass); CHECK_ARG(aSelector); // If the class has a dtable installed, then we can use the fast path if (classHasInstalledDtable(aClass)) { // Do a dtable lookup to find out which class the method comes from. struct objc_slot *slot = objc_get_slot(aClass, aSelector); if (NULL == slot) { slot = objc_get_slot(aClass, sel_registerName(sel_getName(aSelector))); if (NULL == slot) { return NULL; } } // Now find the typed variant of the selector, with the correct types. aSelector = slot->selector; // Then do the slow lookup to find the method. return class_getInstanceMethodNonrecursive(slot->owner, aSelector); } Method m = class_getInstanceMethodNonrecursive(aClass, aSelector); if (NULL != m) { return m; } return class_getInstanceMethod(class_getSuperclass(aClass), aSelector); }
static VALUE method_inspect(VALUE method, SEL sel) { rb_vm_method_t *data; VALUE str; const char *s; const char *sharp = "#"; Data_Get_Struct(method, rb_vm_method_t, data); str = rb_str_buf_new2("#<"); s = rb_obj_classname(method); rb_str_buf_cat2(str, s); rb_str_buf_cat2(str, ": "); rb_str_buf_cat2(str, rb_class2name(data->rclass)); if (data->rclass != data->oclass) { rb_str_buf_cat2(str, "("); rb_str_buf_cat2(str, rb_class2name(data->oclass)); rb_str_buf_cat2(str, ")"); } rb_str_buf_cat2(str, sharp); rb_str_buf_cat2(str, sel_getName(data->sel)); rb_str_buf_cat2(str, ">"); return str; }
static VALUE rb_struct_set(VALUE obj, SEL sel, VALUE val) { VALUE members, slot; long i; // foo=: -> foo char buf[100]; const size_t s = strlcpy(buf, sel_getName(sel), sizeof buf); buf[s - 2] = '\0'; ID field = rb_intern(buf); members = rb_struct_members(obj); rb_struct_modify(obj); for (i=0; i<RARRAY_LEN(members); i++) { slot = RARRAY_AT(members, i); if (SYM2ID(slot) == field) { GC_WB(&RSTRUCT_PTR(obj)[i], val); return val; } } rb_name_error(rb_frame_this_func(), "`%s' is not a struct member", rb_id2name(field)); return Qnil; /* not reached */ }
static VALUE method_name(VALUE obj, SEL sel) { rb_vm_method_t *data; Data_Get_Struct(obj, rb_vm_method_t, data); ID mid = rb_intern(sel_getName(data->sel)); return ID2SYM(mid); }
VALUE rb_sel_to_sym(SEL sel) { if (sel == 0) { return Qnil; } return ID2SYM(rb_intern(sel_getName(sel))); }
VALUE rb_f_notimplement(VALUE rcv, SEL sel) { rb_raise(rb_eNotImpError, "%s() function is unimplemented on this machine", sel_getName(sel)); return Qnil; // never reached }
static VALUE inspect_enumerator(VALUE obj, VALUE dummy, int recur) { struct enumerator *e; const char *cname; VALUE eobj, str; int tainted, untrusted; Data_Get_Struct(obj, struct enumerator, e); cname = rb_obj_classname(obj); if (!e || e->obj == Qundef) { return rb_sprintf("#<%s: uninitialized>", cname); } if (recur) { str = rb_sprintf("#<%s: ...>", cname); OBJ_TAINT(str); return str; } eobj = e->obj; tainted = OBJ_TAINTED(eobj); untrusted = OBJ_UNTRUSTED(eobj); /* (1..100).each_cons(2) => "#<Enumerator: 1..100:each_cons(2)>" */ str = rb_sprintf("#<%s: ", cname); rb_str_concat(str, rb_inspect(eobj)); rb_str_buf_cat2(str, ":"); rb_str_buf_cat2(str, sel_getName(e->sel)); if (e->args) { long argc = RARRAY_LEN(e->args); VALUE *argv = (VALUE*)RARRAY_PTR(e->args); rb_str_buf_cat2(str, "("); while (argc--) { VALUE arg = *argv++; rb_str_concat(str, rb_inspect(arg)); rb_str_buf_cat2(str, argc > 0 ? ", " : ")"); if (OBJ_TAINTED(arg)) tainted = TRUE; if (OBJ_UNTRUSTED(arg)) untrusted = TRUE; } } rb_str_buf_cat2(str, ">"); if (tainted) OBJ_TAINT(str); if (untrusted) OBJ_UNTRUST(str); return str; }
/* __attribute__((noinline)) */ __attribute__((always_inline)) struct objc_slot * objc_msg_lookup_default(id *receiver, SEL selector, id sender) { Class class = objc_object_get_class_inline((*receiver)); struct objc_slot *result = objc_dtable_lookup(class->dtable, selector); if (UNLIKELY(NULL == result)) { dtable_t dtable = dtable_for_class(class); /* Install the dtable if it hasn't already been initialized. */ if (dtable == uninstalled_dtable){ objc_debug_log("Sending initialize to receiver %p, selector %s[%d]\n", *receiver, sel_getName(selector), (unsigned)selector); objc_send_initialize(*receiver); dtable = dtable_for_class(class); result = objc_dtable_lookup(dtable, selector); }else{
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) { CHECK_ARG(cls); CHECK_ARG(name); CHECK_ARG(imp); CHECK_ARG(types); const char *methodName = sel_getName(name); struct objc_method_list *methods; for (methods=cls->methods; methods!=NULL ; methods=methods->next) { for (int i=0 ; i<methods->count ; i++) { Method method = &methods->methods[i]; if (strcmp(sel_getName(method->selector), methodName) == 0) { return NO; } } } methods = malloc(sizeof(struct objc_method_list) + sizeof(struct objc_method)); methods->next = cls->methods; cls->methods = methods; methods->count = 1; methods->methods[0].selector = sel_registerTypedName_np(methodName, types); methods->methods[0].types = strdup(types); methods->methods[0].imp = imp; if (objc_test_class_flag(cls, objc_class_flag_resolved)) { add_method_list_to_class(cls, methods); } return YES; }
void printMethodsForClass(Class cls) { unsigned int totalCount; Method *methods __attribute__((cleanup(free_methods))) = class_copyMethodList(cls, &totalCount); printf("=> List Methods For Class: %s (%u)\n", class_getName(cls), totalCount); for (unsigned int i = 0; i<totalCount; i++) { Method method = methods[i]; SEL sel = method_getName(method); IMP imp = method_getImplementation(method); printf(" method<%p>: %s \n", imp, sel_getName(sel)); } printf("\n"); }
void protocol_addMethodDescription(Protocol *aProtocol, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod) { if ((NULL == aProtocol) || (NULL == name) || (NULL == types)) { return; } if (nil != aProtocol->isa) { return; } Protocol2 *proto = (Protocol2*)aProtocol; struct objc_method_description_list **listPtr; if (isInstanceMethod) { if (isRequiredMethod) { listPtr = &proto->instance_methods; } else { listPtr = &proto->optional_instance_methods; } } else { if (isRequiredMethod) { listPtr = &proto->class_methods; } else { listPtr = &proto->optional_class_methods; } } if (NULL == *listPtr) { *listPtr = calloc(1, sizeof(struct objc_method_description_list) + sizeof(struct objc_method_description)); (*listPtr)->count = 1; } else { (*listPtr)->count++; *listPtr = realloc(*listPtr, sizeof(struct objc_method_description_list) + sizeof(struct objc_method_description) * (*listPtr)->count); } struct objc_method_description_list *list = *listPtr; int index = list->count-1; list->methods[index].name = sel_getName(name); list->methods[index].types= types; }
static VALUE method_missing(VALUE obj, SEL sel, rb_vm_block_t *block, int argc, const VALUE *argv, rb_vm_method_missing_reason_t call_status) { if (sel == selAlloc) { rb_raise(rb_eTypeError, "allocator undefined for %s", RSTRING_PTR(rb_inspect(obj))); } GET_VM()->set_method_missing_reason(call_status); VALUE *new_argv = (VALUE *)xmalloc_ptrs(sizeof(VALUE) * (argc + 1)); char buf[100]; int n = snprintf(buf, sizeof buf, "%s", sel_getName(sel)); if (buf[n - 1] == ':') { // Let's see if there are more colons making this a real selector. bool multiple_colons = false; for (int i = 0; i < (n - 1); i++) { if (buf[i] == ':') { multiple_colons = true; break; } } if (!multiple_colons) { // Not a typical multiple argument selector. So as this is // probably a typical ruby method name, chop off the colon. buf[n - 1] = '\0'; } } new_argv[0] = ID2SYM(rb_intern(buf)); MEMCPY(&new_argv[1], argv, VALUE, argc); // In case the missing selector _is_ method_missing: OR the object does // not respond to method_missing: (this can happen for NSProxy-based // objects), directly trigger the exception. Class k = (Class)CLASS_OF(obj); if (sel == selMethodMissing || class_getInstanceMethod(k, selMethodMissing) == NULL) { rb_vm_method_missing(obj, argc + 1, new_argv); return Qnil; // never reached } else { return rb_vm_call2(block, obj, (VALUE)k, selMethodMissing, argc + 1, new_argv); } }
static void rb_export_method(VALUE klass, ID name, ID noex) { rb_vm_method_node_t *node; SEL sel; if (klass == rb_cObject) { rb_secure(4); } if (!rb_vm_lookup_method2((Class)klass, name, &sel, NULL, &node)) { if (TYPE(klass) != T_MODULE || !rb_vm_lookup_method2((Class)rb_cObject, name, &sel, NULL, &node)) { rb_print_undef(klass, name, 0); } } if (node == NULL) { rb_raise(rb_eRuntimeError, "can't change visibility of non Ruby method `%s'", sel_getName(sel)); } long flags = (node->flags & ~VM_METHOD_PRIVATE) & ~VM_METHOD_PROTECTED; switch (noex) { case NOEX_PRIVATE: flags |= VM_METHOD_PRIVATE; break; case NOEX_PROTECTED: flags |= VM_METHOD_PROTECTED; break; default: break; } if (node->flags != flags) { if (node->klass == (Class)klass) { node->flags = flags; } else { rb_vm_define_method2((Class)klass, sel, node, flags, false); } } }
PRIMITIVE VALUE vm_dispatch(VALUE top, VALUE self, void *sel, void *block, unsigned char opt, int argc, VALUE *argv) { if (opt & DISPATCH_SUPER) { if (sel == 0) { rb_raise(rb_eNoMethodError, "super called outside of method"); } } VALUE buf[100]; if (opt & DISPATCH_SPLAT) { if (argc == 1 && !SPECIAL_CONST_P(argv[1]) && *(VALUE *)argv[1] == rb_cRubyArray) { argc = RARY(argv[1])->len; argv = rary_ptr(argv[1]); } else { VALUE *new_argv = buf; vm_resolve_args(&new_argv, 100, &argc, argv); argv = new_argv; } if (argc == 0) { const char *selname = sel_getName((SEL)sel); const size_t selnamelen = strlen(selname); if (selname[selnamelen - 1] == ':') { // Because // def foo; end; foo(*[]) // creates foo but dispatches foo:. char buf[100]; strncpy(buf, selname, sizeof buf); buf[selnamelen - 1] = '\0'; sel = sel_registerName(buf); } } } void *vm = rb_vm_current_vm(); VALUE klass = vm_class_of(self); return rb_vm_call0(vm, top, self, (Class)klass, (SEL)sel, (rb_vm_block_t *)block, opt, argc, argv); }
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) { if (Nil == cls) { return (IMP)0; } SEL sel = sel_registerTypedName_np(sel_getName(name), types); Method method = class_getInstanceMethodNonrecursive(cls, sel); if (method == NULL) { class_addMethod(cls, sel, imp, types); return NULL; } IMP old = (IMP)method->imp; method->imp = imp; if (objc_test_class_flag(cls, objc_class_flag_resolved)) { objc_update_dtable_for_class(cls); } return old; }
static void _objc_unload_IMPs_in_class(Class cl, void *kernel_module){ if (cl->methods == NULL){ return; } objc_debug_log("Unloading IMPs in class %s\n", class_getName(cl)); for (objc_method_list *list = cl->methods; list != NULL; list = list->next){ objc_debug_log("\t [%p]\n", list); for (int i = 0; i < list->size; ++i){ Method m = &list->list[i]; if (module_contains_IMP(kernel_module, m->implementation)){ objc_debug_log("\t\t [%02i] - %s\n", i, sel_getName(m->selector)); IMP old_imp = m->implementation; m->implementation = objc_unloaded_module_method; /* Since the selector name and types can actually be also * allocated in the module, we better update the strings * as well. */ m->selector_name = "__objc_unloaded_method"; m->selector_types = sizeof(void*) == 4 ? "v8@0:4" : "v16@0:8"; /* Update the dtable! */ SparseArray *arr = (SparseArray*)cl->dtable; if (arr != NULL && arr != uninstalled_dtable){ struct objc_slot *slot = SparseArrayLookup(arr, m->selector); if (slot != NULL && slot->implementation == old_imp){ slot->implementation = m->implementation; slot->types = m->selector_types; ++slot->version; } } } } } }
static void fill_ocache(struct mcache *cache, VALUE self, Class klass, IMP imp, SEL sel, Method method, int argc) { cache->flag = MCACHE_OCALL; cache->sel = sel; cache->klass = klass; cache->as.ocall.imp = imp; cache->as.ocall.argc = argc; cache->as.ocall.bs_method = GET_CORE()->find_bs_method(klass, sel); char types[200]; if (!rb_objc_get_types(self, klass, sel, method, cache->as.ocall.bs_method, types, sizeof types)) { printf("cannot get encoding types for %c[%s %s]\n", class_isMetaClass(klass) ? '+' : '-', class_getName(klass), sel_getName(sel)); abort(); } bool variadic = false; if (cache->as.ocall.bs_method != NULL && cache->as.ocall.bs_method->variadic && method != NULL) { // TODO honor printf_format const int real_argc = rb_method_getNumberOfArguments(method) - 2; if (real_argc < argc) { const size_t s = strlen(types); assert(s + argc - real_argc < sizeof types); for (int i = real_argc; i < argc; i++) { strlcat(types, "@", sizeof types); } argc = real_argc; } variadic = true; } cache->as.ocall.stub = (rb_vm_objc_stub_t *)GET_CORE()->gen_stub(types, variadic, argc, true); }
struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod) { struct objc_method_description output = {NULL, NULL}; if (p != NULL) { struct objc_method_description_list *methods; const char *name = sel_getName(aSel); int i; if (isInstanceMethod) { methods = p->instance_methods; } else { methods = p->class_methods; } if (methods != NULL) { for (i = 0; i < methods->count; i++) { if (!strcmp((char*)methods->list[i].name, name)) { output = methods->list[i]; // HACK: the name field of the objc_method_description struct // is supposed to be a selector, but testing reveals it is a string output.name = sel_registerName((const char *) output.name); break; } } } } return output; }
VALUE rb_vm_dispatch(void *_vm, struct mcache *cache, VALUE top, VALUE self, Class klass, SEL sel, rb_vm_block_t *block, unsigned char opt, int argc, const VALUE *argv) { RoxorVM *vm = (RoxorVM *)_vm; #if ROXOR_VM_DEBUG bool cached = true; #endif bool cache_method = true; Class current_super_class = vm->get_current_super_class(); SEL current_super_sel = vm->get_current_super_sel(); if (opt & DISPATCH_SUPER) { // TODO goto recache; } if (cache->sel != sel || cache->klass != klass || cache->flag == 0) { recache: #if ROXOR_VM_DEBUG cached = false; #endif Method method; if (opt & DISPATCH_SUPER) { if (!sel_equal(klass, current_super_sel, sel)) { current_super_sel = sel; current_super_class = klass; } else { // Let's make sure the current_super_class is valid before // using it; we check this by verifying that it's a real // super class of the current class, as we may be calling // a super method of the same name but on a totally different // class hierarchy. Class k = klass; bool current_super_class_ok = false; while (k != NULL) { if (k == current_super_class) { current_super_class_ok = true; break; } k = class_getSuperclass(k); } if (!current_super_class_ok) { current_super_class = klass; } } method = rb_vm_super_lookup(current_super_class, sel, ¤t_super_class); } else { current_super_sel = 0; method = class_getInstanceMethod(klass, sel); } if (method != NULL) { recache2: IMP imp = method_getImplementation(method); if (UNAVAILABLE_IMP(imp)) { // Method was undefined. goto call_method_missing; } rb_vm_method_node_t *node = GET_CORE()->method_node_get(method); if (node != NULL) { // ruby call fill_rcache(cache, klass, sel, node); } else { // objc call fill_ocache(cache, self, klass, imp, sel, method, argc); } if (opt & DISPATCH_SUPER) { cache->flag |= MCACHE_SUPER; } } else { // Method is not found... #if !defined(MACRUBY_STATIC) // Force a method resolving, because the objc cache might be // wrong. if (rb_vm_resolve_method(klass, sel)) { goto recache; } #endif // Does the receiver implements -forwardInvocation:? if ((opt & DISPATCH_SUPER) == 0 && rb_objc_supports_forwarding(self, sel)) { //#if MAC_OS_X_VERSION_MAX_ALLOWED < 1070 // In earlier versions of the Objective-C runtime, there seems // to be a bug where class_getInstanceMethod isn't atomic, // and might return NULL while at the exact same time another // thread registers the related method. // As a work-around, we double-check if the method still does // not exist here. If he does, we can dispatch it properly. // note: OS X 10.7 also, this workaround is required. see #1476 method = class_getInstanceMethod(klass, sel); if (method != NULL) { goto recache2; } //#endif fill_ocache(cache, self, klass, (IMP)objc_msgSend, sel, NULL, argc); goto dispatch; } // Let's see if are not trying to call a Ruby method that accepts // a regular argument then an optional Hash argument, to be // compatible with the Ruby specification. const char *selname = (const char *)sel; size_t selname_len = strlen(selname); if (argc > 1) { const char *p = strchr(selname, ':'); if (p != NULL && p + 1 != '\0') { char *tmp = (char *)malloc(selname_len + 1); assert(tmp != NULL); strncpy(tmp, selname, p - selname + 1); tmp[p - selname + 1] = '\0'; sel = sel_registerName(tmp); VALUE h = rb_hash_new(); bool ok = true; p += 1; for (int i = 1; i < argc; i++) { const char *p2 = strchr(p, ':'); if (p2 == NULL) { ok = false; break; } strlcpy(tmp, p, selname_len); tmp[p2 - p] = '\0'; p = p2 + 1; rb_hash_aset(h, ID2SYM(rb_intern(tmp)), argv[i]); } free(tmp); tmp = NULL; if (ok) { argc = 2; ((VALUE *)argv)[1] = h; // bad, I know... Method m = class_getInstanceMethod(klass, sel); if (m != NULL) { method = m; cache_method = false; goto recache2; } } } } // Enable helpers for classes which are not RubyObject based. if ((RCLASS_VERSION(klass) & RCLASS_IS_OBJECT_SUBCLASS) != RCLASS_IS_OBJECT_SUBCLASS) { // Let's try to see if we are not given a helper selector. SEL new_sel = helper_sel(selname, selname_len); if (new_sel != NULL) { Method m = class_getInstanceMethod(klass, new_sel); if (m != NULL) { sel = new_sel; method = m; // We need to invert arguments because // #[]= and setObject:forKey: take arguments // in a reverse order if (new_sel == selSetObjectForKey && argc == 2) { VALUE swap = argv[0]; ((VALUE *)argv)[0] = argv[1]; ((VALUE *)argv)[1] = swap; cache_method = false; } goto recache2; } } } // Let's see if we are not trying to call a BridgeSupport function. if (selname[selname_len - 1] == ':') { selname_len--; } std::string name(selname, selname_len); bs_element_function_t *bs_func = GET_CORE()->find_bs_function(name); if (bs_func != NULL) { if ((unsigned)argc < bs_func->args_count || ((unsigned)argc > bs_func->args_count && bs_func->variadic == false)) { rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, bs_func->args_count); } std::string types; vm_gen_bs_func_types(argc, argv, bs_func, types); cache->flag = MCACHE_FCALL; cache->sel = sel; cache->klass = klass; cache->as.fcall.bs_function = bs_func; cache->as.fcall.imp = (IMP)dlsym(RTLD_DEFAULT, bs_func->name); assert(cache->as.fcall.imp != NULL); cache->as.fcall.stub = (rb_vm_c_stub_t *)GET_CORE()->gen_stub( types, bs_func->variadic, bs_func->args_count, false); } else { // Still nothing, then let's call #method_missing. goto call_method_missing; } } } dispatch: if (cache->flag & MCACHE_RCALL) { if (!cache_method) { cache->flag = 0; } #if ROXOR_VM_DEBUG printf("ruby dispatch %c[<%s %p> %s] (imp %p block %p argc %d opt %d cache %p cached %s)\n", class_isMetaClass(klass) ? '+' : '-', class_getName(klass), (void *)self, sel_getName(sel), cache->as.rcall.node->ruby_imp, block, argc, opt, cache, cached ? "true" : "false"); #endif bool block_already_current = vm->is_block_current(block); Class current_klass = vm->get_current_class(); if (!block_already_current) { vm->add_current_block(block); } vm->set_current_class(NULL); Class old_current_super_class = vm->get_current_super_class(); vm->set_current_super_class(current_super_class); SEL old_current_super_sel = vm->get_current_super_sel(); vm->set_current_super_sel(current_super_sel); const bool should_pop_broken_with = sel != selInitialize && sel != selInitialize2; struct Finally { bool block_already_current; Class current_class; Class current_super_class; SEL current_super_sel; bool should_pop_broken_with; RoxorVM *vm; Finally(bool _block_already_current, Class _current_class, Class _current_super_class, SEL _current_super_sel, bool _should_pop_broken_with, RoxorVM *_vm) { block_already_current = _block_already_current; current_class = _current_class; current_super_class = _current_super_class; current_super_sel = _current_super_sel; should_pop_broken_with = _should_pop_broken_with; vm = _vm; } ~Finally() { if (!block_already_current) { vm->pop_current_block(); } vm->set_current_class(current_class); if (should_pop_broken_with) { vm->pop_broken_with(); } vm->set_current_super_class(current_super_class); vm->set_current_super_sel(current_super_sel); vm->pop_current_binding(); } } finalizer(block_already_current, current_klass, old_current_super_class, old_current_super_sel, should_pop_broken_with, vm); // DTrace probe: method__entry if (MACRUBY_METHOD_ENTRY_ENABLED()) { char *class_name = (char *)rb_class2name((VALUE)klass); char *method_name = (char *)sel_getName(sel); char file[PATH_MAX]; unsigned long line = 0; GET_CORE()->symbolize_backtrace_entry(1, file, sizeof file, &line, NULL, 0); MACRUBY_METHOD_ENTRY(class_name, method_name, file, line); } VALUE v = ruby_dispatch(top, self, sel, cache->as.rcall.node, opt, argc, argv); // DTrace probe: method__return if (MACRUBY_METHOD_RETURN_ENABLED()) { char *class_name = (char *)rb_class2name((VALUE)klass); char *method_name = (char *)sel_getName(sel); char file[PATH_MAX]; unsigned long line = 0; GET_CORE()->symbolize_backtrace_entry(1, file, sizeof file, &line, NULL, 0); MACRUBY_METHOD_RETURN(class_name, method_name, file, line); } return v; } else if (cache->flag & MCACHE_OCALL) { if (cache->as.ocall.argc != argc) { goto recache; } if (!cache_method) { cache->flag = 0; } if (block != NULL) { rb_warn("passing a block to an Objective-C method - " \ "will be ignored"); } else if (sel == selNew) { if (self == rb_cNSMutableArray) { self = rb_cRubyArray; } } else if (sel == selClass) { // Because +[NSObject class] returns self. if (RCLASS_META(klass)) { return RCLASS_MODULE(self) ? rb_cModule : rb_cClass; } // Because the CF classes should be hidden, for Ruby compat. if (self == Qnil) { return rb_cNilClass; } if (self == Qtrue) { return rb_cTrueClass; } if (self == Qfalse) { return rb_cFalseClass; } return rb_class_real((VALUE)klass, true); } #if ROXOR_VM_DEBUG printf("objc dispatch %c[<%s %p> %s] imp=%p cache=%p argc=%d (cached=%s)\n", class_isMetaClass(klass) ? '+' : '-', class_getName(klass), (void *)self, sel_getName(sel), cache->as.ocall.imp, cache, argc, cached ? "true" : "false"); #endif id ocrcv = RB2OC(self); if (cache->as.ocall.bs_method != NULL) { Class ocklass = object_getClass(ocrcv); for (int i = 0; i < (int)cache->as.ocall.bs_method->args_count; i++) { bs_element_arg_t *arg = &cache->as.ocall.bs_method->args[i]; if (arg->sel_of_type != NULL) { // 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. const int arg_i = arg->index; assert(arg_i >= 0 && arg_i < argc); if (argv[arg_i] != Qnil) { ID arg_selid = rb_to_id(argv[arg_i]); SEL arg_sel = sel_registerName(rb_id2name(arg_selid)); if (reinstall_method_maybe(ocklass, arg_sel, arg->sel_of_type)) { goto sel_target_found; } for (int j = 0; j < argc; j++) { if (j != arg_i && !SPECIAL_CONST_P(argv[j])) { if (reinstall_method_maybe(*(Class *)argv[j], arg_sel, arg->sel_of_type)) { goto sel_target_found; } } } } sel_target_found: // There can only be one sel_of_type argument. break; } } } return __rb_vm_objc_dispatch(cache->as.ocall.stub, cache->as.ocall.imp, ocrcv, sel, argc, argv); } else if (cache->flag & MCACHE_FCALL) { #if ROXOR_VM_DEBUG printf("C dispatch %s() imp=%p argc=%d (cached=%s)\n", cache->as.fcall.bs_function->name, cache->as.fcall.imp, argc, cached ? "true" : "false"); #endif return (*cache->as.fcall.stub)(cache->as.fcall.imp, argc, argv); } printf("method dispatch is b0rked\n"); abort(); call_method_missing: // Before calling method_missing, let's check if we are not in the following // cases: // // def foo; end; foo(42) // def foo(x); end; foo // // If yes, we need to raise an ArgumentError exception instead. const char *selname = sel_getName(sel); const size_t selname_len = strlen(selname); SEL new_sel = 0; if (argc > 0 && selname[selname_len - 1] == ':') { char buf[100]; assert(sizeof buf > selname_len - 1); strlcpy(buf, selname, sizeof buf); buf[selname_len - 1] = '\0'; new_sel = sel_registerName(buf); } else if (argc == 0) { char buf[100]; snprintf(buf, sizeof buf, "%s:", selname); new_sel = sel_registerName(buf); } if (new_sel != 0) { Method m = class_getInstanceMethod(klass, new_sel); if (m != NULL) { IMP mimp = method_getImplementation(m); if (!UNAVAILABLE_IMP(mimp)) { unsigned expected_argc; rb_vm_method_node_t *node = GET_CORE()->method_node_get(m); if (node != NULL) { expected_argc = node->arity.min; } else { expected_argc = rb_method_getNumberOfArguments(m); expected_argc -= 2; // removing receiver and selector } rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", argc, expected_argc); } } } rb_vm_method_missing_reason_t status; if (opt & DISPATCH_VCALL) { status = METHOD_MISSING_VCALL; } else if (opt & DISPATCH_SUPER) { status = METHOD_MISSING_SUPER; } else { status = METHOD_MISSING_DEFAULT; } return method_missing((VALUE)self, sel, block, argc, argv, status); }
void msg_debug_super(struct objc_super *super, SEL op) { msg_debug(object_getClassName(super->receiver), sel_getName(op)); }
static Method rb_vm_super_lookup(Class klass, SEL sel, Class *super_class_p) { // Locate the current method implementation. Class self_class = klass; Method method = class_getInstanceMethod(self_class, sel); if (method == NULL) { // The given selector does not exist, let's go through // #method_missing... *super_class_p = NULL; return NULL; } IMP self_imp = method_getImplementation(method); // Iterate over ancestors, locate the current class and return the // super method, if it exists. VALUE ary = rb_mod_ancestors_nocopy((VALUE)klass); const int count = RARRAY_LEN(ary); bool klass_located = false; #if ROXOR_VM_DEBUG printf("locating super method %s of class %s (%p) in ancestor chain: ", sel_getName(sel), rb_class2name((VALUE)klass), klass); for (int i = 0; i < count; i++) { VALUE sk = RARRAY_AT(ary, i); printf("%s (%p) ", rb_class2name(sk), (void *)sk); } printf("\n"); #endif try_again: for (int i = 0; i < count; i++) { if (!klass_located && RARRAY_AT(ary, i) == (VALUE)self_class) { klass_located = true; } if (klass_located) { if (i < count - 1) { VALUE k = RARRAY_AT(ary, i + 1); #if ROXOR_VM_DEBUG printf("looking in %s\n", rb_class2name((VALUE)k)); #endif Method method = class_getInstanceMethod((Class)k, sel); if (method == NULL) { continue; } IMP imp = method_getImplementation(method); if (imp == self_imp || UNAVAILABLE_IMP(imp)) { continue; } VALUE super = RCLASS_SUPER(k); if (super != 0 && class_getInstanceMethod((Class)super, sel) == method) { continue; } #if ROXOR_VM_DEBUG printf("returning method %p of class %s (#%d)\n", method, rb_class2name(k), i + 1); #endif *super_class_p = (Class)k; return method; } } } if (!klass_located) { // Could not locate the receiver's class in the ancestors list. // It probably means that the receiver has been extended somehow. // We therefore assume that the super method will be in the direct // superclass. klass_located = true; goto try_again; } *super_class_p = NULL; return NULL; }
bool RoxorCore::respond_to(VALUE obj, VALUE klass, SEL sel, bool priv, bool check_override) { if (klass == Qnil) { klass = CLASS_OF(obj); } else { assert(!check_override); } IMP imp = NULL; const bool overriden = check_override ? ((imp = class_getMethodImplementation((Class)klass, selRespondTo)) != basic_respond_to_imp) : false; if (!overriden) { lock(); const long key = respond_to_key((Class)klass, sel); std::map<long, int>::iterator iter = respond_to_cache.find(key); int iter_cached = (iter != respond_to_cache.end()); unlock(); int status; if (iter_cached) { status = iter->second; } else { Method m = class_getInstanceMethod((Class)klass, sel); if (m == NULL) { const char *selname = sel_getName(sel); sel = helper_sel(selname, strlen(selname)); if (sel != NULL) { m = class_getInstanceMethod((Class)klass, sel); } } IMP imp = method_getImplementation(m); if (UNAVAILABLE_IMP(imp) || imp == (IMP)rb_f_notimplement) { status = RESPOND_TO_NOT_EXIST; } else { rb_vm_method_node_t *node = method_node_get(m); if (node != NULL && (node->flags & VM_METHOD_PRIVATE)) { status = RESPOND_TO_PRIVATE; } else { status = RESPOND_TO_PUBLIC; } } lock(); respond_to_cache[key] = status; unlock(); } return status == RESPOND_TO_PUBLIC || (priv && status == RESPOND_TO_PRIVATE); } else { if (imp == NULL || imp == _objc_msgForward) { // The class does not respond to respond_to?:, it's probably // NSProxy-based. return false; } VALUE args[2]; int n = 0; args[n++] = ID2SYM(rb_intern(sel_getName(sel))); if (priv) { rb_vm_method_node_t *node = method_node_get(imp); if (node != NULL && (2 < node->arity.min || (node->arity.max != -1 && 2 > node->arity.max))) { // Do nothing, custom respond_to? method incompatible arity. } else { args[n++] = Qtrue; } } return rb_vm_call(obj, selRespondTo, n, args) == Qtrue; } }
void msg_debug_super_fixup(struct objc_super *super, struct fixup *fixup) { msg_debug(object_getClassName(super->receiver), sel_getName(fixup->sel)); }
PRIMITIVE VALUE vm_sel_to_rval(void *sel) { return sel == 0 ? Qnil : ID2SYM(rb_intern(sel_getName((SEL)sel))); }
BOOL sel_isMapped(SEL selector) { return (sel_getName(selector) != NULL) ? YES : NO; }
void msg_debug_fixup(id self, struct fixup *fixup) { msg_debug(object_getClassName(self), sel_getName(fixup->sel)); }
{ objc_debug_log("Destroying dispatch tables.\n"); objc_rw_lock_destroy(&initialize_lock); SparseArrayDestroy(&uninstalled_dtable); slot_pool_free(); } static BOOL installMethodInDtable(Class class, Class owner, SparseArray *dtable, struct objc_method *method, BOOL replaceExisting) { objc_debug_log("Installing method %s into dtable of class %s\n", sel_getName(method->selector), class_getName(class)); objc_assert(uninstalled_dtable != dtable, ""); SEL sel_id = method->selector; struct objc_slot *slot = SparseArrayLookup(dtable, sel_id); if (NULL != slot) { // If this method is the one already installed, pretend to install it again. if (slot->implementation == method->implementation) { return NO; } // If the existing slot is for this class, we can just replace the // implementation. We don't need to bump the version; this operation // updates cached slots, it doesn't invalidate them. if (slot->owner == owner) { // Don't replace methods if we're not meant to (if they're from
void msg_debug_regular(id self, SEL op) { msg_debug(object_getClassName(self), sel_getName(op)); }