annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/debugger_r.py @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
jpayne@69 1 """Support for remote Python debugging.
jpayne@69 2
jpayne@69 3 Some ASCII art to describe the structure:
jpayne@69 4
jpayne@69 5 IN PYTHON SUBPROCESS # IN IDLE PROCESS
jpayne@69 6 #
jpayne@69 7 # oid='gui_adapter'
jpayne@69 8 +----------+ # +------------+ +-----+
jpayne@69 9 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
jpayne@69 10 +-----+--calls-->+----------+ # +------------+ +-----+
jpayne@69 11 | Idb | # /
jpayne@69 12 +-----+<-calls--+------------+ # +----------+<--calls-/
jpayne@69 13 | IdbAdapter |<--remote#call--| IdbProxy |
jpayne@69 14 +------------+ # +----------+
jpayne@69 15 oid='idb_adapter' #
jpayne@69 16
jpayne@69 17 The purpose of the Proxy and Adapter classes is to translate certain
jpayne@69 18 arguments and return values that cannot be transported through the RPC
jpayne@69 19 barrier, in particular frame and traceback objects.
jpayne@69 20
jpayne@69 21 """
jpayne@69 22
jpayne@69 23 import types
jpayne@69 24 from idlelib import debugger
jpayne@69 25
jpayne@69 26 debugging = 0
jpayne@69 27
jpayne@69 28 idb_adap_oid = "idb_adapter"
jpayne@69 29 gui_adap_oid = "gui_adapter"
jpayne@69 30
jpayne@69 31 #=======================================
jpayne@69 32 #
jpayne@69 33 # In the PYTHON subprocess:
jpayne@69 34
jpayne@69 35 frametable = {}
jpayne@69 36 dicttable = {}
jpayne@69 37 codetable = {}
jpayne@69 38 tracebacktable = {}
jpayne@69 39
jpayne@69 40 def wrap_frame(frame):
jpayne@69 41 fid = id(frame)
jpayne@69 42 frametable[fid] = frame
jpayne@69 43 return fid
jpayne@69 44
jpayne@69 45 def wrap_info(info):
jpayne@69 46 "replace info[2], a traceback instance, by its ID"
jpayne@69 47 if info is None:
jpayne@69 48 return None
jpayne@69 49 else:
jpayne@69 50 traceback = info[2]
jpayne@69 51 assert isinstance(traceback, types.TracebackType)
jpayne@69 52 traceback_id = id(traceback)
jpayne@69 53 tracebacktable[traceback_id] = traceback
jpayne@69 54 modified_info = (info[0], info[1], traceback_id)
jpayne@69 55 return modified_info
jpayne@69 56
jpayne@69 57 class GUIProxy:
jpayne@69 58
jpayne@69 59 def __init__(self, conn, gui_adap_oid):
jpayne@69 60 self.conn = conn
jpayne@69 61 self.oid = gui_adap_oid
jpayne@69 62
jpayne@69 63 def interaction(self, message, frame, info=None):
jpayne@69 64 # calls rpc.SocketIO.remotecall() via run.MyHandler instance
jpayne@69 65 # pass frame and traceback object IDs instead of the objects themselves
jpayne@69 66 self.conn.remotecall(self.oid, "interaction",
jpayne@69 67 (message, wrap_frame(frame), wrap_info(info)),
jpayne@69 68 {})
jpayne@69 69
jpayne@69 70 class IdbAdapter:
jpayne@69 71
jpayne@69 72 def __init__(self, idb):
jpayne@69 73 self.idb = idb
jpayne@69 74
jpayne@69 75 #----------called by an IdbProxy----------
jpayne@69 76
jpayne@69 77 def set_step(self):
jpayne@69 78 self.idb.set_step()
jpayne@69 79
jpayne@69 80 def set_quit(self):
jpayne@69 81 self.idb.set_quit()
jpayne@69 82
jpayne@69 83 def set_continue(self):
jpayne@69 84 self.idb.set_continue()
jpayne@69 85
jpayne@69 86 def set_next(self, fid):
jpayne@69 87 frame = frametable[fid]
jpayne@69 88 self.idb.set_next(frame)
jpayne@69 89
jpayne@69 90 def set_return(self, fid):
jpayne@69 91 frame = frametable[fid]
jpayne@69 92 self.idb.set_return(frame)
jpayne@69 93
jpayne@69 94 def get_stack(self, fid, tbid):
jpayne@69 95 frame = frametable[fid]
jpayne@69 96 if tbid is None:
jpayne@69 97 tb = None
jpayne@69 98 else:
jpayne@69 99 tb = tracebacktable[tbid]
jpayne@69 100 stack, i = self.idb.get_stack(frame, tb)
jpayne@69 101 stack = [(wrap_frame(frame2), k) for frame2, k in stack]
jpayne@69 102 return stack, i
jpayne@69 103
jpayne@69 104 def run(self, cmd):
jpayne@69 105 import __main__
jpayne@69 106 self.idb.run(cmd, __main__.__dict__)
jpayne@69 107
jpayne@69 108 def set_break(self, filename, lineno):
jpayne@69 109 msg = self.idb.set_break(filename, lineno)
jpayne@69 110 return msg
jpayne@69 111
jpayne@69 112 def clear_break(self, filename, lineno):
jpayne@69 113 msg = self.idb.clear_break(filename, lineno)
jpayne@69 114 return msg
jpayne@69 115
jpayne@69 116 def clear_all_file_breaks(self, filename):
jpayne@69 117 msg = self.idb.clear_all_file_breaks(filename)
jpayne@69 118 return msg
jpayne@69 119
jpayne@69 120 #----------called by a FrameProxy----------
jpayne@69 121
jpayne@69 122 def frame_attr(self, fid, name):
jpayne@69 123 frame = frametable[fid]
jpayne@69 124 return getattr(frame, name)
jpayne@69 125
jpayne@69 126 def frame_globals(self, fid):
jpayne@69 127 frame = frametable[fid]
jpayne@69 128 dict = frame.f_globals
jpayne@69 129 did = id(dict)
jpayne@69 130 dicttable[did] = dict
jpayne@69 131 return did
jpayne@69 132
jpayne@69 133 def frame_locals(self, fid):
jpayne@69 134 frame = frametable[fid]
jpayne@69 135 dict = frame.f_locals
jpayne@69 136 did = id(dict)
jpayne@69 137 dicttable[did] = dict
jpayne@69 138 return did
jpayne@69 139
jpayne@69 140 def frame_code(self, fid):
jpayne@69 141 frame = frametable[fid]
jpayne@69 142 code = frame.f_code
jpayne@69 143 cid = id(code)
jpayne@69 144 codetable[cid] = code
jpayne@69 145 return cid
jpayne@69 146
jpayne@69 147 #----------called by a CodeProxy----------
jpayne@69 148
jpayne@69 149 def code_name(self, cid):
jpayne@69 150 code = codetable[cid]
jpayne@69 151 return code.co_name
jpayne@69 152
jpayne@69 153 def code_filename(self, cid):
jpayne@69 154 code = codetable[cid]
jpayne@69 155 return code.co_filename
jpayne@69 156
jpayne@69 157 #----------called by a DictProxy----------
jpayne@69 158
jpayne@69 159 def dict_keys(self, did):
jpayne@69 160 raise NotImplementedError("dict_keys not public or pickleable")
jpayne@69 161 ## dict = dicttable[did]
jpayne@69 162 ## return dict.keys()
jpayne@69 163
jpayne@69 164 ### Needed until dict_keys is type is finished and pickealable.
jpayne@69 165 ### Will probably need to extend rpc.py:SocketIO._proxify at that time.
jpayne@69 166 def dict_keys_list(self, did):
jpayne@69 167 dict = dicttable[did]
jpayne@69 168 return list(dict.keys())
jpayne@69 169
jpayne@69 170 def dict_item(self, did, key):
jpayne@69 171 dict = dicttable[did]
jpayne@69 172 value = dict[key]
jpayne@69 173 value = repr(value) ### can't pickle module 'builtins'
jpayne@69 174 return value
jpayne@69 175
jpayne@69 176 #----------end class IdbAdapter----------
jpayne@69 177
jpayne@69 178
jpayne@69 179 def start_debugger(rpchandler, gui_adap_oid):
jpayne@69 180 """Start the debugger and its RPC link in the Python subprocess
jpayne@69 181
jpayne@69 182 Start the subprocess side of the split debugger and set up that side of the
jpayne@69 183 RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
jpayne@69 184 objects and linking them together. Register the IdbAdapter with the
jpayne@69 185 RPCServer to handle RPC requests from the split debugger GUI via the
jpayne@69 186 IdbProxy.
jpayne@69 187
jpayne@69 188 """
jpayne@69 189 gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
jpayne@69 190 idb = debugger.Idb(gui_proxy)
jpayne@69 191 idb_adap = IdbAdapter(idb)
jpayne@69 192 rpchandler.register(idb_adap_oid, idb_adap)
jpayne@69 193 return idb_adap_oid
jpayne@69 194
jpayne@69 195
jpayne@69 196 #=======================================
jpayne@69 197 #
jpayne@69 198 # In the IDLE process:
jpayne@69 199
jpayne@69 200
jpayne@69 201 class FrameProxy:
jpayne@69 202
jpayne@69 203 def __init__(self, conn, fid):
jpayne@69 204 self._conn = conn
jpayne@69 205 self._fid = fid
jpayne@69 206 self._oid = "idb_adapter"
jpayne@69 207 self._dictcache = {}
jpayne@69 208
jpayne@69 209 def __getattr__(self, name):
jpayne@69 210 if name[:1] == "_":
jpayne@69 211 raise AttributeError(name)
jpayne@69 212 if name == "f_code":
jpayne@69 213 return self._get_f_code()
jpayne@69 214 if name == "f_globals":
jpayne@69 215 return self._get_f_globals()
jpayne@69 216 if name == "f_locals":
jpayne@69 217 return self._get_f_locals()
jpayne@69 218 return self._conn.remotecall(self._oid, "frame_attr",
jpayne@69 219 (self._fid, name), {})
jpayne@69 220
jpayne@69 221 def _get_f_code(self):
jpayne@69 222 cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
jpayne@69 223 return CodeProxy(self._conn, self._oid, cid)
jpayne@69 224
jpayne@69 225 def _get_f_globals(self):
jpayne@69 226 did = self._conn.remotecall(self._oid, "frame_globals",
jpayne@69 227 (self._fid,), {})
jpayne@69 228 return self._get_dict_proxy(did)
jpayne@69 229
jpayne@69 230 def _get_f_locals(self):
jpayne@69 231 did = self._conn.remotecall(self._oid, "frame_locals",
jpayne@69 232 (self._fid,), {})
jpayne@69 233 return self._get_dict_proxy(did)
jpayne@69 234
jpayne@69 235 def _get_dict_proxy(self, did):
jpayne@69 236 if did in self._dictcache:
jpayne@69 237 return self._dictcache[did]
jpayne@69 238 dp = DictProxy(self._conn, self._oid, did)
jpayne@69 239 self._dictcache[did] = dp
jpayne@69 240 return dp
jpayne@69 241
jpayne@69 242
jpayne@69 243 class CodeProxy:
jpayne@69 244
jpayne@69 245 def __init__(self, conn, oid, cid):
jpayne@69 246 self._conn = conn
jpayne@69 247 self._oid = oid
jpayne@69 248 self._cid = cid
jpayne@69 249
jpayne@69 250 def __getattr__(self, name):
jpayne@69 251 if name == "co_name":
jpayne@69 252 return self._conn.remotecall(self._oid, "code_name",
jpayne@69 253 (self._cid,), {})
jpayne@69 254 if name == "co_filename":
jpayne@69 255 return self._conn.remotecall(self._oid, "code_filename",
jpayne@69 256 (self._cid,), {})
jpayne@69 257
jpayne@69 258
jpayne@69 259 class DictProxy:
jpayne@69 260
jpayne@69 261 def __init__(self, conn, oid, did):
jpayne@69 262 self._conn = conn
jpayne@69 263 self._oid = oid
jpayne@69 264 self._did = did
jpayne@69 265
jpayne@69 266 ## def keys(self):
jpayne@69 267 ## return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
jpayne@69 268
jpayne@69 269 # 'temporary' until dict_keys is a pickleable built-in type
jpayne@69 270 def keys(self):
jpayne@69 271 return self._conn.remotecall(self._oid,
jpayne@69 272 "dict_keys_list", (self._did,), {})
jpayne@69 273
jpayne@69 274 def __getitem__(self, key):
jpayne@69 275 return self._conn.remotecall(self._oid, "dict_item",
jpayne@69 276 (self._did, key), {})
jpayne@69 277
jpayne@69 278 def __getattr__(self, name):
jpayne@69 279 ##print("*** Failed DictProxy.__getattr__:", name)
jpayne@69 280 raise AttributeError(name)
jpayne@69 281
jpayne@69 282
jpayne@69 283 class GUIAdapter:
jpayne@69 284
jpayne@69 285 def __init__(self, conn, gui):
jpayne@69 286 self.conn = conn
jpayne@69 287 self.gui = gui
jpayne@69 288
jpayne@69 289 def interaction(self, message, fid, modified_info):
jpayne@69 290 ##print("*** Interaction: (%s, %s, %s)" % (message, fid, modified_info))
jpayne@69 291 frame = FrameProxy(self.conn, fid)
jpayne@69 292 self.gui.interaction(message, frame, modified_info)
jpayne@69 293
jpayne@69 294
jpayne@69 295 class IdbProxy:
jpayne@69 296
jpayne@69 297 def __init__(self, conn, shell, oid):
jpayne@69 298 self.oid = oid
jpayne@69 299 self.conn = conn
jpayne@69 300 self.shell = shell
jpayne@69 301
jpayne@69 302 def call(self, methodname, /, *args, **kwargs):
jpayne@69 303 ##print("*** IdbProxy.call %s %s %s" % (methodname, args, kwargs))
jpayne@69 304 value = self.conn.remotecall(self.oid, methodname, args, kwargs)
jpayne@69 305 ##print("*** IdbProxy.call %s returns %r" % (methodname, value))
jpayne@69 306 return value
jpayne@69 307
jpayne@69 308 def run(self, cmd, locals):
jpayne@69 309 # Ignores locals on purpose!
jpayne@69 310 seq = self.conn.asyncqueue(self.oid, "run", (cmd,), {})
jpayne@69 311 self.shell.interp.active_seq = seq
jpayne@69 312
jpayne@69 313 def get_stack(self, frame, tbid):
jpayne@69 314 # passing frame and traceback IDs, not the objects themselves
jpayne@69 315 stack, i = self.call("get_stack", frame._fid, tbid)
jpayne@69 316 stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
jpayne@69 317 return stack, i
jpayne@69 318
jpayne@69 319 def set_continue(self):
jpayne@69 320 self.call("set_continue")
jpayne@69 321
jpayne@69 322 def set_step(self):
jpayne@69 323 self.call("set_step")
jpayne@69 324
jpayne@69 325 def set_next(self, frame):
jpayne@69 326 self.call("set_next", frame._fid)
jpayne@69 327
jpayne@69 328 def set_return(self, frame):
jpayne@69 329 self.call("set_return", frame._fid)
jpayne@69 330
jpayne@69 331 def set_quit(self):
jpayne@69 332 self.call("set_quit")
jpayne@69 333
jpayne@69 334 def set_break(self, filename, lineno):
jpayne@69 335 msg = self.call("set_break", filename, lineno)
jpayne@69 336 return msg
jpayne@69 337
jpayne@69 338 def clear_break(self, filename, lineno):
jpayne@69 339 msg = self.call("clear_break", filename, lineno)
jpayne@69 340 return msg
jpayne@69 341
jpayne@69 342 def clear_all_file_breaks(self, filename):
jpayne@69 343 msg = self.call("clear_all_file_breaks", filename)
jpayne@69 344 return msg
jpayne@69 345
jpayne@69 346 def start_remote_debugger(rpcclt, pyshell):
jpayne@69 347 """Start the subprocess debugger, initialize the debugger GUI and RPC link
jpayne@69 348
jpayne@69 349 Request the RPCServer start the Python subprocess debugger and link. Set
jpayne@69 350 up the Idle side of the split debugger by instantiating the IdbProxy,
jpayne@69 351 debugger GUI, and debugger GUIAdapter objects and linking them together.
jpayne@69 352
jpayne@69 353 Register the GUIAdapter with the RPCClient to handle debugger GUI
jpayne@69 354 interaction requests coming from the subprocess debugger via the GUIProxy.
jpayne@69 355
jpayne@69 356 The IdbAdapter will pass execution and environment requests coming from the
jpayne@69 357 Idle debugger GUI to the subprocess debugger via the IdbProxy.
jpayne@69 358
jpayne@69 359 """
jpayne@69 360 global idb_adap_oid
jpayne@69 361
jpayne@69 362 idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
jpayne@69 363 (gui_adap_oid,), {})
jpayne@69 364 idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
jpayne@69 365 gui = debugger.Debugger(pyshell, idb_proxy)
jpayne@69 366 gui_adap = GUIAdapter(rpcclt, gui)
jpayne@69 367 rpcclt.register(gui_adap_oid, gui_adap)
jpayne@69 368 return gui
jpayne@69 369
jpayne@69 370 def close_remote_debugger(rpcclt):
jpayne@69 371 """Shut down subprocess debugger and Idle side of debugger RPC link
jpayne@69 372
jpayne@69 373 Request that the RPCServer shut down the subprocess debugger and link.
jpayne@69 374 Unregister the GUIAdapter, which will cause a GC on the Idle process
jpayne@69 375 debugger and RPC link objects. (The second reference to the debugger GUI
jpayne@69 376 is deleted in pyshell.close_remote_debugger().)
jpayne@69 377
jpayne@69 378 """
jpayne@69 379 close_subprocess_debugger(rpcclt)
jpayne@69 380 rpcclt.unregister(gui_adap_oid)
jpayne@69 381
jpayne@69 382 def close_subprocess_debugger(rpcclt):
jpayne@69 383 rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})
jpayne@69 384
jpayne@69 385 def restart_subprocess_debugger(rpcclt):
jpayne@69 386 idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
jpayne@69 387 (gui_adap_oid,), {})
jpayne@69 388 assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'
jpayne@69 389
jpayne@69 390
jpayne@69 391 if __name__ == "__main__":
jpayne@69 392 from unittest import main
jpayne@69 393 main('idlelib.idle_test.test_debugger', verbosity=2, exit=False)