From f8abe97ae74c307dc0178baf2ea246fe99be6c9a Mon Sep 17 00:00:00 2001 From: Keshava P Date: Mon, 5 Oct 2026 17:10:24 +0530 Subject: [PATCH] parse richInspectVariables result with ast.literal_eval --- ipykernel/debugger.py | 8 +++++- tests/test_debugger.py | 63 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index ebb9c7777..33a6bab60 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,5 +1,6 @@ """Debugger implementation for the IPython kernel.""" +import ast import os import re import sys @@ -665,7 +666,12 @@ async def richInspectVariables(self, message): } ) if reply["success"]: - repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) + # The result is the repr of a (data, metadata) tuple, read it + # as a literal so nothing in it gets evaluated here. + try: + repr_data, repr_metadata = ast.literal_eval(reply["body"]["result"]) + except (ValueError, TypeError, SyntaxError): + self.log.debug("Could not parse rich representation of %s", var_name) body = { "data": repr_data, diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 5e40d7165..35656d369 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -361,6 +361,69 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): assert reply["body"]["data"] == {"text/plain": locals_[0]["value"]} +def test_rich_inspect_at_breakpoint_non_literal_repr(kernel_with_debug): + # The rich representation comes back from debugpy as a string. A value in + # it whose repr is an expression must be read as data, not evaluated. + code = """class Expr: + def __repr__(self): + return "len('abc')" + +class Rich: + def _repr_mimebundle_(self, include=None, exclude=None): + return {"text/plain": Expr()} + +def f(a): + b = a + return b + +f(Rich())""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 10}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "debugInfo") + + wait_for_debug_request(kernel_with_debug, "configurationDone") + + kernel_with_debug.execute(code) + + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + + # Wait for stop on breakpoint + msg: dict = {"msg_type": "", "content": {}} + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][ + "stackFrames" + ] + + reply = wait_for_debug_request( + kernel_with_debug, + "richInspectVariables", + {"variableName": "a", "frameId": stacks[0]["id"]}, + ) + + assert reply["success"] + assert reply["body"] == {"data": {}, "metadata": {}} + + def test_convert_to_long_pathname(): if sys.platform == "win32": from ipykernel.compiler import _convert_to_long_pathname