Mercurial > octave
changeset 23137:334119c390b3
move bp_table class to separate file
* libinterp/parse-tree/bp-table.h: Rename from
libinterp/corefcn/debug.h. Change all uses.
* libinterp/parse-tree/bp-table.cc: New file, extracted from debug.h.
* bp-table.h, bp-table.cc (bp_table::parse_dbfunction_params):
Now static member function instead of friend of bp_table class.
Change all uses.
* libinterp/corefcn/module.mk, libinterp/parse-tree/module.mk:
Update.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Thu, 02 Feb 2017 16:16:26 -0500 |
parents | 2165993aed7d |
children | e66b3bfea380 |
files | libgui/src/m-editor/file-editor-tab.cc libinterp/corefcn/debug.cc libinterp/corefcn/debug.h libinterp/corefcn/error.cc libinterp/corefcn/input.cc libinterp/corefcn/module.mk libinterp/corefcn/sighandlers.cc libinterp/corefcn/symtab.cc libinterp/parse-tree/bp-table.cc libinterp/parse-tree/bp-table.h libinterp/parse-tree/module.mk libinterp/parse-tree/pt-eval.cc libinterp/parse-tree/pt-jit.cc libinterp/parse-tree/pt-stmt.cc libinterp/parse-tree/pt-stmt.h |
diffstat | 15 files changed, 1311 insertions(+), 1261 deletions(-) [+] |
line wrap: on
line diff
--- a/libgui/src/m-editor/file-editor-tab.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libgui/src/m-editor/file-editor-tab.cc Thu Feb 02 16:16:26 2017 -0500 @@ -69,8 +69,8 @@ #include "file-ops.h" +#include "bp-table.h" #include "call-stack.h" -#include "debug.h" #include "octave-qt-link.h" #include "version.h" #include "utils.h"
--- a/libinterp/corefcn/debug.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/corefcn/debug.cc Thu Feb 02 16:16:26 2017 -0500 @@ -20,6 +20,7 @@ <http://www.gnu.org/licenses/>. */ + #if defined (HAVE_CONFIG_H) # include "config.h" #endif @@ -32,957 +33,28 @@ #include <set> #include <string> -#include "file-stat.h" -#include "singleton-cleanup.h" +#include "dNDArray.h" +#include "bp-table.h" #include "call-stack.h" #include "defun.h" #include "error.h" +#include "errwarn.h" #include "file-ops.h" #include "help.h" #include "input.h" -#include "pager.h" -#include "octave-link.h" -#include "ovl.h" -#include "utils.h" -#include "parse.h" -#include "symtab.h" -#include "errwarn.h" #include "octave-preserve-stream-state.h" +#include "ov-usr-fcn.h" #include "ov.h" -#include "ov-usr-fcn.h" -#include "ov-fcn.h" -#include "ov-struct.h" +#include "ovl.h" +#include "pager.h" +#include "parse.h" #include "pt-eval.h" -#include "pt-exp.h" -#include "pt-stmt.h" -#include "sighandlers.h" -#include "interpreter.h" #include "unwind-prot.h" #include "utils.h" +#include "utils.h" #include "variables.h" -#include "debug.h" - -// Initialize the singleton object -bp_table *bp_table::instance = 0; - -std::set<std::string> bp_table::errors_that_stop; -std::set<std::string> bp_table::caught_that_stop; -std::set<std::string> 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) -{ - std::string retval; - - octave::sys::file_stat fs (fname); - - if (fs) - { - size_t sz = fs.size (); - - std::ifstream file (fname.c_str (), std::ios::in | std::ios::binary); - - if (file) - { - std::string buf (sz+1, 0); - - file.read (&buf[0], sz+1); - - if (! file.eof ()) - error ("error reading file %s", fname.c_str ()); - - // Expected to read the entire file. - retval = buf; - } - } - - return retval; -} - -static std::deque<size_t> -get_line_offsets (const std::string& buf) -{ - // FIXME: This could maybe be smarter. Is deque the right thing to use here? - - std::deque<size_t> offsets; - - offsets.push_back (0); - - size_t len = buf.length (); - - for (size_t i = 0; i < len; i++) - { - char c = buf[i]; - - if (c == '\r' && ++i < len) - { - c = buf[i]; - - if (c == '\n') - offsets.push_back (i+1); - else - offsets.push_back (i); - } - else if (c == '\n') - offsets.push_back (i+1); - } - - offsets.push_back (len); - - return offsets; -} - -std::string -get_file_line (const std::string& fname, size_t line) -{ - std::string retval; - - static std::string last_fname; - - static std::string buf; - - static std::deque<size_t> offsets; - - if (fname != last_fname) - { - buf = snarf_file (fname); - - offsets = get_line_offsets (buf); - } - - if (line > 0) - line--; - - if (line < offsets.size () - 1) - { - size_t bol = offsets[line]; - size_t eol = offsets[line+1]; - - while (eol > 0 && eol > bol && (buf[eol-1] == '\n' || buf[eol-1] == '\r')) - eol--; - - retval = buf.substr (bol, eol - bol); - } - - return retval; -} - -// Return a pointer to the user-defined function FNAME. If FNAME is -// empty, search backward for the first user-defined function in the -// current call stack. - -static octave_user_code * -get_user_code (const std::string& fname = "") -{ - octave_user_code *dbg_fcn = 0; - - if (fname.empty ()) - dbg_fcn = octave::call_stack::debug_user_code (); - else - { - std::string name = fname; - - if (octave::sys::file_ops::dir_sep_char () != '/' && name[0] == '@') - { - int len = name.length () - 1; // -1: can't have trailing '/' - for (int i = 2; i < len; i++) // 2: can't have @/method - if (name[i] == '/') - name[i] = octave::sys::file_ops::dir_sep_char (); - } - - size_t name_len = name.length (); - - if (! name.empty () && name_len > 2 && name.substr (name_len-2) == ".m") - name = name.substr (0, name_len-2); - - octave_value fcn = symbol_table::find_function (name); - - if (fcn.is_defined () && fcn.is_user_code ()) - dbg_fcn = fcn.user_code_value (); - } - - return dbg_fcn; -} - -#if defined (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& cond) -{ - int nargin = args.length (); - int list_idx = 0; - symbol_name = ""; - lines = bp_table::intmap (); - - if (nargin == 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 tok = dbstop_none; - while (pos < nargin) - { - // allow "in" and "at" to be implicit - if (args(pos).is_string ()) - { - std::string arg = args(pos).string_value (); - if (arg == "in") - { - tok = dbstop_in; - pos++; - } - else if (arg == "at") - { - tok = dbstop_at; - pos++; - } - else if (arg == "if") - { - tok = dbstop_if; - pos++; - } - else if (atoi (args(pos).string_value ().c_str ()) > 0) - tok = dbstop_at; - else - tok = dbstop_in; - } - else - tok = dbstop_at; - - if (pos >= nargin) - error ("%s: '%s' missing argument", who, - (tok == dbstop_in - ? "in" : (tok == dbstop_at ? "at" : "if"))); - - // process the actual arguments - switch (tok) - { - 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; - - 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; - - if (! seen_in) - { - // It was a line number. Get function name from debugger. - if (Vdebugging) - symbol_name = get_user_code ()->profiler_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"); - - // 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 ()); - - 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<int> (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<std::string> *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") - { - octave::Vdebug_on_interrupt = on_off; - } - else if (condition == "naninf") -#if defined (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 <error_ID>" 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) - { - // Matlab stops on both. - octave::Vdebug_on_interrupt = on_off; - } - } - } - - pos = nargin; - } - break; - - default: // dbstop_none should never occur - break; - } - } -} - -/* -%!test -%! dbclear all; # Clear out breakpoints before test -%! dbstop help; -%! dbstop in ls; -%! dbstop help at 100; -%! dbstop in ls 100; -%! dbstop help 201 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, 201, 58, 100]); -%! assert (s.errs, {"Octave:undefined-function"}); -*/ - -// Return true if there is a valid breakpoint table, false otherwise. -// If no table exists, one is created; false is only returned if this fails. -bool -bp_table::instance_ok (void) -{ - if (! instance) - { - instance = new bp_table (); - - if (instance) - singleton_cleanup_list::add (cleanup_instance); - } - - if (! instance) - error ("unable to create breakpoint table!"); - - return true; -} - -// 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 (); - - octave::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<octave_value> W = U.index (static_cast<octave_idx_type> (0)); - if (W.is_empty () || W(0).is_empty () == 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<octave_value> W = U.index (static_cast<octave_idx_type> (0)); - if (W.is_empty () || W(0).is_empty ()) - 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<octave_value> W = U.index (static_cast<octave_idx_type> (0)); - if (W.is_empty () || W(0).is_empty ()) - 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")) - octave::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; - - octave::tree_statement_list *cmds = fcn->body (); - - std::string file = fcn->fcn_file_name (); - - if (cmds) - { - retval = cmds->add_breakpoint (file, line, condition); - - for (auto& idx_line_p : retval) - { - if (idx_line_p.second != 0) - { - // Normalize 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 (), Vfilemarker); - if (s) - bp_set.insert (fname.substr (0, s - fname.c_str ())); - else - bp_set.insert (fname); - found = true; - break; - } - } - } - - return found; -} - -// 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". -// Throws an exception if not valid. -bool -bp_table::condition_valid (const std::string& cond) -{ - if (cond.length () > 0) - { - octave::parser parser (cond + " ;"); // ; to reject partial expr like "y==" - parser.reset (); - int parse_status = parser.run (); - if (parse_status) - error ("dbstop: Cannot parse condition '%s'", cond.c_str ()); - else - { - octave::tree_statement *stmt = 0; - if (! parser.stmt_list) - error ("dbstop: " - "condition is not empty, but has nothing to evaluate"); - else - { - if (parser.stmt_list->length () == 1 - && (stmt = parser.stmt_list->front ()) - && stmt->is_expression ()) - { - octave::tree_expression *expr = stmt->expression (); - if (expr->is_assignment_expression ()) - error ("dbstop: condition cannot be an assignment. " - "Did you mean '=='?"); - } - else - error ("dbstop: condition must be an expression"); - } - } - } - return true; -} - -// Return the sub/nested/main function of MAIN_FCN that contains -// line number LINENO of the source file. -// If END_LINE != 0, *END_LINE is set to last line of the returned function. -static octave_user_code* -find_fcn_by_line (octave_user_code *main_fcn, int lineno, int *end_line = 0) -{ - octave_user_code *retval = 0; - octave_user_code *next_fcn = 0; // 1st function starting after lineno - - // Find innermost nested (or parent) function containing lineno. - int earliest_end = std::numeric_limits<int>::max (); - - std::map<std::string, octave_value> subfcns = main_fcn->subfunctions (); - for (const auto& str_val_p : subfcns) - { - if (str_val_p.second.is_user_function ()) - { - auto *dbg_subfcn = str_val_p.second.user_function_value (); - - // Check if lineno is within dbg_subfcn. - // FIXME: we could break when beginning_line() > lineno, - // but that makes the code "fragile" - // if the order of walking subfcns changes, - // for a minor speed improvement in non-critical code. - if (dbg_subfcn->ending_line () < earliest_end - && dbg_subfcn->ending_line () >= lineno - && dbg_subfcn->beginning_line () <= lineno) - { - earliest_end = dbg_subfcn->ending_line (); - retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end); - } - - // Find the first fcn starting after lineno. - // This is used if line is not inside any function. - if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn) - next_fcn = dbg_subfcn; - } - } - - // The breakpoint is either in the subfunction found above, - // or in the main function, which we check now. - if (main_fcn->is_user_function ()) - { - int e = dynamic_cast<octave_user_function*> (main_fcn)->ending_line (); - if (e >= lineno && e < earliest_end) - retval = main_fcn; - - if (! retval) - retval = next_fcn; - } - else // main_fcn is a script. - { - if (! retval) - retval = main_fcn; - } - - if (end_line != 0 && earliest_end < *end_line) - *end_line = earliest_end; - - return retval; -} - -// Given file name fname, find the subfunction at line and create -// a breakpoint there. Put the system into debug_mode. -bp_table::intmap -bp_table::do_add_breakpoint (const std::string& fname, - const bp_table::intmap& line, - const std::string& condition) -{ - octave_user_code *main_fcn = get_user_code (fname); - - if (! main_fcn) - error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); - - condition_valid (condition); // Throw error if condition not valid. - - intmap retval; - - octave_idx_type len = line.size (); - - for (int i = 0; i < len; i++) - { - const_intmap_iterator m = line.find (i); - - if (m != line.end ()) - { - int lineno = m->second; - - octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno); - - // We've found the right (sub)function. Now insert the breakpoint. - // We insert all breakpoints. - // If multiple are in the same function, we insert multiple times. - intmap ret_one; - if (dbg_fcn - && do_add_breakpoint_1 (dbg_fcn, fname, line, condition, ret_one)) - retval.insert (std::pair<int,int> (i, ret_one.find (i)->second)); - } - } - - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; - - return retval; -} - -int -bp_table::do_remove_breakpoint_1 (octave_user_code *fcn, - const std::string& fname, - const bp_table::intmap& line) -{ - int retval = 0; - - std::string file = fcn->fcn_file_name (); - - octave::tree_statement_list *cmds = fcn->body (); - - // FIXME: move the operation on cmds to the tree_statement_list class? - - if (cmds) - { - octave_value_list results = cmds->list_breakpoints (); - - if (results.length () > 0) - { - octave_idx_type len = line.size (); - - for (int i = 0; i < len; i++) - { - const_intmap_iterator p = line.find (i); - - if (p != line.end ()) - { - int lineno = p->second; - - cmds->delete_breakpoint (lineno); - - if (! file.empty ()) - octave_link::update_breakpoint (false, file, lineno); - } - } - - results = cmds->list_breakpoints (); - - bp_set_iterator it = bp_set.find (fname); - if (results.empty () && it != bp_set.end ()) - bp_set.erase (it); - } - - retval = results.length (); - } - - return retval; -} - -int -bp_table::do_remove_breakpoint (const std::string& fname, - const bp_table::intmap& line) -{ - int retval = 0; - - octave_idx_type len = line.size (); - - if (len == 0) - { - intmap results = remove_all_breakpoints_in_file (fname); - retval = results.size (); - } - else - { - octave_user_code *dbg_fcn = get_user_code (fname); - - if (! dbg_fcn) - error ("remove_breakpoint: unable to find function %s\n", - fname.c_str ()); - - retval = do_remove_breakpoint_1 (dbg_fcn, fname, line); - - // Search subfunctions in the order they appear in the file. - - const std::list<std::string> subfcn_names - = dbg_fcn->subfunction_names (); - - std::map<std::string, octave_value> subfcns - = dbg_fcn->subfunctions (); - - for (const auto& subf_nm : subfcn_names) - { - const auto q = subfcns.find (subf_nm); - - if (q != subfcns.end ()) - { - octave_user_code *dbg_subfcn = q->second.user_code_value (); - - retval += do_remove_breakpoint_1 (dbg_subfcn, fname, line); - } - } - } - - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; - - return retval; -} - -// Remove all breakpoints from a file, including those in subfunctions -bp_table::intmap -bp_table::do_remove_all_breakpoints_in_file (const std::string& fname, - bool silent) -{ - intmap retval; - - octave_user_code *dbg_fcn = get_user_code (fname); - - if (dbg_fcn) - { - std::string file = dbg_fcn->fcn_file_name (); - - octave::tree_statement_list *cmds = dbg_fcn->body (); - - if (cmds) - { - retval = cmds->remove_all_breakpoints (file); - - bp_set_iterator it = bp_set.find (fname); - if (it != bp_set.end ()) - bp_set.erase (it); - } - } - else if (! silent) - error ("remove_all_breakpoint_in_file: " - "unable to find function %s\n", fname.c_str ()); - - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; - - return retval; -} - -void -bp_table::do_remove_all_breakpoints (void) -{ - // Odd loop structure required because delete will invalidate bp_set iterators - for (const_bp_set_iterator it=bp_set.begin (), it_next=it; - it != bp_set.end (); - it=it_next) - { - ++it_next; - remove_all_breakpoints_in_file (*it); - } - - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; -} - -std::string -do_find_bkpt_list (octave_value_list slist, std::string match) -{ - std::string retval; - - for (int i = 0; i < slist.length (); i++) - { - if (slist(i).string_value () == match) - { - retval = slist(i).string_value (); - break; - } - } - - return retval; -} - -bp_table::fname_bp_map -bp_table::do_get_breakpoint_list (const octave_value_list& fname_list) -{ - fname_bp_map retval; - - // make copy since changes may invalidate iters of bp_set. - std::set<std::string> tmp_bp_set = bp_set; - - for (auto& bp_fname : tmp_bp_set) - { - if (fname_list.empty () - || do_find_bkpt_list (fname_list, bp_fname) != "") - { - octave_user_code *f = get_user_code (bp_fname); - - if (f) - { - octave::tree_statement_list *cmds = f->body (); - - // FIXME: move the operation on cmds to the - // tree_statement_list class? - if (cmds) - { - std::list<bp_type> bkpts = cmds->breakpoints_and_conds (); - - if (! bkpts.empty ()) - retval[bp_fname] = bkpts; - } - - // look for breakpoints in subfunctions - const std::list<std::string> subf_nm = f->subfunction_names (); - - std::map<std::string, octave_value> subf = f->subfunctions (); - - for (const auto& subfcn_nm : subf_nm) - { - const auto q = subf.find (subfcn_nm); - - if (q != subf.end ()) - { - octave_user_code *ff = q->second.user_code_value (); - - cmds = ff->body (); - if (cmds) - { - std::list<bp_type> bkpts - = cmds->breakpoints_and_conds (); - - if (! bkpts.empty ()) - retval[bp_fname + Vfilemarker + ff->name ()] = bkpts; - } - } - } - } - } - } - - return retval; -} - static octave_value intmap_to_ov (const bp_table::intmap& line) { @@ -1105,7 +177,8 @@ if (args.length() >= 1 && ! args(0).is_map ()) { // explicit function / line / condition - parse_dbfunction_params ("dbstop", args, symbol_name, lines, condition); + bp_table::parse_dbfunction_params ("dbstop", args, symbol_name, + lines, condition); if (lines.size () == 0) lines[0] = 1; @@ -1224,7 +297,8 @@ int nargin = args.length (); - parse_dbfunction_params ("dbclear", args, symbol_name, lines, dummy); + bp_table::parse_dbfunction_params ("dbclear", args, symbol_name, + lines, dummy); if (nargin == 1 && symbol_name == "all") { @@ -1240,111 +314,6 @@ return ovl (); } -// 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 (const auto& e : errors_that_stop) - { - 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 (const auto& e : caught_that_stop) - { - 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 (const auto& w : warnings_that_stop) - { - 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 (octave::Vdebug_on_interrupt) - { - if (to_screen) - octave_stdout << "stop if interrupt\n"; - else - retval.assign ("intr", octave_value ()); - } - - return retval; -} - DEFUN (dbstatus, args, nargout, doc: /* -*- texinfo -*- @deftypefn {} {} dbstatus
--- a/libinterp/corefcn/debug.h Wed Feb 01 22:22:19 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,203 +0,0 @@ -/* - -Copyright (C) 2001-2016 Ben Sapp - -This file is part of Octave. - -Octave is free software; you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 3 of the License, or -(at your option) any later version. - -Octave is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Octave; see the file COPYING. If not, see -<http://www.gnu.org/licenses/>. - -*/ - -#if ! defined (octave_debug_h) -#define octave_debug_h 1 - -#include "octave-config.h" - -#include <map> -#include <set> -#include "ov.h" -#include "dRowVector.h" - -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 -{ -private: - - bp_table (void) : bp_set () { } - - ~bp_table (void) = default; - -public: - - // mapping from (FIXME: arbitrary index??) to line number of breakpoint - typedef std::map<int, int> intmap; - - typedef intmap::const_iterator const_intmap_iterator; - typedef intmap::iterator intmap_iterator; - - typedef std::map <std::string, intmap> fname_line_map; - - typedef fname_line_map::const_iterator const_fname_line_map_iterator; - typedef fname_line_map::iterator fname_line_map_iterator; - - typedef std::map <std::string, std::list<bp_type> > 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 std::string& condition = bp_empty_string) - { - return instance_ok () - ? instance->do_add_breakpoint (fname, lines, condition) : intmap (); - } - - // Remove a breakpoint from a line in file. - static int remove_breakpoint (const std::string& fname = "", - const intmap& lines = intmap ()) - { - return instance_ok () - ? instance->do_remove_breakpoint (fname, lines) : 0; - } - - // Remove all the breakpoints in a specified file. - static intmap remove_all_breakpoints_in_file (const std::string& fname, - bool silent = false) - { - return instance_ok () - ? instance->do_remove_all_breakpoints_in_file (fname, silent) - : intmap (); - } - - // Remove all the breakpoints registered with octave. - static void remove_all_breakpoints (void) - { - if (instance_ok ()) - instance->do_remove_all_breakpoints (); - } - - // Return all breakpoints. Each element of the map is a vector - // containing the breakpoints corresponding to a given function name. - static fname_bp_map - get_breakpoint_list (const octave_value_list& fname_list) - { - return instance_ok () - ? instance->do_get_breakpoint_list (fname_list) : fname_bp_map (); - } - - static bool - have_breakpoints (void) - { - 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); - - static bool condition_valid (const std::string& cond); - - friend void parse_dbfunction_params (const char *, const octave_value_list&, - std::string&, bp_table::intmap&, - std::string&); - -private: - - typedef std::set<std::string>::const_iterator const_bp_set_iterator; - typedef std::set<std::string>::iterator bp_set_iterator; - - // Set of function (.m file) names containing at least one breakpoint. - std::set<std::string> 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<std::string> errors_that_stop; - static std::set<std::string> caught_that_stop; - static std::set<std::string> 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, const std::string& condition, - intmap& retval); - - 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); - - int do_remove_breakpoint (const std::string&, const intmap& lines); - - intmap do_remove_all_breakpoints_in_file_1 (octave_user_code *fcn, - const std::string& fname); - - intmap do_remove_all_breakpoints_in_file (const std::string& fname, - bool silent); - - void do_remove_all_breakpoints (void); - - fname_bp_map do_get_breakpoint_list (const octave_value_list& fname_list); - - bool do_have_breakpoints (void) { return (! bp_set.empty ()); } -}; - -extern std::string get_file_line (const std::string& fname, size_t line); - -#endif
--- a/libinterp/corefcn/error.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/corefcn/error.cc Thu Feb 02 16:16:26 2017 -0500 @@ -32,8 +32,8 @@ #include <sstream> #include <string> +#include "bp-table.h" #include "call-stack.h" -#include "debug.h" #include "defun.h" #include "error.h" #include "input.h"
--- a/libinterp/corefcn/input.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/corefcn/input.cc Thu Feb 02 16:16:26 2017 -0500 @@ -40,9 +40,9 @@ #include "quit.h" #include "str-vec.h" +#include "bp-table.h" #include "builtin-defun-decls.h" #include "call-stack.h" -#include "debug.h" #include "defun.h" #include "dirfns.h" #include "error.h"
--- a/libinterp/corefcn/module.mk Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/corefcn/module.mk Thu Feb 02 16:16:26 2017 -0500 @@ -24,7 +24,6 @@ libinterp/corefcn/cdisplay.h \ libinterp/corefcn/comment-list.h \ libinterp/corefcn/data.h \ - libinterp/corefcn/debug.h \ libinterp/corefcn/defun-dld.h \ libinterp/corefcn/defun-int.h \ libinterp/corefcn/defun.h \
--- a/libinterp/corefcn/sighandlers.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/corefcn/sighandlers.cc Thu Feb 02 16:16:26 2017 -0500 @@ -41,7 +41,7 @@ #include "singleton-cleanup.h" #include "signal-wrappers.h" -#include "debug.h" +#include "bp-table.h" #include "defun.h" #include "error.h" #include "input.h"
--- a/libinterp/corefcn/symtab.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/corefcn/symtab.cc Thu Feb 02 16:16:26 2017 -0500 @@ -33,7 +33,7 @@ #include "oct-time.h" #include "singleton-cleanup.h" -#include "debug.h" +#include "bp-table.h" #include "defun.h" #include "dirfns.h" #include "input.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/parse-tree/bp-table.cc Thu Feb 02 16:16:26 2017 -0500 @@ -0,0 +1,1078 @@ +/* + +Copyright (C) 2001-2016 Ben Sapp +Copyright (C) 2007-2009 John Swensen + +This file is part of Octave. + +Octave is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +Octave is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Octave; see the file COPYING. If not, see +<http://www.gnu.org/licenses/>. + +*/ + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#include <fstream> +#include <limits> +#include <list> +#include <map> +#include <set> +#include <string> + +#include "file-ops.h" +#include "file-stat.h" +#include "singleton-cleanup.h" + +#include "bp-table.h" +#include "defun-int.h" +#include "call-stack.h" +#include "error.h" +#include "input.h" +#include "oct-map.h" +#include "octave-link.h" +#include "ov-usr-fcn.h" +#include "ov.h" +#include "ovl.h" +#include "pager.h" +#include "parse.h" +#include "pt-eval.h" +#include "pt-exp.h" +#include "pt-stmt.h" +#include "sighandlers.h" +#include "symtab.h" + +// Initialize the singleton object +bp_table *bp_table::instance = 0; + +std::set<std::string> bp_table::errors_that_stop; +std::set<std::string> bp_table::caught_that_stop; +std::set<std::string> 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) +{ + std::string retval; + + octave::sys::file_stat fs (fname); + + if (fs) + { + size_t sz = fs.size (); + + std::ifstream file (fname.c_str (), std::ios::in | std::ios::binary); + + if (file) + { + std::string buf (sz+1, 0); + + file.read (&buf[0], sz+1); + + if (! file.eof ()) + error ("error reading file %s", fname.c_str ()); + + // Expected to read the entire file. + retval = buf; + } + } + + return retval; +} + +static std::deque<size_t> +get_line_offsets (const std::string& buf) +{ + // FIXME: This could maybe be smarter. Is deque the right thing to use here? + + std::deque<size_t> offsets; + + offsets.push_back (0); + + size_t len = buf.length (); + + for (size_t i = 0; i < len; i++) + { + char c = buf[i]; + + if (c == '\r' && ++i < len) + { + c = buf[i]; + + if (c == '\n') + offsets.push_back (i+1); + else + offsets.push_back (i); + } + else if (c == '\n') + offsets.push_back (i+1); + } + + offsets.push_back (len); + + return offsets; +} + +std::string +get_file_line (const std::string& fname, size_t line) +{ + std::string retval; + + static std::string last_fname; + + static std::string buf; + + static std::deque<size_t> offsets; + + if (fname != last_fname) + { + buf = snarf_file (fname); + + offsets = get_line_offsets (buf); + } + + if (line > 0) + line--; + + if (line < offsets.size () - 1) + { + size_t bol = offsets[line]; + size_t eol = offsets[line+1]; + + while (eol > 0 && eol > bol && (buf[eol-1] == '\n' || buf[eol-1] == '\r')) + eol--; + + retval = buf.substr (bol, eol - bol); + } + + return retval; +} + +// Return a pointer to the user-defined function FNAME. If FNAME is +// empty, search backward for the first user-defined function in the +// current call stack. + +octave_user_code * +get_user_code (const std::string& fname) +{ + octave_user_code *dbg_fcn = 0; + + if (fname.empty ()) + dbg_fcn = octave::call_stack::debug_user_code (); + else + { + std::string name = fname; + + if (octave::sys::file_ops::dir_sep_char () != '/' && name[0] == '@') + { + int len = name.length () - 1; // -1: can't have trailing '/' + for (int i = 2; i < len; i++) // 2: can't have @/method + if (name[i] == '/') + name[i] = octave::sys::file_ops::dir_sep_char (); + } + + size_t name_len = name.length (); + + if (! name.empty () && name_len > 2 && name.substr (name_len-2) == ".m") + name = name.substr (0, name_len-2); + + octave_value fcn = symbol_table::find_function (name); + + if (fcn.is_defined () && fcn.is_user_code ()) + dbg_fcn = fcn.user_code_value (); + } + + return dbg_fcn; +} + +// Return true if there is a valid breakpoint table, false otherwise. +// If no table exists, one is created; false is only returned if this fails. +bool +bp_table::instance_ok (void) +{ + if (! instance) + { + instance = new bp_table (); + + if (instance) + singleton_cleanup_list::add (cleanup_instance); + } + + if (! instance) + error ("unable to create breakpoint table!"); + + return true; +} + +// 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 (); + + octave::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<octave_value> W = U.index (static_cast<octave_idx_type> (0)); + if (W.is_empty () || W(0).is_empty () == 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<octave_value> W = U.index (static_cast<octave_idx_type> (0)); + if (W.is_empty () || W(0).is_empty ()) + 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<octave_value> W = U.index (static_cast<octave_idx_type> (0)); + if (W.is_empty () || W(0).is_empty ()) + 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")) + octave::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; + + octave::tree_statement_list *cmds = fcn->body (); + + std::string file = fcn->fcn_file_name (); + + if (cmds) + { + retval = cmds->add_breakpoint (file, line, condition); + + for (auto& idx_line_p : retval) + { + if (idx_line_p.second != 0) + { + // Normalize 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 (), Vfilemarker); + if (s) + bp_set.insert (fname.substr (0, s - fname.c_str ())); + else + bp_set.insert (fname); + found = true; + break; + } + } + } + + return found; +} + +// 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". +// Throws an exception if not valid. +bool +bp_table::condition_valid (const std::string& cond) +{ + if (cond.length () > 0) + { + octave::parser parser (cond + " ;"); // ; to reject partial expr like "y==" + parser.reset (); + int parse_status = parser.run (); + if (parse_status) + error ("dbstop: Cannot parse condition '%s'", cond.c_str ()); + else + { + octave::tree_statement *stmt = 0; + if (! parser.stmt_list) + error ("dbstop: " + "condition is not empty, but has nothing to evaluate"); + else + { + if (parser.stmt_list->length () == 1 + && (stmt = parser.stmt_list->front ()) + && stmt->is_expression ()) + { + octave::tree_expression *expr = stmt->expression (); + if (expr->is_assignment_expression ()) + error ("dbstop: condition cannot be an assignment. " + "Did you mean '=='?"); + } + else + error ("dbstop: condition must be an expression"); + } + } + } + return true; +} + +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 +bp_table::parse_dbfunction_params (const char *who, + const octave_value_list& args, + std::string& symbol_name, + bp_table::intmap& lines, + std::string& cond) +{ + int nargin = args.length (); + int list_idx = 0; + symbol_name = ""; + lines = bp_table::intmap (); + + if (nargin == 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 tok = dbstop_none; + while (pos < nargin) + { + // allow "in" and "at" to be implicit + if (args(pos).is_string ()) + { + std::string arg = args(pos).string_value (); + if (arg == "in") + { + tok = dbstop_in; + pos++; + } + else if (arg == "at") + { + tok = dbstop_at; + pos++; + } + else if (arg == "if") + { + tok = dbstop_if; + pos++; + } + else if (atoi (args(pos).string_value ().c_str ()) > 0) + tok = dbstop_at; + else + tok = dbstop_in; + } + else + tok = dbstop_at; + + if (pos >= nargin) + error ("%s: '%s' missing argument", who, + (tok == dbstop_in + ? "in" : (tok == dbstop_at ? "at" : "if"))); + + // process the actual arguments + switch (tok) + { + 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; + + 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; + + if (! seen_in) + { + // It was a line number. Get function name from debugger. + if (Vdebugging) + symbol_name = get_user_code ()->profiler_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"); + + // 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 ()); + + 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<int> (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<std::string> *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") + { + octave::Vdebug_on_interrupt = on_off; + } + else if (condition == "naninf") + { +#if defined (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 <error_ID>" 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) + { + // Matlab stops on both. + octave::Vdebug_on_interrupt = on_off; + } + } + } + + pos = nargin; + } + break; + + default: // dbstop_none should never occur + break; + } + } +} + +/* +%!test +%! dbclear all; # Clear out breakpoints before test +%! dbstop help; +%! dbstop in ls; +%! dbstop help at 100; +%! dbstop in ls 100; +%! dbstop help 201 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, 201, 58, 100]); +%! assert (s.errs, {"Octave:undefined-function"}); +*/ + +// Return the sub/nested/main function of MAIN_FCN that contains +// line number LINENO of the source file. +// If END_LINE != 0, *END_LINE is set to last line of the returned function. +static octave_user_code* +find_fcn_by_line (octave_user_code *main_fcn, int lineno, int *end_line = 0) +{ + octave_user_code *retval = 0; + octave_user_code *next_fcn = 0; // 1st function starting after lineno + + // Find innermost nested (or parent) function containing lineno. + int earliest_end = std::numeric_limits<int>::max (); + + std::map<std::string, octave_value> subfcns = main_fcn->subfunctions (); + for (const auto& str_val_p : subfcns) + { + if (str_val_p.second.is_user_function ()) + { + auto *dbg_subfcn = str_val_p.second.user_function_value (); + + // Check if lineno is within dbg_subfcn. + // FIXME: we could break when beginning_line() > lineno, + // but that makes the code "fragile" + // if the order of walking subfcns changes, + // for a minor speed improvement in non-critical code. + if (dbg_subfcn->ending_line () < earliest_end + && dbg_subfcn->ending_line () >= lineno + && dbg_subfcn->beginning_line () <= lineno) + { + earliest_end = dbg_subfcn->ending_line (); + retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end); + } + + // Find the first fcn starting after lineno. + // This is used if line is not inside any function. + if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn) + next_fcn = dbg_subfcn; + } + } + + // The breakpoint is either in the subfunction found above, + // or in the main function, which we check now. + if (main_fcn->is_user_function ()) + { + int e = dynamic_cast<octave_user_function*> (main_fcn)->ending_line (); + if (e >= lineno && e < earliest_end) + retval = main_fcn; + + if (! retval) + retval = next_fcn; + } + else // main_fcn is a script. + { + if (! retval) + retval = main_fcn; + } + + if (end_line != 0 && earliest_end < *end_line) + *end_line = earliest_end; + + return retval; +} + +// Given file name fname, find the subfunction at line and create +// a breakpoint there. Put the system into debug_mode. +bp_table::intmap +bp_table::do_add_breakpoint (const std::string& fname, + const bp_table::intmap& line, + const std::string& condition) +{ + octave_user_code *main_fcn = get_user_code (fname); + + if (! main_fcn) + error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); + + condition_valid (condition); // Throw error if condition not valid. + + intmap retval; + + octave_idx_type len = line.size (); + + for (int i = 0; i < len; i++) + { + const_intmap_iterator m = line.find (i); + + if (m != line.end ()) + { + int lineno = m->second; + + octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno); + + // We've found the right (sub)function. Now insert the breakpoint. + // We insert all breakpoints. + // If multiple are in the same function, we insert multiple times. + intmap ret_one; + if (dbg_fcn + && do_add_breakpoint_1 (dbg_fcn, fname, line, condition, ret_one)) + retval.insert (std::pair<int,int> (i, ret_one.find (i)->second)); + } + } + + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; + + return retval; +} + +int +bp_table::do_remove_breakpoint_1 (octave_user_code *fcn, + const std::string& fname, + const bp_table::intmap& line) +{ + int retval = 0; + + std::string file = fcn->fcn_file_name (); + + octave::tree_statement_list *cmds = fcn->body (); + + // FIXME: move the operation on cmds to the tree_statement_list class? + + if (cmds) + { + octave_value_list results = cmds->list_breakpoints (); + + if (results.length () > 0) + { + octave_idx_type len = line.size (); + + for (int i = 0; i < len; i++) + { + const_intmap_iterator p = line.find (i); + + if (p != line.end ()) + { + int lineno = p->second; + + cmds->delete_breakpoint (lineno); + + if (! file.empty ()) + octave_link::update_breakpoint (false, file, lineno); + } + } + + results = cmds->list_breakpoints (); + + bp_set_iterator it = bp_set.find (fname); + if (results.empty () && it != bp_set.end ()) + bp_set.erase (it); + } + + retval = results.length (); + } + + return retval; +} + +int +bp_table::do_remove_breakpoint (const std::string& fname, + const bp_table::intmap& line) +{ + int retval = 0; + + octave_idx_type len = line.size (); + + if (len == 0) + { + intmap results = remove_all_breakpoints_in_file (fname); + retval = results.size (); + } + else + { + octave_user_code *dbg_fcn = get_user_code (fname); + + if (! dbg_fcn) + error ("remove_breakpoint: unable to find function %s\n", + fname.c_str ()); + + retval = do_remove_breakpoint_1 (dbg_fcn, fname, line); + + // Search subfunctions in the order they appear in the file. + + const std::list<std::string> subfcn_names + = dbg_fcn->subfunction_names (); + + std::map<std::string, octave_value> subfcns + = dbg_fcn->subfunctions (); + + for (const auto& subf_nm : subfcn_names) + { + const auto q = subfcns.find (subf_nm); + + if (q != subfcns.end ()) + { + octave_user_code *dbg_subfcn = q->second.user_code_value (); + + retval += do_remove_breakpoint_1 (dbg_subfcn, fname, line); + } + } + } + + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; + + return retval; +} + +// Remove all breakpoints from a file, including those in subfunctions +bp_table::intmap +bp_table::do_remove_all_breakpoints_in_file (const std::string& fname, + bool silent) +{ + intmap retval; + + octave_user_code *dbg_fcn = get_user_code (fname); + + if (dbg_fcn) + { + std::string file = dbg_fcn->fcn_file_name (); + + octave::tree_statement_list *cmds = dbg_fcn->body (); + + if (cmds) + { + retval = cmds->remove_all_breakpoints (file); + + bp_set_iterator it = bp_set.find (fname); + if (it != bp_set.end ()) + bp_set.erase (it); + } + } + else if (! silent) + error ("remove_all_breakpoint_in_file: " + "unable to find function %s\n", fname.c_str ()); + + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; + + return retval; +} + +void +bp_table::do_remove_all_breakpoints (void) +{ + // Odd loop structure required because delete will invalidate bp_set iterators + for (const_bp_set_iterator it=bp_set.begin (), it_next=it; + it != bp_set.end (); + it=it_next) + { + ++it_next; + remove_all_breakpoints_in_file (*it); + } + + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; +} + +std::string +do_find_bkpt_list (octave_value_list slist, std::string match) +{ + std::string retval; + + for (int i = 0; i < slist.length (); i++) + { + if (slist(i).string_value () == match) + { + retval = slist(i).string_value (); + break; + } + } + + return retval; +} + +bp_table::fname_bp_map +bp_table::do_get_breakpoint_list (const octave_value_list& fname_list) +{ + fname_bp_map retval; + + // make copy since changes may invalidate iters of bp_set. + std::set<std::string> tmp_bp_set = bp_set; + + for (auto& bp_fname : tmp_bp_set) + { + if (fname_list.empty () + || do_find_bkpt_list (fname_list, bp_fname) != "") + { + octave_user_code *f = get_user_code (bp_fname); + + if (f) + { + octave::tree_statement_list *cmds = f->body (); + + // FIXME: move the operation on cmds to the + // tree_statement_list class? + if (cmds) + { + std::list<bp_type> bkpts = cmds->breakpoints_and_conds (); + + if (! bkpts.empty ()) + retval[bp_fname] = bkpts; + } + + // look for breakpoints in subfunctions + const std::list<std::string> subf_nm = f->subfunction_names (); + + std::map<std::string, octave_value> subf = f->subfunctions (); + + for (const auto& subfcn_nm : subf_nm) + { + const auto q = subf.find (subfcn_nm); + + if (q != subf.end ()) + { + octave_user_code *ff = q->second.user_code_value (); + + cmds = ff->body (); + if (cmds) + { + std::list<bp_type> bkpts + = cmds->breakpoints_and_conds (); + + if (! bkpts.empty ()) + retval[bp_fname + Vfilemarker + ff->name ()] = bkpts; + } + } + } + } + } + } + + 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 (const auto& e : errors_that_stop) + { + 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 (const auto& e : caught_that_stop) + { + 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 (const auto& w : warnings_that_stop) + { + 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 (octave::Vdebug_on_interrupt) + { + if (to_screen) + octave_stdout << "stop if interrupt\n"; + else + retval.assign ("intr", octave_value ()); + } + + return retval; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libinterp/parse-tree/bp-table.h Thu Feb 02 16:16:26 2017 -0500 @@ -0,0 +1,206 @@ +/* + +Copyright (C) 2001-2016 Ben Sapp + +This file is part of Octave. + +Octave is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +Octave is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Octave; see the file COPYING. If not, see +<http://www.gnu.org/licenses/>. + +*/ + +#if ! defined (octave_bp_table_h) +#define octave_bp_table_h 1 + +#include "octave-config.h" + +#include <list> +#include <map> +#include <set> + +class octave_map; +class octave_user_code; +class octave_value_list; + +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 +{ +private: + + bp_table (void) : bp_set () { } + + ~bp_table (void) = default; + +public: + + // mapping from (FIXME: arbitrary index??) to line number of breakpoint + typedef std::map<int, int> intmap; + + typedef intmap::const_iterator const_intmap_iterator; + typedef intmap::iterator intmap_iterator; + + typedef std::map <std::string, intmap> fname_line_map; + + typedef fname_line_map::const_iterator const_fname_line_map_iterator; + typedef fname_line_map::iterator fname_line_map_iterator; + + typedef std::map <std::string, std::list<bp_type> > 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 std::string& condition = bp_empty_string) + { + return instance_ok () + ? instance->do_add_breakpoint (fname, lines, condition) : intmap (); + } + + // Remove a breakpoint from a line in file. + static int remove_breakpoint (const std::string& fname = "", + const intmap& lines = intmap ()) + { + return instance_ok () + ? instance->do_remove_breakpoint (fname, lines) : 0; + } + + // Remove all the breakpoints in a specified file. + static intmap remove_all_breakpoints_in_file (const std::string& fname, + bool silent = false) + { + return instance_ok () + ? instance->do_remove_all_breakpoints_in_file (fname, silent) + : intmap (); + } + + // Remove all the breakpoints registered with octave. + static void remove_all_breakpoints (void) + { + if (instance_ok ()) + instance->do_remove_all_breakpoints (); + } + + // Return all breakpoints. Each element of the map is a vector + // containing the breakpoints corresponding to a given function name. + static fname_bp_map + get_breakpoint_list (const octave_value_list& fname_list) + { + return instance_ok () + ? instance->do_get_breakpoint_list (fname_list) : fname_bp_map (); + } + + static bool + have_breakpoints (void) + { + 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); + + static bool condition_valid (const std::string& cond); + + static void parse_dbfunction_params (const char *, const octave_value_list&, + std::string&, bp_table::intmap&, + std::string&); + +private: + + typedef std::set<std::string>::const_iterator const_bp_set_iterator; + typedef std::set<std::string>::iterator bp_set_iterator; + + // Set of function (.m file) names containing at least one breakpoint. + std::set<std::string> 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<std::string> errors_that_stop; + static std::set<std::string> caught_that_stop; + static std::set<std::string> 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, const std::string& condition, + intmap& retval); + + 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); + + int do_remove_breakpoint (const std::string&, const intmap& lines); + + intmap do_remove_all_breakpoints_in_file_1 (octave_user_code *fcn, + const std::string& fname); + + intmap do_remove_all_breakpoints_in_file (const std::string& fname, + bool silent); + + void do_remove_all_breakpoints (void); + + fname_bp_map do_get_breakpoint_list (const octave_value_list& fname_list); + + bool do_have_breakpoints (void) { return (! bp_set.empty ()); } +}; + +extern std::string get_file_line (const std::string& fname, size_t line); + +extern octave_user_code *get_user_code (const std::string& fname = ""); + +#endif
--- a/libinterp/parse-tree/module.mk Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/parse-tree/module.mk Thu Feb 02 16:16:26 2017 -0500 @@ -1,4 +1,5 @@ PARSE_TREE_INC = \ + libinterp/parse-tree/bp-table.h \ libinterp/parse-tree/jit-ir.h \ libinterp/parse-tree/jit-typeinfo.h \ libinterp/parse-tree/jit-util.h \ @@ -43,6 +44,7 @@ ## be distributed but not installed. PARSE_TREE_SRC = \ + libinterp/parse-tree/bp-table.cc \ libinterp/parse-tree/jit-ir.cc \ libinterp/parse-tree/jit-typeinfo.cc \ libinterp/parse-tree/jit-util.cc \
--- a/libinterp/parse-tree/pt-eval.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/parse-tree/pt-eval.cc Thu Feb 02 16:16:26 2017 -0500 @@ -31,8 +31,8 @@ #include <fstream> #include <typeinfo> +#include "bp-table.h" #include "call-stack.h" -#include "debug.h" #include "defun.h" #include "error.h" #include "errwarn.h"
--- a/libinterp/parse-tree/pt-jit.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/parse-tree/pt-jit.cc Thu Feb 02 16:16:26 2017 -0500 @@ -29,7 +29,7 @@ # include "config.h" #endif -#include "debug.h" +#include "bp-table.h" #include "defun.h" #include "errwarn.h" #include "ov.h"
--- a/libinterp/parse-tree/pt-stmt.cc Wed Feb 01 22:22:19 2017 +0100 +++ b/libinterp/parse-tree/pt-stmt.cc Thu Feb 02 16:16:26 2017 -0500 @@ -28,13 +28,14 @@ #include "quit.h" +#include "bp-table.h" #include "defun.h" #include "error.h" #include "errwarn.h" -#include "ov.h" +#include "input.h" +#include "oct-lvalue.h" #include "octave-link.h" -#include "oct-lvalue.h" -#include "input.h" +#include "ov.h" #include "pager.h" #include "pt-bp.h" #include "pt-cmd.h" @@ -48,8 +49,6 @@ #include "utils.h" #include "variables.h" -#include "debug.h" - namespace octave { // A list of commands to be executed.