Mercurial > octave
diff libinterp/parse-tree/bp-table.cc @ 24738:3695c2cd69b8
don't use singleton pattern for bp_table
* bp-table.h, bp-table.cc (class bp_table): Don't use singleton
pattern. Move inside octave namespace. Change all uses.
* interpreter-private.h, interpreter-private.cc (__get_bp_table__):
New function.
* interpreter.h, interpreter.cc (interpreter::m_bp_table): New data
member.
(interpreter::interpreter): Initialize it.
(interpreter::get_bp_table): New function.
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Mon, 12 Feb 2018 00:58:31 -0500 |
parents | 194eb4bd202b |
children | 6652d3823428 |
line wrap: on
line diff
--- a/libinterp/parse-tree/bp-table.cc Sun Feb 11 10:26:22 2018 -0800 +++ b/libinterp/parse-tree/bp-table.cc Mon Feb 12 00:58:31 2018 -0500 @@ -36,7 +36,6 @@ #include <iostream> #include "file-ops.h" -#include "singleton-cleanup.h" #include "bp-table.h" #include "defun-int.h" @@ -56,940 +55,919 @@ #include "sighandlers.h" #include "symtab.h" -// Initialize the singleton object -bp_table *bp_table::instance = nullptr; - -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; - -// 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 = nullptr; - - if (fname.empty ()) - { - octave::call_stack& cs = octave::__get_call_stack__ ("get_user_code"); - - dbg_fcn = cs.debug_user_code (); - } - else - { - std::string name = fname; - - if (octave::sys::file_ops::dir_sep_char () != '/' && name[0] == '@') - { - auto beg = name.begin () + 2; // never have @/method - auto end = name.end () - 1; // never have trailing '/' - std::replace (beg, end, '/', octave::sys::file_ops::dir_sep_char ()); - } - - size_t name_len = name.length (); - - if (name_len > 2 && name.substr (name_len-2) == ".m") - name = name.substr (0, name_len-2); - - octave::symbol_table& symtab = - octave::__get_symbol_table__ ("get_user_code"); - - octave_value fcn = symtab.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) +namespace octave { - // 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.isempty () || W(0).isempty ()) - Vdebug_on_error = 1; // like "dbstop if error" with no identifier - else if (! W(0).iscell ()) - 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"); + // Clear all reasons to stop, other than breakpoints. + + void bp_table::dbclear_all_signals (void) + { + Vdebug_on_error = false; + bp_table::m_errors_that_stop.clear (); + + Vdebug_on_caught = false; + bp_table::m_caught_that_stop.clear (); + + Vdebug_on_warning = false; + bp_table::m_warnings_that_stop.clear (); + + octave::Vdebug_on_interrupt = false; + } + + // Process the "warn", "errs", "caught" and "intr" fields for a call of + // "dbstop (p)". - // 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.isempty () || W(0).isempty ()) - Vdebug_on_caught = 1; // like "dbstop if caught error" with no ID - else if (! W(0).iscell ()) - 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"); + 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.isempty () || W(0).isempty ()) + Vdebug_on_error = 1; // like "dbstop if error" with no identifier + else if (! W(0).iscell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + m_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.isempty () || W(0).isempty ()) + Vdebug_on_caught = 1; // like "dbstop if caught error" with no ID + else if (! W(0).iscell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + m_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.isempty () || W(0).isempty ()) - Vdebug_on_warning = 1; // like "dbstop if warning" with no identifier - else if (! W(0).iscell ()) - 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 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.isempty () || W(0).isempty ()) + Vdebug_on_warning = 1; // like "dbstop if warning" with no identifier + else if (! W(0).iscell ()) + fail = true; + else + { + Cell V = W(0).cell_value (); + for (int i = 0; i < V.numel (); i++) + { + m_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; -} + // 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 m_bp_set that fname contains a breakpoint. + + bool bp_table::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); -// 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; + 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 (), '>'); + if (s) + m_bp_set.insert (fname.substr (0, s - fname.c_str ())); + else + m_bp_set.insert (fname); + found = true; + break; + } + } + } - octave::tree_statement_list *cmds = fcn->body (); - - std::string file = fcn->fcn_file_name (); + return found; + } - if (cmds) - { - retval = cmds->add_breakpoint (file, line, condition); + // 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. - 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 (), '>'); - if (s) - bp_set.insert (fname.substr (0, s - fname.c_str ())); - else - bp_set.insert (fname); - found = true; - break; - } - } - } + 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 = nullptr; + if (! parser.m_stmt_list) + error ("dbstop: " + "condition is not empty, but has nothing to evaluate"); + else + { + if (parser.m_stmt_list->length () == 1 + && (stmt = parser.m_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 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) + enum dbstop_args { - 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 = nullptr; - if (! parser.m_stmt_list) - error ("dbstop: " - "condition is not empty, but has nothing to evaluate"); - else - { - if (parser.m_stmt_list->length () == 1 - && (stmt = parser.m_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; -} + 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); -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; - // 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 (pos >= nargin) - error ("%s: '%s' missing argument", who, - (tok == dbstop_in - ? "in" : (tok == dbstop_at ? "at" : "if"))); + 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"); - // 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; + // 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 ()); - 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 (line > 0) + lines[list_idx++] = line; + else + break; // may be "if" + } + else if (args(pos).isnumeric ()) + { + 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; - 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"); + case dbstop_if: + if (seen_in) // conditional breakpoint + { + cond = ""; // remaining arguments form condition + for (; pos < nargin; pos++) + { + if (args(pos).is_string ()) + cond += ' ' + args(pos).string_value (); + else + error ("%s: arguments to 'if' must all be strings", who); + } - // 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 ()); + 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"); - if (line > 0) - lines[list_idx++] = line; - else - break; // may be "if" - } - else if (args(pos).isnumeric ()) - { - const NDArray arg = args(pos).array_value (); + // list of error/warning IDs to update + std::set<std::string> *id_list = nullptr; + bool *stop_flag = nullptr; // Vdebug_on_... flag - 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 += ' ' + 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 = nullptr; - bool *stop_flag = nullptr; // Vdebug_on_... flag + if (condition == "error") + { + id_list = &m_errors_that_stop; + stop_flag = &Vdebug_on_error; + } + else if (condition == "warning") + { + id_list = &m_warnings_that_stop; + stop_flag = &Vdebug_on_warning; + } + else if (condition == "caught" && nargin > pos+1 + && args(pos+1).string_value () == "error") + { + id_list = &m_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 ()); - 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) + { + 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 = true; + } + else + { + id_list->erase (args(pos).string_value ()); + if (id_list->empty ()) + *stop_flag = false; + } + } + 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; - // process ID list for "dbstop if error <error_ID>" etc - if (id_list != nullptr) - { - 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 = true; - } - else - { - id_list->erase (args(pos).string_value ()); - if (id_list->empty ()) - *stop_flag = false; - } - } - 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; - } - } - } + 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 = nullptr) + { + octave_user_code *retval = nullptr; + octave_user_code *next_fcn = nullptr; // 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 (); - pos = nargin; - } - break; + // 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); + } - default: // dbstop_none should never occur - break; - } - } -} + // 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; -/* -%!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"}); -*/ + if (! retval) + retval = next_fcn; + } + else // main_fcn is a script. + { + if (! retval) + retval = main_fcn; + } + + if (end_line && earliest_end < *end_line) + *end_line = earliest_end; + + return retval; + } -// 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 = nullptr) -{ - octave_user_code *retval = nullptr; - octave_user_code *next_fcn = nullptr; // 1st function starting after lineno + // 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::add_breakpoint (const std::string& fname, + const bp_table::intmap& line, + const std::string& condition) + { + octave_user_code *main_fcn = get_user_code (fname); - // Find innermost nested (or parent) function containing lineno. - int earliest_end = std::numeric_limits<int>::max (); + if (! main_fcn) + error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); - 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 (); + condition_valid (condition); // Throw error if condition not valid. + + intmap retval; - // 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); - } + 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); - // 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; - } - } + // 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 + && add_breakpoint_1 (dbg_fcn, fname, line, condition, ret_one)) + retval.insert (std::pair<int,int> (i, ret_one.find (i)->second)); + } + } - // 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; + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; + + return retval; + } - if (! retval) - retval = next_fcn; - } - else // main_fcn is a script. - { - if (! retval) - retval = main_fcn; - } + int bp_table::remove_breakpoint_1 (octave_user_code *fcn, + const std::string& fname, + const bp_table::intmap& line) + { + int retval = 0; - if (end_line != nullptr && earliest_end < *end_line) - *end_line = earliest_end; + std::string file = fcn->fcn_file_name (); - return retval; -} + octave::tree_statement_list *cmds = fcn->body (); + + // FIXME: move the operation on cmds to the tree_statement_list class? -// 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 (cmds) + { + octave_value_list results = cmds->list_breakpoints (); - if (! main_fcn) - error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ()); + if (results.length () > 0) + { + octave_idx_type len = line.size (); - 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 p = line.find (i); - for (int i = 0; i < len; i++) - { - const_intmap_iterator m = line.find (i); + if (p != line.end ()) + { + int lineno = p->second; - if (m != line.end ()) - { - int lineno = m->second; + cmds->delete_breakpoint (lineno); - octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno); + if (! file.empty ()) + octave_link::update_breakpoint (false, file, 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)); - } - } + results = cmds->list_breakpoints (); + + bp_set_iterator it = m_bp_set.find (fname); + if (results.empty () && it != m_bp_set.end ()) + m_bp_set.erase (it); + } - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; + retval = results.length (); + } - return retval; -} + 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; + int bp_table::remove_breakpoint (const std::string& fname, + const bp_table::intmap& line) + { + int retval = 0; - std::string file = fcn->fcn_file_name (); + octave_idx_type len = line.size (); - octave::tree_statement_list *cmds = fcn->body (); - - // FIXME: move the operation on cmds to the tree_statement_list class? + 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 (cmds) - { - octave_value_list results = cmds->list_breakpoints (); + if (! dbg_fcn) + error ("remove_breakpoint: unable to find function %s\n", + fname.c_str ()); - if (results.length () > 0) - { - octave_idx_type len = line.size (); + retval = remove_breakpoint_1 (dbg_fcn, fname, line); - for (int i = 0; i < len; i++) - { - const_intmap_iterator p = line.find (i); + // Search subfunctions in the order they appear in the file. - if (p != line.end ()) - { - int lineno = p->second; + const std::list<std::string> subfcn_names + = dbg_fcn->subfunction_names (); + + std::map<std::string, octave_value> subfcns + = dbg_fcn->subfunctions (); - cmds->delete_breakpoint (lineno); + for (const auto& subf_nm : subfcn_names) + { + const auto q = subfcns.find (subf_nm); - if (! file.empty ()) - octave_link::update_breakpoint (false, file, lineno); - } - } - - results = cmds->list_breakpoints (); + if (q != subfcns.end ()) + { + octave_user_code *dbg_subfcn = q->second.user_code_value (); - bp_set_iterator it = bp_set.find (fname); - if (results.empty () && it != bp_set.end ()) - bp_set.erase (it); - } + retval += remove_breakpoint_1 (dbg_subfcn, fname, line); + } + } + } - retval = results.length (); - } + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; - return retval; -} + return retval; + } + + // Remove all breakpoints from a file, including those in subfunctions. -int -bp_table::do_remove_breakpoint (const std::string& fname, - const bp_table::intmap& line) -{ - int retval = 0; - - octave_idx_type len = line.size (); + bp_table::intmap + bp_table::remove_all_breakpoints_in_file (const std::string& fname, + bool silent) + { + intmap retval; - if (len == 0) - { - intmap results = remove_all_breakpoints_in_file (fname); - retval = results.size (); - } - else - { - octave_user_code *dbg_fcn = get_user_code (fname); + 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 (! dbg_fcn) - error ("remove_breakpoint: unable to find function %s\n", - fname.c_str ()); - - retval = do_remove_breakpoint_1 (dbg_fcn, fname, line); + if (cmds) + { + retval = cmds->remove_all_breakpoints (file); - // Search subfunctions in the order they appear in the file. + bp_set_iterator it = m_bp_set.find (fname); + if (it != m_bp_set.end ()) + m_bp_set.erase (it); + } + } + else if (! silent) + error ("remove_all_breakpoint_in_file: " + "unable to find function %s\n", fname.c_str ()); - const std::list<std::string> subfcn_names - = dbg_fcn->subfunction_names (); - - std::map<std::string, octave_value> subfcns - = dbg_fcn->subfunctions (); + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; - 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 (); + return retval; + } - retval += do_remove_breakpoint_1 (dbg_subfcn, fname, line); - } - } - } + void bp_table::remove_all_breakpoints (void) + { + // Odd loop structure required because delete will invalidate m_bp_set iterators + for (const_bp_set_iterator it = m_bp_set.begin (), it_next = it; + it != m_bp_set.end (); + it = it_next) + { + ++it_next; + remove_all_breakpoints_in_file (*it); + } - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; - - return retval; -} + octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () + || Vdebugging; + } -// 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); + std::string find_bkpt_list (octave_value_list slist, std::string match) + { + std::string retval; - if (dbg_fcn) - { - std::string file = dbg_fcn->fcn_file_name (); + for (int i = 0; i < slist.length (); i++) + { + if (slist(i).string_value () == match) + { + retval = slist(i).string_value (); + break; + } + } - octave::tree_statement_list *cmds = dbg_fcn->body (); + return retval; + } - if (cmds) - { - retval = cmds->remove_all_breakpoints (file); + bp_table::fname_bp_map + bp_table::get_breakpoint_list (const octave_value_list& fname_list) + { + fname_bp_map retval; + + // make copy since changes may invalidate iters of m_bp_set. + std::set<std::string> tmp_bp_set = m_bp_set; - 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 ()); + for (auto& bp_fname : tmp_bp_set) + { + if (fname_list.empty () + || find_bkpt_list (fname_list, bp_fname) != "") + { + octave_user_code *dbg_fcn = get_user_code (bp_fname); - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; - - return retval; -} + if (dbg_fcn) + { + octave::tree_statement_list *cmds = dbg_fcn->body (); -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); - } + // FIXME: move the operation on cmds to the + // tree_statement_list class? + if (cmds) + { + std::list<bp_type> bkpts = cmds->breakpoints_and_conds (); - octave::tree_evaluator::debug_mode = bp_table::have_breakpoints () - || Vdebugging; -} + if (! bkpts.empty ()) + retval[bp_fname] = bkpts; + } -std::string -do_find_bkpt_list (octave_value_list slist, std::string match) -{ - std::string retval; + // look for breakpoints in subfunctions + const std::list<std::string> subf_nm + = dbg_fcn->subfunction_names (); + + std::map<std::string, octave_value> subfcns + = dbg_fcn->subfunctions (); - for (int i = 0; i < slist.length (); i++) - { - if (slist(i).string_value () == match) - { - retval = slist(i).string_value (); - break; - } - } + for (const auto& subfcn_nm : subf_nm) + { + const auto q = subfcns.find (subfcn_nm); - return retval; -} + if (q != subfcns.end ()) + { + octave_user_code *dbg_subfcn + = q->second.user_code_value (); -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; + cmds = dbg_subfcn->body (); + if (cmds) + { + std::list<bp_type> bkpts + = cmds->breakpoints_and_conds (); - for (auto& bp_fname : tmp_bp_set) - { - if (fname_list.empty () - || do_find_bkpt_list (fname_list, bp_fname) != "") - { - octave_user_code *dbg_fcn = get_user_code (bp_fname); + if (! bkpts.empty ()) + { + std::string key + = bp_fname + '>' + dbg_subfcn->name (); - if (dbg_fcn) - { - octave::tree_statement_list *cmds = dbg_fcn->body (); + retval[key] = bkpts; + } + } + } + } + } + } + } - // FIXME: move the operation on cmds to the - // tree_statement_list class? - if (cmds) - { - std::list<bp_type> bkpts = cmds->breakpoints_and_conds (); + return retval; + } - if (! bkpts.empty ()) - retval[bp_fname] = bkpts; - } + // 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 - // look for breakpoints in subfunctions - const std::list<std::string> subf_nm - = dbg_fcn->subfunction_names (); + octave_map bp_table::stop_on_err_warn_status (bool to_screen) + { + octave_map retval; - std::map<std::string, octave_value> subfcns - = dbg_fcn->subfunctions (); - - for (const auto& subfcn_nm : subf_nm) - { - const auto q = subfcns.find (subfcn_nm); - - if (q != subfcns.end ()) - { - octave_user_code *dbg_subfcn - = q->second.user_code_value (); - - cmds = dbg_subfcn->body (); - if (cmds) - { - std::list<bp_type> bkpts - = cmds->breakpoints_and_conds (); + // print dbstop if error information + if (Vdebug_on_error) + { + if (m_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::m_errors_that_stop.size (), 1)); + int i = 0; - if (! bkpts.empty ()) - { - std::string key - = bp_fname + '>' + dbg_subfcn->name (); + for (const auto& e : m_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)); + } + } - retval[key] = bkpts; - } - } - } - } - } - } - } - - return retval; -} + // print dbstop if caught error information + if (Vdebug_on_caught) + { + if (m_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 (m_caught_that_stop.size (), 1)); + int i = 0; -// 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; + for (const auto& e : m_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 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 warning information + if (Vdebug_on_warning) + { + if (m_warnings_that_stop.empty ()) + { + if (to_screen) + octave_stdout << "stop if warning\n"; + else + retval.assign ("warn", octave_value("")); + } + else + { + Cell warn (dim_vector (m_warnings_that_stop.size (), 1)); + int i = 0; - // 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& w : m_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)); + } + } - 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 interrupt information + if (octave::Vdebug_on_interrupt) + { + if (to_screen) + octave_stdout << "stop if interrupt\n"; + else + retval.assign ("intr", octave_value ()); + } + + 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 = nullptr; - // 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; + if (fname.empty ()) + { + octave::call_stack& cs = octave::__get_call_stack__ ("get_user_code"); + + dbg_fcn = cs.debug_user_code (); + } + else + { + std::string name = fname; + + if (octave::sys::file_ops::dir_sep_char () != '/' && name[0] == '@') + { + auto beg = name.begin () + 2; // never have @/method + auto end = name.end () - 1; // never have trailing '/' + std::replace (beg, end, '/', octave::sys::file_ops::dir_sep_char ()); + } - 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)); - } - } + size_t name_len = name.length (); + + if (name_len > 2 && name.substr (name_len-2) == ".m") + name = name.substr (0, name_len-2); + + octave::symbol_table& symtab = + octave::__get_symbol_table__ ("get_user_code"); - // 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 ()); - } + octave_value fcn = symtab.find_function (name); - return retval; + if (fcn.is_defined () && fcn.is_user_code ()) + dbg_fcn = fcn.user_code_value (); + } + + return dbg_fcn; + } } -