aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorko1 <ko1@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2012-08-15 04:39:10 +0000
committerko1 <ko1@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2012-08-15 04:39:10 +0000
commit8eb93103e4651aa9d6ed147208f6d90b211e9bfa (patch)
tree7c7b466abd35440994a94979182a19ad01a44fcc
parentd2e9b54564fedd9ee4fff18c2a7f1df2d6dcdd93 (diff)
downloadruby-8eb93103e4651aa9d6ed147208f6d90b211e9bfa.tar.gz
* vm_trace.c: separate trace_func related functions from
thread.c. * thread.c: ditto. * common.mk: add vm_trace.o. * inits.c: call Init_vm_trace(). git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@36703 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog11
-rw-r--r--common.mk4
-rw-r--r--inits.c1
-rw-r--r--thread.c555
-rw-r--r--vm_trace.c596
5 files changed, 612 insertions, 555 deletions
diff --git a/ChangeLog b/ChangeLog
index fc8ae2c2a9..546a93a218 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+Wed Aug 15 11:39:53 2012 Koichi Sasada <ko1@atdot.net>
+
+ * vm_trace.c: separate trace_func related functions from
+ thread.c.
+
+ * thread.c: ditto.
+
+ * common.mk: add vm_trace.o.
+
+ * inits.c: call Init_vm_trace().
+
Tue Aug 14 16:25:46 2012 Shugo Maeda <shugo@ruby-lang.org>
* test/erb/test_erb.rb (test_html_escape): add assertions for the
diff --git a/common.mk b/common.mk
index 9a72a68eec..e6c0f424f9 100644
--- a/common.mk
+++ b/common.mk
@@ -92,6 +92,7 @@ COMMONOBJS = array.$(OBJEXT) \
vm.$(OBJEXT) \
vm_dump.$(OBJEXT) \
vm_backtrace.$(OBJEXT) \
+ vm_trace.$(OBJEXT) \
thread.$(OBJEXT) \
cont.$(OBJEXT) \
$(BUILTIN_ENCOBJS) \
@@ -781,6 +782,9 @@ id.$(OBJEXT): {$(VPATH)}id.c $(RUBY_H_INCLUDES) $(ID_H_INCLUDES)
vm_backtrace.$(OBJEXT): {$(VPATH)}vm_backtrace.c \
$(VM_CORE_H_INCLUDES) $(RUBY_H_INCLUDES) \
{$(VPATH)}internal.h {$(VPATH)}iseq.h
+vm_trace.$(OBJEXT): {$(VPATH)}vm_trace.c \
+ $(VM_CORE_H_INCLUDES) $(RUBY_H_INCLUDES) \
+ {$(VPATH)}internal.h
miniprelude.$(OBJEXT): {$(VPATH)}miniprelude.c $(RUBY_H_INCLUDES) \
$(VM_CORE_H_INCLUDES) {$(VPATH)}debug.h {$(VPATH)}internal.h
prelude.$(OBJEXT): {$(VPATH)}prelude.c $(RUBY_H_INCLUDES) \
diff --git a/inits.c b/inits.c
index 73b9eb4283..fe0aade090 100644
--- a/inits.c
+++ b/inits.c
@@ -60,5 +60,6 @@ rb_call_inits(void)
CALL(Rational);
CALL(Complex);
CALL(version);
+ CALL(vm_trace);
}
#undef CALL
diff --git a/thread.c b/thread.c
index d68fead92e..c807913864 100644
--- a/thread.c
+++ b/thread.c
@@ -4442,556 +4442,6 @@ rb_exec_recursive_outer(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg)
return exec_recursive(func, obj, 0, arg, 1);
}
-/* tracer */
-#define RUBY_EVENT_REMOVED 0x1000000
-
-enum {
- EVENT_RUNNING_NOTHING,
- EVENT_RUNNING_TRACE = 1,
- EVENT_RUNNING_THREAD = 2,
- EVENT_RUNNING_VM = 4,
- EVENT_RUNNING_EVENT_MASK = EVENT_RUNNING_VM|EVENT_RUNNING_THREAD
-};
-
-static VALUE thread_suppress_tracing(rb_thread_t *th, int ev, VALUE (*func)(VALUE, int), VALUE arg, int always);
-
-struct event_call_args {
- rb_thread_t *th;
- VALUE klass;
- VALUE self;
- VALUE proc;
- ID id;
- rb_event_flag_t event;
-};
-
-static rb_event_hook_t *
-alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
-{
- rb_event_hook_t *hook = ALLOC(rb_event_hook_t);
- hook->func = func;
- hook->flag = events;
- hook->data = data;
- return hook;
-}
-
-static void
-thread_reset_event_flags(rb_thread_t *th)
-{
- rb_event_hook_t *hook = th->event_hooks;
- rb_event_flag_t flag = th->event_flags & RUBY_EVENT_VM;
-
- while (hook) {
- if (!(flag & RUBY_EVENT_REMOVED))
- flag |= hook->flag;
- hook = hook->next;
- }
- th->event_flags = flag;
-}
-
-static void
-rb_threadptr_add_event_hook(rb_thread_t *th,
- rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
-{
- rb_event_hook_t *hook = alloc_event_hook(func, events, data);
- hook->next = th->event_hooks;
- th->event_hooks = hook;
- thread_reset_event_flags(th);
-}
-
-static rb_thread_t *
-thval2thread_t(VALUE thval)
-{
- rb_thread_t *th;
- GetThreadPtr(thval, th);
- return th;
-}
-
-void
-rb_thread_add_event_hook(VALUE thval,
- rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
-{
- rb_threadptr_add_event_hook(thval2thread_t(thval), func, events, data);
-}
-
-static int
-set_threads_event_flags_i(st_data_t key, st_data_t val, st_data_t flag)
-{
- VALUE thval = key;
- rb_thread_t *th;
- GetThreadPtr(thval, th);
-
- if (flag) {
- th->event_flags |= RUBY_EVENT_VM;
- }
- else {
- th->event_flags &= (~RUBY_EVENT_VM);
- }
- return ST_CONTINUE;
-}
-
-static void
-set_threads_event_flags(int flag)
-{
- st_foreach(GET_VM()->living_threads, set_threads_event_flags_i, (st_data_t) flag);
-}
-
-static inline int
-exec_event_hooks(const rb_event_hook_t *hook, rb_event_flag_t flag, VALUE self, ID id, VALUE klass)
-{
- int removed = 0;
- for (; hook; hook = hook->next) {
- if (hook->flag & RUBY_EVENT_REMOVED) {
- removed++;
- continue;
- }
- if (flag & hook->flag) {
- (*hook->func)(flag, hook->data, self, id, klass);
- }
- }
- return removed;
-}
-
-static int remove_defered_event_hook(rb_event_hook_t **root);
-
-static VALUE
-thread_exec_event_hooks(VALUE args, int running)
-{
- struct event_call_args *argp = (struct event_call_args *)args;
- rb_thread_t *th = argp->th;
- rb_event_flag_t flag = argp->event;
- VALUE self = argp->self;
- ID id = argp->id;
- VALUE klass = argp->klass;
- const rb_event_flag_t wait_event = th->event_flags;
- int removed;
-
- if (self == rb_mRubyVMFrozenCore) return 0;
-
- if ((wait_event & flag) && !(running & EVENT_RUNNING_THREAD)) {
- th->tracing |= EVENT_RUNNING_THREAD;
- removed = exec_event_hooks(th->event_hooks, flag, self, id, klass);
- th->tracing &= ~EVENT_RUNNING_THREAD;
- if (removed) {
- remove_defered_event_hook(&th->event_hooks);
- }
- }
- if (wait_event & RUBY_EVENT_VM) {
- if (th->vm->event_hooks == NULL) {
- th->event_flags &= (~RUBY_EVENT_VM);
- }
- else if (!(running & EVENT_RUNNING_VM)) {
- th->tracing |= EVENT_RUNNING_VM;
- removed = exec_event_hooks(th->vm->event_hooks, flag, self, id, klass);
- th->tracing &= ~EVENT_RUNNING_VM;
- if (removed) {
- remove_defered_event_hook(&th->vm->event_hooks);
- }
- }
- }
- return 0;
-}
-
-void
-rb_threadptr_exec_event_hooks(rb_thread_t *th, rb_event_flag_t flag, VALUE self, ID id, VALUE klass)
-{
- const VALUE errinfo = th->errinfo;
- struct event_call_args args;
- args.th = th;
- args.event = flag;
- args.self = self;
- args.id = id;
- args.klass = klass;
- args.proc = 0;
- thread_suppress_tracing(th, EVENT_RUNNING_EVENT_MASK, thread_exec_event_hooks, (VALUE)&args, FALSE);
- th->errinfo = errinfo;
-}
-
-void
-rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
-{
- rb_event_hook_t *hook = alloc_event_hook(func, events, data);
- rb_vm_t *vm = GET_VM();
-
- hook->next = vm->event_hooks;
- vm->event_hooks = hook;
-
- set_threads_event_flags(1);
-}
-
-static int
-defer_remove_event_hook(rb_event_hook_t *hook, rb_event_hook_func_t func)
-{
- while (hook) {
- if (func == 0 || hook->func == func) {
- hook->flag |= RUBY_EVENT_REMOVED;
- }
- hook = hook->next;
- }
- return -1;
-}
-
-static int
-remove_event_hook(rb_event_hook_t **root, rb_event_hook_func_t func)
-{
- rb_event_hook_t *hook = *root, *next;
-
- while (hook) {
- next = hook->next;
- if (func == 0 || hook->func == func || (hook->flag & RUBY_EVENT_REMOVED)) {
- *root = next;
- xfree(hook);
- }
- else {
- root = &hook->next;
- }
- hook = next;
- }
- return -1;
-}
-
-static int
-remove_defered_event_hook(rb_event_hook_t **root)
-{
- rb_event_hook_t *hook = *root, *next;
-
- while (hook) {
- next = hook->next;
- if (hook->flag & RUBY_EVENT_REMOVED) {
- *root = next;
- xfree(hook);
- }
- else {
- root = &hook->next;
- }
- hook = next;
- }
- return -1;
-}
-
-static int
-rb_threadptr_remove_event_hook(rb_thread_t *th, rb_event_hook_func_t func)
-{
- int ret;
- if (th->tracing & EVENT_RUNNING_THREAD) {
- ret = defer_remove_event_hook(th->event_hooks, func);
- }
- else {
- ret = remove_event_hook(&th->event_hooks, func);
- }
- thread_reset_event_flags(th);
- return ret;
-}
-
-int
-rb_thread_remove_event_hook(VALUE thval, rb_event_hook_func_t func)
-{
- return rb_threadptr_remove_event_hook(thval2thread_t(thval), func);
-}
-
-static rb_event_hook_t *
-search_live_hook(rb_event_hook_t *hook)
-{
- while (hook) {
- if (!(hook->flag & RUBY_EVENT_REMOVED))
- return hook;
- hook = hook->next;
- }
- return NULL;
-}
-
-static int
-running_vm_event_hooks(st_data_t key, st_data_t val, st_data_t data)
-{
- rb_thread_t *th = thval2thread_t((VALUE)key);
- if (!(th->tracing & EVENT_RUNNING_VM)) return ST_CONTINUE;
- *(rb_thread_t **)data = th;
- return ST_STOP;
-}
-
-static rb_thread_t *
-vm_event_hooks_running_thread(rb_vm_t *vm)
-{
- rb_thread_t *found = NULL;
- st_foreach(vm->living_threads, running_vm_event_hooks, (st_data_t)&found);
- return found;
-}
-
-int
-rb_remove_event_hook(rb_event_hook_func_t func)
-{
- rb_vm_t *vm = GET_VM();
- rb_event_hook_t *hook = search_live_hook(vm->event_hooks);
- int ret;
-
- if (vm_event_hooks_running_thread(vm)) {
- ret = defer_remove_event_hook(vm->event_hooks, func);
- }
- else {
- ret = remove_event_hook(&vm->event_hooks, func);
- }
-
- if (hook && !search_live_hook(vm->event_hooks)) {
- set_threads_event_flags(0);
- }
-
- return ret;
-}
-
-static int
-clear_trace_func_i(st_data_t key, st_data_t val, st_data_t flag)
-{
- rb_thread_t *th;
- GetThreadPtr((VALUE)key, th);
- rb_threadptr_remove_event_hook(th, 0);
- return ST_CONTINUE;
-}
-
-void
-rb_clear_trace_func(void)
-{
- st_foreach(GET_VM()->living_threads, clear_trace_func_i, (st_data_t) 0);
- rb_remove_event_hook(0);
-}
-
-static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALUE klass);
-
-/*
- * call-seq:
- * set_trace_func(proc) -> proc
- * set_trace_func(nil) -> nil
- *
- * Establishes _proc_ as the handler for tracing, or disables
- * tracing if the parameter is +nil+. _proc_ takes up
- * to six parameters: an event name, a filename, a line number, an
- * object id, a binding, and the name of a class. _proc_ is
- * invoked whenever an event occurs. Events are: <code>c-call</code>
- * (call a C-language routine), <code>c-return</code> (return from a
- * C-language routine), <code>call</code> (call a Ruby method),
- * <code>class</code> (start a class or module definition),
- * <code>end</code> (finish a class or module definition),
- * <code>line</code> (execute code on a new line), <code>raise</code>
- * (raise an exception), and <code>return</code> (return from a Ruby
- * method). Tracing is disabled within the context of _proc_.
- *
- * class Test
- * def test
- * a = 1
- * b = 2
- * end
- * end
- *
- * set_trace_func proc { |event, file, line, id, binding, classname|
- * printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
- * }
- * t = Test.new
- * t.test
- *
- * line prog.rb:11 false
- * c-call prog.rb:11 new Class
- * c-call prog.rb:11 initialize Object
- * c-return prog.rb:11 initialize Object
- * c-return prog.rb:11 new Class
- * line prog.rb:12 false
- * call prog.rb:2 test Test
- * line prog.rb:3 test Test
- * line prog.rb:4 test Test
- * return prog.rb:4 test Test
- */
-
-static VALUE
-set_trace_func(VALUE obj, VALUE trace)
-{
- rb_remove_event_hook(call_trace_func);
-
- if (NIL_P(trace)) {
- GET_THREAD()->tracing = EVENT_RUNNING_NOTHING;
- return Qnil;
- }
-
- if (!rb_obj_is_proc(trace)) {
- rb_raise(rb_eTypeError, "trace_func needs to be Proc");
- }
-
- rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace);
- return trace;
-}
-
-static void
-thread_add_trace_func(rb_thread_t *th, VALUE trace)
-{
- if (!rb_obj_is_proc(trace)) {
- rb_raise(rb_eTypeError, "trace_func needs to be Proc");
- }
-
- rb_threadptr_add_event_hook(th, call_trace_func, RUBY_EVENT_ALL, trace);
-}
-
-/*
- * call-seq:
- * thr.add_trace_func(proc) -> proc
- *
- * Adds _proc_ as a handler for tracing.
- * See <code>Thread#set_trace_func</code> and +set_trace_func+.
- */
-
-static VALUE
-thread_add_trace_func_m(VALUE obj, VALUE trace)
-{
- rb_thread_t *th;
- GetThreadPtr(obj, th);
- thread_add_trace_func(th, trace);
- return trace;
-}
-
-/*
- * call-seq:
- * thr.set_trace_func(proc) -> proc
- * thr.set_trace_func(nil) -> nil
- *
- * Establishes _proc_ on _thr_ as the handler for tracing, or
- * disables tracing if the parameter is +nil+.
- * See +set_trace_func+.
- */
-
-static VALUE
-thread_set_trace_func_m(VALUE obj, VALUE trace)
-{
- rb_thread_t *th;
- GetThreadPtr(obj, th);
- rb_threadptr_remove_event_hook(th, call_trace_func);
-
- if (NIL_P(trace)) {
- th->tracing = EVENT_RUNNING_NOTHING;
- return Qnil;
- }
- thread_add_trace_func(th, trace);
- return trace;
-}
-
-static const char *
-get_event_name(rb_event_flag_t event)
-{
- switch (event) {
- case RUBY_EVENT_LINE:
- return "line";
- case RUBY_EVENT_CLASS:
- return "class";
- case RUBY_EVENT_END:
- return "end";
- case RUBY_EVENT_CALL:
- return "call";
- case RUBY_EVENT_RETURN:
- return "return";
- case RUBY_EVENT_C_CALL:
- return "c-call";
- case RUBY_EVENT_C_RETURN:
- return "c-return";
- case RUBY_EVENT_RAISE:
- return "raise";
- default:
- return "unknown";
- }
-}
-
-static VALUE
-call_trace_proc(VALUE args, int tracing)
-{
- struct event_call_args *p = (struct event_call_args *)args;
- const char *srcfile = rb_sourcefile();
- VALUE eventname = rb_str_new2(get_event_name(p->event));
- VALUE filename = srcfile ? rb_str_new2(srcfile) : Qnil;
- VALUE argv[6];
- int line = rb_sourceline();
- ID id = 0;
- VALUE klass = 0;
-
- if (p->klass != 0) {
- id = p->id;
- klass = p->klass;
- }
- else {
- rb_thread_method_id_and_class(p->th, &id, &klass);
- }
- if (id == ID_ALLOCATOR)
- return Qnil;
- if (klass) {
- if (RB_TYPE_P(klass, T_ICLASS)) {
- klass = RBASIC(klass)->klass;
- }
- else if (FL_TEST(klass, FL_SINGLETON)) {
- klass = rb_iv_get(klass, "__attached__");
- }
- }
-
- argv[0] = eventname;
- argv[1] = filename;
- argv[2] = INT2FIX(line);
- argv[3] = id ? ID2SYM(id) : Qnil;
- argv[4] = (p->self && srcfile) ? rb_binding_new() : Qnil;
- argv[5] = klass ? klass : Qnil;
-
- return rb_proc_call_with_block(p->proc, 6, argv, Qnil);
-}
-
-static void
-call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
-{
- struct event_call_args args;
-
- args.th = GET_THREAD();
- args.event = event;
- args.proc = proc;
- args.self = self;
- args.id = id;
- args.klass = klass;
- ruby_suppress_tracing(call_trace_proc, (VALUE)&args, FALSE);
-}
-
-VALUE
-ruby_suppress_tracing(VALUE (*func)(VALUE, int), VALUE arg, int always)
-{
- rb_thread_t *th = GET_THREAD();
- return thread_suppress_tracing(th, EVENT_RUNNING_TRACE, func, arg, always);
-}
-
-static VALUE
-thread_suppress_tracing(rb_thread_t *th, int ev, VALUE (*func)(VALUE, int), VALUE arg, int always)
-{
- int state, tracing = th->tracing, running = tracing & ev;
- volatile int raised;
- volatile int outer_state;
- VALUE result = Qnil;
-
- if (running == ev && !always) {
- return Qnil;
- }
- else {
- th->tracing |= ev;
- }
-
- raised = rb_threadptr_reset_raised(th);
- outer_state = th->state;
- th->state = 0;
-
- PUSH_TAG();
- if ((state = EXEC_TAG()) == 0) {
- result = (*func)(arg, running);
- }
-
- if (raised) {
- rb_threadptr_set_raised(th);
- }
- POP_TAG();
-
- th->tracing = tracing;
- if (state) {
- JUMP_TAG(state);
- }
- th->state = outer_state;
-
- return result;
-}
-
/*
* call-seq:
* thr.backtrace -> array
@@ -5110,11 +4560,6 @@ Init_Thread(void)
recursive_key = rb_intern("__recursive_key__");
rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError);
- /* trace */
- rb_define_global_function("set_trace_func", set_trace_func, 1);
- rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1);
- rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1);
-
/* init thread core */
{
/* main thread setting */
diff --git a/vm_trace.c b/vm_trace.c
new file mode 100644
index 0000000000..f608e1b2c0
--- /dev/null
+++ b/vm_trace.c
@@ -0,0 +1,596 @@
+/**********************************************************************
+
+ vm_trace.c -
+
+ $Author: ko1 $
+ created at: Tue Aug 14 19:37:09 2012
+
+ Copyright (C) 1993-2012 Yukihiro Matsumoto
+
+**********************************************************************/
+
+/*
+ * This file incldue two parts:
+ *
+ * (1) set_trace_func internal mechanisms
+ * and C level API
+ *
+ * (2) Ruby level API
+ * (2-1) set_trace_func API
+ * (2-2) TracePoint API (not yet)
+ *
+ */
+
+#include "ruby/ruby.h"
+#include "ruby/encoding.h"
+
+#include "internal.h"
+#include "vm_core.h"
+#include "eval_intern.h"
+
+/* (1) trace mechanisms */
+
+#define RUBY_EVENT_REMOVED 0x1000000
+
+enum {
+ EVENT_RUNNING_NOTHING,
+ EVENT_RUNNING_TRACE = 1,
+ EVENT_RUNNING_THREAD = 2,
+ EVENT_RUNNING_VM = 4,
+ EVENT_RUNNING_EVENT_MASK = EVENT_RUNNING_VM|EVENT_RUNNING_THREAD
+};
+
+static VALUE thread_suppress_tracing(rb_thread_t *th, int ev, VALUE (*func)(VALUE, int), VALUE arg, int always);
+
+struct event_call_args {
+ rb_thread_t *th;
+ VALUE klass;
+ VALUE self;
+ VALUE proc;
+ ID id;
+ rb_event_flag_t event;
+};
+
+static rb_event_hook_t *
+alloc_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
+{
+ rb_event_hook_t *hook = ALLOC(rb_event_hook_t);
+ hook->func = func;
+ hook->flag = events;
+ hook->data = data;
+ return hook;
+}
+
+static void
+thread_reset_event_flags(rb_thread_t *th)
+{
+ rb_event_hook_t *hook = th->event_hooks;
+ rb_event_flag_t flag = th->event_flags & RUBY_EVENT_VM;
+
+ while (hook) {
+ if (!(flag & RUBY_EVENT_REMOVED))
+ flag |= hook->flag;
+ hook = hook->next;
+ }
+ th->event_flags = flag;
+}
+
+static void
+rb_threadptr_add_event_hook(rb_thread_t *th,
+ rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
+{
+ rb_event_hook_t *hook = alloc_event_hook(func, events, data);
+ hook->next = th->event_hooks;
+ th->event_hooks = hook;
+ thread_reset_event_flags(th);
+}
+
+static rb_thread_t *
+thval2thread_t(VALUE thval)
+{
+ rb_thread_t *th;
+ GetThreadPtr(thval, th);
+ return th;
+}
+
+void
+rb_thread_add_event_hook(VALUE thval,
+ rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
+{
+ rb_threadptr_add_event_hook(thval2thread_t(thval), func, events, data);
+}
+
+static int
+set_threads_event_flags_i(st_data_t key, st_data_t val, st_data_t flag)
+{
+ VALUE thval = key;
+ rb_thread_t *th;
+ GetThreadPtr(thval, th);
+
+ if (flag) {
+ th->event_flags |= RUBY_EVENT_VM;
+ }
+ else {
+ th->event_flags &= (~RUBY_EVENT_VM);
+ }
+ return ST_CONTINUE;
+}
+
+static void
+set_threads_event_flags(int flag)
+{
+ st_foreach(GET_VM()->living_threads, set_threads_event_flags_i, (st_data_t) flag);
+}
+
+static inline int
+exec_event_hooks(const rb_event_hook_t *hook, rb_event_flag_t flag, VALUE self, ID id, VALUE klass)
+{
+ int removed = 0;
+ for (; hook; hook = hook->next) {
+ if (hook->flag & RUBY_EVENT_REMOVED) {
+ removed++;
+ continue;
+ }
+ if (flag & hook->flag) {
+ (*hook->func)(flag, hook->data, self, id, klass);
+ }
+ }
+ return removed;
+}
+
+static int remove_defered_event_hook(rb_event_hook_t **root);
+
+static VALUE
+thread_exec_event_hooks(VALUE args, int running)
+{
+ struct event_call_args *argp = (struct event_call_args *)args;
+ rb_thread_t *th = argp->th;
+ rb_event_flag_t flag = argp->event;
+ VALUE self = argp->self;
+ ID id = argp->id;
+ VALUE klass = argp->klass;
+ const rb_event_flag_t wait_event = th->event_flags;
+ int removed;
+
+ if (self == rb_mRubyVMFrozenCore) return 0;
+
+ if ((wait_event & flag) && !(running & EVENT_RUNNING_THREAD)) {
+ th->tracing |= EVENT_RUNNING_THREAD;
+ removed = exec_event_hooks(th->event_hooks, flag, self, id, klass);
+ th->tracing &= ~EVENT_RUNNING_THREAD;
+ if (removed) {
+ remove_defered_event_hook(&th->event_hooks);
+ }
+ }
+ if (wait_event & RUBY_EVENT_VM) {
+ if (th->vm->event_hooks == NULL) {
+ th->event_flags &= (~RUBY_EVENT_VM);
+ }
+ else if (!(running & EVENT_RUNNING_VM)) {
+ th->tracing |= EVENT_RUNNING_VM;
+ removed = exec_event_hooks(th->vm->event_hooks, flag, self, id, klass);
+ th->tracing &= ~EVENT_RUNNING_VM;
+ if (removed) {
+ remove_defered_event_hook(&th->vm->event_hooks);
+ }
+ }
+ }
+ return 0;
+}
+
+void
+rb_threadptr_exec_event_hooks(rb_thread_t *th, rb_event_flag_t flag, VALUE self, ID id, VALUE klass)
+{
+ const VALUE errinfo = th->errinfo;
+ struct event_call_args args;
+ args.th = th;
+ args.event = flag;
+ args.self = self;
+ args.id = id;
+ args.klass = klass;
+ args.proc = 0;
+ thread_suppress_tracing(th, EVENT_RUNNING_EVENT_MASK, thread_exec_event_hooks, (VALUE)&args, FALSE);
+ th->errinfo = errinfo;
+}
+
+void
+rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data)
+{
+ rb_event_hook_t *hook = alloc_event_hook(func, events, data);
+ rb_vm_t *vm = GET_VM();
+
+ hook->next = vm->event_hooks;
+ vm->event_hooks = hook;
+
+ set_threads_event_flags(1);
+}
+
+static int
+defer_remove_event_hook(rb_event_hook_t *hook, rb_event_hook_func_t func)
+{
+ while (hook) {
+ if (func == 0 || hook->func == func) {
+ hook->flag |= RUBY_EVENT_REMOVED;
+ }
+ hook = hook->next;
+ }
+ return -1;
+}
+
+static int
+remove_event_hook(rb_event_hook_t **root, rb_event_hook_func_t func)
+{
+ rb_event_hook_t *hook = *root, *next;
+
+ while (hook) {
+ next = hook->next;
+ if (func == 0 || hook->func == func || (hook->flag & RUBY_EVENT_REMOVED)) {
+ *root = next;
+ xfree(hook);
+ }
+ else {
+ root = &hook->next;
+ }
+ hook = next;
+ }
+ return -1;
+}
+
+static int
+remove_defered_event_hook(rb_event_hook_t **root)
+{
+ rb_event_hook_t *hook = *root, *next;
+
+ while (hook) {
+ next = hook->next;
+ if (hook->flag & RUBY_EVENT_REMOVED) {
+ *root = next;
+ xfree(hook);
+ }
+ else {
+ root = &hook->next;
+ }
+ hook = next;
+ }
+ return -1;
+}
+
+static int
+rb_threadptr_remove_event_hook(rb_thread_t *th, rb_event_hook_func_t func)
+{
+ int ret;
+ if (th->tracing & EVENT_RUNNING_THREAD) {
+ ret = defer_remove_event_hook(th->event_hooks, func);
+ }
+ else {
+ ret = remove_event_hook(&th->event_hooks, func);
+ }
+ thread_reset_event_flags(th);
+ return ret;
+}
+
+int
+rb_thread_remove_event_hook(VALUE thval, rb_event_hook_func_t func)
+{
+ return rb_threadptr_remove_event_hook(thval2thread_t(thval), func);
+}
+
+static rb_event_hook_t *
+search_live_hook(rb_event_hook_t *hook)
+{
+ while (hook) {
+ if (!(hook->flag & RUBY_EVENT_REMOVED))
+ return hook;
+ hook = hook->next;
+ }
+ return NULL;
+}
+
+static int
+running_vm_event_hooks(st_data_t key, st_data_t val, st_data_t data)
+{
+ rb_thread_t *th = thval2thread_t((VALUE)key);
+ if (!(th->tracing & EVENT_RUNNING_VM)) return ST_CONTINUE;
+ *(rb_thread_t **)data = th;
+ return ST_STOP;
+}
+
+static rb_thread_t *
+vm_event_hooks_running_thread(rb_vm_t *vm)
+{
+ rb_thread_t *found = NULL;
+ st_foreach(vm->living_threads, running_vm_event_hooks, (st_data_t)&found);
+ return found;
+}
+
+int
+rb_remove_event_hook(rb_event_hook_func_t func)
+{
+ rb_vm_t *vm = GET_VM();
+ rb_event_hook_t *hook = search_live_hook(vm->event_hooks);
+ int ret;
+
+ if (vm_event_hooks_running_thread(vm)) {
+ ret = defer_remove_event_hook(vm->event_hooks, func);
+ }
+ else {
+ ret = remove_event_hook(&vm->event_hooks, func);
+ }
+
+ if (hook && !search_live_hook(vm->event_hooks)) {
+ set_threads_event_flags(0);
+ }
+
+ return ret;
+}
+
+static int
+clear_trace_func_i(st_data_t key, st_data_t val, st_data_t flag)
+{
+ rb_thread_t *th;
+ GetThreadPtr((VALUE)key, th);
+ rb_threadptr_remove_event_hook(th, 0);
+ return ST_CONTINUE;
+}
+
+void
+rb_clear_trace_func(void)
+{
+ st_foreach(GET_VM()->living_threads, clear_trace_func_i, (st_data_t) 0);
+ rb_remove_event_hook(0);
+}
+
+static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALUE klass);
+
+/* (2-1) set_trace_func (old API) */
+
+/*
+ * call-seq:
+ * set_trace_func(proc) -> proc
+ * set_trace_func(nil) -> nil
+ *
+ * Establishes _proc_ as the handler for tracing, or disables
+ * tracing if the parameter is +nil+. _proc_ takes up
+ * to six parameters: an event name, a filename, a line number, an
+ * object id, a binding, and the name of a class. _proc_ is
+ * invoked whenever an event occurs. Events are: <code>c-call</code>
+ * (call a C-language routine), <code>c-return</code> (return from a
+ * C-language routine), <code>call</code> (call a Ruby method),
+ * <code>class</code> (start a class or module definition),
+ * <code>end</code> (finish a class or module definition),
+ * <code>line</code> (execute code on a new line), <code>raise</code>
+ * (raise an exception), and <code>return</code> (return from a Ruby
+ * method). Tracing is disabled within the context of _proc_.
+ *
+ * class Test
+ * def test
+ * a = 1
+ * b = 2
+ * end
+ * end
+ *
+ * set_trace_func proc { |event, file, line, id, binding, classname|
+ * printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
+ * }
+ * t = Test.new
+ * t.test
+ *
+ * line prog.rb:11 false
+ * c-call prog.rb:11 new Class
+ * c-call prog.rb:11 initialize Object
+ * c-return prog.rb:11 initialize Object
+ * c-return prog.rb:11 new Class
+ * line prog.rb:12 false
+ * call prog.rb:2 test Test
+ * line prog.rb:3 test Test
+ * line prog.rb:4 test Test
+ * return prog.rb:4 test Test
+ */
+
+static VALUE
+set_trace_func(VALUE obj, VALUE trace)
+{
+ rb_remove_event_hook(call_trace_func);
+
+ if (NIL_P(trace)) {
+ GET_THREAD()->tracing = EVENT_RUNNING_NOTHING;
+ return Qnil;
+ }
+
+ if (!rb_obj_is_proc(trace)) {
+ rb_raise(rb_eTypeError, "trace_func needs to be Proc");
+ }
+
+ rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace);
+ return trace;
+}
+
+static void
+thread_add_trace_func(rb_thread_t *th, VALUE trace)
+{
+ if (!rb_obj_is_proc(trace)) {
+ rb_raise(rb_eTypeError, "trace_func needs to be Proc");
+ }
+
+ rb_threadptr_add_event_hook(th, call_trace_func, RUBY_EVENT_ALL, trace);
+}
+
+/*
+ * call-seq:
+ * thr.add_trace_func(proc) -> proc
+ *
+ * Adds _proc_ as a handler for tracing.
+ * See <code>Thread#set_trace_func</code> and +set_trace_func+.
+ */
+
+static VALUE
+thread_add_trace_func_m(VALUE obj, VALUE trace)
+{
+ rb_thread_t *th;
+ GetThreadPtr(obj, th);
+ thread_add_trace_func(th, trace);
+ return trace;
+}
+
+/*
+ * call-seq:
+ * thr.set_trace_func(proc) -> proc
+ * thr.set_trace_func(nil) -> nil
+ *
+ * Establishes _proc_ on _thr_ as the handler for tracing, or
+ * disables tracing if the parameter is +nil+.
+ * See +set_trace_func+.
+ */
+
+static VALUE
+thread_set_trace_func_m(VALUE obj, VALUE trace)
+{
+ rb_thread_t *th;
+ GetThreadPtr(obj, th);
+ rb_threadptr_remove_event_hook(th, call_trace_func);
+
+ if (NIL_P(trace)) {
+ th->tracing = EVENT_RUNNING_NOTHING;
+ return Qnil;
+ }
+ thread_add_trace_func(th, trace);
+ return trace;
+}
+
+static const char *
+get_event_name(rb_event_flag_t event)
+{
+ switch (event) {
+ case RUBY_EVENT_LINE:
+ return "line";
+ case RUBY_EVENT_CLASS:
+ return "class";
+ case RUBY_EVENT_END:
+ return "end";
+ case RUBY_EVENT_CALL:
+ return "call";
+ case RUBY_EVENT_RETURN:
+ return "return";
+ case RUBY_EVENT_C_CALL:
+ return "c-call";
+ case RUBY_EVENT_C_RETURN:
+ return "c-return";
+ case RUBY_EVENT_RAISE:
+ return "raise";
+ default:
+ return "unknown";
+ }
+}
+
+static VALUE
+call_trace_proc(VALUE args, int tracing)
+{
+ struct event_call_args *p = (struct event_call_args *)args;
+ const char *srcfile = rb_sourcefile();
+ VALUE eventname = rb_str_new2(get_event_name(p->event));
+ VALUE filename = srcfile ? rb_str_new2(srcfile) : Qnil;
+ VALUE argv[6];
+ int line = rb_sourceline();
+ ID id = 0;
+ VALUE klass = 0;
+
+ if (p->klass != 0) {
+ id = p->id;
+ klass = p->klass;
+ }
+ else {
+ rb_thread_method_id_and_class(p->th, &id, &klass);
+ }
+ if (id == ID_ALLOCATOR)
+ return Qnil;
+ if (klass) {
+ if (RB_TYPE_P(klass, T_ICLASS)) {
+ klass = RBASIC(klass)->klass;
+ }
+ else if (FL_TEST(klass, FL_SINGLETON)) {
+ klass = rb_iv_get(klass, "__attached__");
+ }
+ }
+
+ argv[0] = eventname;
+ argv[1] = filename;
+ argv[2] = INT2FIX(line);
+ argv[3] = id ? ID2SYM(id) : Qnil;
+ argv[4] = (p->self && srcfile) ? rb_binding_new() : Qnil;
+ argv[5] = klass ? klass : Qnil;
+
+ return rb_proc_call_with_block(p->proc, 6, argv, Qnil);
+}
+
+static void
+call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass)
+{
+ struct event_call_args args;
+
+ args.th = GET_THREAD();
+ args.event = event;
+ args.proc = proc;
+ args.self = self;
+ args.id = id;
+ args.klass = klass;
+ ruby_suppress_tracing(call_trace_proc, (VALUE)&args, FALSE);
+}
+
+VALUE
+ruby_suppress_tracing(VALUE (*func)(VALUE, int), VALUE arg, int always)
+{
+ rb_thread_t *th = GET_THREAD();
+ return thread_suppress_tracing(th, EVENT_RUNNING_TRACE, func, arg, always);
+}
+
+static VALUE
+thread_suppress_tracing(rb_thread_t *th, int ev, VALUE (*func)(VALUE, int), VALUE arg, int always)
+{
+ int state, tracing = th->tracing, running = tracing & ev;
+ volatile int raised;
+ volatile int outer_state;
+ VALUE result = Qnil;
+
+ if (running == ev && !always) {
+ return Qnil;
+ }
+ else {
+ th->tracing |= ev;
+ }
+
+ raised = rb_threadptr_reset_raised(th);
+ outer_state = th->state;
+ th->state = 0;
+
+ PUSH_TAG();
+ if ((state = EXEC_TAG()) == 0) {
+ result = (*func)(arg, running);
+ }
+
+ if (raised) {
+ rb_threadptr_set_raised(th);
+ }
+ POP_TAG();
+
+ th->tracing = tracing;
+ if (state) {
+ JUMP_TAG(state);
+ }
+ th->state = outer_state;
+
+ return result;
+}
+
+/* (2-2) TracePoint API (not yet) */
+
+
+/* This function is called from inits.c */
+void
+Init_vm_trace(void)
+{
+ /* trace */
+ rb_define_global_function("set_trace_func", set_trace_func, 1);
+ rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1);
+ rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1);
+}
+