retracesoftware-proxy 0.1.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/config.json +382 -0
- retracesoftware/install/__init__.py +0 -0
- retracesoftware/install/config.py +53 -0
- retracesoftware/install/edgecases.py +220 -0
- retracesoftware/install/globals.py +17 -0
- retracesoftware/install/install.py +172 -0
- retracesoftware/install/patcher.py +494 -0
- retracesoftware/install/predicate.py +92 -0
- retracesoftware/install/record.py +128 -0
- retracesoftware/install/references.py +65 -0
- retracesoftware/install/replay.py +151 -0
- retracesoftware/install/tracer.py +144 -0
- retracesoftware/install/typeutils.py +72 -0
- retracesoftware/proxy/__init__.py +3 -0
- retracesoftware/proxy/gateway.py +100 -0
- retracesoftware/proxy/proxyfactory.py +357 -0
- retracesoftware/proxy/proxysystem.py +20 -0
- retracesoftware/proxy/proxytype.py +280 -0
- retracesoftware/proxy/record.py +133 -0
- retracesoftware/proxy/replay.py +110 -0
- retracesoftware/proxy/thread.py +11 -0
- retracesoftware_proxy-0.1.0.dist-info/METADATA +12 -0
- retracesoftware_proxy-0.1.0.dist-info/RECORD +26 -0
- retracesoftware_proxy-0.1.0.dist-info/WHEEL +5 -0
- retracesoftware_proxy-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
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))
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
import json
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
def write_files(recording_path):
|
|
17
|
+
with open(recording_path / 'env', 'w') as f:
|
|
18
|
+
json.dump(dict(os.environ), f, indent=2)
|
|
19
|
+
|
|
20
|
+
with open(recording_path / 'exe', 'w') as f:
|
|
21
|
+
f.write(sys.executable)
|
|
22
|
+
|
|
23
|
+
with open(recording_path / 'cwd', 'w') as f:
|
|
24
|
+
f.write(os.getcwd())
|
|
25
|
+
|
|
26
|
+
with open(recording_path / 'cmd', 'w') as f:
|
|
27
|
+
json.dump(sys.orig_argv, f, indent=2)
|
|
28
|
+
|
|
29
|
+
def create_recording_path(path):
|
|
30
|
+
expanded = datetime.now().strftime(path.format(pid = os.getpid()))
|
|
31
|
+
os.environ['RETRACE_RECORDING_PATH'] = expanded
|
|
32
|
+
return Path(expanded)
|
|
33
|
+
|
|
34
|
+
def tracing_level(config):
|
|
35
|
+
return os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
|
|
36
|
+
|
|
37
|
+
# def tracing_config(config):
|
|
38
|
+
# level = os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
|
|
39
|
+
# return config['tracing_levels'].get(level, {})
|
|
40
|
+
|
|
41
|
+
def thread_aware_writer(writer):
|
|
42
|
+
on_thread_switch = functional.sequence(utils.thread_id(), writer.handle('THREAD_SWITCH'))
|
|
43
|
+
return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = writer)
|
|
44
|
+
|
|
45
|
+
def record_system(thread_state, immutable_types, config):
|
|
46
|
+
|
|
47
|
+
recording_path = create_recording_path(config['recording_path'])
|
|
48
|
+
|
|
49
|
+
recording_path.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
|
|
51
|
+
globals.recording_path = globals.RecordingPath(recording_path)
|
|
52
|
+
|
|
53
|
+
write_files(recording_path)
|
|
54
|
+
|
|
55
|
+
tracing_config = config['tracing_levels'].get(tracing_level(config), {})
|
|
56
|
+
|
|
57
|
+
with open(recording_path / 'tracing_config.json', 'w') as f:
|
|
58
|
+
json.dump(tracing_config, f, indent=2)
|
|
59
|
+
|
|
60
|
+
writer = thread_aware_writer(stream.writer(path = recording_path / 'trace.bin'))
|
|
61
|
+
|
|
62
|
+
# os.register_at_fork(
|
|
63
|
+
# # before = self.thread_state.wrap('disabled', self.before_fork),
|
|
64
|
+
# before = before,
|
|
65
|
+
# after_in_parent = self.thread_state.wrap('disabled', self.after_fork_in_parent),
|
|
66
|
+
# after_in_child = self.thread_state.wrap('disabled', self.after_fork_in_child))
|
|
67
|
+
|
|
68
|
+
# self.writer = thread_state.wrap(
|
|
69
|
+
# desired_state = 'disabled',
|
|
70
|
+
# sticky = True,
|
|
71
|
+
# function = VerboseWriter(writer)) if verbose else writer
|
|
72
|
+
|
|
73
|
+
# def gc_start(self):
|
|
74
|
+
# self.before_gc = self.thread_state.value
|
|
75
|
+
# self.thread_state.value = 'external'
|
|
76
|
+
|
|
77
|
+
# def gc_end(self):
|
|
78
|
+
# self.thread_state.value = self.before_gc
|
|
79
|
+
# del self.before_gc
|
|
80
|
+
|
|
81
|
+
# def gc_hook(self, phase, info):
|
|
82
|
+
# if phase == 'start':
|
|
83
|
+
# self.gc_start()
|
|
84
|
+
|
|
85
|
+
# elif phase == 'stop':
|
|
86
|
+
# self.gc_end()
|
|
87
|
+
# gc.callbacks.append(self.gc_hook)
|
|
88
|
+
|
|
89
|
+
w = writer.handle('TRACE')
|
|
90
|
+
def trace_writer(*args):
|
|
91
|
+
print(f'Trace: {args}')
|
|
92
|
+
w(*args)
|
|
93
|
+
|
|
94
|
+
# print(f'Tracing config: {tracing_config(config)}')
|
|
95
|
+
|
|
96
|
+
tracer = Tracer(tracing_config, writer = trace_writer)
|
|
97
|
+
# tracer = Tracer(config = tracing_config(config), writer = writer.handle('TRACE'))
|
|
98
|
+
|
|
99
|
+
factory = RecordProxySystem(thread_state = thread_state,
|
|
100
|
+
immutable_types = immutable_types,
|
|
101
|
+
tracer = tracer,
|
|
102
|
+
writer = writer)
|
|
103
|
+
|
|
104
|
+
# factory.proxy_type = compose(factory.proxy_type, side_effect(...))
|
|
105
|
+
|
|
106
|
+
def on_patched(module_name, updates):
|
|
107
|
+
...
|
|
108
|
+
# for name, value in updates.items():
|
|
109
|
+
# if isinstance(value, type) and \
|
|
110
|
+
# issubclass(value, ExtendingProxy) and \
|
|
111
|
+
# not issubclass(value, InternalProxy):
|
|
112
|
+
|
|
113
|
+
# ref = writer.handle(ExtendingProxySpec(module = module_name, name = name))
|
|
114
|
+
# try:
|
|
115
|
+
# writer.add_type_serializer(cls = value, serializer = functional.constantly(ref))
|
|
116
|
+
# except:
|
|
117
|
+
# pass
|
|
118
|
+
|
|
119
|
+
factory.on_patched = on_patched
|
|
120
|
+
|
|
121
|
+
factory.sync = lambda function: functional.observer(on_call = functional.always(writer.handle('SYNC')), function = function)
|
|
122
|
+
|
|
123
|
+
# factory.set_thread_number = writer.thread_number
|
|
124
|
+
factory.tracer = tracer
|
|
125
|
+
|
|
126
|
+
factory.log = functional.partial(writer, 'LOG')
|
|
127
|
+
|
|
128
|
+
return factory
|
|
@@ -0,0 +1,65 @@
|
|
|
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 value is old:
|
|
35
|
+
container[key] = new
|
|
36
|
+
|
|
37
|
+
elif isinstance(container, list):
|
|
38
|
+
for i,value in enumerate(container):
|
|
39
|
+
if value is old:
|
|
40
|
+
container[i] = new
|
|
41
|
+
|
|
42
|
+
elif isinstance(container, set):
|
|
43
|
+
container.remove(old)
|
|
44
|
+
container.add(new)
|
|
45
|
+
|
|
46
|
+
# elif isinstance(container, tuple):
|
|
47
|
+
# pdb.set_trace()
|
|
48
|
+
# else:
|
|
49
|
+
# pdb.set_trace()
|
|
50
|
+
# raise Exception('TODO')
|
|
51
|
+
|
|
52
|
+
def update(f, obj):
|
|
53
|
+
new = f(obj)
|
|
54
|
+
if new is not obj:
|
|
55
|
+
refs = references(obj)
|
|
56
|
+
for ref in refs:
|
|
57
|
+
replace(ref, obj, new)
|
|
58
|
+
|
|
59
|
+
return new
|
|
60
|
+
|
|
61
|
+
# import math
|
|
62
|
+
# import os
|
|
63
|
+
# print(references(math.sqrt))
|
|
64
|
+
# print(references(os.nice))
|
|
65
|
+
# print(references(os.open))
|
|
@@ -0,0 +1,151 @@
|
|
|
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
|
+
from retracesoftware.install.tracer import Tracer
|
|
7
|
+
from retracesoftware.install import globals
|
|
8
|
+
|
|
9
|
+
import os, json, re, glob, weakref
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
def latest_from_pattern(pattern: str) -> str | None:
|
|
14
|
+
"""
|
|
15
|
+
Given a strftime-style filename pattern (e.g. "recordings/%Y%m%d_%H%M%S_%f"),
|
|
16
|
+
return the path to the most recent matching file, or None if no files exist.
|
|
17
|
+
"""
|
|
18
|
+
# Turn strftime placeholders into '*' for globbing
|
|
19
|
+
# (very simple replacement: %... -> *)
|
|
20
|
+
glob_pattern = re.sub(r"%[a-zA-Z]", "*", pattern)
|
|
21
|
+
|
|
22
|
+
# Find all matching files
|
|
23
|
+
candidates = glob.glob(glob_pattern)
|
|
24
|
+
if not candidates:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
# Derive the datetime format from the pattern (basename only)
|
|
28
|
+
base_pattern = os.path.basename(pattern)
|
|
29
|
+
|
|
30
|
+
def parse_time(path: str):
|
|
31
|
+
name = os.path.basename(path)
|
|
32
|
+
return datetime.strptime(name, base_pattern)
|
|
33
|
+
|
|
34
|
+
# Find the latest by parsed timestamp
|
|
35
|
+
latest = max(candidates, key=parse_time)
|
|
36
|
+
return latest
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def thread_aware_reader(reader):
|
|
41
|
+
def on_thread_switch():
|
|
42
|
+
...
|
|
43
|
+
|
|
44
|
+
return utils.threadawareproxy(on_thread_switch = on_thread_switch, target = reader)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def replay_system(thread_state, immutable_types, config):
|
|
48
|
+
|
|
49
|
+
recording_path = Path(latest_from_pattern(config['recording_path']))
|
|
50
|
+
|
|
51
|
+
print(f"replay running against path: {recording_path}")
|
|
52
|
+
|
|
53
|
+
globals.recording_path = globals.RecordingPath(recording_path)
|
|
54
|
+
|
|
55
|
+
assert recording_path.exists()
|
|
56
|
+
assert recording_path.is_dir()
|
|
57
|
+
|
|
58
|
+
with open(recording_path / "env", "r", encoding="utf-8") as f:
|
|
59
|
+
os.environ.update(json.load(f))
|
|
60
|
+
|
|
61
|
+
with open(recording_path / "tracing_config.json", "r", encoding="utf-8") as f:
|
|
62
|
+
tracing_config = json.load(f)
|
|
63
|
+
|
|
64
|
+
handler = None
|
|
65
|
+
|
|
66
|
+
# def create_stub_type(proxyspec):
|
|
67
|
+
# if isinstance(proxyspec, ExtendingProxySpec):
|
|
68
|
+
# mod = sys.modules[proxyspec.module]
|
|
69
|
+
# resolved = getattr(mod, proxyspec.name)
|
|
70
|
+
# assert issubclass(resolved, ExtendingProxy)
|
|
71
|
+
|
|
72
|
+
# return resolved
|
|
73
|
+
# # unproxied = resolved.__base__
|
|
74
|
+
# # print(f'base: {unproxied} has_generic_new: {utils.has_generic_new(unproxied)}')
|
|
75
|
+
# # print(f'base: {unproxied} has_generic_alloc: {utils.has_generic_alloc(unproxied)}')
|
|
76
|
+
# # utils.create_stub_object(resolved)
|
|
77
|
+
# # os._exit(1)
|
|
78
|
+
# # return resolved
|
|
79
|
+
|
|
80
|
+
# elif isinstance(proxyspec, WrappingProxySpec):
|
|
81
|
+
# nonlocal handler
|
|
82
|
+
# return proxyspec.create_type(handler)
|
|
83
|
+
# else:
|
|
84
|
+
# print(f'In create_stub_type!!!! {proxyspec}')
|
|
85
|
+
# os._exit(1)
|
|
86
|
+
|
|
87
|
+
# deserializer = functional.compose(pickle.loads, functional.when_instanceof(ProxySpec, create_stub_type))
|
|
88
|
+
|
|
89
|
+
reader = thread_aware_reader(stream.reader(path = recording_path / 'trace.bin'))
|
|
90
|
+
|
|
91
|
+
# reader = utils.threadawareproxy(on_thread_switch = ..., target = reader)
|
|
92
|
+
|
|
93
|
+
def readnext():
|
|
94
|
+
return reader()
|
|
95
|
+
# print(f'read: {obj}')
|
|
96
|
+
# return obj
|
|
97
|
+
|
|
98
|
+
lookup = weakref.WeakKeyDictionary()
|
|
99
|
+
|
|
100
|
+
# debug = debug_level(config)
|
|
101
|
+
|
|
102
|
+
# int_refs = {}
|
|
103
|
+
|
|
104
|
+
def checkpoint(replay):
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def read_required(required):
|
|
108
|
+
obj = readnext()
|
|
109
|
+
if obj != required:
|
|
110
|
+
print(f'Expected: {required} but got: {obj}')
|
|
111
|
+
for i in range(5):
|
|
112
|
+
readnext()
|
|
113
|
+
|
|
114
|
+
utils.sigtrap(None)
|
|
115
|
+
os._exit(1)
|
|
116
|
+
raise Exception(f'Expected: {required} but got: {obj}')
|
|
117
|
+
|
|
118
|
+
def trace_writer(name, *args):
|
|
119
|
+
print(f'Trace: {name} {args}')
|
|
120
|
+
|
|
121
|
+
read_required('TRACE')
|
|
122
|
+
read_required(name)
|
|
123
|
+
|
|
124
|
+
for arg in args:
|
|
125
|
+
read_required(arg)
|
|
126
|
+
|
|
127
|
+
tracer = Tracer(tracing_config, writer = trace_writer)
|
|
128
|
+
|
|
129
|
+
factory = ReplayProxySystem(thread_state = thread_state,
|
|
130
|
+
immutable_types = immutable_types,
|
|
131
|
+
tracer = tracer,
|
|
132
|
+
reader = reader)
|
|
133
|
+
|
|
134
|
+
# factory = replaying_proxy_factory(thread_state = thread_state,
|
|
135
|
+
# is_immutable_type = is_immutable_type,
|
|
136
|
+
# tracer = tracer,
|
|
137
|
+
# bind = reader.supply,
|
|
138
|
+
# next = next_result,
|
|
139
|
+
# checkpoint = checkpoint)
|
|
140
|
+
|
|
141
|
+
factory.tracer = tracer
|
|
142
|
+
|
|
143
|
+
def read_sync(): read_required('SYNC')
|
|
144
|
+
|
|
145
|
+
factory.sync = lambda function: functional.observer(on_call = functional.always(read_sync), function = function)
|
|
146
|
+
|
|
147
|
+
# factory.set_thread_number = writer.thread_number
|
|
148
|
+
|
|
149
|
+
factory.log = lambda message: checkpoint({'type': 'log_message', 'message': message})
|
|
150
|
+
|
|
151
|
+
return factory
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import retracesoftware.functional as functional
|
|
2
|
+
import retracesoftware_utils as utils
|
|
3
|
+
from retracesoftware.proxy.proxytype import Proxy
|
|
4
|
+
|
|
5
|
+
import types
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
def format_kwargs(kwargs):
|
|
9
|
+
result = []
|
|
10
|
+
for k, v in kwargs.items():
|
|
11
|
+
result.extend([f"{k} = ", v])
|
|
12
|
+
|
|
13
|
+
return tuple(result)
|
|
14
|
+
|
|
15
|
+
class Tracer:
|
|
16
|
+
def __init__(self, config, writer):
|
|
17
|
+
self.config = config
|
|
18
|
+
serialize = functional.walker(self.serialize)
|
|
19
|
+
self.writer = functional.mapargs(transform = serialize, function = writer)
|
|
20
|
+
|
|
21
|
+
def serialize(self, obj):
|
|
22
|
+
try:
|
|
23
|
+
if obj is None: return None
|
|
24
|
+
|
|
25
|
+
cls = functional.typeof(obj)
|
|
26
|
+
|
|
27
|
+
if issubclass(cls, (int, str)):
|
|
28
|
+
return obj
|
|
29
|
+
|
|
30
|
+
else:
|
|
31
|
+
return 'FOO!!!!'
|
|
32
|
+
|
|
33
|
+
if issubclass(cls, Proxy):
|
|
34
|
+
return f'<Proxy>'
|
|
35
|
+
|
|
36
|
+
if issubclass(cls, types.TracebackType):
|
|
37
|
+
return '<traceback>'
|
|
38
|
+
|
|
39
|
+
elif issubclass(cls, utils.wrapped_function):
|
|
40
|
+
return utils.unwrap(obj).__name__
|
|
41
|
+
|
|
42
|
+
elif hasattr(obj, '__module__') and hasattr(obj, '__name__'):
|
|
43
|
+
return f'{obj.__module__}.{obj.__name__}'
|
|
44
|
+
# elif isinstance(obj, type):
|
|
45
|
+
# return f'{obj.__module__}.{obj.__name__}'
|
|
46
|
+
else:
|
|
47
|
+
return '<other>'
|
|
48
|
+
# return f'instance type: {str(self.serialize(type(obj)))}'
|
|
49
|
+
except:
|
|
50
|
+
print("ERROR in tracer serialize!!!!")
|
|
51
|
+
os._exit(1)
|
|
52
|
+
|
|
53
|
+
def log(self, name, message):
|
|
54
|
+
if name in self.config:
|
|
55
|
+
self.writer(name, message)
|
|
56
|
+
|
|
57
|
+
def __call__(self, name, func = None):
|
|
58
|
+
if name in self.config:
|
|
59
|
+
if name.endswith('.call'):
|
|
60
|
+
def write_call(*args, **kwargs):
|
|
61
|
+
self.writer(name, args[0].__name__, args[1:], kwargs)
|
|
62
|
+
|
|
63
|
+
return functional.firstof(write_call, func) if func else write_call
|
|
64
|
+
|
|
65
|
+
elif name.endswith('.result'):
|
|
66
|
+
def write_result(obj):
|
|
67
|
+
self.writer(name, obj)
|
|
68
|
+
return obj
|
|
69
|
+
|
|
70
|
+
return functional.sequence(func, write_result) if func else write_result
|
|
71
|
+
|
|
72
|
+
elif name.endswith('.error'):
|
|
73
|
+
def write_error(*args):
|
|
74
|
+
self.writer(name, args)
|
|
75
|
+
|
|
76
|
+
return functional.firstof(write_error, func) if func else write_error
|
|
77
|
+
|
|
78
|
+
elif name.endswith('.event'):
|
|
79
|
+
def write_event(*args, **kwargs):
|
|
80
|
+
self.writer(name)
|
|
81
|
+
|
|
82
|
+
return functional.firstof(write_event, func) if func else write_event
|
|
83
|
+
|
|
84
|
+
elif name.endswith('.wrap'):
|
|
85
|
+
def wrapper(*args, **kwargs):
|
|
86
|
+
self.writer(name, 'enter')
|
|
87
|
+
try:
|
|
88
|
+
return func(*args, **kwargs)
|
|
89
|
+
finally:
|
|
90
|
+
self.writer(name, 'exit')
|
|
91
|
+
|
|
92
|
+
return wrapper
|
|
93
|
+
|
|
94
|
+
elif name.endswith('.stack'):
|
|
95
|
+
def write_event(*args, **kwargs):
|
|
96
|
+
self.writer(name, utils.stacktrace())
|
|
97
|
+
|
|
98
|
+
return functional.firstof(write_event, func) if func else write_event
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
return func
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def write(self, name):
|
|
105
|
+
if name in self.config:
|
|
106
|
+
return functional.partial(self.writer, name)
|
|
107
|
+
|
|
108
|
+
def write_call(self, name, func = None):
|
|
109
|
+
|
|
110
|
+
def writer(*args, **kwargs):
|
|
111
|
+
self.writer(name, *(args + format_kwargs(kwargs)))
|
|
112
|
+
|
|
113
|
+
if name in self.config:
|
|
114
|
+
return functional.firstof(writer, func) if func else writer
|
|
115
|
+
else:
|
|
116
|
+
return func
|
|
117
|
+
|
|
118
|
+
def write_result(self, name, func):
|
|
119
|
+
|
|
120
|
+
writer = functional.partial(self.writer, name)
|
|
121
|
+
|
|
122
|
+
if name in self.config:
|
|
123
|
+
return functional.compose(func, functional.side_effect(writer))
|
|
124
|
+
else:
|
|
125
|
+
return func
|
|
126
|
+
|
|
127
|
+
def event(self, name):
|
|
128
|
+
return functional.always(lambda: self.writer(name))
|
|
129
|
+
|
|
130
|
+
def event_before(self, name, func = None):
|
|
131
|
+
if name in self.config:
|
|
132
|
+
# def foo(*args, **kwargs):
|
|
133
|
+
# print("GRRRRR!!!!!")
|
|
134
|
+
|
|
135
|
+
# return functional.firstof(foo, func)
|
|
136
|
+
return functional.firstof(self.event(name), func)
|
|
137
|
+
else:
|
|
138
|
+
return func
|
|
139
|
+
|
|
140
|
+
def event_after(self, name, func):
|
|
141
|
+
if name in self.config:
|
|
142
|
+
return functional.compose(func, functional.side_effect(self.event(name)))
|
|
143
|
+
else:
|
|
144
|
+
return func
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
def add_flag(cls : type, flag):
|
|
39
|
+
if isinstance(flag, str):
|
|
40
|
+
flag = utils.TypeFlags[flag]
|
|
41
|
+
|
|
42
|
+
flags = utils.type_flags(cls)
|
|
43
|
+
utils.set_type_flags(cls, flags | flag)
|
|
44
|
+
|
|
45
|
+
def modify(cls):
|
|
46
|
+
return WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE")
|
|
47
|
+
|
|
48
|
+
def type_has_feature(cls, flag):
|
|
49
|
+
if isinstance(flag, str):
|
|
50
|
+
flag = utils.TypeFlags[flag]
|
|
51
|
+
return (utils.type_flags(cls) & flag) != 0
|
|
52
|
+
|
|
53
|
+
def type_disallow_instantiation(cls : type):
|
|
54
|
+
return utils.type_flags(cls) & utils.Py_TPFLAGS_DISALLOW_INSTANTIATION
|
|
55
|
+
|
|
56
|
+
def is_method_descriptor(cls : type):
|
|
57
|
+
return (utils.type_flags(cls) & utils.TypeFlags["Py_TPFLAGS_METHOD_DESCRIPTOR"]) != 0
|
|
58
|
+
|
|
59
|
+
def extend_type(cls : type):
|
|
60
|
+
subclasses = list(cls.__subclasses__())
|
|
61
|
+
|
|
62
|
+
utils.make_extensible(cls)
|
|
63
|
+
|
|
64
|
+
print(f'extending: {cls}')
|
|
65
|
+
|
|
66
|
+
extended = utils.extend_type(cls)
|
|
67
|
+
|
|
68
|
+
for subclass in subclasses:
|
|
69
|
+
print(f"updating subclass: {subclass}")
|
|
70
|
+
subclass.__bases__ = tuple(map(lambda x: extended if x is cls else x, subclass.__bases__))
|
|
71
|
+
|
|
72
|
+
return extended
|