/* (c) 2011 John Mair (banisterfiend), MIT license */ #include #include "vm_core.h" #include "gc.h" typedef enum { false, true } bool; static VALUE string2sym(const char * string) { return ID2SYM(rb_intern(string)); } static inline const rb_data_type_t * threadptr_data_type(void) { static const rb_data_type_t *thread_data_type; if (!thread_data_type) { VALUE current_thread = rb_thread_current(); thread_data_type = RTYPEDDATA_TYPE(current_thread); } return thread_data_type; } #define ruby_thread_data_type *threadptr_data_type() #define ruby_current_thread ((rb_thread_t *)RTYPEDDATA_DATA(rb_thread_current())) static size_t binding_memsize(const void *ptr) { return ptr ? sizeof(rb_binding_t) : 0; } static void binding_free(void *ptr) { rb_binding_t *bind; RUBY_FREE_ENTER("binding"); if (ptr) { bind = ptr; ruby_xfree(ptr); } RUBY_FREE_LEAVE("binding"); } static void binding_mark(void *ptr) { rb_binding_t *bind; RUBY_MARK_ENTER("binding"); if (ptr) { bind = ptr; RUBY_MARK_UNLESS_NULL(bind->env); #ifdef RUBY_192 RUBY_MARK_UNLESS_NULL(bind->filename); #endif } RUBY_MARK_LEAVE("binding"); } static const rb_data_type_t binding_data_type = { "binding", binding_mark, binding_free, binding_memsize, }; static VALUE binding_alloc(VALUE klass) { VALUE obj; rb_binding_t *bind; obj = TypedData_Make_Struct(klass, rb_binding_t, &binding_data_type, bind); return obj; } static bool valid_frame_p(rb_control_frame_t * cfp, rb_control_frame_t * limit_cfp) { return cfp->iseq && !NIL_P(cfp->self); } static rb_control_frame_t * find_valid_frame(rb_control_frame_t * cfp, rb_control_frame_t * limit_cfp) { while (cfp < limit_cfp) { cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); if (cfp >= limit_cfp) return NULL; if (valid_frame_p(cfp, limit_cfp)) return cfp; } // beyond end of stack return NULL; } static VALUE frametype_name(VALUE flag) { switch (flag & VM_FRAME_MAGIC_MASK) { case VM_FRAME_MAGIC_METHOD: return string2sym("method"); case VM_FRAME_MAGIC_BLOCK: return string2sym("block"); case VM_FRAME_MAGIC_CLASS: return string2sym("class"); case VM_FRAME_MAGIC_TOP: return string2sym("top"); case VM_FRAME_MAGIC_FINISH: return string2sym("finish"); case VM_FRAME_MAGIC_CFUNC: return string2sym("cfunc"); case VM_FRAME_MAGIC_PROC: return string2sym("proc"); case VM_FRAME_MAGIC_IFUNC: return string2sym("ifunc"); case VM_FRAME_MAGIC_EVAL: return string2sym("eval"); case VM_FRAME_MAGIC_LAMBDA: return string2sym("lambda"); default: rb_raise(rb_eRuntimeError, "Unknown frame type! got flag: %d", FIX2INT(flag)); } } static VALUE binding_of_caller(VALUE self, VALUE rb_level) { rb_thread_t *th; GetThreadPtr(rb_thread_current(), th); rb_control_frame_t *cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); rb_control_frame_t *limit_cfp = (void *)(th->stack + th->stack_size); int level = FIX2INT(rb_level); // attempt to locate the nth parent control frame for (int i = 0; i < level; i++) { cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); if (cfp >= limit_cfp) rb_raise(rb_eRuntimeError, "Invalid frame, gone beyond end of stack!"); // skip invalid frames if (!valid_frame_p(cfp, limit_cfp)) cfp = find_valid_frame(cfp, limit_cfp); } VALUE bindval = binding_alloc(rb_cBinding); rb_binding_t *bind; if (cfp == 0) rb_raise(rb_eRuntimeError, "Can't create Binding Object on top of Fiber."); GetBindingPtr(bindval, bind); bind->env = rb_vm_make_env_object(th, cfp); bind->filename = cfp->iseq->filename; bind->line_no = rb_vm_get_sourceline(cfp); rb_iv_set(bindval, "@frame_type", frametype_name(cfp->flag)); rb_iv_set(bindval, "@frame_description", cfp->iseq->name); return bindval; } static VALUE frame_type(VALUE self) { return rb_iv_get(self, "@frame_type"); } static VALUE frame_description(VALUE self) { return rb_iv_get(self, "@frame_description"); } static VALUE frame_count(VALUE self) { rb_thread_t *th; GetThreadPtr(rb_thread_current(), th); rb_control_frame_t *cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); rb_control_frame_t *limit_cfp = (void *)(th->stack + th->stack_size); int i = 1; while (cfp < limit_cfp) { cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); if (cfp >= limit_cfp) return INT2FIX(i); // skip invalid frames if (!valid_frame_p(cfp, limit_cfp)) cfp = find_valid_frame(cfp, limit_cfp); if (!cfp) break; i++; } return INT2FIX(i); } static VALUE callers(VALUE self) { VALUE ary = rb_ary_new(); for (int i = 0; i < FIX2INT(frame_count(self)); i++) rb_ary_push(ary, binding_of_caller(self, INT2FIX(i))); return ary; } void Init_binding_of_caller() { VALUE mBindingOfCaller = rb_define_module("BindingOfCaller"); rb_define_method(mBindingOfCaller, "of_caller", binding_of_caller, 1); rb_define_method(mBindingOfCaller, "frame_count", frame_count, 0); rb_define_method(mBindingOfCaller, "frame_type", frame_type, 0); rb_define_method(mBindingOfCaller, "frame_description", frame_description, 0); rb_define_method(mBindingOfCaller, "callers", callers, 0); rb_include_module(rb_cBinding, mBindingOfCaller); }