/********************************************************************** eval.c - $Author$ 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 "eval_intern.h" #include "iseq.h" #include "gc.h" #include "ruby/vm.h" #include "ruby/encoding.h" #define numberof(array) (int)(sizeof(array) / sizeof((array)[0])) VALUE proc_invoke(VALUE, VALUE, VALUE, VALUE); VALUE rb_binding_new(void); NORETURN(void rb_raise_jump(VALUE)); ID rb_frame_callee(void); VALUE rb_eLocalJumpError; VALUE rb_eSysStackError; #define exception_error GET_VM()->special_exceptions[ruby_error_reenter] #include "eval_error.c" #include "eval_jump.c" /* initialize ruby */ void rb_clear_trace_func(void); void rb_thread_stop_timer_thread(void); void rb_call_inits(void); void Init_heap(void); void Init_BareVM(void); void ruby_init(void) { static int initialized = 0; int state; if (initialized) return; initialized = 1; ruby_init_stack((void *)&state); Init_BareVM(); Init_heap(); PUSH_TAG(); if ((state = EXEC_TAG()) == 0) { rb_call_inits(); ruby_prog_init(); } POP_TAG(); if (state) { error_print(); exit(EXIT_FAILURE); } GET_VM()->running = 1; } extern void rb_clear_trace_func(void); 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(); } void ruby_finalize(void) { ruby_finalize_0(); ruby_finalize_1(); } void rb_thread_stop_timer_thread(void); int ruby_cleanup(volatile int ex) { int state; volatile VALUE errs[2]; rb_thread_t *th = GET_THREAD(); int nerr; void rb_threadptr_interrupt(rb_thread_t *th); void rb_threadptr_check_signal(rb_thread_t *mth); rb_threadptr_interrupt(th); rb_threadptr_check_signal(th); RUBY_VM_CHECK_INTS(); 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(); 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); ruby_finalize_1(); POP_TAG(); rb_thread_stop_timer_thread(); 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 (TYPE(err) == T_NODE) continue; if (rb_obj_is_kind_of(err, rb_eSystemExit)) { return sysexit_status(err); } else if (rb_obj_is_kind_of(err, rb_eSignal)) { VALUE sig = rb_iv_get(err, "signo"); state = NUM2INT(sig); break; } else if (ex == 0) { ex = 1; } } ruby_vm_destruct(GET_VM()); if (state) ruby_default_signal(state); #if EXIT_SUCCESS != 0 || EXIT_FAILURE != 1 switch (ex) { #if EXIT_SUCCESS != 0 case 0: return EXIT_SUCCESS; #endif #if EXIT_FAILURE != 1 case 1: return EXIT_FAILURE; #endif } #endif 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; } void ruby_stop(int ex) { exit(ruby_cleanup(ex)); } 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; } 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)); } 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 * * Returns an array of the names of all constants defined in the * system. This list includes the names of all modules and classes. * * p Module.constants.sort[1..5] * * produces: * * ["ARGV", "ArgumentError", "Array", "Bignum", "Binding"] */ 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) { return rb_mod_constants(argc, argv, rb_cModule); } while (cref) { klass = cref->nd_clss; if (!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) { const char *desc = "something(?!)"; if (OBJ_FROZEN(klass)) { if (FL_TEST(klass, FL_SINGLETON)) desc = "object"; else { switch (TYPE(klass)) { case T_MODULE: case T_ICLASS: desc = "module"; break; case T_CLASS: desc = "class"; break; } } rb_error_frozen(desc); } } NORETURN(static void rb_longjmp(int, volatile VALUE)); static void setup_exception(rb_thread_t *th, int tag, volatile VALUE mesg) { VALUE at; VALUE e; const char *file; volatile int line = 0; if (NIL_P(mesg)) mesg = th->errinfo; if (NIL_P(mesg)) { mesg = rb_exc_new(rb_eRuntimeError, 0, 0); } file = rb_sourcefile(); if (file) line = rb_sourceline(); if (file && !NIL_P(mesg)) { if (mesg == sysstack_error) { at = rb_enc_sprintf(rb_usascii_encoding(), "%s:%d", file, line); rb_iv_set(mesg, "bt", at); } else { at = get_backtrace(mesg); if (NIL_P(at)) { at = rb_make_backtrace(); if (OBJ_FROZEN(mesg)) { mesg = rb_obj_dup(mesg); } 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; PUSH_TAG(); if ((status = EXEC_TAG()) == 0) { RB_GC_GUARD(e) = rb_obj_as_string(e); if (file && line) { warn_printf("Exception `%s' at %s:%d - %s\n", rb_obj_classname(th->errinfo), file, line, RSTRING_PTR(e)); } else if (file) { warn_printf("Exception `%s' at %s - %s\n", rb_obj_classname(th->errinfo), file, RSTRING_PTR(e)); } else { warn_printf("Exception `%s' - %s\n", rb_obj_classname(th->errinfo), RSTRING_PTR(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); } rb_trap_restore_mask(); if (tag != TAG_FATAL) { EXEC_EVENT_HOOK(th, RUBY_EVENT_RAISE, th->cfp->self, 0, 0); } } static void rb_longjmp(int tag, volatile VALUE mesg) { rb_thread_t *th = GET_THREAD(); setup_exception(th, tag, mesg); rb_thread_raised_clear(th); JUMP_TAG(tag); } static VALUE make_exception(int argc, 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); } void rb_exc_fatal(VALUE mesg) { if (!NIL_P(mesg)) { mesg = make_exception(1, &mesg, FALSE); } rb_longjmp(TAG_FATAL, mesg); } void rb_interrupt(void) { rb_raise(rb_eInterrupt, "%s", ""); } static VALUE get_errinfo(void); /* * call-seq: * raise * raise(string) * raise(exception [, string [, array]]) * fail * fail(string) * fail(exception [, string [, array]]) * * With no arguments, raises the exception in $! or raises * a RuntimeError if $! 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 begin...end blocks. * * raise "Failed to create socket" * raise ArgumentError, "No parameters", caller */ static VALUE rb_f_raise(int argc, VALUE *argv) { VALUE err; if (argc == 0) { err = get_errinfo(); if (!NIL_P(err)) { argc = 1; argv = &err; } } rb_raise_jump(rb_make_exception(argc, argv)); return Qnil; /* not reached */ } static VALUE make_exception(int argc, VALUE *argv, int isstr) { VALUE mesg; ID exception; int n; mesg = Qnil; switch (argc) { case 0: break; case 1: if (NIL_P(argv[0])) break; if (isstr) { mesg = rb_check_string_type(argv[0]); if (!NIL_P(mesg)) { mesg = rb_exc_new3(rb_eRuntimeError, mesg); break; } } n = 0; goto exception_call; case 2: case 3: n = 1; exception_call: if (argv[0] == sysstack_error) return argv[0]; CONST_ID(exception, "exception"); mesg = rb_check_funcall(argv[0], exception, n, argv+1); if (mesg == Qundef) { rb_raise(rb_eTypeError, "exception class/object expected"); } break; default: rb_raise(rb_eArgError, "wrong number of arguments"); 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, VALUE *argv) { return make_exception(argc, argv, TRUE); } void rb_raise_jump(VALUE mesg) { 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); setup_exception(th, TAG_RAISE, mesg); EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, self, mid, klass); 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 ((th->cfp->lfp[0] & 0x02) == 0 && GC_GUARDED_PTR_REF(th->cfp->lfp[0])) { 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; volatile VALUE e_info = th->errinfo; va_list args; PUSH_TAG(); if ((state = EXEC_TAG()) == 0) { retry_entry: result = (*b_proc) (data1); } else { th->cfp = cfp; /* restore */ 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) { if (r_proc) { PUSH_TAG(); if ((state = EXEC_TAG()) == 0) { result = (*r_proc) (data2, th->errinfo); } POP_TAG(); if (state == TAG_RETRY) { state = 0; th->errinfo = Qnil; goto retry_entry; } } else { result = Qnil; state = 0; } if (state == 0) { th->errinfo = e_info; } } } } 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; 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; PUSH_TAG(); th->protect_tag = &protect_tag; MEMCPY(&org_jmpbuf, &(th)->root_jmpbuf, rb_jmpbuf_t, 1); if ((status = EXEC_TAG()) == 0) { SAVE_ROOT_JMPBUF(th, result = (*proc) (data)); } MEMCPY(&(th)->root_jmpbuf, &org_jmpbuf, rb_jmpbuf_t, 1); th->protect_tag = protect_tag.prev; POP_TAG(); if (state) { *state = status; } if (status != 0) { th->cfp = cfp; return Qnil; } return result; } VALUE rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1, VALUE (*e_proc)(ANYARGS), VALUE data2) { int state; volatile VALUE result = Qnil; PUSH_TAG(); if ((state = EXEC_TAG()) == 0) { result = (*b_proc) (data1); } POP_TAG(); /* TODO: fix me */ /* retval = prot_tag ? prot_tag->retval : Qnil; */ /* save retval */ (*e_proc) (data2); if (state) JUMP_TAG(state); return result; } static ID frame_func_id(rb_control_frame_t *cfp) { rb_iseq_t *iseq = cfp->iseq; if (!iseq) { return cfp->me->def->original_id; } while (iseq) { if (RUBY_VM_IFUNC_P(iseq)) { return rb_intern(""); } 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_func_id(GET_THREAD()->cfp); } static ID rb_frame_caller(void) { rb_thread_t *th = GET_THREAD(); 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 frame_func_id(prev_cfp); } void rb_frame_pop(void) { rb_thread_t *th = GET_THREAD(); th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp); } /* * call-seq: * append_features(mod) => mod * * When this module is included in another, Ruby calls * 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 rb_mod_append_features(VALUE module, VALUE include) { switch (TYPE(include)) { case T_CLASS: case T_MODULE: break; default: Check_Type(include, T_CLASS); break; } rb_include_module(include, module); return module; } /* * call-seq: * include(module, ...) => self * * Invokes Module.append_features on each parameter in reverse order. */ static VALUE rb_mod_include(int argc, VALUE *argv, VALUE module) { int i; for (i = 0; i < argc; i++) Check_Type(argv[i], T_MODULE); while (argc--) { rb_funcall(argv[argc], rb_intern("append_features"), 1, module); rb_funcall(argv[argc], rb_intern("included"), 1, module); } return module; } void rb_obj_call_init(VALUE obj, int argc, 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 Object#extend. * * 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 * * produces: * * 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) #=> # * k.hello #=> "Hello from Mod.\n" */ static VALUE rb_obj_extend(int argc, VALUE *argv, VALUE obj) { int i; if (argc == 0) { rb_raise(rb_eArgError, "wrong number of arguments (0 for 1)"); } for (i = 0; i < argc; i++) Check_Type(argv[i], T_MODULE); while (argc--) { rb_funcall(argv[argc], rb_intern("extend_object"), 1, obj); rb_funcall(argv[argc], rb_intern("extended"), 1, obj); } return obj; } /* * call-seq: * include(module, ...) => self * * Invokes Module.append_features * 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(); rb_secure(4); 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); } VALUE rb_f_trace_var(); VALUE rb_f_untrace_var(); 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->dfp[-2]; } else if (cfp->iseq->type == ISEQ_TYPE_ENSURE && TYPE(cfp->dfp[-2]) != T_NODE && !FIXNUM_P(cfp->dfp[-2])) { return &cfp->dfp[-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 * __callee__ => symbol * * Returns the name of the current method as a Symbol. * If called outside of a method, it returns nil. * */ static VALUE rb_f_method_name(void) { ID fname = rb_frame_caller(); /* need *caller* ID */ if (fname) { return ID2SYM(fname); } else { return Qnil; } } 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_method_name, 0); 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, "include", rb_mod_include, -1); rb_undef_method(rb_cClass, "module_function"); { extern void Init_vm_eval(void); extern void Init_eval_method(void); 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_singleton_method(rb_vm_top_self(), "include", top_include, -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 */ exception_error = rb_exc_new3(rb_eFatal, rb_obj_freeze(rb_str_new2("exception reentered"))); OBJ_TAINT(exception_error); OBJ_FREEZE(exception_error); }