diff options
Diffstat (limited to 'jni/ruby/eval.c')
-rw-r--r-- | jni/ruby/eval.c | 1713 |
1 files changed, 1713 insertions, 0 deletions
diff --git a/jni/ruby/eval.c b/jni/ruby/eval.c new file mode 100644 index 0000000..e1729ed --- /dev/null +++ b/jni/ruby/eval.c @@ -0,0 +1,1713 @@ +/********************************************************************** + + eval.c - + + $Author: nobu $ + created at: Thu Jun 10 14:22:17 JST 1993 + + Copyright (C) 1993-2007 Yukihiro Matsumoto + Copyright (C) 2000 Network Applied Communication Laboratory, Inc. + Copyright (C) 2000 Information-technology Promotion Agency, Japan + +**********************************************************************/ + +#include "internal.h" +#include "eval_intern.h" +#include "iseq.h" +#include "gc.h" +#include "ruby/vm.h" +#include "vm_core.h" +#include "probes_helper.h" + +NORETURN(void rb_raise_jump(VALUE, VALUE)); + +VALUE rb_eLocalJumpError; +VALUE rb_eSysStackError; + +#define exception_error GET_VM()->special_exceptions[ruby_error_reenter] + +#include "eval_error.c" +#include "eval_jump.c" + +#define CLASS_OR_MODULE_P(obj) \ + (!SPECIAL_CONST_P(obj) && \ + (BUILTIN_TYPE(obj) == T_CLASS || BUILTIN_TYPE(obj) == T_MODULE)) + +/* Initializes the Ruby VM and builtin libraries. + * @retval 0 if succeeded. + * @retval non-zero an error occurred. + */ +int +ruby_setup(void) +{ + static int initialized = 0; + int state; + + if (initialized) + return 0; + initialized = 1; + + ruby_init_stack((void *)&state); + Init_BareVM(); + Init_heap(); + Init_vm_objects(); + Init_frozen_strings(); + + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + rb_call_inits(); + ruby_prog_init(); + GET_VM()->running = 1; + } + POP_TAG(); + + return state; +} + +/* Calls ruby_setup() and check error. + * + * Prints errors and calls exit(3) if an error occurred. + */ +void +ruby_init(void) +{ + int state = ruby_setup(); + if (state) { + error_print(); + exit(EXIT_FAILURE); + } +} + +/*! Processes command line arguments and compiles the Ruby source to execute. + * + * This function does: + * \li Processes the given command line flags and arguments for ruby(1) + * \li compiles the source code from the given argument, -e or stdin, and + * \li returns the compiled source as an opaque pointer to an internal data structure + * + * @return an opaque pointer to the compiled source or an internal special value. + * @sa ruby_executable_node(). + */ +void * +ruby_options(int argc, char **argv) +{ + int state; + void *volatile iseq = 0; + + ruby_init_stack((void *)&iseq); + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(GET_THREAD(), iseq = ruby_process_options(argc, argv)); + } + else { + rb_clear_trace_func(); + state = error_handle(state); + iseq = (void *)INT2FIX(state); + } + POP_TAG(); + return iseq; +} + +static void +ruby_finalize_0(void) +{ + PUSH_TAG(); + if (EXEC_TAG() == 0) { + rb_trap_exit(); + } + POP_TAG(); + rb_exec_end_proc(); + rb_clear_trace_func(); +} + +static void +ruby_finalize_1(void) +{ + ruby_sig_finalize(); + GET_THREAD()->errinfo = Qnil; + rb_gc_call_finalizer_at_exit(); +} + +/** Runs the VM finalization processes. + * + * <code>END{}</code> and procs registered by <code>Kernel.#at_exit</code> are + * executed here. See the Ruby language spec for more details. + * + * @note This function is allowed to raise an exception if an error occurred. + */ +void +ruby_finalize(void) +{ + ruby_finalize_0(); + ruby_finalize_1(); +} + +/** Destructs the VM. + * + * Runs the VM finalization processes as well as ruby_finalize(), and frees + * resources used by the VM. + * + * @param ex Default value to the return value. + * @return If an error occurred returns a non-zero. If otherwise, returns the + * given ex. + * @note This function does not raise any exception. + */ +int +ruby_cleanup(volatile int ex) +{ + int state; + volatile VALUE errs[2]; + rb_thread_t *th = GET_THREAD(); + int nerr; + + rb_threadptr_interrupt(th); + rb_threadptr_check_signal(th); + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(th, { RUBY_VM_CHECK_INTS(th); }); + } + POP_TAG(); + + errs[1] = th->errinfo; + th->safe_level = 0; + ruby_init_stack(&errs[STACK_UPPER(errs, 0, 1)]); + + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(th, ruby_finalize_0()); + } + POP_TAG(); + + /* protect from Thread#raise */ + th->status = THREAD_KILLED; + + errs[0] = th->errinfo; + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(th, rb_thread_terminate_all()); + } + else if (ex == 0) { + ex = state; + } + th->errinfo = errs[1]; + ex = error_handle(ex); + +#if EXIT_SUCCESS != 0 || EXIT_FAILURE != 1 + switch (ex) { +#if EXIT_SUCCESS != 0 + case 0: ex = EXIT_SUCCESS; break; +#endif +#if EXIT_FAILURE != 1 + case 1: ex = EXIT_FAILURE; break; +#endif + } +#endif + + state = 0; + for (nerr = 0; nerr < numberof(errs); ++nerr) { + VALUE err = errs[nerr]; + + if (!RTEST(err)) continue; + + /* th->errinfo contains a NODE while break'ing */ + if (RB_TYPE_P(err, T_NODE)) continue; + + if (rb_obj_is_kind_of(err, rb_eSystemExit)) { + ex = sysexit_status(err); + break; + } + else if (rb_obj_is_kind_of(err, rb_eSignal)) { + VALUE sig = rb_iv_get(err, "signo"); + state = NUM2INT(sig); + break; + } + else if (ex == EXIT_SUCCESS) { + ex = EXIT_FAILURE; + } + } + + ruby_finalize_1(); + + /* unlock again if finalizer took mutexes. */ + rb_threadptr_unlock_all_locking_mutexes(GET_THREAD()); + POP_TAG(); + rb_thread_stop_timer_thread(1); + ruby_vm_destruct(GET_VM()); + if (state) ruby_default_signal(state); + + return ex; +} + +static int +ruby_exec_internal(void *n) +{ + volatile int state; + VALUE iseq = (VALUE)n; + rb_thread_t *th = GET_THREAD(); + + if (!n) return 0; + + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(th, { + th->base_block = 0; + rb_iseq_eval_main(iseq); + }); + } + POP_TAG(); + return state; +} + +/*! Calls ruby_cleanup() and exits the process */ +void +ruby_stop(int ex) +{ + exit(ruby_cleanup(ex)); +} + +/*! Checks the return value of ruby_options(). + * @param n return value of ruby_options(). + * @param status pointer to the exit status of this process. + * + * ruby_options() sometimes returns a special value to indicate this process + * should immediately exit. This function checks if the case. Also stores the + * exit status that the caller have to pass to exit(3) into + * <code>*status</code>. + * + * @retval non-zero if the given opaque pointer is actually a compiled source. + * @retval 0 if the given value is such a special value. + */ +int +ruby_executable_node(void *n, int *status) +{ + VALUE v = (VALUE)n; + int s; + + switch (v) { + case Qtrue: s = EXIT_SUCCESS; break; + case Qfalse: s = EXIT_FAILURE; break; + default: + if (!FIXNUM_P(v)) return TRUE; + s = FIX2INT(v); + } + if (status) *status = s; + return FALSE; +} + +/*! Runs the given compiled source and exits this process. + * @retval 0 if successfully run the source + * @retval non-zero if an error occurred. +*/ +int +ruby_run_node(void *n) +{ + int status; + if (!ruby_executable_node(n, &status)) { + ruby_cleanup(0); + return status; + } + return ruby_cleanup(ruby_exec_node(n)); +} + +/*! Runs the given compiled source */ +int +ruby_exec_node(void *n) +{ + ruby_init_stack((void *)&n); + return ruby_exec_internal(n); +} + +/* + * call-seq: + * Module.nesting -> array + * + * Returns the list of +Modules+ nested at the point of call. + * + * module M1 + * module M2 + * $a = Module.nesting + * end + * end + * $a #=> [M1::M2, M1] + * $a[0].name #=> "M1::M2" + */ + +static VALUE +rb_mod_nesting(void) +{ + VALUE ary = rb_ary_new(); + const NODE *cref = rb_vm_cref(); + + while (cref && cref->nd_next) { + VALUE klass = cref->nd_clss; + if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && + !NIL_P(klass)) { + rb_ary_push(ary, klass); + } + cref = cref->nd_next; + } + return ary; +} + +/* + * call-seq: + * Module.constants -> array + * Module.constants(inherited) -> array + * + * In the first form, returns an array of the names of all + * constants accessible from the point of call. + * This list includes the names of all modules and classes + * defined in the global scope. + * + * Module.constants.first(4) + * # => [:ARGF, :ARGV, :ArgumentError, :Array] + * + * Module.constants.include?(:SEEK_SET) # => false + * + * class IO + * Module.constants.include?(:SEEK_SET) # => true + * end + * + * The second form calls the instance method +constants+. + */ + +static VALUE +rb_mod_s_constants(int argc, VALUE *argv, VALUE mod) +{ + const NODE *cref = rb_vm_cref(); + VALUE klass; + VALUE cbase = 0; + void *data = 0; + + if (argc > 0 || mod != rb_cModule) { + return rb_mod_constants(argc, argv, mod); + } + + while (cref) { + klass = cref->nd_clss; + if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && + !NIL_P(klass)) { + data = rb_mod_const_at(cref->nd_clss, data); + if (!cbase) { + cbase = klass; + } + } + cref = cref->nd_next; + } + + if (cbase) { + data = rb_mod_const_of(cbase, data); + } + return rb_const_list(data); +} + +void +rb_frozen_class_p(VALUE klass) +{ + if (SPECIAL_CONST_P(klass)) { + noclass: + Check_Type(klass, T_CLASS); + } + if (OBJ_FROZEN(klass)) { + const char *desc; + + if (FL_TEST(klass, FL_SINGLETON)) { + desc = "object"; + klass = rb_ivar_get(klass, id__attached__); + if (!SPECIAL_CONST_P(klass)) { + switch (BUILTIN_TYPE(klass)) { + case T_MODULE: + case T_ICLASS: + desc = "Module"; + break; + case T_CLASS: + desc = "Class"; + break; + } + } + } + else { + switch (BUILTIN_TYPE(klass)) { + case T_MODULE: + case T_ICLASS: + desc = "module"; + break; + case T_CLASS: + desc = "class"; + break; + default: + goto noclass; + } + } + rb_error_frozen(desc); + } +} + +NORETURN(static void rb_longjmp(int, volatile VALUE, VALUE)); +static VALUE get_errinfo(void); +static VALUE get_thread_errinfo(rb_thread_t *th); + +static VALUE +exc_setup_cause(VALUE exc, VALUE cause) +{ + ID id_cause; + CONST_ID(id_cause, "cause"); + +#if SUPPORT_JOKE + if (NIL_P(cause)) { + ID id_true_cause; + CONST_ID(id_true_cause, "true_cause"); + + cause = rb_attr_get(rb_eFatal, id_true_cause); + if (NIL_P(cause)) { + cause = rb_exc_new_cstr(rb_eFatal, "because using such Ruby"); + rb_ivar_set(cause, id_cause, INT2FIX(42)); /* the answer */ + OBJ_FREEZE(cause); + rb_ivar_set(rb_eFatal, id_true_cause, cause); + } + } +#endif + if (!NIL_P(cause) && cause != exc) { + rb_ivar_set(exc, id_cause, cause); + } + return exc; +} + +static inline int +sysstack_error_p(VALUE exc) +{ + return exc == sysstack_error || (!SPECIAL_CONST_P(exc) && RBASIC_CLASS(exc) == rb_eSysStackError); +} + +static void +setup_exception(rb_thread_t *th, int tag, volatile VALUE mesg, VALUE cause) +{ + VALUE e; + const char *file = 0; + volatile int line = 0; + int nocause = 0; + + if (NIL_P(mesg)) { + mesg = th->errinfo; + if (INTERNAL_EXCEPTION_P(mesg)) JUMP_TAG(TAG_FATAL); + nocause = 1; + } + if (NIL_P(mesg)) { + mesg = rb_exc_new(rb_eRuntimeError, 0, 0); + nocause = 0; + } + if (cause == Qundef) { + cause = nocause ? Qnil : get_thread_errinfo(th); + } + exc_setup_cause(mesg, cause); + + file = rb_sourcefile(); + if (file) line = rb_sourceline(); + if (file && !NIL_P(mesg)) { + VALUE at; + if (sysstack_error_p(mesg)) { + if (NIL_P(rb_attr_get(mesg, idBt))) { + at = rb_vm_backtrace_object(); + if (mesg == sysstack_error) { + mesg = ruby_vm_sysstack_error_copy(); + } + rb_ivar_set(mesg, idBt, at); + rb_ivar_set(mesg, idBt_locations, at); + } + } + else if (NIL_P(get_backtrace(mesg))) { + at = rb_vm_backtrace_object(); + if (OBJ_FROZEN(mesg)) { + mesg = rb_obj_dup(mesg); + } + rb_ivar_set(mesg, idBt_locations, at); + set_backtrace(mesg, at); + } + } + + if (!NIL_P(mesg)) { + th->errinfo = mesg; + } + + if (RTEST(ruby_debug) && !NIL_P(e = th->errinfo) && + !rb_obj_is_kind_of(e, rb_eSystemExit)) { + int status; + + mesg = e; + PUSH_TAG(); + if ((status = EXEC_TAG()) == 0) { + th->errinfo = Qnil; + e = rb_obj_as_string(mesg); + th->errinfo = mesg; + if (file && line) { + warn_printf("Exception `%"PRIsVALUE"' at %s:%d - %"PRIsVALUE"\n", + rb_obj_class(mesg), file, line, e); + } + else if (file) { + warn_printf("Exception `%"PRIsVALUE"' at %s - %"PRIsVALUE"\n", + rb_obj_class(mesg), file, e); + } + else { + warn_printf("Exception `%"PRIsVALUE"' - %"PRIsVALUE"\n", + rb_obj_class(mesg), e); + } + } + POP_TAG(); + if (status == TAG_FATAL && th->errinfo == exception_error) { + th->errinfo = mesg; + } + else if (status) { + rb_threadptr_reset_raised(th); + JUMP_TAG(status); + } + } + + if (rb_threadptr_set_raised(th)) { + th->errinfo = exception_error; + rb_threadptr_reset_raised(th); + JUMP_TAG(TAG_FATAL); + } + + if (tag != TAG_FATAL) { + if (RUBY_DTRACE_RAISE_ENABLED()) { + RUBY_DTRACE_RAISE(rb_obj_classname(th->errinfo), + rb_sourcefile(), + rb_sourceline()); + } + EXEC_EVENT_HOOK(th, RUBY_EVENT_RAISE, th->cfp->self, 0, 0, mesg); + } +} + +static void +rb_longjmp(int tag, volatile VALUE mesg, VALUE cause) +{ + rb_thread_t *th = GET_THREAD(); + setup_exception(th, tag, mesg, cause); + rb_thread_raised_clear(th); + JUMP_TAG(tag); +} + +static VALUE make_exception(int argc, const VALUE *argv, int isstr); + +void +rb_exc_raise(VALUE mesg) +{ + if (!NIL_P(mesg)) { + mesg = make_exception(1, &mesg, FALSE); + } + rb_longjmp(TAG_RAISE, mesg, Qundef); +} + +void +rb_exc_fatal(VALUE mesg) +{ + if (!NIL_P(mesg)) { + mesg = make_exception(1, &mesg, FALSE); + } + rb_longjmp(TAG_FATAL, mesg, Qnil); +} + +void +rb_interrupt(void) +{ + rb_raise(rb_eInterrupt, "%s", ""); +} + +enum {raise_opt_cause, raise_max_opt}; + +static int +extract_raise_opts(int argc, VALUE *argv, VALUE *opts) +{ + int i; + if (argc > 0) { + VALUE opt = argv[argc-1]; + if (RB_TYPE_P(opt, T_HASH)) { + if (!RHASH_EMPTY_P(opt)) { + ID keywords[1]; + CONST_ID(keywords[0], "cause"); + rb_get_kwargs(opt, keywords, 0, -1-raise_max_opt, opts); + if (RHASH_EMPTY_P(opt)) --argc; + return argc; + } + } + } + for (i = 0; i < raise_max_opt; ++i) { + opts[i] = Qundef; + } + return argc; +} + +/* + * call-seq: + * raise + * raise(string) + * raise(exception [, string [, array]]) + * fail + * fail(string) + * fail(exception [, string [, array]]) + * + * With no arguments, raises the exception in <code>$!</code> or raises + * a <code>RuntimeError</code> if <code>$!</code> is +nil+. + * With a single +String+ argument, raises a + * +RuntimeError+ with the string as a message. Otherwise, + * the first parameter should be the name of an +Exception+ + * class (or an object that returns an +Exception+ object when sent + * an +exception+ message). The optional second parameter sets the + * message associated with the exception, and the third parameter is an + * array of callback information. Exceptions are caught by the + * +rescue+ clause of <code>begin...end</code> blocks. + * + * raise "Failed to create socket" + * raise ArgumentError, "No parameters", caller + */ + +static VALUE +rb_f_raise(int argc, VALUE *argv) +{ + VALUE err; + VALUE opts[raise_max_opt], *const cause = &opts[raise_opt_cause]; + + argc = extract_raise_opts(argc, argv, opts); + if (argc == 0) { + if (*cause != Qundef) { + rb_raise(rb_eArgError, "only cause is given with no arguments"); + } + err = get_errinfo(); + if (!NIL_P(err)) { + argc = 1; + argv = &err; + } + } + rb_raise_jump(rb_make_exception(argc, argv), *cause); + + UNREACHABLE; +} + +static VALUE +make_exception(int argc, const VALUE *argv, int isstr) +{ + VALUE mesg, exc; + int n; + + mesg = Qnil; + switch (argc) { + case 0: + break; + case 1: + exc = argv[0]; + if (NIL_P(exc)) + break; + if (isstr) { + mesg = rb_check_string_type(exc); + if (!NIL_P(mesg)) { + mesg = rb_exc_new3(rb_eRuntimeError, mesg); + break; + } + } + n = 0; + goto exception_call; + + case 2: + case 3: + exc = argv[0]; + n = 1; + exception_call: + if (sysstack_error_p(exc)) return exc; + mesg = rb_check_funcall(exc, idException, n, argv+1); + if (mesg == Qundef) { + rb_raise(rb_eTypeError, "exception class/object expected"); + } + break; + default: + rb_check_arity(argc, 0, 3); + break; + } + if (argc > 0) { + if (!rb_obj_is_kind_of(mesg, rb_eException)) + rb_raise(rb_eTypeError, "exception object expected"); + if (argc > 2) + set_backtrace(mesg, argv[2]); + } + + return mesg; +} + +VALUE +rb_make_exception(int argc, const VALUE *argv) +{ + return make_exception(argc, argv, TRUE); +} + +void +rb_raise_jump(VALUE mesg, VALUE cause) +{ + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp = th->cfp; + VALUE klass = cfp->me->klass; + VALUE self = cfp->self; + ID mid = cfp->me->called_id; + + th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); + EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, self, mid, klass, Qnil); + + setup_exception(th, TAG_RAISE, mesg, cause); + + rb_thread_raised_clear(th); + JUMP_TAG(TAG_RAISE); +} + +void +rb_jump_tag(int tag) +{ + JUMP_TAG(tag); +} + +int +rb_block_given_p(void) +{ + rb_thread_t *th = GET_THREAD(); + + if (rb_vm_control_frame_block_ptr(th->cfp)) { + return TRUE; + } + else { + return FALSE; + } +} + +int +rb_iterator_p(void) +{ + return rb_block_given_p(); +} + +VALUE rb_eThreadError; + +void +rb_need_block(void) +{ + if (!rb_block_given_p()) { + rb_vm_localjump_error("no block given", Qnil, 0); + } +} + +VALUE +rb_rescue2(VALUE (* b_proc) (ANYARGS), VALUE data1, + VALUE (* r_proc) (ANYARGS), VALUE data2, ...) +{ + int state; + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp = th->cfp; + volatile VALUE result = Qfalse; + volatile VALUE e_info = th->errinfo; + va_list args; + + TH_PUSH_TAG(th); + if ((state = TH_EXEC_TAG()) == 0) { + retry_entry: + result = (*b_proc) (data1); + } + else if (result) { + /* escape from r_proc */ + if (state == TAG_RETRY) { + state = 0; + th->errinfo = Qnil; + result = Qfalse; + goto retry_entry; + } + } + else { + rb_vm_rewind_cfp(th, cfp); + + if (state == TAG_RAISE) { + int handle = FALSE; + VALUE eclass; + + va_init_list(args, data2); + while ((eclass = va_arg(args, VALUE)) != 0) { + if (rb_obj_is_kind_of(th->errinfo, eclass)) { + handle = TRUE; + break; + } + } + va_end(args); + + if (handle) { + result = Qnil; + state = 0; + if (r_proc) { + result = (*r_proc) (data2, th->errinfo); + } + th->errinfo = e_info; + } + } + } + TH_POP_TAG(); + if (state) + JUMP_TAG(state); + + return result; +} + +VALUE +rb_rescue(VALUE (* b_proc)(ANYARGS), VALUE data1, + VALUE (* r_proc)(ANYARGS), VALUE data2) +{ + return rb_rescue2(b_proc, data1, r_proc, data2, rb_eStandardError, + (VALUE)0); +} + +VALUE +rb_protect(VALUE (* proc) (VALUE), VALUE data, int * state) +{ + volatile VALUE result = Qnil; + volatile int status; + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp = th->cfp; + struct rb_vm_protect_tag protect_tag; + rb_jmpbuf_t org_jmpbuf; + + protect_tag.prev = th->protect_tag; + + TH_PUSH_TAG(th); + th->protect_tag = &protect_tag; + MEMCPY(&org_jmpbuf, &(th)->root_jmpbuf, rb_jmpbuf_t, 1); + if ((status = TH_EXEC_TAG()) == 0) { + SAVE_ROOT_JMPBUF(th, result = (*proc) (data)); + } + else { + rb_vm_rewind_cfp(th, cfp); + } + MEMCPY(&(th)->root_jmpbuf, &org_jmpbuf, rb_jmpbuf_t, 1); + th->protect_tag = protect_tag.prev; + TH_POP_TAG(); + + if (state) { + *state = status; + } + + return result; +} + +VALUE +rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2) +{ + int state; + volatile VALUE result = Qnil; + volatile VALUE errinfo; + rb_thread_t *const th = GET_THREAD(); + rb_ensure_list_t ensure_list; + ensure_list.entry.marker = 0; + ensure_list.entry.e_proc = e_proc; + ensure_list.entry.data2 = data2; + ensure_list.next = th->ensure_list; + th->ensure_list = &ensure_list; + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + result = (*b_proc) (data1); + } + POP_TAG(); + errinfo = th->errinfo; + th->ensure_list=ensure_list.next; + (*ensure_list.entry.e_proc)(ensure_list.entry.data2); + th->errinfo = errinfo; + if (state) + JUMP_TAG(state); + return result; +} + +static const rb_method_entry_t * +method_entry_of_iseq(rb_control_frame_t *cfp, rb_iseq_t *iseq) +{ + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp_limit; + + cfp_limit = (rb_control_frame_t *)(th->stack + th->stack_size); + while (cfp_limit > cfp) { + if (cfp->iseq == iseq) + return cfp->me; + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + return 0; +} + +static ID +frame_func_id(rb_control_frame_t *cfp) +{ + const rb_method_entry_t *me_local; + rb_iseq_t *iseq = cfp->iseq; + if (cfp->me) { + return cfp->me->def->original_id; + } + while (iseq) { + if (RUBY_VM_IFUNC_P(iseq)) { + NODE *ifunc = (NODE *)iseq; + if (ifunc->nd_aid) return ifunc->nd_aid; + return idIFUNC; + } + me_local = method_entry_of_iseq(cfp, iseq); + if (me_local) { + cfp->me = me_local; + return me_local->def->original_id; + } + if (iseq->defined_method_id) { + return iseq->defined_method_id; + } + if (iseq->local_iseq == iseq) { + break; + } + iseq = iseq->parent_iseq; + } + return 0; +} + +static ID +frame_called_id(rb_control_frame_t *cfp) +{ + const rb_method_entry_t *me_local; + rb_iseq_t *iseq = cfp->iseq; + if (cfp->me) { + return cfp->me->called_id; + } + while (iseq) { + if (RUBY_VM_IFUNC_P(iseq)) { + NODE *ifunc = (NODE *)iseq; + if (ifunc->nd_aid) return ifunc->nd_aid; + return idIFUNC; + } + me_local = method_entry_of_iseq(cfp, iseq); + if (me_local) { + cfp->me = me_local; + return me_local->called_id; + } + if (iseq->defined_method_id) { + return iseq->defined_method_id; + } + if (iseq->local_iseq == iseq) { + break; + } + iseq = iseq->parent_iseq; + } + return 0; +} + +ID +rb_frame_this_func(void) +{ + return frame_func_id(GET_THREAD()->cfp); +} + +ID +rb_frame_callee(void) +{ + return frame_called_id(GET_THREAD()->cfp); +} + +static rb_control_frame_t * +previous_frame(rb_thread_t *th) +{ + rb_control_frame_t *prev_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); + /* check if prev_cfp can be accessible */ + if ((void *)(th->stack + th->stack_size) == (void *)(prev_cfp)) { + return 0; + } + return prev_cfp; +} + +static ID +prev_frame_callee(void) +{ + rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + if (!prev_cfp) return 0; + return frame_called_id(prev_cfp); +} + +static ID +prev_frame_func(void) +{ + rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + if (!prev_cfp) return 0; + return frame_func_id(prev_cfp); +} + +ID +rb_frame_last_func(void) +{ + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp = th->cfp; + ID mid; + + while (!(mid = frame_func_id(cfp)) && + (cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp), + !RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(th, cfp))); + return mid; +} + +/* + * call-seq: + * append_features(mod) -> mod + * + * When this module is included in another, Ruby calls + * <code>append_features</code> in this module, passing it the + * receiving module in _mod_. Ruby's default implementation is + * to add the constants, methods, and module variables of this module + * to _mod_ if this module has not already been added to + * _mod_ or one of its ancestors. See also <code>Module#include</code>. + */ + +static VALUE +rb_mod_append_features(VALUE module, VALUE include) +{ + if (!CLASS_OR_MODULE_P(include)) { + Check_Type(include, T_CLASS); + } + rb_include_module(include, module); + + return module; +} + +/* + * call-seq: + * include(module, ...) -> self + * + * Invokes <code>Module.append_features</code> on each parameter in reverse order. + */ + +static VALUE +rb_mod_include(int argc, VALUE *argv, VALUE module) +{ + int i; + ID id_append_features, id_included; + + CONST_ID(id_append_features, "append_features"); + CONST_ID(id_included, "included"); + + for (i = 0; i < argc; i++) + Check_Type(argv[i], T_MODULE); + while (argc--) { + rb_funcall(argv[argc], id_append_features, 1, module); + rb_funcall(argv[argc], id_included, 1, module); + } + return module; +} + +/* + * call-seq: + * prepend_features(mod) -> mod + * + * When this module is prepended in another, Ruby calls + * <code>prepend_features</code> in this module, passing it the + * receiving module in _mod_. Ruby's default implementation is + * to overlay the constants, methods, and module variables of this module + * to _mod_ if this module has not already been added to + * _mod_ or one of its ancestors. See also <code>Module#prepend</code>. + */ + +static VALUE +rb_mod_prepend_features(VALUE module, VALUE prepend) +{ + if (!CLASS_OR_MODULE_P(prepend)) { + Check_Type(prepend, T_CLASS); + } + rb_prepend_module(prepend, module); + + return module; +} + +/* + * call-seq: + * prepend(module, ...) -> self + * + * Invokes <code>Module.prepend_features</code> on each parameter in reverse order. + */ + +static VALUE +rb_mod_prepend(int argc, VALUE *argv, VALUE module) +{ + int i; + ID id_prepend_features, id_prepended; + + CONST_ID(id_prepend_features, "prepend_features"); + CONST_ID(id_prepended, "prepended"); + for (i = 0; i < argc; i++) + Check_Type(argv[i], T_MODULE); + while (argc--) { + rb_funcall(argv[argc], id_prepend_features, 1, module); + rb_funcall(argv[argc], id_prepended, 1, module); + } + return module; +} + +static VALUE +hidden_identity_hash_new(void) +{ + VALUE hash = rb_hash_new(); + + rb_funcall(hash, rb_intern("compare_by_identity"), 0); + RBASIC_CLEAR_CLASS(hash); /* hide from ObjectSpace */ + return hash; +} + +void +rb_using_refinement(NODE *cref, VALUE klass, VALUE module) +{ + VALUE iclass, c, superclass = klass; + + Check_Type(klass, T_CLASS); + Check_Type(module, T_MODULE); + if (NIL_P(cref->nd_refinements)) { + RB_OBJ_WRITE(cref, &cref->nd_refinements, hidden_identity_hash_new()); + } + else { + if (cref->flags & NODE_FL_CREF_OMOD_SHARED) { + RB_OBJ_WRITE(cref, &cref->nd_refinements, rb_hash_dup(cref->nd_refinements)); + cref->flags &= ~NODE_FL_CREF_OMOD_SHARED; + } + if (!NIL_P(c = rb_hash_lookup(cref->nd_refinements, klass))) { + superclass = c; + while (c && RB_TYPE_P(c, T_ICLASS)) { + if (RBASIC(c)->klass == module) { + /* already used refinement */ + return; + } + c = RCLASS_SUPER(c); + } + } + } + FL_SET(module, RMODULE_IS_OVERLAID); + c = iclass = rb_include_class_new(module, superclass); + RCLASS_REFINED_CLASS(c) = klass; + + RCLASS_M_TBL_WRAPPER(OBJ_WB_UNPROTECT(c)) = + RCLASS_M_TBL_WRAPPER(OBJ_WB_UNPROTECT(module)); + + module = RCLASS_SUPER(module); + while (module && module != klass) { + FL_SET(module, RMODULE_IS_OVERLAID); + c = RCLASS_SET_SUPER(c, rb_include_class_new(module, RCLASS_SUPER(c))); + RCLASS_REFINED_CLASS(c) = klass; + module = RCLASS_SUPER(module); + } + rb_hash_aset(cref->nd_refinements, klass, iclass); +} + +static int +using_refinement(VALUE klass, VALUE module, VALUE arg) +{ + NODE *cref = (NODE *) arg; + + rb_using_refinement(cref, klass, module); + return ST_CONTINUE; +} + +static void +using_module_recursive(NODE *cref, VALUE klass) +{ + ID id_refinements; + VALUE super, module, refinements; + + super = RCLASS_SUPER(klass); + if (super) { + using_module_recursive(cref, super); + } + switch (BUILTIN_TYPE(klass)) { + case T_MODULE: + module = klass; + break; + + case T_ICLASS: + module = RBASIC(klass)->klass; + break; + + default: + rb_raise(rb_eTypeError, "wrong argument type %s (expected Module)", + rb_obj_classname(klass)); + break; + } + CONST_ID(id_refinements, "__refinements__"); + refinements = rb_attr_get(module, id_refinements); + if (NIL_P(refinements)) return; + rb_hash_foreach(refinements, using_refinement, (VALUE) cref); +} + +void +rb_using_module(NODE *cref, VALUE module) +{ + Check_Type(module, T_MODULE); + using_module_recursive(cref, module); + rb_clear_method_cache_by_class(rb_cObject); +} + +VALUE +rb_refinement_module_get_refined_class(VALUE module) +{ + ID id_refined_class; + + CONST_ID(id_refined_class, "__refined_class__"); + return rb_attr_get(module, id_refined_class); +} + +static void +add_activated_refinement(VALUE activated_refinements, + VALUE klass, VALUE refinement) +{ + VALUE iclass, c, superclass = klass; + + if (!NIL_P(c = rb_hash_lookup(activated_refinements, klass))) { + superclass = c; + while (c && RB_TYPE_P(c, T_ICLASS)) { + if (RBASIC(c)->klass == refinement) { + /* already used refinement */ + return; + } + c = RCLASS_SUPER(c); + } + } + FL_SET(refinement, RMODULE_IS_OVERLAID); + c = iclass = rb_include_class_new(refinement, superclass); + RCLASS_REFINED_CLASS(c) = klass; + refinement = RCLASS_SUPER(refinement); + while (refinement) { + FL_SET(refinement, RMODULE_IS_OVERLAID); + c = RCLASS_SET_SUPER(c, rb_include_class_new(refinement, RCLASS_SUPER(c))); + RCLASS_REFINED_CLASS(c) = klass; + refinement = RCLASS_SUPER(refinement); + } + rb_hash_aset(activated_refinements, klass, iclass); +} + +/* + * call-seq: + * refine(klass) { block } -> module + * + * Refine <i>klass</i> in the receiver. + * + * Returns an overlaid module. + */ + +static VALUE +rb_mod_refine(VALUE module, VALUE klass) +{ + VALUE refinement; + ID id_refinements, id_activated_refinements, + id_refined_class, id_defined_at; + VALUE refinements, activated_refinements; + rb_thread_t *th = GET_THREAD(); + rb_block_t *block = rb_vm_control_frame_block_ptr(th->cfp); + + if (!block) { + rb_raise(rb_eArgError, "no block given"); + } + if (block->proc) { + rb_raise(rb_eArgError, + "can't pass a Proc as a block to Module#refine"); + } + Check_Type(klass, T_CLASS); + CONST_ID(id_refinements, "__refinements__"); + refinements = rb_attr_get(module, id_refinements); + if (NIL_P(refinements)) { + refinements = hidden_identity_hash_new(); + rb_ivar_set(module, id_refinements, refinements); + } + CONST_ID(id_activated_refinements, "__activated_refinements__"); + activated_refinements = rb_attr_get(module, id_activated_refinements); + if (NIL_P(activated_refinements)) { + activated_refinements = hidden_identity_hash_new(); + rb_ivar_set(module, id_activated_refinements, + activated_refinements); + } + refinement = rb_hash_lookup(refinements, klass); + if (NIL_P(refinement)) { + refinement = rb_module_new(); + RCLASS_SET_SUPER(refinement, klass); + FL_SET(refinement, RMODULE_IS_REFINEMENT); + CONST_ID(id_refined_class, "__refined_class__"); + rb_ivar_set(refinement, id_refined_class, klass); + CONST_ID(id_defined_at, "__defined_at__"); + rb_ivar_set(refinement, id_defined_at, module); + rb_hash_aset(refinements, klass, refinement); + add_activated_refinement(activated_refinements, klass, refinement); + } + rb_yield_refine_block(refinement, activated_refinements); + return refinement; +} + +/* + * call-seq: + * using(module) -> self + * + * Import class refinements from <i>module</i> into the current class or + * module definition. + */ + +static VALUE +mod_using(VALUE self, VALUE module) +{ + NODE *cref = rb_vm_cref(); + rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + + if (prev_frame_func()) { + rb_raise(rb_eRuntimeError, + "Module#using is not permitted in methods"); + } + if (prev_cfp && prev_cfp->self != self) { + rb_raise(rb_eRuntimeError, "Module#using is not called on self"); + } + rb_using_module(cref, module); + return self; +} + +void +rb_obj_call_init(VALUE obj, int argc, const VALUE *argv) +{ + PASS_PASSED_BLOCK(); + rb_funcall2(obj, idInitialize, argc, argv); +} + +void +rb_extend_object(VALUE obj, VALUE module) +{ + rb_include_module(rb_singleton_class(obj), module); +} + +/* + * call-seq: + * extend_object(obj) -> obj + * + * Extends the specified object by adding this module's constants and + * methods (which are added as singleton methods). This is the callback + * method used by <code>Object#extend</code>. + * + * module Picky + * def Picky.extend_object(o) + * if String === o + * puts "Can't add Picky to a String" + * else + * puts "Picky added to #{o.class}" + * super + * end + * end + * end + * (s = Array.new).extend Picky # Call Object.extend + * (s = "quick brown fox").extend Picky + * + * <em>produces:</em> + * + * Picky added to Array + * Can't add Picky to a String + */ + +static VALUE +rb_mod_extend_object(VALUE mod, VALUE obj) +{ + rb_extend_object(obj, mod); + return obj; +} + +/* + * call-seq: + * obj.extend(module, ...) -> obj + * + * Adds to _obj_ the instance methods from each module given as a + * parameter. + * + * module Mod + * def hello + * "Hello from Mod.\n" + * end + * end + * + * class Klass + * def hello + * "Hello from Klass.\n" + * end + * end + * + * k = Klass.new + * k.hello #=> "Hello from Klass.\n" + * k.extend(Mod) #=> #<Klass:0x401b3bc8> + * k.hello #=> "Hello from Mod.\n" + */ + +static VALUE +rb_obj_extend(int argc, VALUE *argv, VALUE obj) +{ + int i; + ID id_extend_object, id_extended; + + CONST_ID(id_extend_object, "extend_object"); + CONST_ID(id_extended, "extended"); + + rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); + for (i = 0; i < argc; i++) + Check_Type(argv[i], T_MODULE); + while (argc--) { + rb_funcall(argv[argc], id_extend_object, 1, obj); + rb_funcall(argv[argc], id_extended, 1, obj); + } + return obj; +} + +/* + * call-seq: + * include(module, ...) -> self + * + * Invokes <code>Module.append_features</code> + * on each parameter in turn. Effectively adds the methods and constants + * in each module to the receiver. + */ + +static VALUE +top_include(int argc, VALUE *argv, VALUE self) +{ + rb_thread_t *th = GET_THREAD(); + + if (th->top_wrapper) { + rb_warning("main.include in the wrapped load is effective only in wrapper module"); + return rb_mod_include(argc, argv, th->top_wrapper); + } + return rb_mod_include(argc, argv, rb_cObject); +} + +/* + * call-seq: + * using(module) -> self + * + * Import class refinements from <i>module</i> into the scope where + * <code>using</code> is called. + */ + +static VALUE +top_using(VALUE self, VALUE module) +{ + NODE *cref = rb_vm_cref(); + rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + + if (cref->nd_next || (prev_cfp && prev_cfp->me)) { + rb_raise(rb_eRuntimeError, + "main.using is permitted only at toplevel"); + } + rb_using_module(cref, module); + return self; +} + +static VALUE * +errinfo_place(rb_thread_t *th) +{ + rb_control_frame_t *cfp = th->cfp; + rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(th); + + while (RUBY_VM_VALID_CONTROL_FRAME_P(cfp, end_cfp)) { + if (RUBY_VM_NORMAL_ISEQ_P(cfp->iseq)) { + if (cfp->iseq->type == ISEQ_TYPE_RESCUE) { + return &cfp->ep[-2]; + } + else if (cfp->iseq->type == ISEQ_TYPE_ENSURE && + !RB_TYPE_P(cfp->ep[-2], T_NODE) && + !FIXNUM_P(cfp->ep[-2])) { + return &cfp->ep[-2]; + } + } + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + } + return 0; +} + +static VALUE +get_thread_errinfo(rb_thread_t *th) +{ + VALUE *ptr = errinfo_place(th); + if (ptr) { + return *ptr; + } + else { + return th->errinfo; + } +} + +static VALUE +get_errinfo(void) +{ + return get_thread_errinfo(GET_THREAD()); +} + +static VALUE +errinfo_getter(ID id) +{ + return get_errinfo(); +} + +#if 0 +static void +errinfo_setter(VALUE val, ID id, VALUE *var) +{ + if (!NIL_P(val) && !rb_obj_is_kind_of(val, rb_eException)) { + rb_raise(rb_eTypeError, "assigning non-exception to $!"); + } + else { + VALUE *ptr = errinfo_place(GET_THREAD()); + if (ptr) { + *ptr = val; + } + else { + rb_raise(rb_eRuntimeError, "errinfo_setter: not in rescue clause."); + } + } +} +#endif + +VALUE +rb_errinfo(void) +{ + rb_thread_t *th = GET_THREAD(); + return th->errinfo; +} + +void +rb_set_errinfo(VALUE err) +{ + if (!NIL_P(err) && !rb_obj_is_kind_of(err, rb_eException)) { + rb_raise(rb_eTypeError, "assigning non-exception to $!"); + } + GET_THREAD()->errinfo = err; +} + +VALUE +rb_rubylevel_errinfo(void) +{ + return get_errinfo(); +} + +static VALUE +errat_getter(ID id) +{ + VALUE err = get_errinfo(); + if (!NIL_P(err)) { + return get_backtrace(err); + } + else { + return Qnil; + } +} + +static void +errat_setter(VALUE val, ID id, VALUE *var) +{ + VALUE err = get_errinfo(); + if (NIL_P(err)) { + rb_raise(rb_eArgError, "$! not set"); + } + set_backtrace(err, val); +} + +/* + * call-seq: + * __method__ -> symbol + * + * Returns the name at the definition of the current method as a + * Symbol. + * If called outside of a method, it returns <code>nil</code>. + * + */ + +static VALUE +rb_f_method_name(void) +{ + ID fname = prev_frame_func(); /* need *method* ID */ + + if (fname) { + return ID2SYM(fname); + } + else { + return Qnil; + } +} + +/* + * call-seq: + * __callee__ -> symbol + * + * Returns the called name of the current method as a Symbol. + * If called outside of a method, it returns <code>nil</code>. + * + */ + +static VALUE +rb_f_callee_name(void) +{ + ID fname = prev_frame_callee(); /* need *callee* ID */ + + if (fname) { + return ID2SYM(fname); + } + else { + return Qnil; + } +} + +/* + * call-seq: + * __dir__ -> string + * + * Returns the canonicalized absolute path of the directory of the file from + * which this method is called. It means symlinks in the path is resolved. + * If <code>__FILE__</code> is <code>nil</code>, it returns <code>nil</code>. + * The return value equals to <code>File.dirname(File.realpath(__FILE__))</code>. + * + */ +static VALUE +f_current_dirname(void) +{ + VALUE base = rb_current_realfilepath(); + if (NIL_P(base)) { + return Qnil; + } + base = rb_file_dirname(base); + return base; +} + +void +Init_eval(void) +{ + rb_define_virtual_variable("$@", errat_getter, errat_setter); + rb_define_virtual_variable("$!", errinfo_getter, 0); + + rb_define_global_function("raise", rb_f_raise, -1); + rb_define_global_function("fail", rb_f_raise, -1); + + rb_define_global_function("global_variables", rb_f_global_variables, 0); /* in variable.c */ + + rb_define_global_function("__method__", rb_f_method_name, 0); + rb_define_global_function("__callee__", rb_f_callee_name, 0); + rb_define_global_function("__dir__", f_current_dirname, 0); + + rb_define_method(rb_cModule, "include", rb_mod_include, -1); + rb_define_method(rb_cModule, "prepend", rb_mod_prepend, -1); + + rb_define_private_method(rb_cModule, "append_features", rb_mod_append_features, 1); + rb_define_private_method(rb_cModule, "extend_object", rb_mod_extend_object, 1); + rb_define_private_method(rb_cModule, "prepend_features", rb_mod_prepend_features, 1); + rb_define_private_method(rb_cModule, "refine", rb_mod_refine, 1); + rb_define_private_method(rb_cModule, "using", mod_using, 1); + rb_undef_method(rb_cClass, "refine"); + + rb_undef_method(rb_cClass, "module_function"); + + Init_vm_eval(); + Init_eval_method(); + + rb_define_singleton_method(rb_cModule, "nesting", rb_mod_nesting, 0); + rb_define_singleton_method(rb_cModule, "constants", rb_mod_s_constants, -1); + + rb_define_private_method(rb_singleton_class(rb_vm_top_self()), + "include", top_include, -1); + rb_define_private_method(rb_singleton_class(rb_vm_top_self()), + "using", top_using, 1); + + rb_define_method(rb_mKernel, "extend", rb_obj_extend, -1); + + rb_define_global_function("trace_var", rb_f_trace_var, -1); /* in variable.c */ + rb_define_global_function("untrace_var", rb_f_untrace_var, -1); /* in variable.c */ + + rb_vm_register_special_exception(ruby_error_reenter, rb_eFatal, "exception reentered"); +} |