diff --git a/Lib/test/test_gdb.py b/Lib/test/test_gdb.py index 9e0eaea8c8f692..7a6784d7cb8261 100644 --- a/Lib/test/test_gdb.py +++ b/Lib/test/test_gdb.py @@ -889,6 +889,62 @@ def test_printing_builtin(self): self.assertMultilineMatches(bt, r".*\nbuiltin 'len' = \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") diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-04-17-09-16-40.bpo-33294.J7sTti.rst b/Misc/NEWS.d/next/Tools-Demos/2018-04-17-09-16-40.bpo-33294.J7sTti.rst new file mode 100644 index 00000000000000..438058822c8d4c --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2018-04-17-09-16-40.bpo-33294.J7sTti.rst @@ -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' = +(gdb) py-print t.t[4]["a"] +local 't.t[4]["a"]' = 'b' diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index 27eabcb0d3d091..91f0d1d9c608df 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -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) @@ -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): + # field must be an integer index + index = int(field) + return PyObjectPtr.from_pyobject_ptr(obj[index]) + + dictionary = None + if isinstance(obj, PyDictObjectPtr): + dictionary = obj + # 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()