bool InlineCache::fill_method_missing(STATE, Module* module) { MethodTableBucket* entry; Symbol* name = G(sym_method_missing); do { entry = module->method_table()->find_entry(state, name); /* Nothing, there? Ok, keep looking. */ if(entry) { /* A 'false' method means to terminate method lookup. * (eg. undef_method) */ if(entry->undef_p(state)) return false; /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks. * * This is pretty much always where a subclass marks a superclass * method as public. We don't move the method, we just put this * marker into the method table. */ if(!entry->method()->nil_p()) { if(Alias* alias = try_as<Alias>(entry->method())) { this->method = alias->original_exec(); this->module = alias->original_module(); } else { this->method = entry->method(); this->module = module; } return true; } } module = module->superclass(); /* No more places to look, we couldn't find it. */ if(module->nil_p()) return false; } while(1); // Shouldn't be here! rubinius::abort(); }
Object* MethodTable::alias(STATE, Symbol* name, Symbol* vis, Symbol* orig_name, Object* orig_method, Module* orig_mod) { check_frozen(state); utilities::thread::SpinLock::LockGuard lg(lock_); Executable* orig_exec; if(Alias* alias = try_as<Alias>(orig_method)) { orig_exec = alias->original_exec(); orig_mod = alias->original_module(); orig_name = alias->original_name(); } else if(orig_method->nil_p()) { orig_exec = nil<Executable>(); } else { orig_exec = as<Executable>(orig_method); } Alias* method = Alias::create(state, orig_name, orig_mod, orig_exec); native_int num_entries = entries_->to_native(); native_int num_bins = bins_->to_native(); if(max_density_p(num_entries, num_bins)) { redistribute(state, num_bins <<= 1); } native_int bin = find_bin(key_hash(name), num_bins); MethodTableBucket* entry = try_as<MethodTableBucket>(values_->at(state, bin)); MethodTableBucket* last = NULL; while(entry) { if(entry->name() == name) { entry->method(state, method); entry->visibility(state, vis); return name; } last = entry; entry = try_as<MethodTableBucket>(entry->next()); } if(last) { last->next(state, MethodTableBucket::create(state, name, method, vis)); } else { values_->put(state, bin, MethodTableBucket::create(state, name, method, vis)); } entries(state, Fixnum::from(num_entries + 1)); return name; }
MethodTableBucket* MethodTableBucket::create(STATE, Symbol* name, Executable* method, Symbol* vis) { MethodTableBucket *entry = state->new_object<MethodTableBucket>(G(methtblbucket)); entry->name(state, name); entry->method(state, method); entry->visibility(state, vis); return entry; }
Executable* Module::find_method(Symbol* name, Module** defined_in) { Module* mod = this; do { MethodTableBucket* buk = mod->method_table()->find_entry(name); if(buk) { if(defined_in) *defined_in = mod; return buk->method(); } mod = mod->superclass(); } while(!mod->nil_p()); return NULL; }
MethodTableBucket* MethodTableBucket::create(STATE, Symbol* name, Object* method_id, Object* method, Object* scope, Fixnum* serial, Symbol* vis) { MethodTableBucket *entry = state->new_object<MethodTableBucket>(G(methtblbucket)); entry->name(state, name); entry->method_id(state, method_id); entry->method(state, method); entry->scope(state, scope); entry->serial(state, serial); entry->visibility(state, vis); return entry; }
Object* MethodTable::store(STATE, Symbol* name, Object* exec, Symbol* vis) { unsigned int num_entries, num_bins, bin; MethodTableBucket* entry; MethodTableBucket* last = NULL; Executable* method; if(exec->nil_p()) { method = reinterpret_cast<Executable*>(Qnil); } else { if(Alias* alias = try_as<Alias>(exec)) { method = alias->original_exec(); } else { method = as<Executable>(exec); } } num_entries = entries_->to_native(); num_bins = bins_->to_native(); if(max_density_p(num_entries, num_bins)) { redistribute(state, num_bins <<= 1); } bin = find_bin(key_hash(name), num_bins); entry = try_as<MethodTableBucket>(values_->at(state, bin)); while(entry) { if(entry->name() == name) { entry->method(state, method); entry->visibility(state, vis); return name; } last = entry; entry = try_as<MethodTableBucket>(entry->next()); } if(last) { last->next(state, MethodTableBucket::create(state, name, method, vis)); } else { values_->put(state, bin, MethodTableBucket::create(state, name, method, vis)); } entries(state, Fixnum::from(num_entries + 1)); return name; }
Object* MethodTable::alias(STATE, Symbol* name, Symbol* vis, Symbol* orig_name, Executable* orig_method, Module* orig_mod) { unsigned int num_entries, num_bins, bin; MethodTableBucket* entry; MethodTableBucket* last = NULL; if(Alias* alias = try_as<Alias>(orig_method)) { orig_method = alias->original_exec(); orig_mod = alias->original_module(); orig_name = alias->original_name(); } Alias* method = Alias::create(state, orig_name, orig_mod, orig_method); num_entries = entries_->to_native(); num_bins = bins_->to_native(); if(max_density_p(num_entries, num_bins)) { redistribute(state, num_bins <<= 1); } bin = find_bin(key_hash(name), num_bins); entry = try_as<MethodTableBucket>(values_->at(state, bin)); while(entry) { if(entry->name() == name) { entry->method(state, method); entry->visibility(state, vis); return name; } last = entry; entry = try_as<MethodTableBucket>(entry->next()); } if(last) { last->next(state, MethodTableBucket::create(state, name, method, vis)); } else { values_->put(state, bin, MethodTableBucket::create(state, name, method, vis)); } entries(state, Fixnum::from(num_entries + 1)); return name; }
MethodTable* MethodTable::duplicate(STATE) { size_t size, i; MethodTable *dup; size = bins_->to_native(); dup = MethodTable::create(state, size); // Allow for subclassing. dup->klass(state, class_object(state)); size_t num = entries_->to_native(); Array* entries = all_entries(state); for(i = 0; i < num; i++) { MethodTableBucket* entry = as<MethodTableBucket>(entries->get(state, i)); dup->store(state, entry->name(), entry->method(), entry->visibility()); } return dup; }
Executable* MethodTable::remove(STATE, Symbol* name) { check_frozen(state); utilities::thread::SpinLock::LockGuard lg(lock_); native_int num_entries = entries_->to_native(); native_int num_bins = bins_->to_native(); if(min_density_p(num_entries, num_bins) && (num_bins >> 1) >= METHODTABLE_MIN_SIZE) { redistribute(state, num_bins >>= 1); } native_int bin = find_bin(key_hash(name), num_bins); MethodTableBucket* entry = try_as<MethodTableBucket>(values_->at(state, bin)); MethodTableBucket* last = NULL; while(entry) { if(entry->name() == name) { Executable* val = entry->method(); if(last) { last->next(state, entry->next()); } else { values_->put(state, bin, entry->next()); } entries(state, Fixnum::from(entries_->to_native() - 1)); return val; } last = entry; entry = try_as<MethodTableBucket>(entry->next()); } return nil<Executable>(); }
Object* System::vm_check_super_callable(STATE, CallFrame* call_frame) { Module* mod = call_frame->module()->superclass(); MethodTableBucket* entry; Symbol* sym = call_frame->original_name(); while(!mod->nil_p()) { entry = mod->method_table()->find_entry(state, sym); if(entry) { if(entry->undef_p(state)) return Qfalse; // It's callable, ok, but see if we should see if it's just a stub // to change the visibility of another method. if(!entry->method()->nil_p()) { return Qtrue; } } mod = mod->superclass(); } return Qfalse; }
MethodMissingReason InlineCache::fill_public(STATE, Object* self, Symbol* name) { MethodTableBucket* entry; Module* module = klass_; // Check the global cache first! GlobalCache::cache_entry* global_entry = state->global_cache()->lookup(module, name); if(global_entry && global_entry->is_public && !global_entry->method_missing) { this->method = global_entry->method; this->module = global_entry->module; return eNone; } bool skip_vis_check = false; MethodTableBucket* vis_entry = 0; Symbol* target_name = name; do { entry = module->method_table()->find_entry(state, target_name); /* Nothing, there? Ok, keep looking. */ if(entry) { /* A 'false' method means to terminate method lookup. * (eg. undef_method) */ if(entry->undef_p(state)) return eNormal; if(!skip_vis_check) { /* The method is private, but this wasn't a private send. */ if(entry->private_p(state)) { return ePrivate; } else if(entry->protected_p(state)) { /* The method is protected, but it's not being called from * the same module */ Module* check_mod; if(IncludedModule* im = try_as<IncludedModule>(module)) { check_mod = im->module(); } else { check_mod = module; } if(!self->kind_of_p(state, check_mod)) { return eProtected; } } } /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks. * * This is pretty much always where a subclass marks a superclass * method as public. We don't move the method, we just put this * marker into the method table. */ if(entry->method()->nil_p()) { vis_entry = entry; skip_vis_check = true; } else { Module* use_module; Executable* use_exec; if(Alias* alias = try_as<Alias>(entry->method())) { // Same check as above, allow an alias to be for a superclass // method. if(alias->original_exec()->nil_p()) { vis_entry = entry; skip_vis_check = true; use_exec = 0; target_name = alias->original_name(); } else { use_exec = alias->original_exec(); use_module = alias->original_module(); } } else { use_module = module; use_exec = entry->method(); } if(use_exec) { this->module = use_module; this->method = use_exec; if(!vis_entry) vis_entry = entry; state->global_cache()->retain(state, klass_, name, this->module, this->method, false, !vis_entry->public_p(state)); return eNone; } } } module = module->superclass(); /* No more places to look, we couldn't find it. */ if(module->nil_p()) return eNormal; } while(1); // Shouldn't be here! rubinius::abort(); }
bool InlineCache::fill_private(STATE, Symbol* name, Module* start) { MethodTableBucket* entry; Module* module = start; // Check the global cache first! GlobalCache::cache_entry* global_entry = state->global_cache()->lookup(module, name); if(global_entry && !global_entry->method_missing) { this->method = global_entry->method; this->module = global_entry->module; return true; } MethodTableBucket* vis_entry = 0; do { entry = module->method_table()->find_entry(state, name); /* Nothing, there? Ok, keep looking. */ if(entry) { /* A 'false' method means to terminate method lookup. * (eg. undef_method) */ if(entry->undef_p(state)) return false; /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks. * * This is pretty much always where a subclass marks a superclass * method as public. We don't move the method, we just put this * marker into the method table. */ if(!entry->method()->nil_p()) { if(Alias* alias = try_as<Alias>(entry->method())) { this->method = alias->original_exec(); this->module = alias->original_module(); } else { this->method = entry->method(); this->module = module; } if(!vis_entry) vis_entry = entry; state->global_cache()->retain(state, start, name, this->module, this->method, false, !vis_entry->public_p(state)); return true; } else { // Remember this entry as defining the visibility vis_entry = entry; } } module = module->superclass(); /* No more places to look, we couldn't find it. */ if(module->nil_p()) return false; } while(1); // Shouldn't be here! rubinius::abort(); }
static bool hierarchy_resolve(STATE, Symbol* name, Dispatch& msg, LookupData& lookup) { Module* module = lookup.from; MethodTableBucket* entry; bool skip_vis_check = false; do { if(module != module->origin()) { module = module->superclass(); } entry = module->method_table()->find_entry(state, name); /* Nothing, there? Ok, keep looking. */ if(!entry) goto keep_looking; /* A 'false' method means to terminate method lookup. * (eg. undef_method) */ if(entry->undef_p(state)) { msg.method_missing = eNormal; return false; } /* If this was a private send, then we can handle use * any method seen. */ if(lookup.min_visibility == G(sym_private) || skip_vis_check) { /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks. If we are skipping visibility checks we * shouldn't update visibility anymore because the implementation * might have a different visibility than the original lookup. */ if(!skip_vis_check) { msg.visibility = entry->visibility(); } if(entry->method()->nil_p() && entry->method_id()->nil_p()) { // TODO: fix using method() == cNil for this // if(entry->method()->nil_p()) { skip_vis_check = true; goto keep_looking; } if(Alias* alias = try_as<Alias>(entry->method())) { if(alias->original_exec()->nil_p()) { name = alias->original_name(); msg.visibility = entry->visibility(); skip_vis_check = true; } else { msg.method = alias->original_exec(); msg.module = alias->original_module(); } } else { msg.method = entry->get_method(state); msg.module = module; } if(msg.method) return true; } else { /* The method is private, but this wasn't a private send. */ if(entry->private_p(state)) { msg.method_missing = ePrivate; return false; } else if(entry->protected_p(state)) { /* The method is protected, but it's not being called from * the same module, or we only want public methods. */ Module* check_mod; if(IncludedModule* im = try_as<IncludedModule>(module)) { check_mod = im->module(); } else { check_mod = module; } if(lookup.min_visibility == G(sym_public) && !lookup.recv->kind_of_p(state, check_mod)) { msg.method_missing = eProtected; return false; } } /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks. If we are skipping visibility checks we * shouldn't update visibility anymore because the implementation * might have a different visibility than the original lookup. */ if(!skip_vis_check) { msg.visibility = entry->visibility(); } if(entry->method()->nil_p() && entry->method_id()->nil_p()) { // TODO: fix using method() == cNil for this // if(entry->method()->nil_p()) { skip_vis_check = true; goto keep_looking; } if(Alias* alias = try_as<Alias>(entry->method())) { if(alias->original_exec()->nil_p()) { name = alias->original_name(); msg.visibility = entry->visibility(); skip_vis_check = true; } else { msg.method = alias->original_exec(); msg.module = alias->original_module(); } } else { msg.method = entry->get_method(state); msg.module = module; } if(msg.method) return true; } keep_looking: module = module->superclass(); /* No more places to look, we couldn't find it. */ if(module->nil_p()) { msg.method_missing = eNormal; return false; } } while(1); rubinius::bug("Control flow bug in method lookup"); }
MethodMissingReason InlineCache::fill_public(STATE, Object* self, Symbol* name) { MethodTableBucket* entry; Module* module = klass_; // Check the global cache first! GlobalCache::cache_entry* global_entry = state->global_cache->lookup(module, name); if(global_entry && global_entry->is_public && !global_entry->method_missing) { this->method = global_entry->method; this->module = global_entry->module; return eNone; } bool skip_vis_check = false; do { entry = module->method_table()->find_entry(state, name); /* Nothing, there? Ok, keep looking. */ if(entry) { /* A 'false' method means to terminate method lookup. * (eg. undef_method) */ if(entry->undef_p(state)) return eNormal; if(!skip_vis_check) { /* The method is private, but this wasn't a private send. */ if(entry->private_p(state)) { return ePrivate; } else if(entry->protected_p(state)) { /* The method is protected, but it's not being called from * the same module */ if(!self->kind_of_p(state, module)) { return eProtected; } } } /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks. * * This is pretty much always where a subclass marks a superclass * method as public. We don't move the method, we just put this * marker into the method table. */ if(entry->method()->nil_p()) { skip_vis_check = true; } else { this->method = entry->method(); this->module = module; state->global_cache->retain(state, klass_, name, this->module, this->method, false, !entry->public_p(state)); return eNone; } } module = module->superclass(); /* No more places to look, we couldn't find it. */ if(module->nil_p()) return eNormal; } while(1); // Shouldn't be here! rubinius::abort(); }
static bool hierarchy_resolve(STATE, Symbol* name, Dispatch& msg, LookupData& lookup, bool* was_private) { Module* module = lookup.from; MethodTableBucket* entry; bool skip_vis_check = false; do { entry = module->method_table()->find_entry(state, name); /* Nothing, there? Ok, keep looking. */ if(!entry) goto keep_looking; /* A 'false' method means to terminate method lookup. * (eg. undef_method) */ if(entry->undef_p(state)) return false; /* If this was a private send, then we can handle use * any method seen. */ if(lookup.priv || skip_vis_check) { /* nil means that the actual method object is 'up' from here */ if(entry->method()->nil_p()) goto keep_looking; *was_private = entry->private_p(state); if(Alias* alias = try_as<Alias>(entry->method())) { msg.method = alias->original_exec(); msg.module = alias->original_module(); } else { msg.method = entry->method(); msg.module = module; } break; } else { /* The method is private, but this wasn't a private send. */ if(entry->private_p(state)) { return false; } else if(entry->protected_p(state)) { /* The method is protected, but it's not being called from * the same module */ if(!lookup.recv->kind_of_p(state, module)) { return false; } } /* The method was callable, but we need to keep looking * for the implementation, so make the invocation bypass all further * visibility checks */ if(entry->method()->nil_p()) { skip_vis_check = true; goto keep_looking; } if(Alias* alias = try_as<Alias>(entry->method())) { msg.method = alias->original_exec(); msg.module = alias->original_module(); } else { msg.method = entry->method(); msg.module = module; } break; } keep_looking: module = module->superclass(); /* No more places to look, we couldn't find it. */ if(module->nil_p()) return false; } while(1); return true; }