mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
The assumption is that we're usually calling these methods on boot, and they don't need CCs. CCs scale with the number of classes that use these methods in the app, so if you have 10k classes and these classes use 3 of these methods on average, then that saves 30k CC objects. Methods on Module that are marked cold are: public, protected, private, module_function, ruby2_keywords, alias_method, undef_method, remove_method, attr_reader, attr_writer, attr_accessor, include, prepend. If these methods ever get redefined, they get new method definitions that are not marked uncacheable (CCs will get created). In one of our apps (not the largest), we observed this difference after boot: | method | before | after | delta | |-------------------|-------:|------:|------------:| | `include` | 4518 | 2 | -4516 | | `alias_method` | 3921 | 0 | -3921 | | `ruby2_keywords` | 3784 | 0 | -3784 | | `private` | 3519 | 3 | -3516 | | `attr_reader` | 2738 | 0 | -2738 | | `attr_accessor` | 2288 | 0 | -2288 | | `prepend` | 354 | 1 | -353 | | `protected` | 303 | 0 | -303 | | `public` | 302 | 0 | -302 | | `undef_method` | 178 | 0 | -178 | | `attr_writer` | 124 | 0 | -124 | | `module_function` | 42 | 0 | -42 | | `remove_method` | 24 | 0 | -24 | | **total** | | | **-22,089** | NOTE: CC tables and ccs (the malloced struct) are still being created, so this only reduces the CC objects themselves. I will tackle that in a follow-up.
3845 lines
122 KiB
C
3845 lines
122 KiB
C
/*
|
|
* This file is included by vm.c
|
|
*/
|
|
|
|
#include "id_table.h"
|
|
#include "yjit.h"
|
|
|
|
#define METHOD_DEBUG 0
|
|
|
|
static int vm_redefinition_check_flag(VALUE klass);
|
|
static void rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass);
|
|
static inline rb_method_entry_t *lookup_method_table(VALUE klass, ID id);
|
|
|
|
#define object_id idObject_id
|
|
#define added idMethod_added
|
|
#define singleton_added idSingleton_method_added
|
|
#define removed idMethod_removed
|
|
#define singleton_removed idSingleton_method_removed
|
|
#define undefined idMethod_undefined
|
|
#define singleton_undefined idSingleton_method_undefined
|
|
|
|
#define ruby_running (GET_VM()->running)
|
|
/* int ruby_running = 0; */
|
|
|
|
static enum rb_id_table_iterator_result
|
|
mark_cc_entry_i(VALUE ccs_ptr, void *data)
|
|
{
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
|
|
|
|
VM_ASSERT(vm_ccs_p(ccs));
|
|
|
|
if (METHOD_ENTRY_INVALIDATED(ccs->cme)) {
|
|
/* Never prune from a GC, only mark. A cc table walk (dup and friends) can trigger a
|
|
* GC from an allocation midway, so an xfree+DELETE while marking would derail the
|
|
* walking iterator into freed ccs. The mutator cleans up, under the VM lock. */
|
|
rb_gc_mark_movable((VALUE)ccs->cme);
|
|
for (int i = 0; i < ccs->len; i++) {
|
|
rb_gc_mark_movable((VALUE)ccs->entries[i].cc);
|
|
}
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
else {
|
|
rb_gc_mark_movable((VALUE)ccs->cme);
|
|
|
|
for (int i = 0; i < ccs->len; i++) {
|
|
const struct rb_callcache *cc = ccs->entries[i].cc;
|
|
VM_ASSERT(cc->klass == Qundef || vm_cc_check_cme(cc, ccs->cme));
|
|
|
|
rb_gc_mark_movable((VALUE)cc);
|
|
}
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
vm_cc_table_mark(void *data)
|
|
{
|
|
struct rb_id_table *tbl = (struct rb_id_table *)data;
|
|
if (tbl) {
|
|
rb_id_table_foreach_values(tbl, mark_cc_entry_i, NULL);
|
|
}
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
cc_table_free_i(VALUE ccs_ptr, void *data)
|
|
{
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
|
|
VM_ASSERT(vm_ccs_p(ccs));
|
|
|
|
ruby_xfree_sized(ccs, vm_ccs_alloc_size(ccs->capa));
|
|
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
vm_cc_table_free(void *data)
|
|
{
|
|
struct rb_id_table *tbl = (struct rb_id_table *)data;
|
|
|
|
rb_id_table_foreach_values(tbl, cc_table_free_i, NULL);
|
|
rb_managed_id_table_type.function.dfree(data);
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
cc_table_memsize_i(VALUE ccs_ptr, void *data_ptr)
|
|
{
|
|
size_t *total_size = data_ptr;
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
|
|
*total_size += sizeof(*ccs);
|
|
*total_size += sizeof(ccs->entries[0]) * ccs->capa;
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static size_t
|
|
vm_cc_table_memsize(const void *data)
|
|
{
|
|
size_t memsize = rb_managed_id_table_type.function.dsize(data);
|
|
struct rb_id_table *tbl = (struct rb_id_table *)data;
|
|
rb_id_table_foreach_values(tbl, cc_table_memsize_i, &memsize);
|
|
return memsize;
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
compact_cc_entry_i(VALUE ccs_ptr, void *data)
|
|
{
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_ptr;
|
|
|
|
rb_gc_update_moved_ptr(&ccs->cme);
|
|
VM_ASSERT(vm_ccs_p(ccs));
|
|
|
|
for (int i=0; i<ccs->len; i++) {
|
|
rb_gc_update_moved_ptr(&ccs->entries[i].cc);
|
|
}
|
|
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
vm_cc_table_compact(void *data)
|
|
{
|
|
struct rb_id_table *tbl = (struct rb_id_table *)data;
|
|
rb_id_table_foreach_values(tbl, compact_cc_entry_i, NULL);
|
|
}
|
|
|
|
static const rb_data_type_t cc_table_type = {
|
|
.wrap_struct_name = "VM/cc_table",
|
|
.function = {
|
|
.dmark = vm_cc_table_mark,
|
|
.dfree = vm_cc_table_free,
|
|
.dsize = vm_cc_table_memsize,
|
|
.dcompact = vm_cc_table_compact,
|
|
},
|
|
.parent = &rb_managed_id_table_type,
|
|
.flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE,
|
|
};
|
|
|
|
VALUE
|
|
rb_vm_cc_table_create(size_t capa)
|
|
{
|
|
return rb_managed_id_table_create(&cc_table_type, capa);
|
|
}
|
|
|
|
static void vm_ccs_invalidate(struct rb_class_cc_entries *ccs);
|
|
|
|
static enum rb_id_table_iterator_result
|
|
vm_cc_table_dup_i(ID key, VALUE old_ccs_ptr, void *data)
|
|
{
|
|
VALUE new_table = (VALUE)data;
|
|
struct rb_class_cc_entries *old_ccs = (struct rb_class_cc_entries *)old_ccs_ptr;
|
|
|
|
if (METHOD_ENTRY_INVALIDATED(old_ccs->cme)) {
|
|
// At this point, old_ccs is valid and hasn't been freed.
|
|
// However once we allocate below, mark_cc_entry_i may free the entries
|
|
// If this is invalidated, we should avoid the copy, and invalidate the CCs
|
|
// since later we will CAS the new cc_table, disconnecting old_ccs and it
|
|
// may not be marked.
|
|
// We don't want to copy this anyways since it's invalidated.
|
|
vm_ccs_invalidate(old_ccs);
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
size_t memsize = vm_ccs_alloc_size(old_ccs->capa);
|
|
struct rb_class_cc_entries *new_ccs = ruby_xcalloc(1, memsize);
|
|
rb_managed_id_table_insert(new_table, key, (VALUE)new_ccs);
|
|
|
|
// We hold the VM lock, so invalidation should not have happened between
|
|
// our earlier invalidation check and now.
|
|
VM_ASSERT(!METHOD_ENTRY_INVALIDATED(old_ccs->cme));
|
|
|
|
memcpy(new_ccs, old_ccs, memsize);
|
|
|
|
#if VM_CHECK_MODE > 0
|
|
new_ccs->debug_sig = ~(VALUE)new_ccs;
|
|
#endif
|
|
|
|
RB_OBJ_WRITTEN(new_table, Qundef, (VALUE)new_ccs->cme);
|
|
for (int index = 0; index < new_ccs->len; index++) {
|
|
RB_OBJ_WRITTEN(new_table, Qundef, new_ccs->entries[index].cc);
|
|
}
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
VALUE
|
|
rb_vm_cc_table_dup(VALUE old_table)
|
|
{
|
|
ASSERT_vm_locking();
|
|
VALUE new_table = rb_vm_cc_table_create(rb_managed_id_table_size(old_table));
|
|
rb_managed_id_table_foreach(old_table, vm_cc_table_dup_i, (void *)new_table);
|
|
return new_table;
|
|
}
|
|
|
|
static void
|
|
vm_ccs_invalidate(struct rb_class_cc_entries *ccs)
|
|
{
|
|
for (int i=0; i<ccs->len; i++) {
|
|
const struct rb_callcache *cc = ccs->entries[i].cc;
|
|
if (cc->klass == Qundef) continue; // already invalidated
|
|
VM_ASSERT(!vm_cc_super_p(cc) && !vm_cc_refinement_p(cc));
|
|
vm_cc_invalidate(cc);
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_vm_ccs_invalidate_and_free(struct rb_class_cc_entries *ccs)
|
|
{
|
|
RB_DEBUG_COUNTER_INC(ccs_free);
|
|
vm_ccs_invalidate(ccs);
|
|
ruby_xfree_sized(ccs, vm_ccs_alloc_size(ccs->capa));
|
|
}
|
|
|
|
void
|
|
rb_vm_cc_table_delete(VALUE table, ID mid)
|
|
{
|
|
VALUE ccs_obj;
|
|
if (rb_managed_id_table_lookup(table, mid, &ccs_obj)) {
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_obj;
|
|
rb_managed_id_table_delete(table, mid);
|
|
rb_vm_ccs_invalidate_and_free(ccs);
|
|
}
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
vm_ccs_dump_i(ID mid, VALUE val, void *data)
|
|
{
|
|
const struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)val;
|
|
fprintf(stderr, " | %s (len:%d) ", rb_id2name(mid), ccs->len);
|
|
rp(ccs->cme);
|
|
|
|
for (int i=0; i<ccs->len; i++) {
|
|
rp_m( " | \t", ccs->entries[i].cc);
|
|
}
|
|
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static void
|
|
vm_ccs_dump(VALUE klass, ID target_mid)
|
|
{
|
|
VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass);
|
|
if (cc_tbl) {
|
|
VALUE ccs;
|
|
if (target_mid) {
|
|
if (rb_managed_id_table_lookup(cc_tbl, target_mid, &ccs)) {
|
|
fprintf(stderr, " [CCTB] %p\n", (void *)cc_tbl);
|
|
vm_ccs_dump_i(target_mid, ccs, NULL);
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, " [CCTB] %p\n", (void *)cc_tbl);
|
|
rb_managed_id_table_foreach(cc_tbl, vm_ccs_dump_i, (void *)target_mid);
|
|
}
|
|
}
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
vm_cme_dump_i(ID mid, VALUE val, void *data)
|
|
{
|
|
ID target_mid = (ID)data;
|
|
if (target_mid == 0 || mid == target_mid) {
|
|
rp_m(" > ", val);
|
|
}
|
|
return ID_TABLE_CONTINUE;
|
|
}
|
|
|
|
static VALUE
|
|
vm_mtbl_dump(VALUE klass, ID target_mid)
|
|
{
|
|
fprintf(stderr, "# vm_mtbl\n");
|
|
while (klass) {
|
|
rp_m(" -> ", klass);
|
|
VALUE me;
|
|
|
|
if (RCLASS_M_TBL(klass)) {
|
|
if (target_mid != 0) {
|
|
if (rb_id_table_lookup(RCLASS_M_TBL(klass), target_mid, &me)) {
|
|
rp_m(" [MTBL] ", me);
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, " ## RCLASS_M_TBL (%p)\n", (void *)RCLASS_M_TBL(klass));
|
|
rb_id_table_foreach(RCLASS_M_TBL(klass), vm_cme_dump_i, NULL);
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, " MTBL: NULL\n");
|
|
}
|
|
if (RCLASS_WRITABLE_CALLABLE_M_TBL(klass)) {
|
|
if (target_mid != 0) {
|
|
if (rb_id_table_lookup(RCLASS_WRITABLE_CALLABLE_M_TBL(klass), target_mid, &me)) {
|
|
rp_m(" [CM**] ", me);
|
|
}
|
|
}
|
|
else {
|
|
fprintf(stderr, " ## RCLASS_CALLABLE_M_TBL\n");
|
|
rb_id_table_foreach(RCLASS_WRITABLE_CALLABLE_M_TBL(klass), vm_cme_dump_i, NULL);
|
|
}
|
|
}
|
|
if (RCLASS_WRITABLE_CC_TBL(klass)) {
|
|
vm_ccs_dump(klass, target_mid);
|
|
}
|
|
klass = RCLASS_SUPER(klass);
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
void
|
|
rb_vm_mtbl_dump(const char *msg, VALUE klass, ID target_mid)
|
|
{
|
|
fprintf(stderr, "[%s] ", msg);
|
|
vm_mtbl_dump(klass, target_mid);
|
|
}
|
|
|
|
static inline void
|
|
vm_cme_invalidate(rb_callable_method_entry_t *cme)
|
|
{
|
|
VM_ASSERT(IMEMO_TYPE_P(cme, imemo_ment), "cme: %d", imemo_type((VALUE)cme));
|
|
VM_ASSERT(callable_method_entry_p(cme));
|
|
METHOD_ENTRY_INVALIDATED_SET(cme);
|
|
RB_DEBUG_COUNTER_INC(cc_cme_invalidate);
|
|
|
|
rb_yjit_cme_invalidate(cme);
|
|
rb_zjit_cme_invalidate(cme);
|
|
}
|
|
|
|
static int
|
|
rb_clear_constant_cache_for_id_i(st_data_t ic, st_data_t arg)
|
|
{
|
|
((IC) ic)->entry = NULL;
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
void
|
|
rb_clear_constant_cache_for_id(ID id)
|
|
{
|
|
VALUE lookup_result;
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
if (rb_id_table_lookup(&vm->constant_cache, id, &lookup_result)) {
|
|
set_table *ics = (set_table *)lookup_result;
|
|
set_table_foreach(ics, rb_clear_constant_cache_for_id_i, (st_data_t) NULL);
|
|
ruby_vm_constant_cache_invalidations += ics->num_entries;
|
|
}
|
|
|
|
rb_yjit_constant_state_changed(id);
|
|
rb_zjit_constant_state_changed(id);
|
|
}
|
|
|
|
static void
|
|
invalidate_negative_cache(ID mid)
|
|
{
|
|
VALUE cme;
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
if (rb_id_table_lookup(&vm->negative_cme_table, mid, &cme)) {
|
|
rb_id_table_delete(&vm->negative_cme_table, mid);
|
|
vm_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_negative);
|
|
}
|
|
}
|
|
|
|
const rb_method_entry_t * rb_method_entry_clone(const rb_method_entry_t *src_me);
|
|
static const rb_callable_method_entry_t *complemented_callable_method_entry(VALUE klass, ID id);
|
|
static const rb_callable_method_entry_t *lookup_overloaded_cme(const rb_callable_method_entry_t *cme);
|
|
|
|
static void
|
|
invalidate_method_cache_in_cc_table(VALUE tbl, ID mid)
|
|
{
|
|
VALUE ccs_data;
|
|
if (tbl && rb_managed_id_table_lookup(tbl, mid, &ccs_data)) {
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_data;
|
|
rb_yjit_cme_invalidate((rb_callable_method_entry_t *)ccs->cme);
|
|
rb_zjit_cme_invalidate((rb_callable_method_entry_t *)ccs->cme);
|
|
if (NIL_P(ccs->cme->owner)) invalidate_negative_cache(mid);
|
|
rb_vm_ccs_invalidate_and_free(ccs);
|
|
rb_managed_id_table_delete(tbl, mid);
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_leaf_ccs);
|
|
}
|
|
}
|
|
|
|
static void
|
|
invalidate_callable_method_entry_in_callable_m_table(struct rb_id_table *tbl, ID mid)
|
|
{
|
|
VALUE cme;
|
|
if (tbl && rb_id_table_lookup(tbl, mid, &cme)) {
|
|
rb_yjit_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
rb_zjit_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
rb_id_table_delete(tbl, mid);
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_leaf_callable);
|
|
}
|
|
}
|
|
|
|
struct invalidate_callable_method_entry_foreach_arg {
|
|
VALUE klass;
|
|
ID mid;
|
|
const rb_method_entry_t *cme;
|
|
const rb_method_entry_t *newer;
|
|
};
|
|
|
|
static void
|
|
invalidate_callable_method_entry_in_every_m_table_i(rb_classext_t *ext, bool is_prime, VALUE box_value, void *data)
|
|
{
|
|
st_data_t me;
|
|
struct invalidate_callable_method_entry_foreach_arg *arg = (struct invalidate_callable_method_entry_foreach_arg *)data;
|
|
struct rb_id_table *tbl = RCLASSEXT_M_TBL(ext);
|
|
|
|
if (rb_id_table_lookup(tbl, arg->mid, &me) && arg->cme == (const rb_method_entry_t *)me) {
|
|
rb_method_table_insert(arg->klass, tbl, arg->mid, arg->newer);
|
|
}
|
|
}
|
|
|
|
struct collect_per_box_origins_arg {
|
|
VALUE owner;
|
|
VALUE klass_housing_cme;
|
|
VALUE origins; // Array of origins
|
|
};
|
|
|
|
static void
|
|
collect_per_box_origins_i(rb_classext_t *ext, bool is_prime, VALUE box_value, void *data)
|
|
{
|
|
struct collect_per_box_origins_arg *arg = (struct collect_per_box_origins_arg *)data;
|
|
VALUE origin = RCLASSEXT_ORIGIN(ext);
|
|
|
|
if (origin == arg->owner || origin == arg->klass_housing_cme) {
|
|
return;
|
|
}
|
|
rb_len_t len = RARRAY_LEN(arg->origins);
|
|
for (rb_len_t i = 0; i < len; i++) {
|
|
if (RARRAY_AREF(arg->origins, i) == origin) {
|
|
return;
|
|
}
|
|
}
|
|
rb_ary_push(arg->origins, origin);
|
|
}
|
|
|
|
static void
|
|
invalidate_callable_method_entry_in_every_m_table(VALUE klass, ID mid, const rb_callable_method_entry_t *cme)
|
|
{
|
|
// The argument cme must be invalidated later in the caller side
|
|
const rb_method_entry_t *newer = rb_method_entry_clone((const rb_method_entry_t *)cme);
|
|
struct invalidate_callable_method_entry_foreach_arg arg = {
|
|
.klass = klass,
|
|
.mid = mid,
|
|
.cme = (const rb_method_entry_t *) cme,
|
|
.newer = newer,
|
|
};
|
|
rb_class_classext_foreach(klass, invalidate_callable_method_entry_in_every_m_table_i, (void *)&arg);
|
|
}
|
|
|
|
static void
|
|
invalidate_complemented_method_entry_in_callable_m_table(struct rb_id_table *tbl, ID mid)
|
|
{
|
|
VALUE cme;
|
|
if (tbl && rb_id_table_lookup(tbl, mid, &cme)) {
|
|
rb_yjit_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
rb_zjit_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
rb_id_table_delete(tbl, mid);
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_tree_callable);
|
|
}
|
|
}
|
|
|
|
static void
|
|
clear_method_cache_by_id_in_class(VALUE klass, ID mid)
|
|
{
|
|
VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS);
|
|
if (rb_objspace_garbage_object_p(klass)) return;
|
|
|
|
RB_VM_LOCKING() {
|
|
rb_vm_barrier();
|
|
|
|
if (LIKELY(!RCLASS_SUBCLASSES(klass)) &&
|
|
!FL_TEST_RAW(klass, RCLASS_HAS_SUBCLASSES) &&
|
|
// Non-refinement ICLASSes (from module inclusion) previously had
|
|
// subclasses reparented onto them, so they need the tree path for
|
|
// broader cme-based invalidation even though they now have no subclasses.
|
|
!(RB_TYPE_P(klass, T_ICLASS) && NIL_P(RCLASS_REFINED_CLASS(klass)))) {
|
|
// no subclasses
|
|
// check only current class
|
|
|
|
// invalidate CCs
|
|
VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass);
|
|
invalidate_method_cache_in_cc_table(cc_tbl, mid);
|
|
if (RCLASS_CC_TBL_NOT_PRIME_P(klass, cc_tbl)) {
|
|
invalidate_method_cache_in_cc_table(RCLASS_PRIME_CC_TBL(klass), mid);
|
|
}
|
|
|
|
// remove from callable_m_tbl, if exists
|
|
struct rb_id_table *cm_tbl = RCLASS_WRITABLE_CALLABLE_M_TBL(klass);
|
|
invalidate_callable_method_entry_in_callable_m_table(cm_tbl, mid);
|
|
if (RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(klass, cm_tbl)) {
|
|
invalidate_callable_method_entry_in_callable_m_table(RCLASS_PRIME_CALLABLE_M_TBL(klass), mid);
|
|
}
|
|
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_leaf);
|
|
}
|
|
else {
|
|
const rb_callable_method_entry_t *cme = complemented_callable_method_entry(klass, mid);
|
|
|
|
if (cme) {
|
|
// invalidate cme if found to invalidate the inline method cache.
|
|
if (METHOD_ENTRY_CACHED(cme)) {
|
|
if (METHOD_ENTRY_COMPLEMENTED(cme)) {
|
|
// do nothing
|
|
}
|
|
else {
|
|
// invalidate cc by invalidating cc->cme
|
|
VALUE owner = cme->owner;
|
|
VM_ASSERT_TYPE(owner, T_CLASS);
|
|
VALUE klass_housing_cme;
|
|
if (cme->def->type == VM_METHOD_TYPE_REFINED && !cme->def->body.refined.orig_me) {
|
|
klass_housing_cme = owner;
|
|
}
|
|
else {
|
|
klass_housing_cme = RCLASS_ORIGIN(owner);
|
|
}
|
|
|
|
// replace the cme that will be invalid in the all classexts
|
|
invalidate_callable_method_entry_in_every_m_table(klass_housing_cme, mid, cme);
|
|
// owner may be a boxable class with per-box classext copies of its m_tbl
|
|
// (klass_housing_cme may be a non-boxable origin ICLASS that doesn't cover them)
|
|
if (klass_housing_cme != owner) {
|
|
invalidate_callable_method_entry_in_every_m_table(owner, mid, cme);
|
|
}
|
|
// Also update per-box origin ICLASSes. When ensure_origin is called in
|
|
// one box's context, it creates a per-box origin ICLASS whose m_tbl is
|
|
// a copy of owner's m_tbl at that time. The current execution box may
|
|
// not see these origins via RCLASS_ORIGIN(owner), so we find them by
|
|
// iterating all of owner's classexts and checking their origin_ fields.
|
|
{
|
|
VALUE origins = rb_ary_hidden_new(1);
|
|
struct collect_per_box_origins_arg origins_arg = {
|
|
.owner = owner,
|
|
.klass_housing_cme = klass_housing_cme,
|
|
.origins = origins,
|
|
};
|
|
rb_class_classext_foreach(owner, collect_per_box_origins_i, &origins_arg);
|
|
for (rb_len_t i = 0; i < RARRAY_LEN(origins); i++) {
|
|
invalidate_callable_method_entry_in_every_m_table(RARRAY_AREF(origins, i), mid, cme);
|
|
}
|
|
RB_GC_GUARD(origins);
|
|
}
|
|
}
|
|
|
|
vm_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_tree_cme);
|
|
|
|
// In case of refinement ME, also invalidate the wrapped ME that
|
|
// could be cached at some callsite and is unreachable from any
|
|
// RCLASS_WRITABLE_CC_TBL.
|
|
if (cme->def->type == VM_METHOD_TYPE_REFINED && cme->def->body.refined.orig_me) {
|
|
vm_cme_invalidate((rb_callable_method_entry_t *)cme->def->body.refined.orig_me);
|
|
}
|
|
|
|
if (cme->def->iseq_overload) {
|
|
rb_callable_method_entry_t *monly_cme = (rb_callable_method_entry_t *)lookup_overloaded_cme(cme);
|
|
if (monly_cme) {
|
|
vm_cme_invalidate(monly_cme);
|
|
}
|
|
}
|
|
}
|
|
|
|
// invalidate complement tbl
|
|
if (METHOD_ENTRY_COMPLEMENTED(cme)) {
|
|
VALUE defined_class = cme->defined_class;
|
|
struct rb_id_table *cm_tbl = RCLASS_WRITABLE_CALLABLE_M_TBL(defined_class);
|
|
invalidate_complemented_method_entry_in_callable_m_table(cm_tbl, mid);
|
|
if (RCLASS_CALLABLE_M_TBL_NOT_PRIME_P(defined_class, cm_tbl)) {
|
|
struct rb_id_table *prime_cm_table = RCLASS_PRIME_CALLABLE_M_TBL(defined_class);
|
|
invalidate_complemented_method_entry_in_callable_m_table(prime_cm_table, mid);
|
|
}
|
|
}
|
|
|
|
RB_DEBUG_COUNTER_INC(cc_invalidate_tree);
|
|
}
|
|
else {
|
|
invalidate_negative_cache(mid);
|
|
}
|
|
}
|
|
|
|
rb_gccct_clear_table();
|
|
}
|
|
}
|
|
|
|
static void
|
|
clear_iclass_method_cache_by_id(VALUE iclass, VALUE d)
|
|
{
|
|
VM_ASSERT_TYPE(iclass, T_ICLASS);
|
|
ID mid = (ID)d;
|
|
clear_method_cache_by_id_in_class(iclass, mid);
|
|
}
|
|
|
|
static void
|
|
clear_iclass_method_cache_by_id_for_refinements(VALUE klass, VALUE d)
|
|
{
|
|
if (RB_TYPE_P(klass, T_ICLASS)) {
|
|
ID mid = (ID)d;
|
|
clear_method_cache_by_id_in_class(klass, mid);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_clear_method_cache(VALUE klass_or_module, ID mid)
|
|
{
|
|
if (RB_TYPE_P(klass_or_module, T_MODULE)) {
|
|
VALUE module = klass_or_module; // alias
|
|
|
|
if (FL_TEST(module, RMODULE_IS_REFINEMENT)) {
|
|
VALUE refined_class = rb_refinement_module_get_refined_class(module);
|
|
rb_clear_method_cache(refined_class, mid);
|
|
rb_class_foreach_subclass(refined_class, clear_iclass_method_cache_by_id_for_refinements, mid);
|
|
rb_clear_all_refinement_method_cache();
|
|
}
|
|
rb_class_foreach_subclass(module, clear_iclass_method_cache_by_id, mid);
|
|
}
|
|
else {
|
|
clear_method_cache_by_id_in_class(klass_or_module, mid);
|
|
}
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
invalidate_method_entry_in_iclass_callable_m_tbl(VALUE cme, void *data)
|
|
{
|
|
vm_cme_invalidate((rb_callable_method_entry_t *)cme);
|
|
return ID_TABLE_DELETE;
|
|
}
|
|
|
|
static enum rb_id_table_iterator_result
|
|
invalidate_ccs_in_iclass_cc_tbl(VALUE value, void *data)
|
|
{
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)value;
|
|
vm_cme_invalidate((rb_callable_method_entry_t *)ccs->cme);
|
|
ruby_xfree_sized(ccs, vm_ccs_alloc_size(ccs->capa));
|
|
return ID_TABLE_DELETE;
|
|
}
|
|
|
|
void
|
|
rb_invalidate_method_caches(struct rb_id_table *cm_tbl, VALUE cc_tbl)
|
|
{
|
|
if (cm_tbl) {
|
|
rb_id_table_foreach_values(cm_tbl, invalidate_method_entry_in_iclass_callable_m_tbl, NULL);
|
|
}
|
|
if (cc_tbl) {
|
|
rb_managed_id_table_foreach_values(cc_tbl, invalidate_ccs_in_iclass_cc_tbl, NULL);
|
|
}
|
|
}
|
|
|
|
static st_index_t
|
|
vm_ci_hash(VALUE v)
|
|
{
|
|
const struct rb_callinfo *ci = (const struct rb_callinfo *)v;
|
|
st_index_t h;
|
|
h = rb_hash_start(ci->mid);
|
|
h = rb_hash_uint(h, ci->flag);
|
|
h = rb_hash_uint(h, ci->argc);
|
|
if (ci->kwarg) {
|
|
for (int i = 0; i < ci->kwarg->keyword_len; i++) {
|
|
h = rb_hash_uint(h, ci->kwarg->keywords[i]);
|
|
}
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static int
|
|
vm_ci_hash_cmp(VALUE v1, VALUE v2)
|
|
{
|
|
const struct rb_callinfo *ci1 = (const struct rb_callinfo *)v1;
|
|
const struct rb_callinfo *ci2 = (const struct rb_callinfo *)v2;
|
|
if (ci1->mid != ci2->mid) return 1;
|
|
if (ci1->flag != ci2->flag) return 1;
|
|
if (ci1->argc != ci2->argc) return 1;
|
|
if (ci1->kwarg != NULL) {
|
|
VM_ASSERT(ci2->kwarg != NULL); // implied by matching flags
|
|
|
|
if (ci1->kwarg->keyword_len != ci2->kwarg->keyword_len)
|
|
return 1;
|
|
|
|
for (int i = 0; i < ci1->kwarg->keyword_len; i++) {
|
|
if (ci1->kwarg->keywords[i] != ci2->kwarg->keywords[i]) {
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
VM_ASSERT(ci2->kwarg == NULL); // implied by matching flags
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static const struct st_hash_type vm_ci_hashtype = {
|
|
vm_ci_hash_cmp,
|
|
vm_ci_hash
|
|
};
|
|
|
|
static int
|
|
ci_lookup_i(st_data_t *key, st_data_t *value, st_data_t data, int existing)
|
|
{
|
|
const struct rb_callinfo *ci = (const struct rb_callinfo *)*key;
|
|
st_data_t *ret = (st_data_t *)data;
|
|
|
|
if (existing) {
|
|
if (rb_objspace_garbage_object_p((VALUE)ci)) {
|
|
*ret = (st_data_t)NULL;
|
|
return ST_DELETE;
|
|
}
|
|
else {
|
|
*ret = *key;
|
|
return ST_STOP;
|
|
}
|
|
}
|
|
else {
|
|
*key = *value = *ret = (st_data_t)ci;
|
|
return ST_CONTINUE;
|
|
}
|
|
}
|
|
|
|
const struct rb_callinfo *
|
|
rb_vm_ci_lookup(ID mid, unsigned int flag, unsigned int argc, const struct rb_callinfo_kwarg *kwarg)
|
|
{
|
|
rb_vm_t *vm = GET_VM();
|
|
const struct rb_callinfo *ci = NULL;
|
|
|
|
if (kwarg) {
|
|
RUBY_ATOMIC_FETCH_ADD(((struct rb_callinfo_kwarg *)kwarg)->references, 1);
|
|
}
|
|
|
|
struct rb_callinfo *new_ci = SHAREABLE_IMEMO_NEW(struct rb_callinfo, imemo_callinfo, (VALUE)kwarg);
|
|
new_ci->mid = mid;
|
|
new_ci->flag = flag;
|
|
new_ci->argc = argc;
|
|
|
|
RB_VM_LOCKING() {
|
|
st_table *ci_table = &vm->ci_table;
|
|
|
|
do {
|
|
st_update(ci_table, (st_data_t)new_ci, ci_lookup_i, (st_data_t)&ci);
|
|
} while (ci == NULL);
|
|
}
|
|
|
|
VM_ASSERT(ci);
|
|
|
|
return ci;
|
|
}
|
|
|
|
void
|
|
rb_vm_ci_free(const struct rb_callinfo *ci)
|
|
{
|
|
ASSERT_vm_locking();
|
|
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
st_data_t key = (st_data_t)ci;
|
|
st_delete(&vm->ci_table, &key, NULL);
|
|
}
|
|
|
|
struct cc_refinement_entries {
|
|
VALUE *entries;
|
|
size_t len;
|
|
size_t capa;
|
|
};
|
|
|
|
static void
|
|
cc_refinement_set_free(void *ptr)
|
|
{
|
|
struct cc_refinement_entries *e = ptr;
|
|
xfree(e->entries);
|
|
}
|
|
|
|
static size_t
|
|
cc_refinement_set_memsize(const void *ptr)
|
|
{
|
|
const struct cc_refinement_entries *e = ptr;
|
|
return e->capa * sizeof(VALUE);
|
|
}
|
|
|
|
static void
|
|
cc_refinement_set_compact(void *ptr)
|
|
{
|
|
struct cc_refinement_entries *e = ptr;
|
|
for (size_t i = 0; i < e->len; i++) {
|
|
rb_gc_update_moved(&e->entries[i]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cc_refinement_set_handle_weak_references(void *ptr)
|
|
{
|
|
struct cc_refinement_entries *e = ptr;
|
|
size_t write = 0;
|
|
for (size_t read = 0; read < e->len; read++) {
|
|
if (rb_gc_handle_weak_references_alive_p(e->entries[read])) {
|
|
e->entries[write++] = e->entries[read];
|
|
}
|
|
}
|
|
e->len = write;
|
|
}
|
|
|
|
static const rb_data_type_t cc_refinement_set_type = {
|
|
"VM/cc_refinement_set",
|
|
{
|
|
NULL,
|
|
cc_refinement_set_free,
|
|
cc_refinement_set_memsize,
|
|
cc_refinement_set_compact,
|
|
cc_refinement_set_handle_weak_references,
|
|
},
|
|
0, 0, RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
|
|
};
|
|
|
|
VALUE
|
|
rb_cc_refinement_set_create(void)
|
|
{
|
|
struct cc_refinement_entries *e;
|
|
VALUE obj = TypedData_Make_Struct(0, struct cc_refinement_entries, &cc_refinement_set_type, e);
|
|
|
|
e->entries = NULL;
|
|
e->len = 0;
|
|
e->capa = 0;
|
|
|
|
rb_gc_declare_weak_references(obj);
|
|
|
|
return obj;
|
|
}
|
|
|
|
void
|
|
rb_vm_insert_cc_refinement(const struct rb_callcache *cc)
|
|
{
|
|
rb_vm_t *vm = GET_VM();
|
|
RB_VM_LOCK_ENTER();
|
|
{
|
|
VALUE set = vm->cc_refinement_set;
|
|
struct cc_refinement_entries *e = RTYPEDDATA_GET_DATA(set);
|
|
if (e->len == e->capa) {
|
|
size_t new_capa = e->capa == 0 ? 16 : e->capa * 2;
|
|
SIZED_REALLOC_N(e->entries, VALUE, new_capa, e->capa);
|
|
e->capa = new_capa;
|
|
}
|
|
e->entries[e->len++] = (VALUE)cc;
|
|
|
|
// We never mark the cc, but we need to issue a writebarrier so that
|
|
// the refinement set can be added to the remembered set
|
|
RB_OBJ_WRITTEN(set, Qundef, (VALUE)cc);
|
|
/* The entries are embedded in set, so keep it pinned while e is in use. */
|
|
RB_GC_GUARD(set);
|
|
}
|
|
RB_VM_LOCK_LEAVE();
|
|
}
|
|
|
|
void
|
|
rb_clear_all_refinement_method_cache(void)
|
|
{
|
|
rb_vm_t *vm = GET_VM();
|
|
|
|
RB_VM_LOCK_ENTER();
|
|
{
|
|
struct cc_refinement_entries *e = RTYPEDDATA_GET_DATA(vm->cc_refinement_set);
|
|
for (size_t i = 0; i < e->len; i++) {
|
|
VALUE v = e->entries[i];
|
|
|
|
// All objects should be live as weak references are pruned in
|
|
// cc_refinement_set_handle_weak_references
|
|
VM_ASSERT(!rb_objspace_garbage_object_p(v));
|
|
|
|
const struct rb_callcache *cc = (const struct rb_callcache *)v;
|
|
VM_ASSERT(vm_cc_refinement_p(cc));
|
|
|
|
if (vm_cc_valid(cc)) {
|
|
vm_cc_invalidate(cc);
|
|
}
|
|
}
|
|
e->len = 0;
|
|
}
|
|
RB_VM_LOCK_LEAVE();
|
|
|
|
rb_yjit_invalidate_all_method_lookup_assumptions();
|
|
}
|
|
|
|
void
|
|
rb_method_table_insert(VALUE klass, struct rb_id_table *table, ID method_id, const rb_method_entry_t *me)
|
|
{
|
|
RB_VM_LOCKING() {
|
|
rb_method_table_insert0(klass, table, method_id, me, RB_TYPE_P(klass, T_ICLASS) && !RICLASS_OWNS_M_TBL_P(klass));
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_method_table_insert0(VALUE klass, struct rb_id_table *table, ID method_id, const rb_method_entry_t *me, bool iclass_shared_mtbl)
|
|
{
|
|
VALUE table_owner = klass;
|
|
if (iclass_shared_mtbl) {
|
|
table_owner = RBASIC(table_owner)->klass;
|
|
}
|
|
VM_ASSERT_TYPE3(table_owner, T_CLASS, T_ICLASS, T_MODULE);
|
|
rb_id_table_insert(table, method_id, (VALUE)me);
|
|
RB_OBJ_WRITTEN(table_owner, Qundef, (VALUE)me);
|
|
}
|
|
|
|
// rb_f_notimplement has an extra trailing argument to distinguish it from other methods
|
|
// at compile-time to override arity to be -1. But the trailing argument introduces a
|
|
// signature mismatch between caller and callee, so rb_define_method family inserts a
|
|
// method entry with rb_f_notimplement_internal, which has canonical arity=-1 signature,
|
|
// instead of rb_f_notimplement.
|
|
NORETURN(static VALUE rb_f_notimplement_internal(int argc, const VALUE *argv, VALUE obj));
|
|
|
|
static VALUE
|
|
rb_f_notimplement_internal(int argc, const VALUE *argv, VALUE obj)
|
|
{
|
|
rb_notimplement();
|
|
|
|
UNREACHABLE_RETURN(Qnil);
|
|
}
|
|
|
|
VALUE
|
|
rb_f_notimplement(int argc, const VALUE *argv, VALUE obj, VALUE marker)
|
|
{
|
|
rb_f_notimplement_internal(argc, argv, obj);
|
|
}
|
|
|
|
static void
|
|
rb_define_notimplement_method_id(VALUE mod, ID id, rb_method_visibility_t visi)
|
|
{
|
|
rb_add_method(mod, id, VM_METHOD_TYPE_NOTIMPLEMENTED, (void *)1, visi);
|
|
}
|
|
|
|
void
|
|
rb_add_method_cfunc(VALUE klass, ID mid, VALUE (*func)(ANYARGS), int argc, rb_method_visibility_t visi)
|
|
{
|
|
if (argc < -2 || 15 < argc) rb_raise(rb_eArgError, "arity out of range: %d for -2..15", argc);
|
|
if (func != (VALUE(*)(ANYARGS))rb_f_notimplement) {
|
|
rb_method_cfunc_t opt;
|
|
opt.func = func;
|
|
opt.argc = argc;
|
|
rb_add_method(klass, mid, VM_METHOD_TYPE_CFUNC, &opt, visi);
|
|
}
|
|
else {
|
|
rb_define_notimplement_method_id(klass, mid, visi);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_add_method_optimized(VALUE klass, ID mid, enum method_optimized_type opt_type, unsigned int index, rb_method_visibility_t visi)
|
|
{
|
|
rb_method_optimized_t opt = {
|
|
.type = opt_type,
|
|
.index = index,
|
|
};
|
|
rb_add_method(klass, mid, VM_METHOD_TYPE_OPTIMIZED, &opt, visi);
|
|
}
|
|
|
|
static void
|
|
method_definition_release(rb_method_definition_t *def)
|
|
{
|
|
if (def != NULL) {
|
|
const unsigned int reference_count_was = RUBY_ATOMIC_FETCH_SUB(def->reference_count, 1);
|
|
|
|
RUBY_ASSERT_ALWAYS(reference_count_was != 0);
|
|
|
|
if (reference_count_was == 1) {
|
|
if (METHOD_DEBUG) fprintf(stderr, "-%p-%s:1->0 (remove)\n", (void *)def,
|
|
rb_id2name(def->original_id));
|
|
SIZED_FREE(def);
|
|
}
|
|
else {
|
|
if (METHOD_DEBUG) fprintf(stderr, "-%p-%s:%d->%d (dec)\n", (void *)def, rb_id2name(def->original_id),
|
|
reference_count_was, reference_count_was - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_method_definition_release(rb_method_definition_t *def)
|
|
{
|
|
method_definition_release(def);
|
|
}
|
|
|
|
static void delete_overloaded_cme(const rb_callable_method_entry_t *cme);
|
|
|
|
void
|
|
rb_free_method_entry_vm_weak_references(const rb_method_entry_t *me)
|
|
{
|
|
if (me->def && me->def->iseq_overload) {
|
|
delete_overloaded_cme((const rb_callable_method_entry_t *)me);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_free_method_entry(const rb_method_entry_t *me)
|
|
{
|
|
#if USE_ZJIT
|
|
if (METHOD_ENTRY_CACHED(me)) {
|
|
rb_zjit_cme_free((const rb_callable_method_entry_t *)me);
|
|
}
|
|
#endif
|
|
|
|
#if USE_YJIT
|
|
// YJIT rb_yjit_root_mark() roots CMEs in `Invariants`,
|
|
// to remove from `Invariants` here.
|
|
#endif
|
|
|
|
method_definition_release(me->def);
|
|
}
|
|
|
|
static inline rb_method_entry_t *search_method(VALUE klass, ID id, VALUE *defined_class_ptr);
|
|
extern int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2);
|
|
|
|
static VALUE
|
|
(*call_cfunc_invoker_func(int argc))(VALUE recv, int argc, const VALUE *, VALUE (*func)(ANYARGS))
|
|
{
|
|
if (!GET_THREAD()->ext_config.ractor_safe) {
|
|
switch (argc) {
|
|
case -2: return &call_cfunc_m2;
|
|
case -1: return &call_cfunc_m1;
|
|
case 0: return &call_cfunc_0;
|
|
case 1: return &call_cfunc_1;
|
|
case 2: return &call_cfunc_2;
|
|
case 3: return &call_cfunc_3;
|
|
case 4: return &call_cfunc_4;
|
|
case 5: return &call_cfunc_5;
|
|
case 6: return &call_cfunc_6;
|
|
case 7: return &call_cfunc_7;
|
|
case 8: return &call_cfunc_8;
|
|
case 9: return &call_cfunc_9;
|
|
case 10: return &call_cfunc_10;
|
|
case 11: return &call_cfunc_11;
|
|
case 12: return &call_cfunc_12;
|
|
case 13: return &call_cfunc_13;
|
|
case 14: return &call_cfunc_14;
|
|
case 15: return &call_cfunc_15;
|
|
default:
|
|
rb_bug("unsupported length: %d", argc);
|
|
}
|
|
}
|
|
else {
|
|
switch (argc) {
|
|
case -2: return &ractor_safe_call_cfunc_m2;
|
|
case -1: return &ractor_safe_call_cfunc_m1;
|
|
case 0: return &ractor_safe_call_cfunc_0;
|
|
case 1: return &ractor_safe_call_cfunc_1;
|
|
case 2: return &ractor_safe_call_cfunc_2;
|
|
case 3: return &ractor_safe_call_cfunc_3;
|
|
case 4: return &ractor_safe_call_cfunc_4;
|
|
case 5: return &ractor_safe_call_cfunc_5;
|
|
case 6: return &ractor_safe_call_cfunc_6;
|
|
case 7: return &ractor_safe_call_cfunc_7;
|
|
case 8: return &ractor_safe_call_cfunc_8;
|
|
case 9: return &ractor_safe_call_cfunc_9;
|
|
case 10: return &ractor_safe_call_cfunc_10;
|
|
case 11: return &ractor_safe_call_cfunc_11;
|
|
case 12: return &ractor_safe_call_cfunc_12;
|
|
case 13: return &ractor_safe_call_cfunc_13;
|
|
case 14: return &ractor_safe_call_cfunc_14;
|
|
case 15: return &ractor_safe_call_cfunc_15;
|
|
default:
|
|
rb_bug("unsupported length: %d", argc);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_method_cfunc_struct(rb_method_cfunc_t *cfunc, VALUE (*func)(ANYARGS), int argc)
|
|
{
|
|
cfunc->func = func;
|
|
cfunc->argc = argc;
|
|
cfunc->invoker = call_cfunc_invoker_func(argc);
|
|
}
|
|
|
|
|
|
static rb_method_definition_t *
|
|
method_definition_addref(rb_method_definition_t *def, bool complemented)
|
|
{
|
|
unsigned int reference_count_was = RUBY_ATOMIC_FETCH_ADD(def->reference_count, 1);
|
|
if (!complemented && reference_count_was > 0) {
|
|
/* TODO: A Ractor can reach this via UnboundMethod#bind */
|
|
def->aliased = true;
|
|
}
|
|
if (METHOD_DEBUG) fprintf(stderr, "+%p-%s:%d->%d\n", (void *)def, rb_id2name(def->original_id), reference_count_was, reference_count_was+1);
|
|
|
|
return def;
|
|
}
|
|
|
|
void
|
|
rb_method_definition_addref(rb_method_definition_t *def)
|
|
{
|
|
method_definition_addref(def, false);
|
|
}
|
|
|
|
void
|
|
rb_method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *def, void *opts)
|
|
{
|
|
method_definition_release(me->def);
|
|
*(rb_method_definition_t **)&me->def = method_definition_addref(def, METHOD_ENTRY_COMPLEMENTED(me));
|
|
|
|
if (!ruby_running) add_opt_method_entry(me);
|
|
|
|
if (opts != NULL) {
|
|
switch (def->type) {
|
|
case VM_METHOD_TYPE_ISEQ:
|
|
{
|
|
rb_method_iseq_t *iseq_body = (rb_method_iseq_t *)opts;
|
|
const rb_iseq_t *iseq = iseq_body->iseqptr;
|
|
rb_cref_t *method_cref, *cref = iseq_body->cref;
|
|
|
|
/* setup iseq first (before invoking GC) */
|
|
RB_OBJ_WRITE(me, &def->body.iseq.iseqptr, iseq);
|
|
|
|
// Methods defined in `with_jit` should be considered METHOD_ENTRY_BASIC
|
|
if (rb_iseq_attr_p(iseq, BUILTIN_ATTR_C_TRACE)) {
|
|
METHOD_ENTRY_BASIC_SET((rb_method_entry_t *)me, TRUE);
|
|
}
|
|
|
|
if (ISEQ_BODY(iseq)->mandatory_only_iseq) def->iseq_overload = 1;
|
|
|
|
if (0) vm_cref_dump("rb_method_definition_create", cref);
|
|
|
|
if (cref) {
|
|
method_cref = cref;
|
|
}
|
|
else {
|
|
method_cref = vm_cref_new_toplevel(GET_EC()); /* TODO: can we reuse? */
|
|
}
|
|
|
|
RB_OBJ_WRITE(me, &def->body.iseq.cref, method_cref);
|
|
return;
|
|
}
|
|
case VM_METHOD_TYPE_CFUNC:
|
|
{
|
|
rb_method_cfunc_t *cfunc = (rb_method_cfunc_t *)opts;
|
|
setup_method_cfunc_struct(UNALIGNED_MEMBER_PTR(def, body.cfunc), cfunc->func, cfunc->argc);
|
|
return;
|
|
}
|
|
case VM_METHOD_TYPE_ATTRSET:
|
|
case VM_METHOD_TYPE_IVAR:
|
|
{
|
|
const rb_execution_context_t *ec = GET_EC();
|
|
rb_control_frame_t *cfp;
|
|
int line;
|
|
|
|
def->body.attr.id = (ID)(VALUE)opts;
|
|
|
|
cfp = rb_vm_get_ruby_level_next_cfp(ec, ec->cfp);
|
|
|
|
if (cfp && (line = rb_vm_get_sourceline(cfp))) {
|
|
VALUE location = rb_ary_new3(2, rb_iseq_path(CFP_ISEQ(cfp)), INT2FIX(line));
|
|
rb_ary_freeze(location);
|
|
RB_OBJ_SET_SHAREABLE(location);
|
|
RB_OBJ_WRITE(me, &def->body.attr.location, location);
|
|
}
|
|
else {
|
|
VM_ASSERT(def->body.attr.location == 0);
|
|
}
|
|
return;
|
|
}
|
|
case VM_METHOD_TYPE_BMETHOD:
|
|
RB_OBJ_WRITE(me, &def->body.bmethod.proc, (VALUE)opts);
|
|
def->body.bmethod.defined_ractor_id = rb_ec_ractor_id(GET_EC());
|
|
return;
|
|
case VM_METHOD_TYPE_NOTIMPLEMENTED:
|
|
setup_method_cfunc_struct(UNALIGNED_MEMBER_PTR(def, body.cfunc), (VALUE(*)(ANYARGS))rb_f_notimplement_internal, -1);
|
|
return;
|
|
case VM_METHOD_TYPE_OPTIMIZED:
|
|
def->body.optimized = *(rb_method_optimized_t *)opts;
|
|
return;
|
|
case VM_METHOD_TYPE_REFINED:
|
|
{
|
|
const rb_method_entry_t *orig_me = (const rb_method_entry_t *)opts;
|
|
RB_OBJ_WRITE(me, &def->body.refined.orig_me, orig_me);
|
|
RB_OBJ_WRITE(me, &def->original_module, orig_me->def->original_module);
|
|
return;
|
|
}
|
|
case VM_METHOD_TYPE_ALIAS:
|
|
{
|
|
const rb_method_entry_t *orig_me = (const rb_method_entry_t *)opts;
|
|
RB_OBJ_WRITE(me, &def->body.alias.original_me, orig_me);
|
|
RB_OBJ_WRITE(me, &def->original_module, orig_me->def->original_module);
|
|
return;
|
|
}
|
|
case VM_METHOD_TYPE_ZSUPER:
|
|
case VM_METHOD_TYPE_UNDEF:
|
|
case VM_METHOD_TYPE_MISSING:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
method_definition_reset(const rb_method_entry_t *me)
|
|
{
|
|
rb_method_definition_t *def = me->def;
|
|
|
|
RB_OBJ_WRITTEN(me, Qundef, def->original_module);
|
|
|
|
switch (def->type) {
|
|
case VM_METHOD_TYPE_ISEQ:
|
|
RB_OBJ_WRITTEN(me, Qundef, def->body.iseq.iseqptr);
|
|
RB_OBJ_WRITTEN(me, Qundef, def->body.iseq.cref);
|
|
break;
|
|
case VM_METHOD_TYPE_ATTRSET:
|
|
case VM_METHOD_TYPE_IVAR:
|
|
RB_OBJ_WRITTEN(me, Qundef, def->body.attr.location);
|
|
break;
|
|
case VM_METHOD_TYPE_BMETHOD:
|
|
RB_OBJ_WRITTEN(me, Qundef, def->body.bmethod.proc);
|
|
break;
|
|
case VM_METHOD_TYPE_REFINED:
|
|
RB_OBJ_WRITTEN(me, Qundef, def->body.refined.orig_me);
|
|
break;
|
|
case VM_METHOD_TYPE_ALIAS:
|
|
RB_OBJ_WRITTEN(me, Qundef, def->body.alias.original_me);
|
|
break;
|
|
case VM_METHOD_TYPE_CFUNC:
|
|
case VM_METHOD_TYPE_ZSUPER:
|
|
case VM_METHOD_TYPE_MISSING:
|
|
case VM_METHOD_TYPE_OPTIMIZED:
|
|
case VM_METHOD_TYPE_UNDEF:
|
|
case VM_METHOD_TYPE_NOTIMPLEMENTED:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static rb_atomic_t method_serial = 1;
|
|
|
|
rb_method_definition_t *
|
|
rb_method_definition_create(rb_method_type_t type, ID mid)
|
|
{
|
|
rb_method_definition_t *def;
|
|
def = ZALLOC(rb_method_definition_t);
|
|
def->type = type;
|
|
def->original_id = mid;
|
|
def->method_serial = (uintptr_t)RUBY_ATOMIC_FETCH_ADD(method_serial, 1);
|
|
def->box = rb_current_box();
|
|
return def;
|
|
}
|
|
|
|
static rb_method_entry_t *
|
|
rb_method_entry_alloc(ID called_id, VALUE owner, VALUE defined_class, rb_method_definition_t *def, bool complement)
|
|
{
|
|
if (def) method_definition_addref(def, complement);
|
|
if (RTEST(defined_class)) {
|
|
// not negative cache
|
|
VM_ASSERT_TYPE2(defined_class, T_CLASS, T_ICLASS);
|
|
}
|
|
rb_method_entry_t *me = SHAREABLE_IMEMO_NEW(rb_method_entry_t, imemo_ment, defined_class);
|
|
|
|
// mark_and_move_method_entry pins itself when it is in the overloaded_cme table
|
|
rb_gc_register_pinning_obj((VALUE)me);
|
|
|
|
*((rb_method_definition_t **)&me->def) = def;
|
|
me->called_id = called_id;
|
|
me->owner = owner;
|
|
|
|
return me;
|
|
}
|
|
|
|
static VALUE
|
|
filter_defined_class(VALUE klass)
|
|
{
|
|
switch (BUILTIN_TYPE(klass)) {
|
|
case T_CLASS:
|
|
return klass;
|
|
case T_MODULE:
|
|
return 0;
|
|
case T_ICLASS:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
rb_bug("filter_defined_class: %s", rb_obj_info(klass));
|
|
}
|
|
|
|
rb_method_entry_t *
|
|
rb_method_entry_create(ID called_id, VALUE klass, rb_method_visibility_t visi, rb_method_definition_t *def)
|
|
{
|
|
rb_method_entry_t *me = rb_method_entry_alloc(called_id, klass, filter_defined_class(klass), def, false);
|
|
METHOD_ENTRY_FLAGS_SET(me, visi, ruby_running ? FALSE : TRUE);
|
|
if (def != NULL) method_definition_reset(me);
|
|
return me;
|
|
}
|
|
|
|
// Return a cloned ME that's not invalidated (MEs are disposable for caching).
|
|
const rb_method_entry_t *
|
|
rb_method_entry_clone(const rb_method_entry_t *src_me)
|
|
{
|
|
rb_method_entry_t *me = rb_method_entry_alloc(src_me->called_id, src_me->owner, src_me->defined_class, src_me->def, METHOD_ENTRY_COMPLEMENTED(src_me));
|
|
|
|
METHOD_ENTRY_FLAGS_COPY(me, src_me);
|
|
|
|
// Also clone inner ME in case of refinement ME
|
|
if (src_me->def &&
|
|
src_me->def->type == VM_METHOD_TYPE_REFINED &&
|
|
src_me->def->body.refined.orig_me) {
|
|
const rb_method_entry_t *orig_me = src_me->def->body.refined.orig_me;
|
|
VM_ASSERT(orig_me->def->type != VM_METHOD_TYPE_REFINED);
|
|
|
|
rb_method_entry_t *orig_clone = rb_method_entry_alloc(orig_me->called_id,
|
|
orig_me->owner, orig_me->defined_class, orig_me->def, METHOD_ENTRY_COMPLEMENTED(orig_me));
|
|
METHOD_ENTRY_FLAGS_COPY(orig_clone, orig_me);
|
|
|
|
// Clone definition, since writing a VALUE to a shared definition
|
|
// can create reference edges we can't run WBs for.
|
|
rb_method_definition_t *clone_def =
|
|
rb_method_definition_create(VM_METHOD_TYPE_REFINED, src_me->called_id);
|
|
rb_method_definition_set(me, clone_def, orig_clone);
|
|
}
|
|
return me;
|
|
}
|
|
|
|
const rb_callable_method_entry_t *
|
|
rb_method_entry_complement_defined_class(const rb_method_entry_t *src_me, ID called_id, VALUE defined_class)
|
|
{
|
|
rb_method_definition_t *def = src_me->def;
|
|
rb_method_entry_t *me;
|
|
const rb_method_entry_t *refined_orig_me = NULL;
|
|
|
|
if (!src_me->defined_class &&
|
|
def->type == VM_METHOD_TYPE_REFINED &&
|
|
def->body.refined.orig_me) {
|
|
const rb_method_entry_t *orig_me =
|
|
rb_method_entry_clone(def->body.refined.orig_me);
|
|
RB_OBJ_WRITE((VALUE)orig_me, &orig_me->defined_class, defined_class);
|
|
refined_orig_me = orig_me;
|
|
def = NULL;
|
|
}
|
|
|
|
me = rb_method_entry_alloc(called_id, src_me->owner, defined_class, def, true);
|
|
METHOD_ENTRY_FLAGS_COPY(me, src_me);
|
|
METHOD_ENTRY_COMPLEMENTED_SET(me);
|
|
if (!def) {
|
|
def = rb_method_definition_create(VM_METHOD_TYPE_REFINED, called_id);
|
|
rb_method_definition_set(me, def, (void *)refined_orig_me);
|
|
}
|
|
|
|
VM_ASSERT_TYPE(me->owner, T_MODULE);
|
|
|
|
return (rb_callable_method_entry_t *)me;
|
|
}
|
|
|
|
void
|
|
rb_method_entry_copy(rb_method_entry_t *dst, const rb_method_entry_t *src)
|
|
{
|
|
method_definition_release(dst->def);
|
|
*(rb_method_definition_t **)&dst->def = method_definition_addref(src->def, METHOD_ENTRY_COMPLEMENTED(src));
|
|
method_definition_reset(dst);
|
|
dst->called_id = src->called_id;
|
|
RB_OBJ_WRITE((VALUE)dst, &dst->owner, src->owner);
|
|
RB_OBJ_WRITE((VALUE)dst, &dst->defined_class, src->defined_class);
|
|
METHOD_ENTRY_FLAGS_COPY(dst, src);
|
|
}
|
|
|
|
static void
|
|
make_method_entry_refined(VALUE owner, rb_method_entry_t *me)
|
|
{
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
return;
|
|
}
|
|
else {
|
|
rb_method_definition_t *def;
|
|
|
|
rb_vm_check_redefinition_opt_method(me, me->owner);
|
|
|
|
struct rb_method_entry_struct *orig_me =
|
|
rb_method_entry_alloc(me->called_id,
|
|
me->owner,
|
|
me->defined_class,
|
|
me->def,
|
|
true);
|
|
METHOD_ENTRY_FLAGS_COPY(orig_me, me);
|
|
|
|
def = rb_method_definition_create(VM_METHOD_TYPE_REFINED, me->called_id);
|
|
rb_method_definition_set(me, def, orig_me);
|
|
METHOD_ENTRY_VISI_SET(me, METHOD_VISI_PUBLIC);
|
|
}
|
|
}
|
|
|
|
static inline rb_method_entry_t *
|
|
lookup_method_table(VALUE klass, ID id)
|
|
{
|
|
st_data_t body;
|
|
struct rb_id_table *m_tbl = RCLASS_M_TBL(klass);
|
|
|
|
if (rb_id_table_lookup(m_tbl, id, &body)) {
|
|
return (rb_method_entry_t *) body;
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
struct method_entry_warnings;
|
|
static rb_method_entry_t *rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibility_t visi,
|
|
rb_method_type_t type, rb_method_definition_t *def, ID original_id, void *opts,
|
|
struct method_entry_warnings *warnings);
|
|
|
|
/* rb_method_entry_make() passes its `warnings`, as it has the VM lock and so can't
|
|
* call rb_add_method() (which must be called without it); the caller emits them. */
|
|
static void
|
|
add_refined_method_entry(VALUE refined_class, ID mid, struct method_entry_warnings *warnings)
|
|
{
|
|
rb_method_entry_t *me = lookup_method_table(refined_class, mid);
|
|
|
|
if (me) {
|
|
make_method_entry_refined(refined_class, me);
|
|
rb_clear_method_cache(refined_class, mid);
|
|
}
|
|
else if (warnings) {
|
|
RB_VM_LOCKING() {
|
|
rb_method_entry_make(refined_class, mid, refined_class, METHOD_VISI_PUBLIC,
|
|
VM_METHOD_TYPE_REFINED, NULL, mid, NULL, warnings);
|
|
}
|
|
}
|
|
else {
|
|
rb_add_method(refined_class, mid, VM_METHOD_TYPE_REFINED, 0, METHOD_VISI_PUBLIC);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_add_refined_method_entry(VALUE refined_class, ID mid)
|
|
{
|
|
add_refined_method_entry(refined_class, mid, NULL);
|
|
}
|
|
|
|
static void
|
|
check_override_opt_method_i(VALUE klass, VALUE arg)
|
|
{
|
|
if (RB_TYPE_P(klass, T_ICLASS)) {
|
|
// ICLASS from a module's subclass list: check the includer and
|
|
// recurse into the includer's T_CLASS subclasses.
|
|
VALUE includer = RCLASS_INCLUDER(klass);
|
|
if (!UNDEF_P(includer) && includer) {
|
|
check_override_opt_method_i(includer, arg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
ID mid = (ID)arg;
|
|
const rb_method_entry_t *me, *newme;
|
|
|
|
if (vm_redefinition_check_flag(klass)) {
|
|
me = lookup_method_table(RCLASS_ORIGIN(klass), mid);
|
|
if (me) {
|
|
newme = rb_method_entry(klass, mid);
|
|
if (newme != me) rb_vm_check_redefinition_opt_method(me, me->owner);
|
|
}
|
|
}
|
|
rb_class_foreach_subclass(klass, check_override_opt_method_i, (VALUE)mid);
|
|
}
|
|
|
|
static void
|
|
check_override_opt_method(VALUE klass, VALUE mid)
|
|
{
|
|
if (rb_vm_check_optimizable_mid(mid)) {
|
|
check_override_opt_method_i(klass, mid);
|
|
}
|
|
}
|
|
|
|
VALUE
|
|
rb_zsuper_to_super(int argc, VALUE *argv, VALUE self)
|
|
{
|
|
return rb_call_super_kw(argc, argv, RB_PASS_CALLED_KEYWORDS);
|
|
}
|
|
|
|
static inline rb_method_entry_t* search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined);
|
|
static void
|
|
method_entry_modify_check(VALUE klass, rb_method_type_t type)
|
|
{
|
|
ASSERT_vm_unlocking();
|
|
if (type != VM_METHOD_TYPE_REFINED) {
|
|
rb_class_modify_check(NIL_P(klass) ? rb_cObject : klass);
|
|
}
|
|
}
|
|
|
|
/* rb_method_entry_make() runs under the VM lock, where it must not warn:
|
|
* rb_warn() dispatches Warning.warn and writes to $stderr, either of which can
|
|
* check for interrupts. It formats the message instead, and the caller emits
|
|
* it once the lock is released. */
|
|
struct method_entry_warnings {
|
|
VALUE redefined; /* $VERBOSE only */
|
|
VALUE problem;
|
|
};
|
|
|
|
static void
|
|
method_entry_warnings_emit(const struct method_entry_warnings *warnings)
|
|
{
|
|
if (warnings->redefined) rb_warning("%"PRIsVALUE, warnings->redefined);
|
|
if (warnings->problem) rb_warn("%"PRIsVALUE, warnings->problem);
|
|
}
|
|
|
|
/*
|
|
* klass->method_table[mid] = method_entry(defined_class, visi, def)
|
|
*
|
|
* If def is given (!= NULL), then just use it and ignore original_id and otps.
|
|
* If not given, then make a new def with original_id and opts.
|
|
*/
|
|
static rb_method_entry_t *
|
|
rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibility_t visi,
|
|
rb_method_type_t type, rb_method_definition_t *def, ID original_id, void *opts,
|
|
struct method_entry_warnings *warnings)
|
|
{
|
|
rb_method_entry_t *me;
|
|
struct rb_id_table *mtbl;
|
|
st_data_t data;
|
|
int make_refined = 0;
|
|
VALUE orig_klass;
|
|
bool turn_zsuper_to_super = false;
|
|
|
|
if (NIL_P(klass)) {
|
|
klass = rb_cObject;
|
|
}
|
|
orig_klass = klass;
|
|
|
|
if (!RCLASS_SINGLETON_P(klass) &&
|
|
type != VM_METHOD_TYPE_NOTIMPLEMENTED &&
|
|
type != VM_METHOD_TYPE_ZSUPER) {
|
|
switch (mid) {
|
|
case idInitialize:
|
|
case idInitialize_copy:
|
|
case idInitialize_clone:
|
|
case idInitialize_dup:
|
|
case idRespond_to_missing:
|
|
visi = METHOD_VISI_PRIVATE;
|
|
}
|
|
}
|
|
|
|
if (RB_TYPE_P(klass, T_MODULE) && FL_TEST(klass, RMODULE_IS_REFINEMENT)) {
|
|
VALUE refined_class = rb_refinement_module_get_refined_class(klass);
|
|
if (type == VM_METHOD_TYPE_ZSUPER) {
|
|
turn_zsuper_to_super = true;
|
|
}
|
|
add_refined_method_entry(refined_class, mid, warnings);
|
|
}
|
|
if (type == VM_METHOD_TYPE_REFINED) {
|
|
rb_method_entry_t *old_me = lookup_method_table(RCLASS_ORIGIN(klass), mid);
|
|
if (old_me) rb_vm_check_redefinition_opt_method(old_me, klass);
|
|
}
|
|
else {
|
|
klass = RCLASS_ORIGIN(klass);
|
|
if (klass != orig_klass) {
|
|
rb_clear_method_cache(orig_klass, mid);
|
|
}
|
|
}
|
|
mtbl = RCLASS_WRITABLE_M_TBL(klass);
|
|
|
|
/* check re-definition */
|
|
if (rb_id_table_lookup(mtbl, mid, &data)) {
|
|
rb_method_entry_t *old_me = (rb_method_entry_t *)data;
|
|
rb_method_definition_t *old_def = old_me->def;
|
|
|
|
if (rb_method_definition_eq(old_def, def)) return old_me;
|
|
rb_vm_check_redefinition_opt_method(old_me, klass);
|
|
|
|
if (old_def->type == VM_METHOD_TYPE_REFINED) make_refined = 1;
|
|
|
|
if (RTEST(ruby_verbose) &&
|
|
type != VM_METHOD_TYPE_UNDEF &&
|
|
(old_def->aliased == false) &&
|
|
(!old_def->no_redef_warning) &&
|
|
!make_refined &&
|
|
old_def->type != VM_METHOD_TYPE_UNDEF &&
|
|
old_def->type != VM_METHOD_TYPE_ZSUPER &&
|
|
old_def->type != VM_METHOD_TYPE_ALIAS) {
|
|
const rb_iseq_t *iseq = 0;
|
|
|
|
switch (old_def->type) {
|
|
case VM_METHOD_TYPE_ISEQ:
|
|
iseq = def_iseq_ptr(old_def);
|
|
break;
|
|
case VM_METHOD_TYPE_BMETHOD:
|
|
iseq = rb_proc_get_iseq(old_def->body.bmethod.proc, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (iseq) {
|
|
warnings->redefined = rb_sprintf(
|
|
"method redefined; discarding old %"PRIsVALUE"\n%"PRIsVALUE":%d: warning: previous definition of %"PRIsVALUE" was here",
|
|
rb_id2str(mid),
|
|
rb_iseq_path(iseq),
|
|
ISEQ_BODY(iseq)->location.first_lineno,
|
|
rb_id2str(old_def->original_id)
|
|
);
|
|
}
|
|
else {
|
|
warnings->redefined = rb_sprintf("method redefined; discarding old %"PRIsVALUE, rb_id2str(mid));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* create method entry */
|
|
me = rb_method_entry_create(mid, defined_class, visi, NULL);
|
|
if (def == NULL) {
|
|
def = rb_method_definition_create(type, original_id);
|
|
if (turn_zsuper_to_super) {
|
|
def->type = VM_METHOD_TYPE_CFUNC;
|
|
def->body.cfunc.func = (rb_cfunc_t)rb_zsuper_to_super;
|
|
def->body.cfunc.invoker = ractor_safe_call_cfunc_m1;
|
|
def->body.cfunc.argc = -1;
|
|
}
|
|
RB_OBJ_WRITE(me, &def->original_module, me->owner);
|
|
}
|
|
rb_method_definition_set(me, def, opts);
|
|
|
|
rb_clear_method_cache(klass, mid);
|
|
|
|
/* check mid */
|
|
if (klass == rb_cObject) {
|
|
switch (mid) {
|
|
case idInitialize:
|
|
case idRespond_to_missing:
|
|
case idMethodMissing:
|
|
case idRespond_to:
|
|
warnings->problem = rb_sprintf("redefining Object#%s may cause infinite loop", rb_id2name(mid));
|
|
}
|
|
}
|
|
/* check mid */
|
|
if (mid == object_id || mid == id__id__ || mid == id__send__) {
|
|
if (type != VM_METHOD_TYPE_CFUNC && search_method(klass, mid, 0)) {
|
|
warnings->problem = rb_sprintf("redefining '%s' may cause serious problems", rb_id2name(mid));
|
|
}
|
|
}
|
|
|
|
if (make_refined) {
|
|
make_method_entry_refined(klass, me);
|
|
}
|
|
|
|
rb_method_table_insert(klass, mtbl, mid, me);
|
|
|
|
VM_ASSERT(me->def != NULL);
|
|
|
|
/* check optimized method override by a prepended module */
|
|
if (RB_TYPE_P(orig_klass, T_MODULE)) {
|
|
check_override_opt_method(klass, (VALUE)mid);
|
|
}
|
|
|
|
return me;
|
|
}
|
|
|
|
static st_table *
|
|
overloaded_cme_table(void)
|
|
{
|
|
return &GET_VM()->overloaded_cme_table;
|
|
}
|
|
|
|
#if VM_CHECK_MODE > 0
|
|
static int
|
|
vm_dump_overloaded_cme_table(st_data_t key, st_data_t val, st_data_t dmy)
|
|
{
|
|
fprintf(stderr, "key: "); rp(key);
|
|
fprintf(stderr, "val: "); rp(val);
|
|
return ST_CONTINUE;
|
|
}
|
|
|
|
void
|
|
rb_vm_dump_overloaded_cme_table(void)
|
|
{
|
|
fprintf(stderr, "== rb_vm_dump_overloaded_cme_table\n");
|
|
st_foreach(overloaded_cme_table(), vm_dump_overloaded_cme_table, 0);
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
lookup_overloaded_cme_i(st_data_t *key, st_data_t *value, st_data_t data, int existing)
|
|
{
|
|
if (existing) {
|
|
const rb_callable_method_entry_t *cme = (const rb_callable_method_entry_t *)*key;
|
|
const rb_callable_method_entry_t *monly_cme = (const rb_callable_method_entry_t *)*value;
|
|
const rb_callable_method_entry_t **ptr = (const rb_callable_method_entry_t **)data;
|
|
|
|
if (rb_objspace_garbage_object_p((VALUE)cme) ||
|
|
rb_objspace_garbage_object_p((VALUE)monly_cme)) {
|
|
*ptr = NULL;
|
|
return ST_DELETE;
|
|
}
|
|
else {
|
|
*ptr = monly_cme;
|
|
}
|
|
}
|
|
|
|
return ST_STOP;
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
lookup_overloaded_cme(const rb_callable_method_entry_t *cme)
|
|
{
|
|
ASSERT_vm_locking();
|
|
|
|
const rb_callable_method_entry_t *monly_cme = NULL;
|
|
st_update(overloaded_cme_table(), (st_data_t)cme, lookup_overloaded_cme_i, (st_data_t)&monly_cme);
|
|
return monly_cme;
|
|
}
|
|
|
|
#if VM_CHECK_MODE > 0
|
|
const rb_callable_method_entry_t *
|
|
rb_vm_lookup_overloaded_cme(const rb_callable_method_entry_t *cme)
|
|
{
|
|
return lookup_overloaded_cme(cme);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
delete_overloaded_cme(const rb_callable_method_entry_t *cme)
|
|
{
|
|
st_data_t cme_data = (st_data_t)cme;
|
|
ASSERT_vm_locking();
|
|
st_delete(overloaded_cme_table(), &cme_data, NULL);
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
get_overloaded_cme(const rb_callable_method_entry_t *cme)
|
|
{
|
|
const rb_callable_method_entry_t *monly_cme = lookup_overloaded_cme(cme);
|
|
|
|
if (monly_cme && !METHOD_ENTRY_INVALIDATED(monly_cme)) {
|
|
return monly_cme;
|
|
}
|
|
else {
|
|
// create
|
|
rb_method_definition_t *def = rb_method_definition_create(VM_METHOD_TYPE_ISEQ, cme->def->original_id);
|
|
rb_method_entry_t *me = rb_method_entry_alloc(cme->called_id,
|
|
cme->owner,
|
|
cme->defined_class,
|
|
def,
|
|
false);
|
|
|
|
RB_OBJ_WRITE(me, &def->body.iseq.cref, cme->def->body.iseq.cref);
|
|
RB_OBJ_WRITE(me, &def->body.iseq.iseqptr, ISEQ_BODY(cme->def->body.iseq.iseqptr)->mandatory_only_iseq);
|
|
RB_OBJ_WRITE(me, &def->original_module, cme->def->original_module);
|
|
|
|
ASSERT_vm_locking();
|
|
st_insert(overloaded_cme_table(), (st_data_t)cme, (st_data_t)me);
|
|
|
|
METHOD_ENTRY_VISI_SET(me, METHOD_ENTRY_VISI(cme));
|
|
return (rb_callable_method_entry_t *)me;
|
|
}
|
|
}
|
|
|
|
const rb_callable_method_entry_t *
|
|
rb_check_overloaded_cme(const rb_callable_method_entry_t *cme, const struct rb_callinfo * const ci)
|
|
{
|
|
if (UNLIKELY(cme->def->iseq_overload) &&
|
|
(vm_ci_flag(ci) & (VM_CALL_ARGS_SIMPLE)) &&
|
|
(!(vm_ci_flag(ci) & VM_CALL_FORWARDING)) &&
|
|
(int)vm_ci_argc(ci) == ISEQ_BODY(method_entry_iseqptr(cme))->param.lead_num) {
|
|
VM_ASSERT(cme->def->type == VM_METHOD_TYPE_ISEQ, "type: %d", cme->def->type); // iseq_overload is marked only on ISEQ methods
|
|
|
|
cme = get_overloaded_cme(cme);
|
|
|
|
VM_ASSERT(cme != NULL);
|
|
METHOD_ENTRY_CACHED_SET((struct rb_callable_method_entry_struct *)cme);
|
|
}
|
|
|
|
return cme;
|
|
}
|
|
|
|
static inline void stack_check(rb_execution_context_t *ec);
|
|
|
|
#define CALL_METHOD_HOOK(klass, hook, mid) do { \
|
|
const VALUE arg = ID2SYM(mid); \
|
|
VALUE recv_class = (klass); \
|
|
ID hook_id = (hook); \
|
|
if (RCLASS_SINGLETON_P((klass))) { \
|
|
recv_class = RCLASS_ATTACHED_OBJECT((klass)); \
|
|
hook_id = singleton_##hook; \
|
|
} \
|
|
rb_funcallv_uncached(recv_class, hook_id, 1, &arg); \
|
|
} while (0)
|
|
|
|
static void
|
|
method_added(VALUE klass, ID mid, const rb_method_entry_t *me)
|
|
{
|
|
if (ruby_running) {
|
|
/* [Bug #22179] Record the just-defined method entry for method coverage
|
|
* so the result no longer depends on walking the heap (and thus on GC
|
|
* timing). */
|
|
if (me) rb_vm_coverage_record_me(me);
|
|
CALL_METHOD_HOOK(klass, added, mid);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_add_method(VALUE klass, ID mid, rb_method_type_t type, void *opts, rb_method_visibility_t visi)
|
|
{
|
|
const rb_method_entry_t *me;
|
|
struct method_entry_warnings warnings = {0};
|
|
|
|
method_entry_modify_check(klass, type);
|
|
|
|
RB_VM_LOCKING() {
|
|
me = rb_method_entry_make(klass, mid, klass, visi, type, NULL, mid, opts, &warnings);
|
|
}
|
|
|
|
method_entry_warnings_emit(&warnings);
|
|
|
|
if (type != VM_METHOD_TYPE_UNDEF && type != VM_METHOD_TYPE_REFINED) {
|
|
method_added(klass, mid, me);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_add_method_iseq(VALUE klass, ID mid, const rb_iseq_t *iseq, rb_cref_t *cref, rb_method_visibility_t visi)
|
|
{
|
|
struct { /* should be same fields with rb_method_iseq_struct */
|
|
const rb_iseq_t *iseqptr;
|
|
rb_cref_t *cref;
|
|
} iseq_body;
|
|
|
|
iseq_body.iseqptr = iseq;
|
|
iseq_body.cref = cref;
|
|
|
|
rb_add_method(klass, mid, VM_METHOD_TYPE_ISEQ, &iseq_body, visi);
|
|
}
|
|
|
|
static rb_method_entry_t *
|
|
method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *me,
|
|
rb_method_visibility_t visi, VALUE defined_class)
|
|
{
|
|
rb_method_entry_t *newme;
|
|
struct method_entry_warnings warnings = {0};
|
|
|
|
method_entry_modify_check(klass, me->def->type);
|
|
|
|
RB_VM_LOCKING() {
|
|
newme = rb_method_entry_make(klass, mid, defined_class, visi,
|
|
me->def->type, me->def, 0, NULL, &warnings);
|
|
if (newme == me) {
|
|
me->def->no_redef_warning = TRUE;
|
|
METHOD_ENTRY_FLAGS_SET(newme, visi, FALSE);
|
|
}
|
|
}
|
|
|
|
method_entry_warnings_emit(&warnings);
|
|
|
|
method_added(klass, mid, newme);
|
|
return newme;
|
|
}
|
|
|
|
rb_method_entry_t *
|
|
rb_method_entry_set(VALUE klass, ID mid, const rb_method_entry_t *me, rb_method_visibility_t visi)
|
|
{
|
|
return method_entry_set(klass, mid, me, visi, klass);
|
|
}
|
|
|
|
#define UNDEF_ALLOC_FUNC ((rb_alloc_func_t)-1)
|
|
|
|
static void
|
|
propagate_alloc_func(VALUE subclass, VALUE arg)
|
|
{
|
|
if (RB_TYPE_P(subclass, T_CLASS) &&
|
|
!RCLASS_SINGLETON_P(subclass) &&
|
|
!FL_TEST_RAW(subclass, RCLASS_ALLOCATOR_DEFINED)) {
|
|
RCLASS_SET_ALLOCATOR(subclass, (rb_alloc_func_t)arg);
|
|
rb_class_foreach_subclass(subclass, propagate_alloc_func, arg);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_define_alloc_func(VALUE klass, VALUE (*func)(VALUE))
|
|
{
|
|
Check_Type(klass, T_CLASS);
|
|
if (RCLASS_SINGLETON_P(klass)) {
|
|
rb_raise(rb_eTypeError, "can't define an allocator for a singleton class");
|
|
}
|
|
RCLASS_SET_ALLOCATOR(klass, func);
|
|
FL_SET_RAW(klass, RCLASS_ALLOCATOR_DEFINED);
|
|
rb_class_foreach_subclass(klass, propagate_alloc_func, (VALUE)func);
|
|
}
|
|
|
|
void
|
|
rb_undef_alloc_func(VALUE klass)
|
|
{
|
|
Check_Type(klass, T_CLASS);
|
|
RCLASS_SET_ALLOCATOR(klass, UNDEF_ALLOC_FUNC);
|
|
FL_SET_RAW(klass, RCLASS_ALLOCATOR_DEFINED);
|
|
rb_class_foreach_subclass(klass, propagate_alloc_func, (VALUE)UNDEF_ALLOC_FUNC);
|
|
}
|
|
|
|
rb_alloc_func_t
|
|
rb_get_alloc_func(VALUE klass)
|
|
{
|
|
RBIMPL_ASSERT_TYPE(klass, T_CLASS);
|
|
|
|
rb_alloc_func_t allocator = RCLASS_ALLOCATOR(klass);
|
|
if (allocator == UNDEF_ALLOC_FUNC) return 0;
|
|
RUBY_ASSERT(allocator);
|
|
return allocator;
|
|
}
|
|
|
|
const rb_method_entry_t *
|
|
rb_method_entry_at(VALUE klass, ID id)
|
|
{
|
|
return lookup_method_table(klass, id);
|
|
}
|
|
|
|
static inline rb_method_entry_t*
|
|
search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined)
|
|
{
|
|
rb_method_entry_t *me = NULL;
|
|
|
|
RB_DEBUG_COUNTER_INC(mc_search);
|
|
|
|
for (; klass; klass = RCLASS_SUPER(klass)) {
|
|
RB_DEBUG_COUNTER_INC(mc_search_super);
|
|
if ((me = lookup_method_table(klass, id)) != 0) {
|
|
if (!skip_refined || me->def->type != VM_METHOD_TYPE_REFINED ||
|
|
me->def->body.refined.orig_me) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (defined_class_ptr) *defined_class_ptr = klass;
|
|
|
|
if (me == NULL) RB_DEBUG_COUNTER_INC(mc_search_notfound);
|
|
|
|
VM_ASSERT(me == NULL || !METHOD_ENTRY_INVALIDATED(me),
|
|
"invalid me, mid:%s, klass:%"PRIsVALUE"(%s)",
|
|
rb_id2name(id),
|
|
RTEST(rb_mod_name(klass)) ? rb_mod_name(klass) : rb_str_new_lit("anonymous"),
|
|
rb_obj_info(klass));
|
|
return me;
|
|
}
|
|
|
|
static inline rb_method_entry_t*
|
|
search_method(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
return search_method0(klass, id, defined_class_ptr, false);
|
|
}
|
|
|
|
static rb_method_entry_t *
|
|
search_method_protect(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
rb_method_entry_t *me = search_method(klass, id, defined_class_ptr);
|
|
|
|
if (!UNDEFINED_METHOD_ENTRY_P(me)) {
|
|
return me;
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const rb_method_entry_t *
|
|
rb_method_entry(VALUE klass, ID id)
|
|
{
|
|
return search_method_protect(klass, id, NULL);
|
|
}
|
|
|
|
static inline const rb_callable_method_entry_t *
|
|
prepare_callable_method_entry(VALUE defined_class, ID id, const rb_method_entry_t * const me, int create)
|
|
{
|
|
struct rb_id_table *mtbl;
|
|
const rb_callable_method_entry_t *cme;
|
|
VALUE cme_data;
|
|
int cme_found = 0;
|
|
|
|
if (me) {
|
|
if (me->defined_class == 0) {
|
|
RB_DEBUG_COUNTER_INC(mc_cme_complement);
|
|
VM_ASSERT_TYPE2(defined_class, T_ICLASS, T_MODULE);
|
|
|
|
mtbl = RCLASS_WRITABLE_CALLABLE_M_TBL(defined_class);
|
|
if (mtbl && rb_id_table_lookup(mtbl, id, &cme_data)) {
|
|
cme = (rb_callable_method_entry_t *)cme_data;
|
|
cme_found = 1;
|
|
}
|
|
if (cme_found) {
|
|
RB_DEBUG_COUNTER_INC(mc_cme_complement_hit);
|
|
VM_ASSERT(callable_method_entry_p(cme));
|
|
VM_ASSERT(!METHOD_ENTRY_INVALIDATED(cme));
|
|
}
|
|
else if (create) {
|
|
if (!mtbl) {
|
|
mtbl = rb_id_table_create(0);
|
|
RCLASS_WRITE_CALLABLE_M_TBL(defined_class, mtbl);
|
|
}
|
|
cme = rb_method_entry_complement_defined_class(me, me->called_id, defined_class);
|
|
rb_id_table_insert(mtbl, id, (VALUE)cme);
|
|
RB_OBJ_WRITTEN(defined_class, Qundef, (VALUE)cme);
|
|
VM_ASSERT(callable_method_entry_p(cme));
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
else {
|
|
cme = (const rb_callable_method_entry_t *)me;
|
|
VM_ASSERT(callable_method_entry_p(cme));
|
|
VM_ASSERT(!METHOD_ENTRY_INVALIDATED(cme));
|
|
}
|
|
return cme;
|
|
}
|
|
else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* A hook like this fires from C with no call site to cache into except for the gccct table,
|
|
* which is often cleared anyway. It would leave a permanent CC behind (tied to the class) if it
|
|
* created one, so we try to avoid it. */
|
|
VALUE
|
|
rb_funcallv_uncached(VALUE recv, ID mid, int argc, const VALUE *argv)
|
|
{
|
|
VALUE defined_class;
|
|
const rb_method_entry_t *me = search_method(CLASS_OF(recv), mid, &defined_class);
|
|
|
|
if (UNLIKELY(UNDEFINED_METHOD_ENTRY_P(me))) {
|
|
return rb_funcallv(recv, mid, argc, argv);
|
|
}
|
|
|
|
const rb_callable_method_entry_t *cme;
|
|
|
|
if (UNLIKELY(me->defined_class == 0)) {
|
|
// produce a transient CME that will get collected
|
|
cme = rb_method_entry_complement_defined_class(me, me->called_id, defined_class);
|
|
}
|
|
else {
|
|
cme = (const rb_callable_method_entry_t *)me;
|
|
}
|
|
|
|
rb_execution_context_t *ec = GET_EC();
|
|
stack_check(ec);
|
|
return rb_vm_call_kw(ec, recv, mid, argc, argv, cme, RB_NO_KEYWORDS);
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
complemented_callable_method_entry(VALUE klass, ID id)
|
|
{
|
|
VALUE defined_class;
|
|
rb_method_entry_t *me = search_method(klass, id, &defined_class);
|
|
return prepare_callable_method_entry(defined_class, id, me, FALSE);
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
cached_callable_method_entry(VALUE klass, ID mid)
|
|
{
|
|
ASSERT_vm_locking();
|
|
|
|
VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass);
|
|
VALUE ccs_data;
|
|
|
|
if (cc_tbl && rb_managed_id_table_lookup(cc_tbl, mid, &ccs_data)) {
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_data;
|
|
VM_ASSERT(vm_ccs_p(ccs));
|
|
|
|
if (LIKELY(!METHOD_ENTRY_INVALIDATED(ccs->cme))) {
|
|
VM_ASSERT(ccs->cme->called_id == mid);
|
|
RB_DEBUG_COUNTER_INC(ccs_found);
|
|
return ccs->cme;
|
|
}
|
|
else {
|
|
rb_vm_barrier();
|
|
|
|
rb_managed_id_table_delete(cc_tbl, mid);
|
|
rb_vm_ccs_invalidate_and_free(ccs);
|
|
}
|
|
}
|
|
|
|
RB_DEBUG_COUNTER_INC(ccs_not_found);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
cache_callable_method_entry(VALUE klass, ID mid, const rb_callable_method_entry_t *cme)
|
|
{
|
|
ASSERT_vm_locking();
|
|
VM_ASSERT(cme != NULL);
|
|
|
|
VALUE cc_tbl = RCLASS_WRITABLE_CC_TBL(klass);
|
|
VALUE ccs_data;
|
|
|
|
if (!cc_tbl) {
|
|
cc_tbl = rb_vm_cc_table_create(2);
|
|
RCLASS_WRITE_CC_TBL(klass, cc_tbl);
|
|
}
|
|
|
|
if (rb_managed_id_table_lookup(cc_tbl, mid, &ccs_data)) {
|
|
#if VM_CHECK_MODE > 0
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_data;
|
|
VM_ASSERT(ccs->cme == cme);
|
|
#endif
|
|
}
|
|
else {
|
|
if (rb_multi_ractor_p()) {
|
|
VALUE new_cc_tbl = rb_vm_cc_table_dup(cc_tbl);
|
|
vm_ccs_create(klass, new_cc_tbl, mid, cme);
|
|
RB_OBJ_ATOMIC_WRITE(klass, &RCLASSEXT_CC_TBL(RCLASS_EXT_WRITABLE(klass)), new_cc_tbl);
|
|
}
|
|
else {
|
|
vm_ccs_create(klass, cc_tbl, mid, cme);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
negative_cme(ID mid)
|
|
{
|
|
rb_vm_t *vm = GET_VM();
|
|
const rb_callable_method_entry_t *cme;
|
|
VALUE cme_data;
|
|
|
|
if (rb_id_table_lookup(&vm->negative_cme_table, mid, &cme_data)) {
|
|
cme = (rb_callable_method_entry_t *)cme_data;
|
|
}
|
|
else {
|
|
cme = (rb_callable_method_entry_t *)rb_method_entry_alloc(mid, Qnil, Qnil, NULL, false);
|
|
rb_id_table_insert(&vm->negative_cme_table, mid, (VALUE)cme);
|
|
}
|
|
|
|
VM_ASSERT(cme != NULL);
|
|
return cme;
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
callable_method_entry_or_negative(VALUE klass, ID mid, VALUE *defined_class_ptr)
|
|
{
|
|
const rb_callable_method_entry_t *cme;
|
|
|
|
VM_ASSERT(!SPECIAL_CONST_P(klass));
|
|
|
|
if (RB_BUILTIN_TYPE(klass) == T_NONE) {
|
|
// If we find a T_NONE here, it's most likely we called CLASS_OF(obj) on a
|
|
// garbage collected object (the freelist is stored in the class pointer),
|
|
// but it's possible that just the class was GC'd.
|
|
// This message intentionally tries to imply the former, but make an
|
|
// accurate statement for either case.
|
|
rb_bug("attempted to search method '%s' on a garbage collected object",
|
|
rb_id2name(mid));
|
|
}
|
|
|
|
VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS);
|
|
|
|
/* Fast path: lock-free read from cache */
|
|
VALUE cc_tbl = RUBY_ATOMIC_VALUE_LOAD(RCLASS_WRITABLE_CC_TBL(klass));
|
|
if (cc_tbl) {
|
|
VALUE ccs_data;
|
|
if (rb_managed_id_table_lookup(cc_tbl, mid, &ccs_data)) {
|
|
struct rb_class_cc_entries *ccs = (struct rb_class_cc_entries *)ccs_data;
|
|
VM_ASSERT(vm_ccs_p(ccs));
|
|
|
|
if (LIKELY(!METHOD_ENTRY_INVALIDATED(ccs->cme))) {
|
|
VM_ASSERT(ccs->cme->called_id == mid);
|
|
if (defined_class_ptr != NULL) *defined_class_ptr = ccs->cme->defined_class;
|
|
RB_DEBUG_COUNTER_INC(ccs_found);
|
|
return ccs->cme;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Slow path: need to lock and potentially populate cache */
|
|
RB_VM_LOCKING() {
|
|
cme = cached_callable_method_entry(klass, mid);
|
|
|
|
if (cme) {
|
|
if (defined_class_ptr != NULL) *defined_class_ptr = cme->defined_class;
|
|
}
|
|
else {
|
|
VALUE defined_class;
|
|
rb_method_entry_t *me = search_method(klass, mid, &defined_class);
|
|
if (defined_class_ptr) *defined_class_ptr = defined_class;
|
|
|
|
if (me != NULL) {
|
|
cme = prepare_callable_method_entry(defined_class, mid, me, TRUE);
|
|
}
|
|
else {
|
|
cme = negative_cme(mid);
|
|
}
|
|
|
|
cache_callable_method_entry(klass, mid, cme);
|
|
}
|
|
}
|
|
|
|
return cme;
|
|
}
|
|
|
|
// This is exposed for YJIT so that we can make assumptions that methods are
|
|
// not defined.
|
|
const rb_callable_method_entry_t *
|
|
rb_callable_method_entry_or_negative(VALUE klass, ID mid)
|
|
{
|
|
return callable_method_entry_or_negative(klass, mid, NULL);
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
callable_method_entry(VALUE klass, ID mid, VALUE *defined_class_ptr)
|
|
{
|
|
const rb_callable_method_entry_t *cme;
|
|
cme = callable_method_entry_or_negative(klass, mid, defined_class_ptr);
|
|
return !UNDEFINED_METHOD_ENTRY_P(cme) ? cme : NULL;
|
|
}
|
|
|
|
const rb_callable_method_entry_t *
|
|
rb_callable_method_entry(VALUE klass, ID mid)
|
|
{
|
|
return callable_method_entry(klass, mid, NULL);
|
|
}
|
|
|
|
static const rb_method_entry_t *resolve_refined_method(VALUE refinements, const rb_method_entry_t *me, VALUE *defined_class_ptr);
|
|
|
|
static const rb_method_entry_t *
|
|
method_entry_resolve_refinement(VALUE klass, ID id, int with_refinement, VALUE *defined_class_ptr)
|
|
{
|
|
const rb_method_entry_t *me = search_method_protect(klass, id, defined_class_ptr);
|
|
|
|
if (me) {
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
if (with_refinement) {
|
|
const rb_cref_t *cref = rb_vm_cref();
|
|
VALUE refinements = cref ? CREF_REFINEMENTS(cref) : Qnil;
|
|
me = resolve_refined_method(refinements, me, defined_class_ptr);
|
|
}
|
|
else {
|
|
me = resolve_refined_method(Qnil, me, defined_class_ptr);
|
|
}
|
|
|
|
if (UNDEFINED_METHOD_ENTRY_P(me)) me = NULL;
|
|
}
|
|
}
|
|
|
|
return me;
|
|
}
|
|
|
|
const rb_method_entry_t *
|
|
rb_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
return method_entry_resolve_refinement(klass, id, TRUE, defined_class_ptr);
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
callable_method_entry_refinements0(VALUE klass, ID id, VALUE *defined_class_ptr, bool with_refinements,
|
|
const rb_callable_method_entry_t *cme)
|
|
{
|
|
if (cme == NULL || LIKELY(cme->def->type != VM_METHOD_TYPE_REFINED)) {
|
|
return cme;
|
|
}
|
|
else {
|
|
VALUE defined_class, *dcp = defined_class_ptr ? defined_class_ptr : &defined_class;
|
|
const rb_method_entry_t *me = method_entry_resolve_refinement(klass, id, with_refinements, dcp);
|
|
return prepare_callable_method_entry(*dcp, id, me, TRUE);
|
|
}
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
callable_method_entry_refinements(VALUE klass, ID id, VALUE *defined_class_ptr, bool with_refinements)
|
|
{
|
|
const rb_callable_method_entry_t *cme = callable_method_entry(klass, id, defined_class_ptr);
|
|
return callable_method_entry_refinements0(klass, id, defined_class_ptr, with_refinements, cme);
|
|
}
|
|
|
|
const rb_callable_method_entry_t *
|
|
rb_callable_method_entry_with_refinements(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
return callable_method_entry_refinements(klass, id, defined_class_ptr, true);
|
|
}
|
|
|
|
static const rb_callable_method_entry_t *
|
|
callable_method_entry_without_refinements(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
return callable_method_entry_refinements(klass, id, defined_class_ptr, false);
|
|
}
|
|
|
|
const rb_method_entry_t *
|
|
rb_method_entry_without_refinements(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
return method_entry_resolve_refinement(klass, id, FALSE, defined_class_ptr);
|
|
}
|
|
|
|
const rb_callable_method_entry_t *
|
|
rb_callable_method_entry_without_refinements(VALUE klass, ID id, VALUE *defined_class_ptr)
|
|
{
|
|
VALUE defined_class, *dcp = defined_class_ptr ? defined_class_ptr : &defined_class;
|
|
const rb_method_entry_t *me = method_entry_resolve_refinement(klass, id, FALSE, dcp);
|
|
return prepare_callable_method_entry(*dcp, id, me, TRUE);
|
|
}
|
|
|
|
static const rb_method_entry_t *
|
|
resolve_refined_method(VALUE refinements, const rb_method_entry_t *me, VALUE *defined_class_ptr)
|
|
{
|
|
while (me && me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
VALUE refinement;
|
|
const rb_method_entry_t *tmp_me;
|
|
VALUE super;
|
|
|
|
refinement = find_refinement(refinements, me->owner);
|
|
if (!NIL_P(refinement)) {
|
|
if (RB_TYPE_P(me->owner, T_MODULE) && defined_class_ptr && RB_TYPE_P(*defined_class_ptr, T_ICLASS)) {
|
|
refinement = module_refinement_iclass(refinement, *defined_class_ptr);
|
|
}
|
|
tmp_me = search_method_protect(refinement, me->called_id, defined_class_ptr);
|
|
|
|
if (tmp_me && tmp_me->def->type != VM_METHOD_TYPE_REFINED) {
|
|
return tmp_me;
|
|
}
|
|
}
|
|
|
|
tmp_me = me->def->body.refined.orig_me;
|
|
if (tmp_me) {
|
|
if (!tmp_me->defined_class) {
|
|
VM_ASSERT_TYPE(tmp_me->owner, T_MODULE);
|
|
}
|
|
else if (defined_class_ptr) {
|
|
*defined_class_ptr = tmp_me->defined_class;
|
|
}
|
|
return tmp_me;
|
|
}
|
|
|
|
super = RCLASS_SUPER(me->owner);
|
|
if (!super) {
|
|
return 0;
|
|
}
|
|
|
|
me = search_method_protect(super, me->called_id, defined_class_ptr);
|
|
}
|
|
return me;
|
|
}
|
|
|
|
const rb_method_entry_t *
|
|
rb_resolve_refined_method(VALUE refinements, const rb_method_entry_t *me)
|
|
{
|
|
return resolve_refined_method(refinements, me, NULL);
|
|
}
|
|
|
|
const rb_callable_method_entry_t *
|
|
rb_resolve_refined_method_callable(VALUE refinements, const rb_callable_method_entry_t *me)
|
|
{
|
|
VALUE defined_class = me->defined_class;
|
|
const rb_method_entry_t *resolved_me = resolve_refined_method(refinements, (const rb_method_entry_t *)me, &defined_class);
|
|
|
|
if (resolved_me && resolved_me->defined_class == 0) {
|
|
return rb_method_entry_complement_defined_class(resolved_me, me->called_id, defined_class);
|
|
}
|
|
else {
|
|
return (const rb_callable_method_entry_t *)resolved_me;
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_method(VALUE klass, ID mid)
|
|
{
|
|
VALUE data;
|
|
rb_method_entry_t *me = 0;
|
|
VALUE self = klass;
|
|
|
|
rb_class_modify_check(klass);
|
|
klass = RCLASS_ORIGIN(klass);
|
|
if (mid == object_id || mid == id__id__ || mid == id__send__ || mid == idInitialize) {
|
|
rb_warn("removing '%s' may cause serious problems", rb_id2name(mid));
|
|
}
|
|
|
|
if (!rb_id_table_lookup(RCLASS_M_TBL(klass), mid, &data) ||
|
|
!(me = (rb_method_entry_t *)data) ||
|
|
(!me->def || me->def->type == VM_METHOD_TYPE_UNDEF) ||
|
|
UNDEFINED_REFINED_METHOD_P(me->def)) {
|
|
rb_name_err_raise("method '%1$s' not defined in %2$s",
|
|
klass, ID2SYM(mid));
|
|
}
|
|
|
|
if (klass != self) {
|
|
rb_clear_method_cache(self, mid);
|
|
}
|
|
rb_clear_method_cache(klass, mid);
|
|
rb_id_table_delete(RCLASS_WRITABLE_M_TBL(klass), mid);
|
|
|
|
rb_vm_check_redefinition_opt_method(me, klass);
|
|
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
rb_add_refined_method_entry(klass, mid);
|
|
}
|
|
|
|
CALL_METHOD_HOOK(self, removed, mid);
|
|
}
|
|
|
|
void
|
|
rb_remove_method_id(VALUE klass, ID mid)
|
|
{
|
|
remove_method(klass, mid);
|
|
}
|
|
|
|
void
|
|
rb_remove_method(VALUE klass, const char *name)
|
|
{
|
|
remove_method(klass, rb_intern(name));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* remove_method(symbol) -> self
|
|
* remove_method(string) -> self
|
|
*
|
|
* Removes the method identified by _symbol_ from the current
|
|
* class. For an example, see Module#undef_method.
|
|
* String arguments are converted to symbols.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_remove_method(int argc, VALUE *argv, VALUE mod)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
VALUE v = argv[i];
|
|
ID id = rb_check_id(&v);
|
|
if (!id) {
|
|
rb_name_err_raise("method '%1$s' not defined in %2$s",
|
|
mod, v);
|
|
}
|
|
remove_method(mod, id);
|
|
}
|
|
return mod;
|
|
}
|
|
|
|
static void
|
|
rb_export_method(VALUE klass, ID name, rb_method_visibility_t visi)
|
|
{
|
|
rb_method_entry_t *me;
|
|
VALUE defined_class;
|
|
VALUE origin_class = RCLASS_ORIGIN(klass);
|
|
|
|
me = search_method0(origin_class, name, &defined_class, true);
|
|
|
|
if (!me && RB_TYPE_P(klass, T_MODULE)) {
|
|
me = search_method(rb_cObject, name, &defined_class);
|
|
}
|
|
|
|
if (UNDEFINED_METHOD_ENTRY_P(me) ||
|
|
UNDEFINED_REFINED_METHOD_P(me->def)) {
|
|
rb_print_undef(klass, name, METHOD_VISI_UNDEF);
|
|
}
|
|
|
|
if (METHOD_ENTRY_VISI(me) != visi) {
|
|
rb_vm_check_redefinition_opt_method(me, klass);
|
|
|
|
if (klass == defined_class || origin_class == defined_class) {
|
|
if (me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
// Refinement method entries should always be public because the refinement
|
|
// search is always performed.
|
|
if (me->def->body.refined.orig_me) {
|
|
METHOD_ENTRY_VISI_SET((rb_method_entry_t *)me->def->body.refined.orig_me, visi);
|
|
}
|
|
}
|
|
else {
|
|
METHOD_ENTRY_VISI_SET(me, visi);
|
|
}
|
|
rb_clear_method_cache(klass, name);
|
|
}
|
|
else {
|
|
rb_add_method(klass, name, VM_METHOD_TYPE_ZSUPER, 0, visi);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define BOUND_PRIVATE 0x01
|
|
#define BOUND_RESPONDS 0x02
|
|
|
|
static int
|
|
method_boundp(VALUE klass, ID id, int ex)
|
|
{
|
|
const rb_callable_method_entry_t *cme;
|
|
|
|
VM_ASSERT_TYPE2(klass, T_CLASS, T_ICLASS);
|
|
|
|
if (ex & BOUND_RESPONDS) {
|
|
cme = rb_callable_method_entry_with_refinements(klass, id, NULL);
|
|
}
|
|
else {
|
|
cme = callable_method_entry_without_refinements(klass, id, NULL);
|
|
}
|
|
|
|
if (cme != NULL) {
|
|
if (ex & ~BOUND_RESPONDS) {
|
|
switch (METHOD_ENTRY_VISI(cme)) {
|
|
case METHOD_VISI_PRIVATE:
|
|
return 0;
|
|
case METHOD_VISI_PROTECTED:
|
|
if (ex & BOUND_RESPONDS) return 0;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cme->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) {
|
|
if (ex & BOUND_RESPONDS) return 2;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// deprecated
|
|
int
|
|
rb_method_boundp(VALUE klass, ID id, int ex)
|
|
{
|
|
return method_boundp(klass, id, ex);
|
|
}
|
|
|
|
static void
|
|
vm_cref_set_visibility(rb_method_visibility_t method_visi, int module_func)
|
|
{
|
|
rb_scope_visibility_t *scope_visi = (rb_scope_visibility_t *)&rb_vm_cref()->scope_visi;
|
|
scope_visi->method_visi = method_visi;
|
|
scope_visi->module_func = module_func;
|
|
}
|
|
|
|
void
|
|
rb_scope_visibility_set(rb_method_visibility_t visi)
|
|
{
|
|
vm_cref_set_visibility(visi, FALSE);
|
|
}
|
|
|
|
static void
|
|
scope_visibility_check(void)
|
|
{
|
|
/* Check for public/protected/private/module_function called inside a method */
|
|
rb_control_frame_t *cfp = GET_EC()->cfp+1;
|
|
if (cfp && CFP_ISEQ(cfp) && ISEQ_BODY(CFP_ISEQ(cfp))->type == ISEQ_TYPE_METHOD) {
|
|
rb_warn("calling %s without arguments inside a method may not have the intended effect",
|
|
rb_id2name(rb_frame_this_func()));
|
|
}
|
|
}
|
|
|
|
static void
|
|
rb_scope_module_func_set(void)
|
|
{
|
|
scope_visibility_check();
|
|
vm_cref_set_visibility(METHOD_VISI_PRIVATE, TRUE);
|
|
}
|
|
|
|
const rb_cref_t *rb_vm_cref_in_context(VALUE self, VALUE cbase);
|
|
void
|
|
rb_attr(VALUE klass, ID id, int read, int write, int ex)
|
|
{
|
|
ID attriv;
|
|
rb_method_visibility_t visi;
|
|
const rb_execution_context_t *ec = GET_EC();
|
|
const rb_cref_t *cref = rb_vm_cref_in_context(klass, klass);
|
|
|
|
if (!ex || !cref) {
|
|
visi = METHOD_VISI_PUBLIC;
|
|
}
|
|
else {
|
|
switch (vm_scope_visibility_get(ec)) {
|
|
case METHOD_VISI_PRIVATE:
|
|
if (vm_scope_module_func_check(ec)) {
|
|
rb_warning("attribute accessor as module_function");
|
|
}
|
|
visi = METHOD_VISI_PRIVATE;
|
|
break;
|
|
case METHOD_VISI_PROTECTED:
|
|
visi = METHOD_VISI_PROTECTED;
|
|
break;
|
|
default:
|
|
visi = METHOD_VISI_PUBLIC;
|
|
break;
|
|
}
|
|
}
|
|
|
|
attriv = rb_intern_str(rb_sprintf("@%"PRIsVALUE, rb_id2str(id)));
|
|
if (read) {
|
|
rb_add_method(klass, id, VM_METHOD_TYPE_IVAR, (void *)attriv, visi);
|
|
}
|
|
if (write) {
|
|
rb_add_method(klass, rb_id_attrset(id), VM_METHOD_TYPE_ATTRSET, (void *)attriv, visi);
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_undef(VALUE klass, ID id)
|
|
{
|
|
const rb_method_entry_t *me;
|
|
|
|
if (NIL_P(klass)) {
|
|
rb_raise(rb_eTypeError, "no class to undef method");
|
|
}
|
|
rb_class_modify_check(klass);
|
|
if (id == object_id || id == id__id__ || id == id__send__ || id == idInitialize) {
|
|
rb_warn("undefining '%s' may cause serious problems", rb_id2name(id));
|
|
}
|
|
|
|
me = search_method(klass, id, 0);
|
|
if (me && me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
me = rb_resolve_refined_method(Qnil, me);
|
|
}
|
|
|
|
if (UNDEFINED_METHOD_ENTRY_P(me) ||
|
|
UNDEFINED_REFINED_METHOD_P(me->def)) {
|
|
rb_method_name_error(klass, rb_id2str(id));
|
|
}
|
|
|
|
rb_add_method(klass, id, VM_METHOD_TYPE_UNDEF, 0, METHOD_VISI_PUBLIC);
|
|
|
|
CALL_METHOD_HOOK(klass, undefined, id);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* undef_method(symbol) -> self
|
|
* undef_method(string) -> self
|
|
*
|
|
* Prevents the current class from responding to calls to the named
|
|
* method. Contrast this with <code>remove_method</code>, which deletes
|
|
* the method from the particular class; Ruby will still search
|
|
* superclasses and mixed-in modules for a possible receiver.
|
|
* String arguments are converted to symbols.
|
|
*
|
|
* class Parent
|
|
* def hello
|
|
* puts "In parent"
|
|
* end
|
|
* end
|
|
* class Child < Parent
|
|
* def hello
|
|
* puts "In child"
|
|
* end
|
|
* end
|
|
*
|
|
*
|
|
* c = Child.new
|
|
* c.hello
|
|
*
|
|
*
|
|
* class Child
|
|
* remove_method :hello # remove from child, still in parent
|
|
* end
|
|
* c.hello
|
|
*
|
|
*
|
|
* class Child
|
|
* undef_method :hello # prevent any calls to 'hello'
|
|
* end
|
|
* c.hello
|
|
*
|
|
* <em>produces:</em>
|
|
*
|
|
* In child
|
|
* In parent
|
|
* prog.rb:23: undefined method 'hello' for #<Child:0x401b3bb4> (NoMethodError)
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_undef_method(int argc, VALUE *argv, VALUE mod)
|
|
{
|
|
int i;
|
|
for (i = 0; i < argc; i++) {
|
|
VALUE v = argv[i];
|
|
ID id = rb_check_id(&v);
|
|
if (!id) {
|
|
rb_method_name_error(mod, v);
|
|
}
|
|
rb_undef(mod, id);
|
|
}
|
|
return mod;
|
|
}
|
|
|
|
static rb_method_visibility_t
|
|
check_definition_visibility(VALUE mod, VALUE mid, bool inc_super)
|
|
{
|
|
ID id = rb_check_id(&mid);
|
|
if (!id) return METHOD_VISI_UNDEF;
|
|
|
|
VALUE lookup_mod = inc_super ? mod : RCLASS_ORIGIN(mod);
|
|
|
|
const rb_method_entry_t *me = rb_method_entry_without_refinements(lookup_mod, id, NULL);
|
|
if (me) {
|
|
if (me->def->type == VM_METHOD_TYPE_NOTIMPLEMENTED) return METHOD_VISI_UNDEF;
|
|
if (!inc_super && me->owner != mod) return METHOD_VISI_UNDEF;
|
|
return METHOD_ENTRY_VISI(me);
|
|
}
|
|
return METHOD_VISI_UNDEF;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.method_defined?(symbol, inherit=true, include_all = false) -> true or false
|
|
* mod.method_defined?(string, inherit=true, include_all = false) -> true or false
|
|
*
|
|
* Returns +true+ if the named method is defined by
|
|
* _mod_. If _inherit_ is set, the lookup will also search _mod_'s
|
|
* ancestors.
|
|
* By default only public and protected methods are matched, but if _include_all_
|
|
* is set the lookup will also consider private methods.
|
|
* String arguments are converted to symbols.
|
|
*
|
|
* module A
|
|
* def method1() end
|
|
* def protected_method1() end
|
|
* protected :protected_method1
|
|
* end
|
|
* class B
|
|
* def method2() end
|
|
* def private_method2() end
|
|
* private :private_method2
|
|
* end
|
|
* class C < B
|
|
* include A
|
|
* def method3() end
|
|
* end
|
|
*
|
|
* A.method_defined? :method1 #=> true
|
|
* C.method_defined? "method1" #=> true
|
|
* C.method_defined? "method2" #=> true
|
|
* C.method_defined? "method2", true #=> true
|
|
* C.method_defined? "method2", false #=> false
|
|
* C.method_defined? "method3" #=> true
|
|
* C.method_defined? "protected_method1" #=> true
|
|
* C.method_defined? "method4" #=> false
|
|
* C.method_defined? "private_method2" #=> false
|
|
* C.method_defined? "private_method2", true, true #=> true
|
|
* C.method_defined? "private_method2", false, true #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_method_defined(int argc, VALUE *argv, VALUE mod)
|
|
{
|
|
VALUE mid, include_super, include_private;
|
|
|
|
rb_scan_args(argc, argv, "12", &mid, &include_super, &include_private);
|
|
if (argc < 3) {
|
|
include_private = Qfalse;
|
|
if (argc < 2) {
|
|
include_super = Qtrue;
|
|
}
|
|
}
|
|
|
|
rb_method_visibility_t visi = check_definition_visibility(mod, mid, RTEST(include_super));
|
|
switch (visi) {
|
|
case METHOD_VISI_UNDEF:
|
|
return Qfalse;
|
|
case METHOD_VISI_PUBLIC:
|
|
case METHOD_VISI_PROTECTED:
|
|
return Qtrue;
|
|
case METHOD_VISI_PRIVATE:
|
|
return RBOOL(RTEST(include_private));
|
|
default:
|
|
UNREACHABLE_RETURN(Qundef);
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
check_definition(VALUE mod, int argc, VALUE *argv, rb_method_visibility_t visi)
|
|
{
|
|
VALUE mid, include_super;
|
|
rb_scan_args(argc, argv, "11", &mid, &include_super);
|
|
if (argc < 2) {
|
|
include_super = Qtrue;
|
|
}
|
|
return RBOOL(check_definition_visibility(mod, mid, RTEST(include_super)) == visi);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.public_method_defined?(symbol, inherit=true) -> true or false
|
|
* mod.public_method_defined?(string, inherit=true) -> true or false
|
|
*
|
|
* Returns +true+ if the named public method is defined by
|
|
* _mod_. If _inherit_ is set, the lookup will also search _mod_'s
|
|
* ancestors.
|
|
* String arguments are converted to symbols.
|
|
*
|
|
* module A
|
|
* def method1() end
|
|
* end
|
|
* class B
|
|
* protected
|
|
* def method2() end
|
|
* end
|
|
* class C < B
|
|
* include A
|
|
* def method3() end
|
|
* end
|
|
*
|
|
* A.method_defined? :method1 #=> true
|
|
* C.public_method_defined? "method1" #=> true
|
|
* C.public_method_defined? "method1", true #=> true
|
|
* C.public_method_defined? "method1", false #=> true
|
|
* C.public_method_defined? "method2" #=> false
|
|
* C.method_defined? "method2" #=> true
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_public_method_defined(int argc, VALUE *argv, VALUE mod)
|
|
{
|
|
return check_definition(mod, argc, argv, METHOD_VISI_PUBLIC);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.private_method_defined?(symbol, inherit=true) -> true or false
|
|
* mod.private_method_defined?(string, inherit=true) -> true or false
|
|
*
|
|
* Returns +true+ if the named private method is defined by
|
|
* _mod_. If _inherit_ is set, the lookup will also search _mod_'s
|
|
* ancestors.
|
|
* String arguments are converted to symbols.
|
|
*
|
|
* module A
|
|
* def method1() end
|
|
* end
|
|
* class B
|
|
* private
|
|
* def method2() end
|
|
* end
|
|
* class C < B
|
|
* include A
|
|
* def method3() end
|
|
* end
|
|
*
|
|
* A.method_defined? :method1 #=> true
|
|
* C.private_method_defined? "method1" #=> false
|
|
* C.private_method_defined? "method2" #=> true
|
|
* C.private_method_defined? "method2", true #=> true
|
|
* C.private_method_defined? "method2", false #=> false
|
|
* C.method_defined? "method2" #=> false
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_private_method_defined(int argc, VALUE *argv, VALUE mod)
|
|
{
|
|
return check_definition(mod, argc, argv, METHOD_VISI_PRIVATE);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.protected_method_defined?(symbol, inherit=true) -> true or false
|
|
* mod.protected_method_defined?(string, inherit=true) -> true or false
|
|
*
|
|
* Returns +true+ if the named protected method is defined
|
|
* _mod_. If _inherit_ is set, the lookup will also search _mod_'s
|
|
* ancestors.
|
|
* String arguments are converted to symbols.
|
|
*
|
|
* module A
|
|
* def method1() end
|
|
* end
|
|
* class B
|
|
* protected
|
|
* def method2() end
|
|
* end
|
|
* class C < B
|
|
* include A
|
|
* def method3() end
|
|
* end
|
|
*
|
|
* A.method_defined? :method1 #=> true
|
|
* C.protected_method_defined? "method1" #=> false
|
|
* C.protected_method_defined? "method2" #=> true
|
|
* C.protected_method_defined? "method2", true #=> true
|
|
* C.protected_method_defined? "method2", false #=> false
|
|
* C.method_defined? "method2" #=> true
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_protected_method_defined(int argc, VALUE *argv, VALUE mod)
|
|
{
|
|
return check_definition(mod, argc, argv, METHOD_VISI_PROTECTED);
|
|
}
|
|
|
|
int
|
|
rb_method_entry_eq(const rb_method_entry_t *m1, const rb_method_entry_t *m2)
|
|
{
|
|
return rb_method_definition_eq(m1->def, m2->def);
|
|
}
|
|
|
|
static const rb_method_definition_t *
|
|
original_method_definition(const rb_method_definition_t *def)
|
|
{
|
|
again:
|
|
if (def) {
|
|
switch (def->type) {
|
|
case VM_METHOD_TYPE_REFINED:
|
|
if (def->body.refined.orig_me) {
|
|
def = def->body.refined.orig_me->def;
|
|
goto again;
|
|
}
|
|
break;
|
|
case VM_METHOD_TYPE_ALIAS:
|
|
def = def->body.alias.original_me->def;
|
|
goto again;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return def;
|
|
}
|
|
|
|
int
|
|
rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2)
|
|
{
|
|
d1 = original_method_definition(d1);
|
|
d2 = original_method_definition(d2);
|
|
|
|
if (d1 == d2) return 1;
|
|
if (!d1 || !d2) return 0;
|
|
if (d1->type != d2->type) return 0;
|
|
|
|
switch (d1->type) {
|
|
case VM_METHOD_TYPE_ISEQ:
|
|
return d1->body.iseq.iseqptr == d2->body.iseq.iseqptr;
|
|
case VM_METHOD_TYPE_CFUNC:
|
|
return
|
|
d1->body.cfunc.func == d2->body.cfunc.func &&
|
|
d1->body.cfunc.argc == d2->body.cfunc.argc;
|
|
case VM_METHOD_TYPE_ATTRSET:
|
|
case VM_METHOD_TYPE_IVAR:
|
|
return d1->body.attr.id == d2->body.attr.id;
|
|
case VM_METHOD_TYPE_BMETHOD:
|
|
return RTEST(rb_proc_eq(d1->body.bmethod.proc, d2->body.bmethod.proc));
|
|
case VM_METHOD_TYPE_MISSING:
|
|
return d1->original_id == d2->original_id;
|
|
case VM_METHOD_TYPE_ZSUPER:
|
|
case VM_METHOD_TYPE_NOTIMPLEMENTED:
|
|
case VM_METHOD_TYPE_UNDEF:
|
|
return 1;
|
|
case VM_METHOD_TYPE_OPTIMIZED:
|
|
return (d1->body.optimized.type == d2->body.optimized.type) &&
|
|
(d1->body.optimized.index == d2->body.optimized.index);
|
|
case VM_METHOD_TYPE_REFINED:
|
|
case VM_METHOD_TYPE_ALIAS:
|
|
break;
|
|
}
|
|
rb_bug("rb_method_definition_eq: unsupported type: %d", d1->type);
|
|
}
|
|
|
|
static st_index_t
|
|
rb_hash_method_definition(st_index_t hash, const rb_method_definition_t *def)
|
|
{
|
|
hash = rb_hash_uint(hash, def->type);
|
|
def = original_method_definition(def);
|
|
|
|
if (!def) return hash;
|
|
|
|
switch (def->type) {
|
|
case VM_METHOD_TYPE_ISEQ:
|
|
return rb_hash_uint(hash, (st_index_t)def->body.iseq.iseqptr->body);
|
|
case VM_METHOD_TYPE_CFUNC:
|
|
hash = rb_hash_uint(hash, (st_index_t)def->body.cfunc.func);
|
|
return rb_hash_uint(hash, def->body.cfunc.argc);
|
|
case VM_METHOD_TYPE_ATTRSET:
|
|
case VM_METHOD_TYPE_IVAR:
|
|
return rb_hash_uint(hash, def->body.attr.id);
|
|
case VM_METHOD_TYPE_BMETHOD:
|
|
return rb_hash_proc(hash, def->body.bmethod.proc);
|
|
case VM_METHOD_TYPE_MISSING:
|
|
return rb_hash_uint(hash, def->original_id);
|
|
case VM_METHOD_TYPE_ZSUPER:
|
|
case VM_METHOD_TYPE_NOTIMPLEMENTED:
|
|
case VM_METHOD_TYPE_UNDEF:
|
|
return hash;
|
|
case VM_METHOD_TYPE_OPTIMIZED:
|
|
hash = rb_hash_uint(hash, def->body.optimized.index);
|
|
return rb_hash_uint(hash, def->body.optimized.type);
|
|
case VM_METHOD_TYPE_REFINED:
|
|
case VM_METHOD_TYPE_ALIAS:
|
|
break; /* unreachable */
|
|
}
|
|
rb_bug("rb_hash_method_definition: unsupported method type (%d)", def->type);
|
|
}
|
|
|
|
st_index_t
|
|
rb_hash_method_entry(st_index_t hash, const rb_method_entry_t *me)
|
|
{
|
|
return rb_hash_method_definition(hash, me->def);
|
|
}
|
|
|
|
void
|
|
rb_add_alias(VALUE klass, ID alias_name, ID original_name, rb_method_visibility_t visi_alias)
|
|
{
|
|
const VALUE target_klass = klass;
|
|
VALUE defined_class;
|
|
const rb_method_entry_t *orig_me;
|
|
rb_method_visibility_t visi = METHOD_VISI_UNDEF;
|
|
|
|
if (NIL_P(klass)) {
|
|
rb_raise(rb_eTypeError, "no class to make alias");
|
|
}
|
|
|
|
rb_class_modify_check(target_klass);
|
|
|
|
again:
|
|
orig_me = search_method(klass, original_name, &defined_class);
|
|
|
|
if (orig_me && orig_me->def->type == VM_METHOD_TYPE_REFINED) {
|
|
orig_me = rb_resolve_refined_method(Qnil, orig_me);
|
|
}
|
|
|
|
if (UNDEFINED_METHOD_ENTRY_P(orig_me) ||
|
|
UNDEFINED_REFINED_METHOD_P(orig_me->def)) {
|
|
if ((!RB_TYPE_P(klass, T_MODULE)) ||
|
|
(orig_me = search_method(rb_cObject, original_name, &defined_class),
|
|
UNDEFINED_METHOD_ENTRY_P(orig_me))) {
|
|
rb_print_undef(target_klass, original_name, METHOD_VISI_UNDEF);
|
|
}
|
|
rb_warn_scheduled_deprecation(4.2, 4.3,
|
|
"the fallback to Object for alias of '%"PRIsVALUE"' in module '%"PRIsVALUE"'",
|
|
NULL, QUOTE_ID(original_name), rb_class_path(target_klass));
|
|
}
|
|
|
|
switch (orig_me->def->type) {
|
|
case VM_METHOD_TYPE_ZSUPER:
|
|
klass = RCLASS_SUPER(klass);
|
|
original_name = orig_me->def->original_id;
|
|
visi = METHOD_ENTRY_VISI(orig_me);
|
|
goto again;
|
|
case VM_METHOD_TYPE_ALIAS:
|
|
visi = METHOD_ENTRY_VISI(orig_me);
|
|
orig_me = orig_me->def->body.alias.original_me;
|
|
VM_ASSERT(orig_me->def->type != VM_METHOD_TYPE_ALIAS);
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
if (visi == METHOD_VISI_UNDEF) visi = METHOD_ENTRY_VISI(orig_me);
|
|
if (visi_alias != METHOD_VISI_UNDEF) visi = visi_alias;
|
|
|
|
if (!NIL_P(ruby_verbose) && rb_warning_category_enabled_p(RB_WARN_CATEGORY_DEPRECATED)) {
|
|
VALUE owner_class = orig_me->defined_class ? orig_me->defined_class : defined_class;
|
|
VALUE origin = RCLASS_ORIGIN(target_klass);
|
|
bool in_prepended_module = false;
|
|
|
|
if (origin != target_klass) {
|
|
for (VALUE p = RCLASS_SUPER(target_klass); !in_prepended_module && p && p != origin; p = RCLASS_SUPER(p)) {
|
|
if (p == owner_class) {
|
|
in_prepended_module = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (in_prepended_module) {
|
|
rb_warn_scheduled_deprecation(4.2, 4.3,
|
|
"aliasing %"PRIsVALUE"#%"PRIsVALUE" defined in a prepended module %"PRIsVALUE,
|
|
NULL,
|
|
rb_class_path(target_klass), QUOTE_ID(original_name),
|
|
rb_class_path(orig_me->owner));
|
|
}
|
|
}
|
|
|
|
if (orig_me->defined_class == 0) {
|
|
struct method_entry_warnings warnings = {0};
|
|
const rb_method_entry_t *alias_me =
|
|
// TODO: needs vm lock?
|
|
rb_method_entry_make(target_klass, alias_name, target_klass, visi,
|
|
VM_METHOD_TYPE_ALIAS, NULL, orig_me->called_id,
|
|
(void *)rb_method_entry_clone(orig_me), &warnings);
|
|
|
|
method_entry_warnings_emit(&warnings);
|
|
method_added(target_klass, alias_name, alias_me);
|
|
}
|
|
else {
|
|
rb_method_entry_t *alias_me;
|
|
|
|
alias_me = method_entry_set(target_klass, alias_name, orig_me, visi, orig_me->owner);
|
|
RB_OBJ_WRITE(alias_me, &alias_me->owner, target_klass);
|
|
|
|
if (RB_TYPE_P(target_klass, T_MODULE)) {
|
|
// defined_class should not be set
|
|
}
|
|
else {
|
|
RB_OBJ_WRITE(alias_me, &alias_me->defined_class, orig_me->defined_class);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
rb_alias(VALUE klass, ID alias_name, ID original_name)
|
|
{
|
|
rb_add_alias(klass, alias_name, original_name, METHOD_VISI_UNDEF);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* alias_method(new_name, old_name) -> symbol
|
|
*
|
|
* Makes <i>new_name</i> a new copy of the method <i>old_name</i>. This can
|
|
* be used to retain access to methods that are overridden.
|
|
*
|
|
* class Greeter
|
|
* def hello
|
|
* "hello"
|
|
* end
|
|
* alias_method :orig_hello, :hello #=> :orig_hello
|
|
* def hello
|
|
* "#{orig_hello}, world"
|
|
* end
|
|
* end
|
|
* Greeter.new.hello #=> "hello, world"
|
|
*
|
|
* In a module, <i>old_name</i> must be a method of the module or its
|
|
* ancestors. Aliasing a method found only through the fallback to
|
|
* Object is deprecated, and will raise a NameError in Ruby 4.3.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_alias_method(VALUE mod, VALUE newname, VALUE oldname)
|
|
{
|
|
ID oldid = rb_check_id(&oldname);
|
|
if (!oldid) {
|
|
rb_print_undef_str(mod, oldname);
|
|
}
|
|
VALUE id = rb_to_id(newname);
|
|
rb_alias(mod, id, oldid);
|
|
return ID2SYM(id);
|
|
}
|
|
|
|
static void
|
|
check_and_export_method(VALUE self, VALUE name, rb_method_visibility_t visi)
|
|
{
|
|
ID id = rb_check_id(&name);
|
|
if (!id) {
|
|
rb_print_undef_str(self, name);
|
|
}
|
|
rb_export_method(self, id, visi);
|
|
}
|
|
|
|
static void
|
|
set_method_visibility(VALUE self, int argc, const VALUE *argv, rb_method_visibility_t visi)
|
|
{
|
|
int i;
|
|
|
|
// Not rb_class_modify_check: that also marks a module initialized, which this
|
|
// path has never done.
|
|
rb_check_frozen(self);
|
|
rb_class_owner_check(self);
|
|
if (argc == 0) {
|
|
rb_warning("%"PRIsVALUE" with no argument is just ignored",
|
|
QUOTE_ID(rb_frame_callee()));
|
|
return;
|
|
}
|
|
|
|
|
|
VALUE v;
|
|
|
|
if (argc == 1 && (v = rb_check_array_type(argv[0])) != Qnil) {
|
|
rb_len_t j;
|
|
|
|
for (j = 0; j < RARRAY_LEN(v); j++) {
|
|
check_and_export_method(self, RARRAY_AREF(v, j), visi);
|
|
}
|
|
}
|
|
else {
|
|
for (i = 0; i < argc; i++) {
|
|
check_and_export_method(self, argv[i], visi);
|
|
}
|
|
}
|
|
}
|
|
|
|
static VALUE
|
|
set_visibility(int argc, const VALUE *argv, VALUE module, rb_method_visibility_t visi)
|
|
{
|
|
if (argc == 0) {
|
|
scope_visibility_check();
|
|
rb_scope_visibility_set(visi);
|
|
return Qnil;
|
|
}
|
|
|
|
set_method_visibility(module, argc, argv, visi);
|
|
if (argc == 1) {
|
|
return argv[0];
|
|
}
|
|
return rb_ary_new_from_values(argc, argv);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* public -> nil
|
|
* public(method_name) -> method_name
|
|
* public(method_name, method_name, ...) -> array
|
|
* public(array) -> array
|
|
*
|
|
* With no arguments, sets the default visibility for subsequently
|
|
* defined methods to public. With arguments, sets the named methods to
|
|
* have public visibility.
|
|
* String arguments are converted to symbols.
|
|
* An Array of Symbols and/or Strings is also accepted.
|
|
* If a single argument is passed, it is returned.
|
|
* If no argument is passed, nil is returned.
|
|
* If multiple arguments are passed, the arguments are returned as an array.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_public(int argc, VALUE *argv, VALUE module)
|
|
{
|
|
return set_visibility(argc, argv, module, METHOD_VISI_PUBLIC);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* protected -> nil
|
|
* protected(method_name) -> method_name
|
|
* protected(method_name, method_name, ...) -> array
|
|
* protected(array) -> array
|
|
*
|
|
* Sets the visibility of a section or of a list of method names as protected.
|
|
* Accepts no arguments, a splat of method names (symbols or strings) or an
|
|
* array of method names. Returns the arguments that it received.
|
|
*
|
|
* == Important difference between protected in other languages
|
|
*
|
|
* Protected methods in Ruby are different from other languages such as Java,
|
|
* where methods are marked as protected to give access to subclasses. In Ruby,
|
|
* subclasses <b>already have access to all methods defined in the parent
|
|
* class</b>, even private ones.
|
|
*
|
|
* Marking a method as protected allows <b>different objects of the same
|
|
* class</b> to call it.
|
|
*
|
|
* One use case is for comparison methods, such as <code>==</code>, if we want
|
|
* to expose a method for comparison between objects of the same class without
|
|
* making the method public to objects of other classes.
|
|
*
|
|
* == Performance considerations
|
|
*
|
|
* Protected methods are slower than others because they can't use inline
|
|
* cache.
|
|
*
|
|
* == Example
|
|
*
|
|
* class Account
|
|
* # Mark balance as protected, so that we can compare between accounts
|
|
* # without making it public.
|
|
* attr_reader :balance
|
|
* protected :balance
|
|
*
|
|
* def initialize(balance)
|
|
* @balance = balance
|
|
* end
|
|
*
|
|
* def >(other)
|
|
* # The invocation to `other.balance` is allowed because `other` is a
|
|
* # different object of the same class (Account).
|
|
* balance > other.balance
|
|
* end
|
|
* end
|
|
*
|
|
* account1 = Account.new(100)
|
|
* account2 = Account.new(50)
|
|
*
|
|
* account1 > account2 # => true (works)
|
|
* account1.balance # => NoMethodError (fails because balance is not public)
|
|
*
|
|
* To show a private method on RDoc, use <code>:doc:</code> instead of this.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_protected(int argc, VALUE *argv, VALUE module)
|
|
{
|
|
return set_visibility(argc, argv, module, METHOD_VISI_PROTECTED);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* private -> nil
|
|
* private(method_name) -> method_name
|
|
* private(method_name, method_name, ...) -> array
|
|
* private(array) -> array
|
|
*
|
|
* With no arguments, sets the default visibility for subsequently
|
|
* defined methods to private. With arguments, sets the named methods
|
|
* to have private visibility.
|
|
* String arguments are converted to symbols.
|
|
* An Array of Symbols and/or Strings is also accepted.
|
|
* If a single argument is passed, it is returned.
|
|
* If no argument is passed, nil is returned.
|
|
* If multiple arguments are passed, the arguments are returned as an array.
|
|
*
|
|
* module Mod
|
|
* def a() end
|
|
* def b() end
|
|
* private
|
|
* def c() end
|
|
* private :a
|
|
* end
|
|
* Mod.private_instance_methods #=> [:a, :c]
|
|
*
|
|
* Note that to show a private method on RDoc, use <code>:doc:</code>.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_private(int argc, VALUE *argv, VALUE module)
|
|
{
|
|
return set_visibility(argc, argv, module, METHOD_VISI_PRIVATE);
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ruby2_keywords(method_name, ...) -> nil
|
|
*
|
|
* Deprecated: will be removed in Ruby 4.4. Use <tt>...</tt>
|
|
* {argument forwarding}[rdoc-ref:syntax/methods.rdoc@Argument+Forwarding]
|
|
* or other delegation styles instead; they work correctly on Ruby 3.0
|
|
* and later. See https://bugs.ruby-lang.org/issues/22205 for the
|
|
* schedule.
|
|
*
|
|
* For the given method names, marks the method as passing keywords through
|
|
* a normal argument splat. This should only be called on methods that
|
|
* accept an argument splat (<tt>*args</tt>) but not explicit keywords or
|
|
* a keyword splat. It marks the method such that if the method is called
|
|
* with keyword arguments, the final hash argument is marked with a special
|
|
* flag such that if it is the final element of a normal argument splat to
|
|
* another method call, and that method call does not include explicit
|
|
* keywords or a keyword splat, the final element is interpreted as keywords.
|
|
* In other words, keywords will be passed through the method to other
|
|
* methods.
|
|
*
|
|
* This should only be used for methods that delegate keywords to another
|
|
* method, and only for backwards compatibility with Ruby versions before 3.0.
|
|
* See https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
|
|
* for details on why +ruby2_keywords+ exists and when and how to use it.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_ruby2_keywords(int argc, VALUE *argv, VALUE module)
|
|
{
|
|
int i;
|
|
VALUE origin_class = RCLASS_ORIGIN(module);
|
|
|
|
rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS);
|
|
rb_check_frozen(module);
|
|
rb_class_owner_check(module);
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
VALUE v = argv[i];
|
|
ID name = rb_check_id(&v);
|
|
rb_method_entry_t *me;
|
|
VALUE defined_class;
|
|
|
|
if (!name) {
|
|
rb_print_undef_str(module, v);
|
|
}
|
|
|
|
me = search_method(origin_class, name, &defined_class);
|
|
if (!me && RB_TYPE_P(module, T_MODULE)) {
|
|
me = search_method(rb_cObject, name, &defined_class);
|
|
}
|
|
|
|
if (UNDEFINED_METHOD_ENTRY_P(me) ||
|
|
UNDEFINED_REFINED_METHOD_P(me->def)) {
|
|
rb_print_undef(module, name, METHOD_VISI_UNDEF);
|
|
}
|
|
|
|
if (module == defined_class || origin_class == defined_class) {
|
|
switch (me->def->type) {
|
|
case VM_METHOD_TYPE_ISEQ:
|
|
if (ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_rest &&
|
|
!ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_post &&
|
|
!ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_kw &&
|
|
!ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.has_kwrest) {
|
|
ISEQ_BODY(me->def->body.iseq.iseqptr)->param.flags.ruby2_keywords = 1;
|
|
rb_clear_method_cache(module, name);
|
|
}
|
|
else {
|
|
rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method accepts keywords or post arguments or method does not accept argument splat)", QUOTE_ID(name));
|
|
}
|
|
break;
|
|
case VM_METHOD_TYPE_BMETHOD: {
|
|
VALUE procval = me->def->body.bmethod.proc;
|
|
if (vm_block_handler_type(procval) == block_handler_type_proc) {
|
|
procval = vm_proc_to_block_handler(VM_BH_TO_PROC(procval));
|
|
}
|
|
|
|
if (vm_block_handler_type(procval) == block_handler_type_iseq) {
|
|
const struct rb_captured_block *captured = VM_BH_TO_ISEQ_BLOCK(procval);
|
|
const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq);
|
|
if (ISEQ_BODY(iseq)->param.flags.has_rest &&
|
|
!ISEQ_BODY(iseq)->param.flags.has_post &&
|
|
!ISEQ_BODY(iseq)->param.flags.has_kw &&
|
|
!ISEQ_BODY(iseq)->param.flags.has_kwrest) {
|
|
ISEQ_BODY(iseq)->param.flags.ruby2_keywords = 1;
|
|
rb_clear_method_cache(module, name);
|
|
}
|
|
else {
|
|
rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method accepts keywords or post arguments or method does not accept argument splat)", QUOTE_ID(name));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/* fallthrough */
|
|
default:
|
|
rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (method not defined in Ruby)", QUOTE_ID(name));
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
rb_warn("Skipping set of ruby2_keywords flag for %"PRIsVALUE" (can only set in method defining module)", QUOTE_ID(name));
|
|
}
|
|
}
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.public_class_method(symbol, ...) -> mod
|
|
* mod.public_class_method(string, ...) -> mod
|
|
* mod.public_class_method(array) -> mod
|
|
*
|
|
* Makes a list of existing class methods public.
|
|
*
|
|
* String arguments are converted to symbols.
|
|
* An Array of Symbols and/or Strings is also accepted.
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_public_method(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
set_method_visibility(rb_singleton_class(obj), argc, argv, METHOD_VISI_PUBLIC);
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* mod.private_class_method(symbol, ...) -> mod
|
|
* mod.private_class_method(string, ...) -> mod
|
|
* mod.private_class_method(array) -> mod
|
|
*
|
|
* Makes existing class methods private. Often used to hide the default
|
|
* constructor <code>new</code>.
|
|
*
|
|
* String arguments are converted to symbols.
|
|
* An Array of Symbols and/or Strings is also accepted.
|
|
*
|
|
* class SimpleSingleton # Not thread safe
|
|
* private_class_method :new
|
|
* def SimpleSingleton.create(*args, &block)
|
|
* @me = new(*args, &block) if ! @me
|
|
* @me
|
|
* end
|
|
* end
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_private_method(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
set_method_visibility(rb_singleton_class(obj), argc, argv, METHOD_VISI_PRIVATE);
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* public
|
|
* public(symbol, ...)
|
|
* public(string, ...)
|
|
* public(array)
|
|
*
|
|
* With no arguments, sets the default visibility for subsequently
|
|
* defined methods to public. With arguments, sets the named methods to
|
|
* have public visibility.
|
|
*
|
|
* String arguments are converted to symbols.
|
|
* An Array of Symbols and/or Strings is also accepted.
|
|
*/
|
|
|
|
static VALUE
|
|
top_public(int argc, VALUE *argv, VALUE _)
|
|
{
|
|
return rb_mod_public(argc, argv, rb_top_main_class("public"));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* private
|
|
* private(symbol, ...)
|
|
* private(string, ...)
|
|
* private(array)
|
|
*
|
|
* With no arguments, sets the default visibility for subsequently
|
|
* defined methods to private. With arguments, sets the named methods to
|
|
* have private visibility.
|
|
*
|
|
* String arguments are converted to symbols.
|
|
* An Array of Symbols and/or Strings is also accepted.
|
|
*/
|
|
static VALUE
|
|
top_private(int argc, VALUE *argv, VALUE _)
|
|
{
|
|
return rb_mod_private(argc, argv, rb_top_main_class("private"));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* ruby2_keywords(method_name, ...) -> self
|
|
*
|
|
* Deprecated: will be removed in Ruby 4.4. Use <tt>...</tt>
|
|
* {argument forwarding}[rdoc-ref:syntax/methods.rdoc@Argument+Forwarding]
|
|
* or other delegation styles instead; they work correctly on Ruby 3.0
|
|
* and later. See https://bugs.ruby-lang.org/issues/22205 for the
|
|
* schedule.
|
|
*
|
|
* For the given method names, marks the method as passing keywords through
|
|
* a normal argument splat. See Module#ruby2_keywords in detail.
|
|
*/
|
|
static VALUE
|
|
top_ruby2_keywords(int argc, VALUE *argv, VALUE module)
|
|
{
|
|
return rb_mod_ruby2_keywords(argc, argv, rb_top_main_class("ruby2_keywords"));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* module_function -> nil
|
|
* module_function(method_name) -> method_name
|
|
* module_function(method_name, method_name, ...) -> array
|
|
*
|
|
* Creates module functions for the named methods. These functions may
|
|
* be called with the module as a receiver, and also become available
|
|
* as instance methods to classes that mix in the module. Module
|
|
* functions are copies of the original, and so may be changed
|
|
* independently. The instance-method versions are made private.
|
|
* String arguments are converted to symbols.
|
|
* If a single argument is passed, it is returned.
|
|
* If no argument is passed, nil is returned.
|
|
* If multiple arguments are passed, the arguments are returned as an array.
|
|
*
|
|
* module Mod
|
|
* def one
|
|
* "This is one"
|
|
* end
|
|
* module_function :one
|
|
* end
|
|
* class Cls
|
|
* include Mod
|
|
* def call_one
|
|
* one
|
|
* end
|
|
* end
|
|
* Mod.one #=> "This is one"
|
|
* c = Cls.new
|
|
* c.call_one #=> "This is one"
|
|
* module Mod
|
|
* def one
|
|
* "This is the new one"
|
|
* end
|
|
* end
|
|
* Mod.one #=> "This is one"
|
|
* c.call_one #=> "This is the new one"
|
|
*
|
|
* If used with no arguments, subsequently defined methods become module
|
|
* functions:
|
|
*
|
|
* module Mod
|
|
* module_function
|
|
*
|
|
* def two
|
|
* "This is two"
|
|
* end
|
|
* end
|
|
* Mod.two #=> "This is two"
|
|
*/
|
|
|
|
static VALUE
|
|
rb_mod_modfunc(int argc, VALUE *argv, VALUE module)
|
|
{
|
|
int i;
|
|
ID id;
|
|
const rb_method_entry_t *me;
|
|
|
|
if (!RB_TYPE_P(module, T_MODULE)) {
|
|
rb_raise(rb_eTypeError, "module_function must be called for modules");
|
|
}
|
|
|
|
if (argc == 0) {
|
|
rb_scope_module_func_set();
|
|
return Qnil;
|
|
}
|
|
|
|
set_method_visibility(module, argc, argv, METHOD_VISI_PRIVATE);
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
VALUE m = module;
|
|
|
|
id = rb_to_id(argv[i]);
|
|
for (;;) {
|
|
me = search_method(m, id, 0);
|
|
if (me == 0) {
|
|
me = search_method(rb_cObject, id, 0);
|
|
}
|
|
if (UNDEFINED_METHOD_ENTRY_P(me)) {
|
|
rb_print_undef(module, id, METHOD_VISI_UNDEF);
|
|
}
|
|
if (me->def->type != VM_METHOD_TYPE_ZSUPER) {
|
|
break; /* normal case: need not to follow 'super' link */
|
|
}
|
|
m = RCLASS_SUPER(m);
|
|
if (!m)
|
|
break;
|
|
}
|
|
rb_method_entry_set(rb_singleton_class(module), id, me, METHOD_VISI_PUBLIC);
|
|
}
|
|
if (argc == 1) {
|
|
return argv[0];
|
|
}
|
|
return rb_ary_new_from_values(argc, argv);
|
|
}
|
|
|
|
#ifdef __GNUC__
|
|
#pragma push_macro("rb_method_basic_definition_p")
|
|
#undef rb_method_basic_definition_p
|
|
#endif
|
|
int
|
|
rb_method_basic_definition_p(VALUE klass, ID id)
|
|
{
|
|
const rb_callable_method_entry_t *cme;
|
|
if (!klass) return TRUE; /* hidden object cannot be overridden */
|
|
cme = rb_callable_method_entry(klass, id);
|
|
return (cme && METHOD_ENTRY_BASIC(cme)) ? TRUE : FALSE;
|
|
}
|
|
#ifdef __GNUC__
|
|
#pragma pop_macro("rb_method_basic_definition_p")
|
|
#endif
|
|
|
|
static VALUE
|
|
call_method_entry(rb_execution_context_t *ec, VALUE defined_class, VALUE obj, ID id,
|
|
const rb_callable_method_entry_t *cme, int argc, const VALUE *argv, int kw_splat)
|
|
{
|
|
VALUE passed_block_handler = vm_passed_block_handler(ec);
|
|
VALUE result = rb_vm_call_kw(ec, obj, id, argc, argv, cme, kw_splat);
|
|
vm_passed_block_handler_set(ec, passed_block_handler);
|
|
return result;
|
|
}
|
|
|
|
static VALUE
|
|
basic_obj_respond_to_missing(rb_execution_context_t *ec, VALUE klass, VALUE obj,
|
|
VALUE mid, VALUE priv)
|
|
{
|
|
VALUE defined_class, args[2];
|
|
const ID rtmid = idRespond_to_missing;
|
|
const rb_callable_method_entry_t *const cme = callable_method_entry(klass, rtmid, &defined_class);
|
|
|
|
if (!cme || METHOD_ENTRY_BASIC(cme)) return Qundef;
|
|
args[0] = mid;
|
|
args[1] = priv;
|
|
return call_method_entry(ec, defined_class, obj, rtmid, cme, 2, args, RB_NO_KEYWORDS);
|
|
}
|
|
|
|
static inline int
|
|
basic_obj_respond_to(rb_execution_context_t *ec, VALUE obj, ID id, int pub)
|
|
{
|
|
VALUE klass = CLASS_OF(obj);
|
|
VALUE ret;
|
|
|
|
switch (method_boundp(klass, id, pub|BOUND_RESPONDS)) {
|
|
case 2:
|
|
return FALSE;
|
|
case 0:
|
|
ret = basic_obj_respond_to_missing(ec, klass, obj, ID2SYM(id),
|
|
RBOOL(!pub));
|
|
return RTEST(ret) && !UNDEF_P(ret);
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static int
|
|
vm_respond_to(rb_execution_context_t *ec, VALUE klass, VALUE obj, ID id, int priv)
|
|
{
|
|
VALUE defined_class;
|
|
const ID resid = idRespond_to;
|
|
const rb_callable_method_entry_t *const cme = callable_method_entry(klass, resid, &defined_class);
|
|
|
|
if (!cme) return -1;
|
|
if (METHOD_ENTRY_BASIC(cme)) {
|
|
return -1;
|
|
}
|
|
else {
|
|
int argc = 1;
|
|
VALUE args[2];
|
|
VALUE result;
|
|
|
|
args[0] = ID2SYM(id);
|
|
args[1] = Qtrue;
|
|
if (priv) {
|
|
argc = rb_method_entry_arity((const rb_method_entry_t *)cme);
|
|
if (argc > 2) {
|
|
rb_raise(rb_eArgError,
|
|
"respond_to? must accept 1 or 2 arguments (requires %d)",
|
|
argc);
|
|
}
|
|
if (argc != 1) {
|
|
argc = 2;
|
|
}
|
|
else if (!NIL_P(ruby_verbose)) {
|
|
VALUE location = rb_method_entry_location((const rb_method_entry_t *)cme);
|
|
rb_category_warn(RB_WARN_CATEGORY_DEPRECATED,
|
|
"%"PRIsVALUE"%c""respond_to?(:%"PRIsVALUE") uses"
|
|
" the deprecated method signature, which takes one parameter",
|
|
(RCLASS_SINGLETON_P(klass) ? obj : klass),
|
|
(RCLASS_SINGLETON_P(klass) ? '.' : '#'),
|
|
QUOTE_ID(id));
|
|
if (!NIL_P(location)) {
|
|
VALUE path = RARRAY_AREF(location, 0);
|
|
VALUE line = RARRAY_AREF(location, 1);
|
|
if (!NIL_P(path)) {
|
|
rb_category_compile_warn(RB_WARN_CATEGORY_DEPRECATED,
|
|
RSTRING_PTR(path), NUM2INT(line),
|
|
"respond_to? is defined here");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
result = call_method_entry(ec, defined_class, obj, resid, cme, argc, args, RB_NO_KEYWORDS);
|
|
return RTEST(result);
|
|
}
|
|
}
|
|
|
|
int
|
|
rb_obj_respond_to(VALUE obj, ID id, int priv)
|
|
{
|
|
rb_execution_context_t *ec = GET_EC();
|
|
return rb_ec_obj_respond_to(ec, obj, id, priv);
|
|
}
|
|
|
|
int
|
|
rb_ec_obj_respond_to(rb_execution_context_t *ec, VALUE obj, ID id, int priv)
|
|
{
|
|
VALUE klass = CLASS_OF(obj);
|
|
int ret = vm_respond_to(ec, klass, obj, id, priv);
|
|
if (ret == -1) ret = basic_obj_respond_to(ec, obj, id, !priv);
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
rb_respond_to(VALUE obj, ID id)
|
|
{
|
|
return rb_obj_respond_to(obj, id, FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.respond_to?(symbol, include_all=false) -> true or false
|
|
* obj.respond_to?(string, include_all=false) -> true or false
|
|
*
|
|
* Returns +true+ if _obj_ responds to the given method. Private and
|
|
* protected methods are included in the search only if the optional
|
|
* second parameter evaluates to +true+.
|
|
*
|
|
* If the method is not implemented,
|
|
* as Process.fork on Windows, File.lchmod on GNU/Linux, etc.,
|
|
* false is returned.
|
|
*
|
|
* If the method is not defined, <code>respond_to_missing?</code>
|
|
* method is called and the result is returned.
|
|
*
|
|
* When the method name parameter is given as a string, the string is
|
|
* converted to a symbol.
|
|
*/
|
|
|
|
static VALUE
|
|
obj_respond_to(int argc, VALUE *argv, VALUE obj)
|
|
{
|
|
VALUE mid, priv;
|
|
ID id;
|
|
rb_execution_context_t *ec = GET_EC();
|
|
|
|
rb_scan_args(argc, argv, "11", &mid, &priv);
|
|
if (!(id = rb_check_id(&mid))) {
|
|
VALUE ret = basic_obj_respond_to_missing(ec, CLASS_OF(obj), obj,
|
|
rb_to_symbol(mid), priv);
|
|
if (UNDEF_P(ret)) ret = Qfalse;
|
|
return ret;
|
|
}
|
|
return RBOOL(basic_obj_respond_to(ec, obj, id, !RTEST(priv)));
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* obj.respond_to_missing?(symbol, include_all) -> true or false
|
|
* obj.respond_to_missing?(string, include_all) -> true or false
|
|
*
|
|
* DO NOT USE THIS DIRECTLY.
|
|
*
|
|
* Hook method to return whether the _obj_ can respond to _id_ method
|
|
* or not.
|
|
*
|
|
* When the method name parameter is given as a string, the string is
|
|
* converted to a symbol.
|
|
*
|
|
* See #respond_to?, and the example of BasicObject.
|
|
*/
|
|
static VALUE
|
|
obj_respond_to_missing(VALUE obj, VALUE mid, VALUE priv)
|
|
{
|
|
return Qfalse;
|
|
}
|
|
|
|
static void
|
|
method_uncached(VALUE klass, const char *name)
|
|
{
|
|
rb_method_entry_t *me = lookup_method_table(klass, rb_intern(name));
|
|
|
|
VM_ASSERT(me != NULL);
|
|
me->def->uncached = 1;
|
|
}
|
|
|
|
/* Called a fixed number of times per class, from a class body, so a cc would be
|
|
* a permanent cost per class for no reuse. */
|
|
void
|
|
Init_uncached_methods(void)
|
|
{
|
|
static const char *const names[] = {
|
|
"public", "protected", "private", "module_function", "ruby2_keywords",
|
|
"alias_method", "undef_method", "remove_method",
|
|
"attr_reader", "attr_writer", "attr_accessor",
|
|
"include", "prepend",
|
|
};
|
|
|
|
for (int i = 0; i < numberof(names); i++) {
|
|
method_uncached(rb_cModule, names[i]);
|
|
}
|
|
}
|
|
|
|
void
|
|
Init_eval_method(void)
|
|
{
|
|
rb_define_method(rb_mKernel, "respond_to?", obj_respond_to, -1);
|
|
rb_define_method(rb_mKernel, "respond_to_missing?", obj_respond_to_missing, 2);
|
|
|
|
rb_define_method(rb_cModule, "remove_method", rb_mod_remove_method, -1);
|
|
rb_define_method(rb_cModule, "undef_method", rb_mod_undef_method, -1);
|
|
rb_define_method(rb_cModule, "alias_method", rb_mod_alias_method, 2);
|
|
rb_define_private_method(rb_cModule, "public", rb_mod_public, -1);
|
|
rb_define_private_method(rb_cModule, "protected", rb_mod_protected, -1);
|
|
rb_define_private_method(rb_cModule, "private", rb_mod_private, -1);
|
|
rb_define_private_method(rb_cModule, "module_function", rb_mod_modfunc, -1);
|
|
rb_define_private_method(rb_cModule, "ruby2_keywords", rb_mod_ruby2_keywords, -1);
|
|
|
|
rb_define_method(rb_cModule, "method_defined?", rb_mod_method_defined, -1);
|
|
rb_define_method(rb_cModule, "public_method_defined?", rb_mod_public_method_defined, -1);
|
|
rb_define_method(rb_cModule, "private_method_defined?", rb_mod_private_method_defined, -1);
|
|
rb_define_method(rb_cModule, "protected_method_defined?", rb_mod_protected_method_defined, -1);
|
|
rb_define_method(rb_cModule, "public_class_method", rb_mod_public_method, -1);
|
|
rb_define_method(rb_cModule, "private_class_method", rb_mod_private_method, -1);
|
|
|
|
rb_define_private_method(rb_singleton_class(rb_vm_top_self()),
|
|
"public", top_public, -1);
|
|
rb_define_private_method(rb_singleton_class(rb_vm_top_self()),
|
|
"private", top_private, -1);
|
|
rb_define_private_method(rb_singleton_class(rb_vm_top_self()),
|
|
"ruby2_keywords", top_ruby2_keywords, -1);
|
|
|
|
{
|
|
#define REPLICATE_METHOD(klass, id) do { \
|
|
const rb_method_entry_t *me = rb_method_entry((klass), (id)); \
|
|
rb_method_entry_set((klass), (id), me, METHOD_ENTRY_VISI(me)); \
|
|
} while (0)
|
|
|
|
REPLICATE_METHOD(rb_eException, idMethodMissing);
|
|
REPLICATE_METHOD(rb_eException, idRespond_to);
|
|
REPLICATE_METHOD(rb_eException, idRespond_to_missing);
|
|
}
|
|
}
|