diff options
Diffstat (limited to 'eval.c')
| -rw-r--r-- | eval.c | 1410 |
1 files changed, 824 insertions, 586 deletions
@@ -11,20 +11,46 @@ **********************************************************************/ -#include "internal.h" +#include "ruby/internal/config.h" + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + #include "eval_intern.h" -#include "iseq.h" #include "gc.h" +#include "internal.h" +#include "internal/class.h" +#include "internal/error.h" +#include "internal/eval.h" +#include "internal/hash.h" +#include "internal/inits.h" +#include "internal/io.h" +#include "internal/object.h" +#include "internal/thread.h" +#include "internal/variable.h" +#include "ruby/fiber/scheduler.h" +#include "iseq.h" +#include "mjit.h" +#include "probes.h" +#include "probes_helper.h" #include "ruby/vm.h" #include "vm_core.h" -#include "probes_helper.h" +#include "ractor_core.h" -NORETURN(void rb_raise_jump(VALUE, VALUE)); +NORETURN(static void rb_raise_jump(VALUE, VALUE)); +void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec); +void rb_ec_clear_all_trace_func(const rb_execution_context_t *ec); + +static int rb_ec_cleanup(rb_execution_context_t *ec, int ex); +static int rb_ec_exec_node(rb_execution_context_t *ec, void *n); VALUE rb_eLocalJumpError; VALUE rb_eSysStackError; -static ID id_cause; +ID ruby_static_id_signo, ruby_static_id_status; +extern ID ruby_static_id_cause; +#define id_cause ruby_static_id_cause #define exception_error GET_VM()->special_exceptions[ruby_error_reenter] @@ -35,250 +61,236 @@ static ID id_cause; (!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; + enum ruby_tag_type state; - if (initialized) + if (GET_VM()) return 0; - initialized = 1; ruby_init_stack((void *)&state); + + /* + * Disable THP early before mallocs happen because we want this to + * affect as many future pages as possible for CoW-friendliness + */ +#if defined(__linux__) && defined(PR_SET_THP_DISABLE) + prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0); +#endif Init_BareVM(); Init_heap(); + rb_vm_encoded_insn_data_table_init(); Init_vm_objects(); - Init_frozen_strings(); - PUSH_TAG(); - if ((state = EXEC_TAG()) == 0) { + EC_PUSH_TAG(GET_EC()); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { rb_call_inits(); ruby_prog_init(); GET_VM()->running = 1; } - POP_TAG(); + EC_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(); + if (RTEST(ruby_debug)) + error_print(GET_EC()); 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; + rb_execution_context_t *ec = GET_EC(); + enum ruby_tag_type state; void *volatile iseq = 0; ruby_init_stack((void *)&iseq); - PUSH_TAG(); - if ((state = EXEC_TAG()) == 0) { + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { SAVE_ROOT_JMPBUF(GET_THREAD(), iseq = ruby_process_options(argc, argv)); } else { - rb_clear_trace_func(); - state = error_handle(state); + rb_ec_clear_current_thread_trace_func(ec); + state = error_handle(ec, state); iseq = (void *)INT2FIX(state); } - POP_TAG(); + EC_POP_TAG(); return iseq; } static void -ruby_finalize_0(void) +rb_ec_fiber_scheduler_finalize(rb_execution_context_t *ec) +{ + enum ruby_tag_type state; + + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + rb_fiber_scheduler_set(Qnil); + } + else { + state = error_handle(ec, state); + } + EC_POP_TAG(); +} + +static void +rb_ec_teardown(rb_execution_context_t *ec) { - PUSH_TAG(); - if (EXEC_TAG() == 0) { - rb_trap_exit(); + // If the user code defined a scheduler for the top level thread, run it: + rb_ec_fiber_scheduler_finalize(ec); + + EC_PUSH_TAG(ec); + if (EC_EXEC_TAG() == TAG_NONE) { + rb_vm_trap_exit(rb_ec_vm_ptr(ec)); } - POP_TAG(); - rb_exec_end_proc(); - rb_clear_trace_func(); + EC_POP_TAG(); + rb_ec_exec_end_proc(ec); + rb_ec_clear_all_trace_func(ec); } static void -ruby_finalize_1(void) +rb_ec_finalize(rb_execution_context_t *ec) { ruby_sig_finalize(); - GET_THREAD()->errinfo = Qnil; - rb_gc_call_finalizer_at_exit(); + ec->errinfo = Qnil; + rb_objspace_call_finalizer(rb_ec_vm_ptr(ec)->objspace); } -/** 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(); + rb_execution_context_t *ec = GET_EC(); + rb_ec_teardown(ec); + rb_ec_finalize(ec); } -/** 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) +ruby_cleanup(int ex) +{ + return rb_ec_cleanup(GET_EC(), ex); +} + +static int +rb_ec_cleanup(rb_execution_context_t *ec, int ex0) { int state; - volatile VALUE errs[2]; - rb_thread_t *th = GET_THREAD(); + volatile VALUE errs[2] = { Qundef, Qundef }; int nerr; + rb_thread_t *th = rb_ec_thread_ptr(ec); + rb_thread_t *const volatile th0 = th; + volatile int sysex = EXIT_SUCCESS; + volatile int step = 0; + volatile int ex = ex0; 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)]); + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + SAVE_ROOT_JMPBUF(th, { RUBY_VM_CHECK_INTS(ec); }); - PUSH_TAG(); - if ((state = EXEC_TAG()) == 0) { - SAVE_ROOT_JMPBUF(th, ruby_finalize_0()); - } - POP_TAG(); + step_0: step++; + errs[1] = ec->errinfo; + if (THROW_DATA_P(ec->errinfo)) ec->errinfo = Qnil; + ruby_init_stack(&errs[STACK_UPPER(errs, 0, 1)]); - /* protect from Thread#raise */ - th->status = THREAD_KILLED; + SAVE_ROOT_JMPBUF(th, rb_ec_teardown(ec)); - 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); + step_1: step++; + /* protect from Thread#raise */ + th->status = THREAD_KILLED; -#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 + errs[0] = ec->errinfo; + SAVE_ROOT_JMPBUF(th, rb_ractor_terminate_all()); } -#endif + else { + th = th0; + switch (step) { + case 0: goto step_0; + case 1: goto step_1; + } + if (ex == 0) ex = state; + } + ec->errinfo = errs[1]; + sysex = error_handle(ec, ex); state = 0; for (nerr = 0; nerr < numberof(errs); ++nerr) { - VALUE err = errs[nerr]; + VALUE err = ATOMIC_VALUE_EXCHANGE(errs[nerr], Qnil); + VALUE sig; if (!RTEST(err)) continue; - /* th->errinfo contains a NODE while break'ing */ - if (RB_TYPE_P(err, T_NODE)) continue; + /* ec->errinfo contains a NODE while break'ing */ + if (THROW_DATA_P(err)) continue; if (rb_obj_is_kind_of(err, rb_eSystemExit)) { - ex = sysexit_status(err); + sysex = sysexit_status(err); break; } else if (rb_obj_is_kind_of(err, rb_eSignal)) { - VALUE sig = rb_iv_get(err, "signo"); + VALUE sig = rb_ivar_get(err, id_signo); state = NUM2INT(sig); break; } - else if (ex == EXIT_SUCCESS) { - ex = EXIT_FAILURE; + else if (rb_obj_is_kind_of(err, rb_eSystemCallError) && + FIXNUM_P(sig = rb_attr_get(err, id_signo))) { + state = NUM2INT(sig); + break; + } + else if (sysex == EXIT_SUCCESS) { + sysex = EXIT_FAILURE; } } - ruby_finalize_1(); + mjit_finish(true); // We still need ISeqs here. + + rb_ec_finalize(ec); /* 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()); + rb_threadptr_unlock_all_locking_mutexes(th); + th = th0; + EC_POP_TAG(); + th = th0; + rb_thread_stop_timer_thread(); + ruby_vm_destruct(th->vm); if (state) ruby_default_signal(state); - return ex; + return sysex; } static int -ruby_exec_internal(void *n) +rb_ec_exec_node(rb_execution_context_t *ec, void *n) { volatile int state; - VALUE iseq = (VALUE)n; - rb_thread_t *th = GET_THREAD(); - + rb_iseq_t *iseq = (rb_iseq_t *)n; if (!n) return 0; - PUSH_TAG(); - if ((state = EXEC_TAG()) == 0) { + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + rb_thread_t *const th = rb_ec_thread_ptr(ec); SAVE_ROOT_JMPBUF(th, { - th->base_block = 0; rb_iseq_eval_main(iseq); }); } - POP_TAG(); + EC_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) { @@ -296,27 +308,24 @@ ruby_executable_node(void *n, int *status) 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) { + rb_execution_context_t *ec = GET_EC(); int status; if (!ruby_executable_node(n, &status)) { - ruby_cleanup(0); + rb_ec_cleanup(ec, 0); return status; } - return ruby_cleanup(ruby_exec_node(n)); + ruby_init_stack((void *)&status); + return rb_ec_cleanup(ec, rb_ec_exec_node(ec, n)); } -/*! Runs the given compiled source */ int ruby_exec_node(void *n) { ruby_init_stack((void *)&n); - return ruby_exec_internal(n); + return rb_ec_exec_node(GET_EC(), n); } /* @@ -335,18 +344,18 @@ ruby_exec_node(void *n) */ static VALUE -rb_mod_nesting(void) +rb_mod_nesting(VALUE _) { VALUE ary = rb_ary_new(); - const NODE *cref = rb_vm_cref(); + const rb_cref_t *cref = rb_vm_cref(); - while (cref && cref->nd_next) { - VALUE klass = cref->nd_clss; - if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && + while (cref && CREF_NEXT(cref)) { + VALUE klass = CREF_CLASS(cref); + if (!CREF_PUSHED_BY_EVAL(cref) && !NIL_P(klass)) { rb_ary_push(ary, klass); } - cref = cref->nd_next; + cref = CREF_NEXT(cref); } return ary; } @@ -376,7 +385,7 @@ rb_mod_nesting(void) static VALUE rb_mod_s_constants(int argc, VALUE *argv, VALUE mod) { - const NODE *cref = rb_vm_cref(); + const rb_cref_t *cref = rb_vm_cref(); VALUE klass; VALUE cbase = 0; void *data = 0; @@ -386,15 +395,15 @@ rb_mod_s_constants(int argc, VALUE *argv, VALUE mod) } while (cref) { - klass = cref->nd_clss; - if (!(cref->flags & NODE_FL_CREF_PUSHED_BY_EVAL) && + klass = CREF_CLASS(cref); + if (!CREF_PUSHED_BY_EVAL(cref) && !NIL_P(klass)) { - data = rb_mod_const_at(cref->nd_clss, data); + data = rb_mod_const_at(CREF_CLASS(cref), data); if (!cbase) { cbase = klass; } } - cref = cref->nd_next; + cref = CREF_NEXT(cref); } if (cbase) { @@ -403,13 +412,21 @@ rb_mod_s_constants(int argc, VALUE *argv, VALUE mod) return rb_const_list(data); } +/*! + * Asserts that \a klass is not a frozen class. + * \param[in] klass a \c Module object + * \exception RuntimeError if \a klass is not a class or frozen. + * \ingroup class + */ void -rb_frozen_class_p(VALUE klass) +rb_class_modify_check(VALUE klass) { if (SPECIAL_CONST_P(klass)) { - noclass: Check_Type(klass, T_CLASS); } + if (RB_TYPE_P(klass, T_MODULE)) { + rb_module_set_initialized(klass); + } if (OBJ_FROZEN(klass)) { const char *desc; @@ -425,6 +442,8 @@ rb_frozen_class_p(VALUE klass) case T_CLASS: desc = "Class"; break; + default: + break; } } } @@ -438,21 +457,22 @@ rb_frozen_class_p(VALUE klass) desc = "class"; break; default: - goto noclass; + Check_Type(klass, T_CLASS); + UNREACHABLE; } } - rb_error_frozen(desc); + rb_frozen_error_raise(klass, "can't modify frozen %s: %"PRIsVALUE, desc, klass); } } -NORETURN(static void rb_longjmp(int, volatile VALUE, VALUE)); +NORETURN(static void rb_longjmp(rb_execution_context_t *, int, volatile VALUE, VALUE)); static VALUE get_errinfo(void); -static VALUE get_thread_errinfo(rb_thread_t *th); +#define get_ec_errinfo(ec) rb_ec_get_errinfo(ec) static VALUE exc_setup_cause(VALUE exc, VALUE cause) { -#if SUPPORT_JOKE +#if OPT_SUPPORT_JOKE if (NIL_P(cause)) { ID id_true_cause; CONST_ID(id_true_cause, "true_cause"); @@ -468,183 +488,222 @@ exc_setup_cause(VALUE exc, VALUE cause) #endif if (!NIL_P(cause) && cause != exc) { rb_ivar_set(exc, id_cause, cause); + if (!rb_ivar_defined(cause, id_cause)) { + rb_ivar_set(cause, id_cause, Qnil); + } } return exc; } -static inline int -sysstack_error_p(VALUE exc) +static inline VALUE +exc_setup_message(const rb_execution_context_t *ec, VALUE mesg, VALUE *cause) { - 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; + int nocircular = 0; if (NIL_P(mesg)) { - mesg = th->errinfo; - if (INTERNAL_EXCEPTION_P(mesg)) JUMP_TAG(TAG_FATAL); + mesg = ec->errinfo; + if (INTERNAL_EXCEPTION_P(mesg)) EC_JUMP_TAG(ec, TAG_FATAL); nocause = 1; } if (NIL_P(mesg)) { mesg = rb_exc_new(rb_eRuntimeError, 0, 0); nocause = 0; + nocircular = 1; } - if (cause != Qundef) { - exc_setup_cause(mesg, cause); - } - else if (nocause) { - exc_setup_cause(mesg, Qnil); - } - else if (!rb_ivar_defined(mesg, id_cause)) { - exc_setup_cause(mesg, get_thread_errinfo(th)); + if (*cause == Qundef) { + if (nocause) { + *cause = Qnil; + nocircular = 1; + } + else if (!rb_ivar_defined(mesg, id_cause)) { + *cause = get_ec_errinfo(ec); + } + else { + nocircular = 1; + } + } + else if (!NIL_P(*cause) && !rb_obj_is_kind_of(*cause, rb_eException)) { + rb_raise(rb_eTypeError, "exception object expected"); + } + + if (!nocircular && !NIL_P(*cause) && *cause != Qundef && *cause != mesg) { +#if 0 /* maybe critical for some cases */ + rb_exc_check_circular_cause(*cause); +#else + VALUE c = *cause; + while (!NIL_P(c = rb_attr_get(c, id_cause))) { + if (c == mesg) { + rb_raise(rb_eArgError, "circular causes"); + } + } +#endif } + return mesg; +} - 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(); +static void +setup_exception(rb_execution_context_t *ec, int tag, volatile VALUE mesg, VALUE cause) +{ + VALUE e; + int line; + const char *file = rb_source_location_cstr(&line); + const char *const volatile file0 = file; + + if ((file && !NIL_P(mesg)) || (cause != Qundef)) { + volatile int state = 0; + + EC_PUSH_TAG(ec); + if (EC_EXEC_TAG() == TAG_NONE && !(state = rb_ec_set_raised(ec))) { + VALUE bt = rb_get_backtrace(mesg); + if (!NIL_P(bt) || cause == Qundef) { + if (OBJ_FROZEN(mesg)) { + mesg = rb_obj_dup(mesg); } - rb_ivar_set(mesg, idBt, at); - rb_ivar_set(mesg, idBt_locations, at); } - } - else { - int status; - - TH_PUSH_TAG(th); - if ((status = EXEC_TAG()) == 0) { - VALUE bt; - if (rb_threadptr_set_raised(th)) goto fatal; - bt = rb_get_backtrace(mesg); - if (NIL_P(bt)) { - 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); - } - rb_threadptr_reset_raised(th); + if (cause != Qundef && !THROW_DATA_P(cause)) { + exc_setup_cause(mesg, cause); } - TH_POP_TAG(); + if (NIL_P(bt)) { + VALUE at = rb_ec_backtrace_object(ec); + rb_ivar_set(mesg, idBt_locations, at); + set_backtrace(mesg, at); + } + rb_ec_reset_raised(ec); } + EC_POP_TAG(); + file = file0; + if (state) goto fatal; } if (!NIL_P(mesg)) { - th->errinfo = mesg; + ec->errinfo = mesg; } - if (RTEST(ruby_debug) && !NIL_P(e = th->errinfo) && + if (RTEST(ruby_debug) && !NIL_P(e = ec->errinfo) && !rb_obj_is_kind_of(e, rb_eSystemExit)) { - int status; + enum ruby_tag_type state; mesg = e; - PUSH_TAG(); - if ((status = EXEC_TAG()) == 0) { - th->errinfo = Qnil; + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + ec->errinfo = Qnil; e = rb_obj_as_string(mesg); - th->errinfo = mesg; + ec->errinfo = mesg; if (file && line) { - warn_printf("Exception `%"PRIsVALUE"' at %s:%d - %"PRIsVALUE"\n", - rb_obj_class(mesg), file, line, e); + e = rb_sprintf("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); + e = rb_sprintf("Exception `%"PRIsVALUE"' at %s - %"PRIsVALUE"\n", + rb_obj_class(mesg), file, e); } else { - warn_printf("Exception `%"PRIsVALUE"' - %"PRIsVALUE"\n", - rb_obj_class(mesg), e); + e = rb_sprintf("Exception `%"PRIsVALUE"' - %"PRIsVALUE"\n", + rb_obj_class(mesg), e); } + warn_print_str(e); } - POP_TAG(); - if (status == TAG_FATAL && th->errinfo == exception_error) { - th->errinfo = mesg; + EC_POP_TAG(); + if (state == TAG_FATAL && ec->errinfo == exception_error) { + ec->errinfo = mesg; } - else if (status) { - rb_threadptr_reset_raised(th); - JUMP_TAG(status); + else if (state) { + rb_ec_reset_raised(ec); + EC_JUMP_TAG(ec, state); } } - if (rb_threadptr_set_raised(th)) { - fatal: - th->errinfo = exception_error; - rb_threadptr_reset_raised(th); - JUMP_TAG(TAG_FATAL); + if (rb_ec_set_raised(ec)) { + goto 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); + RUBY_DTRACE_HOOK(RAISE, rb_obj_classname(ec->errinfo)); + EXEC_EVENT_HOOK(ec, RUBY_EVENT_RAISE, ec->cfp->self, 0, 0, 0, mesg); } + return; + + fatal: + ec->errinfo = exception_error; + rb_ec_reset_raised(ec); + EC_JUMP_TAG(ec, TAG_FATAL); } +/*! \private */ void -rb_threadptr_setup_exception(rb_thread_t *th, VALUE mesg, VALUE cause) +rb_ec_setup_exception(const rb_execution_context_t *ec, VALUE mesg, VALUE cause) { if (cause == Qundef) { - cause = get_thread_errinfo(th); + cause = get_ec_errinfo(ec); } if (cause != mesg) { - rb_ivar_set(mesg, id_cause, cause); + if (THROW_DATA_P(cause)) { + cause = Qnil; + } + + rb_ivar_set(mesg, id_cause, cause); } } static void -rb_longjmp(int tag, volatile VALUE mesg, VALUE cause) +rb_longjmp(rb_execution_context_t *ec, 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); + mesg = exc_setup_message(ec, mesg, &cause); + setup_exception(ec, tag, mesg, cause); + rb_ec_raised_clear(ec); + EC_JUMP_TAG(ec, tag); } static VALUE make_exception(int argc, const VALUE *argv, int isstr); -void -rb_exc_raise(VALUE mesg) +NORETURN(static void rb_exc_exception(VALUE mesg, int tag, VALUE cause)); + +static void +rb_exc_exception(VALUE mesg, int tag, VALUE cause) { if (!NIL_P(mesg)) { mesg = make_exception(1, &mesg, FALSE); } - rb_longjmp(TAG_RAISE, mesg, Qundef); + rb_longjmp(GET_EC(), tag, mesg, cause); } +/*! + * Raises an exception in the current thread. + * \param[in] mesg an Exception class or an \c Exception object. + * \exception always raises an instance of the given exception class or + * the given \c Exception object. + * \ingroup exception + */ +void +rb_exc_raise(VALUE mesg) +{ + rb_exc_exception(mesg, TAG_RAISE, Qundef); +} + +/*! + * Raises a fatal error in the current thread. + * + * Same as rb_exc_raise() but raises a fatal error, which Ruby codes + * cannot rescue. + * \ingroup exception + */ void rb_exc_fatal(VALUE mesg) { - if (!NIL_P(mesg)) { - mesg = make_exception(1, &mesg, FALSE); - } - rb_longjmp(TAG_FATAL, mesg, Qnil); + rb_exc_exception(mesg, TAG_FATAL, Qnil); } void rb_interrupt(void) { - rb_raise(rb_eInterrupt, "%s", ""); + rb_exc_raise(rb_exc_new(rb_eInterrupt, 0, 0)); } -enum {raise_opt_cause, raise_max_opt}; +enum {raise_opt_cause, raise_max_opt}; /*< \private */ static int -extract_raise_opts(int argc, VALUE *argv, VALUE *opts) +extract_raise_opts(int argc, const VALUE *argv, VALUE *opts) { int i; if (argc > 0) { @@ -665,96 +724,98 @@ extract_raise_opts(int argc, VALUE *argv, VALUE *opts) return argc; } +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_RETURN(Qnil); +} + /* * call-seq: * raise - * raise(string) - * raise(exception [, string [, array]]) + * raise(string, cause: $!) + * raise(exception [, string [, array]], cause: $!) * fail - * fail(string) - * fail(exception [, string [, array]]) + * fail(string, cause: $!) + * fail(exception [, string [, array]], cause: $!) * * 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. + * a RuntimeError 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 an +Exception+ class (or another + * object that returns an +Exception+ object when sent an +exception+ + * message). The optional second parameter sets the message associated with + * the exception (accessible via Exception#message), and the third parameter + * is an array of callback information (accessible via Exception#backtrace). + * The +cause+ of the generated exception (accessible via Exception#cause) + * is automatically set to the "current" exception (<code>$!</code>), if any. + * An alternative value, either an +Exception+ object or +nil+, can be + * specified via the +:cause+ argument. + * + * 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) +f_raise(int c, VALUE *v, VALUE _) { - 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; + return rb_f_raise(c, v); } static VALUE make_exception(int argc, const VALUE *argv, int isstr) { VALUE mesg, exc; - int n; mesg = Qnil; switch (argc) { case 0: - break; + return Qnil; case 1: exc = argv[0]; - if (NIL_P(exc)) - break; - if (isstr) { + if (isstr &&! NIL_P(exc)) { mesg = rb_check_string_type(exc); if (!NIL_P(mesg)) { - mesg = rb_exc_new3(rb_eRuntimeError, mesg); - break; + return rb_exc_new3(rb_eRuntimeError, mesg); } } - 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; + rb_error_arity(argc, 0, 3); } - 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]); + if (NIL_P(mesg)) { + mesg = rb_check_funcall(argv[0], idException, argc != 1, &argv[1]); + } + if (mesg == Qundef) { + rb_raise(rb_eTypeError, "exception class/object expected"); + } + if (!rb_obj_is_kind_of(mesg, rb_eException)) { + rb_raise(rb_eTypeError, "exception object expected"); + } + if (argc == 3) { + set_backtrace(mesg, argv[2]); } return mesg; @@ -766,47 +827,50 @@ rb_make_exception(int argc, const VALUE *argv) return make_exception(argc, argv, TRUE); } -void +/*! \private + */ +static 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; + rb_execution_context_t *ec = GET_EC(); + const rb_control_frame_t *cfp = ec->cfp; + const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp); + VALUE klass = me->owner; 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); + ID mid = me->called_id; - setup_exception(th, TAG_RAISE, mesg, cause); + rb_vm_pop_frame(ec); + EXEC_EVENT_HOOK(ec, RUBY_EVENT_C_RETURN, self, me->def->original_id, mid, klass, Qnil); - rb_thread_raised_clear(th); - JUMP_TAG(TAG_RAISE); + rb_longjmp(ec, TAG_RAISE, mesg, cause); } void rb_jump_tag(int tag) { - JUMP_TAG(tag); + if (UNLIKELY(tag < TAG_RETURN || tag > TAG_FATAL)) { + unknown_longjmp_status(tag); + } + EC_JUMP_TAG(GET_EC(), tag); } int rb_block_given_p(void) { - rb_thread_t *th = GET_THREAD(); - - if (rb_vm_control_frame_block_ptr(th->cfp)) { - return TRUE; + if (rb_vm_frame_block_handler(GET_EC()->cfp) == VM_BLOCK_HANDLER_NONE) { + return FALSE; } else { - return FALSE; + return TRUE; } } +int rb_vm_cframe_keyword_p(const rb_control_frame_t *cfp); + int -rb_iterator_p(void) +rb_keyword_given_p(void) { - return rb_block_given_p(); + return rb_vm_cframe_keyword_p(GET_EC()->cfp); } VALUE rb_eThreadError; @@ -820,223 +884,179 @@ rb_need_block(void) } VALUE -rb_rescue2(VALUE (* b_proc) (ANYARGS), VALUE data1, - VALUE (* r_proc) (ANYARGS), VALUE data2, ...) +rb_rescue2(VALUE (* b_proc) (VALUE), VALUE data1, + VALUE (* r_proc) (VALUE, VALUE), VALUE data2, ...) { - int state; - rb_thread_t *th = GET_THREAD(); - rb_control_frame_t *volatile cfp = th->cfp; + va_list ap; + va_start(ap, data2); + VALUE ret = rb_vrescue2(b_proc, data1, r_proc, data2, ap); + va_end(ap); + return ret; +} + +VALUE +rb_vrescue2(VALUE (* b_proc) (VALUE), VALUE data1, + VALUE (* r_proc) (VALUE, VALUE), VALUE data2, + va_list args) +{ + enum ruby_tag_type state; + rb_execution_context_t * volatile ec = GET_EC(); + rb_control_frame_t *volatile cfp = ec->cfp; volatile VALUE result = Qfalse; - volatile VALUE e_info = th->errinfo; - va_list args; + volatile VALUE e_info = ec->errinfo; - TH_PUSH_TAG(th); - if ((state = TH_EXEC_TAG()) == 0) { + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { retry_entry: result = (*b_proc) (data1); } else if (result) { /* escape from r_proc */ if (state == TAG_RETRY) { - state = 0; - th->errinfo = Qnil; + state = TAG_NONE; + ec->errinfo = Qnil; result = Qfalse; goto retry_entry; } } else { - rb_vm_rewind_cfp(th, cfp); + rb_vm_rewind_cfp(ec, cfp); if (state == TAG_RAISE) { int handle = FALSE; VALUE eclass; + va_list ap; - va_init_list(args, data2); - while ((eclass = va_arg(args, VALUE)) != 0) { - if (rb_obj_is_kind_of(th->errinfo, eclass)) { + result = Qnil; + /* reuses args when raised again after retrying in r_proc */ + va_copy(ap, args); + while ((eclass = va_arg(ap, VALUE)) != 0) { + if (rb_obj_is_kind_of(ec->errinfo, eclass)) { handle = TRUE; break; } } - va_end(args); + va_end(ap); if (handle) { - result = Qnil; - state = 0; + state = TAG_NONE; if (r_proc) { - result = (*r_proc) (data2, th->errinfo); + result = (*r_proc) (data2, ec->errinfo); } - th->errinfo = e_info; + ec->errinfo = e_info; } } } - TH_POP_TAG(); + EC_POP_TAG(); if (state) - JUMP_TAG(state); + EC_JUMP_TAG(ec, state); return result; } VALUE -rb_rescue(VALUE (* b_proc)(ANYARGS), VALUE data1, - VALUE (* r_proc)(ANYARGS), VALUE data2) +rb_rescue(VALUE (* b_proc)(VALUE), VALUE data1, + VALUE (* r_proc)(VALUE, VALUE), 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) +rb_protect(VALUE (* proc) (VALUE), VALUE data, int *pstate) { volatile VALUE result = Qnil; - volatile int status; - rb_thread_t *th = GET_THREAD(); - rb_control_frame_t *volatile cfp = th->cfp; - struct rb_vm_protect_tag protect_tag; - rb_jmpbuf_t org_jmpbuf; - - protect_tag.prev = th->protect_tag; + volatile enum ruby_tag_type state; + rb_execution_context_t * volatile ec = GET_EC(); + rb_control_frame_t *volatile cfp = ec->cfp; - 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)); + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { + SAVE_ROOT_JMPBUF(rb_ec_thread_ptr(ec), 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; + rb_vm_rewind_cfp(ec, cfp); } + EC_POP_TAG(); + if (pstate != NULL) *pstate = state; return result; } VALUE -rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2) +rb_ensure(VALUE (*b_proc)(VALUE), VALUE data1, VALUE (*e_proc)(VALUE), VALUE data2) { int state; volatile VALUE result = Qnil; - volatile VALUE errinfo; - rb_thread_t *const th = GET_THREAD(); + VALUE errinfo; + rb_execution_context_t * volatile ec = GET_EC(); 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) { + ensure_list.next = ec->ensure_list; + ec->ensure_list = &ensure_list; + EC_PUSH_TAG(ec); + if ((state = EC_EXEC_TAG()) == TAG_NONE) { result = (*b_proc) (data1); } - POP_TAG(); - errinfo = th->errinfo; - th->ensure_list=ensure_list.next; + EC_POP_TAG(); + errinfo = ec->errinfo; + if (!NIL_P(errinfo) && !RB_TYPE_P(errinfo, T_OBJECT)) { + ec->errinfo = Qnil; + } + ec->ensure_list=ensure_list.next; (*ensure_list.entry.e_proc)(ensure_list.entry.data2); - th->errinfo = errinfo; + ec->errinfo = errinfo; if (state) - JUMP_TAG(state); + EC_JUMP_TAG(ec, state); return result; } -static const rb_method_entry_t * -method_entry_of_iseq(rb_control_frame_t *cfp, rb_iseq_t *iseq) +static ID +frame_func_id(const rb_control_frame_t *cfp) { - rb_thread_t *th = GET_THREAD(); - rb_control_frame_t *cfp_limit; + const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp); - 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); + if (me) { + return me->def->original_id; } - 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; + else { + return 0; } - 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; + const rb_callable_method_entry_t *me = rb_vm_frame_method_entry(cfp); + + if (me) { + return 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; + else { + return 0; } - return 0; } ID rb_frame_this_func(void) { - return frame_func_id(GET_THREAD()->cfp); + return frame_func_id(GET_EC()->cfp); } ID rb_frame_callee(void) { - return frame_called_id(GET_THREAD()->cfp); + return frame_called_id(GET_EC()->cfp); } static rb_control_frame_t * -previous_frame(rb_thread_t *th) +previous_frame(const rb_execution_context_t *ec) { - rb_control_frame_t *prev_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); + rb_control_frame_t *prev_cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(ec->cfp); /* check if prev_cfp can be accessible */ - if ((void *)(th->stack + th->stack_size) == (void *)(prev_cfp)) { + if ((void *)(ec->vm_stack + ec->vm_stack_size) == (void *)(prev_cfp)) { return 0; } return prev_cfp; @@ -1045,7 +1065,7 @@ previous_frame(rb_thread_t *th) static ID prev_frame_callee(void) { - rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + rb_control_frame_t *prev_cfp = previous_frame(GET_EC()); if (!prev_cfp) return 0; return frame_called_id(prev_cfp); } @@ -1053,21 +1073,27 @@ prev_frame_callee(void) static ID prev_frame_func(void) { - rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + rb_control_frame_t *prev_cfp = previous_frame(GET_EC()); if (!prev_cfp) return 0; return frame_func_id(prev_cfp); } +/*! + * \private + * Returns the ID of the last method in the call stack. + * \sa rb_frame_this_func + * \ingroup defmethod + */ ID rb_frame_last_func(void) { - rb_thread_t *th = GET_THREAD(); - rb_control_frame_t *cfp = th->cfp; + const rb_execution_context_t *ec = GET_EC(); + const rb_control_frame_t *cfp = ec->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))); + !RUBY_VM_CONTROL_FRAME_STACK_OVERFLOW_P(ec, cfp))); return mid; } @@ -1076,11 +1102,11 @@ rb_frame_last_func(void) * 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>. + * #append_features 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 Module#include. */ static VALUE @@ -1098,7 +1124,7 @@ rb_mod_append_features(VALUE module, VALUE include) * call-seq: * include(module, ...) -> self * - * Invokes <code>Module.append_features</code> on each parameter in reverse order. + * Invokes Module.append_features on each parameter in reverse order. */ static VALUE @@ -1110,6 +1136,11 @@ rb_mod_include(int argc, VALUE *argv, VALUE module) CONST_ID(id_append_features, "append_features"); CONST_ID(id_included, "included"); + if (FL_TEST(module, RMODULE_IS_REFINEMENT)) { + rb_warn_deprecated_to_remove_at(3.2, "Refinement#include", NULL); + } + + rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); for (i = 0; i < argc; i++) Check_Type(argv[i], T_MODULE); while (argc--) { @@ -1124,11 +1155,11 @@ rb_mod_include(int argc, VALUE *argv, VALUE module) * 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>. + * #prepend_features 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 Module#prepend. */ static VALUE @@ -1146,7 +1177,7 @@ rb_mod_prepend_features(VALUE module, VALUE prepend) * call-seq: * prepend(module, ...) -> self * - * Invokes <code>Module.prepend_features</code> on each parameter in reverse order. + * Invokes Module.prepend_features on each parameter in reverse order. */ static VALUE @@ -1155,8 +1186,14 @@ rb_mod_prepend(int argc, VALUE *argv, VALUE module) int i; ID id_prepend_features, id_prepended; + if (FL_TEST(module, RMODULE_IS_REFINEMENT)) { + rb_warn_deprecated_to_remove_at(3.2, "Refinement#prepend", NULL); + } + CONST_ID(id_prepend_features, "prepend_features"); CONST_ID(id_prepended, "prepended"); + + rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); for (i = 0; i < argc; i++) Check_Type(argv[i], T_MODULE); while (argc--) { @@ -1166,32 +1203,56 @@ rb_mod_prepend(int argc, VALUE *argv, VALUE module) return module; } +static void +ensure_class_or_module(VALUE obj) +{ + if (!RB_TYPE_P(obj, T_CLASS) && !RB_TYPE_P(obj, T_MODULE)) { + rb_raise(rb_eTypeError, + "wrong argument type %"PRIsVALUE" (expected Class or Module)", + rb_obj_class(obj)); + } +} + static VALUE hidden_identity_hash_new(void) { - VALUE hash = rb_hash_new(); + VALUE hash = rb_ident_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) +static VALUE +refinement_superclass(VALUE superclass) +{ + if (RB_TYPE_P(superclass, T_MODULE)) { + /* FIXME: Should ancestors of superclass be used here? */ + return rb_include_class_new(RCLASS_ORIGIN(superclass), rb_cBasicObject); + } + else { + return superclass; + } +} + +/*! + * \private + */ +static void +rb_using_refinement(rb_cref_t *cref, VALUE klass, VALUE module) { VALUE iclass, c, superclass = klass; - Check_Type(klass, T_CLASS); + ensure_class_or_module(klass); Check_Type(module, T_MODULE); - if (NIL_P(cref->nd_refinements)) { - RB_OBJ_WRITE(cref, &cref->nd_refinements, hidden_identity_hash_new()); + if (NIL_P(CREF_REFINEMENTS(cref))) { + CREF_REFINEMENTS_SET(cref, 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 (CREF_OMOD_SHARED(cref)) { + CREF_REFINEMENTS_SET(cref, rb_hash_dup(CREF_REFINEMENTS(cref))); + CREF_OMOD_SHARED_UNSET(cref); } - if (!NIL_P(c = rb_hash_lookup(cref->nd_refinements, klass))) { + if (!NIL_P(c = rb_hash_lookup(CREF_REFINEMENTS(cref), klass))) { superclass = c; while (c && RB_TYPE_P(c, T_ICLASS)) { if (RBASIC(c)->klass == module) { @@ -1203,33 +1264,33 @@ rb_using_refinement(NODE *cref, VALUE klass, VALUE module) } } FL_SET(module, RMODULE_IS_OVERLAID); + superclass = refinement_superclass(superclass); c = iclass = rb_include_class_new(module, superclass); - RCLASS_REFINED_CLASS(c) = klass; + RB_OBJ_WRITE(c, &RCLASS_REFINED_CLASS(c), klass); - RCLASS_M_TBL_WRAPPER(OBJ_WB_UNPROTECT(c)) = - RCLASS_M_TBL_WRAPPER(OBJ_WB_UNPROTECT(module)); + RCLASS_M_TBL(c) = RCLASS_M_TBL(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_OBJ_WRITE(c, &RCLASS_REFINED_CLASS(c), klass); + module = RCLASS_SUPER(module); } - rb_hash_aset(cref->nd_refinements, klass, iclass); + rb_hash_aset(CREF_REFINEMENTS(cref), klass, iclass); } static int using_refinement(VALUE klass, VALUE module, VALUE arg) { - NODE *cref = (NODE *) arg; + rb_cref_t *cref = (rb_cref_t *) arg; rb_using_refinement(cref, klass, module); return ST_CONTINUE; } static void -using_module_recursive(NODE *cref, VALUE klass) +using_module_recursive(const rb_cref_t *cref, VALUE klass) { ID id_refinements; VALUE super, module, refinements; @@ -1258,14 +1319,18 @@ using_module_recursive(NODE *cref, VALUE klass) rb_hash_foreach(refinements, using_refinement, (VALUE) cref); } -void -rb_using_module(NODE *cref, VALUE module) +/*! + * \private + */ +static void +rb_using_module(const rb_cref_t *cref, VALUE module) { Check_Type(module, T_MODULE); using_module_recursive(cref, module); - rb_clear_method_cache_by_class(rb_cObject); + rb_clear_method_cache_all(); } +/*! \private */ VALUE rb_refinement_module_get_refined_class(VALUE module) { @@ -1292,13 +1357,14 @@ add_activated_refinement(VALUE activated_refinements, } } FL_SET(refinement, RMODULE_IS_OVERLAID); + superclass = refinement_superclass(superclass); c = iclass = rb_include_class_new(refinement, superclass); - RCLASS_REFINED_CLASS(c) = klass; + RB_OBJ_WRITE(c, &RCLASS_REFINED_CLASS(c), klass); refinement = RCLASS_SUPER(refinement); - while (refinement) { + while (refinement && refinement != klass) { FL_SET(refinement, RMODULE_IS_OVERLAID); c = RCLASS_SET_SUPER(c, rb_include_class_new(refinement, RCLASS_SUPER(c))); - RCLASS_REFINED_CLASS(c) = klass; + RB_OBJ_WRITE(c, &RCLASS_REFINED_CLASS(c), klass); refinement = RCLASS_SUPER(refinement); } rb_hash_aset(activated_refinements, klass, iclass); @@ -1306,11 +1372,11 @@ add_activated_refinement(VALUE activated_refinements, /* * call-seq: - * refine(klass) { block } -> module + * refine(mod) { block } -> module * - * Refine <i>klass</i> in the receiver. + * Refine <i>mod</i> in the receiver. * - * Returns an overlaid module. + * Returns a module, where refined methods are defined. */ static VALUE @@ -1321,16 +1387,16 @@ rb_mod_refine(VALUE module, VALUE klass) 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); + VALUE block_handler = rb_vm_frame_block_handler(th->ec->cfp); - if (!block) { - rb_raise(rb_eArgError, "no block given"); + if (block_handler == VM_BLOCK_HANDLER_NONE) { + 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"); + if (vm_block_handler_type(block_handler) != block_handler_type_iseq) { + rb_raise(rb_eArgError, "can't pass a Proc as a block to Module#refine"); } - Check_Type(klass, T_CLASS); + + ensure_class_or_module(klass); CONST_ID(id_refinements, "__refinements__"); refinements = rb_attr_get(module, id_refinements); if (NIL_P(refinements)) { @@ -1346,8 +1412,9 @@ rb_mod_refine(VALUE module, VALUE klass) } refinement = rb_hash_lookup(refinements, klass); if (NIL_P(refinement)) { - refinement = rb_module_new(); - RCLASS_SET_SUPER(refinement, klass); + VALUE superclass = refinement_superclass(klass); + refinement = rb_refinement_new(); + RCLASS_SET_SUPER(refinement, superclass); FL_SET(refinement, RMODULE_IS_REFINEMENT); CONST_ID(id_refined_class, "__refined_class__"); rb_ivar_set(refinement, id_refined_class, klass); @@ -1360,6 +1427,17 @@ rb_mod_refine(VALUE module, VALUE klass) return refinement; } +static void +ignored_block(VALUE module, const char *klass) +{ + const char *anon = ""; + Check_Type(module, T_MODULE); + if (!RTEST(rb_search_class_path(module))) { + anon = ", maybe for Module.new"; + } + rb_warn("%s""using doesn't call the given block""%s.", klass, anon); +} + /* * call-seq: * using(module) -> self @@ -1371,8 +1449,7 @@ rb_mod_refine(VALUE module, VALUE klass) static VALUE mod_using(VALUE self, VALUE module) { - NODE *cref = rb_vm_cref(); - rb_control_frame_t *prev_cfp = previous_frame(GET_THREAD()); + rb_control_frame_t *prev_cfp = previous_frame(GET_EC()); if (prev_frame_func()) { rb_raise(rb_eRuntimeError, @@ -1381,15 +1458,129 @@ mod_using(VALUE self, VALUE module) if (prev_cfp && prev_cfp->self != self) { rb_raise(rb_eRuntimeError, "Module#using is not called on self"); } - rb_using_module(cref, module); + if (rb_block_given_p()) { + ignored_block(module, "Module#"); + } + rb_using_module(rb_vm_cref_replace_with_duplicated_cref(), module); return self; } +static int +used_modules_i(VALUE _, VALUE mod, VALUE ary) +{ + ID id_defined_at; + CONST_ID(id_defined_at, "__defined_at__"); + while (FL_TEST(rb_class_of(mod), RMODULE_IS_REFINEMENT)) { + rb_ary_push(ary, rb_attr_get(rb_class_of(mod), id_defined_at)); + mod = RCLASS_SUPER(mod); + } + return ST_CONTINUE; +} + +/* + * call-seq: + * used_modules -> array + * + * Returns an array of all modules used in the current scope. The ordering + * of modules in the resulting array is not defined. + * + * module A + * refine Object do + * end + * end + * + * module B + * refine Object do + * end + * end + * + * using A + * using B + * p Module.used_modules + * + * <em>produces:</em> + * + * [B, A] + */ +static VALUE +rb_mod_s_used_modules(VALUE _) +{ + const rb_cref_t *cref = rb_vm_cref(); + VALUE ary = rb_ary_new(); + + while (cref) { + if (!NIL_P(CREF_REFINEMENTS(cref))) { + rb_hash_foreach(CREF_REFINEMENTS(cref), used_modules_i, ary); + } + cref = CREF_NEXT(cref); + } + + return rb_funcall(ary, rb_intern("uniq"), 0); +} + +struct refinement_import_methods_arg { + rb_cref_t *cref; + VALUE refinement; + VALUE module; +}; + +/* vm.c */ +rb_cref_t *rb_vm_cref_dup_without_refinements(const rb_cref_t *cref); + +static enum rb_id_table_iterator_result +refinement_import_methods_i(ID key, VALUE value, void *data) +{ + const rb_method_entry_t *me = (const rb_method_entry_t *)value; + struct refinement_import_methods_arg *arg = (struct refinement_import_methods_arg *)data; + + if (me->def->type != VM_METHOD_TYPE_ISEQ) { + rb_raise(rb_eArgError, "Can't import method which is not defined with Ruby code: %"PRIsVALUE"#%"PRIsVALUE, rb_class_path(arg->module), rb_id2str(key)); + } + rb_cref_t *new_cref = rb_vm_cref_dup_without_refinements(me->def->body.iseq.cref); + CREF_REFINEMENTS_SET(new_cref, CREF_REFINEMENTS(arg->cref)); + rb_add_method_iseq(arg->refinement, key, me->def->body.iseq.iseqptr, new_cref, METHOD_ENTRY_VISI(me)); + return ID_TABLE_CONTINUE; +} + +/* + * Note: docs for the method are in class.c + */ + +static VALUE +refinement_import_methods(int argc, VALUE *argv, VALUE refinement) +{ + int i; + struct refinement_import_methods_arg arg; + + rb_check_arity(argc, 1, UNLIMITED_ARGUMENTS); + for (i = 0; i < argc; i++) { + Check_Type(argv[i], T_MODULE); + if (RCLASS_SUPER(argv[i])) { + rb_warn("%"PRIsVALUE" has ancestors, but Refinement#import_methods doesn't import their methods", rb_class_path(argv[i])); + } + } + arg.cref = rb_vm_cref_replace_with_duplicated_cref(); + arg.refinement = refinement; + for (i = 0; i < argc; i++) { + arg.module = argv[i]; + struct rb_id_table *m_tbl = RCLASS_M_TBL(argv[i]); + if (!m_tbl) continue; + rb_id_table_foreach(m_tbl, refinement_import_methods_i, &arg); + } + return refinement; +} + void rb_obj_call_init(VALUE obj, int argc, const VALUE *argv) { - PASS_PASSED_BLOCK(); - rb_funcall2(obj, idInitialize, argc, argv); + rb_obj_call_init_kw(obj, argc, argv, RB_NO_KEYWORDS); +} + +void +rb_obj_call_init_kw(VALUE obj, int argc, const VALUE *argv, int kw_splat) +{ + PASS_PASSED_BLOCK_HANDLER(); + rb_funcallv_kw(obj, idInitialize, argc, argv, kw_splat); } void @@ -1404,7 +1595,7 @@ rb_extend_object(VALUE obj, VALUE module) * * 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>. + * method used by Object#extend. * * module Picky * def Picky.extend_object(o) @@ -1480,9 +1671,9 @@ rb_obj_extend(int argc, VALUE *argv, VALUE 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. + * Invokes Module.append_features on each parameter in turn. + * Effectively adds the methods and constants in each module to the + * receiver. */ static VALUE @@ -1502,38 +1693,40 @@ top_include(int argc, VALUE *argv, VALUE self) * using(module) -> self * * Import class refinements from <i>module</i> into the scope where - * <code>using</code> is called. + * #using 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()); + const rb_cref_t *cref = rb_vm_cref(); + rb_control_frame_t *prev_cfp = previous_frame(GET_EC()); - if (cref->nd_next || (prev_cfp && prev_cfp->me)) { - rb_raise(rb_eRuntimeError, - "main.using is permitted only at toplevel"); + if (CREF_NEXT(cref) || (prev_cfp && rb_vm_frame_method_entry(prev_cfp))) { + rb_raise(rb_eRuntimeError, "main.using is permitted only at toplevel"); + } + if (rb_block_given_p()) { + ignored_block(module, "main."); } - rb_using_module(cref, module); + rb_using_module(rb_vm_cref_replace_with_duplicated_cref(), module); return self; } -static VALUE * -errinfo_place(rb_thread_t *th) +static const VALUE * +errinfo_place(const rb_execution_context_t *ec) { - rb_control_frame_t *cfp = th->cfp; - rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(th); + const rb_control_frame_t *cfp = ec->cfp; + const rb_control_frame_t *end_cfp = RUBY_VM_END_CONTROL_FRAME(ec); 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]; + if (VM_FRAME_RUBYFRAME_P(cfp)) { + if (cfp->iseq->body->type == ISEQ_TYPE_RESCUE) { + return &cfp->ep[VM_ENV_INDEX_LAST_LVAR]; } - 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]; + else if (cfp->iseq->body->type == ISEQ_TYPE_ENSURE && + !THROW_DATA_P(cfp->ep[VM_ENV_INDEX_LAST_LVAR]) && + !FIXNUM_P(cfp->ep[VM_ENV_INDEX_LAST_LVAR])) { + return &cfp->ep[VM_ENV_INDEX_LAST_LVAR]; } } cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); @@ -1541,54 +1734,34 @@ errinfo_place(rb_thread_t *th) return 0; } -static VALUE -get_thread_errinfo(rb_thread_t *th) +VALUE +rb_ec_get_errinfo(const rb_execution_context_t *ec) { - VALUE *ptr = errinfo_place(th); + const VALUE *ptr = errinfo_place(ec); if (ptr) { return *ptr; } else { - return th->errinfo; + return ec->errinfo; } } static VALUE get_errinfo(void) { - return get_thread_errinfo(GET_THREAD()); + return get_ec_errinfo(GET_EC()); } static VALUE -errinfo_getter(ID id) +errinfo_getter(ID id, VALUE *_) { 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; + return GET_EC()->errinfo; } void @@ -1597,17 +1770,11 @@ 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(); + GET_EC()->errinfo = err; } static VALUE -errat_getter(ID id) +errat_getter(ID id, VALUE *_) { VALUE err = get_errinfo(); if (!NIL_P(err)) { @@ -1639,7 +1806,7 @@ errat_setter(VALUE val, ID id, VALUE *var) */ static VALUE -rb_f_method_name(void) +rb_f_method_name(VALUE _) { ID fname = prev_frame_func(); /* need *method* ID */ @@ -1661,7 +1828,7 @@ rb_f_method_name(void) */ static VALUE -rb_f_callee_name(void) +rb_f_callee_name(VALUE _) { ID fname = prev_frame_callee(); /* need *callee* ID */ @@ -1684,7 +1851,7 @@ rb_f_callee_name(void) * */ static VALUE -f_current_dirname(void) +f_current_dirname(VALUE _) { VALUE base = rb_current_realfilepath(); if (NIL_P(base)) { @@ -1694,16 +1861,82 @@ f_current_dirname(void) return base; } +/* + * call-seq: + * global_variables -> array + * + * Returns an array of the names of global variables. This includes + * special regexp global variables such as <tt>$~</tt> and <tt>$+</tt>, + * but does not include the numbered regexp global variables (<tt>$1</tt>, + * <tt>$2</tt>, etc.). + * + * global_variables.grep /std/ #=> [:$stdin, :$stdout, :$stderr] + */ + +static VALUE +f_global_variables(VALUE _) +{ + return rb_f_global_variables(); +} + +/* + * call-seq: + * trace_var(symbol, cmd ) -> nil + * trace_var(symbol) {|val| block } -> nil + * + * Controls tracing of assignments to global variables. The parameter + * +symbol+ identifies the variable (as either a string name or a + * symbol identifier). _cmd_ (which may be a string or a + * +Proc+ object) or block is executed whenever the variable + * is assigned. The block or +Proc+ object receives the + * variable's new value as a parameter. Also see + * Kernel::untrace_var. + * + * trace_var :$_, proc {|v| puts "$_ is now '#{v}'" } + * $_ = "hello" + * $_ = ' there' + * + * <em>produces:</em> + * + * $_ is now 'hello' + * $_ is now ' there' + */ + +static VALUE +f_trace_var(int c, const VALUE *a, VALUE _) +{ + return rb_f_trace_var(c, a); +} + +/* + * call-seq: + * untrace_var(symbol [, cmd] ) -> array or nil + * + * Removes tracing for the specified command on the given global + * variable and returns +nil+. If no command is specified, + * removes all tracing for that variable and returns an array + * containing the commands actually removed. + */ + +static VALUE +f_untrace_var(int c, const VALUE *a, VALUE _) +{ + return rb_f_untrace_var(c, a); +} + 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_gvar_ractor_local("$@"); + rb_gvar_ractor_local("$!"); + + rb_define_global_function("raise", f_raise, -1); + rb_define_global_function("fail", f_raise, -1); - rb_define_global_function("global_variables", rb_f_global_variables, 0); /* in variable.c */ + rb_define_global_function("global_variables", f_global_variables, 0); rb_define_global_function("__method__", rb_f_method_name, 0); rb_define_global_function("__callee__", rb_f_callee_name, 0); @@ -1717,7 +1950,10 @@ Init_eval(void) 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_define_singleton_method(rb_cModule, "used_modules", + rb_mod_s_used_modules, 0); rb_undef_method(rb_cClass, "refine"); + rb_define_private_method(rb_cRefinement, "import_methods", refinement_import_methods, -1); rb_undef_method(rb_cClass, "module_function"); @@ -1734,10 +1970,12 @@ Init_eval(void) 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_define_global_function("trace_var", f_trace_var, -1); + rb_define_global_function("untrace_var", f_untrace_var, -1); rb_vm_register_special_exception(ruby_error_reenter, rb_eFatal, "exception reentered"); + rb_vm_register_special_exception(ruby_error_stackfatal, rb_eFatal, "machine stack overflow in critical region"); - id_cause = rb_intern_const("cause"); + id_signo = rb_intern_const("signo"); + id_status = rb_intern_const("status"); } |
