diff -r b6091735f3f5 libinterp/corefcn/debug.cc --- a/libinterp/corefcn/debug.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/corefcn/debug.cc Sun Dec 06 20:50:02 2015 +1100 @@ -52,7 +52,9 @@ along with Octave; see the file COPYING. #include "pt-pr-code.h" #include "pt-bp.h" #include "pt-eval.h" +#include "pt-exp.h" #include "pt-stmt.h" +#include "sighandlers.h" #include "toplev.h" #include "unwind-prot.h" #include "utils.h" @@ -63,6 +65,11 @@ along with Octave; see the file COPYING. // Initialize the singleton object bp_table *bp_table::instance = 0; +std::set bp_table::errors_that_stop; +std::set bp_table::caught_that_stop; +std::set bp_table::warnings_that_stop; + +// Read entire file called fname and return the contents as a string static std::string snarf_file (const std::string& fname) { @@ -192,79 +199,244 @@ get_user_code (const std::string& fname return dbg_fcn; } -static void +#ifdef DBSTOP_NANINF +#include "sigfpe.cc" +#endif + +enum +dbstop_args {dbstop_in, dbstop_at, dbstop_if, dbstop_none}; + +// Parse parameters (args) of dbstop and dbclear commands. +// For dbstop, who=="dbstop"; for dbclear, who=="dbclear". +// The syntax is dbstop [[in] symbol] [[at] line [line [...]]] [if condition] +// where the form of condition depends on whether or not a file or line has +// been seen. +// Also execute "if [error|warning|interrupt|naninf]" clauses +void parse_dbfunction_params (const char *who, const octave_value_list& args, - std::string& symbol_name, bp_table::intmap& lines) + std::string& symbol_name, bp_table::intmap& lines, + std::string& cond) { int nargin = args.length (); - int idx = 0; int list_idx = 0; + symbol_name = std::string (); lines = bp_table::intmap (); - if (args.length () == 0) - return; - - if (args(0).is_string ()) + if (args.length () == 0 || !args(0).is_string ()) + print_usage (who); + // elements already processed + bool seen_in = false, seen_at = false, seen_if = false; + int pos = 0; + dbstop_args token = dbstop_none; + while (pos < nargin) { - // string could be function name or line number - int isint = atoi (args(0).string_value ().c_str ()); - - if (isint == 0) + // allow "in" and "at" to be implicit + if (args(pos).is_string ()) { - // It was a function name - symbol_name = args(0).string_value (); - - idx = 1; + std::string arg = args(pos).string_value (); + if (arg == "in") + { + token = dbstop_in; + pos++; + } + else if (arg == "at") + { + token = dbstop_at; + pos++; + } + else if (arg == "if") + { + token = dbstop_if; + pos++; + } + else if (atoi (args(pos).string_value ().c_str ()) > 0) + token = dbstop_at; + else + token = dbstop_in; } else + token = dbstop_at; + + if (pos >= nargin) + error ("%s: '%s' missing argument", who, + ( token == dbstop_in ? "in" : + (token == dbstop_at ? "at" : "if"))); + + // process the actual arguments + switch (token) { - // It was a line number. Need to get function name from debugger. - if (Vdebugging) - { - symbol_name = get_user_code ()->name (); - idx = 0; - } - else - { - error ("%s: no function specified", who); - } - } - } - else if (args(0).is_map ()) - { - // This is a problem because parse_dbfunction_params() - // can only pass out a single function. - error ("%s: struct input not implemented", who); - return; - } - else - error ("%s: invalid parameter specified", who); + case dbstop_in: + symbol_name = args(pos).string_value (); + if (seen_in) + error ("%s: Too many function names specified -- %s", + who, symbol_name.c_str ()); + else if (seen_at || seen_if) + error ("%s: function name must come before line number and 'if'", + who); + seen_in = true; + pos++; + break; - for (int i = idx; i < nargin; i++) - { - if (args(i).is_string ()) - { - int line = atoi (args(i).string_value ().c_str ()); + case dbstop_at: + if (seen_at) + error ("%s: Only one 'at' clause is allowed -- %s", + who, args(pos).string_value ().c_str ()); + else if (seen_if) + error ("%s: line number must come before 'if' clause\n"); + seen_at = true; - lines[list_idx++] = line; - } - else if (args(i).is_map ()) - octave_stdout << who << ": skipping struct input" << std::endl; - else - { - const NDArray arg = args(i).array_value (); + if (!seen_in) + { + // It was a line number. Get function name from debugger. + if (Vdebugging) + //symbol_name = get_user_code ()->name (); + symbol_name = get_user_code ()->fcn_file_name (); + else + error ("%s: function name must come before line number " + "and 'if'", who); + seen_in = true; + } + else if (seen_if) + error ("%s: line number must come before 'if' clause\n"); - for (octave_idx_type j = 0; j < arg.numel (); j++) - { - int line = static_cast (arg.elem (j)); + // Read a list of line numbers (or arrays thereof) + for ( ; pos < nargin; pos++) + { + if (args(pos).is_string ()) + { + int line = atoi (args(pos).string_value ().c_str ()); - lines[list_idx++] = line; - } + if (line > 0) + lines[list_idx++] = line; + else + break; // may be "if" + } + else if (args(pos).is_numeric_type ()) + { + const NDArray arg = args(pos).array_value (); + + for (octave_idx_type j = 0; j < arg.numel (); j++) + lines[list_idx++] = static_cast (arg.elem (j)); + } + else + error ("%s: Invalid argument type %s", + args(pos).type_name ().c_str ()); + } + break; + + case dbstop_if: + if (seen_in) // conditional breakpoint + { + cond = ""; // remaining arguments form condition + for (; pos < nargin; pos++) + { + if (args(pos).is_string ()) + cond = cond + " " + args(pos).string_value (); + else + error ("%s: arguments to 'if' must all be strings", who); + } + + cond = cond.substr (1); // omit initial space + } + else // stop on event (error, warning, interrupt, NaN/inf) + { + std::string condition = args(pos).string_value (); + int on_off = !strcmp(who, "dbstop"); + + // list of error/warning IDs to update + std::set *id_list = NULL; + bool *stop_flag = NULL; // Vdebug_on_... flag + + if (condition == "error") + { + id_list = &bp_table::errors_that_stop; + stop_flag = &Vdebug_on_error; + } + else if (condition == "warning") + { + id_list = &bp_table::warnings_that_stop; + stop_flag = &Vdebug_on_warning; + } + else if (condition == "caught" && nargin > pos+1 + && args(pos+1).string_value () == "error") + { + id_list = &bp_table::caught_that_stop; + stop_flag = &Vdebug_on_caught; + pos++; + } + else if (condition == "interrupt") + { + Vdebug_on_interrupt = on_off; + } + else if (condition == "naninf") +#ifdef DBSTOP_NANINF + Vdebug_on_naninf = on_off; + enable_fpe (on_off); +#else + warning ("%s: condition '%s' not yet supported", + who, condition.c_str ()); +#endif + else + error ("%s: invalid condition %s", + who, condition.c_str ()); + + // process ID list for "dbstop if error " etc + if (id_list != NULL) + { + pos++; + if (pos < nargin) // only affect a single error ID + { + if (!args(pos).is_string () || nargin > pos+1) + error ("%s: ID must be a single string", who); + else if (on_off == 1) + { + id_list->insert (args(pos).string_value ()); + *stop_flag = 1; + } + else + { + id_list->erase (args(pos).string_value ()); + if (id_list->empty ()) + *stop_flag = 0; + } + } + else // unqualified. Turn all on or off + { + id_list->clear (); + *stop_flag = on_off; + if (stop_flag == &Vdebug_on_error) + Vdebug_on_interrupt = on_off; // Matlabs stops on both + } + } + + pos = nargin; + } + break; + + default: // dbstop_none should never occur + break; } } } +/* +%!test +%! dbstop help; +%! dbstop in ls; +%! dbstop help at 100; +%! dbstop in ls 100; +%! dbstop help 200 if a==5; +%! dbstop if error Octave:undefined-function +%! s = dbstatus; +%! dbclear all +%! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"}); +%! assert ([s.bkpt(:).line], [48, 100, 200, 58, 100]); +%! assert (s.errs, {"Octave:undefined-function"}); +*/ + +// Return true if there is a valid breakpoint table, false otherwise. +// If not table exists, one is created; false is only returned if this fails bool bp_table::instance_ok (void) { @@ -287,10 +459,113 @@ bp_table::instance_ok (void) return retval; } +// Clear all reasons to stop, other than breakpoints +void +bp_table::dbclear_all_signals (void) +{ + Vdebug_on_error = false; bp_table::errors_that_stop.clear (); + Vdebug_on_caught = false; bp_table::caught_that_stop.clear (); + Vdebug_on_warning = false; bp_table::warnings_that_stop.clear (); + Vdebug_on_interrupt = false; +} + +// Process the "warn", "errs", "caught" and "intr" fields for a call of +// "dbstop (p)". +void +bp_table::dbstop_process_map_args (const octave_map& mv) +{ + // process errs + // why so many levels of indirection needed? + bool fail = false; + Cell U = mv.contents ("errs"); + if (U.numel () != 1) + fail = (U.numel () > 1); + else + { + Array W = U.index (0); + if (W.numel () == 0 || W(0).length () == 0) + Vdebug_on_error = 1; // like "dbstop if error" with no identifier + else if (!W(0).is_cell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + errors_that_stop.insert (V(i).string_value ()); + Vdebug_on_error = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'errs' field"); + + // process caught + // why so many levels of indirection needed? + fail = false; + U = mv.contents ("caught"); + if (U.numel () != 1) + fail = (U.numel () > 1); + else + { + Array W = U.index (0); + if (W.numel () == 0 || W(0).length () == 0) + Vdebug_on_caught = 1; // like "dbstop if caught error" with no ID + else if (!W(0).is_cell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + caught_that_stop.insert (V(i).string_value ()); + Vdebug_on_caught = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'caught' field"); + + // process warn + // why so many levels of indirection needed? + fail = false; + U = mv.contents ("warn"); + if (U.numel () != 1) + fail = (U.numel () > 1); + else + { + Array W = U.index (0); + if (W.numel () == 0 || W(0).length () == 0) + Vdebug_on_warning = 1; // like "dbstop if warning" with no identifier + else if (!W(0).is_cell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + warnings_that_stop.insert (V(i).string_value ()); + Vdebug_on_warning = 1; + } + } + } + if (fail) + error ("dbstop: invalid 'warn' field"); + + // process interrupt + if (mv.isfield ("intr")) + Vdebug_on_interrupt = 1; +} + + +// Insert a breakpoint in function fcn at line within file fname, +// to stop only when condition is true. +// Record in bp_set that fname contains a breakpoint. bool bp_table::do_add_breakpoint_1 (octave_user_code *fcn, const std::string& fname, const bp_table::intmap& line, + const std::string& condition, bp_table::intmap& retval) { bool found = false; @@ -301,13 +576,20 @@ bp_table::do_add_breakpoint_1 (octave_us if (cmds) { - retval = cmds->add_breakpoint (file, line); + retval = cmds->add_breakpoint (file, line, condition); for (intmap_iterator p = retval.begin (); p != retval.end (); p++) { if (p->second != 0) { - bp_set.insert (fname); + // normalise to store only the file name. + // otherwise, there can be an entry for both file>subfunction and + // file, which causes a crash on dbclear all + const char *s = strchr (fname.c_str (), '>'); + if (s) + bp_set.insert (fname.substr (0, s - fname.c_str ())); + else + bp_set.insert (fname); found = true; break; } @@ -317,9 +599,14 @@ bp_table::do_add_breakpoint_1 (octave_us return found; } +// Given file name fname, find the subfunction at line line and create +// a breakpoint there. Put the system into debug_mode. +// (FIXME: If line is multiple lines, what happens if they are in different +// functions?) bp_table::intmap bp_table::do_add_breakpoint (const std::string& fname, - const bp_table::intmap& line) + const bp_table::intmap& line, + const std::string& condition) { intmap retval; @@ -327,7 +614,7 @@ bp_table::do_add_breakpoint (const std:: if (dbg_fcn) { - if (! do_add_breakpoint_1 (dbg_fcn, fname, line, retval)) + if (! do_add_breakpoint_1 (dbg_fcn, fname, line, condition, retval)) { // Search subfunctions in the order they appear in the file. @@ -347,14 +634,15 @@ bp_table::do_add_breakpoint (const std:: { octave_user_code *dbg_subfcn = q->second.user_code_value (); - if (do_add_breakpoint_1 (dbg_subfcn, fname, line, retval)) + if (do_add_breakpoint_1 (dbg_subfcn, fname, line, condition, + retval)) break; } } } } else - error ("add_breakpoint: unable to find the requested function\n"); + error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); tree_evaluator::debug_mode = bp_table::have_breakpoints () || Vdebugging; @@ -454,7 +742,8 @@ bp_table::do_remove_breakpoint (const st } } else - error ("remove_breakpoint: unable to find the requested function\n"); + error ("remove_breakpoint: unable to find function %s\n", + fname.c_str ()); } tree_evaluator::debug_mode = bp_table::have_breakpoints () || Vdebugging; @@ -488,7 +777,7 @@ bp_table::do_remove_all_breakpoints_in_f } else if (! silent) error ("remove_all_breakpoint_in_file: " - "unable to find the requested function\n"); + "unable to find function %s\n", fname.c_str ()); tree_evaluator::debug_mode = bp_table::have_breakpoints () || Vdebugging; @@ -528,10 +817,10 @@ do_find_bkpt_list (octave_value_list sli return retval; } -bp_table::fname_line_map +bp_table::fname_bp_map bp_table::do_get_breakpoint_list (const octave_value_list& fname_list) { - fname_line_map retval; + fname_bp_map retval; for (bp_set_iterator it = bp_set.begin (); it != bp_set.end (); it++) { @@ -548,25 +837,49 @@ bp_table::do_get_breakpoint_list (const // tree_statement_list class? if (cmds) { - octave_value_list bkpts = cmds->list_breakpoints (); - octave_idx_type len = bkpts.length (); + std::list bkpts = cmds->breakpoints_and_conds (); - if (len > 0) + if (!bkpts.empty ()) { - bp_table::intmap bkpts_vec; + if (f->name () == *it) + retval[f->name ()] = bkpts; + else + retval[*it + ">" + f->name ()] = bkpts; + } + } - for (int i = 0; i < len; i++) - bkpts_vec[i] = bkpts(i).double_value (); + // look for breakpoints in subfunctions + const std::list subf_nm = f->subfunction_names (); - std::string symbol_name = f->name (); + std::map subf = f->subfunctions (); - retval[symbol_name] = bkpts_vec; + for (std::list::const_iterator p = subf_nm.begin (); + p != subf_nm.end (); p++) + { + std::map::const_iterator + q = subf.find (*p); + + if (q != subf.end ()) + { + octave_user_code *ff = q->second.user_code_value (); + + cmds = ff->body (); + if (cmds) + { + std::list bkpts + = cmds->breakpoints_and_conds (); + + if (!bkpts.empty ()) + { + std::string name = f->name () + ">" + ff->name (); + retval[name] = bkpts; + } + } } } } } } - return retval; } @@ -593,10 +906,111 @@ intmap_to_ov (const bp_table::intmap& li return retval; } +// Cursory check that cond is a valid condition to use for a breakpoint +// Currently allows conditions with side-effects, like 'y+=10' and 'y++'; +// it is odd that the former is not flagged by "is_assignment_expression". +static bool +condition_valid (const std::string& cond) +{ + bool valid_expression = true; + if (cond.length () > 0) + { + octave_parser parser (cond + " ;"); // ; to reject partial expr like "y==" + parser.reset (); + int parse_status = parser.run (); + if (parse_status) + { + valid_expression = false; + error ("dbstop: Cannot parse condition '%s'", cond.c_str ()); + } + else + { + tree_statement *stmt = 0; + if (parser.stmt_list->length () == 1 + && (stmt = parser.stmt_list->front ()) + && stmt->is_expression ()) + { + tree_expression *expr = stmt->expression (); + if (expr->is_assignment_expression ()) + { + valid_expression = false; + error ("dbstop: condition cannot be an assignment. " + "Did you mean '=='?"); + } + } + else + { + valid_expression = false; + error ("dbstop: condition must be an expression"); + } + } + } + return valid_expression; +} + DEFUN (dbstop, args, , "-*- texinfo -*-\n\ -@deftypefn {Command} {} dbstop @var{func}\n\ -@deftypefnx {Command} {} dbstop @var{func} @var{line}\n\ +@deftypefn {Command} {} dbstop in @var{func}\n\ +@deftypefnx {Command} {} dbstop in @var{func} at @var{line}\n\ +@deftypefnx {Command} {} dbstop in @var{func} at @var{line} if @var{condition}\n\ +@deftypefnx {Command} {} dbstop in @var{func} if @var{condition}\n\ +@deftypefnx {Command} {} dbstop if @var{event}\n\ +@deftypefnx {Build-in Function} {} dbstop (@var{state})\n\ +\n\ +The argument @var{func} is the name of a function on the current path.\n\ +The argument @var{line} is the line number at which to break.\n\ +If @code{file.m} has a sub-function @code{func2}, then a breakpoint in\n\ +@code{func2} can be specified by specifying @var{func} either as @code{file}\n\ +or @code{file>func2}. In either case, @var{line} is specified relative to\n\ +the start of @code{file.m}. If @var{line} is not specified, it defaults to\n\ +the first line in @var{func}. Multiple lines can be specified in a single\n\ +command.\n\ +\n\ +The @var{condition} is any Octave expression that will be able to be\n\ +evaluated in the context of the breakpoint. When the breakpoint is\n\ +encountered, @var{condition} will be evaluated, and execution will break if\n\ +@var{condition} is true. If it cannot be evaluated, for example because it\n\ +refers to an undefined variable, an error will be thrown. Expressions with\n\ +side effects (such as @code{y++ > 1}) actually alter the variables, and \n\ +should generally be avoided.\n\ +\n\ +The form specifying @var{event} does not cause a fixed breakpoint. Instead\n\ +it causes debug mode to be entered when specific unexpected circumstances\n\ +arise. Possible values are\n\ +\n\ +@table @asis\n\ +@item @qcode{error}\n\ +Stop whenever an error is reported. This is equivalent to specifying\n\ +both @code{debug_on_error(1)} and @code{debug_on_interrupt(1)}.\n\ +@item @qcode{caught error}\n\ +Stop whenever an error is caught by a try-catch block\n\ +@item @qcode{interrupt}\n\ +Stop when an interrupt (ctrl-C) occurs.\n\ +@item @qcode{warning}\n\ +Stop whenever a warning is reported. This is equivalent to specifying\n\ +@code{debug_on_warning(1)}.\n\ +@end table\n\ +Error, caught error and warning can all be followed by a string specifying\n\ +an error ID or warning ID. If that is done, only errors with the\n\ +specified ID will cause execution to stop. To stop on one of a set of\n\ +IDs, multiple @qcode{dbstop} commands must be issued.\n\ +\n\ +Values @qcode{naninf} and @qcode{caught error} give a warning that these\n\ +are not yet implemented, and other options give an error.\n\ +\n\ +These settings can be undone using @qcode{dbclear} commands with the same\n\ +syntax.\n\ +\n\ +It is possible to save all breakpoints and restore them at once by issuing\n\ +the commands @code{state = dbstatus; ... dbstop (state)}.\n\ +\n\ +When a file is re-parsed, such as when it is modified, all breakpoints within\n\ +that file are cleared.\n\ +\n\ +For compatibility with previous versions of Octave, the following forms are\n\ +also available.\n\ +@end deftypefn\n\ +@deftypefn {Command} {} dbstop @var{func} @var{line}\n\ @deftypefnx {Command} {} dbstop @var{func} @var{line1} @var{line2} @dots{}\n\ @deftypefnx {Command} {} dbstop @var{line} @dots{}\n\ @deftypefnx {Built-in Function} {@var{rline} =} dbstop (\"@var{func}\")\n\ @@ -625,21 +1039,85 @@ The optional output @var{rline} is the r was set. This can differ from the specified line if the line is not\n\ executable. For example, if a breakpoint attempted on a blank line then\n\ Octave will set the real breakpoint at the next executable line.\n\ +\n\ @seealso{dbclear, dbstatus, dbstep, debug_on_error, debug_on_warning, debug_on_interrupt}\n\ @end deftypefn") { - bp_table::intmap retval; - std::string symbol_name; + bp_table::intmap retmap; + std::string symbol_name = ""; // stays empty for "dbstop if error" etc bp_table::intmap lines; + std::string condition = ""; + octave_value retval; - parse_dbfunction_params ("dbstop", args, symbol_name, lines); + if (args.length() >= 1 && !args(0).is_map ()) + { // explicit function / line / condition + parse_dbfunction_params ("dbstop", args, symbol_name, lines, condition); - if (lines.size () == 0) - lines[0] = 1; + if (lines.size () == 0) + lines[0] = 1; - retval = bp_table::add_breakpoint (symbol_name, lines); + if (symbol_name != "" && condition_valid (condition)) + { + retmap = bp_table::add_breakpoint (symbol_name, lines, condition); + retval = intmap_to_ov (retmap); + } + } + else if (args.length () != 1) + { + print_usage (); + } + else // structure of the form output by dbstatus + { + octave_map mv = args(0).map_value (); + if (mv.isfield ("bkpt") || mv.isfield ("errs") || mv.isfield ("warn") + || mv.isfield ("intr")) + { + bp_table::dbstop_process_map_args (mv); - return intmap_to_ov (retval); + // Replace mv by "bkpt", to use the processing below. + octave_value bkpt = mv.getfield ("bkpt"); + if (bkpt.is_empty ()) + mv = octave_map (); + else + { + if (bkpt.is_cell () && bkpt.cell_value ().numel () > 0 + && bkpt.cell_value () (0).is_map ()) + mv = bkpt.cell_value () (0).map_value (); + else + { + error ("dbstop: invalid 'bkpt' field"); + mv = octave_map (); + } + } + } + if (mv.numel () == 0) + { + // no changes requested. Occurs if "errs" non-empty but "bkpt" empty + } + else if (!mv.isfield ("name") || !mv.isfield ("line")) + { + error ("dbstop: Cell array must contain fields 'name' and 'line'"); + retval = octave_value (0); + } + else + { + bool use_cond = mv.isfield ("cond"); + Cell name = mv.getfield ("name"); + Cell line = mv.getfield ("line"); + Cell cond = (use_cond ? mv.getfield ("cond") : Cell ()); + std::string unconditional = ""; + for (octave_idx_type i = 0; i < line.numel (); i++) + { + lines [0] = line(i).double_value (); + bp_table::add_breakpoint (name(i).string_value (), lines, + use_cond ? cond(i).string_value () + : unconditional ); + } + retval = octave_value (line.numel ()); + } + } + + return retval; } DEFUN (dbclear, args, , @@ -649,6 +1127,9 @@ DEFUN (dbclear, args, , @deftypefnx {Command} {} dbclear @var{func} @var{line1} @var{line2} @dots{}\n\ @deftypefnx {Command} {} dbclear @var{line} @dots{}\n\ @deftypefnx {Command} {} dbclear all\n\ +@deftypefnx {Command} {} dbclear in @var{func}\n\ +@deftypefnx {Command} {} dbclear in @var{func} at @var{line}\n\ +@deftypefnx {Command} {} dbclear if @var{event}\n\ @deftypefnx {Built-in Function} {} dbclear (\"@var{func}\")\n\ @deftypefnx {Built-in Function} {} dbclear (\"@var{func}\", @var{line})\n\ @deftypefnx {Built-in Function} {} dbclear (\"@var{func}\", @var{line1}, @var{line2}, @dots{})\n\ @@ -667,6 +1148,8 @@ can be omitted and the current function @item line\n\ Line number from which to remove a breakpoint. Multiple lines may be given\n\ as separate arguments or as a vector.\n\ +@item event\n\ +An even such as error, interrupt or warning; see dbstop for details.\n\ @end table\n\ \n\ When called without a line number specification all breakpoints in the named\n\ @@ -680,17 +1163,135 @@ files.\n\ @end deftypefn") { octave_value retval; - std::string symbol_name = ""; + std::string symbol_name = ""; // stays empty for "dbclear if error" etc bp_table::intmap lines; + std::string dummy; // "if" condition -- only used for dbstop int nargin = args.length (); - parse_dbfunction_params ("dbclear", args, symbol_name, lines); + parse_dbfunction_params ("dbclear", args, symbol_name, lines, dummy); if (nargin == 1 && symbol_name == "all") - bp_table::remove_all_breakpoints (); + { + bp_table::remove_all_breakpoints (); + bp_table::dbclear_all_signals (); + } else - bp_table::remove_breakpoint (symbol_name, lines); + { + if (symbol_name != "") + bp_table::remove_breakpoint (symbol_name, lines); + } + + return retval; +} + +// Report the status of "dbstop if error ..." and "dbstop if warning ..." +// If to_screen is true, the output goes to octave_stdout; otherwise it is +// returned. +// If dbstop if error is true but no explicit IDs are specified, the return +// value will have an empty field called "errs". If IDs are specified, the +// "errs" field will have a row per ID. If dbstop if error is false, there +// is no "errs" field. The "warn" field is set similarly by dbstop if warning +octave_map +bp_table::stop_on_err_warn_status (bool to_screen) +{ + octave_map retval; + + // print dbstop if error information + if (Vdebug_on_error) + { + if (errors_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if error\n"; + else + retval.assign ("errs", octave_value("")); + } + else + { + Cell errs (dim_vector (bp_table::errors_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator e + = errors_that_stop.begin (); + e != errors_that_stop.end (); e++) + { + if (to_screen) + octave_stdout << "stop if error " << *e << "\n"; + else + errs (i++) = *e; + } + if (!to_screen) + retval.assign ("errs", octave_value (errs)); + } + } + + // print dbstop if caught error information + if (Vdebug_on_caught) + { + if (caught_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if caught error\n"; + else + retval.assign ("caught", octave_value("")); + } + else + { + Cell errs (dim_vector (caught_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator e + = caught_that_stop.begin (); + e != caught_that_stop.end (); e++) + { + if (to_screen) + octave_stdout << "stop if caught error " << *e << "\n"; + else + errs (i++) = *e; + } + if (!to_screen) + retval.assign ("caught", octave_value (errs)); + } + } + + // print dbstop if warning information + if (Vdebug_on_warning) + { + if (warnings_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if warning\n"; + else + retval.assign ("warn", octave_value("")); + } + else + { + Cell warn (dim_vector (warnings_that_stop.size (), 1)); + int i = 0; + + for (std::set::const_iterator w + = warnings_that_stop.begin (); + w != warnings_that_stop.end (); w++) + { + if (to_screen) + octave_stdout << "stop if warning " << *w << "\n"; + else + warn (i++) = *w; + } + if (!to_screen) + retval.assign ("warn", octave_value (warn)); + } + } + + // print dbstop if interrupt information + if (Vdebug_on_interrupt) + { + if (to_screen) + octave_stdout << "stop if interrupt\n"; + else + retval.assign ("intr", octave_value ()); + } return retval; } @@ -707,32 +1308,42 @@ functions with breakpoints and the line set.\n\ \n\ If a function name @var{func} is specified then only report breakpoints\n\ -for the named function.\n\ +for the named function, and its sub-functions.\n\ \n\ The optional return argument @var{brk_list} is a struct array with the\n\ following fields.\n\ \n\ @table @asis\n\ @item name\n\ -The name of the function with a breakpoint.\n\ +The name of the function with a breakpoint. A sub-function, say @code{func2}\n\ +within a .m file, say @code{foo/file.m}, is specified as @code{file>func2}.\n\ \n\ @item file\n\ The name of the m-file where the function code is located.\n\ \n\ @item line\n\ -A line number, or vector of line numbers, with a breakpoint.\n\ +The line number with the breakpoint.\n\ +\n\ +@item cond\n\ +The condition that must be satisfied for the breakpoint to be active, or\n\ +the empty string for unconditional breakpoints.\n\ @end table\n\ \n\ -Note: When @code{dbstatus} is called from the debug prompt within a function,\n\ -the list of breakpoints is automatically trimmed to the breakpoints in the\n\ -current function.\n\ -@seealso{dbclear, dbwhere}\n\ +@c Note: When @code{dbstatus} is called from the debug prompt within a function,\n\ +@c the list of breakpoints is automatically trimmed to the breakpoints in the\n\ +@c current function.\n\ +If @code{dbstop if error} is true but no explicit IDs are specified, the\n\ +return value will have an empty field called \"errs\". If IDs are specified,\n\ +the \"errs\" field will have a row per ID. If @code{dbstop if error} is\n\ +false, there is no \"errs\" field. The \"warn\" field is set similarly by\n\ +@code{dbstop if warning}.\n\ +\n\ +@seealso{dbstop, dbclear, dbwhere, dblist, dbstack}\n\ @end deftypefn") { - octave_map retval; int nargin = args.length (); octave_value_list fcn_list; - bp_table::fname_line_map bp_list; + bp_table::fname_bp_map bp_list; std::string symbol_name; if (nargin != 0 && nargin != 1) @@ -754,6 +1365,7 @@ current function.\n\ } else { +/* if (Vdebugging) { octave_user_code *dbg_fcn = get_user_code (); @@ -763,6 +1375,7 @@ current function.\n\ fcn_list(0) = symbol_name; } } +*/ bp_list = bp_table::get_breakpoint_list (fcn_list); } @@ -771,50 +1384,114 @@ current function.\n\ { // Print out the breakpoint information. - for (bp_table::fname_line_map_iterator it = bp_list.begin (); + for (bp_table::fname_bp_map_iterator it = bp_list.begin (); it != bp_list.end (); it++) { - bp_table::intmap m = it->second; + std::list m = it->second; - size_t nel = m.size (); + // print unconditional breakpoints, if any, on a single line - octave_stdout << "breakpoint in " << it->first; - if (nel > 1) - octave_stdout << " at lines "; - else - octave_stdout << " at line "; + // first, check to see if there are any + int have_unconditional = 0; + for (std::list::const_iterator j = m.begin (); + j != m.end (); j++) + { + if (j->cond == "") + { + if (have_unconditional++) + break; // stop once we know its plural + } + } + // If we actually have some, print line numbers only + if (have_unconditional) + { + const char *_s_ = (have_unconditional > 1) ? "s" : ""; + octave_stdout << "breakpoint" << _s_ <<" in " << it->first + << " at line" << _s_ << " "; - for (size_t j = 0; j < nel; j++) - octave_stdout << m[j] << ((j < nel - 1) ? ", " : "."); + for (std::list::const_iterator j = m.begin (); + j != m.end (); j++) + { + if (j->cond == "") + octave_stdout << j->line << " "; + } + octave_stdout << std::endl; + } - if (nel > 0) - octave_stdout << std::endl; + // print conditional breakpoints, one per line, with conditions + for (std::list::const_iterator j = m.begin (); + j != m.end (); j++) + { + if (j->cond != "") + octave_stdout << "breakpoint in " << it->first + << " at line " << j->line + << " if " << j->cond << "\n"; + } } + + bp_table::stop_on_err_warn_status (true); + return octave_value (); } else { // Fill in an array for return. + int i = 0; + octave_map retmap; + octave_value retval; - int i = 0; - Cell names (dim_vector (bp_list.size (), 1)); - Cell file (dim_vector (bp_list.size (), 1)); - Cell line (dim_vector (bp_list.size (), 1)); - - for (bp_table::const_fname_line_map_iterator it = bp_list.begin (); + // count how many breakpoints there are + int count = 0; + for (bp_table::const_fname_bp_map_iterator it = bp_list.begin (); it != bp_list.end (); it++) { - names(i) = it->first; - line(i) = intmap_to_ov (it->second); - file(i) = do_which (it->first); - i++; + for (std::list::const_iterator j = it->second.begin (); + j != it->second.end (); j++) + count++; } - retval.assign ("name", names); - retval.assign ("file", file); - retval.assign ("line", line); + Cell names (dim_vector (count, 1)); + Cell file (dim_vector (count, 1)); + Cell line (dim_vector (count, 1)); + Cell cond (dim_vector (count, 1)); - return octave_value (retval); + for (bp_table::const_fname_bp_map_iterator it = bp_list.begin (); + it != bp_list.end (); it++) + { + octave_value path_name = do_which (it->first); + for (std::list::const_iterator j = it->second.begin (); + j != it->second.end (); j++) + { + names(i) = it->first; + file(i) = path_name; + line(i) = octave_value (j->line); + cond(i) = octave_value (j->cond); + i++; + } + } + + retmap.assign ("name", names); + retmap.assign ("file", file); + retmap.assign ("line", line); + retmap.assign ("cond", cond); + + octave_map ew = bp_table::stop_on_err_warn_status (false); + if (ew.numel () == 0) + { + retval = octave_value (retmap); + } + else + { + octave_map outer (dim_vector (3,1)); + outer.assign ("bkpt", Cell (retmap)); + for (octave_map::const_iterator f = ew.begin (); f != ew.end (); f++) + { + outer.setfield (f->first, ew.contents (f)); + } + retval = octave_value (outer); + } + + return retval; } } @@ -823,7 +1500,7 @@ DEFUN (dbwhere, , , @deftypefn {Command} {} dbwhere\n\ In debugging mode, report the current file and line number where execution\n\ is stopped.\n\ -@seealso{dbstatus, dbcont, dbstep, dbup}\n\ +@seealso{dbstack, dblist, dbstatus, dbcont, dbstep, dbup, dbdown}\n\ @end deftypefn") { octave_value retval; @@ -920,7 +1597,7 @@ specification for the last line of the f \n\ When called with the name of a function, list that script file with line\n\ numbers.\n\ -@seealso{dbwhere, dbstatus, dbstop}\n\ +@seealso{dblist, dbwhere, dbstatus, dbstop}\n\ @end deftypefn") { octave_value retval; @@ -1067,7 +1744,7 @@ In debugging mode, list @var{n} lines of centered around the current line to be executed.\n\ \n\ If unspecified @var{n} defaults to 10 (+/- 5 lines)\n\ -@seealso{dbwhere, dbtype}\n\ +@seealso{dbwhere, dbtype, dbstack}\n\ @end deftypefn") { octave_value retval; @@ -1292,7 +1969,7 @@ Undocumented.\n\ \n\ The return argument @var{idx} specifies which element of the @var{stack}\n\ struct array is currently active.\n\ -@seealso{dbup, dbdown, dbwhere, dbstatus}\n\ +@seealso{dbup, dbdown, dbwhere, dblist, dbstatus}\n\ @end deftypefn") { return do_dbstack (args, nargout, octave_stdout); diff -r b6091735f3f5 libinterp/corefcn/debug.h --- a/libinterp/corefcn/debug.h Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/corefcn/debug.h Sun Dec 06 20:50:02 2015 +1100 @@ -30,9 +30,18 @@ along with Octave; see the file COPYING. class octave_value_list; class octave_user_code; +static std::string bp_empty_string (""); + +struct +bp_type +{ + int line; + std::string cond; + bp_type (int l, const std::string& c) : line (l), cond (c) + { } +}; // Interface to breakpoints,. - class OCTINTERP_API bp_table @@ -45,6 +54,7 @@ private: public: + // mapping from ??? to line number of breakpoint typedef std::map intmap; typedef intmap::const_iterator const_intmap_iterator; @@ -55,14 +65,19 @@ public: typedef fname_line_map::const_iterator const_fname_line_map_iterator; typedef fname_line_map::iterator fname_line_map_iterator; + typedef std::map > fname_bp_map; + typedef fname_bp_map::const_iterator const_fname_bp_map_iterator; + typedef fname_bp_map::iterator fname_bp_map_iterator; + static bool instance_ok (void); // Add a breakpoint at the nearest executable line. static intmap add_breakpoint (const std::string& fname = "", - const intmap& lines = intmap ()) + const intmap& lines = intmap (), + const std::string& condition = bp_empty_string) { return instance_ok () - ? instance->do_add_breakpoint (fname, lines) : intmap (); + ? instance->do_add_breakpoint (fname, lines, condition) : intmap (); } // Remove a breakpoint from a line in file. @@ -91,11 +106,11 @@ public: // Return all breakpoints. Each element of the map is a vector // containing the breakpoints corresponding to a given function name. - static fname_line_map + static fname_bp_map get_breakpoint_list (const octave_value_list& fname_list) { return instance_ok () - ? instance->do_get_breakpoint_list (fname_list) : fname_line_map (); + ? instance->do_get_breakpoint_list (fname_list) : fname_bp_map (); } static bool @@ -104,22 +119,64 @@ public: return instance_ok () ? instance->do_have_breakpoints () : 0; } + // Should we enter debugging for this particular error identifier? + static bool + debug_on_err (const std::string& ID) + { + return (errors_that_stop.empty () || errors_that_stop.count (ID)); + } + + // Should we enter debugging for this particular identifier in a try/catch? + static bool + debug_on_caught (const std::string& ID) + { + return (caught_that_stop.empty () || caught_that_stop.count (ID)); + } + + // Should we enter debugging for this particular warning identifier? + static bool + debug_on_warn (const std::string& ID) + { + return (warnings_that_stop.empty () || warnings_that_stop.count (ID)); + } + + static octave_map stop_on_err_warn_status (bool toScreen); + + static void dbstop_process_map_args (const octave_map& mv); + + static void dbclear_all_signals (void); + + friend void parse_dbfunction_params (const char *, const octave_value_list&, + std::string&, bp_table::intmap&, + std::string&); + private: typedef std::set::const_iterator const_bp_set_iterator; typedef std::set::iterator bp_set_iterator; - // Set of function names containing at least one breakpoint. + // Set of function (.m file) names containing at least one breakpoint. std::set bp_set; + + // Set of error and warning message IDs that cause us to stop + // *if* Vdebug_on_error / Vdebug_on_caught / Vdebug_on_warning is set. + // Empty means stop on any error / caught error / warning. + static std::set errors_that_stop; + static std::set caught_that_stop; + static std::set warnings_that_stop; + + static bp_table *instance; static void cleanup_instance (void) { delete instance; instance = 0; } bool do_add_breakpoint_1 (octave_user_code *fcn, const std::string& fname, - const intmap& line, intmap& retval); + const intmap& line, const std::string& condition, + intmap& retval); - intmap do_add_breakpoint (const std::string& fname, const intmap& lines); + intmap do_add_breakpoint (const std::string& fname, const intmap& lines, + const std::string& condition); int do_remove_breakpoint_1 (octave_user_code *fcn, const std::string&, const intmap& lines); @@ -134,7 +191,7 @@ private: void do_remove_all_breakpoints (void); - fname_line_map do_get_breakpoint_list (const octave_value_list& fname_list); + fname_bp_map do_get_breakpoint_list (const octave_value_list& fname_list); bool do_have_breakpoints (void) { return (! bp_set.empty ()); } }; diff -r b6091735f3f5 libinterp/corefcn/error.cc --- a/libinterp/corefcn/error.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/corefcn/error.cc Sun Dec 06 20:50:02 2015 +1100 @@ -32,6 +32,7 @@ along with Octave; see the file COPYING. #include #include +#include "debug.h" #include "defun.h" #include "error.h" #include "input.h" @@ -57,6 +58,10 @@ static bool Vbeep_on_error = false; // traceback message (you will only see the top-level error message). bool Vdebug_on_error = false; +// TRUE means that Octave will try to enter the debugger when an error +// is encountered within the 'try' section of a 'try' / 'catch' block. +bool Vdebug_on_caught = false; + // TRUE means that Octave will try to enter the debugger when a warning // is encountered. bool Vdebug_on_warning = false; @@ -103,6 +108,10 @@ int error_state = 0; // the 'unwind_protect' statement. int buffer_error_messages = 0; +// The number of layers of try / catch blocks we're in. Used to print +// "caught error" instead of error when "dbstop if caught error" is on. +int in_try_catch = 0; + // TRUE means error messages are turned off. bool discard_error_messages = false; @@ -113,6 +122,7 @@ void reset_error_handler (void) { buffer_error_messages = 0; + in_try_catch = 0; discard_error_messages = false; } @@ -138,10 +148,10 @@ verror (bool save_last_error, std::ostre const char *name, const char *id, const char *fmt, va_list args, bool with_cfn = false) { - if (discard_error_messages) + if (discard_error_messages && ! Vdebug_on_caught) return; - if (! buffer_error_messages) + if (! buffer_error_messages || Vdebug_on_caught) flush_octave_stdout (); // FIXME: we really want to capture the message before it has all the @@ -162,7 +172,10 @@ verror (bool save_last_error, std::ostre msg_string = "\a"; if (name) - msg_string += std::string (name) + ": "; + if (in_try_catch && ! strcmp (name, "error")) + msg_string += "caught error: "; + else + msg_string += std::string (name) + ": "; // If with_fcn is specified, we'll attempt to prefix the message with the name // of the current executing function. But we'll do so only if: @@ -207,7 +220,7 @@ verror (bool save_last_error, std::ostre Vlast_error_stack = initialize_last_error_stack (); } - if (! buffer_error_messages) + if (! buffer_error_messages || Vdebug_on_caught) { octave_diary << msg_string; os << msg_string; @@ -301,7 +314,9 @@ debug_or_throw_exception (const octave_e bool show_stack_trace = false) { if ((interactive || forced_interactive) - && Vdebug_on_error && octave_call_stack::caller_user_code ()) + && ((Vdebug_on_error && bp_table::debug_on_err (last_error_id ())) + || (Vdebug_on_caught && bp_table::debug_on_caught (last_error_id ()))) + && octave_call_stack::caller_user_code ()) { unwind_protect frame; frame.protect_var (Vdebug_on_error); @@ -316,8 +331,8 @@ debug_or_throw_exception (const octave_e do_keyboard (octave_value_list ()); } - else - throw e; + + throw e; } // Warning messages are never buffered. @@ -686,7 +701,7 @@ warning_1 (const char *id, const char *f pr_where (std::cerr, "warning"); if ((interactive || forced_interactive) - && Vdebug_on_warning && in_user_code) + && Vdebug_on_warning && in_user_code && bp_table::debug_on_warn (id)) { unwind_protect frame; frame.protect_var (Vdebug_on_warning); @@ -1696,6 +1711,9 @@ disable escape sequence expansion use a std::string tst = state(i).string_value (); std::string tid = ident(i).string_value (); + if (error_state) + return retval; + set_warning_option (tst, tid); } } @@ -2156,6 +2174,7 @@ interpreter_try (unwind_protect& frame) buffer_error_messages++; Vdebug_on_error = false; Vdebug_on_warning = false; + // leave Vdebug_on_caught as it was, so errors in try/catch are still caught } diff -r b6091735f3f5 libinterp/corefcn/error.h --- a/libinterp/corefcn/error.h Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/corefcn/error.h Sun Dec 06 20:50:02 2015 +1100 @@ -119,6 +119,10 @@ extern OCTINTERP_API void initialize_def // traceback message (you will only see the top-level error message). extern OCTINTERP_API bool Vdebug_on_error; +// TRUE means that Octave will try to enter the debugger when an error +// is encountered within the 'try' section of a 'try' / 'catch' block. +extern OCTINTERP_API bool Vdebug_on_caught; + // TRUE means that Octave will try to enter the debugger when a warning // is encountered. extern OCTINTERP_API bool Vdebug_on_warning; @@ -134,6 +138,10 @@ extern OCTINTERP_API int warning_state; // the 'unwind_protect' statement. extern OCTINTERP_API int buffer_error_messages; +// The number of layers of try / catch blocks we're in. Used to print +// "caught error" instead of "error" when "dbstop if caught error" is on. +extern OCTINTERP_API int in_try_catch; + // TRUE means error messages are turned off. extern OCTINTERP_API bool discard_error_messages; diff -r b6091735f3f5 libinterp/corefcn/toplev.cc --- a/libinterp/corefcn/toplev.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/corefcn/toplev.cc Sun Dec 06 20:50:02 2015 +1100 @@ -679,6 +679,14 @@ main_loop (void) std::cerr << "error: out of memory -- trying to return to prompt" << std::endl; } + +#ifdef DBSTOP_NANINF + if (Vdebug_on_naninf) + { + if (setjump (naninf_jump) != 0) + debug_or_throw_exception (true); // true = stack trace + } +#endif } while (retval == 0); diff -r b6091735f3f5 libinterp/parse-tree/pt-bp.cc --- a/libinterp/parse-tree/pt-bp.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt-bp.cc Sun Dec 06 20:50:02 2015 +1100 @@ -351,6 +351,9 @@ tree_breakpoint::visit_statement (tree_s } } +// Called by +// tree_statement_list::set_breakpoint (int line, std::string& condition) +// with lst consisting of a user function in which to set a breakpoint. void tree_breakpoint::visit_statement_list (tree_statement_list& lst) { @@ -451,7 +454,7 @@ tree_breakpoint::take_action (tree& tr) { if (act == set) { - tr.set_breakpoint (); + tr.set_breakpoint (condition); line = tr.line (); found = true; } @@ -466,7 +469,10 @@ tree_breakpoint::take_action (tree& tr) else if (act == list) { if (tr.is_breakpoint ()) - bp_list.append (octave_value (tr.line ())); + { + bp_list.append (octave_value (tr.line ())); + bp_cond_list.append (octave_value (tr.bp_cond ())); + } } else panic_impossible (); @@ -479,7 +485,7 @@ tree_breakpoint::take_action (tree_state if (act == set) { - stmt.set_breakpoint (); + stmt.set_breakpoint (condition); line = lineno; found = true; } @@ -494,7 +500,10 @@ tree_breakpoint::take_action (tree_state else if (act == list) { if (stmt.is_breakpoint ()) - bp_list.append (octave_value (lineno)); + { + bp_list.append (octave_value (lineno)); + bp_cond_list.append (octave_value (stmt.bp_cond ())); + } } else panic_impossible (); diff -r b6091735f3f5 libinterp/parse-tree/pt-bp.h --- a/libinterp/parse-tree/pt-bp.h Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt-bp.h Sun Dec 06 20:50:02 2015 +1100 @@ -32,6 +32,7 @@ along with Octave; see the file COPYING. class tree; class tree_decl_command; +static std::string pt_bp_empty_string (""); class tree_breakpoint : public tree_walker { @@ -39,8 +40,8 @@ public: enum action { set = 1, clear = 2, list = 3 }; - tree_breakpoint (int l, action a) - : line (l), act (a), found (false), bp_list () { } + tree_breakpoint (int l, action a, const std::string& c = pt_bp_empty_string) + : line (l), act (a), condition (c), found (false), bp_list () { } ~tree_breakpoint (void) { } @@ -135,6 +136,7 @@ public: void visit_unwind_protect_command (tree_unwind_protect_command&); octave_value_list get_list (void) { return bp_list; } + octave_value_list get_cond_list (void) { return bp_cond_list; } int get_line (void) { return found ? line : 0; } @@ -152,12 +154,18 @@ private: // What to do. action act; + // Expression which must be true to break + std::string condition; + // Have we already found the line? bool found; // List of breakpoint line numbers. octave_value_list bp_list; + // List of breakpoint conditions. + octave_value_list bp_cond_list; + // No copying! tree_breakpoint (const tree_breakpoint&); diff -r b6091735f3f5 libinterp/parse-tree/pt-eval.cc --- a/libinterp/parse-tree/pt-eval.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt-eval.cc Sun Dec 06 20:50:02 2015 +1100 @@ -99,7 +99,7 @@ void tree_evaluator::visit_break_command (tree_break_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (statement_context == function || statement_context == script || in_loop_command) @@ -116,7 +116,7 @@ void tree_evaluator::visit_continue_command (tree_continue_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (statement_context == function || statement_context == script || in_loop_command) @@ -212,7 +212,7 @@ void tree_evaluator::visit_global_command (tree_global_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); do_decl_init_list (do_global_init, cmd.initializer_list ()); } @@ -221,7 +221,7 @@ void tree_evaluator::visit_persistent_command (tree_persistent_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); do_decl_init_list (do_static_init, cmd.initializer_list ()); } @@ -284,7 +284,7 @@ void tree_evaluator::visit_simple_for_command (tree_simple_for_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); // FIXME: need to handle PARFOR loops here using cmd.in_parallel () // and cmd.maxproc_expr (); @@ -403,7 +403,7 @@ void tree_evaluator::visit_complex_for_command (tree_complex_for_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); unwind_protect frame; @@ -546,7 +546,7 @@ tree_evaluator::visit_if_command_list (t octave_call_stack::set_location (tic->line (), tic->column ()); if (debug_mode && ! tic->is_else_clause ()) - do_breakpoint (tic->is_breakpoint ()); + do_breakpoint (tic->is_breakpoint (true)); if (tic->is_else_clause () || expr->is_logically_true ("if")) { @@ -588,7 +588,7 @@ void tree_evaluator::visit_no_op_command (tree_no_op_command& cmd) { if (debug_mode && cmd.is_end_of_fcn_or_script ()) - do_breakpoint (cmd.is_breakpoint (), true); + do_breakpoint (cmd.is_breakpoint (true), true); } void @@ -631,7 +631,7 @@ void tree_evaluator::visit_return_command (tree_return_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); // Act like dbcont. @@ -691,7 +691,7 @@ tree_evaluator::visit_statement (tree_st else { if (debug_mode) - do_breakpoint (expr->is_breakpoint ()); + do_breakpoint (expr->is_breakpoint (true)); // FIXME: maybe all of this should be packaged in // one virtual function that returns a flag saying whether @@ -804,7 +804,7 @@ void tree_evaluator::visit_switch_command (tree_switch_command& cmd) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); tree_expression *expr = cmd.switch_value (); @@ -864,12 +864,15 @@ tree_evaluator::visit_try_catch_command { try { + in_try_catch++; try_code->accept (*this); + in_try_catch--; } catch (const octave_execution_exception&) { recover_from_exception (); + in_try_catch--; // must be restored before "catch" block execution_error = true; } } @@ -897,8 +900,10 @@ tree_evaluator::visit_try_catch_command err.assign ("stack", last_error_stack ()); ult.assign (octave_value::op_asn_eq, err); + } + // perform actual "catch" block if (catch_code) catch_code->accept (*this); } @@ -1054,7 +1059,7 @@ tree_evaluator::visit_while_command (tre for (;;) { if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (expr->is_logically_true ("while")) { @@ -1101,7 +1106,7 @@ tree_evaluator::visit_do_until_command ( break; if (debug_mode) - do_breakpoint (cmd.is_breakpoint ()); + do_breakpoint (cmd.is_breakpoint (true)); if (expr->is_logically_true ("do-until")) break; @@ -1111,7 +1116,7 @@ tree_evaluator::visit_do_until_command ( void tree_evaluator::do_breakpoint (tree_statement& stmt) const { - do_breakpoint (stmt.is_breakpoint (), stmt.is_end_of_fcn_or_script ()); + do_breakpoint (stmt.is_breakpoint (true), stmt.is_end_of_fcn_or_script ()); } void diff -r b6091735f3f5 libinterp/parse-tree/pt-stmt.cc --- a/libinterp/parse-tree/pt-stmt.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt-stmt.cc Sun Dec 06 20:50:02 2015 +1100 @@ -48,6 +48,8 @@ along with Octave; see the file COPYING. #include "utils.h" #include "variables.h" +#include "debug.h" + // A list of commands to be executed. tree_statement::~tree_statement (void) @@ -71,12 +73,12 @@ tree_statement::print_result (void) } void -tree_statement::set_breakpoint (void) +tree_statement::set_breakpoint (const std::string& condition) { if (cmd) - cmd->set_breakpoint (); + cmd->set_breakpoint (condition); else if (expr) - expr->set_breakpoint (); + expr->set_breakpoint (condition); } void @@ -89,9 +91,16 @@ tree_statement::delete_breakpoint (void) } bool -tree_statement::is_breakpoint (void) const +tree_statement::is_breakpoint (bool check_active) const { - return cmd ? cmd->is_breakpoint () : (expr ? expr->is_breakpoint () : false); + return cmd ? cmd->is_breakpoint (check_active) + : (expr ? expr->is_breakpoint (check_active) : false); +} + +std::string +tree_statement::bp_cond () const +{ + return cmd ? cmd->bp_cond () : (expr ? expr->bp_cond () : "0"); } int @@ -178,10 +187,12 @@ tree_statement::accept (tree_walker& tw) tw.visit_statement (*this); } +// Create a "breakpoint" tree-walker, and get it to "walk" this statement list +// (TODO: What does that do???) int -tree_statement_list::set_breakpoint (int line) +tree_statement_list::set_breakpoint (int line, const std::string& condition) { - tree_breakpoint tbp (line, tree_breakpoint::set); + tree_breakpoint tbp (line, tree_breakpoint::set, condition); accept (tbp); return tbp.get_line (); @@ -218,9 +229,34 @@ tree_statement_list::list_breakpoints (v return tbp.get_list (); } +// Get list of pairs (breakpoint line, breakpoint condition) +std::list +tree_statement_list::breakpoints_and_conds (void) +{ + tree_breakpoint tbp (0, tree_breakpoint::list); + accept (tbp); + + std::list retval; + octave_value_list lines = tbp.get_list (); + octave_value_list conds = tbp.get_cond_list (); + + for (int i = 0; i < lines.length (); i++) + { + retval.push_back (bp_type (lines(i).double_value (), + conds(i).string_value ())); + } + + return retval; +} + +// Add breakpoints to file at multiple lines (the second arguments of line), +// to stop only if condition is true. +// Does something I don't understand with octave_link::update_breakpoint. +// TODO COME BACK TO ME bp_table::intmap tree_statement_list::add_breakpoint (const std::string& file, - const bp_table::intmap& line) + const bp_table::intmap& line, + const std::string& condition) { bp_table::intmap retval; @@ -234,7 +270,7 @@ tree_statement_list::add_breakpoint (con { int lineno = p->second; - retval[i] = set_breakpoint (lineno); + retval[i] = set_breakpoint (lineno, condition); if (retval[i] != 0 && ! file.empty ()) octave_link::update_breakpoint (true, file, retval[i]); diff -r b6091735f3f5 libinterp/parse-tree/pt-stmt.h --- a/libinterp/parse-tree/pt-stmt.h Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt-stmt.h Sun Dec 06 20:50:02 2015 +1100 @@ -65,11 +65,12 @@ public: bool is_expression (void) const { return expr != 0; } - void set_breakpoint (void); + void set_breakpoint (const std::string& condition); void delete_breakpoint (void); - bool is_breakpoint (void) const; + bool is_breakpoint (bool check_valid = false) const; + std::string bp_cond () const; int line (void) const; int column (void) const; @@ -159,14 +160,17 @@ public: bool is_script_body (void) const { return script_body; } - int set_breakpoint (int line); + int set_breakpoint (int line, const std::string& condition); void delete_breakpoint (int line); octave_value_list list_breakpoints (void); + std::list breakpoints_and_conds (void); + bp_table::intmap add_breakpoint (const std::string& file, - const bp_table::intmap& line); + const bp_table::intmap& line, + const std::string& condition); bp_table::intmap remove_all_breakpoints (const std::string& file); diff -r b6091735f3f5 libinterp/parse-tree/pt.cc --- a/libinterp/parse-tree/pt.cc Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt.cc Sun Dec 06 20:50:02 2015 +1100 @@ -31,6 +31,7 @@ along with Octave; see the file COPYING. #include "ov-fcn.h" #include "pt.h" #include "pt-pr-code.h" +#include "unwind-prot.h" // Hide the details of the string buffer so that we are less likely to // create a memory leak. @@ -48,3 +49,49 @@ tree::str_print_code (void) return retval; } + +// function from libinterp/parse-tree/oct-parse.cc, not listed in oct-parse.h +octave_value_list eval_string (const std::string&, bool, int&, int); +// Is the current breakpoint condition met? +bool +tree::meets_bp_condition () const +{ + bool retval; + if (bp == 0) + retval = false; + else if (bp->length () == 0) // empty condition always met + retval = true; + else + { + int parse_status = 0; + + unwind_protect frame; + frame.protect_var (buffer_error_messages); + frame.protect_var (Vdebug_on_error); + frame.protect_var (Vdebug_on_warning); + + buffer_error_messages++; + Vdebug_on_error = false; + Vdebug_on_warning = false; + + try + { + octave_value_list val = eval_string (*bp, 1, parse_status, 1); + if (parse_status == 0 && !error_state && val(0).bool_value ()) + retval = true; + else + { + buffer_error_messages--; + error_state = 0; + retval = false; + } + } + catch (const octave_execution_exception& e) + { + retval = true; // break if condition couldn't be evaluated + warning ("Error evaluating breakpoint condition:\n %s", + last_error_message ().c_str ()); + } + } + return retval; +} diff -r b6091735f3f5 libinterp/parse-tree/pt.h --- a/libinterp/parse-tree/pt.h Tue Dec 01 21:59:59 2015 +0100 +++ b/libinterp/parse-tree/pt.h Sun Dec 06 20:50:02 2015 +1100 @@ -29,6 +29,8 @@ along with Octave; see the file COPYING. class octave_function; class tree_walker; +class bp_table; +bool meets_condition (std::string *); // Base class for the parse tree. @@ -38,7 +40,7 @@ tree public: tree (int l = -1, int c = -1) - : line_num (l), column_num (c), bp (false) { } + : line_num (l), column_num (c), bp (NULL) { } virtual ~tree (void) { } @@ -56,11 +58,24 @@ public: column_num = c; } - virtual void set_breakpoint (void) { bp = true; } + virtual void set_breakpoint (std::string condition) + { if (bp) + *bp = condition; + else + bp = new std::string(condition); + } - virtual void delete_breakpoint (void) { bp = false; } + virtual void delete_breakpoint (void) { if (bp) delete bp; bp = NULL; } - bool is_breakpoint (void) const { return bp; } + bool meets_bp_condition (void) const; + + bool is_breakpoint (bool check_active = false) const + { return bp && (!check_active || meets_bp_condition ()); } + + // breakpoint condition, or "0" (i.e., "false") if no breakpoint. + // To distinguish "0" from a disabled breakpoint, test "is_breakpoint" too. + const std::string bp_cond (void) const + { return bp ? *bp : std::string("0"); } std::string str_print_code (void); @@ -73,8 +88,8 @@ private: int line_num; int column_num; - // Breakpoint flag. - bool bp; + // Breakpoint flag: NULL if no breakpoint, or the condition if there is one + std::string *bp; // No copying!