GNU Octave  9.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
bp-table.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2001-2024 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #include <algorithm>
31 #include <limits>
32 #include <list>
33 #include <map>
34 #include <set>
35 #include <string>
36 #include <vector>
37 
38 #include "file-ops.h"
39 #include "oct-env.h"
40 
41 #include "bp-table.h"
42 #include "cdef-utils.h"
43 #include "defun-int.h"
44 #include "error.h"
45 #include "event-manager.h"
46 #include "interpreter.h"
47 #include "interpreter-private.h"
48 #include "oct-map.h"
49 #include "ov-usr-fcn.h"
50 #include "ov.h"
51 #include "ovl.h"
52 #include "pager.h"
53 #include "parse.h"
54 #include "pt-eval.h"
55 #include "pt-exp.h"
56 #include "pt-stmt.h"
57 #include "sighandlers.h"
58 
60 
61 class bp_file_info
62 {
63 public:
64 
65  bp_file_info (tree_evaluator& tw, const std::string& file)
66  : m_ok (false), m_file (file), m_dir (), m_fcn (), m_class_name ()
67  {
68  std::string abs_file = sys::env::make_absolute (file);
69 
70  std::string dir = sys::file_ops::dirname (abs_file);
71  std::string fcn = sys::file_ops::tail (abs_file);
72  std::size_t len = fcn.length ();
73  if (len >= 2 && fcn[len-2] == '.' && fcn[len-1] == 'm')
74  fcn = fcn.substr (0, len-2);
75 
76  std::size_t pos = dir.rfind (sys::file_ops::dir_sep_chars ());
77 
78  if (pos != std::string::npos && pos < dir.length () - 1)
79  {
80  if (dir[pos+1] == '@')
81  {
82  m_class_name = dir.substr (pos+1);
83 
84  fcn = sys::file_ops::concat (m_class_name, fcn);
85 
86  dir = dir.substr (0, pos);
87  }
88  }
89 
90  m_dir = dir;
91  m_fcn = fcn;
92 
93  interpreter& interp = tw.get_interpreter ();
94 
95  load_path& lp = interp.get_load_path ();
96 
97  if (lp.contains_file_in_dir (m_file, m_dir))
98  m_ok = true;
99  }
100 
101  std::string file () const { return m_file; }
102  std::string dir () const { return m_fcn; }
103  std::string fcn () const { return m_fcn; }
104  std::string class_name () const { return m_class_name; }
105 
106  bool ok () const { return m_ok; }
107 
108 private:
109 
110  bool m_ok;
111  std::string m_file;
112  std::string m_dir;
113  std::string m_fcn;
114  std::string m_class_name;
115 };
116 
117 // Clear all reasons to stop, other than breakpoints.
118 
119 void
121 {
122  interpreter& interp = m_evaluator.get_interpreter ();
123  error_system& es = interp.get_error_system ();
124 
125  es.debug_on_error (false);
126  bp_table::m_errors_that_stop.clear ();
127 
128  es.debug_on_caught (false);
129  bp_table::m_caught_that_stop.clear ();
130 
131  es.debug_on_warning (false);
132  bp_table::m_warnings_that_stop.clear ();
133 
134  Vdebug_on_interrupt = false;
135 }
136 
137 // Process the "warn", "errs", "caught" and "intr" fields for a call of
138 // "dbstop (p)".
139 
140 void
142 {
143  interpreter& interp = m_evaluator.get_interpreter ();
144  error_system& es = interp.get_error_system ();
145 
146  // process errs
147  // why so many levels of indirection needed?
148  bool fail = false;
149  Cell U = mv.contents ("errs");
150  if (U.numel () != 1)
151  fail = (U.numel () > 1);
152  else
153  {
154  Array<octave_value> W = U.index (0);
155  if (W.isempty () || W(0).isempty ())
156  es.debug_on_error (true); // like "dbstop if error" with no identifier
157  else if (! W(0).iscell ())
158  fail = true;
159  else
160  {
161  Cell V = W(0).cell_value ();
162  for (int i = 0; i < V.numel (); i++)
163  {
164  m_errors_that_stop.insert (V(i).string_value ());
165  es.debug_on_error (true);
166  }
167  }
168  }
169 
170  if (fail)
171  error ("dbstop: invalid 'errs' field");
172 
173  // process caught
174  // why so many levels of indirection needed?
175  fail = false;
176  U = mv.contents ("caught");
177  if (U.numel () != 1)
178  fail = (U.numel () > 1);
179  else
180  {
181  Array<octave_value> W = U.index (0);
182  if (W.isempty () || W(0).isempty ())
183  es.debug_on_caught (true); // like "dbstop if caught error" with no ID
184  else if (! W(0).iscell ())
185  fail = true;
186  else
187  {
188  Cell V = W(0).cell_value ();
189  for (int i = 0; i < V.numel (); i++)
190  {
191  m_caught_that_stop.insert (V(i).string_value ());
192  es.debug_on_caught (true);
193  }
194  }
195  }
196 
197  if (fail)
198  error ("dbstop: invalid 'caught' field");
199 
200  // process warn
201  // why so many levels of indirection needed?
202  fail = false;
203  U = mv.contents ("warn");
204  if (U.numel () != 1)
205  fail = (U.numel () > 1);
206  else
207  {
208  Array<octave_value> W = U.index (0);
209  if (W.isempty () || W(0).isempty ())
210  es.debug_on_warning (true); // like "dbstop if warning" with no identifier
211  else if (! W(0).iscell ())
212  fail = true;
213  else
214  {
215  Cell V = W(0).cell_value ();
216  for (int i = 0; i < V.numel (); i++)
217  {
218  m_warnings_that_stop.insert (V(i).string_value ());
219  es.debug_on_warning (true);
220  }
221  }
222  }
223 
224  if (fail)
225  error ("dbstop: invalid 'warn' field");
226 
227  // process interrupt
228  if (mv.isfield ("intr"))
229  Vdebug_on_interrupt = true;
230 }
231 
232 // Insert a breakpoint in function fcn at line within file fname,
233 // to stop only when condition is true.
234 // Record in m_bp_set that fname contains a breakpoint.
235 
236 bool
237 bp_table::add_breakpoint_1 (octave_user_code *fcn,
238  const std::string& fcn_ident,
239  const bp_table::bp_lines& line,
240  const std::string& condition,
241  bp_table::bp_lines& retval)
242 {
243  bool found = false;
244 
245  tree_statement_list *cmds = fcn->body ();
246 
247  std::string file = fcn->fcn_file_name ();
248 
249  if (cmds)
250  {
251  interpreter& interp = m_evaluator.get_interpreter ();
252 
253  event_manager& evmgr = interp.get_event_manager ();
254 
255  retval = cmds->add_breakpoint (evmgr, file, line, condition);
256 
257  for (auto& lineno : retval)
258  {
259  if (lineno != 0)
260  {
261  // Normalize to store only the file name.
262  // Otherwise, there can be an entry for both
263  // file>subfunction and file, which causes a crash on
264  // dbclear all
265  const char *s = strchr (fcn_ident.c_str (), '>');
266  if (s)
267  m_bp_set.insert (fcn_ident.substr (0, s - fcn_ident.c_str ()));
268  else
269  m_bp_set.insert (fcn_ident);
270 
271  found = true;
272  break;
273  }
274  }
275  }
276 
277  return found;
278 }
279 
280 // Cursory check that cond is a valid condition to use for a breakpoint.
281 // Currently allows conditions with side-effects, like 'y+=10' and 'y++';
282 // it is odd that the former is not flagged by "is_assignment_expression".
283 // Throws an exception if not valid.
284 
285 bool
286 bp_table::condition_valid (const std::string& cond)
287 {
288  if (cond.length () > 0)
289  {
290  // ; to reject partial expr like "y=="
291  parser parser (cond + " ;", m_evaluator.get_interpreter ());
292  parser.reset ();
293  int parse_status = parser.run ();
294  if (parse_status)
295  error ("dbstop: Cannot parse condition '%s'", cond.c_str ());
296  else
297  {
298  tree_statement *stmt = nullptr;
299 
300  std::shared_ptr<tree_statement_list> stmt_list
301  = parser.statement_list ();
302 
303  if (! stmt_list)
304  error ("dbstop: "
305  "condition is not empty, but has nothing to evaluate");
306  else
307  {
308  if (stmt_list->length () == 1
309  && (stmt = stmt_list->front ())
310  && stmt->is_expression ())
311  {
312  tree_expression *expr = stmt->expression ();
313  if (expr->is_assignment_expression ())
314  error ("dbstop: condition cannot be an assignment. "
315  "Did you mean '=='?");
316  }
317  else
318  error ("dbstop: condition must be an expression");
319  }
320  }
321  }
322 
323  return true;
324 }
325 
327 {
332 };
333 
334 // FIXME: This function probably needs to be completely overhauled to
335 // correctly parse the full syntax of the dbstop command and properly
336 // reject incorrect forms.
337 
338 // Parse parameters (args) of dbstop and dbclear commands.
339 // For dbstop, who=="dbstop"; for dbclear, who=="dbclear".
340 // The syntax is: dbstop [[in] symbol] [[at] [method | line [line [...]]]] [if condition]
341 // where the form of condition depends on whether or not a file or line has
342 // been seen. IF symbol and method are specified, then symbol should
343 // be a class name. Otherwise it should be a function name.
344 // Also execute "if [error|warning|interrupt|naninf]" clauses.
345 
346 void
348  const octave_value_list& args,
349  std::string& fcn_name,
350  std::string& class_name,
351  bp_table::bp_lines& lines,
352  std::string& cond)
353 {
354  int nargin = args.length ();
355  fcn_name = "";
356  class_name = "";
357  lines = bp_table::bp_lines ();
358 
359  if (nargin == 0 || ! args(0).is_string ())
360  print_usage (who);
361 
362  // elements already processed
363  bool seen_in = false;
364  bool seen_at = false;
365  bool seen_if = false;
366  int pos = 0;
367  dbstop_args tok = dbstop_none;
368  while (pos < nargin)
369  {
370  // allow "in" and "at" to be implicit
371  if (args(pos).is_string ())
372  {
373  std::string arg = args(pos).string_value ();
374  if (arg == "in")
375  {
376  tok = dbstop_in;
377  pos++;
378  }
379  else if (arg == "at")
380  {
381  tok = dbstop_at;
382  pos++;
383  }
384  else if (arg == "if")
385  {
386  tok = dbstop_if;
387  pos++;
388  }
389  else if (atoi (args(pos).string_value ().c_str ()) > 0)
390  tok = dbstop_at;
391  else
392  tok = dbstop_in;
393  }
394  else
395  tok = dbstop_at;
396 
397  if (pos >= nargin)
398  error ("%s: '%s' missing argument", who,
399  (tok == dbstop_in
400  ? "in" : (tok == dbstop_at ? "at" : "if")));
401 
402  // process the actual arguments
403  switch (tok)
404  {
405  case dbstop_in:
406  fcn_name = args(pos).string_value ();
407  if (seen_in)
408  error ("%s: Too many function names specified -- %s",
409  who, fcn_name.c_str ());
410  else if (seen_at || seen_if)
411  error ("%s: function name must come before line number and 'if'",
412  who);
413  seen_in = true;
414  pos++;
415  break;
416 
417  case dbstop_at:
418  if (seen_at)
419  error ("%s: Only one 'at' clause is allowed -- %s",
420  who, args(pos).string_value ().c_str ());
421  else if (seen_if)
422  error ("%s: line number must come before 'if' clause\n", who);
423  seen_at = true;
424 
425  if (seen_if)
426  error ("%s: line number must come before 'if' clause\n", who);
427  else if (seen_in)
428  {
429  std::string arg = args(pos).string_value ();
430 
431  // FIXME: we really want to distinguish number
432  // vs. method name here.
433 
434  if (atoi (arg.c_str ()) == 0)
435  {
436  // We have class and function names but already
437  // stored the class name in fcn_name.
438  class_name = fcn_name;
439  fcn_name = arg;
440  pos++;
441  break;
442  }
443 
444  }
445  else
446  {
447  // It was a line number. Get function name from debugger.
448  if (m_evaluator.in_debug_repl ())
449  fcn_name = m_evaluator.get_user_code ()->profiler_name ();
450  else
451  error ("%s: function name must come before line number "
452  "and 'if'", who);
453  seen_in = true;
454  }
455 
456  // Read a list of line numbers (or arrays thereof)
457  for ( ; pos < nargin; pos++)
458  {
459  if (args(pos).is_string ())
460  {
461  int line = atoi (args(pos).string_value ().c_str ());
462 
463  if (line > 0)
464  lines.insert (line);
465  else
466  break; // may be "if" or a method name
467  }
468  else if (args(pos).isnumeric ())
469  {
470  const NDArray arg = args(pos).array_value ();
471 
472  for (octave_idx_type j = 0; j < arg.numel (); j++)
473  lines.insert (static_cast<int> (arg.elem (j)));
474  }
475  else
476  error ("%s: Invalid argument type %s",
477  who, args(pos).type_name ().c_str ());
478  }
479  break;
480 
481  case dbstop_if:
482  if (seen_in) // conditional breakpoint
483  {
484  cond = ""; // remaining arguments form condition
485  for (; pos < nargin; pos++)
486  {
487  if (args(pos).is_string ())
488  cond += ' ' + args(pos).string_value ();
489  else
490  error ("%s: arguments to 'if' must all be strings", who);
491  }
492 
493  cond = cond.substr (1); // omit initial space
494  }
495  else // stop on event (error, warning, interrupt, NaN/inf)
496  {
497  std::string condition = args(pos).string_value ();
498  bool on_off = ! strcmp (who, "dbstop");
499 
500  // FIXME: the following seems a bit messy in the way it
501  // duplicates checks on CONDITION.
502 
503  if (condition == "error")
504  process_id_list (who, condition, args, nargin, pos, on_off,
505  m_errors_that_stop);
506  else if (condition == "warning")
507  process_id_list (who, condition, args, nargin, pos, on_off,
508  m_warnings_that_stop);
509  else if (condition == "caught" && nargin > pos+1
510  && args(pos+1).string_value () == "error")
511  {
512  pos++;
513  process_id_list (who, condition, args, nargin, pos, on_off,
514  m_caught_that_stop);
515  }
516  else if (condition == "interrupt")
517  {
518  Vdebug_on_interrupt = on_off;
519  }
520  else if (condition == "naninf")
521  {
522 #if defined (DBSTOP_NANINF)
523  Vdebug_on_naninf = on_off;
524  enable_fpe (on_off);
525 #else
526  warning ("%s: condition '%s' not yet supported",
527  who, condition.c_str ());
528 #endif
529  }
530  else
531  error ("%s: invalid condition %s",
532  who, condition.c_str ());
533 
534  pos = nargin;
535  }
536  break;
537 
538  default: // dbstop_none should never occur
539  break;
540  }
541  }
542 }
543 
544 /*
545 %!test
546 %! if (isguirunning ())
547 %! orig_show_dbg = __event_manager_gui_preference__ ("editor/show_dbg_file",
548 %! "false");
549 %! endif
550 %! unwind_protect
551 %! dbclear all; # Clear out breakpoints before test
552 %! dbstop help;
553 %! dbstop in ls;
554 %! dbstop help at 105; # 105 is a comment; code line is at 106
555 %! dbstop in ls 123; # 123 is a comment; code line is at 126
556 %! dbstop help 204 if a==5;
557 %! dbstop if error Octave:undefined-function;
558 %! s = dbstatus;
559 %! dbclear all;
560 %! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"});
561 %! assert ([s.bkpt(:).line], [56, 106, 208, 63, 126]);
562 %! assert (s.errs, {"Octave:undefined-function"});
563 %! unwind_protect_cleanup
564 %! if (isguirunning ())
565 %! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg);
566 %! endif
567 %! end_unwind_protect
568 */
569 
570 void
571 bp_table::set_stop_flag (const char *who, const std::string& condition,
572  bool on_off)
573 {
574  interpreter& interp = m_evaluator.get_interpreter ();
575  error_system& es = interp.get_error_system ();
576 
577  if (condition == "error")
578  es.debug_on_error (on_off);
579  else if (condition == "warning")
580  es.debug_on_warning (on_off);
581  else if (condition == "caught")
582  es.debug_on_caught (on_off);
583  else
584  error ("%s: internal error in set_stop_flag", who);
585 }
586 
587 void
588 bp_table::process_id_list (const char *who,
589  const std::string& condition,
590  const octave_value_list& args,
591  int nargin, int& pos, bool on_off,
592  std::set<std::string>& id_list)
593 {
594  pos++;
595 
596  if (nargin > pos) // only affect a single error ID
597  {
598  if (! args(pos).is_string () || nargin > pos+1)
599  error ("%s: ID must be a single string", who);
600  else if (on_off)
601  {
602  id_list.insert (args(pos).string_value ());
603  set_stop_flag (who, condition, true);
604  }
605  else
606  {
607  id_list.erase (args(pos).string_value ());
608  if (id_list.empty ())
609  set_stop_flag (who, condition, false);
610  }
611  }
612  else // unqualified. Turn all on or off
613  {
614  id_list.clear ();
615  set_stop_flag (who, condition, on_off);
616 
617  if (condition == "error")
618  {
619  // Matlab stops on both.
620  Vdebug_on_interrupt = on_off;
621  }
622  }
623 }
624 
625 // Return the sub/nested/main function of MAIN_FCN that contains
626 // line number LINENO of the source file.
627 // If END_LINE != 0, *END_LINE is set to last line of the returned function.
628 
629 static octave_user_code *
630 find_fcn_by_line (octave_user_code *main_fcn,
631  int lineno,
632  int *end_line = nullptr)
633 {
634  octave_user_code *retval = nullptr;
635  octave_user_code *next_fcn = nullptr; // 1st function starting after lineno
636 
637  // Find innermost nested (or parent) function containing lineno.
638  int earliest_end = std::numeric_limits<int>::max ();
639 
640  std::map<std::string, octave_value> subfcns = main_fcn->subfunctions ();
641  for (const auto& str_val_p : subfcns)
642  {
643  if (str_val_p.second.is_user_function ())
644  {
645  auto *dbg_subfcn = str_val_p.second.user_function_value ();
646 
647  // Check if lineno is within dbg_subfcn.
648  // FIXME: we could break when beginning_line() > lineno,
649  // but that makes the code "fragile"
650  // if the order of walking subfcns changes,
651  // for a minor speed improvement in non-critical code.
652  if (dbg_subfcn->ending_line () < earliest_end
653  && dbg_subfcn->ending_line () >= lineno
654  && dbg_subfcn->beginning_line () <= lineno)
655  {
656  earliest_end = dbg_subfcn->ending_line ();
657  retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end);
658  }
659 
660  // Find the first fcn starting after lineno.
661  // This is used if line is not inside any function.
662  if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn)
663  next_fcn = dbg_subfcn;
664  }
665  }
666 
667  // The breakpoint is either in the subfunction found above,
668  // or in the main function, which we check now.
669  if (main_fcn->is_user_function ())
670  {
671  int e = dynamic_cast<octave_user_function *> (main_fcn)->ending_line ();
672  if (e >= lineno && e < earliest_end)
673  retval = main_fcn;
674 
675  if (! retval)
676  retval = next_fcn;
677  }
678  else // main_fcn is a script.
679  {
680  if (! retval)
681  retval = main_fcn;
682  }
683 
684  if (end_line && earliest_end < *end_line)
685  *end_line = earliest_end;
686 
687  return retval;
688 }
689 
690 // Given function identifier fcn_ident, find the subfunction at line and create
691 // a breakpoint there. Put the system into debug_mode.
692 int
693 bp_table::add_breakpoint_in_function (const std::string& fcn_ident,
694  int line,
695  const std::string& condition)
696 {
697  bp_lines line_info;
698  line_info.insert (line);
699 
700  bp_lines result
701  = add_breakpoints_in_function (fcn_ident, line_info, condition);
702 
703  return result.empty () ? 0 : *(result.begin ());
704 }
705 
706 // A class that encapsulates the "get_user_code" operation.
707 // This operation changes depending on the symbol being a function or classdef.
708 // Octave used to assume everything was a function, one per file.
709 // Classdefs can have several functions in a single file.
710 class user_code_provider
711 {
712 public:
713  user_code_provider (const std::string& who, const std::string& fcn_ident,
714  octave_user_code *pfcn, bool silent = false)
715  : m_fcn (nullptr), m_is_valid (false)
716  {
717  m_fcn = pfcn;
718  if (m_fcn && ! (m_fcn->is_classdef_method ()
719  || m_fcn->is_classdef_constructor ()))
720  {
721  // Already have the usercode, no need to do anything.
722  m_is_valid = true;
723  return;
724  }
725 
726  // If we get here, try to get a classdef to support getting method by
727  // line number.
728 
729  // Extract class name from function identifier
730  std::string class_name = fcn_ident;
731  const char *s = strchr (fcn_ident.c_str (), '/');
732  if (s && fcn_ident[0] == '@')
733  class_name = fcn_ident.substr (1, s - fcn_ident.c_str () - 1);
734 
735  m_cls = lookup_class (class_name, false);
736  m_is_valid = m_cls.ok () && (m_cls.get_name () == class_name);
737  if (m_is_valid)
738  populate_function_cache ();
739  else if (! silent)
740  error ("%s: unable to find function '%s'\n", who.c_str (),
741  fcn_ident.c_str ());
742 
743  }
744 
745  octave_user_code * operator () (int line = 1)
746  {
747  if (! is_valid ())
748  return nullptr;
749 
750  if (is_function ())
751  return find_fcn_by_line (m_fcn, line);
752 
753  return m_cls.get_method (line).user_code_value (true);
754  }
755 
756  bool is_function () const { return m_fcn; }
757 
758  bool is_valid () const { return m_is_valid; }
759 
760  size_t number_of_functions () const
761  {
762  if (! is_valid ())
763  return 0;
764 
765  if (is_function ())
766  return 1;
767 
768  return m_methods_cache.size ();
769  }
770 
771  octave_user_code * get_function (const size_t i) const
772  {
773  if (! is_valid ())
774  return nullptr;
775 
776  if (is_function ())
777  return m_fcn;
778 
779  if (i < m_methods_cache.size ())
780  return m_methods_cache[i];
781 
782  return nullptr;
783  }
784 
785 private:
786  void populate_function_cache ()
787  {
788  if (m_methods_cache.empty ())
789  {
790  // Not the most effecient, but reuses code:
791  const std::map<std::string, cdef_method>& map
792  = m_cls.get_method_map (false, true);
793  for (auto iter = map.cbegin (); iter != map.cend (); ++iter)
794  {
795  octave_user_code *fcn
796  = m_cls.get_method (iter->first).user_code_value (true);
797  if (fcn != nullptr)
798  m_methods_cache.push_back (fcn);
799  }
800  }
801  }
802 
803 private:
804  std::vector<octave_user_code *> m_methods_cache;
805  cdef_class m_cls;
806  octave_user_code *m_fcn;
807  bool m_is_valid;
808 };
809 
810 // Given file name fname, find the subfunction at line and create
811 // a breakpoint there. Put the system into debug_mode.
813 bp_table::add_breakpoints_in_function (const std::string& fcn_ident,
814  const bp_table::bp_lines& lines,
815  const std::string& condition)
816 {
817  user_code_provider user_code ("add_breakpoints_in_function", fcn_ident,
818  m_evaluator.get_user_code (fcn_ident));
819 
820  condition_valid (condition); // Throw error if condition not valid.
821 
822  bp_lines retval;
823 
824  for (const auto& lineno : lines)
825  {
826  octave_user_code *dbg_fcn = user_code (lineno);
827 
828  // We've found the right (sub)function. Now insert the breakpoint.
829  bp_lines line_info;
830  line_info.insert (lineno);
831 
832  bp_lines ret_one;
833 
834  std::string ident = fcn_ident;
835  if (! user_code.is_function () && fcn_ident[0] != '@' && dbg_fcn)
836  {
837  // identifier of the form @class_name/method_name
838  ident = "@" + fcn_ident + "/" + dbg_fcn->name ();
839  }
840 
841  if (dbg_fcn && add_breakpoint_1 (dbg_fcn, ident, line_info,
842  condition, ret_one))
843  {
844  if (! ret_one.empty ())
845  {
846  int line = *(ret_one.begin ());
847 
848  if (line)
849  retval.insert (line);
850  }
851  }
852  }
853 
854  m_evaluator.reset_debug_state ();
855 
856  return retval;
857 }
858 
859 int
860 bp_table::add_breakpoint_in_file (const std::string& file,
861  int line,
862  const std::string& condition)
863 {
864  // Duplicates what the GUI was doing previously, but this action
865  // should not be specific to the GUI.
866 
867  bp_file_info info (m_evaluator, file);
868 
869  if (! info.ok ())
870  return 0;
871 
872  std::string fcn_ident;
873  if (info.class_name ().empty ())
874  fcn_ident = info.fcn ();
875  else
876  fcn_ident = "@" + info.class_name () + "/" + info.fcn ();
877 
878  return add_breakpoint_in_function (fcn_ident, line, condition);
879 }
880 
882 bp_table::add_breakpoints_in_file (const std::string& file,
883  const bp_lines& lines,
884  const std::string& condition)
885 {
886  // Duplicates what the GUI was doing previously, but this action
887  // should not be specific to the GUI.
888 
889  bp_file_info info (m_evaluator, file);
890 
891  if (! info.ok ())
892  return bp_lines ();
893 
894  std::string fcn_ident;
895  if (info.class_name ().empty ())
896  fcn_ident = info.fcn ();
897  else
898  fcn_ident = "@" + info.class_name () + "/" + info.fcn ();
899 
900  return add_breakpoints_in_function (fcn_ident, lines, condition);
901 }
902 
903 int
904 bp_table::remove_breakpoint_1 (octave_user_code *fcn,
905  const std::string& fcn_ident,
906  const bp_table::bp_lines& lines)
907 {
908  int retval = 0;
909 
910  std::string file = fcn->fcn_file_name ();
911 
912  tree_statement_list *cmds = fcn->body ();
913 
914  // FIXME: move the operation on cmds to the tree_statement_list class?
915 
916  if (cmds)
917  {
918  octave_value_list results = cmds->list_breakpoints ();
919 
920  if (results.length () > 0)
921  {
922  interpreter& interp = m_evaluator.get_interpreter ();
923 
924  event_manager& evmgr = interp.get_event_manager ();
925 
926  for (const auto& lineno : lines)
927  {
928  cmds->delete_breakpoint (lineno);
929 
930  if (! file.empty ())
931  evmgr.update_breakpoint (false, file, lineno);
932  }
933 
934  results = cmds->list_breakpoints ();
935 
936  auto it = m_bp_set.find (fcn_ident);
937  if (results.empty () && it != m_bp_set.end ())
938  m_bp_set.erase (it);
939  }
940 
941  retval = results.length ();
942  }
943 
944  return retval;
945 }
946 
947 int
948 bp_table::remove_breakpoint_from_function (const std::string& fcn_ident,
949  int line)
950 {
951  bp_lines line_info;
952  line_info.insert (line);
953 
954  return remove_breakpoints_from_function (fcn_ident, line_info);
955 }
956 
957 int
958 bp_table::remove_breakpoints_from_function (const std::string& fcn_ident,
959  const bp_table::bp_lines& lines)
960 {
961  int retval = 0;
962 
963  if (lines.empty ())
964  {
965  bp_lines results = remove_all_breakpoints_from_function (fcn_ident);
966  retval = results.size ();
967  }
968  else
969  {
970  octave_user_code *dbg_fcn = m_evaluator.get_user_code (fcn_ident);
971  user_code_provider user_code ("remove_breakpoints_from_function",
972  fcn_ident, dbg_fcn);
973 
974  // Remove all breakpoints from all functions
975  for (const auto line : lines)
976  {
977  octave_user_code *fcn = user_code (line);
978  std::string file = fcn->fcn_file_name ();
979 
980  tree_statement_list *cmds = fcn->body ();
981  if (cmds)
982  {
983  octave_value_list results = cmds->list_breakpoints ();
984 
985  if (results.length () > 0)
986  {
987  interpreter& interp = m_evaluator.get_interpreter ();
988  event_manager& evmgr = interp.get_event_manager ();
989 
990  cmds->delete_breakpoint (line);
991 
992  if (! file.empty ())
993  evmgr.update_breakpoint (false, file, line);
994  }
995  }
996  }
997 
998  // Remove all breakpoints from all subfunctions
999  if (dbg_fcn != nullptr)
1000  {
1001  // Search subfunctions in the order they appear in the file.
1002  const std::list<std::string> subfcn_names
1003  = dbg_fcn->subfunction_names ();
1004 
1005  std::map<std::string, octave_value> subfcns
1006  = dbg_fcn->subfunctions ();
1007 
1008  for (const auto& subf_nm : subfcn_names)
1009  {
1010  const auto q = subfcns.find (subf_nm);
1011 
1012  if (q != subfcns.end ())
1013  {
1014  octave_user_code *dbg_subfcn
1015  = q->second.user_code_value ();
1016 
1017  retval += remove_breakpoint_1 (dbg_subfcn, fcn_ident, lines);
1018  }
1019  }
1020  }
1021  // Remove file from breakpoint set if no breakpoints remain
1022  octave_value_list fname_list = {fcn_ident};
1023  const bool no_breakpoints
1024  = get_breakpoint_list (fname_list).empty ();
1025  auto iter = m_bp_set.find (fcn_ident);
1026  if (no_breakpoints && iter != m_bp_set.end ())
1027  m_bp_set.erase (iter);
1028  }
1029 
1030  m_evaluator.reset_debug_state ();
1031 
1032  return retval;
1033 }
1034 
1035 // Remove all breakpoints from a file, including those in subfunctions.
1036 
1039  bool silent)
1040 {
1041  bp_lines retval;
1042 
1043  octave_user_code *fcn = m_evaluator.get_user_code (fcn_ident);
1044  user_code_provider user_code ("remove_all_breakpoints_from_function",
1045  fcn_ident, fcn, silent);
1046 
1047  if (user_code.is_valid ())
1048  {
1049  for (size_t i = 0; i != user_code.number_of_functions (); ++i)
1050  {
1051  octave_user_code *dbg_fcn = user_code.get_function (i);
1052  std::string file = dbg_fcn->fcn_file_name ();
1053 
1054  tree_statement_list *cmds = dbg_fcn->body ();
1055 
1056  if (cmds)
1057  {
1058  interpreter& interp = m_evaluator.get_interpreter ();
1059 
1060  event_manager& evmgr = interp.get_event_manager ();
1061 
1062  retval = cmds->remove_all_breakpoints (evmgr, file);
1063  }
1064  }
1065 
1066  auto it = m_bp_set.find (fcn_ident);
1067  if (it != m_bp_set.end ())
1068  m_bp_set.erase (it);
1069  }
1070 
1071  m_evaluator.reset_debug_state ();
1072 
1073  return retval;
1074 }
1075 
1076 int
1077 bp_table::remove_breakpoint_from_file (const std::string& file, int line)
1078 {
1079  // Duplicates what the GUI was doing previously, but this action
1080  // should not be specific to the GUI.
1081 
1082  bp_file_info info (m_evaluator, file);
1083 
1084  if (! info.ok ())
1085  return 0;
1086 
1087  return remove_breakpoint_from_function (info.fcn (), line);
1088 }
1089 
1090 int
1092  const bp_lines& lines)
1093 {
1094  // Duplicates what the GUI was doing previously, but this action
1095  // should not be specific to the GUI.
1096 
1097  bp_file_info info (m_evaluator, file);
1098 
1099  if (! info.ok ())
1100  return 0;
1101 
1102  return remove_breakpoints_from_function (info.fcn (), lines);
1103 }
1104 
1107  bool silent)
1108 {
1109  // Duplicates what the GUI was doing previously, but this action
1110  // should not be specific to the GUI.
1111 
1112  bp_file_info info (m_evaluator, file);
1113 
1114  if (! info.ok ())
1115  return bp_lines ();
1116 
1117  return remove_all_breakpoints_from_function (info.fcn (), silent);
1118 }
1119 
1120 void
1122 {
1123  // Odd loop structure required because delete will invalidate
1124  // m_bp_set iterators.
1125  for (auto it = m_bp_set.cbegin (), it_next = it;
1126  it != m_bp_set.cend ();
1127  it = it_next)
1128  {
1129  ++it_next;
1131  }
1132 
1133  m_evaluator.reset_debug_state ();
1134 }
1135 
1136 std::string
1137 find_bkpt_list (octave_value_list slist, std::string match)
1138 {
1139  std::string retval;
1140 
1141  for (int i = 0; i < slist.length (); i++)
1142  {
1143  if (slist(i).string_value () == match)
1144  {
1145  retval = slist(i).string_value ();
1146  break;
1147  }
1148  }
1149 
1150  return retval;
1151 }
1152 
1155 {
1156  fname_bp_map retval;
1157 
1158  // make copy since changes may invalidate iters of m_bp_set.
1159  std::set<std::string> tmp_bp_set = m_bp_set;
1160 
1161  for (auto& bp_fname : tmp_bp_set)
1162  {
1163  if (fname_list.empty ()
1164  || find_bkpt_list (fname_list, bp_fname) != "")
1165  {
1166  octave_user_code *dbg_fcn = m_evaluator.get_user_code (bp_fname);
1167  user_code_provider user_code ("get_breakpoint_list", bp_fname,
1168  dbg_fcn);
1169  // Gather breakpoints from all functions in the file
1170  std::list<bp_type> all_bkpts;
1171  std::list<bp_type>::iterator it (all_bkpts.begin ());
1172  for (size_t i = 0; i != user_code.number_of_functions (); ++i)
1173  {
1174  octave_user_code *fcn = user_code.get_function (i);
1175  if (fcn)
1176  {
1177  tree_statement_list *cmds = fcn->body ();
1178  // FIXME: move the operation on cmds to the
1179  // tree_statement_list class?
1180  if (cmds)
1181  {
1182  const std::list<bp_type>& bp
1183  = cmds->breakpoints_and_conds ();
1184  if (!bp.empty())
1185  it = all_bkpts.insert (it, bp.cbegin (),
1186  bp.cend ());
1187  }
1188  }
1189  }
1190 
1191  if (! all_bkpts.empty ())
1192  retval[bp_fname] = all_bkpts;
1193 
1194  // look for breakpoints in subfunctions
1195  // Assuming classdefs can't have subfunctions
1196  if (dbg_fcn != nullptr)
1197  {
1198  const std::list<std::string> subf_nm
1199  = dbg_fcn->subfunction_names ();
1200 
1201  std::map<std::string, octave_value> subfcns
1202  = dbg_fcn->subfunctions ();
1203 
1204  for (const auto& subfcn_nm : subf_nm)
1205  {
1206  const auto q = subfcns.find (subfcn_nm);
1207 
1208  if (q != subfcns.end ())
1209  {
1210  octave_user_code *dbg_subfcn
1211  = q->second.user_code_value ();
1212 
1213  tree_statement_list *cmds = dbg_subfcn->body ();
1214  if (cmds)
1215  {
1216  std::list<bp_type> bkpts
1217  = cmds->breakpoints_and_conds ();
1218 
1219  if (! bkpts.empty ())
1220  {
1221  std::string key
1222  = bp_fname + '>' + dbg_subfcn->name ();
1223 
1224  retval[key] = bkpts;
1225  }
1226  }
1227  }
1228  }
1229  }
1230  }
1231  }
1232 
1233  return retval;
1234 }
1235 
1236 // Report the status of "dbstop if error ..." and "dbstop if warning ..."
1237 // If to_screen is true, the output goes to octave_stdout; otherwise it is
1238 // returned.
1239 // If dbstop if error is true but no explicit IDs are specified, the return
1240 // value will have an empty field called "errs". If IDs are specified, the
1241 // "errs" field will have a row per ID. If dbstop if error is false, there
1242 // is no "errs" field. The "warn" field is set similarly by dbstop if warning
1243 
1244 octave_map
1246 {
1247  octave_map retval;
1248 
1249  interpreter& interp = m_evaluator.get_interpreter ();
1250  error_system& es = interp.get_error_system ();
1251 
1252  // print dbstop if error information
1253  if (es.debug_on_error ())
1254  {
1255  if (m_errors_that_stop.empty ())
1256  {
1257  if (to_screen)
1258  octave_stdout << "stop if error\n";
1259  else
1260  retval.assign ("errs", octave_value (""));
1261  }
1262  else
1263  {
1264  Cell errs (dim_vector (bp_table::m_errors_that_stop.size (), 1));
1265  int i = 0;
1266 
1267  for (const auto& e : m_errors_that_stop)
1268  {
1269  if (to_screen)
1270  octave_stdout << "stop if error " << e << "\n";
1271  else
1272  errs(i++) = e;
1273  }
1274  if (! to_screen)
1275  retval.assign ("errs", octave_value (errs));
1276  }
1277  }
1278 
1279  // print dbstop if caught error information
1280  if (es.debug_on_caught ())
1281  {
1282  if (m_caught_that_stop.empty ())
1283  {
1284  if (to_screen)
1285  octave_stdout << "stop if caught error\n";
1286  else
1287  retval.assign ("caught", octave_value (""));
1288  }
1289  else
1290  {
1291  Cell errs (dim_vector (m_caught_that_stop.size (), 1));
1292  int i = 0;
1293 
1294  for (const auto& e : m_caught_that_stop)
1295  {
1296  if (to_screen)
1297  octave_stdout << "stop if caught error " << e << "\n";
1298  else
1299  errs(i++) = e;
1300  }
1301  if (! to_screen)
1302  retval.assign ("caught", octave_value (errs));
1303  }
1304  }
1305 
1306  // print dbstop if warning information
1307  if (es.debug_on_warning ())
1308  {
1309  if (m_warnings_that_stop.empty ())
1310  {
1311  if (to_screen)
1312  octave_stdout << "stop if warning\n";
1313  else
1314  retval.assign ("warn", octave_value (""));
1315  }
1316  else
1317  {
1318  Cell warn (dim_vector (m_warnings_that_stop.size (), 1));
1319  int i = 0;
1320 
1321  for (const auto& w : m_warnings_that_stop)
1322  {
1323  if (to_screen)
1324  octave_stdout << "stop if warning " << w << "\n";
1325  else
1326  warn(i++) = w;
1327  }
1328  if (! to_screen)
1329  retval.assign ("warn", octave_value (warn));
1330  }
1331  }
1332 
1333  // print dbstop if interrupt information
1334  if (Vdebug_on_interrupt)
1335  {
1336  if (to_screen)
1337  octave_stdout << "stop if interrupt\n";
1338  else
1339  retval.assign ("intr", octave_value (""));
1340  }
1341 
1342  return retval;
1343 }
1344 
1345 OCTAVE_END_NAMESPACE(octave)
ComplexNDArray concat(NDArray &ra, ComplexNDArray &rb, const Array< octave_idx_type > &ra_idx)
Definition: CNDArray.cc:418
cdef_class lookup_class(const std::string &name, bool error_if_not_found, bool load_if_not_found)
Definition: cdef-utils.cc:80
charNDArray max(char d, const charNDArray &m)
Definition: chNDArray.cc:230
T & elem(octave_idx_type n)
Size of the specified dimension.
Definition: Array.h:562
Array< T, Alloc > & insert(const Array< T, Alloc > &a, const Array< octave_idx_type > &idx)
Insert an array into another at a specified position.
Definition: Array-base.cc:1608
bool isempty() const
Size of the specified dimension.
Definition: Array.h:651
octave_idx_type numel() const
Number of elements in the array.
Definition: Array.h:414
Definition: Cell.h:43
Cell index(const octave_value_list &idx, bool resize_ok=false) const
Definition: Cell.cc:171
void statement_list(std::shared_ptr< tree_statement_list > &lst)
Definition: oct-parse.cc:6825
void reset()
Definition: oct-parse.cc:6723
bool condition_valid(const std::string &cond)
Definition: bp-table.cc:286
int remove_breakpoint_from_function(const std::string &fcn_ident="", int line=1)
Definition: bp-table.cc:948
int remove_breakpoint_from_file(const std::string &file="", int line=1)
Definition: bp-table.cc:1077
void remove_all_breakpoints()
Definition: bp-table.cc:1121
bp_lines remove_all_breakpoints_from_function(const std::string &fcn_ident, bool silent=false)
Definition: bp-table.cc:1038
fname_bp_map get_breakpoint_list(const octave_value_list &fname_list)
Definition: bp-table.cc:1154
int remove_breakpoints_from_file(const std::string &file="", const bp_lines &lines=bp_lines())
Definition: bp-table.cc:1091
std::set< int > bp_lines
Definition: bp-table.h:70
int remove_breakpoints_from_function(const std::string &fcn_ident="", const bp_lines &lines=bp_lines())
Definition: bp-table.cc:958
int add_breakpoint_in_function(const std::string &fcn_ident="", int line=1, const std::string &condition="")
Definition: bp-table.cc:693
bp_lines add_breakpoints_in_function(const std::string &fcn_ident="", const bp_lines &lines=bp_lines(), const std::string &condition="")
Definition: bp-table.cc:813
void dbstop_process_map_args(const octave_map &mv)
Definition: bp-table.cc:141
int add_breakpoint_in_file(const std::string &file="", int line=1, const std::string &condition="")
Definition: bp-table.cc:860
std::map< std::string, std::list< bp_type > > fname_bp_map
Definition: bp-table.h:80
bp_lines add_breakpoints_in_file(const std::string &file="", const bp_lines &lines=bp_lines(), const std::string &condition="")
Definition: bp-table.cc:882
bp_lines remove_all_breakpoints_from_file(const std::string &file, bool silent=false)
Definition: bp-table.cc:1106
void dbclear_all_signals()
Definition: bp-table.cc:120
octave_map stop_on_err_warn_status(bool to_screen)
Definition: bp-table.cc:1245
void parse_dbfunction_params(const char *who, const octave_value_list &args, std::string &fcn_name, std::string &class_name, bp_table::bp_lines &lines, std::string &cond)
Definition: bp-table.cc:347
Vector representing the dimensions (size) of an Array.
Definition: dim-vector.h:94
octave_value debug_on_warning(const octave_value_list &args, int nargout)
Definition: error.cc:292
octave_value debug_on_caught(const octave_value_list &args, int nargout)
Definition: error.cc:285
octave_value debug_on_error(const octave_value_list &args, int nargout)
Definition: error.cc:278
Provides threadsafe access to octave.
void update_breakpoint(bool insert, const std::string &file, int line, const std::string &cond="")
event_manager & get_event_manager()
Definition: interpreter.h:328
load_path & get_load_path()
Definition: interpreter.h:283
error_system & get_error_system()
Definition: interpreter.h:251
bool contains_file_in_dir(const std::string &file_name, const std::string &dir_name)
Definition: load-path.cc:472
virtual bool is_user_function() const
Definition: ov-base.h:535
virtual octave_user_code * user_code_value(bool silent=false)
Definition: ov-base.cc:967
virtual std::string profiler_name() const
Definition: ov-fcn.h:80
std::string name() const
Definition: ov-fcn.h:206
virtual std::list< std::string > subfunction_names() const
Definition: ov-fcn.h:199
bool isfield(const std::string &name) const
Definition: oct-map.h:326
void assign(const std::string &k, const Cell &val)
Definition: oct-map.h:344
const Cell & contents(const_iterator p) const
Definition: oct-map.h:310
std::string fcn_file_name() const
Definition: ov-usr-fcn.h:105
virtual std::map< std::string, octave_value > subfunctions() const
Definition: ov-usr-fcn.cc:130
octave::tree_statement_list * body()
Definition: ov-usr-fcn.h:118
Array< octave_value > array_value() const
Definition: ovl.h:90
bool empty() const
Definition: ovl.h:115
octave_idx_type length() const
Definition: ovl.h:113
Definition: parse.h:822
int run()
Definition: oct-parse.cc:9748
void reset_debug_state()
Definition: pt-eval.cc:1378
bool in_debug_repl() const
Definition: pt-eval.cc:5058
interpreter & get_interpreter()
Definition: pt-eval.h:420
octave_user_code * get_user_code(const std::string &fname="")
Definition: pt-eval.cc:2942
virtual bool is_assignment_expression() const
Definition: pt-exp.h:70
octave_value_list list_breakpoints()
Definition: pt-stmt.cc:221
std::list< bp_type > breakpoints_and_conds()
Definition: pt-stmt.cc:231
void delete_breakpoint(int line)
Definition: pt-stmt.cc:199
bp_table::bp_lines remove_all_breakpoints(event_manager &evmgr, const std::string &file)
Definition: pt-stmt.cc:279
bp_table::bp_lines add_breakpoint(event_manager &evmgr, const std::string &file, const bp_table::bp_lines &lines, const std::string &condition)
Definition: pt-stmt.cc:255
bool is_expression() const
Definition: pt-stmt.h:76
tree_expression * expression()
Definition: pt-stmt.h:97
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage(void)
Definition: defun-int.h:72
void warning(const char *fmt,...)
Definition: error.cc:1063
void() error(const char *fmt,...)
Definition: error.cc:988
std::string dir_sep_chars()
std::string dirname(const std::string &path)
std::string tail(const std::string &path)
F77_RET_T const F77_INT const F77_INT const F77_INT const F77_DBLE const F77_DBLE F77_INT F77_DBLE * V
std::complex< double > w(std::complex< double > z, double relerr=0)
dbstop_args
Definition: bp-table.cc:327
@ dbstop_in
Definition: bp-table.cc:328
@ dbstop_if
Definition: bp-table.cc:330
@ dbstop_at
Definition: bp-table.cc:329
@ dbstop_none
Definition: bp-table.cc:331
std::string find_bkpt_list(octave_value_list slist, std::string match)
Definition: bp-table.cc:1137
bool strcmp(const T &str_a, const T &str_b)
Octave string utility functions.
#define octave_stdout
Definition: pager.h:309
bool Vdebug_on_interrupt
Definition: sighandlers.cc:72
F77_RET_T len
Definition: xerbla.cc:61