changeset 33571:742d8fc77688

Support setting breakpoints in get and set methods of classdef properties (bug #65610). * 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.
author Markus Mützel <markus.muetzel@gmx.de>
date Sat, 20 Apr 2024 13:13:50 +0200
parents c978eff7a857
children c0d79d64a1b8
files etc/NEWS.10.md libinterp/octave-value/cdef-class.cc libinterp/parse-tree/bp-table.cc libinterp/parse-tree/pt-eval.cc test/classdef-debug/classdef_breakpoints2.m test/classdef-debug/module.mk test/classdef-debug/test-classdef-breakpoints.tst
diffstat 7 files changed, 200 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/etc/NEWS.10.md	Sat May 11 14:59:27 2024 +0200
+++ b/etc/NEWS.10.md	Sat Apr 20 13:13:50 2024 +0200
@@ -31,6 +31,9 @@
 - The third output for `unique` is now correct when `stable` sort option is
   used.
 
+- Support setting breakpoints in `set` or `get` methods of `classdef`
+  properties (bug #65610).
+
 ### Graphical User Interface
 
 ### Graphics backend
--- a/libinterp/octave-value/cdef-class.cc	Sat May 11 14:59:27 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<octave_user_function*> (user_code);
+      octave_user_function *pfcn
+        = dynamic_cast<octave_user_function *> (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 ("GetMethod");
+      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 ("SetMethod");
+      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;
 }
 
--- a/libinterp/parse-tree/bp-table.cc	Sat May 11 14:59:27 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<std::string, cdef_property>& 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 ("GetMethod");
+              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 ("SetMethod");
+              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);
+                }
+            }
         }
   }
 
--- a/libinterp/parse-tree/pt-eval.cc	Sat May 11 14:59:27 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 ("GetMethod");
+                      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 ("SetMethod");
+                      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.
--- /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
--- a/test/classdef-debug/module.mk	Sat May 11 14:59:27 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)
--- a/test/classdef-debug/test-classdef-breakpoints.tst	Sat May 11 14:59:27 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
+