/**********************************************************************
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"
VALUE proc_invoke(VALUE, VALUE, VALUE, VALUE);
VALUE rb_binding_new();
VALUE rb_f_block_given_p(void);
ID rb_frame_callee(void);
static VALUE rb_frame_self(void);
static ID removed, singleton_removed, undefined, singleton_undefined;
static ID init, eqq, each, aref, aset, match, missing;
static ID added, singleton_added;
static ID object_id, __send__, respond_to;
VALUE rb_eLocalJumpError;
VALUE rb_eSysStackError;
VALUE sysstack_error;
static VALUE exception_error;
static VALUE eval(VALUE, VALUE, VALUE, const char *, int);
static inline VALUE rb_yield_0(int argc, VALUE *argv);
static VALUE rb_call(VALUE, VALUE, ID, int, const VALUE *, int);
#include "eval_error.c"
#include "eval_method.c"
#include "eval_safe.c"
#include "eval_jump.c"
/* initialize ruby */
#if defined(__APPLE__)
#define environ (*_NSGetEnviron())
#elif !defined(_WIN32) && !defined(__MACOS__) || defined(_WIN32_WCE)
extern char **environ;
#endif
char **rb_origenviron;
void rb_clear_trace_func(void);
void rb_thread_stop_timer_thread(void);
void rb_call_inits(void);
void Init_stack(VALUE *);
void Init_heap(void);
void Init_ext(void);
void Init_BareVM(void);
void
ruby_init(void)
{
static int initialized = 0;
int state;
if (initialized)
return;
initialized = 1;
#ifdef __MACOS__
rb_origenviron = 0;
#else
rb_origenviron = environ;
#endif
Init_stack((void *)&state);
Init_BareVM();
Init_heap();
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
rb_call_inits();
#ifdef __MACOS__
_macruby_init();
#elif defined(__VMS)
_vmsruby_init();
#endif
ruby_prog_init();
ALLOW_INTS;
}
POP_TAG();
if (state) {
error_print();
exit(EXIT_FAILURE);
}
ruby_running = 1;
}
extern void rb_clear_trace_func(void);
void *
ruby_options(int argc, char **argv)
{
int state;
void *tree = 0;
Init_stack((void *)&state);
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
SAVE_ROOT_JMPBUF(GET_THREAD(), tree = ruby_process_options(argc, argv));
}
else {
rb_clear_trace_func();
state = error_handle(state);
tree = (void *)INT2FIX(state);
}
POP_TAG();
return tree;
}
static void
ruby_finalize_0(void)
{
rb_clear_trace_func();
PUSH_TAG();
if (EXEC_TAG() == 0) {
rb_trap_exit();
}
POP_TAG();
rb_exec_end_proc();
}
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(int ex)
{
int state;
volatile VALUE errs[2];
rb_thread_t *th = GET_THREAD();
int nerr;
errs[1] = th->errinfo;
th->safe_level = 0;
Init_stack((void *)&state);
ruby_finalize_0();
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();
for (nerr = 0; nerr < sizeof(errs) / sizeof(errs[0]); ++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");
ruby_default_signal(NUM2INT(sig));
}
else if (ex == 0) {
ex = 1;
}
}
#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;
}
int
ruby_exec_node(void *n, const char *file)
{
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(iseq);
});
}
POP_TAG();
return state;
}
void
ruby_stop(int ex)
{
exit(ruby_cleanup(ex));
}
int
ruby_run_node(void *n)
{
VALUE v = (VALUE)n;
switch (v) {
case Qtrue: return EXIT_SUCCESS;
case Qfalse: return EXIT_FAILURE;
}
if (FIXNUM_P(v)) {
return FIX2INT(v);
}
Init_stack((void *)&n);
return ruby_cleanup(ruby_exec_node(n, 0));
}
VALUE
rb_eval_string(const char *str)
{
return eval(rb_vm_top_self(), rb_str_new2(str), Qnil, "(eval)", 1);
}
VALUE
rb_eval_string_protect(const char *str, int *state)
{
return rb_protect((VALUE (*)(VALUE))rb_eval_string, (VALUE)str, state);
}
VALUE
rb_eval_string_wrap(const char *str, int *state)
{
int status;
rb_thread_t *th = GET_THREAD();
VALUE self = th->top_self;
VALUE wrapper = th->top_wrapper;
VALUE val;
th->top_wrapper = rb_module_new();
th->top_self = rb_obj_clone(rb_vm_top_self());
rb_extend_object(th->top_self, th->top_wrapper);
val = rb_eval_string_protect(str, &status);
th->top_self = self;
th->top_wrapper = wrapper;
if (state) {
*state = status;
}
else if (status) {
JUMP_TAG(status);
}
return val;
}
VALUE
rb_eval_cmd(VALUE cmd, VALUE arg, int level)
{
int state;
VALUE val = Qnil; /* OK */
volatile int safe = rb_safe_level();
if (OBJ_TAINTED(cmd)) {
level = 4;
}
if (TYPE(cmd) != T_STRING) {
PUSH_TAG();
rb_set_safe_level_force(level);
if ((state = EXEC_TAG()) == 0) {
val = rb_funcall2(cmd, rb_intern("call"), RARRAY_LEN(arg),
RARRAY_PTR(arg));
}
POP_TAG();
rb_set_safe_level_force(safe);
if (state)
JUMP_TAG(state);
return val;
}
PUSH_TAG();
if ((state = EXEC_TAG()) == 0) {
val = eval(rb_vm_top_self(), cmd, Qnil, 0, 0);
}
POP_TAG();
rb_set_safe_level_force(safe);
if (state) vm_jump_tag_but_local_jump(state, val);
return val;
}
/*
* 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();
NODE *cref = ruby_cref();
while (cref && cref->nd_next) {
VALUE klass = cref->nd_clss;
if (!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]
*
* <em>produces:</em>
*
* ["ARGV", "ArgumentError", "Array", "Bignum", "Binding"]
*/
static VALUE
rb_mod_s_constants(int argc, VALUE *argv, VALUE mod)
{
NODE *cref = ruby_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)
{
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);
}
}
/*
* call-seq:
* obj.respond_to?(symbol, include_private=false) => true or false
*
* Returns +true+> if _obj_ responds to the given
* method. Private methods are included in the search only if the
* optional second parameter evaluates to +true+.
*/
static NODE *basic_respond_to = 0;
int
rb_obj_respond_to(VALUE obj, ID id, int priv)
{
VALUE klass = CLASS_OF(obj);
if (rb_method_node(klass, respond_to) == basic_respond_to) {
return rb_method_boundp(klass, id, !priv);
}
else {
VALUE args[2];
int n = 0;
args[n++] = ID2SYM(id);
if (priv)
args[n++] = Qtrue;
return rb_funcall2(obj, respond_to, n, args);
}
}
int
rb_respond_to(VALUE obj, ID id)
{
return rb_obj_respond_to(obj, id, Qfalse);
}
/*
* call-seq:
* obj.respond_to?(symbol, include_private=false) => true or false
*
* Returns +true+> if _obj_ responds to the given
* method. Private methods are included in the search only if the
* optional second parameter evaluates to +true+.
*/
static VALUE
obj_respond_to(int argc, VALUE *argv, VALUE obj)
{
VALUE mid, priv;
ID id;
rb_scan_args(argc, argv, "11", &mid, &priv);
id = rb_to_id(mid);
if (rb_method_boundp(CLASS_OF(obj), id, !RTEST(priv))) {
return Qtrue;
}
return Qfalse;
}
/*
* call-seq:
* mod.method_defined?(symbol) => true or false
*
* Returns +true+ if the named method is defined by
* _mod_ (or its included modules and, if _mod_ is a class,
* its ancestors). Public and protected methods are matched.
*
* module A
* def method1() end
* end
* class B
* def method2() end
* end
* class C < B
* include A
* def method3() end
* end
*
* A.method_defined? :method1 #=> true
* C.method_defined? "method1" #=> true
* C.method_defined? "method2" #=> true
* C.method_defined? "method3" #=> true
* C.method_defined? "method4" #=> false
*/
static VALUE
rb_mod_method_defined(VALUE mod, VALUE mid)
{
return rb_method_boundp(mod, rb_to_id(mid), 1);
}
#define VISI_CHECK(x,f) (((x)&NOEX_MASK) == (f))
/*
* call-seq:
* mod.public_method_defined?(symbol) => true or false
*
* Returns +true+ if the named public method is defined by
* _mod_ (or its included modules and, if _mod_ is a class,
* its ancestors).
*
* module A
* def method1() end
* end
* class B
* protected
* def method2() end
* end
* class C < B
* include A
* def method3() end
* end
*
* A.method_defined? :method1 #=> true
* C.public_method_defined? "method1" #=> true
* C.public_method_defined? "method2" #=> false
* C.method_defined? "method2" #=> true
*/
static VALUE
rb_mod_public_method_defined(VALUE mod, VALUE mid)
{
ID id = rb_to_id(mid);
NODE *method;
method = rb_method_node(mod, id);
if (method) {
if (VISI_CHECK(method->nd_noex, NOEX_PUBLIC))
return Qtrue;
}
return Qfalse;
}
/*
* call-seq:
* mod.private_method_defined?(symbol) => true or false
*
* Returns +true+ if the named private method is defined by
* _ mod_ (or its included modules and, if _mod_ is a class,
* its ancestors).
*
* module A
* def method1() end
* end
* class B
* private
* def method2() end
* end
* class C < B
* include A
* def method3() end
* end
*
* A.method_defined? :method1 #=> true
* C.private_method_defined? "method1" #=> false
* C.private_method_defined? "method2" #=> true
* C.method_defined? "method2" #=> false
*/
static VALUE
rb_mod_private_method_defined(VALUE mod, VALUE mid)
{
ID id = rb_to_id(mid);
NODE *method;
method = rb_method_node(mod, id);
if (method) {
if (VISI_CHECK(method->nd_noex, NOEX_PRIVATE))
return Qtrue;
}
return Qfalse;
}
/*
* call-seq:
* mod.protected_method_defined?(symbol) => true or false
*
* Returns +true+ if the named protected method is defined
* by _mod_ (or its included modules and, if _mod_ is a
* class, its ancestors).
*
* module A
* def method1() end
* end
* class B
* protected
* def method2() end
* end
* class C < B
* include A
* def method3() end
* end
*
* A.method_defined? :method1 #=> true
* C.protected_method_defined? "method1" #=> false
* C.protected_method_defined? "method2" #=> true
* C.method_defined? "method2" #=> true
*/
static VALUE
rb_mod_protected_method_defined(VALUE mod, VALUE mid)
{
ID id = rb_to_id(mid);
NODE *method;
method = rb_method_node(mod, id);
if (method) {
if (VISI_CHECK(method->nd_noex, NOEX_PROTECTED))
return Qtrue;
}
return Qfalse;
}
NORETURN(static void rb_longjmp(int, VALUE));
static VALUE make_backtrace(void);
static void
rb_longjmp(int tag, VALUE mesg)
{
VALUE at;
VALUE e;
rb_thread_t *th = GET_THREAD();
const char *file;
int line = 0;
if (rb_thread_set_raised(th)) {
th->errinfo = exception_error;
JUMP_TAG(TAG_FATAL);
}
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)) {
at = get_backtrace(mesg);
if (NIL_P(at)) {
at = make_backtrace();
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) {
warn_printf("Exception `%s' at %s:%d - %s\n",
rb_obj_classname(th->errinfo),
file, line, 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_thread_reset_raised(th);
JUMP_TAG(status);
}
}
rb_trap_restore_mask();
if (tag != TAG_FATAL) {
EXEC_EVENT_HOOK(th, RUBY_EVENT_RAISE, th->cfp->self,
0 /* TODO: id */, 0 /* TODO: klass */);
}
rb_thread_raised_clear(th);
JUMP_TAG(tag);
}
void
rb_exc_raise(VALUE mesg)
{
rb_longjmp(TAG_RAISE, mesg);
}
void
rb_exc_fatal(VALUE mesg)
{
rb_longjmp(TAG_FATAL, mesg);
}
void
rb_interrupt(void)
{
static const char fmt[1] = {'\0'};
rb_raise(rb_eInterrupt, fmt);
}
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 <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;
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 */
}
VALUE
rb_make_exception(int argc, VALUE *argv)
{
VALUE mesg;
ID exception;
int n;
mesg = Qnil;
switch (argc) {
case 0:
mesg = Qnil;
break;
case 1:
if (NIL_P(argv[0]))
break;
if (TYPE(argv[0]) == T_STRING) {
mesg = rb_exc_new3(rb_eRuntimeError, argv[0]);
break;
}
n = 0;
goto exception_call;
case 2:
case 3:
n = 1;
exception_call:
exception = rb_intern("exception");
if (!rb_respond_to(argv[0], exception)) {
rb_raise(rb_eTypeError, "exception class/object expected");
}
mesg = rb_funcall(argv[0], exception, n, argv[1]);
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;
}
void
rb_raise_jump(VALUE mesg)
{
rb_thread_t *th = GET_THREAD();
th->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(th->cfp);
/* TODO: fix me */
rb_longjmp(TAG_RAISE, mesg);
}
void
rb_jump_tag(int tag)
{
JUMP_TAG(tag);
}
int
rb_block_given_p(void)
{
rb_thread_t *th = GET_THREAD();
if (GC_GUARDED_PTR_REF(th->cfp->lfp[0])) {
return Qtrue;
}
else {
return Qfalse;
}
}
int
rb_iterator_p()
{
return rb_block_given_p();
}
/*
* call-seq:
* block_given? => true or false
* iterator? => true or false
*
* Returns <code>true</code> if <code>yield</code> would execute a
* block in the current context. The <code>iterator?</code> form
* is mildly deprecated.
*
* def try
* if block_given?
* yield
* else
* "no block"
* end
* end
* try #=> "no block"
* try { "hello" } #=> "hello"
* try do "hello" end #=> "hello"
*/
VALUE
rb_f_block_given_p()
{
rb_thread_t *th = GET_THREAD();
rb_control_frame_t *cfp = th->cfp;
cfp = vm_get_ruby_level_cfp(th, RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp));
if (GC_GUARDED_PTR_REF(cfp->lfp[0])) {
return Qtrue;
}
else {
return Qfalse;
}
}
VALUE rb_eThreadError;
void
rb_need_block()
{
if (!rb_block_given_p()) {
vm_localjump_error("no block given", Qnil, 0);
}
}
static inline VALUE
rb_yield_0(int argc, VALUE *argv)
{
return vm_yield(GET_THREAD(), argc, argv);
}
VALUE
rb_yield(VALUE val)
{
volatile VALUE tmp = val;
if (val == Qundef) {
tmp = rb_yield_0(0, 0);
}
else {
tmp = rb_yield_0(1, &val);
}
return tmp;
}
VALUE
rb_yield_values(int n, ...)
{
int i;
VALUE *argv;
va_list args;
if (n == 0) {
|