#include "ruby.h" #if RUBY_VERSION >= 200 # include "method.h" # include "internal.h" #elif RUBY_VERSION >= 193 # include "ruby/st.h" # ifdef SA_EMPTY # include "internal_falcon.h" # define Looksee_method_table_foreach sa_foreach # define Looksee_method_table_lookup sa_lookup # else # include "internal.h" # endif # include "vm_core.h" # include "method.h" #elif RUBY_VERSION >= 192 # include "vm_core.h" # include "method.h" # include "ruby/st.h" #elif RUBY_VERSION >= 190 # include "node-1.9.h" # include "ruby/st.h" #else # include "node.h" # include "st.h" #endif #ifndef Looksee_method_table_foreach # define Looksee_method_table_foreach st_foreach # define Looksee_method_table_lookup st_lookup #endif #if RUBY_VERSION < 187 # define RCLASS_IV_TBL(c) (RCLASS(c)->iv_tbl) # define RCLASS_M_TBL(c) (RCLASS(c)->m_tbl) # define RCLASS_SUPER(c) (RCLASS(c)->super) #endif /* * Return the internal superclass of this class. * * This is either a Class or "IClass." IClasses represent Modules * included in the ancestry, and should be treated as opaque objects * in ruby space. Convert the IClass to a Module using #iclass_to_module * before using it in ruby. */ VALUE Looksee_internal_superclass(VALUE self, VALUE internal_class) { VALUE super = RCLASS_SUPER(internal_class); if (!super) return Qnil; return super; } /* * Return the internal class of the given object. * * This is either the object's singleton class, if it exists, or the * object's birth class. */ VALUE Looksee_internal_class(VALUE self, VALUE object) { return CLASS_OF(object); } #if RUBY_VERSION >= 192 # define VISIBILITY_TYPE rb_method_flag_t typedef struct add_method_if_matching_arg { VALUE names; VISIBILITY_TYPE visibility; } add_method_if_matching_arg_t; static int add_method_if_matching(ID method_name, rb_method_entry_t *me, add_method_if_matching_arg_t *arg) { # ifdef ID_ALLOCATOR if (method_name == ID_ALLOCATOR) return ST_CONTINUE; # endif if (UNDEFINED_METHOD_ENTRY_P(me)) return ST_CONTINUE; if ((me->flag & NOEX_MASK) == arg->visibility) rb_ary_push(arg->names, ID2SYM(method_name)); return ST_CONTINUE; } static int add_method_if_undefined(ID method_name, rb_method_entry_t *me, VALUE *names) { # ifdef ID_ALLOCATOR /* The allocator can be undefined with rb_undef_alloc_func, e.g. Struct. */ if (method_name == ID_ALLOCATOR) return ST_CONTINUE; # endif if (UNDEFINED_METHOD_ENTRY_P(me)) rb_ary_push(*names, ID2SYM(method_name)); return ST_CONTINUE; } #else # if RUBY_VERSION >= 190 # define VISIBILITY(node) ((node)->nd_body->nd_noex & NOEX_MASK) # else # define VISIBILITY(node) ((node)->nd_noex & NOEX_MASK) # endif # define VISIBILITY_TYPE unsigned long typedef struct add_method_if_matching_arg { VALUE names; VISIBILITY_TYPE visibility; } add_method_if_matching_arg_t; static int add_method_if_matching(ID method_name, NODE *body, add_method_if_matching_arg_t *arg) { # ifdef ID_ALLOCATOR /* This entry is for the internal allocator function. */ if (method_name == ID_ALLOCATOR) return ST_CONTINUE; # endif /* Module#undef_method: * * sets body->nd_body to NULL in ruby <= 1.8 * * sets body to NULL in ruby >= 1.9 */ if (!body || !body->nd_body) return ST_CONTINUE; if (VISIBILITY(body) == arg->visibility) rb_ary_push(arg->names, ID2SYM(method_name)); return ST_CONTINUE; } static int add_method_if_undefined(ID method_name, NODE *body, VALUE *names) { # ifdef ID_ALLOCATOR /* The allocator can be undefined with rb_undef_alloc_func, e.g. Struct. */ if (method_name == ID_ALLOCATOR) return ST_CONTINUE; # endif if (!body || !body->nd_body) rb_ary_push(*names, ID2SYM(method_name)); return ST_CONTINUE; } #endif static VALUE internal_instance_methods(VALUE klass, VISIBILITY_TYPE visibility) { add_method_if_matching_arg_t arg; arg.names = rb_ary_new(); arg.visibility = visibility; Looksee_method_table_foreach(RCLASS_M_TBL(klass), add_method_if_matching, (st_data_t)&arg); return arg.names; } /* * Return the list of public instance methods (as Symbols) of the * given internal class. */ VALUE Looksee_internal_public_instance_methods(VALUE self, VALUE klass) { return internal_instance_methods(klass, NOEX_PUBLIC); } /* * Return the list of protected instance methods (as Symbols) of the * given internal class. */ VALUE Looksee_internal_protected_instance_methods(VALUE self, VALUE klass) { return internal_instance_methods(klass, NOEX_PROTECTED); } /* * Return the list of private instance methods (as Symbols) of the * given internal class. */ VALUE Looksee_internal_private_instance_methods(VALUE self, VALUE klass) { return internal_instance_methods(klass, NOEX_PRIVATE); } /* * Return the list of undefined instance methods (as Symbols) of the * given internal class. */ VALUE Looksee_internal_undefined_instance_methods(VALUE self, VALUE klass) { VALUE names = rb_ary_new(); Looksee_method_table_foreach(RCLASS_M_TBL(klass), add_method_if_undefined, (st_data_t)&names); return names; } /* * Return true if the given object is an included class or origin class, false * otherwise. */ VALUE Looksee_included_class_p(VALUE self, VALUE object) { return !SPECIAL_CONST_P(object) && BUILTIN_TYPE(object) == T_ICLASS ? Qtrue : Qfalse; } VALUE Looksee_singleton_class_p(VALUE self, VALUE object) { return BUILTIN_TYPE(object) == T_CLASS && FL_TEST(object, FL_SINGLETON) ? Qtrue : Qfalse; } VALUE Looksee_singleton_instance(VALUE self, VALUE singleton_class) { if (BUILTIN_TYPE(singleton_class) == T_CLASS && FL_TEST(singleton_class, FL_SINGLETON)) { VALUE object; if (!Looksee_method_table_lookup(RCLASS_IV_TBL(singleton_class), rb_intern("__attached__"), (st_data_t *)&object)) rb_raise(rb_eRuntimeError, "[looksee bug] can't find singleton object"); return object; } else { rb_raise(rb_eTypeError, "expected singleton class, got %s", rb_obj_classname(singleton_class)); } } VALUE Looksee_module_name(VALUE self, VALUE module) { if (BUILTIN_TYPE(module) == T_CLASS || BUILTIN_TYPE(module) == T_MODULE) { VALUE name = rb_mod_name(module); return name == Qnil ? rb_str_new2("") : name; } else if (BUILTIN_TYPE(module) == T_ICLASS) { VALUE wrapped = RBASIC(module)->klass; VALUE name = Looksee_module_name(self, wrapped); if (BUILTIN_TYPE(wrapped) == T_CLASS) name = rb_str_cat2(name, " (origin)"); return name; } else { rb_raise(rb_eTypeError, "expected module, got %s", rb_obj_classname(module)); } } #if RUBY_VERSION < 190 #include "env-1.8.h" #include "eval_c-1.8.h" /* * Return the source file and line number of the given object and method. */ VALUE Looksee_source_location(VALUE self, VALUE unbound_method) { if (!rb_obj_is_kind_of(unbound_method, rb_cUnboundMethod)) rb_raise(rb_eTypeError, "expected UnboundMethod, got %s", rb_obj_classname(unbound_method)); struct METHOD *method; Data_Get_Struct(unbound_method, struct METHOD, method); NODE *node; switch (nd_type(method->body)) { // Can't be a FBODY or ZSUPER. case NODE_SCOPE: node = method->body->nd_defn; break; case NODE_BMETHOD: { struct BLOCK *block; Data_Get_Struct(method->body->nd_orig, struct BLOCK, block); (node = block->frame.node) || (node = block->body); // Proc#to_s suggests this may be NULL sometimes. if (!node) return Qnil; } break; case NODE_DMETHOD: { struct METHOD *original_method; NODE *body = method->body; Data_Get_Struct(body->nd_orig, struct METHOD, original_method); node = original_method->body->nd_defn; } break; default: return Qnil; } VALUE file = rb_str_new2(node->nd_file); VALUE line = INT2NUM(nd_line(node)); VALUE location = rb_ary_new2(2); rb_ary_store(location, 0, file); rb_ary_store(location, 1, line); return location; } #endif void Init_mri(void) { VALUE mLooksee = rb_const_get(rb_cObject, rb_intern("Looksee")); VALUE mAdapter = rb_const_get(mLooksee, rb_intern("Adapter")); VALUE mBase = rb_const_get(mAdapter, rb_intern("Base")); VALUE mMRI = rb_define_class_under(mAdapter, "MRI", mBase); rb_define_method(mMRI, "internal_superclass", Looksee_internal_superclass, 1); rb_define_method(mMRI, "internal_class", Looksee_internal_class, 1); rb_define_method(mMRI, "internal_public_instance_methods", Looksee_internal_public_instance_methods, 1); rb_define_method(mMRI, "internal_protected_instance_methods", Looksee_internal_protected_instance_methods, 1); rb_define_method(mMRI, "internal_private_instance_methods", Looksee_internal_private_instance_methods, 1); rb_define_method(mMRI, "internal_undefined_instance_methods", Looksee_internal_undefined_instance_methods, 1); rb_define_method(mMRI, "included_class?", Looksee_included_class_p, 1); rb_define_method(mMRI, "singleton_class?", Looksee_singleton_class_p, 1); rb_define_method(mMRI, "singleton_instance", Looksee_singleton_instance, 1); rb_define_method(mMRI, "module_name", Looksee_module_name, 1); #if RUBY_VERSION < 190 rb_define_method(mMRI, "source_location", Looksee_source_location, 1); #endif }