From 2e438cc2554495b28480a3ffe5cdf41b6ab823a0 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 15 Oct 2018 23:19:57 +0200 Subject: [PATCH] bpo-34989: python-gdb.py: fix current_line_num() (GH-9889) python-gdb.py now handles errors on computing the line number of a Python frame. Changes: * PyFrameObjectPtr.current_line_num() now catchs any Exception on calling addr2line(), instead of failing with a surprising " 'FakeRepr' object is not subscriptable" error. * All callers of current_line_num() now handle current_line_num() returning None. * PyFrameObjectPtr.current_line() now also catchs IndexError on getting a line from the Python source file. --- .../2018-10-15-13-22-28.bpo-34989.hU4fra.rst | 2 + Tools/gdb/libpython.py | 46 +++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2018-10-15-13-22-28.bpo-34989.hU4fra.rst diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-10-15-13-22-28.bpo-34989.hU4fra.rst b/Misc/NEWS.d/next/Tools-Demos/2018-10-15-13-22-28.bpo-34989.hU4fra.rst new file mode 100644 index 00000000000..53bb425ea7b --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2018-10-15-13-22-28.bpo-34989.hU4fra.rst @@ -0,0 +1,2 @@ +python-gdb.py now handles errors on computing the line number of a Python +frame. diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 27eabcb0d3d..bfaa9403b78 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -934,35 +934,50 @@ class PyFrameObjectPtr(PyObjectPtr): if long(f_trace) != 0: # we have a non-NULL f_trace: return self.f_lineno - else: - #try: + + try: return self.co.addr2line(self.f_lasti) - #except ValueError: - # return self.f_lineno + except Exception: + # bpo-34989: addr2line() is a complex function, it can fail in many + # ways. For example, it fails with a TypeError on "FakeRepr" if + # gdb fails to load debug symbols. Use a catch-all "except + # Exception" to make the whole function safe. The caller has to + # handle None anyway for optimized Python. + return None def current_line(self): '''Get the text of the current source line as a string, with a trailing newline character''' if self.is_optimized_out(): return '(frame information optimized out)' + + lineno = self.current_line_num() + if lineno is None: + return '(failed to get frame line number)' + filename = self.filename() try: - f = open(os_fsencode(filename), 'r') + with open(os_fsencode(filename), 'r') as fp: + lines = fp.readlines() except IOError: return None - with f: - all_lines = f.readlines() - # Convert from 1-based current_line_num to 0-based list offset: - return all_lines[self.current_line_num()-1] + + try: + # Convert from 1-based current_line_num to 0-based list offset + return lines[lineno - 1] + except IndexError: + return None def write_repr(self, out, visited): if self.is_optimized_out(): out.write('(frame information optimized out)') return - out.write('Frame 0x%x, for file %s, line %i, in %s (' + lineno = self.current_line_num() + lineno = str(lineno) if lineno is not None else "?" + out.write('Frame 0x%x, for file %s, line %s, in %s (' % (self.as_address(), self.co_filename.proxyval(visited), - self.current_line_num(), + lineno, self.co_name.proxyval(visited))) first = True for pyop_name, pyop_value in self.iter_locals(): @@ -981,9 +996,11 @@ class PyFrameObjectPtr(PyObjectPtr): sys.stdout.write(' (frame information optimized out)\n') return visited = set() - sys.stdout.write(' File "%s", line %i, in %s\n' + lineno = self.current_line_num() + lineno = str(lineno) if lineno is not None else "?" + sys.stdout.write(' File "%s", line %s, in %s\n' % (self.co_filename.proxyval(visited), - self.current_line_num(), + lineno, self.co_name.proxyval(visited))) class PySetObjectPtr(PyObjectPtr): @@ -1732,6 +1749,9 @@ class PyList(gdb.Command): filename = pyop.filename() lineno = pyop.current_line_num() + if lineno is None: + print('Unable to read python frame line number') + return if start is None: start = lineno - 5