#include "ruby_debug.h"
VALUE rdebug_breakpoints = Qnil;
VALUE rdebug_catchpoints;
static VALUE cBreakpoint;
static ID idEval;
static VALUE
eval_expression(VALUE args)
{
return rb_funcall2(rb_mKernel, idEval, 2, RARRAY(args)->ptr);
}
int
check_breakpoint_hit_condition(VALUE breakpoint)
{
debug_breakpoint_t *debug_breakpoint;
if(breakpoint == Qnil)
return 0;
Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
debug_breakpoint->hit_count++;
if (!Qtrue == debug_breakpoint->enabled) return 0;
switch(debug_breakpoint->hit_condition)
{
case HIT_COND_NONE:
return 1;
case HIT_COND_GE:
{
if(debug_breakpoint->hit_count >= debug_breakpoint->hit_value)
return 1;
break;
}
case HIT_COND_EQ:
{
if(debug_breakpoint->hit_count == debug_breakpoint->hit_value)
return 1;
break;
}
case HIT_COND_MOD:
{
if(debug_breakpoint->hit_count % debug_breakpoint->hit_value == 0)
return 1;
break;
}
}
return 0;
}
static int
check_breakpoint_by_pos(VALUE breakpoint, char *file, int line)
{
debug_breakpoint_t *debug_breakpoint;
if(breakpoint == Qnil)
return 0;
Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
if (!Qtrue == debug_breakpoint->enabled) return 0;
if(debug_breakpoint->type != BP_POS_TYPE)
return 0;
if(debug_breakpoint->pos.line != line)
return 0;
if(filename_cmp(debug_breakpoint->source, file))
return 1;
return 0;
}
int
check_breakpoint_by_method(VALUE breakpoint, VALUE klass, ID mid, VALUE self)
{
debug_breakpoint_t *debug_breakpoint;
if(breakpoint == Qnil)
return 0;
Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
if (!Qtrue == debug_breakpoint->enabled) return 0;
if(debug_breakpoint->type != BP_METHOD_TYPE)
return 0;
if(debug_breakpoint->pos.mid != mid)
return 0;
if(classname_cmp(debug_breakpoint->source, klass))
return 1;
if ((rb_type(self) == T_CLASS) &&
classname_cmp(debug_breakpoint->source, self))
return 1;
return 0;
}
VALUE
check_breakpoints_by_pos(debug_context_t *debug_context, char *file, int line)
{
VALUE breakpoint;
int i;
if(!CTX_FL_TEST(debug_context, CTX_FL_ENABLE_BKPT))
return Qnil;
if(check_breakpoint_by_pos(debug_context->breakpoint, file, line))
return debug_context->breakpoint;
if(RARRAY(rdebug_breakpoints)->len == 0)
return Qnil;
for(i = 0; i < RARRAY(rdebug_breakpoints)->len; i++)
{
breakpoint = rb_ary_entry(rdebug_breakpoints, i);
if(check_breakpoint_by_pos(breakpoint, file, line))
return breakpoint;
}
return Qnil;
}
VALUE
check_breakpoints_by_method(debug_context_t *debug_context, VALUE klass, ID mid, VALUE self)
{
VALUE breakpoint;
int i;
if(!CTX_FL_TEST(debug_context, CTX_FL_ENABLE_BKPT))
return Qnil;
if(check_breakpoint_by_method(debug_context->breakpoint, klass, mid, self))
return debug_context->breakpoint;
if(RARRAY(rdebug_breakpoints)->len == 0)
return Qnil;
for(i = 0; i < RARRAY(rdebug_breakpoints)->len; i++)
{
breakpoint = rb_ary_entry(rdebug_breakpoints, i);
if(check_breakpoint_by_method(breakpoint, klass, mid, self))
return breakpoint;
}
return Qnil;
}
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);
}
static void
breakpoint_mark(void *data)
{
debug_breakpoint_t *breakpoint;
breakpoint = (debug_breakpoint_t *)data;
rb_gc_mark(breakpoint->source);
rb_gc_mark(breakpoint->expr);
}
VALUE
create_breakpoint_from_args(int argc, VALUE *argv, int id)
{
VALUE source, pos, expr;
debug_breakpoint_t *breakpoint;
int type;
if(rb_scan_args(argc, argv, "21", &source, &pos, &expr) == 2)
{
expr = Qnil;
}
type = FIXNUM_P(pos) ? BP_POS_TYPE : BP_METHOD_TYPE;
if(type == BP_POS_TYPE)
source = StringValue(source);
else
pos = StringValue(pos);
breakpoint = ALLOC(debug_breakpoint_t);
breakpoint->id = id;
breakpoint->source = source;
breakpoint->type = type;
if(type == BP_POS_TYPE)
breakpoint->pos.line = FIX2INT(pos);
else
breakpoint->pos.mid = rb_intern(RSTRING(pos)->ptr);
breakpoint->enabled = Qtrue;
breakpoint->expr = NIL_P(expr) ? expr: StringValue(expr);
breakpoint->hit_count = 0;
breakpoint->hit_value = 0;
breakpoint->hit_condition = HIT_COND_NONE;
return Data_Wrap_Struct(cBreakpoint, breakpoint_mark, xfree, breakpoint);
}
/*
* call-seq:
* Debugger.remove_breakpoint(id) -> breakpoint
*
* Removes breakpoint by its id.
* id is an identificator of a breakpoint.
*/
VALUE
rdebug_remove_breakpoint(VALUE self, VALUE id_value)
{
int i;
int id;
VALUE breakpoint;
debug_breakpoint_t *debug_breakpoint;
id = FIX2INT(id_value);
for( i = 0; i < RARRAY(rdebug_breakpoints)->len; i += 1 )
{
breakpoint = rb_ary_entry(rdebug_breakpoints, i);
Data_Get_Struct(breakpoint, debug_breakpoint_t, debug_breakpoint);
if(debug_breakpoint->id == id)
{
rb_ary_delete_at(rdebug_breakpoints, i);
return breakpoint;
}
}
return Qnil;
}
/*
* call-seq:
* Debugger.catchpoints -> hash
*
* Returns a current catchpoints, which is a hash exception names that will
* trigger a debugger when raised. The values are the number of times taht
* catchpoint was hit, initially 0.
*/
VALUE
debug_catchpoints(VALUE self)
{
debug_check_started();
return rdebug_catchpoints;
}
/*
* call-seq:
* Debugger.catchpoint(string) -> string
*
* Sets catchpoint. Returns the string passed.
*/
VALUE
rdebug_add_catchpoint(VALUE self, VALUE value)
{
debug_check_started();
if (TYPE(value) != T_STRING) {
rb_raise(rb_eTypeError, "value of a catchpoint must be String");
}
rb_hash_aset(rdebug_catchpoints, rb_str_dup(value), INT2FIX(0));
return value;
}
/*
* call-seq:
* context.breakpoint -> Breakpoint
*
* Returns a context-specific temporary Breakpoint object.
*/
VALUE
context_breakpoint(VALUE self)
{
debug_context_t *debug_context;
debug_check_started();
Data_Get_Struct(self, debug_context_t, debug_context);
return debug_context->breakpoint;
}
/*
* call-seq:
* context.set_breakpoint(source, pos, condition = nil) -> breakpoint
*
* Sets a context-specific temporary breakpoint, which can be used to implement
* 'Run to Cursor' debugger function. When this breakpoint is reached, it will be
* cleared out.
*
* source is a name of a file or a class.
* pos is a line number or a method name if source is a class name.
* condition is a string which is evaluated to +true+ when this breakpoint
* is activated.
*/
VALUE
context_set_breakpoint(int argc, VALUE *argv, VALUE self)
{
VALUE result;
debug_context_t *debug_context;
debug_check_started();
Data_Get_Struct(self, debug_context_t, debug_context);
result = create_breakpoint_from_args(argc, argv, 0);
debug_context->breakpoint = result;
return result;
}
/*
* call-seq:
* breakpoint.enabled?
*
* Returns whether breakpoint is enabled or not.
*/
static VALUE
breakpoint_enabled(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return breakpoint->enabled;
}
/*
* call-seq:
* breakpoint.enabled = bool
*
* Enables or disables breakpoint.
*/
static VALUE
breakpoint_set_enabled(VALUE self, VALUE bool)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return breakpoint->enabled = bool;
}
/*
* call-seq:
* breakpoint.source -> string
*
* Returns a source of the breakpoint.
*/
static VALUE
breakpoint_source(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return breakpoint->source;
}
/*
* call-seq:
* breakpoint.source = string
*
* Sets the source of the breakpoint.
*/
static VALUE
breakpoint_set_source(VALUE self, VALUE value)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
breakpoint->source = StringValue(value);
return value;
}
/*
* call-seq:
* breakpoint.pos -> string or int
*
* Returns the position of this breakpoint.
*/
static VALUE
breakpoint_pos(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
if(breakpoint->type == BP_METHOD_TYPE)
return rb_str_new2(rb_id2name(breakpoint->pos.mid));
else
return INT2FIX(breakpoint->pos.line);
}
/*
* call-seq:
* breakpoint.pos = string or int
*
* Sets the position of this breakpoint.
*/
static VALUE
breakpoint_set_pos(VALUE self, VALUE value)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
if(breakpoint->type == BP_METHOD_TYPE)
{
breakpoint->pos.mid = rb_to_id(StringValue(value));
}
else
breakpoint->pos.line = FIX2INT(value);
return value;
}
/*
* call-seq:
* breakpoint.expr -> string
*
* Returns a codition expression when this breakpoint should be activated.
*/
static VALUE
breakpoint_expr(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return breakpoint->expr;
}
/*
* call-seq:
* breakpoint.expr = string | nil
*
* Sets the codition expression when this breakpoint should be activated.
*/
static VALUE
breakpoint_set_expr(VALUE self, VALUE expr)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
breakpoint->expr = NIL_P(expr) ? expr: StringValue(expr);
return expr;
}
/*
* call-seq:
* breakpoint.id -> int
*
* Returns id of the breakpoint.
*/
static VALUE
breakpoint_id(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return INT2FIX(breakpoint->id);
}
/*
* call-seq:
* breakpoint.hit_count -> int
*
* Returns the hit count of the breakpoint.
*/
static VALUE
breakpoint_hit_count(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return INT2FIX(breakpoint->hit_count);
}
/*
* call-seq:
* breakpoint.hit_value -> int
*
* Returns the hit value of the breakpoint.
*/
static VALUE
breakpoint_hit_value(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
return INT2FIX(breakpoint->hit_value);
}
/*
* call-seq:
* breakpoint.hit_value = int
*
* Sets the hit value of the breakpoint.
*/
static VALUE
breakpoint_set_hit_value(VALUE self, VALUE value)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
breakpoint->hit_value = FIX2INT(value);
return value;
}
/*
* call-seq:
* breakpoint.hit_condition -> symbol
*
* Returns the hit condition of the breakpoint:
*
* +nil+ if it is an unconditional breakpoint, or
* :greater_or_equal, :equal, :modulo
*/
static VALUE
breakpoint_hit_condition(VALUE self)
{
debug_breakpoint_t *breakpoint;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
switch(breakpoint->hit_condition)
{
case HIT_COND_GE:
return ID2SYM(rb_intern("greater_or_equal"));
case HIT_COND_EQ:
return ID2SYM(rb_intern("equal"));
case HIT_COND_MOD:
return ID2SYM(rb_intern("modulo"));
case HIT_COND_NONE:
default:
return Qnil;
}
}
/*
* call-seq:
* breakpoint.hit_condition = symbol
*
* Sets the hit condition of the breakpoint which must be one of the following values:
*
* +nil+ if it is an unconditional breakpoint, or
* :greater_or_equal(:ge), :equal(:eq), :modulo(:mod)
*/
static VALUE
breakpoint_set_hit_condition(VALUE self, VALUE value)
{
debug_breakpoint_t *breakpoint;
ID id_value;
Data_Get_Struct(self, debug_breakpoint_t, breakpoint);
id_value = rb_to_id(value);
if(rb_intern("greater_or_equal") == id_value || rb_intern("ge") == id_value)
breakpoint->hit_condition = HIT_COND_GE;
else if(rb_intern("equal") == id_value || rb_intern("eq") == id_value)
breakpoint->hit_condition = HIT_COND_EQ;
else if(rb_intern("modulo") == id_value || rb_intern("mod") == id_value)
breakpoint->hit_condition = HIT_COND_MOD;
else
rb_raise(rb_eArgError, "Invalid condition parameter");
return value;
}
/*
* Document-class: Breakpoint
*
* == Summary
*
* This class represents a breakpoint. It defines position of the breakpoint and
* condition when this breakpoint should be triggered.
*/
void
Init_breakpoint()
{
cBreakpoint = rb_define_class_under(mDebugger, "Breakpoint", rb_cObject);
rb_define_method(cBreakpoint, "enabled=", breakpoint_set_enabled, 1);
rb_define_method(cBreakpoint, "enabled?", breakpoint_enabled, 0);
rb_define_method(cBreakpoint, "expr", breakpoint_expr, 0);
rb_define_method(cBreakpoint, "expr=", breakpoint_set_expr, 1);
rb_define_method(cBreakpoint, "hit_condition", breakpoint_hit_condition, 0);
rb_define_method(cBreakpoint, "hit_condition=", breakpoint_set_hit_condition, 1);
rb_define_method(cBreakpoint, "hit_count", breakpoint_hit_count, 0);
rb_define_method(cBreakpoint, "hit_value", breakpoint_hit_value, 0);
rb_define_method(cBreakpoint, "hit_value=", breakpoint_set_hit_value, 1);
rb_define_method(cBreakpoint, "id", breakpoint_id, 0);
rb_define_method(cBreakpoint, "pos", breakpoint_pos, 0);
rb_define_method(cBreakpoint, "pos=", breakpoint_set_pos, 1);
rb_define_method(cBreakpoint, "source", breakpoint_source, 0);
rb_define_method(cBreakpoint, "source=", breakpoint_set_source, 1);
idEval = rb_intern("eval");
rdebug_catchpoints = rb_hash_new();
}