Debugger: Added $cy_eval() GDB function
authorMark Florisson <markflorisson88@gmail.com>
Fri, 25 Mar 2011 21:18:48 +0000 (22:18 +0100)
committerMark Florisson <markflorisson88@gmail.com>
Fri, 25 Mar 2011 21:18:48 +0000 (22:18 +0100)
Cython/Debugger/Tests/test_libcython_in_gdb.py
Cython/Debugger/libcython.py
Cython/Debugger/libpython.py

index 13e22623519693fcc48b22d796d812d4af55568f..46dba686f061851e5f768c8b8db383842de1a636 100644 (file)
@@ -378,6 +378,22 @@ class TestExec(DebugTestCase):
         gdb.execute('cy exec some_random_var = 14')
         self.assertEqual('14', self.eval_command('some_random_var'))
 
+
+class TestCyEval(DebugTestCase):
+    "Test the $cy_eval() gdb function."
+
+    def test_cy_eval(self):
+        # This function leaks a few objects in the GDB python process. This
+        # is no biggie
+        self.break_and_run('os.path.join("foo", "bar")')
+
+        result = gdb.execute('print $cy_eval("None")', to_string=True)
+        assert re.match(r'\$\d+ = None\n', result), result
+
+        result = gdb.execute('print $cy_eval("[a]")', to_string=True)
+        assert re.match(r'\$\d+ = \[0\]', result), result
+
+
 class TestClosure(DebugTestCase):
 
     def break_and_run_func(self, funcname):
index 8d7771b530c9ba05ac13a93fd96f0c9d1bca3d16..e242bada2c711b4da696b51d888008cd761a2082 100644 (file)
@@ -607,6 +607,7 @@ class CyCy(CythonCommand):
                                             completer_class, prefix=True)
 
         commands = dict(
+            # GDB commands
             import_ = CyImport.register(),
             break_ = CyBreak.register(),
             step = CyStep.register(),
@@ -624,9 +625,12 @@ class CyCy(CythonCommand):
             globals = CyGlobals.register(),
             exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
             _exec = CyExec.register(),
+
+            # GDB functions
             cy_cname = CyCName('cy_cname'),
             cy_cvalue = CyCValue('cy_cvalue'),
             cy_lineno = CyLine('cy_lineno'),
+            cy_eval = CyEval('cy_eval'),
         )
 
         for command_name, command in commands.iteritems():
@@ -1169,15 +1173,14 @@ class CyGlobals(CyLocals):
                                              max_name_length, '    ')
 
 
-class CyExec(CythonCommand, libpython.PyExec):
+
+class EvaluateOrExecuteCodeMixin(object):
     """
-    Execute Python code in the nearest Python or Cython frame.
+    Evaluate or execute Python code in a Cython or Python frame. The 'evalcode'
+    method evaluations Python code, prints a traceback if an exception went
+    uncaught, and returns any return value as a gdb.Value (NULL on exception).
     """
 
-    name = '-cy-exec'
-    command_class = gdb.COMMAND_STACK
-    completer_class = gdb.COMPLETE_NONE
-
     def _fill_locals_dict(self, executor, local_dict_pointer):
         "Fill a remotely allocated dict with values from the Cython C stack"
         cython_func = self.get_cython_function()
@@ -1208,28 +1211,22 @@ class CyExec(CythonCommand, libpython.PyExec):
                         raise gdb.GdbError("Unable to execute Python code.")
                 finally:
                     # PyDict_SetItem doesn't steal our reference
-                    executor.decref(pystringp)
+                    executor.xdecref(pystringp)
 
     def _find_first_cython_or_python_frame(self):
         frame = gdb.selected_frame()
         while frame:
             if (self.is_cython_function(frame) or
                 self.is_python_function(frame)):
+                frame.select()
                 return frame
 
             frame = frame.older()
 
         raise gdb.GdbError("There is no Cython or Python frame on the stack.")
 
-    def invoke(self, expr, from_tty):
-        frame = self._find_first_cython_or_python_frame()
-        if self.is_python_function(frame):
-            libpython.py_exec.invoke(expr, from_tty)
-            return
-
-        expr, input_type = self.readcode(expr)
-        executor = libpython.PythonCodeExecutor()
 
+    def _evalcode_cython(self, executor, code, input_type):
         with libpython.FetchAndRestoreError():
             # get the dict of Cython globals and construct a dict in the
             # inferior with Cython locals
@@ -1240,9 +1237,51 @@ class CyExec(CythonCommand, libpython.PyExec):
             try:
                 self._fill_locals_dict(executor,
                                        libpython.pointervalue(local_dict))
-                executor.evalcode(expr, input_type, global_dict, local_dict)
+                result = executor.evalcode(code, input_type, global_dict,
+                                           local_dict)
             finally:
-                executor.decref(libpython.pointervalue(local_dict))
+                executor.xdecref(libpython.pointervalue(local_dict))
+
+        return result
+
+
+    def _evalcode_python(self, executor, code, input_type):
+        global_dict = gdb.parse_and_eval('PyEval_GetGlobals()')
+        local_dict = gdb.parse_and_eval('PyEval_GetLocals()')
+
+        if (libpython.pointervalue(global_dict) == 0 or
+            libpython.pointervalue(local_dict) == 0):
+            raise gdb.GdbError("Unable to find the locals or globals of the "
+                               "most recent Python function (relative to the "
+                               "selected frame).")
+
+        return executor.evalcode(code, input_type, global_dict, local_dict)
+
+    def evalcode(self, code, input_type):
+        """
+        Evaluate `code` in a Python or Cython stack frame using the given
+        `input_type`.
+        """
+        frame = self._find_first_cython_or_python_frame()
+        executor = libpython.PythonCodeExecutor()
+        if self.is_python_function(frame):
+            return self._evalcode_python(executor, code, input_type)
+        return self._evalcode_cython(executor, code, input_type)
+
+
+class CyExec(CythonCommand, libpython.PyExec, EvaluateOrExecuteCodeMixin):
+    """
+    Execute Python code in the nearest Python or Cython frame.
+    """
+
+    name = '-cy-exec'
+    command_class = gdb.COMMAND_STACK
+    completer_class = gdb.COMPLETE_NONE
+
+    def invoke(self, expr, from_tty):
+        expr, input_type = self.readcode(expr)
+        executor = libpython.PythonCodeExecutor()
+        executor.xdecref(self.evalcode(expr, executor.Py_single_input))
 
 
 # Functions
@@ -1312,6 +1351,18 @@ class CyLine(gdb.Function, CythonBase):
     def invoke(self):
         return self.get_cython_lineno()
 
+
+class CyEval(gdb.Function, CythonBase, EvaluateOrExecuteCodeMixin):
+    """
+    Evaluate Python code in the nearest Python or Cython frame and return
+    """
+
+    @gdb_function_value_to_unicode
+    def invoke(self, python_expression):
+        input_type = libpython.PythonCodeExecutor.Py_eval_input
+        return self.evalcode(python_expression, input_type)
+
+
 cython_info = CythonInfo()
 cy = CyCy.register()
 cython_info.cy = cy
index 4729412ca27a6574f1bc0d55ce21404d96ace33b..44d9f33d7948b49b05e03026aab801cfea3444a6 100644 (file)
@@ -2190,12 +2190,12 @@ class PythonInfo(LanguageInfo):
         try:
             tstate = frame.read_var('tstate').dereference()
             if gdb.parse_and_eval('tstate->frame == f'):
-                # tstate local variable initialized
+                # tstate local variable initialized, check for an exception
                 inf_type = tstate['curexc_type']
                 inf_value = tstate['curexc_value']
+
                 if inf_type:
-                    return 'An exception was raised: %s(%s)' % (inf_type,
-                                                                inf_value)
+                    return 'An exception was raised: %s(%s)' % (inf_value,)
         except (ValueError, RuntimeError), e:
             # Could not read the variable tstate or it's memory, it's ok
             pass
@@ -2342,7 +2342,7 @@ class PythonCodeExecutor(object):
         "Increment the reference count of a Python object in the inferior."
         gdb.parse_and_eval('Py_IncRef((PyObject *) %d)' % pointer)
 
-    def decref(self, pointer):
+    def xdecref(self, pointer):
         "Decrement the reference count of a Python object in the inferior."
         # Py_DecRef is like Py_XDECREF, but a function. So we don't have
         # to check for NULL. This should also decref all our allocated
@@ -2382,10 +2382,11 @@ class PythonCodeExecutor(object):
 
         with FetchAndRestoreError():
             try:
-                self.decref(gdb.parse_and_eval(code))
+                pyobject_return_value = gdb.parse_and_eval(code)
             finally:
                 self.free(pointer)
 
+        return pyobject_return_value
 
 class FetchAndRestoreError(PythonCodeExecutor):
     """
@@ -2484,17 +2485,9 @@ class PyExec(gdb.Command):
 
     def invoke(self, expr, from_tty):
         expr, input_type = self.readcode(expr)
-
         executor = PythonCodeExecutor()
-        global_dict = gdb.parse_and_eval('PyEval_GetGlobals()')
-        local_dict = gdb.parse_and_eval('PyEval_GetLocals()')
-
-        if pointervalue(global_dict) == 0 or pointervalue(local_dict) == 0:
-            raise gdb.GdbError("Unable to find the locals or globals of the "
-                               "most recent Python function (relative to the "
-                               "selected frame).")
-
-        executor.evalcode(expr, input_type, global_dict, local_dict)
+        result = executor.evalcode(expr, input_type, global_dict, local_dict)
+        executor.xdecref(result)
 
 
 gdb.execute('set breakpoint pending on')