# HG changeset patch # User Markus Mützel # Date 1713611630 -7200 # Sat Apr 20 13:13:50 2024 +0200 # Node ID e70cc4c3be65ace488c404f75e3b8dd520be9bff # Parent 69eb4c27d8c80e8a21a96fc69cf76c0f27c30486 Support setting breakpoints in get and set methods of classdef classes (bug #65610). * cdef-property.h (cdef_property_rep, cdef_property): Add new methods `get_get_method` and `get_set_method` to get a handle to the `get` or `set` function of a `classdef` property. * cdef-class.cc (cdef_class::cdef_class_rep::get_method): Also check for any `get` or `set` methods of `classdef` properties. * bp-table.cc (user_code_provider::operator ()): Support getting (closest) user code to `get` or `set` methods of `classdef` classes. (user_code_provider::populate_function_cache): Add `get` and `set` methods to function cache for `classdef` classes. * pt-eval.cc (tree_evaluator::get_user_code): Support getting user code for `get` or `set` methods of `classdef` properties. * test/classdef-debug/classdef_breakpoints2.m: Add handle class with get and set methods for new self tests. * test/classdef-debug/test-classdef-breakpoints.tst: Add new tests for adding and clearing breakpoints in `set` and `get` methods of `classdef` properties by line number or function name. Make sure breakpoints are deleted in existing tests also on test failures. Fix syntax error in 69eb4c27d8c8. * test/classdef-debug/module.mk: Add new file to build system. * etc/NEWS.10.md: Add note about new feature. diff -r 69eb4c27d8c8 -r e70cc4c3be65 etc/NEWS.10.md --- a/etc/NEWS.10.md Fri Apr 19 18:30:39 2024 +0200 +++ b/etc/NEWS.10.md Sat Apr 20 13:13:50 2024 +0200 @@ -24,11 +24,14 @@ - `jsonencode` now outputs integers and floating point integers without ".0" suffix. - + - `hist` now accepts N-dimensional array inputs for input `Y` which is processed in columns as if the array was flattened to a 2-dimensional array. +- Support setting breakpoints in `set` or `get` methods of `classdef` + properties (bug #65610). + ### Graphical User Interface ### Graphics backend diff -r 69eb4c27d8c8 -r e70cc4c3be65 libinterp/octave-value/cdef-class.cc --- a/libinterp/octave-value/cdef-class.cc Fri Apr 19 18:30:39 2024 +0200 +++ b/libinterp/octave-value/cdef-class.cc Sat Apr 20 13:13:50 2024 +0200 @@ -721,27 +721,73 @@ const octave_value& fcn = i->second.get_function (); octave_user_code *user_code = fcn.user_code_value (); - if (user_code == nullptr) + if (! user_code) continue; - octave_user_function* pfcn - = dynamic_cast (user_code); + octave_user_function *pfcn + = dynamic_cast (user_code); - if (pfcn == nullptr) + if (! pfcn) continue; octave::filepos end_pos = pfcn->end_pos (); int end_line = end_pos.line (); - if (line <= end_line && end_line <= closest_match_end_line && pfcn->is_defined () - && pfcn->is_user_code ()) + if (line <= end_line && end_line <= closest_match_end_line + && pfcn->is_defined () && pfcn->is_user_code ()) { closest_match = fcn; closest_match_end_line = end_line; } } + // repeat the same for set and get methods of properties + for (auto i = m_property_map.cbegin (); i != m_property_map.cend (); ++i) + { + const octave_value& get_meth = i->second.get_get_method (); + if (get_meth.is_function_handle ()) + { + octave_user_function *pfcn + = get_meth.user_function_value (); + + if (! pfcn) + continue; + + octave::filepos end_pos = pfcn->end_pos (); + + int end_line = end_pos.line (); + + if (line <= end_line && end_line <= closest_match_end_line + && pfcn->is_defined () && pfcn->is_user_code ()) + { + closest_match = get_meth; + closest_match_end_line = end_line; + } + } + + const octave_value& set_meth = i->second.get_set_method (); + if (set_meth.is_function_handle ()) + { + octave_user_function *pfcn + = set_meth.user_function_value (); + + if (! pfcn) + continue; + + octave::filepos end_pos = pfcn->end_pos (); + + int end_line = end_pos.line (); + + if (line <= end_line && end_line <= closest_match_end_line + && pfcn->is_defined () && pfcn->is_user_code ()) + { + closest_match = set_meth; + closest_match_end_line = end_line; + } + } + } + return closest_match; } diff -r 69eb4c27d8c8 -r e70cc4c3be65 libinterp/octave-value/cdef-property.h --- a/libinterp/octave-value/cdef-property.h Fri Apr 19 18:30:39 2024 +0200 +++ b/libinterp/octave-value/cdef-property.h Sat Apr 20 13:13:50 2024 +0200 @@ -80,6 +80,12 @@ bool do_check_access = true, const std::string& who = ""); + octave_value get_get_method () const + { return get ("GetMethod"); } + + octave_value get_set_method () const + { return get ("SetMethod"); } + OCTINTERP_API bool check_get_access () const; OCTINTERP_API bool check_set_access () const; @@ -151,6 +157,12 @@ get_rep ()->set_value (obj, val, do_check_access, who); } + octave_value get_get_method () const + { return get_rep ()->get_get_method (); } + + octave_value get_set_method () const + { return get_rep ()->get_set_method (); } + bool check_get_access () const { return get_rep ()->check_get_access (); diff -r 69eb4c27d8c8 -r e70cc4c3be65 libinterp/parse-tree/bp-table.cc --- a/libinterp/parse-tree/bp-table.cc Fri Apr 19 18:30:39 2024 +0200 +++ b/libinterp/parse-tree/bp-table.cc Sat Apr 20 13:13:50 2024 +0200 @@ -800,7 +800,11 @@ if (line < 0) return nullptr; - return m_cls.get_method (line).user_code_value (true); + octave_value method = m_cls.get_method (line); + if (method.is_function_handle ()) + return method.user_function_value ()->user_code_value (true); + else + return method.user_code_value (true); } bool is_function () const { return m_fcn; } @@ -847,6 +851,31 @@ if (fcn != nullptr) m_methods_cache.push_back (fcn); } + + + // Check get and set methods of properties + const std::map& prop_map + = m_cls.get_property_map (cdef_class::property_all); + for (auto iter = prop_map.cbegin (); iter != prop_map.cend (); ++iter) + { + octave_value get_meth = iter->second.get_get_method (); + if (get_meth.is_function_handle ()) + { + octave_user_code *fcn + = get_meth.user_function_value ()->user_code_value (true); + if (fcn != nullptr) + m_methods_cache.push_back (fcn); + } + + octave_value set_meth = iter->second.get_set_method (); + if (set_meth.is_function_handle ()) + { + octave_user_code *fcn + = set_meth.user_function_value ()->user_code_value (true); + if (fcn != nullptr) + m_methods_cache.push_back (fcn); + } + } } } diff -r 69eb4c27d8c8 -r e70cc4c3be65 libinterp/parse-tree/pt-eval.cc --- a/libinterp/parse-tree/pt-eval.cc Fri Apr 19 18:30:39 2024 +0200 +++ b/libinterp/parse-tree/pt-eval.cc Sat Apr 20 13:13:50 2024 +0200 @@ -3036,11 +3036,42 @@ cdef_class cls = cdm.find_class (dispatch_type, false); if (cls.ok () && cls.get_name () == dispatch_type) { - cdef_method meth = cls.find_method (method); - if (meth.ok () && meth.get_name () == method) - fcn = meth.get_function (); + if (! method.compare (0, 4, "get.")) + { + // find get method of classdef property + std::string prop_name = method.substr (4); + cdef_property prop = cls.find_property (prop_name); + if (prop.ok () && prop.get_name () == prop_name) + { + const octave_value& get_meth = prop.get_get_method (); + if (get_meth.is_function_handle ()) + return get_meth.user_function_value ()->user_code_value (); + else + return nullptr; + } + } + else if (! method.compare (0, 4, "set.")) + { + // find set method of classdef property + std::string prop_name = method.substr (4); + cdef_property prop = cls.find_property (prop_name); + if (prop.ok () && prop.get_name () == prop_name) + { + const octave_value& set_meth = prop.get_set_method (); + if (set_meth.is_function_handle ()) + return set_meth.user_function_value ()->user_code_value (); + else + return nullptr; + } + } else - return nullptr; + { + cdef_method meth = cls.find_method (method); + if (meth.ok () && meth.get_name () == method) + fcn = meth.get_function (); + else + return nullptr; + } } // If there is no classdef method, then try legacy classes. diff -r 69eb4c27d8c8 -r e70cc4c3be65 test/classdef-debug/classdef_breakpoints2.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/classdef-debug/classdef_breakpoints2.m Sat Apr 20 13:13:50 2024 +0200 @@ -0,0 +1,19 @@ +classdef classdef_breakpoints2 < handle + properties (Dependent) + m_prop; + endproperties + properties (SetAccess=private) + m_prop_value = []; + endproperties + methods + function this = classdef_breakpoints2 (prop) + this.m_prop = prop; + endfunction + function val = get.m_prop (this) + val = this.m_prop_value; + endfunction + function set.m_prop (this, val) + this.m_prop_value = val; + endfunction + endmethods +endclassdef diff -r 69eb4c27d8c8 -r e70cc4c3be65 test/classdef-debug/module.mk --- a/test/classdef-debug/module.mk Fri Apr 19 18:30:39 2024 +0200 +++ b/test/classdef-debug/module.mk Sat Apr 20 13:13:50 2024 +0200 @@ -1,5 +1,6 @@ classdef_breakpoints_TEST_FILES = \ %reldir%/classdef_breakpoints.m \ + %reldir%/classdef_breakpoints2.m \ %reldir%/test-classdef-breakpoints.tst TEST_FILES += $(classdef_breakpoints_TEST_FILES) diff -r 69eb4c27d8c8 -r e70cc4c3be65 test/classdef-debug/test-classdef-breakpoints.tst --- a/test/classdef-debug/test-classdef-breakpoints.tst Fri Apr 19 18:30:39 2024 +0200 +++ b/test/classdef-debug/test-classdef-breakpoints.tst Sat Apr 20 13:13:50 2024 +0200 @@ -30,6 +30,7 @@ %! dbclear classdef_breakpoints; %! assert_dbstatus ([]); %! unwind_protect_cleanup +%! dbclear classdef_breakpoints; %! if (isguirunning ()) %! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg); %! endif @@ -59,13 +60,14 @@ %! dbclear classdef_breakpoints; %! assert_dbstatus ([]); %! unwind_protect_cleanup +%! dbclear classdef_breakpoints; %! if (isguirunning ()) %! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg); %! endif %! end_unwind_protect ## Try to add breakpoint in non-existent method -%!test <65610*> +%!test <*65610> %! if (isguirunning ()) %! orig_show_dbg = __event_manager_gui_preference__ ("editor/show_dbg_file", %! "false"); @@ -89,7 +91,64 @@ %! dbstop classdef_breakpoints 19; %! assert_dbstatus ([20]); %! unwind_protect_cleanup +%! dbclear classdef_breakpoints; +%! if (isguirunning ()) +%! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg); +%! endif +%! end_unwind_protect + +## Set breakpoints in set or get methods by line numbers +%!test <*65610> +%! if (isguirunning ()) +%! orig_show_dbg = __event_manager_gui_preference__ ("editor/show_dbg_file", +%! "false"); +%! endif +%! unwind_protect +%! ## Add breakpoints in different member functions using line numbers. +%! dbstop classdef_breakpoints2 13 16 10; +%! assert_dbstatus ([10, 13, 16]); +%! +%! ## Remove one breakpoint and confirm the others remain. +%! dbclear classdef_breakpoints2 16; +%! assert_dbstatus ([10, 13]); +%! +%! ## Clear all breakpoints, none should be left. +%! dbclear classdef_breakpoints2; +%! assert_dbstatus ([]); +%! unwind_protect_cleanup +%! dbclear classdef_breakpoints2; %! if (isguirunning ()) %! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg); %! endif %! end_unwind_protect + +## Set breakpoints in set or get methods by method name +%!test +%! if (isguirunning ()) +%! orig_show_dbg = __event_manager_gui_preference__ ("editor/show_dbg_file", +%! "false"); +%! endif +%! unwind_protect +%! ## Add breakpoint in constructor +%! dbstop @classdef_breakpoints2/classdef_breakpoints2; +%! assert_dbstatus ([10]); +%! +%! ## Add breakpoints in methods. +%! dbstop @classdef_breakpoints2/get.m_prop; +%! dbstop @classdef_breakpoints2/set.m_prop; +%! assert_dbstatus ([10, 13, 16]); +%! +%! ## Remove breakpoint from one method. +%! dbclear @classdef_breakpoints2/get.m_prop; +%! assert_dbstatus ([10, 16]); +%! +%! ## Clear all breakpoints, none should be left. +%! dbclear classdef_breakpoints2; +%! assert_dbstatus ([]); +%! unwind_protect_cleanup +%! dbclear classdef_breakpoints2; +%! if (isguirunning ()) +%! __event_manager_gui_preference__ ("editor/show_dbg_file", orig_show_dbg); +%! endif +%! end_unwind_protect +