Skip to content

Commit d05f7fd

Browse files
authored
[3.6] bpo-30345: Update test_gdb.py and python-gdb.py from master (#1549)
* python-gdb.py supports method-wrapper bpo-29367: python-gdb.py now supports also method-wrapper (wrapperobject) objects. (cherry picked from commit 6110833) * Update and enhance python-gdb.py bpo-29259: Detect PyCFunction is the current frame, not only in the older frame.
1 parent 3dc7c52 commit d05f7fd

File tree

3 files changed

+78
-14
lines changed

3 files changed

+78
-14
lines changed

Lib/test/test_gdb.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
# The code for testing gdb was adapted from similar work in Unladen Swallow's
44
# Lib/test/test_jit_gdb.py
55

6+
import locale
67
import os
78
import re
89
import subprocess
910
import sys
1011
import sysconfig
12+
import textwrap
1113
import unittest
12-
import locale
1314

1415
# Is this Python configured to support threads?
1516
try:
@@ -845,7 +846,24 @@ def test_pycfunction(self):
845846
breakpoint='time_gmtime',
846847
cmds_after_breakpoint=['py-bt-full'],
847848
)
848-
self.assertIn('#0 <built-in method gmtime', gdb_output)
849+
self.assertIn('#1 <built-in method gmtime', gdb_output)
850+
851+
@unittest.skipIf(python_is_optimized(),
852+
"Python was compiled with optimizations")
853+
def test_wrapper_call(self):
854+
cmd = textwrap.dedent('''
855+
class MyList(list):
856+
def __init__(self):
857+
super().__init__() # wrapper_call()
858+
859+
id("first break point")
860+
l = MyList()
861+
''')
862+
# Verify with "py-bt":
863+
gdb_output = self.get_stack_trace(cmd,
864+
cmds_after_breakpoint=['break wrapper_call', 'continue', 'py-bt'])
865+
self.assertRegex(gdb_output,
866+
r"<method-wrapper u?'__init__' of MyList object at ")
849867

850868

851869
class PyPrintTests(DebuggerTests):

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,12 @@ Documentation
151151

152152
- bpo-26985: Add missing info of code object in inspect documentation.
153153

154+
Tools/Demos
155+
-----------
156+
157+
- Issue #29367: python-gdb.py now supports also ``method-wrapper``
158+
(``wrapperobject``) objects.
159+
154160
Tests
155161
-----
156162

Tools/gdb/libpython.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ def subclass_from_type(cls, t):
362362
'set' : PySetObjectPtr,
363363
'frozenset' : PySetObjectPtr,
364364
'builtin_function_or_method' : PyCFunctionObjectPtr,
365+
'method-wrapper': wrapperobject,
365366
}
366367
if tp_name in name_map:
367368
return name_map[tp_name]
@@ -1330,6 +1331,39 @@ def write_repr(self, out, visited):
13301331
out.write(quote)
13311332

13321333

1334+
class wrapperobject(PyObjectPtr):
1335+
_typename = 'wrapperobject'
1336+
1337+
def safe_name(self):
1338+
try:
1339+
name = self.field('descr')['d_base']['name'].string()
1340+
return repr(name)
1341+
except (NullPyObjectPtr, RuntimeError):
1342+
return '<unknown name>'
1343+
1344+
def safe_tp_name(self):
1345+
try:
1346+
return self.field('self')['ob_type']['tp_name'].string()
1347+
except (NullPyObjectPtr, RuntimeError):
1348+
return '<unknown tp_name>'
1349+
1350+
def safe_self_addresss(self):
1351+
try:
1352+
address = long(self.field('self'))
1353+
return '%#x' % address
1354+
except (NullPyObjectPtr, RuntimeError):
1355+
return '<failed to get self address>'
1356+
1357+
def proxyval(self, visited):
1358+
name = self.safe_name()
1359+
tp_name = self.safe_tp_name()
1360+
self_address = self.safe_self_addresss()
1361+
return ("<method-wrapper %s of %s object at %s>"
1362+
% (name, tp_name, self_address))
1363+
1364+
def write_repr(self, out, visited):
1365+
proxy = self.proxyval(visited)
1366+
out.write(proxy)
13331367

13341368

13351369
def int_from_int(gdbval):
@@ -1364,11 +1398,13 @@ def to_string (self):
13641398

13651399
def pretty_printer_lookup(gdbval):
13661400
type = gdbval.type.unqualified()
1367-
if type.code == gdb.TYPE_CODE_PTR:
1368-
type = type.target().unqualified()
1369-
t = str(type)
1370-
if t in ("PyObject", "PyFrameObject", "PyUnicodeObject"):
1371-
return PyObjectPtrPrinter(gdbval)
1401+
if type.code != gdb.TYPE_CODE_PTR:
1402+
return None
1403+
1404+
type = type.target().unqualified()
1405+
t = str(type)
1406+
if t in ("PyObject", "PyFrameObject", "PyUnicodeObject", "wrapperobject"):
1407+
return PyObjectPtrPrinter(gdbval)
13721408

13731409
"""
13741410
During development, I've been manually invoking the code in this way:
@@ -1497,11 +1533,8 @@ def is_other_python_frame(self):
14971533
return 'Garbage-collecting'
14981534

14991535
# Detect invocations of PyCFunction instances:
1500-
older = self.older()
1501-
if not older:
1502-
return False
1503-
1504-
caller = older._gdbframe.name()
1536+
frame = self._gdbframe
1537+
caller = frame.name()
15051538
if not caller:
15061539
return False
15071540

@@ -1513,18 +1546,25 @@ def is_other_python_frame(self):
15131546
# "self" is the (PyObject*) of the 'self'
15141547
try:
15151548
# Use the prettyprinter for the func:
1516-
func = older._gdbframe.read_var('func')
1549+
func = frame.read_var('func')
15171550
return str(func)
15181551
except RuntimeError:
15191552
return 'PyCFunction invocation (unable to read "func")'
15201553

15211554
elif caller == '_PyCFunction_FastCallDict':
15221555
try:
1523-
func = older._gdbframe.read_var('func_obj')
1556+
func = frame.read_var('func_obj')
15241557
return str(func)
15251558
except RuntimeError:
15261559
return 'PyCFunction invocation (unable to read "func_obj")'
15271560

1561+
if caller == 'wrapper_call':
1562+
try:
1563+
func = frame.read_var('wp')
1564+
return str(func)
1565+
except RuntimeError:
1566+
return '<wrapper_call invocation>'
1567+
15281568
# This frame isn't worth reporting:
15291569
return False
15301570

0 commit comments

Comments
 (0)