retracesoftware-proxy 0.2.18__py3-none-any.whl → 0.2.20__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 +119 -49
- retracesoftware/autoenable.py +4 -0
- retracesoftware/install/patchfindspec.py +14 -108
- retracesoftware/run.py +4 -6
- {retracesoftware_proxy-0.2.18.dist-info → retracesoftware_proxy-0.2.20.dist-info}/METADATA +1 -1
- {retracesoftware_proxy-0.2.18.dist-info → retracesoftware_proxy-0.2.20.dist-info}/RECORD +8 -16
- retracesoftware/install/install.py +0 -142
- retracesoftware/install/modulepatcher.py +0 -506
- retracesoftware/install/phases.py +0 -338
- retracesoftware/install/predicate.py +0 -92
- retracesoftware/install/record.py +0 -174
- retracesoftware/install/references.py +0 -66
- retracesoftware/install/replay.py +0 -102
- retracesoftware/replay.py +0 -105
- {retracesoftware_proxy-0.2.18.dist-info → retracesoftware_proxy-0.2.20.dist-info}/WHEEL +0 -0
- {retracesoftware_proxy-0.2.18.dist-info → retracesoftware_proxy-0.2.20.dist-info}/top_level.txt +0 -0
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import retracesoftware.functional as functional
|
|
2
|
-
import retracesoftware.utils as utils
|
|
3
|
-
from retracesoftware.proxy.thread import start_new_thread_wrapper
|
|
4
|
-
import threading
|
|
5
|
-
import importlib
|
|
6
|
-
from retracesoftware.install.typeutils import modify, WithFlags, WithoutFlags
|
|
7
|
-
import enum
|
|
8
|
-
|
|
9
|
-
from retracesoftware.install.typeutils import modify
|
|
10
|
-
from abc import ABC, abstractmethod
|
|
11
|
-
from typing import List, Dict, Any, Callable
|
|
12
|
-
import functools
|
|
13
|
-
|
|
14
|
-
def simple_phase(names, func):
|
|
15
|
-
"""
|
|
16
|
-
Simplest phase type
|
|
17
|
-
"""
|
|
18
|
-
return {name: func for name in names}
|
|
19
|
-
|
|
20
|
-
def simple_patch_phase(names, func):
|
|
21
|
-
"""
|
|
22
|
-
Simplest phase type
|
|
23
|
-
"""
|
|
24
|
-
def side_effect(obj):
|
|
25
|
-
func(obj)
|
|
26
|
-
return obj
|
|
27
|
-
|
|
28
|
-
return {name: side_effect for name in names}
|
|
29
|
-
|
|
30
|
-
Transform = Callable[[Any], Any]
|
|
31
|
-
Config = Any
|
|
32
|
-
|
|
33
|
-
Updater = Dict[str, Transform]
|
|
34
|
-
|
|
35
|
-
Phase = Callable[[Config], Updater]
|
|
36
|
-
|
|
37
|
-
Patcher = Dict[str, Phase]
|
|
38
|
-
|
|
39
|
-
def update_class(cls : type, actions : Updater):
|
|
40
|
-
...
|
|
41
|
-
|
|
42
|
-
def type_attributes(patcher, config):
|
|
43
|
-
result = {}
|
|
44
|
-
|
|
45
|
-
for typename, action_attrs in config.items():
|
|
46
|
-
|
|
47
|
-
def patch_type(cls):
|
|
48
|
-
with modify(cls):
|
|
49
|
-
for action, attrs in action_attrs.items():
|
|
50
|
-
update_class(cls, patcher[action](attrs))
|
|
51
|
-
return cls
|
|
52
|
-
|
|
53
|
-
result[typename] = patch_type
|
|
54
|
-
|
|
55
|
-
return result
|
|
56
|
-
|
|
57
|
-
def create_patcher(system) -> Patcher:
|
|
58
|
-
patcher = {}
|
|
59
|
-
|
|
60
|
-
def simple_patcher(func): lambda config: {name: func for name in config}
|
|
61
|
-
|
|
62
|
-
patcher.update({
|
|
63
|
-
'type_attributes': functools.partial(type_attributes, patcher),
|
|
64
|
-
'disable': simple_patcher(system.disable),
|
|
65
|
-
'patch_types': simple_patcher(system.patch_type),
|
|
66
|
-
|
|
67
|
-
})
|
|
68
|
-
return patcher
|
|
69
|
-
|
|
70
|
-
class TypeAttributesPhase(Phase):
|
|
71
|
-
|
|
72
|
-
def __init__(self, patcher):
|
|
73
|
-
super().__init__('type_attributes')
|
|
74
|
-
self.patcher = patcher
|
|
75
|
-
|
|
76
|
-
def patch_with_config(self, config, cls):
|
|
77
|
-
# print(f'TypeAttributesPhase: {config} {cls}')
|
|
78
|
-
if not isinstance(cls, type):
|
|
79
|
-
raise Exception("TODO")
|
|
80
|
-
|
|
81
|
-
with modify(cls):
|
|
82
|
-
for phase_name,values in config.items():
|
|
83
|
-
for attribute_name,func in self.patcher.find_phase(phase_name)(values).items():
|
|
84
|
-
utils.update(cls, attribute_name, func)
|
|
85
|
-
|
|
86
|
-
return cls
|
|
87
|
-
|
|
88
|
-
class Phase:
|
|
89
|
-
def __init__(self, system):
|
|
90
|
-
self.system = system
|
|
91
|
-
|
|
92
|
-
def simple_phase(func): return lambda names: {name: func for name in names}
|
|
93
|
-
|
|
94
|
-
self.phases = {
|
|
95
|
-
'disable': simple_phase(system.disable),
|
|
96
|
-
'type_attributes': lambda config: simple_phase(config, system.disable)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
def type_attributes(self, config, cls):
|
|
100
|
-
with modify(cls):
|
|
101
|
-
for action_name, attribute_names in config.items():
|
|
102
|
-
for attribute_name, func in self(action_name, attribute_names).items():
|
|
103
|
-
utils.update(cls, attribute_name, func)
|
|
104
|
-
|
|
105
|
-
return cls
|
|
106
|
-
|
|
107
|
-
def __call__(self, name, config):
|
|
108
|
-
match name:
|
|
109
|
-
case 'disable':
|
|
110
|
-
return {name: self.system.disable for name in config}
|
|
111
|
-
case 'type_attributes':
|
|
112
|
-
return {cls: partial(self.type_attributes, actions) for cls, actions in config.items()}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
phases = {
|
|
116
|
-
'disable': lambda system, config: simple_phase(config, system.disable),
|
|
117
|
-
'type_attributes': lambda system, config: simple_phase(config, system.disable)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
class Phase(ABC):
|
|
121
|
-
"""
|
|
122
|
-
A phase is function when called with system and config yields map of name->func
|
|
123
|
-
A patch phase
|
|
124
|
-
A phase has... keys... and action
|
|
125
|
-
"""
|
|
126
|
-
def __init__(self, name):
|
|
127
|
-
self.name = name
|
|
128
|
-
|
|
129
|
-
def patch(self, obj):
|
|
130
|
-
return obj
|
|
131
|
-
|
|
132
|
-
def patch_with_config(self, config, obj):
|
|
133
|
-
return obj
|
|
134
|
-
|
|
135
|
-
@abstractmethod
|
|
136
|
-
def apply(self, name : str, value : Any) -> Any:
|
|
137
|
-
pass
|
|
138
|
-
|
|
139
|
-
@abstractmethod
|
|
140
|
-
def __call__(self, namespace : Dict[str, Any]) -> None:
|
|
141
|
-
for name, value in namespace.items():
|
|
142
|
-
if
|
|
143
|
-
|
|
144
|
-
def __call__(self, config):
|
|
145
|
-
if isinstance(config, str):
|
|
146
|
-
return {config: self.patch}
|
|
147
|
-
elif isinstance(config, list):
|
|
148
|
-
return {key: self.patch for key in config}
|
|
149
|
-
elif isinstance(config, dict):
|
|
150
|
-
return {key: lambda obj: self.patch_with_config(value, obj) for key,value in config.items()}
|
|
151
|
-
else:
|
|
152
|
-
raise Exception(f'Unhandled config type: {config}')
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
class Proxy(Phase):
|
|
157
|
-
|
|
158
|
-
def proxy()
|
|
159
|
-
def __call__(self, name : str, value : Any) -> None:
|
|
160
|
-
if name in self.targets
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class Patch(Phase):
|
|
164
|
-
|
|
165
|
-
def __init__(self, targets, action):
|
|
166
|
-
self.targets = targets
|
|
167
|
-
|
|
168
|
-
def select(self, name : str) -> bool:
|
|
169
|
-
pass
|
|
170
|
-
|
|
171
|
-
def __call__(self, name : str, value : Any) -> None:
|
|
172
|
-
if name in self.targets
|
|
173
|
-
|
|
174
|
-
class SimplePhase(Phase):
|
|
175
|
-
def __init__(self, name, patch):
|
|
176
|
-
super().__init__(name)
|
|
177
|
-
self.patch = patch
|
|
178
|
-
|
|
179
|
-
class DisablePhase(Phase):
|
|
180
|
-
def __init__(self, thread_state):
|
|
181
|
-
super().__init__('disable')
|
|
182
|
-
self.thread_state = thread_state
|
|
183
|
-
|
|
184
|
-
def patch(self, obj):
|
|
185
|
-
print(f'Disabling: {obj}')
|
|
186
|
-
return self.thread_state.wrap('disabled', obj)
|
|
187
|
-
|
|
188
|
-
class TryPatchPhase(Phase):
|
|
189
|
-
def __init__(self, patcher):
|
|
190
|
-
super().__init__('try_patch')
|
|
191
|
-
self.patcher = patcher
|
|
192
|
-
|
|
193
|
-
def patch(self, obj):
|
|
194
|
-
try:
|
|
195
|
-
return self.patcher(obj)
|
|
196
|
-
except:
|
|
197
|
-
return obj
|
|
198
|
-
|
|
199
|
-
class PatchTypesPhase(Phase):
|
|
200
|
-
def __init__(self, patcher):
|
|
201
|
-
super().__init__('patch_types')
|
|
202
|
-
self.patcher = patcher
|
|
203
|
-
|
|
204
|
-
def patch(self, obj):
|
|
205
|
-
if not isinstance(obj, type):
|
|
206
|
-
raise Exception("TODO")
|
|
207
|
-
|
|
208
|
-
self.patcher(obj)
|
|
209
|
-
return obj
|
|
210
|
-
|
|
211
|
-
class BindPhase(Phase):
|
|
212
|
-
def __init__(self, bind):
|
|
213
|
-
super().__init__('bind')
|
|
214
|
-
self.bind = bind
|
|
215
|
-
|
|
216
|
-
def patch(self, obj):
|
|
217
|
-
# print(f'In BindPhase: {obj} {isinstance(obj, type)}')
|
|
218
|
-
if issubclass(obj, enum.Enum):
|
|
219
|
-
for member in obj:
|
|
220
|
-
# print(f'binding member: {member}')
|
|
221
|
-
# utils.sigtrap(member)
|
|
222
|
-
self.bind(member)
|
|
223
|
-
else:
|
|
224
|
-
self.bind(obj)
|
|
225
|
-
|
|
226
|
-
return obj
|
|
227
|
-
|
|
228
|
-
class ImmutableTypePhase(Phase):
|
|
229
|
-
|
|
230
|
-
def __init__(self, types):
|
|
231
|
-
super().__init__('immutable')
|
|
232
|
-
self.types = types
|
|
233
|
-
|
|
234
|
-
def patch(self, obj):
|
|
235
|
-
if not isinstance(obj, type):
|
|
236
|
-
raise Exception("TODO")
|
|
237
|
-
|
|
238
|
-
self.types.add(obj)
|
|
239
|
-
return obj
|
|
240
|
-
|
|
241
|
-
class PatchThreadPhase(Phase):
|
|
242
|
-
def __init__(self, thread_state, on_exit):
|
|
243
|
-
super().__init__('patch_start_new_thread')
|
|
244
|
-
self.thread_state = thread_state
|
|
245
|
-
self.on_exit = on_exit
|
|
246
|
-
|
|
247
|
-
def patch(self, obj):
|
|
248
|
-
return start_new_thread_wrapper(thread_state = self.thread_state,
|
|
249
|
-
on_exit = self.on_exit,
|
|
250
|
-
start_new_thread = obj)
|
|
251
|
-
|
|
252
|
-
def resolve(path):
|
|
253
|
-
module, sep, name = path.rpartition('.')
|
|
254
|
-
if module == None: module = 'builtins'
|
|
255
|
-
|
|
256
|
-
return getattr(importlib.import_module(module), name)
|
|
257
|
-
|
|
258
|
-
class WrapPhase(Phase):
|
|
259
|
-
def __init__(self):
|
|
260
|
-
super().__init__('wrap')
|
|
261
|
-
|
|
262
|
-
def patch_with_config(self, wrapper, obj):
|
|
263
|
-
return resolve(wrapper)(obj)
|
|
264
|
-
|
|
265
|
-
class PatchClassPhase(Phase):
|
|
266
|
-
def __init__(self):
|
|
267
|
-
super().__init__('patch_class')
|
|
268
|
-
|
|
269
|
-
def patch_with_config(self, config, cls):
|
|
270
|
-
|
|
271
|
-
patchers = utils.map_values(resolve, config)
|
|
272
|
-
|
|
273
|
-
assert cls is not None
|
|
274
|
-
|
|
275
|
-
with WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE"):
|
|
276
|
-
for name,func in patchers.items():
|
|
277
|
-
utils.update(cls, name, func)
|
|
278
|
-
|
|
279
|
-
return cls
|
|
280
|
-
|
|
281
|
-
class ProxyWrapPhase(Phase):
|
|
282
|
-
def __init__(self):
|
|
283
|
-
super().__init__('wrap_proxy')
|
|
284
|
-
|
|
285
|
-
def patch_with_config(self, config, cls):
|
|
286
|
-
|
|
287
|
-
patchers = utils.map_values(resolve, config)
|
|
288
|
-
|
|
289
|
-
def patch(proxytype):
|
|
290
|
-
for name,func in patchers.items():
|
|
291
|
-
utils.update(proxytype, name, func)
|
|
292
|
-
|
|
293
|
-
cls.__retrace_patch_proxy__ = patch
|
|
294
|
-
|
|
295
|
-
return cls
|
|
296
|
-
# return resolve(wrapper)(obj)
|
|
297
|
-
|
|
298
|
-
class TypeAttributesPhase(Phase):
|
|
299
|
-
|
|
300
|
-
def __init__(self, patcher):
|
|
301
|
-
super().__init__('type_attributes')
|
|
302
|
-
self.patcher = patcher
|
|
303
|
-
|
|
304
|
-
def patch_with_config(self, config, cls):
|
|
305
|
-
# print(f'TypeAttributesPhase: {config} {cls}')
|
|
306
|
-
if not isinstance(cls, type):
|
|
307
|
-
raise Exception("TODO")
|
|
308
|
-
|
|
309
|
-
with modify(cls):
|
|
310
|
-
for phase_name,values in config.items():
|
|
311
|
-
for attribute_name,func in self.patcher.find_phase(phase_name)(values).items():
|
|
312
|
-
utils.update(cls, attribute_name, func)
|
|
313
|
-
|
|
314
|
-
return cls
|
|
315
|
-
|
|
316
|
-
class PerThread(threading.local):
|
|
317
|
-
def __init__(self):
|
|
318
|
-
self.internal = utils.counter()
|
|
319
|
-
self.external = utils.counter()
|
|
320
|
-
|
|
321
|
-
class PatchHashPhase(Phase):
|
|
322
|
-
|
|
323
|
-
def __init__(self, thread_state):
|
|
324
|
-
super().__init__('patch_hash')
|
|
325
|
-
|
|
326
|
-
per_thread = PerThread()
|
|
327
|
-
|
|
328
|
-
self.hashfunc = thread_state.dispatch(
|
|
329
|
-
functional.constantly(None),
|
|
330
|
-
internal = functional.repeatedly(functional.partial(getattr, per_thread, 'internal')),
|
|
331
|
-
external = functional.repeatedly(functional.partial(getattr, per_thread, 'external')))
|
|
332
|
-
|
|
333
|
-
def patch(self, obj):
|
|
334
|
-
if not isinstance(obj, type):
|
|
335
|
-
raise Exception("TODO")
|
|
336
|
-
|
|
337
|
-
utils.patch_hash(cls = obj, hashfunc = self.hashfunc)
|
|
338
|
-
return obj
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
from retracesoftware_functional import *
|
|
2
|
-
import re
|
|
3
|
-
import pdb
|
|
4
|
-
import weakref
|
|
5
|
-
from types import MappingProxyType
|
|
6
|
-
|
|
7
|
-
class RegexPredicate:
|
|
8
|
-
__slots__ = ['regex']
|
|
9
|
-
|
|
10
|
-
def __init__(self, regex):
|
|
11
|
-
self.regex = re.compile(regex)
|
|
12
|
-
|
|
13
|
-
def __hash__(self):
|
|
14
|
-
return hash(self.regex)
|
|
15
|
-
|
|
16
|
-
def __eq__(self, other):
|
|
17
|
-
if not isinstance(other, RegexPredicate):
|
|
18
|
-
return NotImplemented # Allows Python to attempt `other.__eq__(self)`
|
|
19
|
-
return self.regex == other.regex
|
|
20
|
-
|
|
21
|
-
def __str__(self):
|
|
22
|
-
return repr(self.regex.pattern)
|
|
23
|
-
|
|
24
|
-
def __call__(self, obj):
|
|
25
|
-
return bool(re.fullmatch(self.regex, str(obj)))
|
|
26
|
-
|
|
27
|
-
def canonical(spec):
|
|
28
|
-
if isinstance(spec, str):
|
|
29
|
-
return ('regex', spec)
|
|
30
|
-
elif isinstance(spec, list):
|
|
31
|
-
return ('or',) + tuple(map(canonical, spec))
|
|
32
|
-
elif 'not' in spec:
|
|
33
|
-
return ('not', canonical(spec['not']))
|
|
34
|
-
elif 'and' in spec:
|
|
35
|
-
return ('and',) + tuple(map(canonical, spec['and']))
|
|
36
|
-
elif 'or' in spec:
|
|
37
|
-
return ('or',) + tuple(map(canonical, spec['or']))
|
|
38
|
-
else:
|
|
39
|
-
pdb.set_trace()
|
|
40
|
-
raise Exception()
|
|
41
|
-
|
|
42
|
-
class PredicateBuilder:
|
|
43
|
-
__slots__ = ['cache']
|
|
44
|
-
|
|
45
|
-
def __init__(self):
|
|
46
|
-
# self.cache = weakref.WeakKeyDictionary()
|
|
47
|
-
self.cache = {}
|
|
48
|
-
|
|
49
|
-
def build(self, spec):
|
|
50
|
-
if spec in self.cache:
|
|
51
|
-
return self.cache[spec]
|
|
52
|
-
|
|
53
|
-
if spec[0] == 'regex':
|
|
54
|
-
pred = RegexPredicate(spec[1])
|
|
55
|
-
elif spec[0] == 'not':
|
|
56
|
-
pred = not_predicate(self.build(spec[1]))
|
|
57
|
-
elif spec[0] == 'and':
|
|
58
|
-
pred = and_predicate(*map(self.build, spec[1:]))
|
|
59
|
-
elif spec[0] == 'or':
|
|
60
|
-
pred = or_predicate(*map(self.build, spec[1:]))
|
|
61
|
-
else:
|
|
62
|
-
raise Exception("TODO")
|
|
63
|
-
|
|
64
|
-
self.cache[spec] = pred
|
|
65
|
-
|
|
66
|
-
return pred
|
|
67
|
-
|
|
68
|
-
def __call__(self, spec):
|
|
69
|
-
return self.build(canonical(spec))
|
|
70
|
-
|
|
71
|
-
class ParamPredicate:
|
|
72
|
-
__slots__ = ['pred', 'index', 'param']
|
|
73
|
-
|
|
74
|
-
def __init__(self, signature, param, pred):
|
|
75
|
-
# params = inspect.signature(target).parameters
|
|
76
|
-
self.index = list(signature.keys()).index(param)
|
|
77
|
-
self.param = param
|
|
78
|
-
self.pred = pred
|
|
79
|
-
|
|
80
|
-
def __str__(self):
|
|
81
|
-
return f'ParamPredicate(pred = {self.pred}, index = {self.index}, param = {self.param})'
|
|
82
|
-
|
|
83
|
-
def find_arg(self, *args, **kwargs):
|
|
84
|
-
if self.param in kwargs:
|
|
85
|
-
return kwargs[self.param]
|
|
86
|
-
elif self.index < len(args):
|
|
87
|
-
return args[self.index]
|
|
88
|
-
else:
|
|
89
|
-
raise Exception(f'Cant get arg: {self.param}')
|
|
90
|
-
|
|
91
|
-
def __call__(self, *args, **kwargs):
|
|
92
|
-
return self.pred(self.find_arg(*args, **kwargs))
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
from retracesoftware.proxy import *
|
|
2
|
-
|
|
3
|
-
import retracesoftware.functional as functional
|
|
4
|
-
import retracesoftware.utils as utils
|
|
5
|
-
import retracesoftware.stream as stream
|
|
6
|
-
|
|
7
|
-
from retracesoftware.install.tracer import Tracer
|
|
8
|
-
from retracesoftware.install import globals
|
|
9
|
-
from retracesoftware.install.config import env_truthy
|
|
10
|
-
from retracesoftware.install.patchfindspec import patch_find_spec
|
|
11
|
-
|
|
12
|
-
import os
|
|
13
|
-
import sys
|
|
14
|
-
from datetime import datetime
|
|
15
|
-
import json
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
import shutil
|
|
18
|
-
|
|
19
|
-
# class ThreadSwitch:
|
|
20
|
-
# def __init__(self, id):
|
|
21
|
-
# self.id = id
|
|
22
|
-
|
|
23
|
-
# def __repr__(self):
|
|
24
|
-
# return f'ThreadSwitch<{self.id}>'
|
|
25
|
-
|
|
26
|
-
# def __str__(self):
|
|
27
|
-
# return f'ThreadSwitch<{self.id}>'
|
|
28
|
-
|
|
29
|
-
def code_workspace():
|
|
30
|
-
return {
|
|
31
|
-
'folders': [
|
|
32
|
-
{'path': '../..', 'name': 'Application'},
|
|
33
|
-
{'path': '.', 'name': 'Recording'}
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
def write_files(recording_path):
|
|
38
|
-
with open(recording_path / 'env', 'w') as f:
|
|
39
|
-
json.dump(dict(os.environ), f, indent=2)
|
|
40
|
-
|
|
41
|
-
with open(recording_path / 'exe', 'w') as f:
|
|
42
|
-
f.write(sys.executable)
|
|
43
|
-
|
|
44
|
-
with open(recording_path / 'cwd', 'w') as f:
|
|
45
|
-
f.write(os.getcwd())
|
|
46
|
-
|
|
47
|
-
with open(recording_path / 'cmd', 'w') as f:
|
|
48
|
-
json.dump(sys.orig_argv, f, indent=2)
|
|
49
|
-
|
|
50
|
-
with open(recording_path / 'replay.code-workspace', 'w') as f:
|
|
51
|
-
json.dump(code_workspace(), f, indent=2)
|
|
52
|
-
|
|
53
|
-
def create_recording_path(path):
|
|
54
|
-
expanded = datetime.now().strftime(path.format(pid = os.getpid()))
|
|
55
|
-
os.environ['RETRACE_RECORDING_PATH'] = expanded
|
|
56
|
-
return Path(expanded)
|
|
57
|
-
|
|
58
|
-
def tracing_level(config):
|
|
59
|
-
return os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
|
|
60
|
-
|
|
61
|
-
# def tracing_config(config):
|
|
62
|
-
# level = os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
|
|
63
|
-
# return config['tracing_levels'].get(level, {})
|
|
64
|
-
|
|
65
|
-
def merge_config(base, override):
|
|
66
|
-
if isinstance(base, dict) and isinstance(override, dict):
|
|
67
|
-
...
|
|
68
|
-
else:
|
|
69
|
-
return override
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def dump_as_json(path, obj):
|
|
74
|
-
with open(path, 'w') as f:
|
|
75
|
-
json.dump(obj, f, indent=2)
|
|
76
|
-
|
|
77
|
-
def record_system(thread_state, immutable_types, config):
|
|
78
|
-
|
|
79
|
-
recording_path = create_recording_path(config['recording_path'])
|
|
80
|
-
recording_path.mkdir(parents=True, exist_ok=True)
|
|
81
|
-
|
|
82
|
-
globals.recording_path = globals.RecordingPath(recording_path)
|
|
83
|
-
|
|
84
|
-
write_files(recording_path)
|
|
85
|
-
|
|
86
|
-
tracing_config = config['tracing_levels'].get(tracing_level(config), {})
|
|
87
|
-
|
|
88
|
-
dump_as_json(path = recording_path / 'tracing_config.json', obj = tracing_config)
|
|
89
|
-
|
|
90
|
-
def write_main_path(path):
|
|
91
|
-
with open(recording_path / 'mainscript', 'w') as f:
|
|
92
|
-
f.write(path)
|
|
93
|
-
|
|
94
|
-
run_path = recording_path / 'run'
|
|
95
|
-
run_path.mkdir(parents=True, exist_ok=False)
|
|
96
|
-
|
|
97
|
-
shutil.copy2(sys.argv[0], run_path)
|
|
98
|
-
|
|
99
|
-
python_path = recording_path / 'pythonpath'
|
|
100
|
-
python_path.mkdir(parents=True, exist_ok=False)
|
|
101
|
-
|
|
102
|
-
vscode = recording_path / '.vscode'
|
|
103
|
-
vscode.mkdir(parents=True, exist_ok=False)
|
|
104
|
-
|
|
105
|
-
# launch_json = {
|
|
106
|
-
# # // Use IntelliSense to learn about possible attributes.
|
|
107
|
-
# # // Hover to view descriptions of existing attributes.
|
|
108
|
-
# # // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
109
|
-
# "version": "0.2.0",
|
|
110
|
-
# "configurations": [
|
|
111
|
-
# {
|
|
112
|
-
# "name": "Python Debugger: Python File",
|
|
113
|
-
# "type": "debugpy",
|
|
114
|
-
# "request": "launch",
|
|
115
|
-
# "cwd": "${workspaceFolder}/run",
|
|
116
|
-
# "env": {
|
|
117
|
-
# "RETRACE_MODE": "replay"
|
|
118
|
-
# },
|
|
119
|
-
# "python": sys.executable,
|
|
120
|
-
# "program": sys.argv[0]
|
|
121
|
-
# }
|
|
122
|
-
# ]
|
|
123
|
-
# }
|
|
124
|
-
|
|
125
|
-
# dump_as_json(vscode / 'launch.json', launch_json)
|
|
126
|
-
|
|
127
|
-
workspace = {
|
|
128
|
-
"folders": [{ 'path': '.' }],
|
|
129
|
-
"settings": {
|
|
130
|
-
# This is the one that works reliably in .code-workspace files
|
|
131
|
-
# "python.defaultInterpreterPath":
|
|
132
|
-
"python.defaultInterpreterPath": sys.executable,
|
|
133
|
-
# "python.interpreterInfo": {
|
|
134
|
-
# "run": {"path": sys.executable}
|
|
135
|
-
# }
|
|
136
|
-
},
|
|
137
|
-
"launch": {
|
|
138
|
-
"version": "0.2.0",
|
|
139
|
-
"configurations": [
|
|
140
|
-
{
|
|
141
|
-
"justMyCode": False,
|
|
142
|
-
"name": "Python Debugger: Python File",
|
|
143
|
-
"type": "debugpy",
|
|
144
|
-
"request": "launch",
|
|
145
|
-
"cwd": "${workspaceFolder}/run",
|
|
146
|
-
"env": {
|
|
147
|
-
"RETRACE_RECORDING_PATH": "..",
|
|
148
|
-
"RETRACE_MODE": "replay"
|
|
149
|
-
},
|
|
150
|
-
# "python": ["${command:python.interpreterPath}"],
|
|
151
|
-
"python": sys.executable,
|
|
152
|
-
# "python": sys.executable,
|
|
153
|
-
"program": sys.argv[0]
|
|
154
|
-
}
|
|
155
|
-
]
|
|
156
|
-
},
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
dump_as_json(recording_path / 'replay.code-workspace', workspace)
|
|
160
|
-
|
|
161
|
-
copy_source = thread_state.wrap('disabled', patch_find_spec(cwd = Path(os.getcwd()), run_path = run_path, python_path = python_path))
|
|
162
|
-
|
|
163
|
-
for finder in sys.meta_path:
|
|
164
|
-
finder.find_spec = functional.sequence(finder.find_spec, functional.side_effect(copy_source))
|
|
165
|
-
|
|
166
|
-
return RecordProxySystem(thread_state = thread_state,
|
|
167
|
-
immutable_types = immutable_types,
|
|
168
|
-
tracing_config = tracing_config,
|
|
169
|
-
write_main_path = write_main_path,
|
|
170
|
-
path = recording_path / 'trace.bin',
|
|
171
|
-
tracecalls = env_truthy('RETRACE_ALL', False),
|
|
172
|
-
verbose = env_truthy('RETRACE_VERBOSE', False),
|
|
173
|
-
stacktraces = env_truthy('RETRACE_STACKTRACES', False),
|
|
174
|
-
magic_markers = env_truthy('RETRACE_MAGIC_MARKERS', False))
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import gc
|
|
2
|
-
import sys
|
|
3
|
-
import pdb
|
|
4
|
-
|
|
5
|
-
def references(obj):
|
|
6
|
-
refcnt = sys.getrefcount(obj) - 2
|
|
7
|
-
|
|
8
|
-
if refcnt == 1:
|
|
9
|
-
moddict = sys.modules[obj.__module__].__dict__
|
|
10
|
-
|
|
11
|
-
if obj.__name__ in moddict and moddict[obj.__name__] is obj:
|
|
12
|
-
return [moddict]
|
|
13
|
-
else:
|
|
14
|
-
refs = []
|
|
15
|
-
|
|
16
|
-
for mod in list(sys.modules.values()):
|
|
17
|
-
moddict = mod.__dict__
|
|
18
|
-
if obj.__name__ in moddict and moddict[obj.__name__] is obj:
|
|
19
|
-
refs.append(moddict)
|
|
20
|
-
|
|
21
|
-
if len(refs) == refcnt:
|
|
22
|
-
return refs
|
|
23
|
-
|
|
24
|
-
return gc.get_referrers(obj)
|
|
25
|
-
|
|
26
|
-
def replace(container, old, new):
|
|
27
|
-
if isinstance(container, dict):
|
|
28
|
-
if old in container:
|
|
29
|
-
elem = container.pop(old)
|
|
30
|
-
container[new] = elem
|
|
31
|
-
replace(container, old, new)
|
|
32
|
-
else:
|
|
33
|
-
for key,value in container.items():
|
|
34
|
-
if key != '__retrace_unproxied__' and value is old:
|
|
35
|
-
print(f'FOO replacing: {key} {old} {new}')
|
|
36
|
-
container[key] = new
|
|
37
|
-
|
|
38
|
-
elif isinstance(container, list):
|
|
39
|
-
for i,value in enumerate(container):
|
|
40
|
-
if value is old:
|
|
41
|
-
container[i] = new
|
|
42
|
-
|
|
43
|
-
elif isinstance(container, set):
|
|
44
|
-
container.remove(old)
|
|
45
|
-
container.add(new)
|
|
46
|
-
|
|
47
|
-
# elif isinstance(container, tuple):
|
|
48
|
-
# pdb.set_trace()
|
|
49
|
-
# else:
|
|
50
|
-
# pdb.set_trace()
|
|
51
|
-
# raise Exception('TODO')
|
|
52
|
-
|
|
53
|
-
def update(f, obj):
|
|
54
|
-
new = f(obj)
|
|
55
|
-
if new is not obj:
|
|
56
|
-
refs = references(obj)
|
|
57
|
-
for ref in refs:
|
|
58
|
-
replace(ref, obj, new)
|
|
59
|
-
|
|
60
|
-
return new
|
|
61
|
-
|
|
62
|
-
# import math
|
|
63
|
-
# import os
|
|
64
|
-
# print(references(math.sqrt))
|
|
65
|
-
# print(references(os.nice))
|
|
66
|
-
# print(references(os.open))
|