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