annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/debugger_r.py @ 68:5028fdace37b

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