retracesoftware-proxy 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- retracesoftware/__init__.py +0 -0
- retracesoftware/__main__.py +266 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +175 -0
- retracesoftware/config.yaml +0 -0
- retracesoftware/install/__init__.py +0 -0
- retracesoftware/install/config.py +59 -0
- retracesoftware/install/edgecases.py +242 -0
- retracesoftware/install/globals.py +17 -0
- retracesoftware/install/install.py +142 -0
- retracesoftware/install/patcher.py +122 -0
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/predicate.py +92 -0
- retracesoftware/install/record.py +174 -0
- retracesoftware/install/references.py +66 -0
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +102 -0
- retracesoftware/install/tracer.py +284 -0
- retracesoftware/install/typeutils.py +92 -0
- retracesoftware/modules.toml +384 -0
- retracesoftware/proxy/__init__.py +3 -0
- retracesoftware/proxy/gateway.py +49 -0
- retracesoftware/proxy/globalref.py +31 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxyfactory.py +357 -0
- retracesoftware/proxy/proxysystem.py +454 -0
- retracesoftware/proxy/proxytype.py +424 -0
- retracesoftware/proxy/record.py +211 -0
- retracesoftware/proxy/replay.py +138 -0
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +195 -0
- retracesoftware/proxy/thread.py +106 -0
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +373 -0
- retracesoftware/stackdifference.py +133 -0
- retracesoftware_proxy-0.0.0.dist-info/METADATA +8 -0
- retracesoftware_proxy-0.0.0.dist-info/RECORD +41 -0
- retracesoftware_proxy-0.0.0.dist-info/WHEEL +5 -0
- retracesoftware_proxy-0.0.0.dist-info/top_level.txt +1 -0
retracesoftware/run.py
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import runpy
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
# from retracesoftware.install.phases import *
|
|
6
|
+
import pkgutil
|
|
7
|
+
import tomllib
|
|
8
|
+
import retracesoftware.functional as functional
|
|
9
|
+
import builtins
|
|
10
|
+
import importlib
|
|
11
|
+
import _imp
|
|
12
|
+
import importlib._bootstrap_external as _bootstrap_external
|
|
13
|
+
import atexit
|
|
14
|
+
import threading
|
|
15
|
+
import _signal
|
|
16
|
+
import retracesoftware.utils as utils
|
|
17
|
+
from retracesoftware.install.patcher import patch_module, create_patcher, patch_imported_module
|
|
18
|
+
from retracesoftware.proxy.startthread import patch_thread_start
|
|
19
|
+
from retracesoftware.install.replace import update
|
|
20
|
+
|
|
21
|
+
thread_states = [
|
|
22
|
+
"disabled", # Default state when retrace is disabled for a thread
|
|
23
|
+
"internal", # Default state when retrace is disabled for a thread
|
|
24
|
+
"external", # When target thread is running outside the python system to be recorded
|
|
25
|
+
"retrace", # When target thread is running outside the retrace system
|
|
26
|
+
"importing", # When target thread is running outside the retrace system
|
|
27
|
+
"gc", # When the target thread is running inside the pyton garbage collector
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
class ImmutableTypes(set):
|
|
31
|
+
def __init__(self, *args, **kwargs):
|
|
32
|
+
super().__init__(*args, **kwargs)
|
|
33
|
+
|
|
34
|
+
def __contains__(self, item):
|
|
35
|
+
assert isinstance(item, type)
|
|
36
|
+
|
|
37
|
+
if super().__contains__(item):
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
for elem in self:
|
|
41
|
+
if issubclass(item, elem):
|
|
42
|
+
self.add(item)
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
def load_module_config(filename):
|
|
48
|
+
data = pkgutil.get_data("retracesoftware", filename)
|
|
49
|
+
assert data is not None
|
|
50
|
+
return tomllib.loads(data.decode("utf-8"))
|
|
51
|
+
|
|
52
|
+
def wait_for_non_daemon_threads(timeout=None):
|
|
53
|
+
"""
|
|
54
|
+
Wait for all non-daemon threads to finish, just like Python does on exit.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
timeout (float, optional): Max seconds to wait. None = wait forever.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
bool: True if all threads finished, False if timeout exceeded.
|
|
61
|
+
"""
|
|
62
|
+
import threading
|
|
63
|
+
import time
|
|
64
|
+
|
|
65
|
+
start_time = time.time()
|
|
66
|
+
main_thread = threading.main_thread()
|
|
67
|
+
|
|
68
|
+
while True:
|
|
69
|
+
# Get all active threads
|
|
70
|
+
active = threading.enumerate()
|
|
71
|
+
|
|
72
|
+
# Filter: non-daemon and not the main thread
|
|
73
|
+
non_daemon_threads = [
|
|
74
|
+
t for t in active
|
|
75
|
+
if t is not main_thread and not t.daemon
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
if not non_daemon_threads:
|
|
79
|
+
return True # All done!
|
|
80
|
+
|
|
81
|
+
# Check timeout
|
|
82
|
+
if timeout is not None:
|
|
83
|
+
elapsed = time.time() - start_time
|
|
84
|
+
if elapsed >= timeout:
|
|
85
|
+
print(f"Timeout: {len(non_daemon_threads)} thread(s) still alive")
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
# Sleep briefly to avoid busy-wait
|
|
89
|
+
time.sleep(0.1)
|
|
90
|
+
|
|
91
|
+
def run_python_command(argv):
|
|
92
|
+
"""
|
|
93
|
+
Run a Python app from a command list using runpy.
|
|
94
|
+
|
|
95
|
+
Supports:
|
|
96
|
+
['-m', 'module', 'arg1', ...] → like `python -m module arg1 ...`
|
|
97
|
+
['script.py', 'arg1', ...] → like `python script.py arg1 ...`
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
argv: List of command-line arguments (first item is either '-m' or script path)
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Exit code (0 on success, 1+ on error)
|
|
104
|
+
"""
|
|
105
|
+
if not argv:
|
|
106
|
+
print("Error: No command provided", file=sys.stderr)
|
|
107
|
+
return 1
|
|
108
|
+
|
|
109
|
+
original_argv = sys.argv[:]
|
|
110
|
+
original_cwd = os.getcwd()
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
if argv[0] == '-m':
|
|
114
|
+
if len(argv) < 2:
|
|
115
|
+
print("Error: -m requires a module name", file=sys.stderr)
|
|
116
|
+
return 1
|
|
117
|
+
module_name = argv[1]
|
|
118
|
+
module_args = argv[2:]
|
|
119
|
+
sys.argv = ['-m', module_name] + module_args
|
|
120
|
+
runpy.run_module(module_name, run_name="__main__")
|
|
121
|
+
return 0
|
|
122
|
+
|
|
123
|
+
else:
|
|
124
|
+
script_path = argv[0]
|
|
125
|
+
script_args = argv[1:]
|
|
126
|
+
path = Path(script_path)
|
|
127
|
+
|
|
128
|
+
if not path.exists():
|
|
129
|
+
print(f"Error: Script not found: {script_path}", file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
if path.suffix != ".py":
|
|
133
|
+
print(f"Error: Not a Python script: {script_path}", file=sys.stderr)
|
|
134
|
+
return 1
|
|
135
|
+
|
|
136
|
+
full_path = str(path.resolve())
|
|
137
|
+
sys.argv = [full_path] + script_args
|
|
138
|
+
|
|
139
|
+
# Change to script's directory
|
|
140
|
+
os.chdir(path.parent)
|
|
141
|
+
|
|
142
|
+
runpy.run_path(full_path, run_name="__main__")
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
except ModuleNotFoundError:
|
|
146
|
+
print(f"Error: No module named '{argv[1]}'", file=sys.stderr)
|
|
147
|
+
return 1
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
150
|
+
return 1
|
|
151
|
+
finally:
|
|
152
|
+
sys.argv = original_argv
|
|
153
|
+
os.chdir(original_cwd)
|
|
154
|
+
|
|
155
|
+
def patch_import(thread_state, patcher, sync, checkpoint):
|
|
156
|
+
# builtins.__import__ = thread_state.dispatch(builtins.__import__, internal = sync(thread_state.wrap('importing', builtins.__import__)))
|
|
157
|
+
# bi = builtins.__import__
|
|
158
|
+
# def foo(*args, **kwargs):
|
|
159
|
+
# print(f'in patched __import__: {thread_state.value} {args[0]}')
|
|
160
|
+
# if thread_state.value == 'internal':
|
|
161
|
+
# with thread_state.select('importing'):
|
|
162
|
+
# return bi(*args, **kwargs)
|
|
163
|
+
# else:
|
|
164
|
+
# return bi(*args, **kwargs)
|
|
165
|
+
|
|
166
|
+
# builtins.__import__ = foo
|
|
167
|
+
|
|
168
|
+
builtins.__import__ = thread_state.dispatch(builtins.__import__, internal = thread_state.wrap('importing', builtins.__import__))
|
|
169
|
+
|
|
170
|
+
def exec(source, globals = None, locals = None):
|
|
171
|
+
checkpoint(f"exec module: {globals.get('__name__', 'unknown')}")
|
|
172
|
+
res = builtins.exec(source, globals, locals)
|
|
173
|
+
patcher(globals, False)
|
|
174
|
+
return res
|
|
175
|
+
|
|
176
|
+
def patch(module):
|
|
177
|
+
patcher(module.__dict__, False)
|
|
178
|
+
return module
|
|
179
|
+
|
|
180
|
+
_imp.exec_dynamic = thread_state.dispatch(_imp.exec_dynamic,
|
|
181
|
+
importing = functional.juxt(
|
|
182
|
+
thread_state.wrap('internal', _imp.exec_dynamic),
|
|
183
|
+
patch))
|
|
184
|
+
# _imp.exec_dynamic = lambda mod: utils.sigtrap(f'{thread_state.value} - {mod}')
|
|
185
|
+
# _imp.exec_builtin = lambda mod: utils.sigtrap(f'{thread_state.value} - {mod}')
|
|
186
|
+
|
|
187
|
+
_imp.exec_builtin = thread_state.dispatch(_imp.exec_builtin,
|
|
188
|
+
importing = functional.juxt(
|
|
189
|
+
thread_state.wrap('internal', _imp.exec_builtin),
|
|
190
|
+
patch))
|
|
191
|
+
|
|
192
|
+
# def runpy_exec(source, globals = None, locals = None):
|
|
193
|
+
# print(f'In runpy exec!!!!!')
|
|
194
|
+
# return builtins.exec(source, globals, locals)
|
|
195
|
+
|
|
196
|
+
# utils.update(runpy, "_run_code",
|
|
197
|
+
# utils.wrap_func_with_overrides,
|
|
198
|
+
# exec = runpy_exec)
|
|
199
|
+
|
|
200
|
+
utils.update(_bootstrap_external._LoaderBasics, "exec_module",
|
|
201
|
+
utils.wrap_func_with_overrides,
|
|
202
|
+
exec = thread_state.dispatch(builtins.exec, importing = thread_state.wrap('internal', sync(exec))))
|
|
203
|
+
|
|
204
|
+
preload = [
|
|
205
|
+
"logging",
|
|
206
|
+
"pathlib",
|
|
207
|
+
"_signal",
|
|
208
|
+
"_posixsubprocess",
|
|
209
|
+
"socket",
|
|
210
|
+
"select",
|
|
211
|
+
"ssl",
|
|
212
|
+
"random",
|
|
213
|
+
"email",
|
|
214
|
+
"email.errors",
|
|
215
|
+
"http.client",
|
|
216
|
+
|
|
217
|
+
"json",
|
|
218
|
+
"typing",
|
|
219
|
+
"queue",
|
|
220
|
+
"mimetypes",
|
|
221
|
+
"tempfile",
|
|
222
|
+
"zipfile",
|
|
223
|
+
"importlib.resources",
|
|
224
|
+
"importlib.metadata",
|
|
225
|
+
"encodings.idna"
|
|
226
|
+
|
|
227
|
+
# "http.client",
|
|
228
|
+
# "queue",
|
|
229
|
+
# "mimetypes",
|
|
230
|
+
# "encodings.idna",
|
|
231
|
+
# "hmac",
|
|
232
|
+
# "ipaddress",
|
|
233
|
+
# "tempfile",
|
|
234
|
+
# "zipfile",
|
|
235
|
+
# "importlib.resources",
|
|
236
|
+
# "importlib.metadata",
|
|
237
|
+
# "atexit",
|
|
238
|
+
# "weakref"
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
# def debugger_is_active():
|
|
242
|
+
# return sys.gettrace() and 'debugpy' in sys.modules
|
|
243
|
+
|
|
244
|
+
def init_weakref():
|
|
245
|
+
import weakref
|
|
246
|
+
def dummy_callback():
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
class DummyTarget:
|
|
250
|
+
pass
|
|
251
|
+
|
|
252
|
+
f = weakref.finalize(DummyTarget(), dummy_callback)
|
|
253
|
+
f.detach()
|
|
254
|
+
|
|
255
|
+
def wrapped_weakref(ref, thread_state, wrap_callback):
|
|
256
|
+
orig_new = ref.__new__
|
|
257
|
+
|
|
258
|
+
def __new__(cls, ob, callback=None, **kwargs):
|
|
259
|
+
return orig_new(cls, ob, wrap_callback(callback) if callback else None)
|
|
260
|
+
|
|
261
|
+
return type('ref', (ref, ), {'__new__': thread_state.dispatch(orig_new, internal = __new__)})
|
|
262
|
+
|
|
263
|
+
def patch_weakref(thread_state, wrap_callback):
|
|
264
|
+
import _weakref
|
|
265
|
+
|
|
266
|
+
update(_weakref.ref, wrapped_weakref(_weakref.ref, thread_state, wrap_callback))
|
|
267
|
+
# _weakref.ref = wrapped_weakref(_weakref.ref)
|
|
268
|
+
|
|
269
|
+
# def patch_signal(thread_state, wrap_callback):
|
|
270
|
+
|
|
271
|
+
# def wrap_handler(handler):
|
|
272
|
+
# return utils.observer(on_call = ..., function = handler)
|
|
273
|
+
|
|
274
|
+
# _signal.signal =
|
|
275
|
+
# update(_signal.signal, thread_state.dispatch(_signal.signal, internal = thread_state.wrap('internal', _signal.signal)))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def install(system):
|
|
280
|
+
|
|
281
|
+
patch_weakref(thread_state = system.thread_state, wrap_callback = system.wrap_weakref_callback)
|
|
282
|
+
|
|
283
|
+
init_weakref()
|
|
284
|
+
|
|
285
|
+
for name in preload:
|
|
286
|
+
importlib.import_module(name)
|
|
287
|
+
|
|
288
|
+
# if 'pydevd' in sys.modules:
|
|
289
|
+
# utils.update(sys.modules['pydevd'].PyDB, 'enable_tracing', system.disable_for)
|
|
290
|
+
# utils.update(sys.modules['pydevd'].PyDB, 'set_suspend', system.disable_for)
|
|
291
|
+
# utils.update(sys.modules['pydevd'].PyDB, 'do_wait_suspend', system.disable_for)
|
|
292
|
+
|
|
293
|
+
# if '_pydevd_bundle.pydevd_trace_dispatch_regular' in sys.modules:
|
|
294
|
+
# mod = sys.modules['_pydevd_bundle.pydevd_trace_dispatch_regular']
|
|
295
|
+
# utils.update(mod.ThreadTracer, '__call__', system.disable_for)
|
|
296
|
+
|
|
297
|
+
for function in utils.stack_functions():
|
|
298
|
+
system.exclude_from_stacktrace(function)
|
|
299
|
+
|
|
300
|
+
def recursive_disable(func):
|
|
301
|
+
if not callable(func):
|
|
302
|
+
return func
|
|
303
|
+
|
|
304
|
+
def wrapped(*args, **kwargs):
|
|
305
|
+
with system.thread_state.select('disabled'):
|
|
306
|
+
return recursive_disable(func(*args, **kwargs))
|
|
307
|
+
|
|
308
|
+
return wrapped
|
|
309
|
+
|
|
310
|
+
sys.settrace = functional.sequence(recursive_disable, sys.settrace)
|
|
311
|
+
sys.setprofile = functional.sequence(recursive_disable, sys.setprofile)
|
|
312
|
+
threading.settrace = functional.sequence(recursive_disable, threading.settrace)
|
|
313
|
+
|
|
314
|
+
sys.settrace(sys.gettrace())
|
|
315
|
+
sys.setprofile(sys.getprofile())
|
|
316
|
+
|
|
317
|
+
system.checkpoint('About to install retrace system')
|
|
318
|
+
|
|
319
|
+
module_config = load_module_config('modules.toml')
|
|
320
|
+
|
|
321
|
+
patch_loaded = functional.partial(patch_module, create_patcher(system), module_config)
|
|
322
|
+
patch_imported = functional.partial(patch_imported_module, create_patcher(system), system.checkpoint, module_config)
|
|
323
|
+
|
|
324
|
+
system.checkpoint('Started installing system 1')
|
|
325
|
+
|
|
326
|
+
for modname in module_config.keys():
|
|
327
|
+
if modname in sys.modules:
|
|
328
|
+
patch_loaded(sys.modules[modname].__dict__, True)
|
|
329
|
+
|
|
330
|
+
system.checkpoint('About to patch threading')
|
|
331
|
+
|
|
332
|
+
patch_thread_start(system.thread_state)
|
|
333
|
+
threading.current_thread().__retrace__ = system
|
|
334
|
+
|
|
335
|
+
system.checkpoint('About to patch import')
|
|
336
|
+
patch_import(thread_state = system.thread_state,
|
|
337
|
+
patcher = patch_imported,
|
|
338
|
+
sync = system.sync,
|
|
339
|
+
checkpoint = system.checkpoint)
|
|
340
|
+
|
|
341
|
+
# print(f'MODULES: {list(sys.modules.keys())}')
|
|
342
|
+
|
|
343
|
+
importlib.import_module = \
|
|
344
|
+
system.thread_state.dispatch(system.disable_for(importlib.import_module),
|
|
345
|
+
internal = system.thread_state.wrap('importing', importlib.import_module))
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
system.checkpoint('About to patch preload libraries')
|
|
349
|
+
|
|
350
|
+
system.checkpoint('system patched...')
|
|
351
|
+
|
|
352
|
+
def run_with_retrace(system, argv, trace_shutdown = False):
|
|
353
|
+
|
|
354
|
+
def runpy_exec(source, globals = None, locals = None):
|
|
355
|
+
with system.thread_state.select('internal'):
|
|
356
|
+
return builtins.exec(source, globals, locals)
|
|
357
|
+
|
|
358
|
+
utils.update(runpy, "_run_code",
|
|
359
|
+
utils.wrap_func_with_overrides,
|
|
360
|
+
exec = runpy_exec)
|
|
361
|
+
|
|
362
|
+
try:
|
|
363
|
+
run_python_command(argv)
|
|
364
|
+
finally:
|
|
365
|
+
wait_for_non_daemon_threads()
|
|
366
|
+
try:
|
|
367
|
+
if trace_shutdown:
|
|
368
|
+
with system.thread_state.select('internal'):
|
|
369
|
+
atexit._run_exitfuncs()
|
|
370
|
+
else:
|
|
371
|
+
atexit._run_exitfuncs()
|
|
372
|
+
except Exception as e:
|
|
373
|
+
print(f"Error in atexit hook: {e}", file=sys.stderr)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import inspect
|
|
3
|
+
import ast
|
|
4
|
+
from typing import Optional
|
|
5
|
+
import importlib
|
|
6
|
+
import retracesoftware.utils as utils
|
|
7
|
+
|
|
8
|
+
def frozen_to_module(name: str):
|
|
9
|
+
"""
|
|
10
|
+
Given a frozen filename like "<frozen importlib._bootstrap>",
|
|
11
|
+
return the actual module object (or None if not frozen).
|
|
12
|
+
"""
|
|
13
|
+
if not name.startswith("<frozen ") or not name.endswith(">"):
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
# Extract the part between <frozen ...>
|
|
17
|
+
module_name = name[len("<frozen "):-1]
|
|
18
|
+
|
|
19
|
+
# Special case: the stdlib ships some as _frozen_importlib and _frozen_importlib_external
|
|
20
|
+
if module_name == "importlib._bootstrap":
|
|
21
|
+
return sys.modules.get("_frozen_importlib") or importlib._bootstrap
|
|
22
|
+
if module_name == "importlib._bootstrap_external":
|
|
23
|
+
return sys.modules.get("_frozen_importlib_external") or importlib._bootstrap_external
|
|
24
|
+
|
|
25
|
+
# Everything else is directly importable
|
|
26
|
+
return importlib.import_module(module_name)
|
|
27
|
+
|
|
28
|
+
def get_function_at_line(filename: str, source: str, lineno: int) -> Optional[str]:
|
|
29
|
+
"""
|
|
30
|
+
Return the name of the function (or class/method) that contains the given line number.
|
|
31
|
+
|
|
32
|
+
Returns None if:
|
|
33
|
+
- file not found
|
|
34
|
+
- syntax error
|
|
35
|
+
- line is outside any function (module level)
|
|
36
|
+
"""
|
|
37
|
+
# except FileNotFoundError:
|
|
38
|
+
# return None
|
|
39
|
+
# except UnicodeDecodeError:
|
|
40
|
+
# return None
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
tree = ast.parse(source, filename=filename)
|
|
44
|
+
except SyntaxError:
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
class FunctionFinder(ast.NodeVisitor):
|
|
48
|
+
def __init__(self, target_lineno: int):
|
|
49
|
+
self.target_lineno = target_lineno
|
|
50
|
+
self.current: Optional[str] = None
|
|
51
|
+
self.stack: list[str] = []
|
|
52
|
+
|
|
53
|
+
def visit_FunctionDef(self, node):
|
|
54
|
+
if node.lineno <= self.target_lineno <= node.end_lineno:
|
|
55
|
+
# breakpoint()
|
|
56
|
+
self.stack.append(node.name)
|
|
57
|
+
self.current = node.name
|
|
58
|
+
self.generic_visit(node)
|
|
59
|
+
self.stack.pop()
|
|
60
|
+
elif self.stack:
|
|
61
|
+
# We're inside a nested function/class, keep traversing
|
|
62
|
+
self.generic_visit(node)
|
|
63
|
+
|
|
64
|
+
def visit_AsyncFunctionDef(self, node):
|
|
65
|
+
self.visit_FunctionDef(node) # same logic
|
|
66
|
+
|
|
67
|
+
def visit_ClassDef(self, node):
|
|
68
|
+
if node.lineno <= self.target_lineno <= node.end_lineno:
|
|
69
|
+
self.stack.append(node.name)
|
|
70
|
+
# Don't set current here — we prefer method names
|
|
71
|
+
self.generic_visit(node)
|
|
72
|
+
self.stack.pop()
|
|
73
|
+
|
|
74
|
+
# def get_result(self) -> Optional[str]:
|
|
75
|
+
# if not self.stack:
|
|
76
|
+
# return None
|
|
77
|
+
# # Return the deepest (most specific) function/method name
|
|
78
|
+
# return self.current
|
|
79
|
+
# # return self.stack[-1]
|
|
80
|
+
|
|
81
|
+
finder = FunctionFinder(lineno)
|
|
82
|
+
finder.visit(tree)
|
|
83
|
+
# print(f'foind: {finder.current}')
|
|
84
|
+
return finder.current
|
|
85
|
+
|
|
86
|
+
def get_source(filename):
|
|
87
|
+
try:
|
|
88
|
+
with open(filename, "r", encoding="utf-8") as f:
|
|
89
|
+
return f.read()
|
|
90
|
+
except FileNotFoundError:
|
|
91
|
+
return inspect.getsource(frozen_to_module(filename))
|
|
92
|
+
|
|
93
|
+
def all_elements_same(t):
|
|
94
|
+
return len(set(t)) <= 1
|
|
95
|
+
|
|
96
|
+
def first(coll): return coll[0]
|
|
97
|
+
|
|
98
|
+
def common_prefix(*colls):
|
|
99
|
+
return list(map(first, itertools.takewhile(all_elements_same, zip(*colls))))
|
|
100
|
+
|
|
101
|
+
def render_stack(frames):
|
|
102
|
+
for filename,lineno in frames:
|
|
103
|
+
try:
|
|
104
|
+
source = get_source(filename)
|
|
105
|
+
print(f'File "{filename}", line {lineno}, in {get_function_at_line(filename, source, lineno)}')
|
|
106
|
+
print(f' {source.splitlines()[lineno - 1].lstrip()}')
|
|
107
|
+
except Exception:
|
|
108
|
+
print(f'File not found: "{filename}", line {lineno}')
|
|
109
|
+
|
|
110
|
+
def on_stack_difference(previous, record, replay):
|
|
111
|
+
|
|
112
|
+
# previous = [x for x in previous if x not in excludes]
|
|
113
|
+
# record = [x for x in record if x not in excludes]
|
|
114
|
+
# replay = [x for x in replay if x not in excludes]
|
|
115
|
+
|
|
116
|
+
if record != replay:
|
|
117
|
+
common = common_prefix(previous, record, replay) if previous else common_prefix(record, replay)
|
|
118
|
+
|
|
119
|
+
if common:
|
|
120
|
+
print('Common root:')
|
|
121
|
+
render_stack(common)
|
|
122
|
+
|
|
123
|
+
if previous:
|
|
124
|
+
print('\nlast matching:')
|
|
125
|
+
render_stack(previous[len(common):])
|
|
126
|
+
|
|
127
|
+
print('\nrecord:')
|
|
128
|
+
render_stack(record[len(common):])
|
|
129
|
+
|
|
130
|
+
print('\nreplay:')
|
|
131
|
+
render_stack(replay[len(common):])
|
|
132
|
+
|
|
133
|
+
utils.sigtrap('on_stack_difference!!!!!')
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
retracesoftware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
retracesoftware/__main__.py,sha256=legPSoOYPrfnB_t2v5NsdGuoBXjFRd8qub98QRXCtFM,8408
|
|
3
|
+
retracesoftware/autoenable.py,sha256=s7dSykM0y3xVqRzrWouGgoed4-lMNRhdA7Gnm1f04NA,1772
|
|
4
|
+
retracesoftware/config.json,sha256=Cw5YZCfTo8GLmtbTH2LYwST7wNe7925DAwsTtRIGhBE,3817
|
|
5
|
+
retracesoftware/config.yaml,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
retracesoftware/modules.toml,sha256=C3O4LnHwWInRT9ZSB-crSM8Q98vYCZSFJB3akGJLfVM,14536
|
|
7
|
+
retracesoftware/replay.py,sha256=rKuhen_MDDi_5WVDmQgchxJ0ZrCbYL1yZZboZsu_q54,3601
|
|
8
|
+
retracesoftware/run.py,sha256=ovunjJjDAe23ik-KbYrLUseb9u2bZ26uZNg-QIPENMc,11966
|
|
9
|
+
retracesoftware/stackdifference.py,sha256=nM9r5YHMCNQcrWozMjUX4ZTrZL5U0rQChSGzpiXy_DU,4466
|
|
10
|
+
retracesoftware/install/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
retracesoftware/install/config.py,sha256=AnSlHI3lWQrPUg0fOS26PqXiq1hJSy56SwrNL8mbAG8,1546
|
|
12
|
+
retracesoftware/install/edgecases.py,sha256=zv6xjUFoiuNWeBhIDIKb7kN6DVv8gxoG797sNV2ak8Y,7099
|
|
13
|
+
retracesoftware/install/globals.py,sha256=F8XvIoZQQ10gSRalk30dvdKllxlwxkaggYY6FogLDxY,510
|
|
14
|
+
retracesoftware/install/install.py,sha256=HCD_ji8XCr96b5fNzNdL_8qcEp0Jf05Em7T6GA6u8HU,4969
|
|
15
|
+
retracesoftware/install/patcher.py,sha256=sHgsKvdvjMJSVl8lOa3YiCjGf6zYhxgkWp67-ptJvlk,4157
|
|
16
|
+
retracesoftware/install/patchfindspec.py,sha256=uQkfdOQOY20hFAcwDAqx7m8rbtGM2rBKOfC3Szb1zhw,4942
|
|
17
|
+
retracesoftware/install/phases.py,sha256=OWaw86xzC924tdIA9C-12Qdr3sCx67hEOn-Ih9FM--4,9359
|
|
18
|
+
retracesoftware/install/predicate.py,sha256=tX7NQc0rGkyyHYO3mduYHcJHbw1wczT53m_Dpkzo6do,2679
|
|
19
|
+
retracesoftware/install/record.py,sha256=08q7zc9cfyCyhLsh15q1d_5vFIhKbs9ln6VvL6kED5o,5861
|
|
20
|
+
retracesoftware/install/references.py,sha256=A-G651IDOfuo00MkbAdpbIQh_15ChvJ7uAVTSmE6zd4,1721
|
|
21
|
+
retracesoftware/install/replace.py,sha256=FYiSJtNrXEhl-H6M5tJm0kbliBA0sZdxE0306pr-YQg,872
|
|
22
|
+
retracesoftware/install/replay.py,sha256=bB2eMpIP2vXZzeJ98jtlkGSE1DEYWk3olUbiBQcdVj0,3539
|
|
23
|
+
retracesoftware/install/tracer.py,sha256=GO3Nnzd0ylxM7-A7RN0LzLCFdouJIiHtIcJ2zjFx0Q0,9363
|
|
24
|
+
retracesoftware/install/typeutils.py,sha256=-oFzgUfq_nHeOkj3YKZiMLlMzQhCedg3qymLiEJNkVE,2253
|
|
25
|
+
retracesoftware/proxy/__init__.py,sha256=ntIyqKhBRkKEkcW_oOPodikh-mxYl8OXRnSaj-9-Xwc,178
|
|
26
|
+
retracesoftware/proxy/gateway.py,sha256=xESohWXkiNm4ZutU0RgWUwxjxcBWRQ4rQyxIGQXv_F4,1590
|
|
27
|
+
retracesoftware/proxy/globalref.py,sha256=yXtJsOeBHN9xoEgJWA3MJco-jD2SQUef_fDatA4A6rg,803
|
|
28
|
+
retracesoftware/proxy/messagestream.py,sha256=kjxIugCSr7YSJbIqsqqXeW7dyE2SzWow2E-UJnkAflk,5890
|
|
29
|
+
retracesoftware/proxy/proxyfactory.py,sha256=qhOqDfMJnLDNkQs26JqDB431MwjjRhGQi8xupJ45asg,12272
|
|
30
|
+
retracesoftware/proxy/proxysystem.py,sha256=Pj9sk5FE3Mfyf4-PAtXSz2Pvr_GwXcpfhUSJvkz00dg,15076
|
|
31
|
+
retracesoftware/proxy/proxytype.py,sha256=LZ1rNMsxXRUMdI4uTzDvGkUyPOYxCFroT_ZAtOGTbBk,14012
|
|
32
|
+
retracesoftware/proxy/record.py,sha256=JTmeZ13ISbsVal1WOtae425DP0iA-QiAv0C6azPpV8k,6680
|
|
33
|
+
retracesoftware/proxy/replay.py,sha256=Kx4XrsP0T35IvTzf3pwhR24Lotoz43p7E8LBevEQHpE,4948
|
|
34
|
+
retracesoftware/proxy/serializer.py,sha256=S5yhHoP2iYaXBY7Jr8Ei630Z31521x0IO6DeuClOD9s,958
|
|
35
|
+
retracesoftware/proxy/startthread.py,sha256=YKUcOSjA-9_5ZsDHhyP0fXJSP3RWDKV-ajX-I0SI-bU,1270
|
|
36
|
+
retracesoftware/proxy/stubfactory.py,sha256=fyQtCZnkXvLaTlGcxE0Lx8od09ZOgnPWPn0YvNFfVeQ,6219
|
|
37
|
+
retracesoftware/proxy/thread.py,sha256=T1ME6DHB8O0xVnX3Rt1lMl7oCJ2Y0aoFT91D76yNICk,3073
|
|
38
|
+
retracesoftware_proxy-0.0.0.dist-info/METADATA,sha256=mJIUCRnUE3D2fGhntlAEyiUYTf22UT94U4KleWa9i3s,226
|
|
39
|
+
retracesoftware_proxy-0.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
40
|
+
retracesoftware_proxy-0.0.0.dist-info/top_level.txt,sha256=hYHsR6txLidmqvjBMITpIHvmJJbmoCAgr76-IpZPRz8,16
|
|
41
|
+
retracesoftware_proxy-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
retracesoftware
|