retracesoftware-proxy 0.1.5__py3-none-any.whl → 0.2.4__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 +285 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +19 -233
- retracesoftware/config.yaml +0 -0
- retracesoftware/install/config.py +6 -0
- retracesoftware/install/edgecases.py +23 -1
- retracesoftware/install/patcher.py +98 -513
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/record.py +111 -40
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +59 -11
- retracesoftware/install/tracer.py +171 -33
- retracesoftware/install/typeutils.py +20 -0
- retracesoftware/modules.toml +384 -0
- retracesoftware/preload.txt +216 -0
- retracesoftware/proxy/__init__.py +1 -1
- retracesoftware/proxy/globalref.py +31 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxysystem.py +328 -71
- retracesoftware/proxy/proxytype.py +90 -38
- retracesoftware/proxy/record.py +109 -119
- retracesoftware/proxy/replay.py +94 -188
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +82 -27
- retracesoftware/proxy/thread.py +64 -4
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +378 -0
- retracesoftware/stackdifference.py +133 -0
- {retracesoftware_proxy-0.1.5.dist-info → retracesoftware_proxy-0.2.4.dist-info}/METADATA +2 -1
- retracesoftware_proxy-0.2.4.dist-info/RECORD +42 -0
- retracesoftware_proxy-0.1.5.dist-info/RECORD +0 -27
- {retracesoftware_proxy-0.1.5.dist-info → retracesoftware_proxy-0.2.4.dist-info}/WHEEL +0 -0
- {retracesoftware_proxy-0.1.5.dist-info → retracesoftware_proxy-0.2.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from ast import dump
|
|
2
|
+
from math import e
|
|
3
|
+
import retracesoftware.stream as stream
|
|
4
|
+
import retracesoftware_utils as utils
|
|
5
|
+
from retracesoftware.proxy.startthread import thread_id
|
|
6
|
+
import gc
|
|
7
|
+
import sys
|
|
8
|
+
import itertools
|
|
9
|
+
import threading
|
|
10
|
+
|
|
11
|
+
class ResultMessage:
|
|
12
|
+
def __init__(self, result):
|
|
13
|
+
self.result = result
|
|
14
|
+
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return f'ResultMessage({self.result})'
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f'ResultMessage({self.result})'
|
|
20
|
+
|
|
21
|
+
class ErrorMessage:
|
|
22
|
+
def __init__(self, type, value):
|
|
23
|
+
self.type = type
|
|
24
|
+
self.value = value
|
|
25
|
+
|
|
26
|
+
def __call__(self):
|
|
27
|
+
utils.raise_exception(self.type, self.value)
|
|
28
|
+
|
|
29
|
+
class CallMessage:
|
|
30
|
+
def __init__(self, func, args, kwargs):
|
|
31
|
+
self.func = func
|
|
32
|
+
self.args = args
|
|
33
|
+
self.kwargs = kwargs
|
|
34
|
+
|
|
35
|
+
def __call__(self):
|
|
36
|
+
try:
|
|
37
|
+
self.func(*self.args, **self.kwargs)
|
|
38
|
+
except BaseException:
|
|
39
|
+
pass
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
class CheckpointMessage:
|
|
43
|
+
def __init__(self, value):
|
|
44
|
+
self.value = value
|
|
45
|
+
|
|
46
|
+
class GCStartMessage:
|
|
47
|
+
def __init__(self, value):
|
|
48
|
+
self.value = value
|
|
49
|
+
|
|
50
|
+
def __call__(self):
|
|
51
|
+
gc.collect(self.value)
|
|
52
|
+
|
|
53
|
+
def next_message(source):
|
|
54
|
+
message = source()
|
|
55
|
+
|
|
56
|
+
if message == 'CALL':
|
|
57
|
+
return CallMessage(source(), source(), source())
|
|
58
|
+
elif message == 'RESULT':
|
|
59
|
+
return ResultMessage(source())
|
|
60
|
+
elif message == 'ERROR':
|
|
61
|
+
return ErrorMessage(source(), source())
|
|
62
|
+
elif message == 'CHECKPOINT':
|
|
63
|
+
return CheckpointMessage(source())
|
|
64
|
+
elif message == 'ON_START_COLLECT':
|
|
65
|
+
return GCStartMessage(source())
|
|
66
|
+
else:
|
|
67
|
+
return message
|
|
68
|
+
|
|
69
|
+
def all_elements_same(t):
|
|
70
|
+
return len(set(t)) <= 1
|
|
71
|
+
|
|
72
|
+
def first(coll): return coll[0]
|
|
73
|
+
|
|
74
|
+
def common_prefix(*colls):
|
|
75
|
+
return list(map(first, itertools.takewhile(all_elements_same, zip(*colls))))
|
|
76
|
+
|
|
77
|
+
def print_stack(stack):
|
|
78
|
+
for filename, lineno in stack:
|
|
79
|
+
print(f'{filename}:{lineno}', file = sys.stderr)
|
|
80
|
+
|
|
81
|
+
def on_stack_difference(previous, record, replay):
|
|
82
|
+
|
|
83
|
+
common = common_prefix(previous, record, replay) if previous else common_prefix(record, replay)
|
|
84
|
+
|
|
85
|
+
if common:
|
|
86
|
+
print('Common root:')
|
|
87
|
+
print_stack(common)
|
|
88
|
+
|
|
89
|
+
if previous:
|
|
90
|
+
print('\nlast matching:')
|
|
91
|
+
print_stack(previous[len(common):])
|
|
92
|
+
|
|
93
|
+
print('\nrecord:')
|
|
94
|
+
print_stack(record[len(common):])
|
|
95
|
+
|
|
96
|
+
print('\nreplay:')
|
|
97
|
+
print_stack(replay[len(common):])
|
|
98
|
+
|
|
99
|
+
utils.sigtrap(None)
|
|
100
|
+
|
|
101
|
+
class MessageStream:
|
|
102
|
+
|
|
103
|
+
def __init__(self, thread_state, source, skip_weakref_callbacks, verbose):
|
|
104
|
+
self.source = stream.per_thread(source = source, thread = thread_id, timeout = 1000)
|
|
105
|
+
self.verbose = verbose
|
|
106
|
+
|
|
107
|
+
self.excludes = set([
|
|
108
|
+
MessageStream.__call__,
|
|
109
|
+
MessageStream.bind,
|
|
110
|
+
MessageStream.checkpoint,
|
|
111
|
+
MessageStream.on_stack,
|
|
112
|
+
MessageStream.read_required,
|
|
113
|
+
MessageStream.result.__wrapped__,
|
|
114
|
+
stream.stack,
|
|
115
|
+
])
|
|
116
|
+
|
|
117
|
+
self.threadlocal = threading.local()
|
|
118
|
+
self.thread_state = thread_state
|
|
119
|
+
self.stack_messages = set([])
|
|
120
|
+
self.skip_weakref_callbacks = skip_weakref_callbacks
|
|
121
|
+
|
|
122
|
+
def on_stack(self, record):
|
|
123
|
+
replay = stream.stack(self.excludes)
|
|
124
|
+
|
|
125
|
+
if replay != record:
|
|
126
|
+
on_stack_difference(self.threadlocal.stacktrace, record, replay)
|
|
127
|
+
|
|
128
|
+
self.threadlocal.stacktrace = record
|
|
129
|
+
|
|
130
|
+
def dump_and_exit(self):
|
|
131
|
+
with self.thread_state.select('disabled'):
|
|
132
|
+
if hasattr(self.threadlocal, 'stacktrace'):
|
|
133
|
+
print('Last matching stacktrace:')
|
|
134
|
+
print_stack(self.threadlocal.stacktrace)
|
|
135
|
+
|
|
136
|
+
print('Current stacktrace:')
|
|
137
|
+
print_stack(stream.stack(set()))
|
|
138
|
+
utils.sigtrap(None)
|
|
139
|
+
|
|
140
|
+
def checkpoint(self, obj):
|
|
141
|
+
if self.verbose:
|
|
142
|
+
print(f'checkpointing: {obj}')
|
|
143
|
+
|
|
144
|
+
next = self()
|
|
145
|
+
|
|
146
|
+
if isinstance(next, CheckpointMessage):
|
|
147
|
+
if obj != next.value:
|
|
148
|
+
print(f'CHECKPOINT, expected: {obj} but got {next.value}', file=sys.stderr)
|
|
149
|
+
self.dump_and_exit()
|
|
150
|
+
else:
|
|
151
|
+
print(f'expected CHECKPOINT message but got {next}', file=sys.stderr)
|
|
152
|
+
self.dump_and_exit()
|
|
153
|
+
|
|
154
|
+
def bind(self, obj):
|
|
155
|
+
|
|
156
|
+
next = self()
|
|
157
|
+
|
|
158
|
+
if isinstance(next, stream.Bind):
|
|
159
|
+
next.value(obj)
|
|
160
|
+
else:
|
|
161
|
+
print(f'expected BIND message but got {next}', file=sys.stderr)
|
|
162
|
+
self.dump_and_exit()
|
|
163
|
+
|
|
164
|
+
def read_required(self, required):
|
|
165
|
+
next = self()
|
|
166
|
+
|
|
167
|
+
if next != required:
|
|
168
|
+
print(f'expected {required} message but got {next}', file=sys.stderr)
|
|
169
|
+
self.dump_and_exit()
|
|
170
|
+
|
|
171
|
+
def consume_to(self, to):
|
|
172
|
+
message = next_message(self.source)
|
|
173
|
+
while message != to:
|
|
174
|
+
message = next_message(self.source)
|
|
175
|
+
|
|
176
|
+
def __call__(self):
|
|
177
|
+
with self.thread_state.select('disabled'):
|
|
178
|
+
while True:
|
|
179
|
+
message = next_message(self.source)
|
|
180
|
+
|
|
181
|
+
if isinstance(message, CallMessage):
|
|
182
|
+
message()
|
|
183
|
+
elif isinstance(message, stream.Stack):
|
|
184
|
+
self.on_stack(message.value)
|
|
185
|
+
elif isinstance(message, GCStartMessage):
|
|
186
|
+
message()
|
|
187
|
+
self.read_required('ON_END_COLLECT')
|
|
188
|
+
elif self.skip_weakref_callbacks and message == 'ON_WEAKREF_CALLBACK_START':
|
|
189
|
+
self.consume_to('ON_WEAKREF_CALLBACK_END')
|
|
190
|
+
else:
|
|
191
|
+
return message
|
|
192
|
+
|
|
193
|
+
@utils.striptraceback
|
|
194
|
+
def result(self):
|
|
195
|
+
|
|
196
|
+
message = self()
|
|
197
|
+
|
|
198
|
+
if isinstance(message, ResultMessage):
|
|
199
|
+
return message.result
|
|
200
|
+
elif isinstance(message, ErrorMessage):
|
|
201
|
+
message()
|
|
202
|
+
else:
|
|
203
|
+
print(f'expected RESULT or ERROR message but got {message}', file=sys.stderr)
|
|
204
|
+
self.dump_and_exit()
|
|
@@ -5,8 +5,16 @@ from retracesoftware.proxy.gateway import adapter_pair
|
|
|
5
5
|
from types import SimpleNamespace
|
|
6
6
|
from retracesoftware.proxy.proxytype import *
|
|
7
7
|
from retracesoftware.proxy.stubfactory import Stub
|
|
8
|
+
from retracesoftware.install.typeutils import modify, WithFlags, WithoutFlags
|
|
9
|
+
|
|
10
|
+
from retracesoftware.proxy.serializer import serializer
|
|
11
|
+
from retracesoftware.install.tracer import Tracer
|
|
8
12
|
import sys
|
|
9
13
|
import gc
|
|
14
|
+
import weakref
|
|
15
|
+
import enum
|
|
16
|
+
import functools
|
|
17
|
+
import re
|
|
10
18
|
|
|
11
19
|
class RetraceError(Exception):
|
|
12
20
|
pass
|
|
@@ -23,6 +31,12 @@ def maybe_proxy(proxytype):
|
|
|
23
31
|
utils.unwrap,
|
|
24
32
|
proxy(functional.memoize_one_arg(proxytype)))
|
|
25
33
|
|
|
34
|
+
class Patched:
|
|
35
|
+
__slots__ = ()
|
|
36
|
+
|
|
37
|
+
class RetraceBase:
|
|
38
|
+
pass
|
|
39
|
+
|
|
26
40
|
unproxy_execute = functional.mapargs(starting = 1,
|
|
27
41
|
transform = functional.walker(utils.try_unwrap),
|
|
28
42
|
function = functional.apply)
|
|
@@ -36,13 +50,86 @@ def resolve(obj):
|
|
|
36
50
|
def is_function_type(cls):
|
|
37
51
|
return cls in [types.BuiltinFunctionType, types.FunctionType]
|
|
38
52
|
|
|
53
|
+
method_types = (types.MethodDescriptorType,
|
|
54
|
+
types.WrapperDescriptorType,
|
|
55
|
+
types.FunctionType)
|
|
56
|
+
|
|
57
|
+
def is_instance_method(obj):
|
|
58
|
+
return isinstance(obj, method_types)
|
|
59
|
+
|
|
60
|
+
def get_all_subtypes(cls):
|
|
61
|
+
"""Recursively find all subtypes of a given class."""
|
|
62
|
+
subclasses = set(cls.__subclasses__())
|
|
63
|
+
for subclass in cls.__subclasses__():
|
|
64
|
+
subclasses.update(get_all_subtypes(subclass))
|
|
65
|
+
return subclasses
|
|
66
|
+
|
|
67
|
+
def cleanse(text):
|
|
68
|
+
pattern = r'0x[a-fA-F0-9]+'
|
|
69
|
+
return re.sub(pattern, '0x####', text)
|
|
70
|
+
|
|
71
|
+
excludes = [
|
|
72
|
+
"ABCMeta.__instancecheck__",
|
|
73
|
+
"ABCMeta.__subclasscheck__",
|
|
74
|
+
"Collection.__subclasshook__",
|
|
75
|
+
re.compile(r"WeakSet"),
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
def exclude(text):
|
|
79
|
+
for elem in excludes:
|
|
80
|
+
if isinstance(elem, str):
|
|
81
|
+
if elem == text: return True
|
|
82
|
+
else:
|
|
83
|
+
# breakpoint()
|
|
84
|
+
# if text.contains('WeakSet'):
|
|
85
|
+
|
|
86
|
+
if re.match(elem, text):
|
|
87
|
+
return True
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
def normalize_for_checkpoint(obj):
|
|
91
|
+
if isinstance(obj, types.FunctionType):
|
|
92
|
+
return obj.__qualname__
|
|
93
|
+
elif isinstance(obj, types.MethodType):
|
|
94
|
+
return obj.__qualname__
|
|
95
|
+
elif isinstance(obj, types.BuiltinFunctionType):
|
|
96
|
+
return obj.__name__
|
|
97
|
+
elif isinstance(obj, types.BuiltinMethodType):
|
|
98
|
+
return obj.__name__
|
|
99
|
+
elif isinstance(obj, str):
|
|
100
|
+
return obj
|
|
101
|
+
elif isinstance(obj, dict):
|
|
102
|
+
return {normalize_for_checkpoint(k): normalize_for_checkpoint(v) for k,v in obj.items()}
|
|
103
|
+
elif isinstance(obj, tuple):
|
|
104
|
+
return tuple(normalize_for_checkpoint(x) for x in obj)
|
|
105
|
+
elif isinstance(obj, list):
|
|
106
|
+
return [normalize_for_checkpoint(x) for x in obj]
|
|
107
|
+
elif isinstance(obj, int):
|
|
108
|
+
# try and filter out memory addresses
|
|
109
|
+
if obj > 1000000 or obj < -1000000:
|
|
110
|
+
return "XXXX"
|
|
111
|
+
else:
|
|
112
|
+
return int(obj)
|
|
113
|
+
elif isinstance(obj, float):
|
|
114
|
+
return obj
|
|
115
|
+
elif isinstance(obj, bool):
|
|
116
|
+
return obj
|
|
117
|
+
elif isinstance(obj, type):
|
|
118
|
+
return obj.__name__
|
|
119
|
+
elif isinstance(obj, enum.Enum):
|
|
120
|
+
return obj.name
|
|
121
|
+
elif isinstance(obj, enum.EnumMeta):
|
|
122
|
+
return obj.__name__
|
|
123
|
+
elif isinstance(obj, enum.EnumMember):
|
|
124
|
+
return obj.name
|
|
125
|
+
else:
|
|
126
|
+
return f"<object of type: {type(obj)}>"
|
|
127
|
+
|
|
39
128
|
class ProxySystem:
|
|
40
129
|
|
|
41
|
-
def bind(self, obj): pass
|
|
130
|
+
# def bind(self, obj): pass
|
|
42
131
|
|
|
43
|
-
def wrap_int_to_ext(self, obj):
|
|
44
|
-
return obj
|
|
45
|
-
# return functional.sequence(functional.side_effect(functional.repeatedly(gc.collect)), obj)
|
|
132
|
+
def wrap_int_to_ext(self, obj): return obj
|
|
46
133
|
|
|
47
134
|
def wrap_ext_to_int(self, obj): return obj
|
|
48
135
|
|
|
@@ -54,35 +141,68 @@ class ProxySystem:
|
|
|
54
141
|
|
|
55
142
|
def on_ext_error(self, err_type, err_value, err_traceback):
|
|
56
143
|
pass
|
|
144
|
+
|
|
145
|
+
def on_ext_call(self, func, *args, **kwargs):
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
# def stacktrace(self):
|
|
149
|
+
# self.tracer.stacktrace()
|
|
150
|
+
|
|
151
|
+
def set_thread_id(self, id):
|
|
152
|
+
utils.set_thread_id(id)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def ext_apply(self): return functional.apply
|
|
57
156
|
|
|
58
|
-
|
|
157
|
+
@property
|
|
158
|
+
def int_apply(self): return functional.apply
|
|
159
|
+
|
|
160
|
+
def wrap_weakref_callback(self, callback):
|
|
161
|
+
def when_internal(f):
|
|
162
|
+
return self.thread_state.dispatch(utils.noop, internal = f)
|
|
163
|
+
|
|
164
|
+
return utils.observer(
|
|
165
|
+
on_call = when_internal(self.on_weakref_callback_start),
|
|
166
|
+
on_result = when_internal(self.on_weakref_callback_end),
|
|
167
|
+
on_error = when_internal(self.on_weakref_callback_end),
|
|
168
|
+
function = callback)
|
|
169
|
+
|
|
170
|
+
def __init__(self, thread_state, immutable_types, tracer, traceargs):
|
|
59
171
|
|
|
172
|
+
self.patched_types = set()
|
|
60
173
|
self.thread_state = thread_state
|
|
61
174
|
self.fork_counter = 0
|
|
62
175
|
self.tracer = tracer
|
|
63
176
|
self.immutable_types = immutable_types
|
|
64
|
-
self.
|
|
65
|
-
|
|
66
|
-
def
|
|
67
|
-
return
|
|
177
|
+
self.base_to_patched = {}
|
|
178
|
+
|
|
179
|
+
def should_proxy_type(cls):
|
|
180
|
+
return cls is not object and \
|
|
181
|
+
not issubclass(cls, tuple(immutable_types)) and \
|
|
182
|
+
cls not in self.patched_types
|
|
68
183
|
|
|
69
|
-
|
|
184
|
+
should_proxy = functional.sequence(functional.typeof, functional.memoize_one_arg(should_proxy_type))
|
|
70
185
|
|
|
71
186
|
def proxyfactory(proxytype):
|
|
72
|
-
return functional.walker(functional.
|
|
187
|
+
return functional.walker(functional.when(should_proxy, maybe_proxy(proxytype)))
|
|
73
188
|
|
|
74
189
|
int_spec = SimpleNamespace(
|
|
75
|
-
apply = thread_state.wrap('internal',
|
|
190
|
+
apply = thread_state.wrap('internal', self.int_apply),
|
|
76
191
|
proxy = proxyfactory(thread_state.wrap('disabled', self.int_proxytype)),
|
|
77
192
|
on_call = tracer('proxy.int.call', self.on_int_call),
|
|
78
193
|
on_result = tracer('proxy.int.result'),
|
|
79
194
|
on_error = tracer('proxy.int.error'),
|
|
80
195
|
)
|
|
196
|
+
|
|
197
|
+
def trace_ext_call(func, *args, **kwargs):
|
|
198
|
+
self.on_ext_call(func, *args, **kwargs)
|
|
199
|
+
self.checkpoint(self.normalize_for_checkpoint({'function': func, 'args': args, 'kwargs': kwargs}))
|
|
81
200
|
|
|
82
201
|
ext_spec = SimpleNamespace(
|
|
83
|
-
apply = thread_state.wrap('external',
|
|
84
|
-
proxy = proxyfactory(thread_state.wrap('disabled', self.
|
|
85
|
-
|
|
202
|
+
apply = thread_state.wrap('external', self.ext_apply),
|
|
203
|
+
proxy = proxyfactory(thread_state.wrap('disabled', self.ext_proxytype)),
|
|
204
|
+
|
|
205
|
+
on_call = trace_ext_call if traceargs else self.on_ext_call,
|
|
86
206
|
on_result = self.on_ext_result,
|
|
87
207
|
on_error = self.on_ext_error,
|
|
88
208
|
)
|
|
@@ -92,13 +212,26 @@ class ProxySystem:
|
|
|
92
212
|
def gateway(name, internal = functional.apply, external = functional.apply):
|
|
93
213
|
default = tracer(name, unproxy_execute)
|
|
94
214
|
return thread_state.dispatch(default, internal = internal, external = external)
|
|
95
|
-
|
|
96
|
-
self.ext_handler =
|
|
97
|
-
self.int_handler =
|
|
215
|
+
|
|
216
|
+
self.ext_handler = thread_state.wrap('retrace', self.wrap_int_to_ext(int2ext))
|
|
217
|
+
self.int_handler = thread_state.wrap('retrace', self.wrap_ext_to_int(ext2int))
|
|
98
218
|
|
|
99
219
|
self.ext_dispatch = gateway('proxy.int.disabled.event', internal = self.ext_handler)
|
|
100
220
|
self.int_dispatch = gateway('proxy.ext.disabled.event', external = self.int_handler)
|
|
101
221
|
|
|
222
|
+
self.exclude_from_stacktrace(Tracer._write_call)
|
|
223
|
+
|
|
224
|
+
# if 'systrace' in tracer.config:
|
|
225
|
+
# func = thread_state.wrap(desired_state = 'disabled', function = tracer.systrace)
|
|
226
|
+
# func = self.thread_state.dispatch(lambda *args: None, internal = func)
|
|
227
|
+
# sys.settrace(func)
|
|
228
|
+
# self.on_new_patched = self.thread_state.dispatch(utils.noop,
|
|
229
|
+
# internal = self.on_new_ext_patched, external = self.on_new_int_patched)
|
|
230
|
+
# tracer.trace_calls(thread_state)
|
|
231
|
+
|
|
232
|
+
def disable_for(self, func):
|
|
233
|
+
return self.thread_state.wrap('disabled', func)
|
|
234
|
+
|
|
102
235
|
def new_child_path(self, path):
|
|
103
236
|
return path.parent / f'fork-{self.fork_counter}' / path.name
|
|
104
237
|
|
|
@@ -114,84 +247,208 @@ class ProxySystem:
|
|
|
114
247
|
self.thread_state.value = self.saved_thread_state
|
|
115
248
|
self.fork_counter += 1
|
|
116
249
|
|
|
117
|
-
def
|
|
250
|
+
def on_thread_exit(self, thread_id):
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
# def create_stub(self): return False
|
|
118
254
|
|
|
119
255
|
def int_proxytype(self, cls):
|
|
256
|
+
if cls is object:
|
|
257
|
+
breakpoint()
|
|
258
|
+
|
|
120
259
|
return dynamic_int_proxytype(
|
|
121
260
|
handler = self.int_dispatch,
|
|
122
261
|
cls = cls,
|
|
123
262
|
bind = self.bind)
|
|
263
|
+
|
|
264
|
+
def ext_proxytype(self, cls):
|
|
124
265
|
|
|
125
|
-
|
|
266
|
+
proxytype = dynamic_proxytype(handler = self.ext_dispatch, cls = cls)
|
|
267
|
+
proxytype.__retrace_source__ = 'external'
|
|
268
|
+
|
|
269
|
+
if issubclass(cls, Patched):
|
|
270
|
+
patched = cls
|
|
271
|
+
elif cls in self.base_to_patched:
|
|
272
|
+
patched = self.base_to_patched[cls]
|
|
273
|
+
else:
|
|
274
|
+
patched = None
|
|
275
|
+
|
|
276
|
+
assert patched == None or patched.__base__ is not object
|
|
277
|
+
|
|
278
|
+
if patched:
|
|
279
|
+
# breakpoint()
|
|
280
|
+
|
|
281
|
+
patcher = getattr(patched, '__retrace_patch_proxy__', None)
|
|
282
|
+
if patcher: patcher(proxytype)
|
|
283
|
+
|
|
284
|
+
# for key,value in patched.__dict__.items():
|
|
285
|
+
# if callable(value) and key not in ['__new__'] and not hasattr(proxytype, key):
|
|
286
|
+
# setattr(proxytype, key, value)
|
|
126
287
|
|
|
127
|
-
proxytype = dynamic_proxytype(
|
|
128
|
-
handler = self.ext_dispatch,
|
|
129
|
-
cls = cls)
|
|
130
|
-
if self.on_proxytype:
|
|
131
|
-
self.on_proxytype(proxytype)
|
|
132
|
-
|
|
133
288
|
return proxytype
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
289
|
+
|
|
290
|
+
def function_target(self, obj): return obj
|
|
291
|
+
|
|
292
|
+
def proxy_function(self, func, **kwargs):
|
|
293
|
+
if is_instance_method(func):
|
|
294
|
+
return self.thread_state.method_dispatch(func, **kwargs)
|
|
295
|
+
else:
|
|
296
|
+
f = self.thread_state.dispatch(func, **kwargs)
|
|
297
|
+
|
|
298
|
+
if isinstance(func, staticmethod):
|
|
299
|
+
return staticmethod(f)
|
|
300
|
+
elif isinstance(func, classmethod):
|
|
301
|
+
return classmethod(f)
|
|
302
|
+
else:
|
|
303
|
+
return f
|
|
304
|
+
|
|
305
|
+
def proxy_ext_function(self, func):
|
|
306
|
+
proxied = utils.wrapped_function(handler = self.ext_handler, target = func)
|
|
307
|
+
return self.proxy_function(func = func, internal = proxied)
|
|
308
|
+
|
|
309
|
+
def proxy_int_function(self, func):
|
|
310
|
+
proxied = utils.wrapped_function(handler = self.int_handler, target = func)
|
|
311
|
+
return self.proxy_function(func = func, external = proxied)
|
|
312
|
+
|
|
313
|
+
def proxy_ext_member(self, member):
|
|
314
|
+
return utils.wrapped_member(handler = self.ext_dispatch, target = member)
|
|
315
|
+
|
|
316
|
+
def proxy_int_member(self, member):
|
|
317
|
+
return utils.wrapped_member(handler = self.int_dispatch, target = member)
|
|
318
|
+
|
|
319
|
+
# def proxy__new__(self, *args, **kwargs):
|
|
320
|
+
# return self.ext_handler(*args, **kwargs)
|
|
321
|
+
|
|
322
|
+
# def on_new_ext_patched(self, obj):
|
|
323
|
+
# print(f'HWE!!!!!!!!!!! 2')
|
|
324
|
+
# print(f'HWE!!!!!!!!!!! 3')
|
|
325
|
+
# return id(obj)
|
|
326
|
+
|
|
327
|
+
# def on_new_int_patched(self, obj):
|
|
328
|
+
# return id(obj)
|
|
329
|
+
|
|
330
|
+
# def on_del_patched(self, ref):
|
|
331
|
+
# pass
|
|
332
|
+
|
|
333
|
+
# def create_from_external(self, obj):
|
|
334
|
+
# pass
|
|
335
|
+
# breakpoint()
|
|
336
|
+
|
|
337
|
+
def patch_type(self, cls):
|
|
338
|
+
|
|
339
|
+
# breakpoint()
|
|
142
340
|
|
|
143
|
-
def ext_proxytype(self, cls):
|
|
144
341
|
assert isinstance(cls, type)
|
|
145
|
-
if utils.is_extendable(cls):
|
|
146
|
-
return self.extend_type(cls)
|
|
147
|
-
else:
|
|
148
|
-
return instantiable_dynamic_proxytype(
|
|
149
|
-
handler = self.ext_dispatch,
|
|
150
|
-
cls = cls,
|
|
151
|
-
thread_state = self.thread_state,
|
|
152
|
-
create_stub = self.create_stub())
|
|
153
|
-
|
|
154
|
-
def extend_type(self, cls):
|
|
155
|
-
|
|
156
|
-
extended = extending_proxytype(
|
|
157
|
-
cls = cls,
|
|
158
|
-
base = Stub if self.create_stub() else cls,
|
|
159
|
-
thread_state = self.thread_state,
|
|
160
|
-
ext_handler = self.ext_dispatch,
|
|
161
|
-
int_handler = self.int_dispatch,
|
|
162
|
-
on_subclass_new = self.bind)
|
|
163
|
-
|
|
164
|
-
self.immutable_types.add(extended)
|
|
165
342
|
|
|
166
|
-
|
|
343
|
+
if issubclass(cls, BaseException): breakpoint()
|
|
344
|
+
|
|
345
|
+
assert not issubclass(cls, BaseException)
|
|
167
346
|
|
|
168
|
-
|
|
347
|
+
assert cls not in self.patched_types
|
|
169
348
|
|
|
170
|
-
|
|
171
|
-
|
|
349
|
+
self.patched_types.add(cls)
|
|
350
|
+
|
|
351
|
+
# if a method returns a patched object... what to do
|
|
352
|
+
# well if its a subclass, may need to return id as may have local state
|
|
353
|
+
# a subclass will have an associated id, should have been created via __new__
|
|
354
|
+
# if not a subclass, create empty proxy, what about object identity?
|
|
355
|
+
|
|
356
|
+
# track ALL creations ? override tp_alloc ?
|
|
357
|
+
# patch tp_alloc and tp_dealloc to call lifecycle object
|
|
358
|
+
# given a lifecycle, can attach to a class
|
|
359
|
+
# have one function attach_lifecycle, on_new returns token passed to on_del function
|
|
172
360
|
|
|
361
|
+
def proxy_attrs(cls, dict, proxy_function, proxy_member):
|
|
362
|
+
|
|
363
|
+
blacklist = ['__new__', '__getattribute__', '__del__', '__dict__']
|
|
364
|
+
|
|
365
|
+
for name, value in dict.items():
|
|
366
|
+
if name not in blacklist:
|
|
367
|
+
if type(value) in [types.MemberDescriptorType, types.GetSetDescriptorType]:
|
|
368
|
+
setattr(cls, name, proxy_member(value))
|
|
369
|
+
elif callable(value):
|
|
370
|
+
setattr(cls, name, proxy_function(value))
|
|
371
|
+
|
|
372
|
+
with WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE"):
|
|
373
|
+
|
|
374
|
+
proxy_attrs(
|
|
375
|
+
cls,
|
|
376
|
+
dict = superdict(cls),
|
|
377
|
+
proxy_function = self.proxy_ext_function,
|
|
378
|
+
proxy_member = self.proxy_ext_member)
|
|
379
|
+
|
|
380
|
+
on_alloc = self.thread_state.dispatch(
|
|
381
|
+
utils.noop,
|
|
382
|
+
internal = self.bind,
|
|
383
|
+
external = self.create_from_external)
|
|
384
|
+
|
|
385
|
+
utils.set_on_alloc(cls, on_alloc)
|
|
386
|
+
|
|
387
|
+
cls.__retrace_system__ = self
|
|
388
|
+
|
|
389
|
+
if utils.is_extendable(cls):
|
|
390
|
+
def init_subclass(cls, **kwargs):
|
|
391
|
+
|
|
392
|
+
self.patched_types.add(cls)
|
|
393
|
+
|
|
394
|
+
def proxy_function(obj):
|
|
395
|
+
return utils.wrapped_function(handler = self.int_handler, target = obj)
|
|
396
|
+
|
|
397
|
+
proxy_attrs(
|
|
398
|
+
cls,
|
|
399
|
+
dict = cls.__dict__,
|
|
400
|
+
proxy_function = self.proxy_int_function,
|
|
401
|
+
proxy_member = self.proxy_int_member)
|
|
402
|
+
|
|
403
|
+
cls.__init_subclass__ = classmethod(init_subclass)
|
|
404
|
+
|
|
405
|
+
for subtype in get_all_subtypes(cls):
|
|
406
|
+
init_subclass(subtype)
|
|
407
|
+
utils.set_on_alloc(subtype, on_alloc)
|
|
408
|
+
|
|
409
|
+
cls.__retrace__ = self
|
|
410
|
+
|
|
411
|
+
self.bind(cls)
|
|
412
|
+
|
|
413
|
+
return cls
|
|
414
|
+
|
|
415
|
+
# def is_entry_frame(self, frame):
|
|
416
|
+
# return frame.globals.get("__name__", None) == "__main__"
|
|
417
|
+
|
|
418
|
+
def proxy_value(self, obj):
|
|
419
|
+
utils.sigtrap('proxy_value')
|
|
420
|
+
|
|
421
|
+
proxytype = dynamic_proxytype(handler = self.ext_dispatch, cls = type(obj))
|
|
422
|
+
proxytype.__retrace_source__ = 'external'
|
|
423
|
+
|
|
424
|
+
return utils.create_wrapped(proxytype, obj)
|
|
425
|
+
|
|
173
426
|
def __call__(self, obj):
|
|
174
427
|
assert not isinstance(obj, BaseException)
|
|
175
428
|
assert not isinstance(obj, Proxy)
|
|
176
429
|
assert not isinstance(obj, utils.wrapped_function)
|
|
177
430
|
|
|
178
431
|
if type(obj) == type:
|
|
179
|
-
|
|
180
|
-
return obj
|
|
181
|
-
|
|
182
|
-
return self.ext_proxytype(obj)
|
|
432
|
+
return self.patch_type(obj)
|
|
183
433
|
|
|
184
434
|
elif type(obj) in self.immutable_types:
|
|
185
435
|
return obj
|
|
186
436
|
|
|
187
|
-
elif is_function_type(type(obj)):
|
|
188
|
-
|
|
189
|
-
return self.thread_state.dispatch(
|
|
190
|
-
obj,
|
|
191
|
-
internal = self.proxy_function(obj))
|
|
437
|
+
elif is_function_type(type(obj)):
|
|
438
|
+
return self.thread_state.dispatch(obj, internal = self.proxy_ext_function(obj))
|
|
192
439
|
|
|
440
|
+
elif type(obj) == types.ClassMethodDescriptorType:
|
|
441
|
+
func = self.thread_state.dispatch(obj, internal = self.proxy_ext_function(obj))
|
|
442
|
+
return classmethod(func)
|
|
193
443
|
else:
|
|
194
|
-
|
|
444
|
+
return self.proxy_value(obj)
|
|
445
|
+
|
|
446
|
+
# def write_trace(self, obj):
|
|
447
|
+
|
|
448
|
+
# exclude = set([
|
|
449
|
+
# "ABCMeta.__subclasscheck__"
|
|
450
|
+
# # "__subclasscheck__"
|
|
451
|
+
# ])
|
|
452
|
+
|
|
195
453
|
|
|
196
|
-
|
|
197
|
-
# raise Exception(f'object {obj} was not proxied as its not a extensible type and is not callable')
|
|
454
|
+
|