Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions Lib/test/test_gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,62 @@ def test_printing_builtin(self):
self.assertMultilineMatches(bt,
r".*\nbuiltin 'len' = <built-in method len of module object at remote 0x-?[0-9a-f]+>\n.*")

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_complex_expr_print(self):
tested_code = '''
class Player:
def __init__(self):
self.name = 'Martin'
self.team = 'HC Sparta Praha'
self.skills = {'defence': 2}
self.numbers = (11, 22, 33)

a = [0, 1, 2, Player()]
b = 0
id(0)'''

gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print a[0]'])
self.assertRegex(gdb_output, r".*\nglobal 'a\[0\]' = 0\n.*")
gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print a[3].name'])
self.assertRegex(gdb_output, r".*\nglobal 'a\[3\]\.name' = 'Martin'\n.*")
gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print a[3].skills["defence"]'])
self.assertRegex(gdb_output, r".*\nglobal 'a\[3\].skills\[\"defence\"\]' = 2\n.*")
gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print a[3].numbers[1]'])
self.assertRegex(gdb_output, r".*\nglobal 'a\[3\].numbers\[1\]' = 22\n.*")

@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
def test_complex_expr_error(self):
tested_code = '''
class Player:
def __init__(self):
self.name = 'Martin'
self.team = 'HC Sparta Praha'
self.skills = {'defence': 2}
self.numbers = (11, 22, 33)

variables = [1, 2, 3]
player = Player()
id(0)'''

gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print variables[0'])
self.assertRegex(gdb_output, r".*\nCan't find closing bracket in variables\[0\n.*")
gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print player.color'])
self.assertRegex(gdb_output, r".*\nCan't find color of <__main__.HeapTypeObjectPtr object at 0x-?[0-9a-f]+>\n.*")
gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print player.skills[mykey]'])
self.assertRegex(gdb_output, r".*\nKey name mykey must be a string value\n.*")
gdb_output = self.get_stack_trace(tested_code,
cmds_after_breakpoint=['py-up', 'py-print player.skills["mykey"]'])
self.assertRegex(gdb_output, r".*\nCan't find mykey of <__main__.PyDictObjectPtr object at 0x-?[0-9a-f]+>\n.*")

class PyLocalsTests(DebuggerTests):
@unittest.skipIf(python_is_optimized(),
"Python was compiled with optimizations")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Purpose of the patch is to support more complex expressions for py-print
command. Small example:

(gdb) py-print t
local 't' = <Test(t=[1, 2, 3, 4, {'a': 'b', 'c': 'd'}], x=123) at remote 0x7ffff6f15780>
(gdb) py-print t.t[4]["a"]
local 't.t[4]["a"]' = 'b'
86 changes: 79 additions & 7 deletions Tools/gdb/libpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -1873,6 +1873,38 @@ def __init__(self):
gdb.COMMAND_DATA,
gdb.COMPLETE_NONE)

@classmethod
def tokenize_square_brackets(self, expr):
'''
Tokenize single expression of an array access or a dictionary access
into parts. Part is either a variable name, an index value of an array
access, or a key name for dictionary access
'''
while '[' in expr:
s = expr.find('[')
e = expr.find(']')
if e == -1:
raise LookupError("Can't find closing bracket in %s" % expr)

name = expr[:s]
if name != '':
yield name

yield expr[s + 1:e]
expr = expr[e + 1:]

if expr != '':
yield expr

@classmethod
def tokenize_expression(self, expr):
'''
Tokenize expression into parts where part can be either subexpression
using dot notation. Or index into an array or key of a dictionary.
'''
for part in expr.split('.'):
for token in self.tokenize_square_brackets(part):
yield token

def invoke(self, args, from_tty):
name = str(args)
Expand All @@ -1887,15 +1919,55 @@ def invoke(self, args, from_tty):
print('Unable to read information on python frame')
return

pyop_var, scope = pyop_frame.get_var_by_name(name)
obj = None
try:
for field in self.tokenize_expression(name):
if obj is None:
obj, scope = pyop_frame.get_var_by_name(field)
else:
obj = self.get_object_field(obj, field)
if obj:
print('%s %r = %s'
% (scope,
name,
obj.get_truncated_repr(MAX_OUTPUT_LEN)))
else:
print('%r not found' % name)

if pyop_var:
print('%s %r = %s'
% (scope,
name,
pyop_var.get_truncated_repr(MAX_OUTPUT_LEN)))
except LookupError as err:
print(err)

def get_object_field(self, obj, field):
'''
Based on type of object, return field of the object
'''
if isinstance(obj, PyListObjectPtr) or isinstance(obj, PyTupleObjectPtr):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add test coverage of a tuple

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

# field must be an integer index
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems a bit odd to be using dotted notation to access by index. Is it possible to support square-bracket syntax here, or does it conflict with gdb's usage of square-bracket syntax for C level lookup?

index = int(field)
return PyObjectPtr.from_pyobject_ptr(obj[index])

dictionary = None
if isinstance(obj, PyDictObjectPtr):
dictionary = obj
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly for dict objects; wouldn't square bracket notation be more Pythonic?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, fixed in newer version.

# key name must be wrapped in apostrophes or quotes
if field[0] == "'" and field[-1] == "'":
pass
elif field[0] == '"' and field[-1] == '"':
pass
else:
raise LookupError("Key name %s must be a string value" % field)
field = field[1:-1]
elif isinstance(obj, HeapTypeObjectPtr):
dictionary = obj.get_attr_dict()
else:
print('%r not found' % name)
raise LookupError("Can't find field %s of %s" % (field, obj))

for k, pyop_var in dictionary.iteritems():
key_name = k.proxyval(set())
if key_name == field:
return pyop_var

raise LookupError("Can't find %s of %s" % (field, obj))

PyPrint()

Expand Down