#include #include #include "ruby_debug.h" VALUE rdebug_breakpoints = Qnil; VALUE rdebug_catchpoints; static VALUE cBreakpoint; static VALUE eval_expression(VALUE args) { return rb_funcall2(rb_mKernel, rb_intern("eval"), 2, RARRAY_PTR(args)); } 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; } #if defined DOSISH #define isdirsep(x) ((x) == '/' || (x) == '\\') #else #define isdirsep(x) ((x) == '/') #endif #ifndef min #define min(x,y) ((x) < (y) ? (x) : (y)) #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_LEN(source); f_len = strlen(file); min_len = min(s_len, f_len); source_ptr = RSTRING_PTR(source); 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(isdirsep(source_ptr[s]) && isdirsep(file_ptr[f])) dirsep_flag = 1; #ifdef DOSISH_DRIVE_LETTER else if (s == 0) return(toupper(source_ptr[s]) == toupper(file_ptr[f])); #endif else if(source_ptr[s] != file_ptr[f]) return 0; } return 1; } static int check_breakpoint_by_pos(VALUE breakpoint, VALUE file, VALUE 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 != FIX2INT(line)) return 0; if(filename_cmp(debug_breakpoint->source, RSTRING_PTR(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, VALUE file, VALUE line) { VALUE breakpoint; int i; if (!CTX_FL_TEST(debug_context, CTX_FL_ENABLE_BKPT)) { if (0) fprintf(stderr, "check_breakpoints_by_pos: !CTX_FL_ENABLE_BKPT\n"); return Qnil; } if (check_breakpoint_by_pos(debug_context->breakpoint, file, line)) { return debug_context->breakpoint; } if (RARRAY_LEN(rdebug_breakpoints) == 0) { return Qnil; } for (i = 0; i < RARRAY_LEN(rdebug_breakpoints); 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_LEN(rdebug_breakpoints) == 0) return Qnil; for(i = 0; i < RARRAY_LEN(rdebug_breakpoints); 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_PTR(pos)); } 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_LEN(rdebug_breakpoints); 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); rdebug_catchpoints = rb_hash_new(); }