<commit_before>#include <stdio.h>
#include <ruby.h>
#include <node.h>
#include <rubysig.h>
#include <st.h>

#define DEBUG_VERSION "0.7.2"

#ifdef _WIN32
struct FRAME {
	VALUE self;
	int argc;
	ID last_func;
	ID orig_func;
	VALUE last_class;
	struct FRAME *prev;
	struct FRAME *tmp;
	struct RNode *node;
	int iter;
	int flags;
	unsigned long uniq;
};

struct SCOPE {
	struct RBasic super;
	ID *local_tbl;
	VALUE *local_vars;
	int flags;
};

struct RVarmap {
	struct RBasic super;
	ID id;
	VALUE val;
	struct RVarmap *next;
};

RUBY_EXTERN struct SCOPE   *ruby_scope;
RUBY_EXTERN struct FRAME   *ruby_frame;
RUBY_EXTERN struct RVarmap *ruby_dyna_vars;
#else
#include <env.h>
#endif

#define CTX_FL_MOVED        (1<<1)
#define CTX_FL_SUSPEND      (1<<2)
#define CTX_FL_TRACING      (1<<3)
#define CTX_FL_SKIPPED      (1<<4)
#define CTX_FL_IGNORE       (1<<5)
#define CTX_FL_DEAD         (1<<6)
#define CTX_FL_WAS_RUNNING  (1<<7)

#define CTX_FL_TEST(c,f) ((c)->flags & (f))
#define CTX_FL_SET(c,f) do { (c)->flags |= (f); } while (0)
#define CTX_FL_UNSET(c,f) do { (c)->flags &= ~(f); } while (0)

#define DID_MOVED   (debug_context->last_line != line || \
                          debug_context->last_file == NULL || \
                          strcmp(debug_context->last_file, file) != 0)

#define IS_STARTED  (threads_tbl != Qnil)
#define FRAME_N(n)  (&debug_context->frames[debug_context->stack_size-(n)-1])
#define GET_FRAME       (FRAME_N(check_frame_number(debug_context, frame)))

#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif

#define STACK_SIZE_INCREMENT 128
				     
typedef struct {
    VALUE binding;
    ID id;
    int line;
    const char * file;
    short dead;
    VALUE self;
    union {
	struct {
	    struct FRAME *frame;
	    struct SCOPE *scope;
	    struct RVarmap *dyna_vars;
	} runtime;
	struct {
	    VALUE locals;
	} copy;
    } info;
} debug_frame_t;

typedef struct {
    VALUE thread_id;
    int thnum;
    int flags;
    int stop_next;
    int dest_frame;
    int stop_line;
    int stop_frame;
    int stack_size;
    int stack_len;
    debug_frame_t *frames;
    const char * last_file;
    int last_line;
} debug_context_t;

enum bp_type {BP_POS_TYPE, BP_METHOD_TYPE};

typedef struct {
    int   id;
    int   type;
    VALUE source;
    union
    {
        int line;
        ID  mid;
    } pos;
    VALUE expr;
} debug_breakpoint_t;

typedef struct {
    st_table *tbl;
} threads_table_t;

static VALUE threads_tbl        = Qnil;
static VALUE breakpoints        = Qnil;
static VALUE catchpoint         = Qnil;
static VALUE tracing            = Qfalse;
static VALUE locker             = Qnil;
static VALUE post_mortem        = Qfalse;
static VALUE keep_frame_binding = Qfalse;

static VALUE last_context = Qnil;
static VALUE last_thread  = Qnil;
static debug_context_t *last_debug_context = NULL;

static VALUE mDebugger;
static VALUE cThreadsTable;
static VALUE cContext;
static VALUE cBreakpoint;

static VALUE rb_mObjectSpace;

static ID idAtLine;
static ID idAtBreakpoint;
static ID idAtCatchpoint;
static ID idAtTracing;
static ID idEval;
static ID idList;

static int start_count = 0;
static int thnum_max = 0;
static int bkp_count = 0;
static int last_debugged_thnum = -1;
static unsigned long last_check = 0;
static unsigned long hook_count = 0;

static VALUE create_binding(VALUE);
static VALUE debug_stop(VALUE);
static void save_current_position(debug_context_t *);
static VALUE context_copy_locals(debug_frame_t *);
static void context_suspend_0(debug_context_t *);
static void context_resume_0(debug_context_t *);

typedef struct locked_thread_t { 
    VALUE thread_id;
    struct locked_thread_t *next;
} locked_thread_t;

static locked_thread_t *locked_head = NULL;
static locked_thread_t *locked_tail = NULL;

inline static void *
ruby_method_ptr(VALUE class, ID meth_id)
{
    NODE *body, *method;
    st_lookup(RCLASS(class)->m_tbl, meth_id, (st_data_t *)&body);
    method       = (NODE *)body->u2.value;
    return (void *)method->u1.value;
}

inline static VALUE
ref2id(VALUE obj)
{
    return rb_obj_id(obj);
}

static VALUE
id2ref_unprotected(VALUE id)
{
    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);
    static id2ref_func_t f_id2ref = NULL;
    if(f_id2ref == NULL)
    {
        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));
    }
    return f_id2ref(rb_mObjectSpace, id);
}

static VALUE
id2ref_error()
{
    rb_p(ruby_errinfo);
    return Qnil;
}

static VALUE
id2ref(VALUE id)
{
    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);
}

inline static VALUE
context_thread_0(debug_context_t *debug_context)
{
    return id2ref(debug_context->thread_id);
}

static int
is_in_locked(VALUE thread_id)
{
    locked_thread_t *node;
    
    if(!locked_head)
        return 0;
    
    for(node = locked_head; node != locked_tail; node = node->next)
    {
        if(node->thread_id == thread_id) return 1;
    }
    return 0;
}

static void
add_to_locked(VALUE thread)
{
    locked_thread_t *node;
    VALUE thread_id = ref2id(thread);
    
    if(is_in_locked(thread_id))
        return;

    node = ALLOC(locked_thread_t);
    node->thread_id = thread_id;
    node->next = NULL;
    if(locked_tail)
        locked_tail->next = node;
    locked_tail = node;
    if(!locked_head)
        locked_head = node;
}

static VALUE
remove_from_locked()
{
    VALUE thread;
    locked_thread_t *node;
    
    if(locked_head == NULL)
        return Qnil;
    node = locked_head;
    locked_head = locked_head->next;
    if(locked_tail == node)
        locked_tail = NULL;
    thread = id2ref(node->thread_id);
    xfree(node);
    return thread;
}

static int
threads_table_mark_keyvalue(VALUE key, VALUE value, int dummy)
{
    rb_gc_mark(value);
    return ST_CONTINUE;
}

static void
threads_table_mark(void* data)
{
    threads_table_t *threads_table = (threads_table_t*)data;
    st_foreach(threads_table->tbl, threads_table_mark_keyvalue, 0);
}

static void
threads_table_free(void* data)
{
    threads_table_t *threads_table = (threads_table_t*)data;
    st_free_table(threads_table->tbl);
    xfree(threads_table);
}

static VALUE
threads_table_create()
{
    threads_table_t *threads_table;
    
    threads_table = ALLOC(threads_table_t);
    threads_table->tbl = st_init_numtable();
    return Data_Wrap_Struct(cThreadsTable, threads_table_mark, threads_table_free, threads_table);
}

static int
threads_table_clear_i(VALUE key, VALUE value, VALUE dummy)
{
    return ST_DELETE;
}

static void
threads_table_clear(VALUE table)
{
    threads_table_t *threads_table;
    
    Data_Get_Struct(table, threads_table_t, threads_table);
    st_foreach(threads_table->tbl, threads_table_clear_i, 0);
}

static VALUE
is_thread_alive(VALUE thread)
{
    typedef VALUE (*thread_alive_func_t)(VALUE);
    static thread_alive_func_t f_thread_alive = NULL;
    if(!f_thread_alive)
    {
        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));
    }
    return f_thread_alive(thread);
}

static int
threads_table_check_i(VALUE key, VALUE value, VALUE dummy)
{
    VALUE thread;
    
    thread = id2ref(key);
    if(!rb_obj_is_kind_of(thread, rb_cThread))
    {
        return ST_DELETE;
    }
    if(rb_protect(is_thread_alive, thread, 0) != Qtrue)
    {
        return ST_DELETE;
    }
    return ST_CONTINUE;
}

static void
check_thread_contexts()
{
    threads_table_t *threads_table;

    Data_Get_Struct(threads_tbl, threads_table_t, threads_table);
    st_foreach(threads_table->tbl, threads_table_check_i, 0);
}

/*
 *   call-seq:
 *      Debugger.started? -> bool
 *   
 *   Returns +true+ the debugger is started.
 */
static VALUE
debug_is_started(VALUE self)
{
    return IS_STARTED ? Qtrue : Qfalse;
}

static void
debug_check_started()
{
    if(!IS_STARTED)
    {
        rb_raise(rb_eRuntimeError, "Debugger.start is not called yet.");
    }
}

static void
debug_context_mark(void *data)
{
    debug_frame_t *frame;
    int i;
    
    debug_context_t *debug_context = (debug_context_t *)data;
    for(i = 0; i < debug_context->stack_size; i++)
    {
	frame = &(debug_context->frames[i]);
	rb_gc_mark(frame->binding);
	rb_gc_mark(frame->self);
	if(frame->dead)
	{
	    rb_gc_mark(frame->info.copy.locals);
	}
    }
}

static void
debug_context_free(void *data)
{
    debug_context_t *debug_context = (debug_context_t *)data;
    xfree(debug_context->frames);
}

static VALUE
debug_context_create(VALUE thread)
{
    debug_context_t *debug_context;
    
    debug_context = ALLOC(debug_context_t);
    debug_context-> thnum = ++thnum_max;
    
    debug_context->last_file = NULL;
    debug_context->last_line = 0;
    debug_context->flags = 0;

    debug_context->stop_next = -1;
    debug_context->dest_frame = -1;
    debug_context->stop_line = -1;
    debug_context->stop_frame = -1;
    debug_context->stack_len = STACK_SIZE_INCREMENT;
    debug_context->frames = ALLOC_N(debug_frame_t, STACK_SIZE_INCREMENT);
    debug_context->stack_size = 0;
    debug_context->thread_id = ref2id(thread);
    return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, debug_context);
}

static VALUE
debug_context_dup(debug_context_t *debug_context)
{
    debug_context_t *new_debug_context;
    debug_frame_t *new_frame, *old_frame;
    int i;

    new_debug_context = ALLOC(debug_context_t);
    memcpy(new_debug_context, debug_context, sizeof(debug_context_t));
    new_debug_context->stop_next = -1;
    new_debug_context->dest_frame = -1;
    new_debug_context->stop_line = -1;
    new_debug_context->stop_frame = -1;
    CTX_FL_SET(new_debug_context, CTX_FL_DEAD);
    new_debug_context->frames = ALLOC_N(debug_frame_t, debug_context->stack_size);
    new_debug_context->stack_len = debug_context->stack_size;
    memcpy(new_debug_context->frames, debug_context->frames, sizeof(debug_frame_t) * debug_context->stack_size);
    for(i = 0; i < debug_context->stack_size; i++)
    {
	new_frame = &(new_debug_context->frames[i]);
	old_frame = &(debug_context->frames[i]);
	new_frame->dead = 1;
	new_frame->info.copy.locals = context_copy_locals(old_frame);
    }
    return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, new_debug_context);
}

static void
thread_context_lookup(VALUE thread, VALUE *context, debug_context_t **debug_context)
{
    threads_table_t *threads_table;
    VALUE thread_id;
    debug_context_t *l_debug_context;

    debug_check_started();

    if(last_thread == thread && last_context != Qnil)
    {
        *context = last_context;
        if(debug_context)
            *debug_context = last_debug_context;
        return;
    }
    thread_id = ref2id(thread);
    Data_Get_Struct(threads_tbl, threads_table_t, threads_table);
    if(!st_lookup(threads_table->tbl, thread_id, context))
    {
        *context = debug_context_create(thread);
        st_insert(threads_table->tbl, thread_id, *context);
    }
    
    Data_Get_Struct(*context, debug_context_t, l_debug_context);
    if(debug_context)
        *debug_context = l_debug_context;

    last_thread = thread;
    last_context = *context;
    last_debug_context = l_debug_context;
}

static VALUE
call_at_line_unprotected(VALUE args)
{
    VALUE context;
    context = *RARRAY(args)->ptr;
    return rb_funcall2(context, idAtLine, RARRAY(args)->len - 1, RARRAY(args)->ptr + 1);
}

static VALUE
call_at_line(VALUE context, debug_context_t *debug_context, VALUE file, VALUE line)
{
    VALUE args;
    
    last_debugged_thnum = debug_context->thnum;
    save_current_position(debug_context);
    
    args = rb_ary_new3(3, context, file, line);
    return rb_protect(call_at_line_unprotected, args, 0);
}

static void
save_call_frame(VALUE self, char *file, int line, ID mid, debug_context_t *debug_context)
{
    VALUE binding;
    debug_frame_t *debug_frame;
    int frame_n;
    
    binding = self && RTEST(keep_frame_binding)? create_binding(self) : Qnil;
    
    frame_n = debug_context->stack_size++;
    if(frame_n >= debug_context->stack_len)
    {
	debug_context->stack_len += STACK_SIZE_INCREMENT;
	debug_context->frames = REALLOC_N(debug_context->frames, debug_frame_t, debug_context->stack_len);
    }
    debug_frame = &debug_context->frames[frame_n];
    debug_frame->file = file;
    debug_frame->line = line;
    debug_frame->binding = binding;
    debug_frame->id = mid;
    debug_frame->dead = 0;
    debug_frame->self = self;
    debug_frame->info.runtime.frame = ruby_frame;
    debug_frame->info.runtime.scope = ruby_scope;
    
    debug_frame->info.runtime.dyna_vars = ruby_dyna_vars;
}

#if defined DOSISH
#define isdirsep(x) ((x) == '/' || (x) == '\\')
#else
#define isdirsep(x) ((x) == '/')
#endif

static int
filename_cmp(VALUE source, char *file)
{
    char *source_ptr, *file_ptr;
    int s_len, f_len, min_len;
    int s,f;
    int dirsep_flag = 0;
    
    s_len = RSTRING(source)->len;
    f_len = strlen(file);
    min_len = min(s_len, f_len);
    
    source_ptr = RSTRING(source)->ptr;
    file_ptr   = file;
    
    for( s = s_len - 1, f = f_len - 1; s >= s_len - min_len && f >= f_len - min_len; s--, f-- )
    {
        if((source_ptr[s] == '.' || file_ptr[f] == '.') && dirsep_flag)
            return 1;
        if(source_ptr[s] != file_ptr[f])
            return 0;
        if(isdirsep(source_ptr[s]))
            dirsep_flag = 1;
    }
    return 1;
}

static int
check_breakpoints_by_pos(debug_context_t *debug_context, char *file, int line)
{
    VALUE breakpoint; 
    debug_breakpoint_t *debug_breakpoint;
    int i;

    if(RARRAY(breakpoints)->len == 0)
        return -1;
    if(!CTX_FL_TEST(debug_context, CTX_FL_MOVED))
        return -1;
    
    for(i = 0; i < RARRAY(breakpoints)->len; i++)
    {
        breakpoint = rb_ary_entry(breakpoints, i);
        Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
        if(debug_breakpoint->type != BP_POS_TYPE)
            continue;
        if(debug_breakpoint->pos.line != line)
            continue;
        if(filename_cmp(debug_breakpoint->source, file))
            return i;
    }
    return -1;
}

inline static int
classname_cmp(VALUE name, VALUE klass)
{
    return (klass != Qnil && rb_str_cmp(name, rb_mod_name(klass)) == 0);
}

static int
check_breakpoints_by_method(debug_context_t *debug_context, VALUE klass, ID mid)
{
    VALUE breakpoint; 
    debug_breakpoint_t *debug_breakpoint;
    int i;
    
    if(RARRAY(breakpoints)->len == 0)
        return -1;
    if(!CTX_FL_TEST(debug_context, CTX_FL_MOVED))
        return -1;
    for(i = 0; i < RARRAY(breakpoints)->len; i++)
    {
        breakpoint = rb_ary_entry(breakpoints, i);
        Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
        if(debug_breakpoint->type != BP_METHOD_TYPE)
            continue;
        if(debug_breakpoint->pos.mid != mid)
            continue;
        if(classname_cmp(debug_breakpoint->source, klass))
            return i;
    }
    return -1;
}

/*
 * This is a NASTY HACK. For some reasons rb_f_binding is decalred 
 * static in eval.c
 */
static VALUE
create_binding(VALUE self)
{
    typedef VALUE (*bind_func_t)(VALUE);
    static bind_func_t f_binding = NULL;

    if(f_binding == NULL)
    {
        f_binding = (bind_func_t)ruby_method_ptr(rb_mKernel, rb_intern("binding"));
    }
    return f_binding(self);
}

static VALUE
get_breakpoint_at(int index) 
{
    return rb_ary_entry(breakpoints, index);
}

static VALUE
eval_expression(VALUE args)
{
    return rb_funcall2(rb_mKernel, idEval, 2, RARRAY(args)->ptr);
}

inline static int
check_breakpoint_expression(VALUE breakpoint, VALUE binding)
{
    debug_breakpoint_t *debug_breakpoint;
    VALUE args, expr_result;
    
    Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
    if(NIL_P(debug_breakpoint->expr))
        return 1;
    
    args = rb_ary_new3(2, debug_breakpoint->expr, binding);
    expr_result = rb_protect(eval_expression, args, 0);
    return RTEST(expr_result);
}

inline static debug_frame_t *
get_top_frame(debug_context_t *debug_context)
{
    if(debug_context->stack_size == 0)
        return NULL;
    else 
	return &(debug_context->frames[debug_context->stack_size-1]);
}

inline static void
save_top_binding(debug_context_t *debug_context, VALUE binding)
{
    debug_frame_t *debug_frame;
    debug_frame = get_top_frame(debug_context);
    if(debug_frame)
        debug_frame->binding = binding;
}

inline static void
set_frame_source(debug_context_t *debug_context, char *file, int line)
{
    debug_frame_t *top_frame;
    top_frame = get_top_frame(debug_context);
    if(top_frame)
    {
        top_frame->file = file;
        top_frame->line = line;
    }
}

inline static void 
set_dyna_vars(debug_context_t *debug_context)
{
    debug_frame_t *top_frame;
    top_frame = get_top_frame(debug_context);
    if(top_frame)
    {
        top_frame->info.runtime.dyna_vars = ruby_dyna_vars;
    }
}

static void
save_current_position(debug_context_t *debug_context)
{
    debug_frame_t *debug_frame;
    
    debug_frame = get_top_frame(debug_context);
    if(!debug_frame) return;
    debug_context->last_file = debug_frame->file;
    debug_context->last_line = debug_frame->line;
    CTX_FL_UNSET(debug_context, CTX_FL_MOVED);
}

static char *
get_event_name(rb_event_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 void
debug_event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
{
    VALUE thread, context, breakpoint;
    VALUE binding = Qnil;
    debug_context_t *debug_context;
    char *file;
    int line;
    int breakpoint_index = -1;
    
    hook_count++;
    
    if (mid == ID_ALLOCATOR) return;
    if(!node) return;

    thread = rb_thread_current();
    thread_context_lookup(thread, &context, &debug_context);
    
    /* return if thread is marked as 'ignored'.
       debugger's threads are marked this way
    */
    if(CTX_FL_TEST(debug_context, CTX_FL_IGNORE)) return;
    
    while(1)
    {
        /* halt execution of the current thread if the debugger
           is activated in another
        */
        while(locker != Qnil && locker != thread)
        {
            add_to_locked(thread);
            rb_thread_stop();
        }

        /* stop the current thread if it's marked as suspended */
        if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))
            rb_thread_stop();
        else break;
    }
    
    /* return if the current thread is the locker */
    if(locker != Qnil) return;
    
    /* only the current thread can proceed */
    locker = thread;

    /* ignore a skipped section of code */
    if(CTX_FL_TEST(debug_context, CTX_FL_SKIPPED)) goto cleanup;

    file = node->nd_file;
    line = nd_line(node);

    if(DID_MOVED)
        CTX_FL_SET(debug_context, CTX_FL_MOVED);

    switch(event)
    {
    case RUBY_EVENT_LINE:
    {
        set_frame_source(debug_context, file, line);
	set_dyna_vars(debug_context);

        if(RTEST(tracing) || CTX_FL_TEST(debug_context, CTX_FL_TRACING))
            rb_funcall(context, idAtTracing, 2, rb_str_new2(file), INT2FIX(line));
        
        if(debug_context->dest_frame == -1 || 
            debug_context->stack_size == debug_context->dest_frame)
        {
            debug_context->stop_next--;
            if(debug_context->stop_next < 0)
                debug_context->stop_next = -1;
            /* we check that we actualy moved to another line */
            if(DID_MOVED)
                debug_context->stop_line--;
        }
        else if(debug_context->stack_size < debug_context->dest_frame)
        {
            debug_context->stop_next = 0;
        }

        if(debug_context->stack_size == 0)
            save_call_frame(self, file, line, mid, debug_context);

        if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||
            (breakpoint_index = check_breakpoints_by_pos(debug_context, file, line)) != -1)
        {
            binding = self? create_binding(self) : Qnil;
            /* check breakpoint expression */
            if(breakpoint_index != -1)
            {
                breakpoint = get_breakpoint_at(breakpoint_index);
                if(check_breakpoint_expression(breakpoint, binding))
                    rb_funcall(context, idAtBreakpoint, 1, breakpoint);
                else
                    break;
            }

            /* reset all pointers */
            debug_context->dest_frame = -1;
            debug_context->stop_line = -1;
            debug_context->stop_next = -1;

            save_top_binding(debug_context, binding);
            call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
        }
        break;
    }
    case RUBY_EVENT_C_CALL:
    {
        set_frame_source(debug_context, file, line);
        break;
    }
    case RUBY_EVENT_CALL:
    {
        save_call_frame(self, file, line, mid, debug_context);
        breakpoint_index = check_breakpoints_by_method(debug_context, klass, mid);
        if(breakpoint_index != -1)
        {
            debug_frame_t *debug_frame;
            debug_frame = get_top_frame(debug_context);
            if(debug_frame)
                binding = debug_frame->binding;
            if(NIL_P(binding) && self)
                binding = create_binding(self);
            breakpoint = get_breakpoint_at(breakpoint_index);
            if(check_breakpoint_expression(breakpoint, binding))
            {
                save_top_binding(debug_context, binding);
                rb_funcall(context, idAtBreakpoint, 1, breakpoint);
                call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
            }
        }
        break;
    }
    case RUBY_EVENT_RETURN:
    case RUBY_EVENT_END:
    {
        if(debug_context->stack_size == debug_context->stop_frame)
        {
            debug_context->stop_next = 1;
            debug_context->stop_frame = 0;
        }
	// frame's method id must be checked to avoid a nasty Ruby's bug where
	// [call] event is received without corresponding [return] event. 
	// For example, it happens when singleton methods are called.
	while(debug_context->stack_size > 0)
	{
	    debug_context->stack_size--;
	    if(debug_context->frames[debug_context->stack_size].id == mid)
	      break;
	}
        break;
    }
    case RUBY_EVENT_CLASS:
    {
        save_call_frame(self, file, line, mid, debug_context);
        break;
    }
    case RUBY_EVENT_RAISE:<commit_msg>found a better patch<commit_after>#include <stdio.h>
#include <ruby.h>
#include <node.h>
#include <rubysig.h>
#include <st.h>

#define DEBUG_VERSION "0.7.2"

#ifdef _WIN32
struct FRAME {
	VALUE self;
	int argc;
	ID last_func;
	ID orig_func;
	VALUE last_class;
	struct FRAME *prev;
	struct FRAME *tmp;
	struct RNode *node;
	int iter;
	int flags;
	unsigned long uniq;
};

struct SCOPE {
	struct RBasic super;
	ID *local_tbl;
	VALUE *local_vars;
	int flags;
};

struct RVarmap {
	struct RBasic super;
	ID id;
	VALUE val;
	struct RVarmap *next;
};

RUBY_EXTERN struct SCOPE   *ruby_scope;
RUBY_EXTERN struct FRAME   *ruby_frame;
RUBY_EXTERN struct RVarmap *ruby_dyna_vars;
#else
#include <env.h>
#endif

#define CTX_FL_MOVED        (1<<1)
#define CTX_FL_SUSPEND      (1<<2)
#define CTX_FL_TRACING      (1<<3)
#define CTX_FL_SKIPPED      (1<<4)
#define CTX_FL_IGNORE       (1<<5)
#define CTX_FL_DEAD         (1<<6)
#define CTX_FL_WAS_RUNNING  (1<<7)

#define CTX_FL_TEST(c,f) ((c)->flags & (f))
#define CTX_FL_SET(c,f) do { (c)->flags |= (f); } while (0)
#define CTX_FL_UNSET(c,f) do { (c)->flags &= ~(f); } while (0)

#define DID_MOVED   (debug_context->last_line != line || \
                          debug_context->last_file == NULL || \
                          strcmp(debug_context->last_file, file) != 0)

#define IS_STARTED  (threads_tbl != Qnil)
#define FRAME_N(n)  (&debug_context->frames[debug_context->stack_size-(n)-1])
#define GET_FRAME       (FRAME_N(check_frame_number(debug_context, frame)))

#ifndef min
#define min(x,y) ((x) < (y) ? (x) : (y))
#endif

#define STACK_SIZE_INCREMENT 128
				     
typedef struct {
    VALUE binding;
    ID id;
    int line;
    const char * file;
    short dead;
    VALUE self;
    union {
	struct {
	    struct FRAME *frame;
	    struct SCOPE *scope;
	    struct RVarmap *dyna_vars;
	} runtime;
	struct {
	    VALUE locals;
	} copy;
    } info;
} debug_frame_t;

typedef struct {
    VALUE thread_id;
    int thnum;
    int flags;
    int stop_next;
    int dest_frame;
    int stop_line;
    int stop_frame;
    int stack_size;
    int stack_len;
    debug_frame_t *frames;
    const char * last_file;
    int last_line;
} debug_context_t;

enum bp_type {BP_POS_TYPE, BP_METHOD_TYPE};

typedef struct {
    int   id;
    int   type;
    VALUE source;
    union
    {
        int line;
        ID  mid;
    } pos;
    VALUE expr;
} debug_breakpoint_t;

typedef struct {
    st_table *tbl;
} threads_table_t;

static VALUE threads_tbl        = Qnil;
static VALUE breakpoints        = Qnil;
static VALUE catchpoint         = Qnil;
static VALUE tracing            = Qfalse;
static VALUE locker             = Qnil;
static VALUE post_mortem        = Qfalse;
static VALUE keep_frame_binding = Qfalse;

static VALUE last_context = Qnil;
static VALUE last_thread  = Qnil;
static debug_context_t *last_debug_context = NULL;

static VALUE mDebugger;
static VALUE cThreadsTable;
static VALUE cContext;
static VALUE cBreakpoint;

static VALUE rb_mObjectSpace;

static ID idAtLine;
static ID idAtBreakpoint;
static ID idAtCatchpoint;
static ID idAtTracing;
static ID idEval;
static ID idList;

static int start_count = 0;
static int thnum_max = 0;
static int bkp_count = 0;
static int last_debugged_thnum = -1;
static unsigned long last_check = 0;
static unsigned long hook_count = 0;

static VALUE create_binding(VALUE);
static VALUE debug_stop(VALUE);
static void save_current_position(debug_context_t *);
static VALUE context_copy_locals(debug_frame_t *);
static void context_suspend_0(debug_context_t *);
static void context_resume_0(debug_context_t *);

typedef struct locked_thread_t { 
    VALUE thread_id;
    struct locked_thread_t *next;
} locked_thread_t;

static locked_thread_t *locked_head = NULL;
static locked_thread_t *locked_tail = NULL;

inline static void *
ruby_method_ptr(VALUE class, ID meth_id)
{
    NODE *body, *method;
    st_lookup(RCLASS(class)->m_tbl, meth_id, (st_data_t *)&body);
    method       = (NODE *)body->u2.value;
    return (void *)method->u1.value;
}

inline static VALUE
ref2id(VALUE obj)
{
    return rb_obj_id(obj);
}

static VALUE
id2ref_unprotected(VALUE id)
{
    typedef VALUE (*id2ref_func_t)(VALUE, VALUE);
    static id2ref_func_t f_id2ref = NULL;
    if(f_id2ref == NULL)
    {
        f_id2ref = (id2ref_func_t)ruby_method_ptr(rb_mObjectSpace, rb_intern("_id2ref"));
    }
    return f_id2ref(rb_mObjectSpace, id);
}

static VALUE
id2ref_error()
{
    rb_p(ruby_errinfo);
    return Qnil;
}

static VALUE
id2ref(VALUE id)
{
    return rb_rescue(id2ref_unprotected, id, id2ref_error, 0);
}

inline static VALUE
context_thread_0(debug_context_t *debug_context)
{
    return id2ref(debug_context->thread_id);
}

static int
is_in_locked(VALUE thread_id)
{
    locked_thread_t *node;
    
    if(!locked_head)
        return 0;
    
    for(node = locked_head; node != locked_tail; node = node->next)
    {
        if(node->thread_id == thread_id) return 1;
    }
    return 0;
}

static void
add_to_locked(VALUE thread)
{
    locked_thread_t *node;
    VALUE thread_id = ref2id(thread);
    
    if(is_in_locked(thread_id))
        return;

    node = ALLOC(locked_thread_t);
    node->thread_id = thread_id;
    node->next = NULL;
    if(locked_tail)
        locked_tail->next = node;
    locked_tail = node;
    if(!locked_head)
        locked_head = node;
}

static VALUE
remove_from_locked()
{
    VALUE thread;
    locked_thread_t *node;
    
    if(locked_head == NULL)
        return Qnil;
    node = locked_head;
    locked_head = locked_head->next;
    if(locked_tail == node)
        locked_tail = NULL;
    thread = id2ref(node->thread_id);
    xfree(node);
    return thread;
}

static int
threads_table_mark_keyvalue(VALUE key, VALUE value, int dummy)
{
    rb_gc_mark(value);
    return ST_CONTINUE;
}

static void
threads_table_mark(void* data)
{
    threads_table_t *threads_table = (threads_table_t*)data;
    st_foreach(threads_table->tbl, threads_table_mark_keyvalue, 0);
}

static void
threads_table_free(void* data)
{
    threads_table_t *threads_table = (threads_table_t*)data;
    st_free_table(threads_table->tbl);
    xfree(threads_table);
}

static VALUE
threads_table_create()
{
    threads_table_t *threads_table;
    
    threads_table = ALLOC(threads_table_t);
    threads_table->tbl = st_init_numtable();
    return Data_Wrap_Struct(cThreadsTable, threads_table_mark, threads_table_free, threads_table);
}

static int
threads_table_clear_i(VALUE key, VALUE value, VALUE dummy)
{
    return ST_DELETE;
}

static void
threads_table_clear(VALUE table)
{
    threads_table_t *threads_table;
    
    Data_Get_Struct(table, threads_table_t, threads_table);
    st_foreach(threads_table->tbl, threads_table_clear_i, 0);
}

static VALUE
is_thread_alive(VALUE thread)
{
    typedef VALUE (*thread_alive_func_t)(VALUE);
    static thread_alive_func_t f_thread_alive = NULL;
    if(!f_thread_alive)
    {
        f_thread_alive = (thread_alive_func_t)ruby_method_ptr(rb_cThread, rb_intern("alive?"));
    }
    return f_thread_alive(thread);
}

static int
threads_table_check_i(VALUE key, VALUE value, VALUE dummy)
{
    VALUE thread;
    
    thread = id2ref(key);
    if(!rb_obj_is_kind_of(thread, rb_cThread))
    {
        return ST_DELETE;
    }
    if(rb_protect(is_thread_alive, thread, 0) != Qtrue)
    {
        return ST_DELETE;
    }
    return ST_CONTINUE;
}

static void
check_thread_contexts()
{
    threads_table_t *threads_table;

    Data_Get_Struct(threads_tbl, threads_table_t, threads_table);
    st_foreach(threads_table->tbl, threads_table_check_i, 0);
}

/*
 *   call-seq:
 *      Debugger.started? -> bool
 *   
 *   Returns +true+ the debugger is started.
 */
static VALUE
debug_is_started(VALUE self)
{
    return IS_STARTED ? Qtrue : Qfalse;
}

static void
debug_check_started()
{
    if(!IS_STARTED)
    {
        rb_raise(rb_eRuntimeError, "Debugger.start is not called yet.");
    }
}

static void
debug_context_mark(void *data)
{
    debug_frame_t *frame;
    int i;
    
    debug_context_t *debug_context = (debug_context_t *)data;
    for(i = 0; i < debug_context->stack_size; i++)
    {
	frame = &(debug_context->frames[i]);
	rb_gc_mark(frame->binding);
	rb_gc_mark(frame->self);
	if(frame->dead)
	{
	    rb_gc_mark(frame->info.copy.locals);
	}
    }
}

static void
debug_context_free(void *data)
{
    debug_context_t *debug_context = (debug_context_t *)data;
    xfree(debug_context->frames);
}

static VALUE
debug_context_create(VALUE thread)
{
    debug_context_t *debug_context;
    
    debug_context = ALLOC(debug_context_t);
    debug_context-> thnum = ++thnum_max;
    
    debug_context->last_file = NULL;
    debug_context->last_line = 0;
    debug_context->flags = 0;

    debug_context->stop_next = -1;
    debug_context->dest_frame = -1;
    debug_context->stop_line = -1;
    debug_context->stop_frame = -1;
    debug_context->stack_len = STACK_SIZE_INCREMENT;
    debug_context->frames = ALLOC_N(debug_frame_t, STACK_SIZE_INCREMENT);
    debug_context->stack_size = 0;
    debug_context->thread_id = ref2id(thread);
    return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, debug_context);
}

static VALUE
debug_context_dup(debug_context_t *debug_context)
{
    debug_context_t *new_debug_context;
    debug_frame_t *new_frame, *old_frame;
    int i;

    new_debug_context = ALLOC(debug_context_t);
    memcpy(new_debug_context, debug_context, sizeof(debug_context_t));
    new_debug_context->stop_next = -1;
    new_debug_context->dest_frame = -1;
    new_debug_context->stop_line = -1;
    new_debug_context->stop_frame = -1;
    CTX_FL_SET(new_debug_context, CTX_FL_DEAD);
    new_debug_context->frames = ALLOC_N(debug_frame_t, debug_context->stack_size);
    new_debug_context->stack_len = debug_context->stack_size;
    memcpy(new_debug_context->frames, debug_context->frames, sizeof(debug_frame_t) * debug_context->stack_size);
    for(i = 0; i < debug_context->stack_size; i++)
    {
	new_frame = &(new_debug_context->frames[i]);
	old_frame = &(debug_context->frames[i]);
	new_frame->dead = 1;
	new_frame->info.copy.locals = context_copy_locals(old_frame);
    }
    return Data_Wrap_Struct(cContext, debug_context_mark, debug_context_free, new_debug_context);
}

static void
thread_context_lookup(VALUE thread, VALUE *context, debug_context_t **debug_context)
{
    threads_table_t *threads_table;
    VALUE thread_id;
    debug_context_t *l_debug_context;

    debug_check_started();

    if(last_thread == thread && last_context != Qnil)
    {
        *context = last_context;
        if(debug_context)
            *debug_context = last_debug_context;
        return;
    }
    thread_id = ref2id(thread);
    Data_Get_Struct(threads_tbl, threads_table_t, threads_table);
    if(!st_lookup(threads_table->tbl, thread_id, context))
    {
        *context = debug_context_create(thread);
        st_insert(threads_table->tbl, thread_id, *context);
    }
    
    Data_Get_Struct(*context, debug_context_t, l_debug_context);
    if(debug_context)
        *debug_context = l_debug_context;

    last_thread = thread;
    last_context = *context;
    last_debug_context = l_debug_context;
}

static VALUE
call_at_line_unprotected(VALUE args)
{
    VALUE context;
    context = *RARRAY(args)->ptr;
    return rb_funcall2(context, idAtLine, RARRAY(args)->len - 1, RARRAY(args)->ptr + 1);
}

static VALUE
call_at_line(VALUE context, debug_context_t *debug_context, VALUE file, VALUE line)
{
    VALUE args;
    
    last_debugged_thnum = debug_context->thnum;
    save_current_position(debug_context);
    
    args = rb_ary_new3(3, context, file, line);
    return rb_protect(call_at_line_unprotected, args, 0);
}

static void
save_call_frame(VALUE self, char *file, int line, ID mid, debug_context_t *debug_context)
{
    VALUE binding;
    debug_frame_t *debug_frame;
    int frame_n;
    
    binding = self && RTEST(keep_frame_binding)? create_binding(self) : Qnil;
    
    frame_n = debug_context->stack_size++;
    if(frame_n >= debug_context->stack_len)
    {
	debug_context->stack_len += STACK_SIZE_INCREMENT;
	debug_context->frames = REALLOC_N(debug_context->frames, debug_frame_t, debug_context->stack_len);
    }
    debug_frame = &debug_context->frames[frame_n];
    debug_frame->file = file;
    debug_frame->line = line;
    debug_frame->binding = binding;
    debug_frame->id = mid;
    debug_frame->dead = 0;
    debug_frame->self = self;
    debug_frame->info.runtime.frame = ruby_frame;
    debug_frame->info.runtime.scope = ruby_scope;
    
    debug_frame->info.runtime.dyna_vars = ruby_dyna_vars;
}

#if defined DOSISH
#define isdirsep(x) ((x) == '/' || (x) == '\\')
#else
#define isdirsep(x) ((x) == '/')
#endif

static int
filename_cmp(VALUE source, char *file)
{
    char *source_ptr, *file_ptr;
    int s_len, f_len, min_len;
    int s,f;
    int dirsep_flag = 0;
    
    s_len = RSTRING(source)->len;
    f_len = strlen(file);
    min_len = min(s_len, f_len);
    
    source_ptr = RSTRING(source)->ptr;
    file_ptr   = file;
    
    for( s = s_len - 1, f = f_len - 1; s >= s_len - min_len && f >= f_len - min_len; s--, f-- )
    {
        if((source_ptr[s] == '.' || file_ptr[f] == '.') && dirsep_flag)
            return 1;
        if(source_ptr[s] != file_ptr[f])
            return 0;
        if(isdirsep(source_ptr[s]))
            dirsep_flag = 1;
    }
    return 1;
}

static int
check_breakpoints_by_pos(debug_context_t *debug_context, char *file, int line)
{
    VALUE breakpoint; 
    debug_breakpoint_t *debug_breakpoint;
    int i;

    if(RARRAY(breakpoints)->len == 0)
        return -1;
    if(!CTX_FL_TEST(debug_context, CTX_FL_MOVED))
        return -1;
    
    for(i = 0; i < RARRAY(breakpoints)->len; i++)
    {
        breakpoint = rb_ary_entry(breakpoints, i);
        Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
        if(debug_breakpoint->type != BP_POS_TYPE)
            continue;
        if(debug_breakpoint->pos.line != line)
            continue;
        if(filename_cmp(debug_breakpoint->source, file))
            return i;
    }
    return -1;
}

inline static int
classname_cmp(VALUE name, VALUE klass)
{
    return (klass != Qnil && rb_str_cmp(name, rb_mod_name(klass)) == 0);
}

static int
check_breakpoints_by_method(debug_context_t *debug_context, VALUE klass, ID mid)
{
    VALUE breakpoint; 
    debug_breakpoint_t *debug_breakpoint;
    int i;
    
    if(RARRAY(breakpoints)->len == 0)
        return -1;
    if(!CTX_FL_TEST(debug_context, CTX_FL_MOVED))
        return -1;
    for(i = 0; i < RARRAY(breakpoints)->len; i++)
    {
        breakpoint = rb_ary_entry(breakpoints, i);
        Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
        if(debug_breakpoint->type != BP_METHOD_TYPE)
            continue;
        if(debug_breakpoint->pos.mid != mid)
            continue;
        if(classname_cmp(debug_breakpoint->source, klass))
            return i;
    }
    return -1;
}

/*
 * This is a NASTY HACK. For some reasons rb_f_binding is decalred 
 * static in eval.c
 */
static VALUE
create_binding(VALUE self)
{
    typedef VALUE (*bind_func_t)(VALUE);
    static bind_func_t f_binding = NULL;

    if(f_binding == NULL)
    {
        f_binding = (bind_func_t)ruby_method_ptr(rb_mKernel, rb_intern("binding"));
    }
    return f_binding(self);
}

static VALUE
get_breakpoint_at(int index) 
{
    return rb_ary_entry(breakpoints, index);
}

static VALUE
eval_expression(VALUE args)
{
    return rb_funcall2(rb_mKernel, idEval, 2, RARRAY(args)->ptr);
}

inline static int
check_breakpoint_expression(VALUE breakpoint, VALUE binding)
{
    debug_breakpoint_t *debug_breakpoint;
    VALUE args, expr_result;
    
    Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
    if(NIL_P(debug_breakpoint->expr))
        return 1;
    
    args = rb_ary_new3(2, debug_breakpoint->expr, binding);
    expr_result = rb_protect(eval_expression, args, 0);
    return RTEST(expr_result);
}

inline static debug_frame_t *
get_top_frame(debug_context_t *debug_context)
{
    if(debug_context->stack_size == 0)
        return NULL;
    else 
	return &(debug_context->frames[debug_context->stack_size-1]);
}

inline static void
save_top_binding(debug_context_t *debug_context, VALUE binding)
{
    debug_frame_t *debug_frame;
    debug_frame = get_top_frame(debug_context);
    if(debug_frame)
        debug_frame->binding = binding;
}

inline static void
set_frame_source(debug_context_t *debug_context, char *file, int line)
{
    debug_frame_t *top_frame;
    top_frame = get_top_frame(debug_context);
    if(top_frame)
    {
        top_frame->file = file;
        top_frame->line = line;
    }
}

inline static void 
set_dyna_vars(debug_context_t *debug_context)
{
    debug_frame_t *top_frame;
    top_frame = get_top_frame(debug_context);
    if(top_frame)
    {
        top_frame->info.runtime.dyna_vars = ruby_dyna_vars;
    }
}

static void
save_current_position(debug_context_t *debug_context)
{
    debug_frame_t *debug_frame;
    
    debug_frame = get_top_frame(debug_context);
    if(!debug_frame) return;
    debug_context->last_file = debug_frame->file;
    debug_context->last_line = debug_frame->line;
    CTX_FL_UNSET(debug_context, CTX_FL_MOVED);
}

static char *
get_event_name(rb_event_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 void
debug_event_hook(rb_event_t event, NODE *node, VALUE self, ID mid, VALUE klass)
{
    VALUE thread, context, breakpoint;
    VALUE binding = Qnil;
    debug_context_t *debug_context;
    char *file;
    int line;
    int breakpoint_index = -1;
    
    hook_count++;
    
    if (mid == ID_ALLOCATOR) return;

    thread = rb_thread_current();
    thread_context_lookup(thread, &context, &debug_context);
    
    /* return if thread is marked as 'ignored'.
       debugger's threads are marked this way
    */
    if(CTX_FL_TEST(debug_context, CTX_FL_IGNORE)) return;
    
    while(1)
    {
        /* halt execution of the current thread if the debugger
           is activated in another
        */
        while(locker != Qnil && locker != thread)
        {
            add_to_locked(thread);
            rb_thread_stop();
        }

        /* stop the current thread if it's marked as suspended */
        if(CTX_FL_TEST(debug_context, CTX_FL_SUSPEND))
            rb_thread_stop();
        else break;
    }
    
    /* return if the current thread is the locker */
    if(locker != Qnil) return;
    
    /* only the current thread can proceed */
    locker = thread;

    /* ignore a skipped section of code */
    if(CTX_FL_TEST(debug_context, CTX_FL_SKIPPED)) goto cleanup;

    if(!node && !(event == RUBY_EVENT_RETURN || event == RUBY_EVENT_C_RETURN)) 
      goto cleanup;
    
    if(node)
    {
      file = node->nd_file;
      line = nd_line(node);
    }

    if(DID_MOVED)
        CTX_FL_SET(debug_context, CTX_FL_MOVED);

    switch(event)
    {
    case RUBY_EVENT_LINE:
    {
        set_frame_source(debug_context, file, line);
	set_dyna_vars(debug_context);

        if(RTEST(tracing) || CTX_FL_TEST(debug_context, CTX_FL_TRACING))
            rb_funcall(context, idAtTracing, 2, rb_str_new2(file), INT2FIX(line));
        
        if(debug_context->dest_frame == -1 || 
            debug_context->stack_size == debug_context->dest_frame)
        {
            debug_context->stop_next--;
            if(debug_context->stop_next < 0)
                debug_context->stop_next = -1;
            /* we check that we actualy moved to another line */
            if(DID_MOVED)
                debug_context->stop_line--;
        }
        else if(debug_context->stack_size < debug_context->dest_frame)
        {
            debug_context->stop_next = 0;
        }

        if(debug_context->stack_size == 0)
            save_call_frame(self, file, line, mid, debug_context);

        if(debug_context->stop_next == 0 || debug_context->stop_line == 0 ||
            (breakpoint_index = check_breakpoints_by_pos(debug_context, file, line)) != -1)
        {
            binding = self? create_binding(self) : Qnil;
            /* check breakpoint expression */
            if(breakpoint_index != -1)
            {
                breakpoint = get_breakpoint_at(breakpoint_index);
                if(check_breakpoint_expression(breakpoint, binding))
                    rb_funcall(context, idAtBreakpoint, 1, breakpoint);
                else
                    break;
            }

            /* reset all pointers */
            debug_context->dest_frame = -1;
            debug_context->stop_line = -1;
            debug_context->stop_next = -1;

            save_top_binding(debug_context, binding);
            call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
        }
        break;
    }
    case RUBY_EVENT_C_CALL:
    {
        set_frame_source(debug_context, file, line);
        break;
    }
    case RUBY_EVENT_CALL:
    {
        save_call_frame(self, file, line, mid, debug_context);
        breakpoint_index = check_breakpoints_by_method(debug_context, klass, mid);
        if(breakpoint_index != -1)
        {
            debug_frame_t *debug_frame;
            debug_frame = get_top_frame(debug_context);
            if(debug_frame)
                binding = debug_frame->binding;
            if(NIL_P(binding) && self)
                binding = create_binding(self);
            breakpoint = get_breakpoint_at(breakpoint_index);
            if(check_breakpoint_expression(breakpoint, binding))
            {
                save_top_binding(debug_context, binding);
                rb_funcall(context, idAtBreakpoint, 1, breakpoint);
                call_at_line(context, debug_context, rb_str_new2(file), INT2FIX(line));
            }
        }
        break;
    }
    case RUBY_EVENT_RETURN:
    case RUBY_EVENT_END:
    {
        if(debug_context->stack_size == debug_context->stop_frame)
        {
            debug_context->stop_next = 1;
            debug_context->stop_frame = 0;
        }
	if(debug_context->stack_size > 0)
	  debug_context->stack_size--;
        break;
    }
    case RUBY_EVENT_CLASS:
    {
        save_call_frame(self, file, line, mid, debug_context);
        break;
    }
    case RUBY_EVENT_RAISE:
{'fraction_non_alphanumeric': 0.10500428185591006, 'fraction_numerical': 0.0061043894512636965, 'mean_word_length': 3.128921124206709, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 289, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3550590', 'n_tokens_mistral': 16255, 'n_tokens_neox': 15852, 'n_words': 3185}