GNU Octave 10.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 
Loading...
Searching...
No Matches
bp-table.cc
Go to the documentation of this file.
1////////////////////////////////////////////////////////////////////////
2//
3// Copyright (C) 2001-2025 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
61class bp_file_info
62{
63public:
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
108private:
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
119void
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
140void
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
236bool
237bp_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
285bool
286bp_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
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->size () == 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
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
346void
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;
368 while (pos < nargin)
369 {
370 // allow "in" and "at" to be implicit
371 if (args(pos).is_string ())
372 {
373 // Default value.
374 tok = dbstop_in;
375
376 std::string arg = args(pos).string_value ();
377 if (arg == "in")
378 {
379 tok = dbstop_in;
380 pos++;
381 }
382 else if (arg == "at")
383 {
384 tok = dbstop_at;
385 pos++;
386 }
387 else if (arg == "if")
388 {
389 tok = dbstop_if;
390 pos++;
391 }
392 else
393 {
394 try
395 {
396 if (std::stoi (args(pos).string_value ()) > 0)
397 tok = dbstop_at;
398 }
399 catch (const std::invalid_argument&) { }
400 catch (const std::out_of_range&) { }
401 }
402 }
403 else
404 tok = dbstop_at;
405
406 if (pos >= nargin)
407 error ("%s: '%s' missing argument", who,
408 (tok == dbstop_in
409 ? "in" : (tok == dbstop_at ? "at" : "if")));
410
411 // process the actual arguments
412 switch (tok)
413 {
414 case dbstop_in:
415 fcn_name = args(pos).string_value ();
416 if (seen_in)
417 error ("%s: Too many function names specified -- %s",
418 who, fcn_name.c_str ());
419 else if (seen_at || seen_if)
420 error ("%s: function name must come before line number and 'if'",
421 who);
422 seen_in = true;
423 pos++;
424 break;
425
426 case dbstop_at:
427 if (seen_at)
428 error ("%s: Only one 'at' clause is allowed -- %s",
429 who, args(pos).string_value ().c_str ());
430 else if (seen_if)
431 error ("%s: line number must come before 'if' clause\n", who);
432 seen_at = true;
433
434 if (seen_if)
435 error ("%s: line number must come before 'if' clause\n", who);
436 else if (seen_in)
437 {
438 std::string arg = args(pos).string_value ();
439
440 // FIXME: we really want to distinguish number
441 // vs. method name here.
442
443 // FIXME: I'm not sure what the
444
445 bool int_conv_ok = true;
446
447 try
448 {
449 if (std::stoi (arg) == 0)
450 int_conv_ok = false;
451 }
452 catch (const std::invalid_argument&)
453 {
454 int_conv_ok = false;
455 }
456 catch (const std::out_of_range&)
457 {
458 int_conv_ok = false;
459 }
460
461 if (! int_conv_ok)
462 {
463 // Assume we are looking at a function name.
464 // We have class and function names but already
465 // stored the class name in fcn_name.
466 class_name = fcn_name;
467 fcn_name = arg;
468 pos++;
469 break;
470 }
471
472 }
473 else
474 {
475 // It was a line number. Get function name from debugger.
476 if (m_evaluator.in_debug_repl ())
477 fcn_name = m_evaluator.get_user_code ()->profiler_name ();
478 else
479 error ("%s: function name must come before line number "
480 "and 'if'", who);
481 seen_in = true;
482 }
483
484 // Read a list of line numbers (or arrays thereof)
485 for ( ; pos < nargin; pos++)
486 {
487 if (args(pos).is_string ())
488 {
489 std::string str = args(pos).string_value ();
490
491 try
492 {
493 int line = std::stoi (str);
494
495 if (line > 0)
496 lines.insert (line);
497 else
498 break; // may be "if" or a method name
499 }
500 catch (const std::invalid_argument&)
501 {
502 break; // may be "if" or a method name
503 }
504 catch (const std::out_of_range&)
505 {
506 error ("dbstop: location value out of range '%s'", str.c_str ());
507 }
508 }
509 else if (args(pos).isnumeric ())
510 {
511 const NDArray arg = args(pos).array_value ();
512
513 for (octave_idx_type j = 0; j < arg.numel (); j++)
514 lines.insert (static_cast<int> (arg.elem (j)));
515 }
516 else
517 error ("%s: Invalid argument type %s",
518 who, args(pos).type_name ().c_str ());
519 }
520 break;
521
522 case dbstop_if:
523 if (seen_in) // conditional breakpoint
524 {
525 cond = ""; // remaining arguments form condition
526 for (; pos < nargin; pos++)
527 {
528 if (args(pos).is_string ())
529 cond += ' ' + args(pos).string_value ();
530 else
531 error ("%s: arguments to 'if' must all be strings", who);
532 }
533
534 cond = cond.substr (1); // omit initial space
535 }
536 else // stop on event (error, warning, interrupt, NaN/inf)
537 {
538 std::string condition = args(pos).string_value ();
539 bool on_off = ! strcmp (who, "dbstop");
540
541 // FIXME: the following seems a bit messy in the way it
542 // duplicates checks on CONDITION.
543
544 if (condition == "error")
545 process_id_list (who, condition, args, nargin, pos, on_off,
546 m_errors_that_stop);
547 else if (condition == "warning")
548 process_id_list (who, condition, args, nargin, pos, on_off,
549 m_warnings_that_stop);
550 else if (condition == "caught" && nargin > pos+1
551 && args(pos+1).string_value () == "error")
552 {
553 pos++;
554 process_id_list (who, condition, args, nargin, pos, on_off,
555 m_caught_that_stop);
556 }
557 else if (condition == "interrupt")
558 {
559 Vdebug_on_interrupt = on_off;
560 }
561 else if (condition == "naninf")
562 {
563#if defined (DBSTOP_NANINF)
564 Vdebug_on_naninf = on_off;
565 enable_fpe (on_off);
566#else
567 warning ("%s: condition '%s' not yet supported",
568 who, condition.c_str ());
569#endif
570 }
571 else
572 error ("%s: invalid condition %s",
573 who, condition.c_str ());
574
575 pos = nargin;
576 }
577 break;
578
579 default: // dbstop_none should never occur
580 break;
581 }
582 }
583}
584
585/*
586%!test
587%! if (isguirunning ())
588%! orig_show_dbg = __event_manager_gui_preference__ ("editor/show_dbg_file",
589%! "false");
590%! endif
591%! unwind_protect
592%! dbclear all; # Clear out breakpoints before test
593%! dbstop help;
594%! dbstop in ls;
595%! dbstop help at 105; # 105 is a comment; code line is at 106
596%! dbstop in ls 123; # 123 is a comment; code line is at 126
597%! dbstop help 204 if a==5;
598%! dbstop if error Octave:undefined-function;
599%! s = dbstatus;
600%! dbclear all;
601%! assert ({s.bkpt(:).name}, {"help", "help", "help>do_contents", "ls", "ls"});
602%! assert ([s.bkpt(:).line], [56, 106, 208, 63, 126]);
603%! assert (s.errs, {"Octave:undefined-function"});
604%! unwind_protect_cleanup
605%! if (isguirunning ())
606%! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg);
607%! endif
608%! end_unwind_protect
609*/
610
611void
612bp_table::set_stop_flag (const char *who, const std::string& condition,
613 bool on_off)
614{
615 interpreter& interp = m_evaluator.get_interpreter ();
616 error_system& es = interp.get_error_system ();
617
618 if (condition == "error")
619 es.debug_on_error (on_off);
620 else if (condition == "warning")
621 es.debug_on_warning (on_off);
622 else if (condition == "caught")
623 es.debug_on_caught (on_off);
624 else
625 error ("%s: internal error in set_stop_flag", who);
626}
627
628void
629bp_table::process_id_list (const char *who,
630 const std::string& condition,
631 const octave_value_list& args,
632 int nargin, int& pos, bool on_off,
633 std::set<std::string>& id_list)
634{
635 pos++;
636
637 if (nargin > pos) // only affect a single error ID
638 {
639 if (! args(pos).is_string () || nargin > pos+1)
640 error ("%s: ID must be a single string", who);
641 else if (on_off)
642 {
643 id_list.insert (args(pos).string_value ());
644 set_stop_flag (who, condition, true);
645 }
646 else
647 {
648 id_list.erase (args(pos).string_value ());
649 if (id_list.empty ())
650 set_stop_flag (who, condition, false);
651 }
652 }
653 else // unqualified. Turn all on or off
654 {
655 id_list.clear ();
656 set_stop_flag (who, condition, on_off);
657
658 if (condition == "error")
659 {
660 // Matlab stops on both.
661 Vdebug_on_interrupt = on_off;
662 }
663 }
664}
665
666// Return the sub/nested/main function of MAIN_FCN that contains
667// line number LINENO of the source file.
668// If FOUND_ENDING_LINE != 0, *FOUND_ENDING_LINE is set to last line of the returned function.
669
670static octave_user_code *
671find_fcn_by_line (octave_user_code *main_fcn, int lineno, int *found_end_line = nullptr)
672{
673 octave_user_code *retval = nullptr;
674 octave_user_code *next_fcn = nullptr; // 1st function starting after lineno
675
676 // Find innermost nested (or parent) function containing lineno.
677 int earliest_end = std::numeric_limits<int>::max ();
678
679 std::map<std::string, octave_value> subfcns = main_fcn->subfunctions ();
680 for (const auto& str_val_p : subfcns)
681 {
682 if (str_val_p.second.is_user_function ())
683 {
684 auto *dbg_subfcn = str_val_p.second.user_function_value ();
685
686 // Check if lineno is within dbg_subfcn.
687 // FIXME: we could break when the beginning line > lineno,
688 // but that makes the code "fragile"
689 // if the order of walking subfcns changes,
690 // for a minor speed improvement in non-critical code.
691
692 filepos beg_pos = dbg_subfcn->beg_pos ();
693 filepos end_pos = dbg_subfcn->end_pos ();
694
695 int beginning_line = beg_pos.line ();
696 int ending_line = end_pos.line ();
697
698 if (ending_line < earliest_end && ending_line >= lineno && beginning_line <= lineno)
699 {
700 earliest_end = ending_line;
701 retval = find_fcn_by_line (dbg_subfcn, lineno, &earliest_end);
702 }
703
704 // Find the first fcn starting after lineno.
705 // This is used if line is not inside any function.
706 if (beginning_line >= lineno && ! next_fcn)
707 next_fcn = dbg_subfcn;
708 }
709 }
710
711 // The breakpoint is either in the subfunction found above,
712 // or in the main function, which we check now.
713 if (main_fcn->is_user_function ())
714 {
715 filepos end_pos = dynamic_cast<octave_user_function *> (main_fcn)->end_pos ();
716
717 int ending_line = end_pos.line ();
718
719 if (ending_line >= lineno && ending_line < earliest_end)
720 retval = main_fcn;
721
722 if (! retval)
723 retval = next_fcn;
724 }
725 else // main_fcn is a script.
726 {
727 if (! retval)
728 retval = main_fcn;
729 }
730
731 if (found_end_line && earliest_end < *found_end_line)
732 *found_end_line = earliest_end;
733
734 return retval;
735}
736
737// Given function identifier fcn_ident, find the subfunction at line and create
738// a breakpoint there. Put the system into debug_mode.
739int
740bp_table::add_breakpoint_in_function (const std::string& fcn_ident,
741 int line,
742 const std::string& condition)
743{
744 bp_lines line_info;
745 line_info.insert (line);
746
747 bp_lines result
748 = add_breakpoints_in_function (fcn_ident, line_info, condition);
749
750 return result.empty () ? 0 : *(result.begin ());
751}
752
753// A class that encapsulates the "get_user_code" operation.
754// This operation changes depending on the symbol being a function or classdef.
755// Octave used to assume everything was a function, one per file.
756// Classdefs can have several functions in a single file.
757class user_code_provider
758{
759public:
760 user_code_provider (const std::string& who, const std::string& fcn_ident,
761 octave_user_code *pfcn, bool silent = false)
762 : m_fcn (nullptr), m_is_valid (false)
763 {
764 m_fcn = pfcn;
765 if (m_fcn && ! (m_fcn->is_classdef_method ()
766 || m_fcn->is_classdef_constructor ()))
767 {
768 // Already have the usercode, no need to do anything.
769 m_is_valid = true;
770 return;
771 }
772
773 // If we get here, try to get a classdef to support getting method by
774 // line number.
775
776 // Extract class name from function identifier
777 std::string class_name = fcn_ident;
778 const char *s = strchr (fcn_ident.c_str (), '/');
779 if (s && fcn_ident[0] == '@')
780 class_name = fcn_ident.substr (1, s - fcn_ident.c_str () - 1);
781
782 m_cls = lookup_class (class_name, false);
783 m_is_valid = m_cls.ok () && (m_cls.get_name () == class_name);
784 if (m_is_valid)
785 populate_function_cache ();
786 else if (! silent)
787 error ("%s: unable to find function '%s'\n", who.c_str (),
788 fcn_ident.c_str ());
789
790 }
791
792 octave_user_code * operator () (int line = 1)
793 {
794 if (! is_valid ())
795 return nullptr;
796
797 if (is_function ())
798 return find_fcn_by_line (m_fcn, line);
799
800 if (line < 0)
801 return nullptr;
802
803 octave_value method = m_cls.get_method (line);
804 if (method.is_function_handle ())
805 return method.user_function_value ()->user_code_value (true);
806 else
807 return method.user_code_value (true);
808 }
809
810 bool is_function () const { return m_fcn; }
811
812 bool is_valid () const { return m_is_valid; }
813
814 size_t number_of_functions () const
815 {
816 if (! is_valid ())
817 return 0;
818
819 if (is_function ())
820 return 1;
821
822 return m_methods_cache.size ();
823 }
824
825 octave_user_code * get_function (const size_t i) const
826 {
827 if (! is_valid ())
828 return nullptr;
829
830 if (is_function ())
831 return m_fcn;
832
833 if (i < m_methods_cache.size ())
834 return m_methods_cache[i];
835
836 return nullptr;
837 }
838
839private:
840 void populate_function_cache ()
841 {
842 if (m_methods_cache.empty ())
843 {
844 // Not the most efficient, but reuses code:
845 const std::map<std::string, cdef_method>& map
846 = m_cls.get_method_map (false, true);
847 for (const auto& meth : map)
848 {
850 = m_cls.get_method (meth.first).user_code_value (true);
851 if (fcn != nullptr)
852 m_methods_cache.push_back (fcn);
853 }
854
855 // Check get and set methods of properties
856 const std::map<property_key, cdef_property>& prop_map
857 = m_cls.get_property_map (cdef_class::property_all);
858 for (const auto& prop : prop_map)
859 {
860 octave_value get_meth = prop.second.get ("GetMethod");
861 if (get_meth.is_function_handle ())
862 {
864 = get_meth.user_function_value ()->user_code_value (true);
865 if (fcn != nullptr)
866 m_methods_cache.push_back (fcn);
867 }
868
869 octave_value set_meth = prop.second.get ("SetMethod");
870 if (set_meth.is_function_handle ())
871 {
873 = set_meth.user_function_value ()->user_code_value (true);
874 if (fcn != nullptr)
875 m_methods_cache.push_back (fcn);
876 }
877 }
878 }
879 }
880
881private:
882 std::vector<octave_user_code *> m_methods_cache;
883 cdef_class m_cls;
884 octave_user_code *m_fcn;
885 bool m_is_valid;
886};
887
888// Given file name fname, find the subfunction at line and create
889// a breakpoint there. Put the system into debug_mode.
891bp_table::add_breakpoints_in_function (const std::string& fcn_ident,
892 const bp_table::bp_lines& lines,
893 const std::string& condition)
894{
895 user_code_provider user_code ("add_breakpoints_in_function", fcn_ident,
896 m_evaluator.get_user_code (fcn_ident));
897
898 condition_valid (condition); // Throw error if condition not valid.
899
900 bp_lines retval;
901
902 for (const auto& lineno : lines)
903 {
904 octave_user_code *dbg_fcn = user_code (lineno);
905
906 if (! dbg_fcn)
907 error ("add_breakpoints_in_function: unable to find function '%s'\n",
908 fcn_ident.c_str ());
909
910 // We've found the right (sub)function. Now insert the breakpoint.
911 bp_lines line_info;
912 line_info.insert (lineno);
913
914 bp_lines ret_one;
915
916 std::string ident = fcn_ident;
917 if (! user_code.is_function () && fcn_ident[0] != '@')
918 {
919 // identifier of the form @class_name/method_name
920 ident = "@" + fcn_ident + "/" + dbg_fcn->name ();
921 }
922
923 if (add_breakpoint_1 (dbg_fcn, ident, line_info, condition, ret_one))
924 {
925 if (! ret_one.empty ())
926 {
927 int line = *(ret_one.begin ());
928
929 if (line)
930 retval.insert (line);
931 }
932 }
933 }
934
935 m_evaluator.reset_debug_state ();
936
937 return retval;
938}
939
940int
941bp_table::add_breakpoint_in_file (const std::string& file,
942 int line,
943 const std::string& condition)
944{
945 // Duplicates what the GUI was doing previously, but this action
946 // should not be specific to the GUI.
947
948 bp_file_info info (m_evaluator, file);
949
950 if (! info.ok ())
951 return 0;
952
953 std::string fcn_ident;
954 if (info.class_name ().empty () || info.fcn ()[0] == '@')
955 fcn_ident = info.fcn ();
956 else
957 fcn_ident = "@" + info.class_name () + "/" + info.fcn ();
958
959 return add_breakpoint_in_function (fcn_ident, line, condition);
960}
961
963bp_table::add_breakpoints_in_file (const std::string& file,
964 const bp_lines& lines,
965 const std::string& condition)
966{
967 // Duplicates what the GUI was doing previously, but this action
968 // should not be specific to the GUI.
969
970 bp_file_info info (m_evaluator, file);
971
972 if (! info.ok ())
973 return bp_lines ();
974
975 std::string fcn_ident;
976 if (info.class_name ().empty () || info.fcn ()[0] == '@')
977 fcn_ident = info.fcn ();
978 else
979 fcn_ident = "@" + info.class_name () + "/" + info.fcn ();
980
981 return add_breakpoints_in_function (fcn_ident, lines, condition);
982}
983
984int
985bp_table::remove_breakpoint_1 (octave_user_code *fcn,
986 const std::string& fcn_ident,
987 const bp_table::bp_lines& lines)
988{
989 int retval = 0;
990
991 std::string file = fcn->fcn_file_name ();
992
993 tree_statement_list *cmds = fcn->body ();
994
995 // FIXME: move the operation on cmds to the tree_statement_list class?
996
997 if (cmds)
998 {
999 octave_value_list results = cmds->list_breakpoints ();
1000
1001 if (results.length () > 0)
1002 {
1003 interpreter& interp = m_evaluator.get_interpreter ();
1004
1005 event_manager& evmgr = interp.get_event_manager ();
1006
1007 for (const auto& lineno : lines)
1008 {
1009 cmds->delete_breakpoint (lineno);
1010
1011 if (! file.empty ())
1012 evmgr.update_breakpoint (false, file, lineno);
1013 }
1014
1015 results = cmds->list_breakpoints ();
1016
1017 auto it = m_bp_set.find (fcn_ident);
1018 if (results.empty () && it != m_bp_set.end ())
1019 m_bp_set.erase (it);
1020 }
1021
1022 retval = results.length ();
1023 }
1024
1025 return retval;
1026}
1027
1028int
1029bp_table::remove_breakpoint_from_function (const std::string& fcn_ident,
1030 int line)
1031{
1032 bp_lines line_info;
1033 line_info.insert (line);
1034
1035 return remove_breakpoints_from_function (fcn_ident, line_info);
1036}
1037
1038int
1040 const bp_table::bp_lines& lines)
1041{
1042 int retval = 0;
1043
1044 if (lines.empty ())
1045 {
1046 bp_lines results = remove_all_breakpoints_from_function (fcn_ident);
1047 retval = results.size ();
1048 }
1049 else
1050 {
1051 octave_user_code *dbg_fcn = m_evaluator.get_user_code (fcn_ident);
1052 user_code_provider user_code ("remove_breakpoints_from_function",
1053 fcn_ident, dbg_fcn);
1054
1055 // Remove all breakpoints from all functions
1056 for (const auto line : lines)
1057 {
1058 octave_user_code *fcn = user_code (line);
1059 std::string file = fcn->fcn_file_name ();
1060
1061 tree_statement_list *cmds = fcn->body ();
1062 if (cmds)
1063 {
1064 octave_value_list results = cmds->list_breakpoints ();
1065
1066 if (results.length () > 0)
1067 {
1068 interpreter& interp = m_evaluator.get_interpreter ();
1069 event_manager& evmgr = interp.get_event_manager ();
1070
1071 cmds->delete_breakpoint (line);
1072
1073 if (! file.empty ())
1074 evmgr.update_breakpoint (false, file, line);
1075 }
1076 }
1077 }
1078
1079 // Remove all breakpoints from all subfunctions
1080 if (dbg_fcn != nullptr)
1081 {
1082 // Search subfunctions in the order they appear in the file.
1083 const std::list<std::string> subfcn_names
1084 = dbg_fcn->subfunction_names ();
1085
1086 std::map<std::string, octave_value> subfcns
1087 = dbg_fcn->subfunctions ();
1088
1089 for (const auto& subf_nm : subfcn_names)
1090 {
1091 const auto q = subfcns.find (subf_nm);
1092
1093 if (q != subfcns.end ())
1094 {
1095 octave_user_code *dbg_subfcn
1096 = q->second.user_code_value ();
1097
1098 retval += remove_breakpoint_1 (dbg_subfcn, fcn_ident, lines);
1099 }
1100 }
1101 }
1102 // Remove file from breakpoint set if no breakpoints remain
1103 octave_value_list fname_list = {fcn_ident};
1104 const bool no_breakpoints
1105 = get_breakpoint_list (fname_list).empty ();
1106 auto iter = m_bp_set.find (fcn_ident);
1107 if (no_breakpoints && iter != m_bp_set.end ())
1108 m_bp_set.erase (iter);
1109 }
1110
1111 m_evaluator.reset_debug_state ();
1112
1113 return retval;
1114}
1115
1116// Remove all breakpoints from a file, including those in subfunctions.
1117
1120 bool silent)
1121{
1122 bp_lines retval;
1123
1124 octave_user_code *fcn = m_evaluator.get_user_code (fcn_ident);
1125 user_code_provider user_code ("remove_all_breakpoints_from_function",
1126 fcn_ident, fcn, silent);
1127
1128 if (user_code.is_valid ())
1129 {
1130 for (size_t i = 0; i != user_code.number_of_functions (); ++i)
1131 {
1132 octave_user_code *dbg_fcn = user_code.get_function (i);
1133 std::string file = dbg_fcn->fcn_file_name ();
1134
1135 tree_statement_list *cmds = dbg_fcn->body ();
1136
1137 if (cmds)
1138 {
1139 interpreter& interp = m_evaluator.get_interpreter ();
1140
1141 event_manager& evmgr = interp.get_event_manager ();
1142
1143 retval = cmds->remove_all_breakpoints (evmgr, file);
1144 }
1145 }
1146
1147 auto it = m_bp_set.find (fcn_ident);
1148 if (it != m_bp_set.end ())
1149 m_bp_set.erase (it);
1150 }
1151
1152 m_evaluator.reset_debug_state ();
1153
1154 return retval;
1155}
1156
1157int
1158bp_table::remove_breakpoint_from_file (const std::string& file, int line)
1159{
1160 // Duplicates what the GUI was doing previously, but this action
1161 // should not be specific to the GUI.
1162
1163 bp_file_info info (m_evaluator, file);
1164
1165 if (! info.ok ())
1166 return 0;
1167
1168 return remove_breakpoint_from_function (info.fcn (), line);
1169}
1170
1171int
1173 const bp_lines& lines)
1174{
1175 // Duplicates what the GUI was doing previously, but this action
1176 // should not be specific to the GUI.
1177
1178 bp_file_info info (m_evaluator, file);
1179
1180 if (! info.ok ())
1181 return 0;
1182
1183 return remove_breakpoints_from_function (info.fcn (), lines);
1184}
1185
1188 bool silent)
1189{
1190 // Duplicates what the GUI was doing previously, but this action
1191 // should not be specific to the GUI.
1192
1193 bp_file_info info (m_evaluator, file);
1194
1195 if (! info.ok ())
1196 return bp_lines ();
1197
1198 return remove_all_breakpoints_from_function (info.fcn (), silent);
1199}
1200
1201void
1203{
1204 // Odd loop structure required because delete will invalidate
1205 // m_bp_set iterators.
1206 for (auto it = m_bp_set.cbegin (), it_next = it;
1207 it != m_bp_set.cend ();
1208 it = it_next)
1209 {
1210 ++it_next;
1212 }
1213
1214 m_evaluator.reset_debug_state ();
1215}
1216
1217std::string
1218find_bkpt_list (octave_value_list slist, std::string match)
1219{
1220 std::string retval;
1221
1222 for (int i = 0; i < slist.length (); i++)
1223 {
1224 if (slist(i).string_value () == match)
1225 {
1226 retval = slist(i).string_value ();
1227 break;
1228 }
1229 }
1230
1231 return retval;
1232}
1233
1236{
1237 fname_bp_map retval;
1238
1239 // make copy since changes may invalidate iters of m_bp_set.
1240 std::set<std::string> tmp_bp_set = m_bp_set;
1241
1242 for (auto& bp_fname : tmp_bp_set)
1243 {
1244 if (fname_list.empty ()
1245 || find_bkpt_list (fname_list, bp_fname) != "")
1246 {
1247 octave_user_code *dbg_fcn = m_evaluator.get_user_code (bp_fname);
1248 user_code_provider user_code ("get_breakpoint_list", bp_fname,
1249 dbg_fcn);
1250 // Gather breakpoints from all functions in the file
1251 std::list<bp_type> all_bkpts;
1252 std::list<bp_type>::iterator it (all_bkpts.begin ());
1253 for (size_t i = 0; i != user_code.number_of_functions (); ++i)
1254 {
1255 octave_user_code *fcn = user_code.get_function (i);
1256 if (fcn)
1257 {
1258 tree_statement_list *cmds = fcn->body ();
1259 // FIXME: move the operation on cmds to the
1260 // tree_statement_list class?
1261 if (cmds)
1262 {
1263 const std::list<bp_type>& bp
1264 = cmds->breakpoints_and_conds ();
1265 if (!bp.empty())
1266 it = all_bkpts.insert (it, bp.cbegin (),
1267 bp.cend ());
1268 }
1269 }
1270 }
1271
1272 if (! all_bkpts.empty ())
1273 retval[bp_fname] = all_bkpts;
1274
1275 // look for breakpoints in subfunctions
1276 // Assuming classdefs can't have subfunctions
1277 if (dbg_fcn != nullptr)
1278 {
1279 const std::list<std::string> subf_nm
1280 = dbg_fcn->subfunction_names ();
1281
1282 std::map<std::string, octave_value> subfcns
1283 = dbg_fcn->subfunctions ();
1284
1285 for (const auto& subfcn_nm : subf_nm)
1286 {
1287 const auto q = subfcns.find (subfcn_nm);
1288
1289 if (q != subfcns.end ())
1290 {
1291 octave_user_code *dbg_subfcn
1292 = q->second.user_code_value ();
1293
1294 tree_statement_list *cmds = dbg_subfcn->body ();
1295 if (cmds)
1296 {
1297 std::list<bp_type> bkpts
1298 = cmds->breakpoints_and_conds ();
1299
1300 if (! bkpts.empty ())
1301 {
1302 std::string key
1303 = bp_fname + '>' + dbg_subfcn->name ();
1304
1305 retval[key] = bkpts;
1306 }
1307 }
1308 }
1309 }
1310 }
1311 }
1312 }
1313
1314 return retval;
1315}
1316
1317// Report the status of "dbstop if error ..." and "dbstop if warning ..."
1318// If to_screen is true, the output goes to octave_stdout; otherwise it is
1319// returned.
1320// If dbstop if error is true but no explicit IDs are specified, the return
1321// value will have an empty field called "errs". If IDs are specified, the
1322// "errs" field will have a row per ID. If dbstop if error is false, there
1323// is no "errs" field. The "warn" field is set similarly by dbstop if warning
1324
1327{
1328 octave_map retval;
1329
1330 interpreter& interp = m_evaluator.get_interpreter ();
1331 error_system& es = interp.get_error_system ();
1332
1333 // print dbstop if error information
1334 if (es.debug_on_error ())
1335 {
1336 if (m_errors_that_stop.empty ())
1337 {
1338 if (to_screen)
1339 octave_stdout << "stop if error\n";
1340 else
1341 retval.assign ("errs", octave_value (""));
1342 }
1343 else
1344 {
1345 Cell errs (dim_vector (bp_table::m_errors_that_stop.size (), 1));
1346 int i = 0;
1347
1348 for (const auto& e : m_errors_that_stop)
1349 {
1350 if (to_screen)
1351 octave_stdout << "stop if error " << e << "\n";
1352 else
1353 errs(i++) = e;
1354 }
1355 if (! to_screen)
1356 retval.assign ("errs", octave_value (errs));
1357 }
1358 }
1359
1360 // print dbstop if caught error information
1361 if (es.debug_on_caught ())
1362 {
1363 if (m_caught_that_stop.empty ())
1364 {
1365 if (to_screen)
1366 octave_stdout << "stop if caught error\n";
1367 else
1368 retval.assign ("caught", octave_value (""));
1369 }
1370 else
1371 {
1372 Cell errs (dim_vector (m_caught_that_stop.size (), 1));
1373 int i = 0;
1374
1375 for (const auto& e : m_caught_that_stop)
1376 {
1377 if (to_screen)
1378 octave_stdout << "stop if caught error " << e << "\n";
1379 else
1380 errs(i++) = e;
1381 }
1382 if (! to_screen)
1383 retval.assign ("caught", octave_value (errs));
1384 }
1385 }
1386
1387 // print dbstop if warning information
1388 if (es.debug_on_warning ())
1389 {
1390 if (m_warnings_that_stop.empty ())
1391 {
1392 if (to_screen)
1393 octave_stdout << "stop if warning\n";
1394 else
1395 retval.assign ("warn", octave_value (""));
1396 }
1397 else
1398 {
1399 Cell warn (dim_vector (m_warnings_that_stop.size (), 1));
1400 int i = 0;
1401
1402 for (const auto& w : m_warnings_that_stop)
1403 {
1404 if (to_screen)
1405 octave_stdout << "stop if warning " << w << "\n";
1406 else
1407 warn(i++) = w;
1408 }
1409 if (! to_screen)
1410 retval.assign ("warn", octave_value (warn));
1411 }
1412 }
1413
1414 // print dbstop if interrupt information
1416 {
1417 if (to_screen)
1418 octave_stdout << "stop if interrupt\n";
1419 else
1420 retval.assign ("intr", octave_value (""));
1421 }
1422
1423 return retval;
1424}
1425
1426OCTAVE_END_NAMESPACE(octave)
std::string find_bkpt_list(octave_value_list slist, std::string match)
Definition bp-table.cc:1218
dbstop_args
Definition bp-table.cc:327
@ dbstop_at
Definition bp-table.cc:329
@ dbstop_in
Definition bp-table.cc:328
@ dbstop_if
Definition bp-table.cc:330
@ dbstop_none
Definition bp-table.cc:331
cdef_class lookup_class(const std::string &name, bool error_if_not_found, bool load_if_not_found)
Definition cdef-utils.cc:80
N Dimensional Array with copy-on-write semantics.
Definition Array.h:130
T & elem(octave_idx_type n)
Size of the specified dimension.
Definition Array.h:563
Array< T, Alloc > & insert(const Array< T, Alloc > &a, const Array< octave_idx_type > &idx)
Insert an array into another at a specified position.
bool isempty() const
Size of the specified dimension.
Definition Array.h:652
octave_idx_type numel() const
Number of elements in the array.
Definition Array.h:418
Definition Cell.h:41
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)
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:1029
int remove_breakpoint_from_file(const std::string &file="", int line=1)
Definition bp-table.cc:1158
void remove_all_breakpoints()
Definition bp-table.cc:1202
bp_lines remove_all_breakpoints_from_function(const std::string &fcn_ident, bool silent=false)
Definition bp-table.cc:1119
fname_bp_map get_breakpoint_list(const octave_value_list &fname_list)
Definition bp-table.cc:1235
int remove_breakpoints_from_file(const std::string &file="", const bp_lines &lines=bp_lines())
Definition bp-table.cc:1172
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:1039
int add_breakpoint_in_function(const std::string &fcn_ident="", int line=1, const std::string &condition="")
Definition bp-table.cc:740
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:891
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:941
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:963
bp_lines remove_all_breakpoints_from_file(const std::string &file, bool silent=false)
Definition bp-table.cc:1187
void dbclear_all_signals()
Definition bp-table.cc:120
octave_map stop_on_err_warn_status(bool to_screen)
Definition bp-table.cc:1326
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:90
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="")
void line(int l)
Definition filepos.h:49
error_system & get_error_system()
load_path & get_load_path()
event_manager & get_event_manager()
bool contains_file_in_dir(const std::string &file_name, const std::string &dir_name)
Definition load-path.cc:480
virtual Matrix size()
Definition ov-base.cc:220
virtual bool is_user_function() const
Definition ov-base.h:547
virtual octave_user_code * user_code_value(bool silent=false)
Definition ov-base.cc:962
virtual std::string profiler_name() const
Definition ov-fcn.h:82
virtual std::list< std::string > subfunction_names() const
Definition ov-fcn.h:201
std::string name() const
Definition ov-fcn.h:208
const Cell & contents(const_iterator p) const
Definition oct-map.h:310
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
std::string fcn_file_name() const
Definition ov-usr-fcn.h:112
virtual std::map< std::string, octave_value > subfunctions() const
octave::tree_statement_list * body()
Definition ov-usr-fcn.h:125
octave_user_code * user_code_value(bool=false)
Definition ov-usr-fcn.h:232
Array< octave_value > array_value() const
Definition ovl.h:88
bool empty() const
Definition ovl.h:113
octave_idx_type length() const
Definition ovl.h:111
bool is_function_handle() const
Definition ov.h:768
octave_user_function * user_function_value(bool silent=false) const
octave_user_code * user_code_value(bool silent=false) const
int run()
void reset_debug_state()
Definition pt-eval.cc:1384
interpreter & get_interpreter()
Definition pt-eval.h:422
bool in_debug_repl() const
Definition pt-eval.cc:5145
octave_user_code * get_user_code(const std::string &fname="")
Definition pt-eval.cc:2994
virtual bool is_assignment_expression() const
Definition pt-exp.h:72
octave_value_list list_breakpoints()
Definition pt-stmt.cc:254
std::list< bp_type > breakpoints_and_conds()
Definition pt-stmt.cc:264
void delete_breakpoint(int line)
Definition pt-stmt.cc:232
bp_table::bp_lines remove_all_breakpoints(event_manager &evmgr, const std::string &file)
Definition pt-stmt.cc:312
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:288
tree_expression * expression()
Definition pt-stmt.h:104
bool is_expression() const
Definition pt-stmt.h:78
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
void print_usage()
Definition defun-int.h:72
void warning(const char *fmt,...)
Definition error.cc:1078
void error(const char *fmt,...)
Definition error.cc:1003
F77_RET_T const F77_INT const F77_INT const F77_INT const F77_DBLE const F77_DBLE F77_INT F77_DBLE * V
bool strcmp(const T &str_a, const T &str_b)
Octave string utility functions.
#define octave_stdout
Definition pager.h:301
bool Vdebug_on_interrupt
F77_RET_T len
Definition xerbla.cc:61