retracesoftware-proxy 0.1.22__py3-none-any.whl → 0.2.1__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/__main__.py +266 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +19 -295
- retracesoftware/install/config.py +6 -0
- retracesoftware/install/edgecases.py +23 -1
- retracesoftware/install/patcher.py +93 -649
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/record.py +97 -37
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +55 -11
- retracesoftware/install/tracer.py +87 -77
- retracesoftware/modules.toml +384 -0
- retracesoftware/preload.txt +216 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxysystem.py +283 -64
- retracesoftware/proxy/proxytype.py +34 -10
- retracesoftware/proxy/record.py +97 -64
- retracesoftware/proxy/replay.py +62 -219
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +42 -19
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +375 -0
- retracesoftware/stackdifference.py +133 -0
- {retracesoftware_proxy-0.1.22.dist-info → retracesoftware_proxy-0.2.1.dist-info}/METADATA +2 -1
- retracesoftware_proxy-0.2.1.dist-info/RECORD +42 -0
- retracesoftware_proxy-0.1.22.dist-info/RECORD +0 -29
- {retracesoftware_proxy-0.1.22.dist-info → retracesoftware_proxy-0.2.1.dist-info}/WHEEL +0 -0
- {retracesoftware_proxy-0.1.22.dist-info → retracesoftware_proxy-0.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,678 +1,122 @@
|
|
|
1
|
-
import
|
|
2
|
-
# from retrace_utils import _intercept
|
|
3
|
-
# import _intercept
|
|
4
|
-
import types
|
|
5
|
-
import re
|
|
6
|
-
import sys
|
|
7
|
-
import pdb
|
|
8
|
-
import builtins
|
|
9
|
-
import types
|
|
10
|
-
import functools
|
|
11
|
-
import traceback
|
|
12
|
-
import functools
|
|
13
|
-
import importlib
|
|
14
|
-
import gc
|
|
15
|
-
import os
|
|
16
|
-
import atexit
|
|
17
|
-
import threading
|
|
18
|
-
from types import SimpleNamespace, MethodType
|
|
19
|
-
|
|
1
|
+
import enum
|
|
20
2
|
from retracesoftware.install.typeutils import modify
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from retracesoftware.install.config import load_config
|
|
25
|
-
|
|
26
|
-
from functools import wraps
|
|
27
|
-
|
|
28
|
-
import retracesoftware.functional as functional
|
|
3
|
+
from retracesoftware.install.replace import update
|
|
4
|
+
import threading
|
|
5
|
+
import sys
|
|
29
6
|
import retracesoftware.utils as utils
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from functools import partial
|
|
33
|
-
from retracesoftware.proxy.thread import start_new_thread_wrapper
|
|
34
|
-
|
|
35
|
-
# from retrace_utils.intercept import proxy
|
|
36
|
-
|
|
37
|
-
# from retrace_utils.intercept.typeutils import *
|
|
38
|
-
# from retrace_utils.intercept.proxytype import DynamicProxyFactory, proxytype
|
|
39
|
-
|
|
40
|
-
from retracesoftware.install.predicate import PredicateBuilder
|
|
41
|
-
|
|
42
|
-
def find_attr(mro, name):
|
|
43
|
-
for cls in mro:
|
|
44
|
-
if name in cls.__dict__:
|
|
45
|
-
return cls.__dict__[name]
|
|
46
|
-
|
|
47
|
-
def is_descriptor(obj):
|
|
48
|
-
return hasattr(obj, '__get__') or hasattr(obj, '__set__') or hasattr(obj, '__delete__')
|
|
49
|
-
|
|
50
|
-
# default_exclude = ['__class__', '__getattribute__', '__init_subclass__', '__dict__', '__del__', '__new__']
|
|
51
|
-
|
|
52
|
-
def is_function_type(cls):
|
|
53
|
-
return issubclass(cls, types.BuiltinFunctionType) or issubclass(cls, types.FunctionType)
|
|
54
|
-
|
|
55
|
-
def select_keys(keys, dict):
|
|
56
|
-
return {key: dict[key] for key in keys if key in dict}
|
|
57
|
-
|
|
58
|
-
def map_values(f, dict):
|
|
59
|
-
return {key: f(value) for key,value in dict.items()}
|
|
60
|
-
|
|
61
|
-
def common_keys(dict, *dicts):
|
|
62
|
-
common_keys = utils.set(dict)
|
|
63
|
-
for d in dicts:
|
|
64
|
-
common_keys &= d.keys()
|
|
65
|
-
|
|
66
|
-
assert isinstance(common_keys, utils.set)
|
|
67
|
-
|
|
68
|
-
return common_keys
|
|
69
|
-
|
|
70
|
-
def intersection(*dicts):
|
|
71
|
-
return { key: tuple(d[key] for d in dicts) for key in common_keys(*dicts) }
|
|
72
|
-
|
|
73
|
-
def intersection_apply(f, *dicts):
|
|
74
|
-
return map_values(lambda vals: f(*vals), intersection(*dicts))
|
|
7
|
+
import retracesoftware.functional as functional
|
|
8
|
+
import importlib
|
|
75
9
|
|
|
76
10
|
def resolve(path):
|
|
77
11
|
module, sep, name = path.rpartition('.')
|
|
78
|
-
|
|
12
|
+
|
|
13
|
+
if module is None:
|
|
14
|
+
module = 'builtins'
|
|
79
15
|
|
|
80
16
|
return getattr(importlib.import_module(module), name)
|
|
81
17
|
|
|
82
|
-
# def sync(spec, module_dict):
|
|
83
|
-
# for name,properties in spec.items():
|
|
84
|
-
|
|
85
|
-
# cls = module_dict[name]
|
|
86
|
-
|
|
87
|
-
# orig_init = cls.__init__
|
|
88
|
-
|
|
89
|
-
# def __init__(inst, *args, **kwargs):
|
|
90
|
-
# orig_init(inst, *args, **kwargs)
|
|
91
|
-
# for prop in properties:
|
|
92
|
-
# self.system.add_sync(getattr(inst, prop))
|
|
93
|
-
|
|
94
18
|
def replace(replacements, coll):
|
|
95
19
|
return map(lambda x: replacements.get(x, x), coll)
|
|
20
|
+
|
|
21
|
+
def patch_class(transforms, cls):
|
|
22
|
+
with modify(cls):
|
|
23
|
+
for attr,transform in transforms.items():
|
|
24
|
+
utils.update(cls, attr, resolve(transform))
|
|
96
25
|
|
|
97
|
-
|
|
98
|
-
if isinstance(container, dict):
|
|
99
|
-
if old in container:
|
|
100
|
-
elem = container.pop(old)
|
|
101
|
-
container[new] = elem
|
|
102
|
-
container_replace(container, old, new)
|
|
103
|
-
else:
|
|
104
|
-
for key,value in container.items():
|
|
105
|
-
if key != '__retrace_unproxied__' and value is old:
|
|
106
|
-
container[key] = new
|
|
107
|
-
return True
|
|
108
|
-
elif isinstance(container, list):
|
|
109
|
-
for i,value in enumerate(container):
|
|
110
|
-
if value is old:
|
|
111
|
-
container[i] = new
|
|
112
|
-
return True
|
|
113
|
-
elif isinstance(container, set):
|
|
114
|
-
container.remove(old)
|
|
115
|
-
container.add(new)
|
|
116
|
-
return True
|
|
117
|
-
else:
|
|
118
|
-
return False
|
|
119
|
-
|
|
120
|
-
def phase(func):
|
|
121
|
-
func.is_phase = True # add marker attribute
|
|
122
|
-
return func
|
|
123
|
-
|
|
124
|
-
def patch(func):
|
|
125
|
-
@wraps(func)
|
|
126
|
-
def wrapper(self, spec, mod_dict):
|
|
127
|
-
if isinstance(spec, str):
|
|
128
|
-
return wrapper(self, [spec], mod_dict)
|
|
129
|
-
elif isinstance(spec, list):
|
|
130
|
-
res = {}
|
|
131
|
-
for name in spec:
|
|
132
|
-
if name in mod_dict:
|
|
133
|
-
value = func(self, mod_dict[name])
|
|
134
|
-
if value is not None:
|
|
135
|
-
res[name] = value
|
|
136
|
-
return res
|
|
137
|
-
elif isinstance(spec, dict):
|
|
138
|
-
# return {name: func(self, mod_dict[name], value) for name, value in spec.items() if name in mod_dict}
|
|
139
|
-
res = {}
|
|
140
|
-
for name,value in spec.items():
|
|
141
|
-
if name in mod_dict:
|
|
142
|
-
value = func(self, mod_dict[name], value)
|
|
143
|
-
if value is not None:
|
|
144
|
-
res[name] = value
|
|
145
|
-
return res
|
|
146
|
-
else:
|
|
147
|
-
raise Exception('TODO')
|
|
148
|
-
|
|
149
|
-
wrapper.is_phase = True
|
|
150
|
-
return wrapper
|
|
151
|
-
|
|
152
|
-
def superdict(cls):
|
|
153
|
-
result = {}
|
|
154
|
-
for cls in list(reversed(cls.__mro__))[1:]:
|
|
155
|
-
result.update(cls.__dict__)
|
|
156
|
-
|
|
157
|
-
return result
|
|
158
|
-
|
|
159
|
-
# def is_method_descriptor(obj):
|
|
160
|
-
# return isinstance(obj, types.FunctionType) or \
|
|
161
|
-
# (isinstance(obj, (types.WrapperDescriptorType, types.MethodDescriptorType)) and obj.__objclass__ != object)
|
|
162
|
-
|
|
163
|
-
def wrap_method_descriptors(wrapper, prefix, base):
|
|
164
|
-
slots = {"__slots__": () }
|
|
165
|
-
|
|
166
|
-
extended = type(f'{prefix}.{base.__module__}.{base.__name__}', (base,), {"__slots__": () })
|
|
167
|
-
|
|
168
|
-
blacklist = ['__getattribute__', '__hash__', '__del__']
|
|
169
|
-
|
|
170
|
-
for name,value in superdict(base).items():
|
|
171
|
-
if name not in blacklist:
|
|
172
|
-
if utils.is_method_descriptor(value):
|
|
173
|
-
setattr(extended, name, wrapper(value))
|
|
174
|
-
|
|
175
|
-
return extended
|
|
26
|
+
return cls
|
|
176
27
|
|
|
177
28
|
class PerThread(threading.local):
|
|
178
29
|
def __init__(self):
|
|
179
30
|
self.internal = utils.counter()
|
|
180
|
-
self.
|
|
181
|
-
|
|
182
|
-
class Patcher:
|
|
183
|
-
|
|
184
|
-
def __init__(self, thread_state, config, system,
|
|
185
|
-
immutable_types,
|
|
186
|
-
on_function_proxy = None,
|
|
187
|
-
debug_level = 0,
|
|
188
|
-
post_commit = None):
|
|
189
|
-
|
|
190
|
-
# validate(config)
|
|
191
|
-
system.set_thread_id(0)
|
|
192
|
-
# utils.set_thread_id(0)
|
|
193
|
-
self.thread_counter = system.sync(utils.counter(1))
|
|
194
|
-
# self.set_thread_number = set_thread_number
|
|
195
|
-
|
|
196
|
-
self.thread_state = thread_state
|
|
197
|
-
self.debug_level = debug_level
|
|
198
|
-
self.on_function_proxy = on_function_proxy
|
|
199
|
-
self.modules = config['modules']
|
|
200
|
-
self.immutable_types_set = immutable_types
|
|
201
|
-
self.predicate = PredicateBuilder()
|
|
202
|
-
self.system = system
|
|
203
|
-
self.type_attribute_filter = self.predicate(config['type_attribute_filter'])
|
|
204
|
-
self.post_commit = post_commit
|
|
205
|
-
self.exclude_paths = [re.compile(s) for s in config.get('exclude_paths', [])]
|
|
206
|
-
self.typepatcher = {}
|
|
207
|
-
|
|
208
|
-
per_thread = PerThread()
|
|
209
|
-
|
|
210
|
-
self.hashfunc = self.thread_state.dispatch(
|
|
211
|
-
functional.constantly(None),
|
|
212
|
-
internal = functional.repeatedly(functional.partial(getattr, per_thread, 'internal')),
|
|
213
|
-
external = functional.repeatedly(functional.partial(getattr, per_thread, 'external')))
|
|
214
|
-
|
|
215
|
-
def is_phase(name): return getattr(getattr(self, name, None), "is_phase", False)
|
|
216
|
-
|
|
217
|
-
self.phases = [(name, getattr(self, name)) for name in Patcher.__dict__.keys() if is_phase(name)]
|
|
218
|
-
|
|
219
|
-
def log(self, *args):
|
|
220
|
-
self.system.tracer.log(*args)
|
|
31
|
+
self.external = utils.counter()
|
|
221
32
|
|
|
222
|
-
|
|
223
|
-
for exclude in self.exclude_paths:
|
|
224
|
-
if exclude.match(str(path)) is not None:
|
|
225
|
-
# print(f'in path_predicate, excluding {path}')
|
|
226
|
-
return False
|
|
227
|
-
return True
|
|
33
|
+
def create_patcher(system):
|
|
228
34
|
|
|
229
|
-
|
|
35
|
+
patcher = {}
|
|
230
36
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
setattr(cls, method, resolve(transform)(getattr(cls, method)))
|
|
37
|
+
def foreach(func): return lambda config: {name: func for name in config}
|
|
38
|
+
def selector(func): return lambda config: {name: functional.partial(func, value) for name, value in config.items()}
|
|
234
39
|
|
|
235
|
-
|
|
236
|
-
spec = self.modules[cls.__module__]
|
|
40
|
+
def simple_patcher(func): return foreach(functional.side_effect(func))
|
|
237
41
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
42
|
+
def type_attributes(transforms, cls):
|
|
43
|
+
with modify(cls):
|
|
44
|
+
for action, attrs in transforms.items():
|
|
45
|
+
for attr,func in patcher[action](attrs).items():
|
|
46
|
+
utils.update(cls, attr, func)
|
|
47
|
+
return cls
|
|
242
48
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def proxyable(self, name, obj):
|
|
248
|
-
if name.startswith('__') and name.endswith('__'):
|
|
249
|
-
return False
|
|
250
|
-
|
|
251
|
-
if isinstance(obj, (str, int, dict, list, tuple)):
|
|
252
|
-
return False
|
|
253
|
-
|
|
254
|
-
if isinstance(obj, type):
|
|
255
|
-
return not issubclass(obj, BaseException) and obj not in self.immutable_types_set
|
|
49
|
+
def bind(obj):
|
|
50
|
+
if issubclass(obj, enum.Enum):
|
|
51
|
+
for member in obj:
|
|
52
|
+
system.bind(member)
|
|
256
53
|
else:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
@phase
|
|
260
|
-
def immutable_types(self, spec, mod_dict):
|
|
261
|
-
if isinstance(spec, str):
|
|
262
|
-
return self.immutable_types([spec], mod_dict)
|
|
263
|
-
|
|
264
|
-
for name in spec:
|
|
265
|
-
if name in mod_dict:
|
|
266
|
-
if isinstance(mod_dict[name], type):
|
|
267
|
-
assert isinstance(mod_dict[name], type)
|
|
268
|
-
self.immutable_types_set.add(mod_dict[name])
|
|
269
|
-
else:
|
|
270
|
-
raise Exception(f'Tried to add "{name}" - {mod_dict[name]} which isn\'t a type to immutable')
|
|
271
|
-
|
|
272
|
-
@patch
|
|
273
|
-
def proxy(self, value):
|
|
274
|
-
return self.system(value)
|
|
275
|
-
|
|
276
|
-
@phase
|
|
277
|
-
def proxy_all_except(self, spec, mod_dict):
|
|
278
|
-
|
|
279
|
-
all_except = set(spec)
|
|
280
|
-
|
|
281
|
-
def proxyable(name, value):
|
|
282
|
-
return name not in all_except and self.proxyable(name, value)
|
|
283
|
-
|
|
284
|
-
return {key: self.system(value) for key,value in mod_dict.items() if proxyable(key, value)}
|
|
285
|
-
|
|
286
|
-
@phase
|
|
287
|
-
def proxy_type_attributes(self, spec, mod_dict):
|
|
288
|
-
for classname, attributes in spec.items():
|
|
289
|
-
if classname in mod_dict:
|
|
290
|
-
cls = mod_dict[classname]
|
|
291
|
-
if isinstance(cls, type):
|
|
292
|
-
for name in attributes:
|
|
293
|
-
attr = find_attr(cls.__mro__, name)
|
|
294
|
-
if attr is not None and (callable(attr) or is_descriptor(attr)):
|
|
295
|
-
proxied = self.system(attr)
|
|
296
|
-
# proxied = self.proxy(attr)
|
|
297
|
-
|
|
298
|
-
with modify(cls):
|
|
299
|
-
setattr(cls, name, proxied)
|
|
300
|
-
else:
|
|
301
|
-
raise Exception(f"Cannot patch attributes for {cls.__module__}.{cls.__name__} as object is: {cls} and not a type")
|
|
302
|
-
|
|
303
|
-
@phase
|
|
304
|
-
def replace(self, spec, mod_dict):
|
|
305
|
-
return {key: resolve(value) for key,value in spec.items()}
|
|
306
|
-
|
|
307
|
-
@patch
|
|
308
|
-
def patch_start_new_thread(self, value):
|
|
309
|
-
return start_new_thread_wrapper(thread_state = self.thread_state,
|
|
310
|
-
on_exit = self.system.on_thread_exit,
|
|
311
|
-
start_new_thread = value)
|
|
312
|
-
|
|
313
|
-
# def start_new_thread(function, *args):
|
|
314
|
-
# # synchronized, replay shoudl yield correct number
|
|
315
|
-
# thread_id = self.thread_counter()
|
|
316
|
-
|
|
317
|
-
# def threadrunner(*args, **kwargs):
|
|
318
|
-
# nonlocal thread_id
|
|
319
|
-
# self.system.set_thread_id(thread_id)
|
|
320
|
-
|
|
321
|
-
# with self.thread_state.select('internal'):
|
|
322
|
-
# try:
|
|
323
|
-
# # if self.tracing:
|
|
324
|
-
# # FrameTracer.install(self.thread_state.dispatch(noop, internal = self.checkpoint))
|
|
325
|
-
# return function(*args, **kwargs)
|
|
326
|
-
# finally:
|
|
327
|
-
# print(f'exiting: {thread_id}')
|
|
328
|
-
|
|
329
|
-
# return value(threadrunner, *args)
|
|
330
|
-
|
|
331
|
-
# return self.thread_state.dispatch(value, internal = start_new_thread)
|
|
332
|
-
|
|
333
|
-
@phase
|
|
334
|
-
def wrappers(self, spec, mod_dict):
|
|
335
|
-
return intersection_apply(lambda path, value: resolve(path)(value), spec, mod_dict)
|
|
336
|
-
|
|
337
|
-
@patch
|
|
338
|
-
def patch_exec(self, exec):
|
|
339
|
-
|
|
340
|
-
def is_module(source, *args):
|
|
341
|
-
return isinstance(source, types.CodeType) and source.co_name == '<module>'
|
|
342
|
-
|
|
343
|
-
def after_exec(source, globals = None, locals = None):
|
|
344
|
-
self(sys.modules[globals['__name__']])
|
|
345
|
-
|
|
346
|
-
def first(x): return x[0]
|
|
347
|
-
|
|
348
|
-
def disable(func): return self.thread_state.wrap('disabled', func)
|
|
349
|
-
|
|
350
|
-
return self.thread_state.dispatch(
|
|
351
|
-
exec,
|
|
352
|
-
internal = functional.sequence(
|
|
353
|
-
functional.vector(exec, functional.when(is_module, disable(after_exec))), first))
|
|
354
|
-
|
|
355
|
-
# self.thread_state.wrap(desired_state = 'disabled', function = exec_wrapper)
|
|
356
|
-
|
|
357
|
-
@patch
|
|
358
|
-
def sync_types(self, value):
|
|
359
|
-
return wrap_method_descriptors(self.system.sync, "retrace", value)
|
|
360
|
-
|
|
361
|
-
@phase
|
|
362
|
-
def with_state_recursive(self, spec, mod_dict):
|
|
363
|
-
|
|
364
|
-
updates = {}
|
|
365
|
-
|
|
366
|
-
for state,elems in spec.items():
|
|
367
|
-
|
|
368
|
-
def wrap(obj):
|
|
369
|
-
return functional.recurive_wrap_function(
|
|
370
|
-
functional.partial(self.thread_state.wrap, state),
|
|
371
|
-
obj)
|
|
372
|
-
|
|
373
|
-
updates.update(map_values(wrap, select_keys(elems, mod_dict)))
|
|
374
|
-
|
|
375
|
-
return updates
|
|
376
|
-
|
|
377
|
-
@phase
|
|
378
|
-
def methods_with_state(self, spec, mod_dict):
|
|
379
|
-
|
|
380
|
-
# updates = {}
|
|
381
|
-
|
|
382
|
-
def update(cls, name, f):
|
|
383
|
-
setattr(cls, name, f(getattr(cls, name)))
|
|
384
|
-
|
|
385
|
-
for state,cls_methods in spec.items():
|
|
386
|
-
def wrap(obj):
|
|
387
|
-
assert callable(obj)
|
|
388
|
-
return self.thread_state.wrap(desired_state = state, function = obj)
|
|
389
|
-
|
|
390
|
-
for typename,methodnames in cls_methods.items():
|
|
391
|
-
cls = mod_dict[typename]
|
|
392
|
-
|
|
393
|
-
for methodname in methodnames:
|
|
394
|
-
update(cls, methodname, wrap)
|
|
395
|
-
|
|
396
|
-
return {}
|
|
397
|
-
|
|
398
|
-
@phase
|
|
399
|
-
def with_state(self, spec, mod_dict):
|
|
400
|
-
|
|
401
|
-
updates = {}
|
|
402
|
-
|
|
403
|
-
for state,elems in spec.items():
|
|
54
|
+
system.bind(obj)
|
|
404
55
|
|
|
405
|
-
|
|
406
|
-
|
|
56
|
+
def add_immutable_type(obj):
|
|
57
|
+
if not isinstance(obj, type):
|
|
58
|
+
raise Exception("TODO")
|
|
59
|
+
system.immutable_types.add(obj)
|
|
60
|
+
return obj
|
|
407
61
|
|
|
408
|
-
|
|
62
|
+
per_thread = PerThread()
|
|
409
63
|
|
|
410
|
-
|
|
64
|
+
hashfunc = system.thread_state.dispatch(
|
|
65
|
+
functional.constantly(None),
|
|
66
|
+
internal = functional.repeatedly(functional.partial(getattr, per_thread, 'internal')),
|
|
67
|
+
external = functional.repeatedly(functional.partial(getattr, per_thread, 'external')))
|
|
411
68
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
69
|
+
def patch_hash(obj):
|
|
70
|
+
if not isinstance(obj, type):
|
|
71
|
+
raise Exception("TODO")
|
|
415
72
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
def first(x): return x[0]
|
|
420
|
-
|
|
421
|
-
def disable(func): return self.thread_state.wrap('disabled', func)
|
|
73
|
+
utils.patch_hash(cls = obj, hashfunc = hashfunc)
|
|
74
|
+
return obj
|
|
422
75
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
then = func,
|
|
452
|
-
otherwise = self.thread_state.wrap('disabled', func))
|
|
453
|
-
|
|
454
|
-
@phase
|
|
455
|
-
def wrap(self, spec, mod_dict):
|
|
456
|
-
updates = {}
|
|
457
|
-
|
|
458
|
-
for path, wrapper_name in spec.items():
|
|
459
|
-
|
|
460
|
-
parts = path.split('.')
|
|
461
|
-
name = parts[0]
|
|
462
|
-
if name in mod_dict:
|
|
463
|
-
value = mod_dict[name]
|
|
464
|
-
assert not isinstance(value, utils.wrapped_function), \
|
|
465
|
-
f"value for key: {name} is already wrapped"
|
|
466
|
-
|
|
467
|
-
if len(parts) == 1:
|
|
468
|
-
updates[name] = resolve(wrapper_name)(value)
|
|
469
|
-
elif len(parts) == 2:
|
|
470
|
-
member = getattr(value, parts[1], None)
|
|
471
|
-
if member:
|
|
472
|
-
new_value = resolve(wrapper_name)(member)
|
|
473
|
-
setattr(value, parts[1], new_value)
|
|
474
|
-
else:
|
|
475
|
-
raise Exception('TODO')
|
|
476
|
-
|
|
477
|
-
return updates
|
|
478
|
-
|
|
479
|
-
def updates(self, spec, mod_dict):
|
|
480
|
-
updates = {}
|
|
481
|
-
|
|
482
|
-
for phase,func in self.phases:
|
|
483
|
-
if phase in spec:
|
|
484
|
-
self.log('install.module.phase', phase)
|
|
485
|
-
|
|
486
|
-
phase_updates = func(spec[phase], mod_dict | updates)
|
|
487
|
-
|
|
488
|
-
if phase_updates:
|
|
489
|
-
self.log('install.module.phase.results', list(phase_updates.keys()))
|
|
490
|
-
for name,value in phase_updates.items():
|
|
491
|
-
if value is not None:
|
|
492
|
-
updates[name] = value
|
|
493
|
-
# updates |= phase_updates
|
|
494
|
-
|
|
495
|
-
return updates
|
|
496
|
-
|
|
497
|
-
def configs(self, module):
|
|
498
|
-
for name,value in sys.modules.items():
|
|
499
|
-
if value is module and name in self.modules:
|
|
500
|
-
yield name
|
|
501
|
-
|
|
502
|
-
def patch_module_with_name(self, mod_name, module):
|
|
503
|
-
with self.disable:
|
|
504
|
-
|
|
505
|
-
assert self.thread_state.value == 'disabled'
|
|
506
|
-
|
|
507
|
-
self.log('install.module', mod_name)
|
|
508
|
-
|
|
509
|
-
# self.system.log(f'patching module: {mod_name}')
|
|
510
|
-
|
|
511
|
-
spec = self.modules.get(mod_name)
|
|
512
|
-
|
|
513
|
-
updates = self.updates(spec = spec, mod_dict = module.__dict__)
|
|
514
|
-
|
|
515
|
-
originals = select_keys(updates.keys(), module.__dict__)
|
|
516
|
-
|
|
517
|
-
module.__dict__.update(updates)
|
|
518
|
-
|
|
519
|
-
for name, value in originals.items():
|
|
520
|
-
for ref in gc.get_referrers(value):
|
|
521
|
-
if ref is not originals:
|
|
522
|
-
container_replace(container = ref, old = value, new = updates[name])
|
|
523
|
-
if isinstance(updates[name], type) and isinstance(value, type) and issubclass(updates[name], value):
|
|
524
|
-
for subclass in value.__subclasses__():
|
|
525
|
-
if subclass not in updates.values():
|
|
526
|
-
subclass.__bases__ = tuple(replace({value: updates[name]}, subclass.__bases__))
|
|
527
|
-
|
|
528
|
-
module.__retrace__ = originals
|
|
529
|
-
|
|
530
|
-
if self.post_commit:
|
|
531
|
-
self.post_commit(mod_name, updates)
|
|
532
|
-
|
|
533
|
-
def __call__(self, module):
|
|
534
|
-
|
|
535
|
-
if not hasattr(module, '__retrace__'):
|
|
536
|
-
|
|
537
|
-
configs = list(self.configs(module))
|
|
538
|
-
|
|
539
|
-
if len(configs) > 0:
|
|
540
|
-
if len(configs) > 1:
|
|
541
|
-
raise Exception(f'TODO')
|
|
542
|
-
else:
|
|
543
|
-
module.__retrace__ = None
|
|
544
|
-
try:
|
|
545
|
-
self.patch_module_with_name(configs[0], module)
|
|
546
|
-
except Exception as error:
|
|
547
|
-
raise Exception(f'Error patching module: {configs[0]}') from error
|
|
548
|
-
|
|
549
|
-
return module
|
|
550
|
-
|
|
551
|
-
def env_truthy(key, default=False):
|
|
552
|
-
value = os.getenv(key)
|
|
553
|
-
if value is None:
|
|
554
|
-
return default
|
|
555
|
-
return value.strip().lower() in ("1", "true", "yes", "on")
|
|
556
|
-
|
|
557
|
-
class ImmutableTypes(set):
|
|
558
|
-
def __init__(self, *args, **kwargs):
|
|
559
|
-
super().__init__(*args, **kwargs)
|
|
560
|
-
|
|
561
|
-
def __contains__(self, item):
|
|
562
|
-
assert isinstance(item, type)
|
|
563
|
-
|
|
564
|
-
if super().__contains__(item):
|
|
565
|
-
return True
|
|
566
|
-
|
|
567
|
-
for elem in self:
|
|
568
|
-
if issubclass(item, elem):
|
|
569
|
-
self.add(item)
|
|
570
|
-
return True
|
|
571
|
-
|
|
572
|
-
return False
|
|
573
|
-
|
|
574
|
-
def install(mode):
|
|
575
|
-
|
|
576
|
-
create_system = None
|
|
577
|
-
|
|
578
|
-
if mode == 'record':
|
|
579
|
-
create_system = record_system
|
|
580
|
-
elif mode == 'replay':
|
|
581
|
-
create_system = replay_system
|
|
582
|
-
else:
|
|
583
|
-
raise Exception(f'mode: {mode} unsupported')
|
|
584
|
-
|
|
585
|
-
importlib.import_module('locale')
|
|
586
|
-
importlib.import_module('calendar')
|
|
587
|
-
importlib.import_module('_strptime')
|
|
588
|
-
|
|
589
|
-
config = load_config('config.json')
|
|
590
|
-
|
|
591
|
-
states = [x for x in config['states'] if isinstance(x, str)]
|
|
592
|
-
|
|
593
|
-
thread_state = utils.ThreadState(*states)
|
|
594
|
-
|
|
595
|
-
immutable_types = ImmutableTypes()
|
|
596
|
-
|
|
597
|
-
if 'RETRACE_RECORDING_PATH' in os.environ:
|
|
598
|
-
config['recording_path'] = os.environ['RETRACE_RECORDING_PATH']
|
|
599
|
-
|
|
600
|
-
# immutable_types = ImmutableTypes(config)
|
|
601
|
-
config['verbose'] = env_truthy('RETRACE_VERBOSE')
|
|
602
|
-
|
|
603
|
-
system = create_system(thread_state = thread_state,
|
|
604
|
-
immutable_types = immutable_types,
|
|
605
|
-
config = config)
|
|
606
|
-
|
|
607
|
-
os.register_at_fork(before = system.before_fork,
|
|
608
|
-
after_in_parent = system.after_fork_in_parent,
|
|
609
|
-
after_in_child = system.after_fork_in_child)
|
|
610
|
-
|
|
611
|
-
patcher = Patcher(thread_state = thread_state,
|
|
612
|
-
config = config,
|
|
613
|
-
system = system,
|
|
614
|
-
post_commit = getattr(system, 'on_patched', None),
|
|
615
|
-
immutable_types = immutable_types)
|
|
616
|
-
|
|
617
|
-
system.on_proxytype = patcher.on_proxytype
|
|
618
|
-
|
|
619
|
-
def at_exit(): thread_state.value = 'disabled'
|
|
620
|
-
|
|
621
|
-
# print(f'MODULES: {list(sys.modules.keys())}')
|
|
622
|
-
|
|
623
|
-
importlib.invalidate_caches()
|
|
624
|
-
|
|
625
|
-
# with thread_state.select('internal'):
|
|
626
|
-
|
|
627
|
-
atexit.register(lambda: at_exit)
|
|
628
|
-
|
|
629
|
-
for key, module in list(sys.modules.items()):
|
|
630
|
-
if not key.startswith('retracesoftware'):
|
|
631
|
-
patcher(module)
|
|
632
|
-
|
|
633
|
-
for library in config.get('preload', []):
|
|
634
|
-
with thread_state.select('internal'):
|
|
635
|
-
importlib.import_module(library)
|
|
636
|
-
|
|
637
|
-
importlib.invalidate_caches()
|
|
638
|
-
# # if env_truthy('RETRACE_TRACE_CALLS'):
|
|
639
|
-
# # trace_calls(system = retracesystem)
|
|
640
|
-
|
|
641
|
-
# thread_state.value = 'internal'
|
|
642
|
-
|
|
643
|
-
# import threading
|
|
644
|
-
# threading.current_thread().retrace = system
|
|
645
|
-
|
|
646
|
-
threading.current_thread().__retrace__ = system
|
|
647
|
-
|
|
648
|
-
# original = atexit.register
|
|
649
|
-
|
|
650
|
-
# def atexit_register(function):
|
|
651
|
-
# return original(thread_state.wrap('internal', function))
|
|
652
|
-
|
|
653
|
-
def wrap_internal(func): return thread_state.wrap('internal', func)
|
|
654
|
-
|
|
655
|
-
atexit.register = \
|
|
656
|
-
thread_state.dispatch(atexit.register, internal = functional.sequence(wrap_internal, atexit.register))
|
|
657
|
-
|
|
658
|
-
def disable_after_main(frame):
|
|
659
|
-
if system.is_entry_frame(frame):
|
|
660
|
-
print('enabling retrace!!!!')
|
|
661
|
-
# utils.intercept_frame_eval(None)
|
|
662
|
-
thread_state.value = 'internal'
|
|
663
|
-
|
|
664
|
-
def on_return():
|
|
665
|
-
thread_state.value = 'disabled'
|
|
666
|
-
|
|
667
|
-
obj = SimpleNamespace()
|
|
668
|
-
obj.on_return = on_return
|
|
669
|
-
|
|
670
|
-
return obj
|
|
76
|
+
patcher.update({
|
|
77
|
+
'type_attributes': selector(type_attributes),
|
|
78
|
+
'patch_class': selector(patch_class),
|
|
79
|
+
'disable': foreach(system.disable_for),
|
|
80
|
+
'patch_types': simple_patcher(system.patch_type),
|
|
81
|
+
'proxy': foreach(system),
|
|
82
|
+
'bind': simple_patcher(bind),
|
|
83
|
+
'wrap': lambda config: {name: resolve(action) for name,action in config.items() },
|
|
84
|
+
'immutable': simple_patcher(add_immutable_type),
|
|
85
|
+
'patch_hash': simple_patcher(patch_hash),
|
|
86
|
+
})
|
|
87
|
+
return patcher
|
|
88
|
+
|
|
89
|
+
def patch_namespace(patcher, config, namespace, update_refs):
|
|
90
|
+
for phase_name, phase_config in config.items():
|
|
91
|
+
if phase_name in patcher:
|
|
92
|
+
for name,func in patcher[phase_name](phase_config).items():
|
|
93
|
+
if name in namespace:
|
|
94
|
+
# print(f"patching: {name}")
|
|
95
|
+
|
|
96
|
+
value = namespace[name]
|
|
97
|
+
new_value = func(value)
|
|
98
|
+
|
|
99
|
+
if value is not new_value:
|
|
100
|
+
namespace[name] = new_value
|
|
101
|
+
|
|
102
|
+
if update_refs:
|
|
103
|
+
update(value, new_value)
|
|
671
104
|
else:
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
105
|
+
print(phase_name)
|
|
106
|
+
utils.sigtrap('FOO1')
|
|
107
|
+
|
|
108
|
+
def patch_module(patcher, config, namespace, update_refs):
|
|
109
|
+
if '__name__' in namespace:
|
|
110
|
+
name = namespace['__name__']
|
|
111
|
+
if name in config:
|
|
112
|
+
patch_namespace(patcher, config = config[name], namespace=namespace, update_refs=update_refs)
|
|
113
|
+
|
|
114
|
+
def patch_imported_module(patcher, checkpoint, config, namespace, update_refs):
|
|
115
|
+
if '__name__' in namespace:
|
|
116
|
+
name = namespace['__name__']
|
|
117
|
+
checkpoint(f"importing module: {name}")
|
|
118
|
+
|
|
119
|
+
if name in config:
|
|
120
|
+
# print(f"Patching imported module: {name}")
|
|
121
|
+
# checkpoint(f"Patching imported module: {name}")
|
|
122
|
+
patch_namespace(patcher, config = config[name], namespace=namespace, update_refs=update_refs)
|