Disable watchpoint stepping by default (use cy step -w or cy step --watchpoint to...
[cython.git] / Cython / Debugger / libcython.py
index dc4ccb991ce5c740f7b69592d58ca0651c53d225..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)
@@ -387,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):
@@ -572,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(),
@@ -598,6 +609,7 @@ class CyCy(CythonCommand):
             finish = CyFinish.register(),
             up = CyUp.register(),
             down = CyDown.register(),
+            select = CySelect.register(),
             bt = CyBacktrace.register(),
             list = CyList.register(),
             print_ = CyPrint.register(),
@@ -823,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):
@@ -836,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():
@@ -860,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:
@@ -869,24 +896,17 @@ class CythonCodeStepper(CythonCommand, libpython.GenericCodeStepper):
                 
             self.finish_executing(gdb.execute(command, to_string=True))
         else:
-            self.step()
-
-
-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
@@ -894,26 +914,26 @@ class CyRun(CythonCodeStepper):
     
     name = 'cy run'
     
-    invoke = CythonCodeStepper.run
+    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'
-    invoke = CythonCodeStepper.cont
+    invoke = CythonExecutionControlCommand.cont
 
 
-class CyFinish(CyRun):
+class CyFinish(CythonExecutionControlCommand):
     """
     Execute until the function returns.
     """
     name = 'cy finish'
 
-    invoke = CythonCodeStepper.finish
+    invoke = CythonExecutionControlCommand.finish
 
 
 class CyUp(CythonCommand):
@@ -949,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'
     
@@ -995,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, 
@@ -1045,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):
@@ -1095,11 +1136,21 @@ 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):
+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
@@ -1107,34 +1158,35 @@ class CyExec(CythonCommand):
     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:
-                # skip unitialized Cython variables 
+            if (cyvar.type == PythonObject and 
+                self.is_initialized(cython_func, name)):
+                
                 try:
                     val = gdb.parse_and_eval(cyvar.cname)
                 except RuntimeError:
                     continue
                 else:
-                    # Fortunately, Cython initializes all local (automatic)
-                    # variables to NULL
-                    if libpython.pointervalue(val) == 0:
+                    if val.is_optimized_out:
                         continue
-
+                
                 pystringp = executor.alloc_pystring(name)
                 code = '''
-                    PyDict_SetItem(
-                        (PyObject *) %d, 
-                        (PyObject *) %d, 
+                    (PyObject *) PyDict_SetItem(
+                        (PyObject *) %d,
+                        (PyObject *) %d,
                         (PyObject *) %s)
                 ''' % (local_dict_pointer, pystringp, cyvar.cname)
-                
-                # PyDict_SetItem doesn't steal our reference
-                executor.decref(pystringp)
-                
-                if gdb.parse_and_eval(code) < 0:
-                    gdb.parse_and_eval('PyErr_Print()')
-                    raise gdb.GdbError("Unable to execute Python code.")
+
+                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()
@@ -1153,19 +1205,24 @@ class CyExec(CythonCommand):
             libpython.py_exec.invoke(expr, from_tty)
             return
         
+        expr, input_type = self.readcode(expr)
         executor = libpython.PythonCodeExecutor()
         
-        # 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()')
-        
-        try:
-            self._fill_locals_dict(executor, libpython.pointervalue(local_dict))
-            executor.evalcode(expr, global_dict, local_dict)
-        finally:
-            executor.decref(libpython.pointervalue(local_dict))
+        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
@@ -1217,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):
@@ -1236,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