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