зеркало из https://github.com/github/ruby.git
* 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
This commit is contained in:
Родитель
d2e9b54564
Коммит
8eb93103e4
11
ChangeLog
11
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>
|
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
|
* test/erb/test_erb.rb (test_html_escape): add assertions for the
|
||||||
|
|
|
@ -92,6 +92,7 @@ COMMONOBJS = array.$(OBJEXT) \
|
||||||
vm.$(OBJEXT) \
|
vm.$(OBJEXT) \
|
||||||
vm_dump.$(OBJEXT) \
|
vm_dump.$(OBJEXT) \
|
||||||
vm_backtrace.$(OBJEXT) \
|
vm_backtrace.$(OBJEXT) \
|
||||||
|
vm_trace.$(OBJEXT) \
|
||||||
thread.$(OBJEXT) \
|
thread.$(OBJEXT) \
|
||||||
cont.$(OBJEXT) \
|
cont.$(OBJEXT) \
|
||||||
$(BUILTIN_ENCOBJS) \
|
$(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_backtrace.$(OBJEXT): {$(VPATH)}vm_backtrace.c \
|
||||||
$(VM_CORE_H_INCLUDES) $(RUBY_H_INCLUDES) \
|
$(VM_CORE_H_INCLUDES) $(RUBY_H_INCLUDES) \
|
||||||
{$(VPATH)}internal.h {$(VPATH)}iseq.h
|
{$(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) \
|
miniprelude.$(OBJEXT): {$(VPATH)}miniprelude.c $(RUBY_H_INCLUDES) \
|
||||||
$(VM_CORE_H_INCLUDES) {$(VPATH)}debug.h {$(VPATH)}internal.h
|
$(VM_CORE_H_INCLUDES) {$(VPATH)}debug.h {$(VPATH)}internal.h
|
||||||
prelude.$(OBJEXT): {$(VPATH)}prelude.c $(RUBY_H_INCLUDES) \
|
prelude.$(OBJEXT): {$(VPATH)}prelude.c $(RUBY_H_INCLUDES) \
|
||||||
|
|
1
inits.c
1
inits.c
|
@ -60,5 +60,6 @@ rb_call_inits(void)
|
||||||
CALL(Rational);
|
CALL(Rational);
|
||||||
CALL(Complex);
|
CALL(Complex);
|
||||||
CALL(version);
|
CALL(version);
|
||||||
|
CALL(vm_trace);
|
||||||
}
|
}
|
||||||
#undef CALL
|
#undef CALL
|
||||||
|
|
555
thread.c
555
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);
|
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:
|
* call-seq:
|
||||||
* thr.backtrace -> array
|
* thr.backtrace -> array
|
||||||
|
@ -5110,11 +4560,6 @@ Init_Thread(void)
|
||||||
recursive_key = rb_intern("__recursive_key__");
|
recursive_key = rb_intern("__recursive_key__");
|
||||||
rb_eThreadError = rb_define_class("ThreadError", rb_eStandardError);
|
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 */
|
/* init thread core */
|
||||||
{
|
{
|
||||||
/* main thread setting */
|
/* main thread setting */
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче