retracesoftware-proxy 0.2.11__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.
Files changed (42) hide show
  1. retracesoftware/__init__.py +0 -0
  2. retracesoftware/__main__.py +288 -0
  3. retracesoftware/autoenable.py +53 -0
  4. retracesoftware/config.json +175 -0
  5. retracesoftware/config.yaml +0 -0
  6. retracesoftware/install/__init__.py +0 -0
  7. retracesoftware/install/config.py +59 -0
  8. retracesoftware/install/edgecases.py +250 -0
  9. retracesoftware/install/globals.py +17 -0
  10. retracesoftware/install/install.py +142 -0
  11. retracesoftware/install/patcher.py +122 -0
  12. retracesoftware/install/patchfindspec.py +117 -0
  13. retracesoftware/install/phases.py +338 -0
  14. retracesoftware/install/predicate.py +92 -0
  15. retracesoftware/install/record.py +174 -0
  16. retracesoftware/install/references.py +66 -0
  17. retracesoftware/install/replace.py +28 -0
  18. retracesoftware/install/replay.py +102 -0
  19. retracesoftware/install/tracer.py +284 -0
  20. retracesoftware/install/typeutils.py +92 -0
  21. retracesoftware/modules.toml +388 -0
  22. retracesoftware/preload.txt +218 -0
  23. retracesoftware/proxy/__init__.py +3 -0
  24. retracesoftware/proxy/gateway.py +49 -0
  25. retracesoftware/proxy/globalref.py +31 -0
  26. retracesoftware/proxy/messagestream.py +204 -0
  27. retracesoftware/proxy/proxyfactory.py +357 -0
  28. retracesoftware/proxy/proxysystem.py +454 -0
  29. retracesoftware/proxy/proxytype.py +424 -0
  30. retracesoftware/proxy/record.py +211 -0
  31. retracesoftware/proxy/replay.py +138 -0
  32. retracesoftware/proxy/serializer.py +28 -0
  33. retracesoftware/proxy/startthread.py +40 -0
  34. retracesoftware/proxy/stubfactory.py +195 -0
  35. retracesoftware/proxy/thread.py +106 -0
  36. retracesoftware/replay.py +104 -0
  37. retracesoftware/run.py +378 -0
  38. retracesoftware/stackdifference.py +133 -0
  39. retracesoftware_proxy-0.2.11.dist-info/METADATA +8 -0
  40. retracesoftware_proxy-0.2.11.dist-info/RECORD +42 -0
  41. retracesoftware_proxy-0.2.11.dist-info/WHEEL +5 -0
  42. retracesoftware_proxy-0.2.11.dist-info/top_level.txt +1 -0
@@ -0,0 +1,102 @@
1
+ from retracesoftware.proxy import *
2
+
3
+ from retracesoftware.install import globals
4
+
5
+ import os, json, re, glob
6
+ from pathlib import Path
7
+ from datetime import datetime
8
+ from retracesoftware.install.config import env_truthy
9
+
10
+ def recordings(pattern):
11
+ # Turn strftime placeholders into '*' for globbing
12
+ # (very simple replacement: %... -> *)
13
+ glob_pattern = re.sub(r"%[a-zA-Z]", "*", pattern)
14
+
15
+ base_pattern = os.path.basename(pattern)
16
+
17
+ # Find all matching files
18
+ for path in glob.glob(glob_pattern):
19
+ try:
20
+ name = os.path.basename(path)
21
+ datetime.strptime(name, base_pattern)
22
+ yield path
23
+ except:
24
+ pass
25
+
26
+ def latest_from_pattern(pattern: str) -> str | None:
27
+ """
28
+ Given a strftime-style filename pattern (e.g. "recordings/%Y%m%d_%H%M%S_%f"),
29
+ return the path to the most recent matching file, or None if no files exist.
30
+ """
31
+ # Derive the datetime format from the pattern (basename only)
32
+ base_pattern = os.path.basename(pattern)
33
+
34
+ def parse_time(path: str):
35
+ name = os.path.basename(path)
36
+ return datetime.strptime(name, base_pattern)
37
+
38
+ # Find the latest by parsed timestamp
39
+ return max(recordings(pattern), key=parse_time)
40
+
41
+ def replay_system(thread_state, immutable_types, config):
42
+
43
+ verbose = env_truthy('RETRACE_VERBOSE', False)
44
+ recording_path = Path(latest_from_pattern(config['recording_path']))
45
+
46
+ # print(f"replay running against path: {recording_path}")
47
+
48
+ globals.recording_path = globals.RecordingPath(recording_path)
49
+
50
+ assert recording_path.exists()
51
+ assert recording_path.is_dir()
52
+
53
+ with open(recording_path / "env", "r", encoding="utf-8") as f:
54
+ os.environ.update(json.load(f))
55
+
56
+ with open(recording_path / "tracing_config.json", "r", encoding="utf-8") as f:
57
+ tracing_config = json.load(f)
58
+
59
+ with open(recording_path / "mainscript", "r", encoding="utf-8") as f:
60
+ mainscript = f.read()
61
+
62
+ call_trace_file = recording_path / "call_trace.txt"
63
+
64
+ if call_trace_file.exists():
65
+ class CallTracer:
66
+ def __init__(self):
67
+ self.file = open(call_trace_file, 'r')
68
+
69
+ def __call__(self, obj):
70
+ recording = json.loads(self.file.readline())
71
+ if obj != recording:
72
+ breakpoint()
73
+
74
+ def close(self):
75
+ self.file.close()
76
+
77
+ # def on_call(self, function_name):
78
+ # recording = self.file.readline()
79
+ # if f'call: {function_name}\n' != recording:
80
+ # breakpoint()
81
+
82
+ # assert f'call: {function_name}\n' == recording
83
+
84
+ # def on_return(self, function_name):
85
+ # recording = self.file.readline()
86
+ # if f'return: {function_name}\n' != recording:
87
+ # breakpoint()
88
+
89
+ # assert f'return: {function_name}\n' == recording
90
+
91
+ call_tracer = CallTracer()
92
+ else:
93
+ call_tracer = None
94
+
95
+ return ReplayProxySystem(thread_state = thread_state,
96
+ immutable_types = immutable_types,
97
+ tracing_config = tracing_config,
98
+ mainscript = mainscript,
99
+ path = recording_path / 'trace.bin',
100
+ tracecalls = env_truthy('RETRACE_ALL', False),
101
+ verbose = verbose,
102
+ magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
@@ -0,0 +1,284 @@
1
+ import retracesoftware.functional as functional
2
+ import retracesoftware_utils as utils
3
+ from retracesoftware.proxy.proxytype import Proxy
4
+
5
+ from retracesoftware.proxy.serializer import serializer
6
+
7
+ import types
8
+ import os
9
+ import sys
10
+ import functools
11
+
12
+ def format_kwargs(kwargs):
13
+ result = []
14
+ for k, v in kwargs.items():
15
+ result.extend([f"{k} = ", v])
16
+
17
+ return tuple(result)
18
+
19
+ class CallTracer:
20
+
21
+ ignore = set([('abc', '__subclasscheck__'),
22
+ ('threading', '_shutdown'),
23
+ ('typing', 'inner')])
24
+
25
+ def __init__(self, writer, thread_state):
26
+ self.writer = writer
27
+ self.thread_state = thread_state
28
+
29
+ def on_result(self, result):
30
+ if type(result) in [str]:
31
+ self.writer(result)
32
+ else:
33
+ self.writer(str(type(result)))
34
+
35
+ def on_error(self, exc_type, exc_value, exc_traceback):
36
+ pass
37
+
38
+ def __call__(self, frame):
39
+ if self.thread_state.value == 'internal':
40
+ with self.thread_state.select('disabled'):
41
+ func = frame.function
42
+
43
+ key = (func.__module__, func.__name__)
44
+
45
+ if key not in CallTracer.ignore and func.__name__ != '__hash__':
46
+ self.writer(f'{func.__module__}.{func.__name__}')
47
+ return self
48
+ else:
49
+ return lambda *args, **kwargs: self(*args, **kwargs)
50
+
51
+ class Tracer:
52
+
53
+ def __init__(self, config, writer):
54
+ self.config = config
55
+ serialize = serializer
56
+ # functional.walker(self.serialize)
57
+ self.writer = functional.mapargs(transform = serialize, function = writer)
58
+
59
+ def systrace(self, frame, event, arg):
60
+ try:
61
+ if event in ['line', 'call']:
62
+ print(f'In systrace: {event} {frame.f_code.co_filename}:{frame.f_lineno}')
63
+ self.writer(event, frame.f_code.co_filename, frame.f_code.co_name, frame.f_lineno)
64
+
65
+ elif event == 'return':
66
+ print(f'In systrace: {event}')
67
+ self.writer(event)
68
+ # self.writer(event, self.serialize(arg))
69
+
70
+ elif event == 'exception':
71
+ print(f'In systrace: {event}')
72
+ self.writer(event)
73
+ else:
74
+ print(f'In systrace: {event}')
75
+
76
+ return self.systrace
77
+ except:
78
+ print(f'systrace RAISED ERROR!')
79
+ raise
80
+
81
+ # def trace_calls(self, thread_state):
82
+ # if 'tracecalls' in self.config:
83
+ # # def on_call(frame):
84
+
85
+ # # func = frame.function
86
+
87
+ # # key = (func.__module__, func.__name__)
88
+
89
+ # # ignore = set([('abc', '__subclasscheck__'),
90
+ # # ('threading', '_shutdown')])
91
+
92
+ # # if key in ignore:
93
+ # # print('About to throw!!!')
94
+ # # raise Exception()
95
+
96
+ # # objtype = None
97
+
98
+ # # if 'self' in frame.locals:
99
+ # # this = frame.locals['self']
100
+ # # objtype = type(this)
101
+
102
+ # # # if objtype:
103
+ # # # print(f'Called!!!!: {func.__module__}.{objtype.__name__}.{func.__name__}')
104
+ # # # else:
105
+ # # # print(f'Called!!!!: {func.__module__}.{func.__name__}')
106
+
107
+ # # qualname = (func.__module__, objtype, func.__name__)
108
+
109
+ # # self.writer(f'{func.__module__}.{func.__name__}')
110
+
111
+ # # # if func.__name__ == '_path_stat':
112
+ # # # utils.sigtrap('_path_stat start')
113
+
114
+ # # return qualname
115
+
116
+ # # def on_result(qualname, res):
117
+ # # mod, obj, func = qualname
118
+
119
+ # # # if obj:
120
+ # # # print(f'Returning!!!!: {mod}.{obj}.{func}')
121
+ # # # else:
122
+ # # # print(f'Returning!!!!: {mod}.{func}')
123
+
124
+ # # # if func == '_path_stat':
125
+ # # # utils.sigtrap('_path_stat res')
126
+
127
+ # # if func != '__hash__':
128
+ # # ignore = set(('typing', 'inner'))
129
+
130
+ # # if qualname not in ignore:
131
+ # # if type(res) in [str, int]:
132
+ # # self.writer(res)
133
+ # # else:
134
+ # # self.writer(str(type(res)))
135
+
136
+ # # def on_call_disabled(frame):
137
+ # # func = frame.function
138
+ # # print(f'disabled: {func.__module__}.{func.__name__}')
139
+
140
+ # callback = CallTracer(writer = self.writer, thread_state = thread_state)
141
+ # utils.intercept_frame_eval(callback)
142
+
143
+ # def serialize(self, obj):
144
+ # try:
145
+ # if obj is None: return None
146
+
147
+ # cls = functional.typeof(obj)
148
+
149
+ # if issubclass(cls, Proxy):
150
+ # return str(cls)
151
+
152
+ # if issubclass(cls, (int, str)):
153
+ # return obj
154
+
155
+ # return str(cls)
156
+
157
+ # # if issubclass(cls, Proxy):
158
+ # # return f'<Proxy>'
159
+
160
+ # # if issubclass(cls, types.TracebackType):
161
+ # # return '<traceback>'
162
+
163
+ # # elif issubclass(cls, utils.wrapped_function):
164
+ # # return utils.unwrap(obj).__name__
165
+
166
+ # # elif hasattr(obj, '__module__') and hasattr(obj, '__name__'):
167
+ # # return f'{obj.__module__}.{obj.__name__}'
168
+ # # # elif isinstance(obj, type):
169
+ # # # return f'{obj.__module__}.{obj.__name__}'
170
+ # # else:
171
+ # # return '<other>'
172
+ # # # return f'instance type: {str(self.serialize(type(obj)))}'
173
+ # except:
174
+ # print("ERROR in tracer serialize!!!!")
175
+ # os._exit(1)
176
+
177
+ def log(self, name, message):
178
+ if name in self.config:
179
+ self.writer(name, message)
180
+
181
+ def checkpoint(self, obj):
182
+ self.writer(obj)
183
+
184
+ # def stacktrace(self):
185
+ # self.writer('stacktrace', utils.stacktrace())
186
+
187
+ def _write_call(self, name, *args, **kwargs):
188
+ # self.stacktrace()
189
+ if len(args) > 0:
190
+ funcname = args[0].__name__
191
+ # if funcname == '__del__':
192
+ # utils.sigtrap('FOO')
193
+
194
+ self.writer(name, funcname, args[1:], kwargs)
195
+ else:
196
+ utils.sigtrap('FOO')
197
+ breakpoint()
198
+
199
+ def __call__(self, name, func = None):
200
+ if name in self.config:
201
+ if name.endswith('.call'):
202
+ f = functools.partial(self._write_call, name)
203
+ return functional.firstof(f, func) if func else f
204
+
205
+ elif name.endswith('.result'):
206
+ def write_result(obj):
207
+ self.writer(name, obj)
208
+ return obj
209
+
210
+ return functional.sequence(func, write_result) if func else write_result
211
+
212
+ elif name.endswith('.error'):
213
+ def write_error(*args):
214
+ self.writer(name, args)
215
+
216
+ return functional.firstof(write_error, func) if func else write_error
217
+
218
+ elif name.endswith('.event'):
219
+ def write_event(*args, **kwargs):
220
+ self.writer(name)
221
+
222
+ return functional.firstof(write_event, func) if func else write_event
223
+
224
+ elif name.endswith('.wrap'):
225
+ def wrapper(*args, **kwargs):
226
+ self.writer(name, 'enter')
227
+ try:
228
+ return func(*args, **kwargs)
229
+ finally:
230
+ self.writer(name, 'exit')
231
+
232
+ return wrapper
233
+
234
+ # elif name.endswith('.stack'):
235
+ # def write_event(*args, **kwargs):
236
+ # self.writer(name, utils.stacktrace())
237
+
238
+ # return functional.firstof(write_event, func) if func else write_event
239
+
240
+
241
+ return func
242
+
243
+
244
+ def write(self, name):
245
+ if name in self.config:
246
+ return functional.partial(self.writer, name)
247
+
248
+ def write_call(self, name, func = None):
249
+
250
+ def writer(*args, **kwargs):
251
+ self.writer(name, *(args + format_kwargs(kwargs)))
252
+
253
+ if name in self.config:
254
+ return functional.firstof(writer, func) if func else writer
255
+ else:
256
+ return func
257
+
258
+ def write_result(self, name, func):
259
+
260
+ writer = functional.partial(self.writer, name)
261
+
262
+ if name in self.config:
263
+ return functional.sequence(func, functional.side_effect(writer))
264
+ else:
265
+ return func
266
+
267
+ def event(self, name):
268
+ return functional.always(lambda: self.writer(name))
269
+
270
+ def event_before(self, name, func = None):
271
+ if name in self.config:
272
+ # def foo(*args, **kwargs):
273
+ # print("GRRRRR!!!!!")
274
+
275
+ # return functional.firstof(foo, func)
276
+ return functional.firstof(self.event(name), func)
277
+ else:
278
+ return func
279
+
280
+ def event_after(self, name, func):
281
+ if name in self.config:
282
+ return functional.sequence(func, functional.side_effect(self.event(name)))
283
+ else:
284
+ return func
@@ -0,0 +1,92 @@
1
+ # from retrace_utils import _intercept
2
+ import retracesoftware_utils as utils
3
+
4
+ def flags(cls : type):
5
+ f = utils.type_flags(cls)
6
+
7
+ s = set()
8
+
9
+ for name,value in utils.TypeFlags.items():
10
+ if (f & value) != 0:
11
+ s.add(name)
12
+ f = f & ~value
13
+
14
+ if f != 0:
15
+ s.add(f)
16
+
17
+ return s
18
+
19
+ class WithoutFlags:
20
+
21
+ def __init__(self, cls , *flags):
22
+ self.cls = cls
23
+ self.flags = flags
24
+
25
+ def __enter__(self):
26
+ self.saved = utils.type_flags(self.cls)
27
+ flags = self.saved
28
+
29
+ for flag in self.flags:
30
+ flags = flags & ~utils.TypeFlags[flag]
31
+
32
+ utils.set_type_flags(self.cls, flags)
33
+ return self.cls
34
+
35
+ def __exit__(self, *args):
36
+ utils.set_type_flags(self.cls, self.saved)
37
+
38
+ class WithFlags:
39
+
40
+ def __init__(self, cls , *flags):
41
+ self.cls = cls
42
+ self.flags = flags
43
+
44
+ def __enter__(self):
45
+ self.saved = utils.type_flags(self.cls)
46
+ flags = self.saved
47
+
48
+ for flag in self.flags:
49
+ flags |= utils.TypeFlags[flag]
50
+
51
+ utils.set_type_flags(self.cls, flags)
52
+ return self.cls
53
+
54
+ def __exit__(self, *args):
55
+ utils.set_type_flags(self.cls, self.saved)
56
+
57
+ def add_flag(cls : type, flag):
58
+ if isinstance(flag, str):
59
+ flag = utils.TypeFlags[flag]
60
+
61
+ flags = utils.type_flags(cls)
62
+ utils.set_type_flags(cls, flags | flag)
63
+
64
+ def modify(cls):
65
+ return WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE")
66
+
67
+
68
+ def type_has_feature(cls, flag):
69
+ if isinstance(flag, str):
70
+ flag = utils.TypeFlags[flag]
71
+ return (utils.type_flags(cls) & flag) != 0
72
+
73
+ def type_disallow_instantiation(cls : type):
74
+ return utils.type_flags(cls) & utils.Py_TPFLAGS_DISALLOW_INSTANTIATION
75
+
76
+ def is_method_descriptor(cls : type):
77
+ return (utils.type_flags(cls) & utils.TypeFlags["Py_TPFLAGS_METHOD_DESCRIPTOR"]) != 0
78
+
79
+ def extend_type(cls : type):
80
+ subclasses = list(cls.__subclasses__())
81
+
82
+ utils.make_extensible(cls)
83
+
84
+ print(f'extending: {cls}')
85
+
86
+ extended = utils.extend_type(cls)
87
+
88
+ for subclass in subclasses:
89
+ print(f"updating subclass: {subclass}")
90
+ subclass.__bases__ = tuple(map(lambda x: extended if x is cls else x, subclass.__bases__))
91
+
92
+ return extended