1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2001-2021 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 
37 #include "file-ops.h"
38 
39 #include "bp-table.h"
40 #include "defun-int.h"
41 #include "error.h"
42 #include "event-manager.h"
43 #include "interpreter.h"
44 #include "interpreter-private.h"
45 #include "oct-map.h"
46 #include "ov-usr-fcn.h"
47 #include "ov.h"
48 #include "ovl.h"
49 #include "pager.h"
50 #include "parse.h"
51 #include "pt-eval.h"
52 #include "pt-exp.h"
53 #include "pt-stmt.h"
54 #include "sighandlers.h"
55 
56 namespace octave
57 {
58   // Clear all reasons to stop, other than breakpoints.
59 
dbclear_all_signals(void)60   void bp_table::dbclear_all_signals (void)
61   {
62     interpreter& interp = m_evaluator.get_interpreter ();
63     error_system& es = interp.get_error_system ();
64 
65     es.debug_on_error (false);
66     bp_table::m_errors_that_stop.clear ();
67 
68     es.debug_on_caught (false);
69     bp_table::m_caught_that_stop.clear ();
70 
71     es.debug_on_warning (false);
72     bp_table::m_warnings_that_stop.clear ();
73 
74     Vdebug_on_interrupt = false;
75   }
76 
77   // Process the "warn", "errs", "caught" and "intr" fields for a call of
78   // "dbstop (p)".
79 
dbstop_process_map_args(const octave_map & mv)80   void bp_table::dbstop_process_map_args (const octave_map& mv)
81   {
82     interpreter& interp = m_evaluator.get_interpreter ();
83     error_system& es = interp.get_error_system ();
84 
85     // process errs
86     // why so many levels of indirection needed?
87     bool fail = false;
88     Cell U = mv.contents ("errs");
89     if (U.numel () != 1)
90       fail = (U.numel () > 1);
91     else
92       {
93         Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
94         if (W.isempty () || W(0).isempty ())
95           es.debug_on_error (true);    // like "dbstop if error" with no identifier
96         else if (! W(0).iscell ())
97           fail = true;
98         else
99           {
100             Cell V = W(0).cell_value ();
101             for (int i = 0; i < V.numel (); i++)
102               {
103                 m_errors_that_stop.insert (V(i).string_value ());
104                 es.debug_on_error (true);
105               }
106           }
107       }
108 
109     if (fail)
110       error ("dbstop: invalid 'errs' field");
111 
112     // process caught
113     // why so many levels of indirection needed?
114     fail = false;
115     U = mv.contents ("caught");
116     if (U.numel () != 1)
117       fail = (U.numel () > 1);
118     else
119       {
120         Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
121         if (W.isempty () || W(0).isempty ())
122           es.debug_on_caught (true);    // like "dbstop if caught error" with no ID
123         else if (! W(0).iscell ())
124           fail = true;
125         else
126           {
127             Cell V = W(0).cell_value ();
128             for (int i = 0; i < V.numel (); i++)
129               {
130                 m_caught_that_stop.insert (V(i).string_value ());
131                 es.debug_on_caught (true);
132               }
133           }
134       }
135 
136     if (fail)
137       error ("dbstop: invalid 'caught' field");
138 
139     // process warn
140     // why so many levels of indirection needed?
141     fail = false;
142     U = mv.contents ("warn");
143     if (U.numel () != 1)
144       fail = (U.numel () > 1);
145     else
146       {
147         Array<octave_value> W = U.index (static_cast<octave_idx_type> (0));
148         if (W.isempty () || W(0).isempty ())
149           es.debug_on_warning (true);    // like "dbstop if warning" with no identifier
150         else if (! W(0).iscell ())
151           fail = true;
152         else
153           {
154             Cell V = W(0).cell_value ();
155             for (int i = 0; i < V.numel (); i++)
156               {
157                 m_warnings_that_stop.insert (V(i).string_value ());
158                 es.debug_on_warning (true);
159               }
160           }
161       }
162 
163     if (fail)
164       error ("dbstop: invalid 'warn' field");
165 
166     // process interrupt
167     if (mv.isfield ("intr"))
168       Vdebug_on_interrupt = true;
169   }
170 
171   // Insert a breakpoint in function fcn at line within file fname,
172   // to stop only when condition is true.
173   // Record in m_bp_set that fname contains a breakpoint.
174 
add_breakpoint_1(octave_user_code * fcn,const std::string & fname,const bp_table::intmap & line,const std::string & condition,bp_table::intmap & retval)175   bool bp_table::add_breakpoint_1 (octave_user_code *fcn,
176                                    const std::string& fname,
177                                    const bp_table::intmap& line,
178                                    const std::string& condition,
179                                    bp_table::intmap& retval)
180   {
181     bool found = false;
182 
183     tree_statement_list *cmds = fcn->body ();
184 
185     std::string file = fcn->fcn_file_name ();
186 
187     if (cmds)
188       {
189         interpreter& interp = m_evaluator.get_interpreter ();
190 
191         event_manager& evmgr = interp.get_event_manager ();
192 
193         retval = cmds->add_breakpoint (evmgr, file, line, condition);
194 
195         for (auto& idx_line_p : retval)
196           {
197             if (idx_line_p.second != 0)
198               {
199                 // Normalize to store only the file name.
200                 // Otherwise, there can be an entry for both
201                 // file>subfunction and file, which causes a crash on
202                 // dbclear all
203                 const char *s = strchr (fname.c_str (), '>');
204                 if (s)
205                   m_bp_set.insert (fname.substr (0, s - fname.c_str ()));
206                 else
207                   m_bp_set.insert (fname);
208                 found = true;
209                 break;
210               }
211           }
212       }
213 
214     return found;
215   }
216 
217   // Cursory check that cond is a valid condition to use for a breakpoint.
218   // Currently allows conditions with side-effects, like 'y+=10' and 'y++';
219   // it is odd that the former is not flagged by "is_assignment_expression".
220   // Throws an exception if not valid.
221 
condition_valid(const std::string & cond)222   bool bp_table::condition_valid (const std::string& cond)
223   {
224     if (cond.length () > 0)
225       {
226         // ; to reject partial expr like "y=="
227         parser parser (cond + " ;", m_evaluator.get_interpreter ());
228         parser.reset ();
229         int parse_status = parser.run ();
230         if (parse_status)
231           error ("dbstop: Cannot parse condition '%s'", cond.c_str ());
232         else
233           {
234             tree_statement *stmt = nullptr;
235 
236             std::shared_ptr<tree_statement_list> stmt_list
237               = parser.statement_list ();
238 
239             if (! stmt_list)
240               error ("dbstop: "
241                      "condition is not empty, but has nothing to evaluate");
242             else
243               {
244                 if (stmt_list->length () == 1
245                     && (stmt = stmt_list->front ())
246                     && stmt->is_expression ())
247                   {
248                     tree_expression *expr = stmt->expression ();
249                     if (expr->is_assignment_expression ())
250                       error ("dbstop: condition cannot be an assignment.  "
251                              "Did you mean '=='?");
252                   }
253                 else
254                   error ("dbstop: condition must be an expression");
255               }
256           }
257       }
258 
259     return true;
260   }
261 
262   enum dbstop_args
263   {
264     dbstop_in,
265     dbstop_at,
266     dbstop_if,
267     dbstop_none
268   };
269 
270   // FIXME: This function probably needs to be completely overhauled to
271   // correctly parse the full syntax of the dbstop command and properly
272   // reject incorrect forms.
273 
274   // Parse parameters (args) of dbstop and dbclear commands.
275   // For dbstop, who=="dbstop"; for dbclear, who=="dbclear".
276   // The syntax is: dbstop [[in] symbol] [[at] [method | line [line [...]]]] [if condition]
277   // where the form of condition depends on whether or not a file or line has
278   // been seen.  IF symbol and method are specified, then symbol should
279   // be a class name.  Otherwise it should be a function name.
280   // Also execute "if [error|warning|interrupt|naninf]" clauses.
281 
parse_dbfunction_params(const char * who,const octave_value_list & args,std::string & func_name,std::string & class_name,bp_table::intmap & lines,std::string & cond)282   void bp_table::parse_dbfunction_params (const char *who,
283                                           const octave_value_list& args,
284                                           std::string& func_name,
285                                           std::string& class_name,
286                                           bp_table::intmap& lines,
287                                           std::string& cond)
288   {
289     int nargin = args.length ();
290     int list_idx = 0;
291     func_name = "";
292     class_name = "";
293     lines = bp_table::intmap ();
294 
295     if (nargin == 0 || ! args(0).is_string ())
296       print_usage (who);
297 
298     // elements already processed
299     bool seen_in = false;
300     bool seen_at = false;
301     bool seen_if = false;
302     int pos = 0;
303     dbstop_args tok = dbstop_none;
304     while (pos < nargin)
305       {
306         // allow "in" and "at" to be implicit
307         if (args(pos).is_string ())
308           {
309             std::string arg = args(pos).string_value ();
310             if (arg == "in")
311               {
312                 tok = dbstop_in;
313                 pos++;
314               }
315             else if (arg == "at")
316               {
317                 tok = dbstop_at;
318                 pos++;
319               }
320             else if (arg == "if")
321               {
322                 tok = dbstop_if;
323                 pos++;
324               }
325             else if (atoi (args(pos).string_value ().c_str ()) > 0)
326               tok = dbstop_at;
327             else
328               tok = dbstop_in;
329           }
330         else
331           tok = dbstop_at;
332 
333         if (pos >= nargin)
334           error ("%s: '%s' missing argument", who,
335                  (tok == dbstop_in
336                   ? "in" : (tok == dbstop_at ? "at" : "if")));
337 
338         // process the actual arguments
339         switch (tok)
340           {
341           case dbstop_in:
342             func_name = args(pos).string_value ();
343             if (seen_in)
344               error ("%s: Too many function names specified -- %s",
345                      who, func_name.c_str ());
346             else if (seen_at || seen_if)
347               error ("%s: function name must come before line number and 'if'",
348                      who);
349             seen_in = true;
350             pos++;
351             break;
352 
353           case dbstop_at:
354             if (seen_at)
355               error ("%s: Only one 'at' clause is allowed -- %s",
356                      who, args(pos).string_value ().c_str ());
357             else if (seen_if)
358               error ("%s: line number must come before 'if' clause\n", who);
359             seen_at = true;
360 
361             if (seen_if)
362               error ("%s: line number must come before 'if' clause\n", who);
363             else if (seen_in)
364               {
365                 std::string arg = args(pos).string_value ();
366 
367                 // FIXME: we really want to distinguish number
368                 // vs. method name here.
369 
370                 if (atoi (arg.c_str ()) == 0)
371                   {
372                     // We have class and function names but already
373                     // stored the class name in func_name.
374                     class_name = func_name;
375                     func_name = arg;
376                     pos++;
377                     break;
378                   }
379 
380               }
381             else
382               {
383                 // It was a line number.  Get function name from debugger.
384                 if (m_evaluator.in_debug_repl ())
385                   func_name = m_evaluator.get_user_code ()->profiler_name ();
386                 else
387                   error ("%s: function name must come before line number "
388                          "and 'if'", who);
389                 seen_in = true;
390               }
391 
392             // Read a list of line numbers (or arrays thereof)
393             for ( ; pos < nargin; pos++)
394               {
395                 if (args(pos).is_string ())
396                   {
397                     int line = atoi (args(pos).string_value ().c_str ());
398 
399                     if (line > 0)
400                       lines[list_idx++] = line;
401                     else
402                       break;        // may be "if" or a method name
403                   }
404                 else if (args(pos).isnumeric ())
405                   {
406                     const NDArray arg = args(pos).array_value ();
407 
408                     for (octave_idx_type j = 0; j < arg.numel (); j++)
409                       lines[list_idx++] = static_cast<int> (arg.elem (j));
410                   }
411                 else
412                   error ("%s: Invalid argument type %s",
413                          who, args(pos).type_name ().c_str ());
414               }
415             break;
416 
417           case dbstop_if:
418             if (seen_in)    // conditional breakpoint
419               {
420                 cond = "";  // remaining arguments form condition
421                 for (; pos < nargin; pos++)
422                   {
423                     if (args(pos).is_string ())
424                       cond += ' ' + args(pos).string_value ();
425                     else
426                       error ("%s: arguments to 'if' must all be strings", who);
427                   }
428 
429                 cond = cond.substr (1);   // omit initial space
430               }
431             else    // stop on event (error, warning, interrupt, NaN/inf)
432               {
433                 std::string condition = args(pos).string_value ();
434                 bool on_off = ! strcmp (who, "dbstop");
435 
436                 // FIXME: the following seems a bit messy in the way it
437                 // duplicates checks on CONDITION.
438 
439                 if (condition == "error")
440                   process_id_list (who, condition, args, nargin, pos, on_off,
441                                    m_errors_that_stop);
442                 else if (condition == "warning")
443                   process_id_list (who, condition, args, nargin, pos, on_off,
444                                    m_warnings_that_stop);
445                 else if (condition == "caught" && nargin > pos+1
446                          && args(pos+1).string_value () == "error")
447                   {
448                     pos++;
449                     process_id_list (who, condition, args, nargin, pos, on_off,
450                                      m_caught_that_stop);
451                   }
452                 else if (condition == "interrupt")
453                   {
454                     Vdebug_on_interrupt = on_off;
455                   }
456                 else if (condition == "naninf")
457                   {
458 #if defined (DBSTOP_NANINF)
459                     Vdebug_on_naninf = on_off;
460                     enable_fpe (on_off);
461 #else
462                     warning ("%s: condition '%s' not yet supported",
463                              who, condition.c_str ());
464 #endif
465                   }
466                 else
467                   error ("%s: invalid condition %s",
468                          who, condition.c_str ());
469 
470                 pos = nargin;
471               }
472             break;
473 
474           default:      // dbstop_none should never occur
475             break;
476           }
477       }
478   }
479 
480 /*
481 %!test
482 %! dbclear all;   # Clear out breakpoints before test
483 %! dbstop help;
484 %! dbstop in ls;
485 %! dbstop help at 104;
486 %! dbstop in ls 102;     ## 102 is a comment; code line is at 105
487 %! dbstop help 204 if a==5;
488 %! dbstop if error Octave:undefined-function;
489 %! s = dbstatus;
490 %! dbclear all;
491 %! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"});
492 %! assert ([s.bkpt(:).line], [55, 105, 207, 63, 102]);
493 %! assert (s.errs, {"Octave:undefined-function"});
494 */
495 
set_stop_flag(const char * who,const std::string & condition,bool on_off)496   void bp_table::set_stop_flag (const char *who, const std::string& condition,
497                                 bool on_off)
498   {
499     interpreter& interp = m_evaluator.get_interpreter ();
500     error_system& es = interp.get_error_system ();
501 
502     if (condition == "error")
503       es.debug_on_error (on_off);
504     else if (condition == "warning")
505       es.debug_on_warning (on_off);
506     else if (condition == "caught")
507       es.debug_on_caught (on_off);
508     else
509       error ("%s: internal error in set_stop_flag", who);
510   }
511 
process_id_list(const char * who,const std::string & condition,const octave_value_list & args,int nargin,int & pos,bool on_off,std::set<std::string> & id_list)512   void bp_table::process_id_list (const char *who,
513                                   const std::string& condition,
514                                   const octave_value_list& args,
515                                   int nargin, int& pos, bool on_off,
516                                   std::set<std::string>& id_list)
517   {
518     pos++;
519 
520     if (nargin > pos)       // only affect a single error ID
521       {
522         if (! args(pos).is_string () || nargin > pos+1)
523           error ("%s: ID must be a single string", who);
524         else if (on_off)
525           {
526             id_list.insert (args(pos).string_value ());
527             set_stop_flag (who, condition, true);
528           }
529         else
530           {
531             id_list.erase (args(pos).string_value ());
532             if (id_list.empty ())
533               set_stop_flag (who, condition, false);
534           }
535       }
536     else   // unqualified.  Turn all on or off
537       {
538         id_list.clear ();
539         set_stop_flag (who, condition, on_off);
540 
541         if (condition == "error")
542           {
543             // Matlab stops on both.
544             Vdebug_on_interrupt = on_off;
545           }
546       }
547   }
548 
549   // Return the sub/nested/main function of MAIN_FCN that contains
550   // line number LINENO of the source file.
551   // If END_LINE != 0, *END_LINE is set to last line of the returned function.
552 
find_fcn_by_line(octave_user_code * main_fcn,int lineno,int * end_line=nullptr)553   static octave_user_code * find_fcn_by_line (octave_user_code *main_fcn,
554                                               int lineno,
555                                               int *end_line = nullptr)
556   {
557     octave_user_code *retval = nullptr;
558     octave_user_code *next_fcn = nullptr;  // 1st function starting after lineno
559 
560     // Find innermost nested (or parent) function containing lineno.
561     int earliest_end = std::numeric_limits<int>::max ();
562 
563     std::map<std::string, octave_value> subfcns = main_fcn->subfunctions ();
564     for (const auto& str_val_p : subfcns)
565       {
566         if (str_val_p.second.is_user_function ())
567           {
568             auto *dbg_subfcn = str_val_p.second.user_function_value ();
569 
570             // Check if lineno is within dbg_subfcn.
571             // FIXME: we could break when beginning_line() > lineno,
572             // but that makes the code "fragile"
573             // if the order of walking subfcns changes,
574             // for a minor speed improvement in non-critical code.
575             if (dbg_subfcn->ending_line () < earliest_end
576                 && dbg_subfcn->ending_line () >= lineno
577                 && dbg_subfcn->beginning_line () <= lineno)
578               {
579                 earliest_end = dbg_subfcn->ending_line ();
580                 retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end);
581               }
582 
583             // Find the first fcn starting after lineno.
584             // This is used if line is not inside any function.
585             if (dbg_subfcn->beginning_line () >= lineno && ! next_fcn)
586               next_fcn = dbg_subfcn;
587           }
588       }
589 
590     // The breakpoint is either in the subfunction found above,
591     // or in the main function, which we check now.
592     if (main_fcn->is_user_function ())
593       {
594         int e = dynamic_cast<octave_user_function *> (main_fcn)->ending_line ();
595         if (e >= lineno && e < earliest_end)
596           retval = main_fcn;
597 
598         if (! retval)
599           retval = next_fcn;
600       }
601     else  // main_fcn is a script.
602       {
603         if (! retval)
604           retval = main_fcn;
605       }
606 
607     if (end_line && earliest_end < *end_line)
608       *end_line = earliest_end;
609 
610     return retval;
611   }
612 
613   // Given file name fname, find the subfunction at line and create
614   // a breakpoint there.  Put the system into debug_mode.
add_breakpoint(const std::string & fname,const std::string & class_name,const bp_table::intmap & line,const std::string & condition)615   bp_table::intmap bp_table::add_breakpoint (const std::string& fname,
616                                              const std::string& class_name,
617                                              const bp_table::intmap& line,
618                                              const std::string& condition)
619   {
620     octave_user_code *main_fcn = m_evaluator.get_user_code (fname, class_name);
621 
622     if (! main_fcn)
623       error ("add_breakpoint: unable to find function '%s'\n", fname.c_str ());
624 
625     condition_valid (condition);  // Throw error if condition not valid.
626 
627     intmap retval;
628 
629     octave_idx_type len = line.size ();
630 
631     for (int i = 0; i < len; i++)
632       {
633         const_intmap_iterator m = line.find (i);
634 
635         if (m != line.end ())
636           {
637             int lineno = m->second;
638 
639             octave_user_code *dbg_fcn = find_fcn_by_line (main_fcn, lineno);
640 
641             // We've found the right (sub)function.  Now insert the breakpoint.
642             // We insert all breakpoints.
643             // If multiple are in the same function, we insert multiple times.
644             intmap ret_one;
645             if (dbg_fcn
646                 && add_breakpoint_1 (dbg_fcn, fname, line, condition, ret_one))
647               retval.insert (std::pair<int,int> (i, ret_one.find (i)->second));
648           }
649       }
650 
651     m_evaluator.reset_debug_state ();
652 
653     return retval;
654   }
655 
remove_breakpoint_1(octave_user_code * fcn,const std::string & fname,const bp_table::intmap & line)656   int bp_table::remove_breakpoint_1 (octave_user_code *fcn,
657                                      const std::string& fname,
658                                      const bp_table::intmap& line)
659   {
660     int retval = 0;
661 
662     std::string file = fcn->fcn_file_name ();
663 
664     tree_statement_list *cmds = fcn->body ();
665 
666     // FIXME: move the operation on cmds to the tree_statement_list class?
667 
668     if (cmds)
669       {
670         octave_value_list results = cmds->list_breakpoints ();
671 
672         if (results.length () > 0)
673           {
674             interpreter& interp = m_evaluator.get_interpreter ();
675 
676             event_manager& evmgr = interp.get_event_manager ();
677 
678             octave_idx_type len = line.size ();
679 
680             for (int i = 0; i < len; i++)
681               {
682                 const_intmap_iterator p = line.find (i);
683 
684                 if (p != line.end ())
685                   {
686                     int lineno = p->second;
687 
688                     cmds->delete_breakpoint (lineno);
689 
690                     if (! file.empty ())
691                       evmgr.update_breakpoint (false, file, lineno);
692                   }
693               }
694 
695             results = cmds->list_breakpoints ();
696 
697             auto it = m_bp_set.find (fname);
698             if (results.empty () && it != m_bp_set.end ())
699               m_bp_set.erase (it);
700           }
701 
702         retval = results.length ();
703       }
704 
705     return retval;
706   }
707 
remove_breakpoint(const std::string & fname,const bp_table::intmap & line)708   int bp_table::remove_breakpoint (const std::string& fname,
709                                    const bp_table::intmap& line)
710   {
711     int retval = 0;
712 
713     octave_idx_type len = line.size ();
714 
715     if (len == 0)
716       {
717         intmap results = remove_all_breakpoints_in_file (fname);
718         retval = results.size ();
719       }
720     else
721       {
722         octave_user_code *dbg_fcn = m_evaluator.get_user_code (fname);
723 
724         if (! dbg_fcn)
725           error ("remove_breakpoint: unable to find function %s\n",
726                  fname.c_str ());
727 
728         retval = remove_breakpoint_1 (dbg_fcn, fname, line);
729 
730         // Search subfunctions in the order they appear in the file.
731 
732         const std::list<std::string> subfcn_names
733           = dbg_fcn->subfunction_names ();
734 
735         std::map<std::string, octave_value> subfcns
736           = dbg_fcn->subfunctions ();
737 
738         for (const auto& subf_nm : subfcn_names)
739           {
740             const auto q = subfcns.find (subf_nm);
741 
742             if (q != subfcns.end ())
743               {
744                 octave_user_code *dbg_subfcn = q->second.user_code_value ();
745 
746                 retval += remove_breakpoint_1 (dbg_subfcn, fname, line);
747               }
748           }
749       }
750 
751     m_evaluator.reset_debug_state ();
752 
753     return retval;
754   }
755 
756   // Remove all breakpoints from a file, including those in subfunctions.
757 
758   bp_table::intmap
remove_all_breakpoints_in_file(const std::string & fname,bool silent)759   bp_table::remove_all_breakpoints_in_file (const std::string& fname,
760                                             bool silent)
761   {
762     intmap retval;
763 
764     octave_user_code *dbg_fcn = m_evaluator.get_user_code (fname);
765 
766     if (dbg_fcn)
767       {
768         std::string file = dbg_fcn->fcn_file_name ();
769 
770         tree_statement_list *cmds = dbg_fcn->body ();
771 
772         if (cmds)
773           {
774             interpreter& interp = m_evaluator.get_interpreter ();
775 
776             event_manager& evmgr = interp.get_event_manager ();
777 
778             retval = cmds->remove_all_breakpoints (evmgr, file);
779 
780             auto it = m_bp_set.find (fname);
781             if (it != m_bp_set.end ())
782               m_bp_set.erase (it);
783           }
784       }
785     else if (! silent)
786       error ("remove_all_breakpoint_in_file: "
787              "unable to find function %s\n", fname.c_str ());
788 
789     m_evaluator.reset_debug_state ();
790 
791     return retval;
792   }
793 
remove_all_breakpoints(void)794   void bp_table::remove_all_breakpoints (void)
795   {
796     // Odd loop structure required because delete will invalidate
797     // m_bp_set iterators.
798     for (auto it = m_bp_set.cbegin (), it_next = it;
799          it != m_bp_set.cend ();
800          it = it_next)
801       {
802         ++it_next;
803         remove_all_breakpoints_in_file (*it);
804       }
805 
806     m_evaluator.reset_debug_state ();
807   }
808 
find_bkpt_list(octave_value_list slist,std::string match)809   std::string find_bkpt_list (octave_value_list slist, std::string match)
810   {
811     std::string retval;
812 
813     for (int i = 0; i < slist.length (); i++)
814       {
815         if (slist(i).string_value () == match)
816           {
817             retval = slist(i).string_value ();
818             break;
819           }
820       }
821 
822     return retval;
823   }
824 
825   bp_table::fname_bp_map
get_breakpoint_list(const octave_value_list & fname_list)826   bp_table::get_breakpoint_list (const octave_value_list& fname_list)
827   {
828     fname_bp_map retval;
829 
830     // make copy since changes may invalidate iters of m_bp_set.
831     std::set<std::string> tmp_bp_set = m_bp_set;
832 
833     for (auto& bp_fname : tmp_bp_set)
834       {
835         if (fname_list.empty ()
836             || find_bkpt_list (fname_list, bp_fname) != "")
837           {
838             octave_user_code *dbg_fcn = m_evaluator.get_user_code (bp_fname);
839 
840             if (dbg_fcn)
841               {
842                 tree_statement_list *cmds = dbg_fcn->body ();
843 
844                 // FIXME: move the operation on cmds to the
845                 //        tree_statement_list class?
846                 if (cmds)
847                   {
848                     std::list<bp_type> bkpts = cmds->breakpoints_and_conds ();
849 
850                     if (! bkpts.empty ())
851                       retval[bp_fname] = bkpts;
852                   }
853 
854                 // look for breakpoints in subfunctions
855                 const std::list<std::string> subf_nm
856                   = dbg_fcn->subfunction_names ();
857 
858                 std::map<std::string, octave_value> subfcns
859                   = dbg_fcn->subfunctions ();
860 
861                 for (const auto& subfcn_nm : subf_nm)
862                   {
863                     const auto q = subfcns.find (subfcn_nm);
864 
865                     if (q != subfcns.end ())
866                       {
867                         octave_user_code *dbg_subfcn
868                           = q->second.user_code_value ();
869 
870                         cmds = dbg_subfcn->body ();
871                         if (cmds)
872                           {
873                             std::list<bp_type> bkpts
874                               = cmds->breakpoints_and_conds ();
875 
876                             if (! bkpts.empty ())
877                               {
878                                 std::string key
879                                   = bp_fname + '>' + dbg_subfcn->name ();
880 
881                                 retval[key] = bkpts;
882                               }
883                           }
884                       }
885                   }
886               }
887           }
888       }
889 
890     return retval;
891   }
892 
893   // Report the status of "dbstop if error ..." and "dbstop if warning ..."
894   // If to_screen is true, the output goes to octave_stdout; otherwise it is
895   // returned.
896   // If dbstop if error is true but no explicit IDs are specified, the return
897   // value will have an empty field called "errs".  If IDs are specified, the
898   // "errs" field will have a row per ID.  If dbstop if error is false, there
899   // is no "errs" field.  The "warn" field is set similarly by dbstop if warning
900 
stop_on_err_warn_status(bool to_screen)901   octave_map bp_table::stop_on_err_warn_status (bool to_screen)
902   {
903     octave_map retval;
904 
905     interpreter& interp = m_evaluator.get_interpreter ();
906     error_system& es = interp.get_error_system ();
907 
908     // print dbstop if error information
909     if (es.debug_on_error ())
910       {
911         if (m_errors_that_stop.empty ())
912           {
913             if (to_screen)
914               octave_stdout << "stop if error\n";
915             else
916               retval.assign ("errs", octave_value(""));
917           }
918         else
919           {
920             Cell errs (dim_vector (bp_table::m_errors_that_stop.size (), 1));
921             int i = 0;
922 
923             for (const auto& e : m_errors_that_stop)
924               {
925                 if (to_screen)
926                   octave_stdout << "stop if error " << e << "\n";
927                 else
928                   errs(i++) = e;
929               }
930             if (! to_screen)
931               retval.assign ("errs", octave_value (errs));
932           }
933       }
934 
935     // print dbstop if caught error information
936     if (es.debug_on_caught ())
937       {
938         if (m_caught_that_stop.empty ())
939           {
940             if (to_screen)
941               octave_stdout << "stop if caught error\n";
942             else
943               retval.assign ("caught", octave_value(""));
944           }
945         else
946           {
947             Cell errs (dim_vector (m_caught_that_stop.size (), 1));
948             int i = 0;
949 
950             for (const auto& e : m_caught_that_stop)
951               {
952                 if (to_screen)
953                   octave_stdout << "stop if caught error " << e << "\n";
954                 else
955                   errs(i++) = e;
956               }
957             if (! to_screen)
958               retval.assign ("caught", octave_value (errs));
959           }
960       }
961 
962     // print dbstop if warning information
963     if (es.debug_on_warning ())
964       {
965         if (m_warnings_that_stop.empty ())
966           {
967             if (to_screen)
968               octave_stdout << "stop if warning\n";
969             else
970               retval.assign ("warn", octave_value(""));
971           }
972         else
973           {
974             Cell warn (dim_vector (m_warnings_that_stop.size (), 1));
975             int i = 0;
976 
977             for (const auto& w : m_warnings_that_stop)
978               {
979                 if (to_screen)
980                   octave_stdout << "stop if warning " << w << "\n";
981                 else
982                   warn(i++) = w;
983               }
984             if (! to_screen)
985               retval.assign ("warn", octave_value (warn));
986           }
987       }
988 
989     // print dbstop if interrupt information
990     if (Vdebug_on_interrupt)
991       {
992         if (to_screen)
993           octave_stdout << "stop if interrupt\n";
994         else
995           retval.assign ("intr", octave_value ());
996       }
997 
998     return retval;
999   }
1000 
1001   octave_user_code *
get_user_code(const std::string & fname)1002   get_user_code (const std::string& fname)
1003   {
1004     tree_evaluator& tw = __get_evaluator__ ("get_user_code");
1005 
1006     return tw.get_user_code (fname);
1007   }
1008 }
1009