Disable watchpoint stepping by default (use cy step -w or cy step --watchpoint to...
[cython.git] / Cython / Debugger / libcython.py
index dd3c99607986e8c6717f5b4e4379bf7c006b6202..4648d522a45448b143dc3ebe98fd0dd80e5168e0 100644 (file)
@@ -159,11 +159,12 @@ class CythonModule(object):
 
 class CythonVariable(object):
 
-    def __init__(self, name, cname, qualified_name, type):
+    def __init__(self, name, cname, qualified_name, type, lineno):
         self.name = name
         self.cname = cname
         self.qualified_name = qualified_name
         self.type = type
+        self.lineno = int(lineno)
 
 class CythonFunction(CythonVariable):
     def __init__(self, 
@@ -177,10 +178,10 @@ class CythonFunction(CythonVariable):
         super(CythonFunction, self).__init__(name, 
                                              cname, 
                                              qualified_name, 
-                                             type)
+                                             type,
+                                             lineno)
         self.module = module
         self.pf_cname = pf_cname
-        self.lineno = int(lineno)
         self.locals = {}
         self.arguments = []
         self.step_into_functions = set()
@@ -255,7 +256,7 @@ class CythonBase(object):
                 filename = None
                 lineno = 0
             else:
-                filename = symbol_and_line_obj.symtab.filename
+                filename = symbol_and_line_obj.symtab.fullname()
                 lineno = symbol_and_line_obj.line
                 if pygments:
                     lexer = pygments.lexers.CLexer(stripall=False)
@@ -277,8 +278,7 @@ class CythonBase(object):
         older_frame = frame.older()
         if self.is_cython_function(frame) or self.is_python_function(frame):
             return True
-        elif (parameters.step_into_c_code and 
-              older_frame and self.is_cython_function(older_frame)):
+        elif older_frame and self.is_cython_function(older_frame):
             # direct C function call from a Cython function
             cython_func = self.get_cython_function(older_frame)
             return name in cython_func.step_into_functions
@@ -347,11 +347,7 @@ class CythonBase(object):
         
         selected_frame.select()
     
-    def get_cython_globals_dict(self):
-        """
-        Get the Cython globals dict where the remote names are turned into
-        local strings.
-        """
+    def get_remote_cython_globals_dict(self):
         m = gdb.parse_and_eval('__pyx_m')
         
         try:
@@ -362,7 +358,16 @@ class CythonBase(object):
                 with debugging support (-g)?"""))
             
         m = m.cast(PyModuleObject.pointer())
-        pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(m['md_dict'])
+        return m['md_dict']
+        
+    
+    def get_cython_globals_dict(self):
+        """
+        Get the Cython globals dict where the remote names are turned into
+        local strings.
+        """
+        remote_dict = self.get_remote_cython_globals_dict()
+        pyobject_dict = libpython.PyObjectPtr.from_pyobject_ptr(remote_dict)
         
         result = {}
         seen = set()
@@ -383,6 +388,11 @@ class CythonBase(object):
             print '%s%-*s = %s%s' % (prefix, max_name_length, name, typename, 
                                      value)
 
+    def is_initialized(self, cython_func, local_name):
+        cur_lineno = self.get_cython_lineno()
+        return (local_name in cython_func.arguments or
+                (local_name in cython_func.locals and
+                 cur_lineno > cython_func.locals[local_name].lineno))
 
 class SourceFileDescriptor(object):
     def __init__(self, filename, lexer, formatter=None):
@@ -506,12 +516,6 @@ class TerminalBackground(CythonParameter):
     Tell cygdb about the user's terminal background (light or dark).
     """
 
-class StepIntoCCode(CythonParameter):
-    """
-    Tells cygdb whether to step into C functions called directly from Cython
-    code.
-    """
-
 class CythonParameters(object):
     """
     Simple container class that might get more functionality in the distant
@@ -534,11 +538,6 @@ class CythonParameters(object):
             gdb.COMMAND_FILES,
             gdb.PARAM_STRING,
             "dark")
-        self.step_into_c_code = StepIntoCCode(
-            'cy_step_into_c_code',
-            gdb.COMMAND_RUNNING,
-            gdb.PARAM_BOOLEAN,
-            True)
         
 parameters = CythonParameters()
 
@@ -579,21 +578,26 @@ class CyCy(CythonCommand):
         cy next
         cy run
         cy cont
+        cy finish
         cy up
         cy down
+        cy select
         cy bt / cy backtrace
-        cy print
         cy list
+        cy print
         cy locals
         cy globals
+        cy exec
     """
     
     name = 'cy'
     command_class = gdb.COMMAND_NONE
     completer_class = gdb.COMPLETE_COMMAND
     
-    def __init__(self, *args):
-        super(CythonCommand, self).__init__(*args, prefix=True)
+    def __init__(self, name, command_class, completer_class):
+        # keep the signature 2.5 compatible (i.e. do not use f(*a, k=v)
+        super(CythonCommand, self).__init__(name, command_class, 
+                                            completer_class, prefix=True)
         
         commands = dict(
             import_ = CyImport.register(),
@@ -602,13 +606,17 @@ class CyCy(CythonCommand):
             next = CyNext.register(),
             run = CyRun.register(),
             cont = CyCont.register(),
+            finish = CyFinish.register(),
             up = CyUp.register(),
             down = CyDown.register(),
+            select = CySelect.register(),
             bt = CyBacktrace.register(),
             list = CyList.register(),
             print_ = CyPrint.register(),
             locals = CyLocals.register(),
             globals = CyGlobals.register(),
+            exec_ = libpython.FixGdbCommand('cy exec', '-cy-exec'),
+            _exec = CyExec.register(),
             cy_cname = CyCName('cy_cname'),
             cy_cvalue = CyCValue('cy_cvalue'),
             cy_lineno = CyLine('cy_lineno'),
@@ -827,10 +835,9 @@ class CyBreak(CythonCommand):
         return compl
 
 
-class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
+class CythonInfo(CythonBase, libpython.PythonInfo):
     """
-    Base class for CyStep and CyNext. It implements the interface dictated by
-    libpython.GenericCodeStepper.
+    Implementation of the interface dictated by libpython.LanguageInfo.
     """
     
     def lineno(self, frame):
@@ -840,20 +847,19 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
         # related code. The C level should be dispatched to the 'step' command.
         if self.is_cython_function(frame):
             return self.get_cython_lineno(frame)
-        else:
-            return libpython.py_step.lineno(frame)
+        return super(CythonInfo, self).lineno(frame)
     
     def get_source_line(self, frame):
         try:
-            line = super(CythonCodeStepper, self).get_source_line(frame)
+            line = super(CythonInfo, self).get_source_line(frame)
         except gdb.GdbError:
             return None
         else:
             return line.strip() or None
 
-    @classmethod
-    def register(cls):
-        return cls(cls.name, stepinto=getattr(cls, 'stepinto', False))
+    def exc_info(self, frame):
+        if self.is_python_function:
+            return super(CythonInfo, self).exc_info(frame)
 
     def runtime_break_functions(self):
         if self.is_cython_function():
@@ -864,8 +870,25 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
         result.extend(self.cy.functions_by_cname)
         return result
 
+
+class CythonExecutionControlCommand(CythonCommand, 
+                                    libpython.ExecutionControlCommandBase):
+    
+    @classmethod
+    def register(cls):
+        return cls(cls.name, cython_info)
+
+
+class CyStep(CythonExecutionControlCommand, libpython.PythonStepperMixin):
+    "Step through Cython, Python or C code."
+    
+    name = 'cy step'
+    stepinto = True
+    
     def invoke(self, args, from_tty):
-        if not self.is_cython_function() and not self.is_python_function():
+        if self.is_python_function():
+            self.python_step(args, self.stepinto)
+        elif not self.is_cython_function():
             if self.stepinto:
                 command = 'step'
             else:
@@ -873,44 +896,44 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
                 
             self.finish_executing(gdb.execute(command, to_string=True))
         else:
-            super(CythonCodeStepper, self).invoke(args, from_tty)
-
-
-class CyStep(CythonCodeStepper):
-    "Step through Cython, Python or C code."
-    
-    name = 'cy step'
-    stepinto = True
+            self.step(stepinto=self.stepinto)
 
 
-class CyNext(CythonCodeStepper):
+class CyNext(CyStep):
     "Step-over Python code."
 
     name = 'cy next'
     stepinto = False
 
 
-class CyRun(CythonCodeStepper):
+class CyRun(CythonExecutionControlCommand):
     """
     Run a Cython program. This is like the 'run' command, except that it 
     displays Cython or Python source lines as well
     """
     
     name = 'cy run'
-    _command = 'run'
     
-    def invoke(self, *args):
-        self.finish_executing(gdb.execute(self._command, to_string=True))
+    invoke = CythonExecutionControlCommand.run
 
 
-class CyCont(CyRun):
+class CyCont(CythonExecutionControlCommand):
     """
     Continue a Cython program. This is like the 'run' command, except that it 
     displays Cython or Python source lines as well.
     """
     
     name = 'cy cont'
-    _command = 'cont'
+    invoke = CythonExecutionControlCommand.cont
+
+
+class CyFinish(CythonExecutionControlCommand):
+    """
+    Execute until the function returns.
+    """
+    name = 'cy finish'
+
+    invoke = CythonExecutionControlCommand.finish
 
 
 class CyUp(CythonCommand):
@@ -946,6 +969,32 @@ class CyDown(CyUp):
     _command = 'down'
 
 
+class CySelect(CythonCommand):
+    """
+    Select a frame. Use frame numbers as listed in `cy backtrace`.
+    This command is useful because `cy backtrace` prints a reversed backtrace.
+    """
+    
+    name = 'cy select'
+    
+    def invoke(self, stackno, from_tty):
+        try:
+            stackno = int(stackno)
+        except ValueError:
+            raise gdb.GdbError("Not a valid number: %r" % (stackno,))
+        
+        frame = gdb.selected_frame()
+        while frame.newer():
+            frame = frame.newer()
+        
+        stackdepth = libpython.stackdepth(frame)
+        
+        try:
+            gdb.execute('select %d' % (stackdepth - stackno - 1,))
+        except RuntimeError, e:
+            raise gdb.GdbError(*e.args)
+
+
 class CyBacktrace(CythonCommand):
     'Print the Cython stack'
     
@@ -992,7 +1041,7 @@ class CyList(CythonCommand):
     command_class = gdb.COMMAND_FILES
     completer_class = gdb.COMPLETE_NONE
     
-    @dispatch_on_frame(c_command='list')
+    @dispatch_on_frame(c_command='list')
     def invoke(self, _, from_tty):
         sd, lineno = self.get_source_desc()
         source = sd.get_source(lineno - 5, lineno + 5, mark_line=lineno, 
@@ -1042,21 +1091,16 @@ class CyLocals(CythonCommand):
     command_class = gdb.COMMAND_STACK
     completer_class = gdb.COMPLETE_NONE
     
-    def _print_if_initialized(self, cyvar, max_name_length, prefix=''):
-        try:
-            value = gdb.parse_and_eval(cyvar.cname)
-        except RuntimeError:
-            # variable not initialized yet
-            pass
-        else:
-            self.print_gdb_value(cyvar.name, value, max_name_length, prefix)
-    
     @dispatch_on_frame(c_command='info locals', python_command='py-locals')
     def invoke(self, args, from_tty):
         local_cython_vars = self.get_cython_function().locals
         max_name_length = len(max(local_cython_vars, key=len))
         for name, cyvar in sorted(local_cython_vars.iteritems(), key=sortkey):
-            self._print_if_initialized(cyvar, max_name_length)
+            if self.is_initialized(self.get_cython_function(), cyvar.name):
+                value = gdb.parse_and_eval(cyvar.cname)
+                if not value.is_optimized_out:
+                    self.print_gdb_value(cyvar.name, value, 
+                                         max_name_length, '')
 
 
 class CyGlobals(CyLocals):
@@ -1092,8 +1136,93 @@ class CyGlobals(CyLocals):
         print 'C globals:'
         for name, cyvar in sorted(module_globals.iteritems(), key=sortkey):
             if name not in seen:
-                self._print_if_initialized(cyvar, max_name_length, 
-                                           prefix='    ')
+                try:
+                    value = gdb.parse_and_eval(cyvar.cname)
+                except RuntimeError:
+                    pass
+                else:
+                    if not value.is_optimized_out:
+                        self.print_gdb_value(cyvar.name, value,
+                                             max_name_length, '    ')
+
+
+class CyExec(CythonCommand, libpython.PyExec):
+    """
+    Execute Python code in the nearest Python or Cython frame.
+    """
+    
+    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()
+        current_lineno = self.get_cython_lineno()
+        
+        for name, cyvar in cython_func.locals.iteritems():
+            if (cyvar.type == PythonObject and 
+                self.is_initialized(cython_func, name)):
+                
+                try:
+                    val = gdb.parse_and_eval(cyvar.cname)
+                except RuntimeError:
+                    continue
+                else:
+                    if val.is_optimized_out:
+                        continue
+                
+                pystringp = executor.alloc_pystring(name)
+                code = '''
+                    (PyObject *) PyDict_SetItem(
+                        (PyObject *) %d,
+                        (PyObject *) %d,
+                        (PyObject *) %s)
+                ''' % (local_dict_pointer, pystringp, cyvar.cname)
+
+                try:
+                    if gdb.parse_and_eval(code) < 0:
+                        gdb.parse_and_eval('PyErr_Print()')
+                        raise gdb.GdbError("Unable to execute Python code.")
+                finally:
+                    # PyDict_SetItem doesn't steal our reference
+                    executor.decref(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)):
+                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()
+        
+        with libpython.FetchAndRestoreError():
+            # get the dict of Cython globals and construct a dict in the 
+            # inferior with Cython locals
+            global_dict = gdb.parse_and_eval(
+                '(PyObject *) PyModule_GetDict(__pyx_m)')
+            local_dict = gdb.parse_and_eval('(PyObject *) PyDict_New()')
+            
+            cython_function = self.get_cython_function()
+            
+            try:
+                self._fill_locals_dict(executor, 
+                                       libpython.pointervalue(local_dict))
+                executor.evalcode(expr, input_type, global_dict, local_dict)
+            finally:
+                executor.decref(libpython.pointervalue(local_dict))
 
 
 # Functions
@@ -1145,14 +1274,14 @@ class CyCValue(CyCName):
         try:
             cname = super(CyCValue, self).invoke(cyname, frame=frame)
             return gdb.parse_and_eval(cname)
-        except (gdb.GdbError, RuntimeError):
+        except (gdb.GdbError, RuntimeError), e:
             # variable exists but may not have been initialized yet, or may be
             # in the globals dict of the Cython module
             d = self.get_cython_globals_dict()
             if cyname in d:
                 return d[cyname]._gdbval
 
-            raise gdb.GdbError("Variable %s not initialized yet." % cyname)
+            raise gdb.GdbError(str(e))
 
 
 class CyLine(gdb.Function, CythonBase):
@@ -1164,5 +1293,6 @@ class CyLine(gdb.Function, CythonBase):
     def invoke(self):
         return self.get_cython_lineno()
 
-
-cy = CyCy.register()
\ No newline at end of file
+cython_info = CythonInfo()
+cy = CyCy.register()
+cython_info.cy = cy
\ No newline at end of file