VALUE rb_mod_init_copy(VALUE clone, SEL sel, VALUE orig) { rb_obj_init_copy(clone, 0, orig); VALUE super; if (!RCLASS_RUBY(orig)) { super = orig; rb_warn("cloning class `%s' is not supported, creating a " \ "subclass instead", rb_class2name(orig)); } else { super = RCLASS_SUPER(orig); } RCLASS_SET_SUPER(clone, super); // Copy flags. unsigned long version_flag = RCLASS_IS_RUBY_CLASS; if ((RCLASS_VERSION(super) & RCLASS_IS_OBJECT_SUBCLASS) == RCLASS_IS_OBJECT_SUBCLASS) { version_flag |= RCLASS_IS_OBJECT_SUBCLASS; } if (RCLASS_MODULE(orig)) { version_flag |= RCLASS_IS_MODULE; } RCLASS_SET_VERSION(clone, version_flag); if (!class_isMetaClass((Class)clone)) { // Clear type info. RCLASS_SET_VERSION(*(Class *)clone, RCLASS_VERSION(*(Class *)clone)); } // Copy methods. rb_vm_copy_methods((Class)orig, (Class)clone); if (!class_isMetaClass((Class)orig)) { rb_vm_copy_methods(*(Class *)orig, *(Class *)clone); } // Copy ivars. CFMutableDictionaryRef orig_dict = rb_class_ivar_dict(orig); CFMutableDictionaryRef clone_dict; if (orig_dict != NULL) { clone_dict = CFDictionaryCreateMutableCopy(NULL, 0, orig_dict); rb_class_ivar_set_dict(clone, clone_dict); CFMakeCollectable(clone_dict); } else { clone_dict = rb_class_ivar_dict_or_create(clone); } // Remove the classpath & classid (name) so that they are not // copied over the new module / class. CFDictionaryRemoveValue(clone_dict, (const void *)id_classpath); CFDictionaryRemoveValue(clone_dict, (const void *)id_classid); return clone; }
void rb_objc_class_sync_version(Class ocklass, Class ocsuper) { const long super_version = RCLASS_VERSION(ocsuper); long klass_version = RCLASS_VERSION(ocklass); if (ocsuper == (Class)rb_cRubyObject || (super_version & RCLASS_IS_OBJECT_SUBCLASS) == RCLASS_IS_OBJECT_SUBCLASS) { klass_version |= RCLASS_IS_OBJECT_SUBCLASS; } RCLASS_SET_VERSION(ocklass, klass_version); }
VALUE rb_singleton_class_clone(VALUE obj) { VALUE klass = RBASIC(obj)->klass; if (!RCLASS_SINGLETON(klass)) { return klass; } // Create new singleton class. VALUE clone = rb_objc_create_class(NULL, RCLASS_SUPER(klass)); // Copy ivars. CFMutableDictionaryRef ivar_dict = rb_class_ivar_dict(klass); if (ivar_dict != NULL) { CFMutableDictionaryRef cloned_ivar_dict = CFDictionaryCreateMutableCopy(NULL, 0, (CFDictionaryRef)ivar_dict); rb_class_ivar_set_dict(clone, cloned_ivar_dict); CFMakeCollectable(cloned_ivar_dict); } // Copy methods. rb_vm_copy_methods((Class)klass, (Class)clone); rb_singleton_class_attached(clone, obj); if (RCLASS_SUPER(clone) == rb_cRubyObject) { long v = RCLASS_VERSION(clone) ^ RCLASS_IS_OBJECT_SUBCLASS; RCLASS_SET_VERSION(clone, v); } RCLASS_SET_VERSION_FLAG(clone, RCLASS_IS_SINGLETON); return clone; }
void rb_define_object_special_methods(VALUE klass) { RCLASS_SET_VERSION(*(VALUE *)klass, (RCLASS_VERSION(*(VALUE *)klass) | RCLASS_HAS_ROBJECT_ALLOC)); rb_objc_define_method(*(VALUE *)klass, "new", rb_class_new_instance_imp, -1); rb_objc_define_method(klass, "dup", rb_obj_dup, 0); rb_objc_define_private_method(klass, "initialize", rb_objc_init, -1); rb_objc_define_private_method(klass, "initialize_copy", rb_obj_init_copy, 1); rb_objc_define_method(klass, "hash", rb_obj_id, 0); // To make sure singleton classes will be filtered. rb_objc_define_method(*(VALUE *)klass, "superclass", rb_obj_superclass, 0); rb_objc_define_method(klass, "class", rb_obj_class, 0); rb_objc_install_method(*(Class *)klass, selAllocWithZone, (IMP)rb_obj_imp_allocWithZone); rb_objc_install_method((Class)klass, selIsEqual, (IMP)rb_obj_imp_isEqual); rb_objc_install_method((Class)klass, selInit, (IMP)rb_obj_imp_init); rb_objc_install_method((Class)klass, selDescription, (IMP)rb_obj_imp_description); // Create -copyWithZone:, since the method doesn't exist yet we need to // find the type encoding somewhere, here we check Symbol since it's // created very early. Method m = class_getInstanceMethod((Class)rb_cSymbol, selCopyWithZone); assert(m != NULL); class_replaceMethod((Class)klass, selCopyWithZone, (IMP)rb_obj_imp_copyWithZone, method_getTypeEncoding(m)); }
VALUE rb_objc_create_class(const char *name, VALUE super) { if (super == 0) { super = rb_cObject; } VALUE klass = rb_objc_alloc_class(name, super, T_CLASS, rb_cClass); if (super != rb_cNSObject && super != 0 && ((RCLASS_VERSION(*(VALUE *)super) & RCLASS_HAS_ROBJECT_ALLOC) == RCLASS_HAS_ROBJECT_ALLOC)) { RCLASS_SET_VERSION(*(VALUE *)klass, (RCLASS_VERSION(*(VALUE *)klass) | RCLASS_HAS_ROBJECT_ALLOC)); } if (name != NULL && rb_class_tbl != NULL) { st_insert(rb_class_tbl, (st_data_t)rb_intern(name), (st_data_t)klass); } return klass; }
/* :nodoc: */ VALUE rb_mod_init_copy(VALUE clone, SEL sel, VALUE orig) { static ID classpath = 0; static ID classid = 0; rb_obj_init_copy(clone, 0, orig); { VALUE super; unsigned long version_flag; if (!RCLASS_RUBY(orig)) { super = orig; rb_warn("cloning class `%s' is not supported, creating a " \ "subclass instead", rb_class2name(orig)); } else { super = RCLASS_SUPER(orig); } RCLASS_SET_SUPER(clone, super); version_flag = RCLASS_IS_RUBY_CLASS; if ((RCLASS_VERSION(super) & RCLASS_IS_OBJECT_SUBCLASS) == RCLASS_IS_OBJECT_SUBCLASS) { version_flag |= RCLASS_IS_OBJECT_SUBCLASS; } RCLASS_SET_VERSION(clone, version_flag); rb_vm_copy_methods((Class)orig, (Class)clone); CFMutableDictionaryRef ivar_dict = rb_class_ivar_dict(orig); if (ivar_dict != NULL) { CFMutableDictionaryRef cloned_ivar_dict; if (classpath == 0) { classpath = rb_intern("__classpath__"); } if (classid == 0) { classid = rb_intern("__classid__"); } cloned_ivar_dict = CFDictionaryCreateMutableCopy(NULL, 0, (CFDictionaryRef)ivar_dict); // Remove the classpath & classid (name) so that they are not // copied over the new module / class CFDictionaryRemoveValue(cloned_ivar_dict, (const void *)classpath); CFDictionaryRemoveValue(cloned_ivar_dict, (const void *)classid); CFMakeCollectable(cloned_ivar_dict); rb_class_ivar_set_dict(clone, cloned_ivar_dict); } } return clone; }
VALUE rb_make_singleton_class(VALUE super) { VALUE klass = rb_objc_create_class(NULL, super); long v = RCLASS_VERSION(klass); if (super == rb_cRubyObject) { v ^= RCLASS_IS_OBJECT_SUBCLASS; } v |= RCLASS_IS_RUBY_CLASS; v |= RCLASS_IS_SINGLETON; RCLASS_SET_VERSION(klass, v); return klass; }
static VALUE specific_eval(int argc, VALUE *argv, VALUE klass, VALUE self) { VALUE retval; // XXX: not exception-safe const long old_version = RCLASS_VERSION(klass); if (rb_block_given_p()) { if (argc > 0) { rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc); } rb_vm_set_current_scope(klass, SCOPE_PUBLIC); retval = rb_vm_yield_under(klass, self, 1, &self); } else { const char *file = "(eval)"; int line = 1; if (argc == 0) { rb_raise(rb_eArgError, "block not supplied"); } if (rb_safe_level() >= 4) { StringValue(argv[0]); } else { SafeStringValue(argv[0]); } if (argc > 3) { const char *name = rb_id2name(rb_frame_callee()); rb_raise(rb_eArgError, "wrong number of arguments: %s(src) or %s{..}", name, name); } if (argc > 2) { line = NUM2INT(argv[2]); } if (argc > 1) { file = StringValuePtr(argv[1]); } rb_vm_set_current_scope(klass, SCOPE_PUBLIC); retval = eval_under(self, klass, argv[0], Qnil, file, line); } RCLASS_SET_VERSION(klass, old_version); return retval; }
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 rb_include_module2(VALUE klass, VALUE orig_klass, VALUE module, bool check, bool add_methods) { if (check) { rb_frozen_class_p(klass); if (!OBJ_TAINTED(klass)) { rb_secure(4); } Check_Type(module, T_MODULE); } // Register the module as included in the class. VALUE ary = rb_attr_get(klass, idIncludedModules); if (ary == Qnil) { ary = rb_ary_new(); rb_ivar_set(klass, idIncludedModules, ary); } else { if (rb_ary_includes(ary, module)) { return; } } rb_ary_insert(ary, 0, module); // Mark the module as included somewhere. const long v = RCLASS_VERSION(module) | RCLASS_IS_INCLUDED; RCLASS_SET_VERSION(module, v); // Register the class as included in the module. ary = rb_attr_get(module, idIncludedInClasses); if (ary == Qnil) { ary = rb_ary_new(); rb_ivar_set(module, idIncludedInClasses, ary); } rb_ary_push(ary, klass); // Delete the ancestors array if it exists, since we just changed it. CFMutableDictionaryRef iv_dict = rb_class_ivar_dict(klass); if (iv_dict != NULL) { CFDictionaryRemoveValue(iv_dict, (const void *)idAncestors); } if (add_methods) { // Copy methods. If original class has the basic -initialize and if the // module has a customized -initialize, we must copy the customized // version to the original class too. rb_vm_copy_methods((Class)module, (Class)klass); // When including into the class Class, also copy the methods to the // singleton class of NSObject. if (klass == rb_cClass || klass == rb_cModule) { rb_vm_copy_methods((Class)module, *(Class *)rb_cNSObject); } if (orig_klass != 0 && orig_klass != klass) { Method m = class_getInstanceMethod((Class)orig_klass, selInitialize); Method m2 = class_getInstanceMethod((Class)klass, selInitialize); if (m != NULL && m2 != NULL && method_getImplementation(m) == (IMP)rb_objc_init && method_getImplementation(m2) != (IMP)rb_objc_init) { rb_vm_copy_method((Class)orig_klass, m2); } } } }