retracesoftware-proxy 0.0.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/__main__.py +266 -0
- retracesoftware/autoenable.py +53 -0
- retracesoftware/config.json +175 -0
- retracesoftware/config.yaml +0 -0
- retracesoftware/install/__init__.py +0 -0
- retracesoftware/install/config.py +59 -0
- retracesoftware/install/edgecases.py +242 -0
- retracesoftware/install/globals.py +17 -0
- retracesoftware/install/install.py +142 -0
- retracesoftware/install/patcher.py +122 -0
- retracesoftware/install/patchfindspec.py +117 -0
- retracesoftware/install/phases.py +338 -0
- retracesoftware/install/predicate.py +92 -0
- retracesoftware/install/record.py +174 -0
- retracesoftware/install/references.py +66 -0
- retracesoftware/install/replace.py +28 -0
- retracesoftware/install/replay.py +102 -0
- retracesoftware/install/tracer.py +284 -0
- retracesoftware/install/typeutils.py +92 -0
- retracesoftware/modules.toml +384 -0
- retracesoftware/proxy/__init__.py +3 -0
- retracesoftware/proxy/gateway.py +49 -0
- retracesoftware/proxy/globalref.py +31 -0
- retracesoftware/proxy/messagestream.py +204 -0
- retracesoftware/proxy/proxyfactory.py +357 -0
- retracesoftware/proxy/proxysystem.py +454 -0
- retracesoftware/proxy/proxytype.py +424 -0
- retracesoftware/proxy/record.py +211 -0
- retracesoftware/proxy/replay.py +138 -0
- retracesoftware/proxy/serializer.py +28 -0
- retracesoftware/proxy/startthread.py +40 -0
- retracesoftware/proxy/stubfactory.py +195 -0
- retracesoftware/proxy/thread.py +106 -0
- retracesoftware/replay.py +104 -0
- retracesoftware/run.py +373 -0
- retracesoftware/stackdifference.py +133 -0
- retracesoftware_proxy-0.0.0.dist-info/METADATA +8 -0
- retracesoftware_proxy-0.0.0.dist-info/RECORD +41 -0
- retracesoftware_proxy-0.0.0.dist-info/WHEEL +5 -0
- retracesoftware_proxy-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# from .proxytype import *
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from retracesoftware.install import globals
|
|
7
|
+
|
|
8
|
+
def recvfrom_into(target):
|
|
9
|
+
@functools.wraps(target)
|
|
10
|
+
def wrapper(self, buffer, nbytes = 0, flags = 0):
|
|
11
|
+
data, address = self.recvfrom(len(buffer) if nbytes == 0 else nbytes, flags)
|
|
12
|
+
buffer[0:len(data)] = data
|
|
13
|
+
return len(data), address
|
|
14
|
+
return wrapper
|
|
15
|
+
|
|
16
|
+
def recv_into(target):
|
|
17
|
+
@functools.wraps(target)
|
|
18
|
+
def wrapper(self, buffer, nbytes = 0, flags = 0):
|
|
19
|
+
data = self.recv(len(buffer) if nbytes == 0 else nbytes, flags)
|
|
20
|
+
buffer[0:len(data)] = data
|
|
21
|
+
return len(data)
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
def recvmsg_into(target):
|
|
25
|
+
@functools.wraps(target)
|
|
26
|
+
def wrapper(self, buffers, ancbufsize = 0, flags = 0):
|
|
27
|
+
raise NotImplementedError('TODO')
|
|
28
|
+
return wrapper
|
|
29
|
+
|
|
30
|
+
def read(target):
|
|
31
|
+
@functools.wraps(target)
|
|
32
|
+
def wrapper(self, *args):
|
|
33
|
+
# super_type = super(type(self), self)
|
|
34
|
+
|
|
35
|
+
if len(args) == 0:
|
|
36
|
+
return target(self)
|
|
37
|
+
else:
|
|
38
|
+
buflen = args[0]
|
|
39
|
+
|
|
40
|
+
# pdb.set_trace()
|
|
41
|
+
|
|
42
|
+
data = target(self, buflen)
|
|
43
|
+
|
|
44
|
+
if len(args) == 1:
|
|
45
|
+
return data
|
|
46
|
+
else:
|
|
47
|
+
buffer = args[1]
|
|
48
|
+
|
|
49
|
+
buffer[0:len(data)] = data
|
|
50
|
+
|
|
51
|
+
return len(data)
|
|
52
|
+
return wrapper
|
|
53
|
+
|
|
54
|
+
def write(target):
|
|
55
|
+
@functools.wraps(target)
|
|
56
|
+
def wrapper(self, byteslike):
|
|
57
|
+
return target(byteslike.tobytes())
|
|
58
|
+
|
|
59
|
+
return wrapper
|
|
60
|
+
|
|
61
|
+
def readinto(target):
|
|
62
|
+
@functools.wraps(target)
|
|
63
|
+
def wrapper(self, buffer):
|
|
64
|
+
bytes = self.read(buffer.nbytes)
|
|
65
|
+
buffer[:len(bytes)] = bytes
|
|
66
|
+
return len(bytes)
|
|
67
|
+
return wrapper
|
|
68
|
+
|
|
69
|
+
typewrappers = {
|
|
70
|
+
'_socket': {
|
|
71
|
+
'socket': {
|
|
72
|
+
'recvfrom_into': recvfrom_into,
|
|
73
|
+
'recv_into': recv_into,
|
|
74
|
+
'recvmsg_into': recvmsg_into
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
'_ssl': {
|
|
78
|
+
'_SSLSocket': {
|
|
79
|
+
'read': read,
|
|
80
|
+
# 'write': write
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
'io': {
|
|
84
|
+
'FileIO': {
|
|
85
|
+
'readinto': readinto
|
|
86
|
+
},
|
|
87
|
+
'BufferedReader': {
|
|
88
|
+
'readinto': readinto
|
|
89
|
+
},
|
|
90
|
+
'BufferedRandom': {
|
|
91
|
+
'readinto': readinto
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def patchtype(module, name, cls : type):
|
|
97
|
+
if module in typewrappers:
|
|
98
|
+
if name in typewrappers[module]:
|
|
99
|
+
for method,patcher in typewrappers[module][name].items():
|
|
100
|
+
setattr(cls, method, patcher(getattr(cls, method)))
|
|
101
|
+
|
|
102
|
+
def transform_argument(target : callable, position : int, name : str, transform : callable, default = None):
|
|
103
|
+
assert callable(transform)
|
|
104
|
+
assert callable(target)
|
|
105
|
+
|
|
106
|
+
@functools.wraps(target)
|
|
107
|
+
def wrapper(*args, **kwargs):
|
|
108
|
+
if name in kwargs:
|
|
109
|
+
kwargs[name] = transform(kwargs[name])
|
|
110
|
+
elif len(args) > position:
|
|
111
|
+
args = list(args)
|
|
112
|
+
args[position] = transform(args[position])
|
|
113
|
+
else:
|
|
114
|
+
kwargs[name] = transform(default)
|
|
115
|
+
|
|
116
|
+
# print(f'Running: {args} {kwargs}')
|
|
117
|
+
return target(*args, **kwargs)
|
|
118
|
+
|
|
119
|
+
return wrapper
|
|
120
|
+
|
|
121
|
+
def retrace_env():
|
|
122
|
+
import threading
|
|
123
|
+
|
|
124
|
+
env = {
|
|
125
|
+
'RETRACE_RECORDING_PATH': globals.recording_path.next_subprocess_path(),
|
|
126
|
+
# 'RETRACE_PARENT_THREAD_ID': threading.current_thread().__retrace_thread_id__,
|
|
127
|
+
# 'RETRACE_PARENT_EXEC_COUNTER': str(next_exec_counter()
|
|
128
|
+
'RETRACE_MODE': 'record'
|
|
129
|
+
# 'RETRACE_MODE': 'proxy' if os.getenv('RETRACE_MODE') == 'proxy' else 'record'
|
|
130
|
+
}
|
|
131
|
+
# a timestamped directory off current directory
|
|
132
|
+
# controlled by config
|
|
133
|
+
# 1. if recording_dir is set use that
|
|
134
|
+
# 2. if recording_dir is not set look for
|
|
135
|
+
|
|
136
|
+
# env['RETRACE_EXECUTION_ID'] = hashlib.md5(json.dumps(env).encode('UTF8')).hexdigest()
|
|
137
|
+
|
|
138
|
+
return env
|
|
139
|
+
|
|
140
|
+
def fork_exec(target):
|
|
141
|
+
|
|
142
|
+
def transform(env):
|
|
143
|
+
r = [f'{k}={v}'.encode('utf-8') for k,v in retrace_env().items()]
|
|
144
|
+
return env + r if env else r
|
|
145
|
+
|
|
146
|
+
return transform_argument(target = target,
|
|
147
|
+
position = 5,
|
|
148
|
+
name = 'env',
|
|
149
|
+
transform = transform, default = os.environ)
|
|
150
|
+
|
|
151
|
+
# import threading
|
|
152
|
+
# thread_state = threading.current_thread().__retrace__.thread_state
|
|
153
|
+
|
|
154
|
+
# patched = transform_argument(target = target,
|
|
155
|
+
# position = 5,
|
|
156
|
+
# name = 'env',
|
|
157
|
+
# transform = transform, default = os.environ)
|
|
158
|
+
|
|
159
|
+
# return thread_state.dispatch(target, internal = patched)
|
|
160
|
+
|
|
161
|
+
def posix_spawn(target):
|
|
162
|
+
|
|
163
|
+
def transform(env):
|
|
164
|
+
r = retrace_env()
|
|
165
|
+
return {**env, **r}
|
|
166
|
+
|
|
167
|
+
return transform_argument(target = target,
|
|
168
|
+
position = 2,
|
|
169
|
+
name = 'env',
|
|
170
|
+
transform = transform, default = os.environ)
|
|
171
|
+
|
|
172
|
+
# import threading
|
|
173
|
+
# thread_state = threading.current_thread().__retrace__.thread_state
|
|
174
|
+
|
|
175
|
+
# patched = transform_argument(target = target,
|
|
176
|
+
# position = 2,
|
|
177
|
+
# name = 'env',
|
|
178
|
+
# transform = transform, default = os.environ)
|
|
179
|
+
|
|
180
|
+
# return thread_state.dispatch(target, internal = patched)
|
|
181
|
+
|
|
182
|
+
function_patchers = {
|
|
183
|
+
'_posixsubprocess': {
|
|
184
|
+
'fork_exec': fork_exec
|
|
185
|
+
},
|
|
186
|
+
'posix': {
|
|
187
|
+
'posix_spawn': posix_spawn
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# import traceback
|
|
192
|
+
|
|
193
|
+
def typepatcher(cls : type):
|
|
194
|
+
|
|
195
|
+
# print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! type: {cls} created')
|
|
196
|
+
# traceback.print_stack()
|
|
197
|
+
|
|
198
|
+
return typewrappers.get(cls.__module__, {}).get(cls.__name__, {})
|
|
199
|
+
|
|
200
|
+
# if cls.__module__ in typewrappers:
|
|
201
|
+
# # print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPEWRAPPER for {cls}')
|
|
202
|
+
|
|
203
|
+
# mod = typewrappers[cls.__module__]
|
|
204
|
+
|
|
205
|
+
# return mod.get(cls.__name__, {})
|
|
206
|
+
|
|
207
|
+
# if cls.__name__ in mod:
|
|
208
|
+
# # if cls.__name__ == '_SSLSocket':
|
|
209
|
+
# # breakpoint()
|
|
210
|
+
|
|
211
|
+
# log.info("Applying specialized typewrapper to %s, updated slots: %s", cls, list(mod[cls.__name__].keys()))
|
|
212
|
+
|
|
213
|
+
# for name,value in mod[cls.__name__].items():
|
|
214
|
+
# setattr(cls, name, value(getattr(cls, name)))
|
|
215
|
+
|
|
216
|
+
# # slots = {'__module__': cls.__module__, '__slots__': ()}
|
|
217
|
+
# # slots.update(mod[cls.__name__])
|
|
218
|
+
# # return type(cls.__name__, (cls, ), slots)
|
|
219
|
+
|
|
220
|
+
# return cls
|
|
221
|
+
|
|
222
|
+
def typewrapper(cls : type):
|
|
223
|
+
|
|
224
|
+
# print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! type: {cls} created')
|
|
225
|
+
# traceback.print_stack()
|
|
226
|
+
|
|
227
|
+
if cls.__module__ in typewrappers:
|
|
228
|
+
# print(f'!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! TYPEWRAPPER for {cls}')
|
|
229
|
+
|
|
230
|
+
mod = typewrappers[cls.__module__]
|
|
231
|
+
|
|
232
|
+
if cls.__name__ in mod:
|
|
233
|
+
# if cls.__name__ == '_SSLSocket':
|
|
234
|
+
# breakpoint()
|
|
235
|
+
|
|
236
|
+
log.info("Applying specialized typewrapper to %s, updated slots: %s", classname(cls), list(mod[cls.__name__].keys()))
|
|
237
|
+
|
|
238
|
+
slots = {'__module__': cls.__module__, '__slots__': ()}
|
|
239
|
+
slots.update(mod[cls.__name__])
|
|
240
|
+
return type(cls.__name__, (cls, ), slots)
|
|
241
|
+
|
|
242
|
+
return cls
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class RecordingPath:
|
|
2
|
+
def __init__(self, path):
|
|
3
|
+
self.path = path
|
|
4
|
+
self.subprocess_counter = 0
|
|
5
|
+
self.fork_counter = 0
|
|
6
|
+
|
|
7
|
+
def next_subprocess_path(self):
|
|
8
|
+
path = self.path / f'subprocess-{self.subprocess_counter}'
|
|
9
|
+
self.subprocess_counter = self.subprocess_counter + 1
|
|
10
|
+
return path
|
|
11
|
+
|
|
12
|
+
def next_fork_path(self):
|
|
13
|
+
path = self.path / f'fork-{self.fork_counter}'
|
|
14
|
+
self.fork_counter = self.fork_counter + 1
|
|
15
|
+
return path
|
|
16
|
+
|
|
17
|
+
recording_path = None
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import retracesoftware.functional as functional
|
|
4
|
+
import retracesoftware_utils as utils
|
|
5
|
+
|
|
6
|
+
# from retracesoftware_functional import mapargs, walker, first_arg, if_then_else, compose, observer, anyargs, memoize_one_arg, side_effect, partial, threadwatcher, firstof, notinstance_test, instance_test,typeof, isinstanceof, always
|
|
7
|
+
# from retracesoftware.proxy.proxy import ProxyFactory, InternalProxy, ProxySpec, WrappingProxySpec, ExtendingProxySpec, ExtendingProxy
|
|
8
|
+
from retracesoftware_proxy import thread_id
|
|
9
|
+
# from retracesoftware_stream import ObjectWriter, ObjectReader
|
|
10
|
+
import retracesoftware.stream as stream
|
|
11
|
+
|
|
12
|
+
# from retracesoftware_utils import visitor
|
|
13
|
+
from retracesoftware.install.tracer import Tracer
|
|
14
|
+
from retracesoftware.proxy.record import RecordProxySystem
|
|
15
|
+
from retracesoftware.proxy.replay import ReplayProxySystem
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
import json
|
|
21
|
+
import enum
|
|
22
|
+
import _thread
|
|
23
|
+
import pickle
|
|
24
|
+
import weakref
|
|
25
|
+
import types
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
import glob, os
|
|
28
|
+
import re
|
|
29
|
+
|
|
30
|
+
# from retracesoftware_proxy import *
|
|
31
|
+
# from retracesoftware_utils import *
|
|
32
|
+
# from retracesoftware.proxy import references
|
|
33
|
+
from retracesoftware.install import patcher
|
|
34
|
+
from retracesoftware.install.config import load_config
|
|
35
|
+
# from retracesoftware.proxy.immutabletypes import ImmutableTypes
|
|
36
|
+
# from retracesoftware.proxy import edgecases
|
|
37
|
+
from retracesoftware.install import globals
|
|
38
|
+
# from retracesoftware.proxy.record import RecordProxyFactory
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DebugWriter:
|
|
42
|
+
__slot__ = ['checkpoint']
|
|
43
|
+
|
|
44
|
+
def __init__(self, checkpoint):
|
|
45
|
+
self.checkpoint = checkpoint
|
|
46
|
+
|
|
47
|
+
def write_call(self, func, *args, **kwargs):
|
|
48
|
+
self.checkpoint({'type': 'call', 'func': ...})
|
|
49
|
+
|
|
50
|
+
def write_result(self, res):
|
|
51
|
+
self.checkpoint({'type': 'result', 'result': res})
|
|
52
|
+
|
|
53
|
+
def write_error(self, *args):
|
|
54
|
+
self.checkpoint({'type': 'error', 'error': tuple(args)})
|
|
55
|
+
|
|
56
|
+
class MethodDescriptor:
|
|
57
|
+
def __init__(self, descriptor):
|
|
58
|
+
self.cls = descriptor.__objclass__
|
|
59
|
+
self.name = descriptor.name
|
|
60
|
+
|
|
61
|
+
def once(*args): return functional.memoize_one_arg(functional.sequence(*args))
|
|
62
|
+
|
|
63
|
+
any = functional.firstof
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def compose(*args):
|
|
67
|
+
new_args = [item for item in args if item is not None]
|
|
68
|
+
if len(new_args) == 0:
|
|
69
|
+
raise Exception('TODO')
|
|
70
|
+
elif len(new_args) == 1:
|
|
71
|
+
return new_args[0]
|
|
72
|
+
else:
|
|
73
|
+
return functional.compose(*new_args)
|
|
74
|
+
|
|
75
|
+
class SerializedWrappedFunction:
|
|
76
|
+
def __init__(self, func):
|
|
77
|
+
if hasattr(func, '__objclass__'):
|
|
78
|
+
self.cls = func.__objclass__
|
|
79
|
+
elif hasattr(func, '__module__'):
|
|
80
|
+
self.module = func.__module__
|
|
81
|
+
|
|
82
|
+
if hasattr(func, '__name__'):
|
|
83
|
+
self.name = func.__name__
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def replaying_proxy_factory(thread_state, is_immutable_type, tracer, next, bind, checkpoint):
|
|
87
|
+
|
|
88
|
+
# def on_new_ext_proxytype(proxytype):
|
|
89
|
+
# assert not issubclass(proxytype, DynamicProxy)
|
|
90
|
+
# bind(proxytype)
|
|
91
|
+
# writer.add_type_serializer(cls = proxytype, serializer = functional.typeof)
|
|
92
|
+
|
|
93
|
+
# bind_new_int_proxy = functional.if_then_else(functional.isinstanceof(InternalProxy), functional.memoize_one_arg(bind), None)
|
|
94
|
+
|
|
95
|
+
# on_ext_call = utils.visitor(from_arg = 1, function = bind_new_int_proxy)
|
|
96
|
+
|
|
97
|
+
def wrap_int_call(handler):
|
|
98
|
+
return functional.observer(
|
|
99
|
+
on_call = tracer('proxy.int.call'),
|
|
100
|
+
on_result = tracer('proxy.int.result'),
|
|
101
|
+
on_error = tracer('proxy.int.error'),
|
|
102
|
+
function = handler)
|
|
103
|
+
|
|
104
|
+
# def is_stub_type(obj):
|
|
105
|
+
# return type(obj) == type and issubclass(obj, (WrappingProxy, ExtendingProxy))
|
|
106
|
+
|
|
107
|
+
def is_stub_type(obj):
|
|
108
|
+
return type(obj) == type
|
|
109
|
+
|
|
110
|
+
create_stubs = functional.walker(functional.when(is_stub_type, utils.create_stub_object))
|
|
111
|
+
# create_stubs = functional.walker(functional.when(is_stub_type, utils.create_stub_object))
|
|
112
|
+
|
|
113
|
+
def wrap_ext_call(handler):
|
|
114
|
+
return functional.observer(
|
|
115
|
+
on_call = tracer('proxy.ext.call'),
|
|
116
|
+
on_result = tracer('proxy.ext.result'),
|
|
117
|
+
on_error = tracer('proxy.ext.error'),
|
|
118
|
+
function = functional.sequence(functional.always(next), create_stubs))
|
|
119
|
+
|
|
120
|
+
return ProxyFactory(thread_state = thread_state,
|
|
121
|
+
is_immutable_type = is_immutable_type,
|
|
122
|
+
tracer = tracer,
|
|
123
|
+
on_new_int_proxy = bind,
|
|
124
|
+
# on_new_ext_proxytype = on_new_ext_proxytype,
|
|
125
|
+
wrap_int_call = wrap_int_call,
|
|
126
|
+
wrap_ext_call = wrap_ext_call)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# class Reader:
|
|
130
|
+
|
|
131
|
+
# def __init__(self, objectreader):
|
|
132
|
+
# self.objectreader = objectreader
|
|
133
|
+
|
|
134
|
+
# self.objectreader()
|
|
135
|
+
|
|
136
|
+
def tracing_config(config):
|
|
137
|
+
level = os.environ.get('RETRACE_DEBUG', config['default_tracing_level'])
|
|
138
|
+
return config['tracing_levels'].get(level, {})
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def install(create_system):
|
|
142
|
+
return patcher.install(config = load_config('config.json'), create_system = create_system)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
from retracesoftware.install.typeutils import modify
|
|
3
|
+
from retracesoftware.install.replace import update
|
|
4
|
+
import threading
|
|
5
|
+
import sys
|
|
6
|
+
import retracesoftware.utils as utils
|
|
7
|
+
import retracesoftware.functional as functional
|
|
8
|
+
import importlib
|
|
9
|
+
|
|
10
|
+
def resolve(path):
|
|
11
|
+
module, sep, name = path.rpartition('.')
|
|
12
|
+
|
|
13
|
+
if module is None:
|
|
14
|
+
module = 'builtins'
|
|
15
|
+
|
|
16
|
+
return getattr(importlib.import_module(module), name)
|
|
17
|
+
|
|
18
|
+
def replace(replacements, coll):
|
|
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))
|
|
25
|
+
|
|
26
|
+
return cls
|
|
27
|
+
|
|
28
|
+
class PerThread(threading.local):
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.internal = utils.counter()
|
|
31
|
+
self.external = utils.counter()
|
|
32
|
+
|
|
33
|
+
def create_patcher(system):
|
|
34
|
+
|
|
35
|
+
patcher = {}
|
|
36
|
+
|
|
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()}
|
|
39
|
+
|
|
40
|
+
def simple_patcher(func): return foreach(functional.side_effect(func))
|
|
41
|
+
|
|
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
|
|
48
|
+
|
|
49
|
+
def bind(obj):
|
|
50
|
+
if issubclass(obj, enum.Enum):
|
|
51
|
+
for member in obj:
|
|
52
|
+
system.bind(member)
|
|
53
|
+
else:
|
|
54
|
+
system.bind(obj)
|
|
55
|
+
|
|
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
|
|
61
|
+
|
|
62
|
+
per_thread = PerThread()
|
|
63
|
+
|
|
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')))
|
|
68
|
+
|
|
69
|
+
def patch_hash(obj):
|
|
70
|
+
if not isinstance(obj, type):
|
|
71
|
+
raise Exception("TODO")
|
|
72
|
+
|
|
73
|
+
utils.patch_hash(cls = obj, hashfunc = hashfunc)
|
|
74
|
+
return obj
|
|
75
|
+
|
|
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)
|
|
104
|
+
else:
|
|
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)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Set to track copied modules
|
|
8
|
+
copied_modules = set()
|
|
9
|
+
|
|
10
|
+
def get_relative_path(module_path: Path, sys_path: list) -> Path:
|
|
11
|
+
"""Compute the relative path of module_path relative to sys.path entries."""
|
|
12
|
+
module_path = module_path.resolve()
|
|
13
|
+
for base in sys_path:
|
|
14
|
+
base_path = Path(base).resolve()
|
|
15
|
+
try:
|
|
16
|
+
if module_path.is_relative_to(base_path):
|
|
17
|
+
return module_path.relative_to(base_path)
|
|
18
|
+
except ValueError:
|
|
19
|
+
continue
|
|
20
|
+
return Path(module_path.name)
|
|
21
|
+
|
|
22
|
+
class patch_find_spec:
|
|
23
|
+
def __init__(self, cwd, run_path, python_path):
|
|
24
|
+
self.cwd = cwd
|
|
25
|
+
self.run_path = run_path
|
|
26
|
+
self.python_path = python_path
|
|
27
|
+
|
|
28
|
+
def __call__(self, spec):
|
|
29
|
+
if spec is not None and spec.origin and spec.origin != "built-in" and os.path.isfile(spec.origin):
|
|
30
|
+
module_name = spec.name
|
|
31
|
+
if module_name not in copied_modules:
|
|
32
|
+
module_path = Path(spec.origin)
|
|
33
|
+
|
|
34
|
+
dest_path = None
|
|
35
|
+
|
|
36
|
+
if module_path.is_relative_to(self.cwd):
|
|
37
|
+
dest_path = self.run_path / module_path.relative_to(self.cwd)
|
|
38
|
+
elif self.python_path:
|
|
39
|
+
relative_path = get_relative_path(module_path, sys.path)
|
|
40
|
+
dest_path = self.python_path / relative_path
|
|
41
|
+
|
|
42
|
+
if dest_path:
|
|
43
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
shutil.copy2(module_path, dest_path)
|
|
45
|
+
copied_modules.add(module_name)
|
|
46
|
+
|
|
47
|
+
# def __call__(self, fullname, path, target=None):
|
|
48
|
+
# spec = self.original_find_spec(fullname, path, target)
|
|
49
|
+
|
|
50
|
+
# if spec is not None and spec.origin and spec.origin != "built-in" and os.path.isfile(spec.origin):
|
|
51
|
+
# module_name = spec.name
|
|
52
|
+
# if module_name not in copied_modules:
|
|
53
|
+
# module_path = Path(spec.origin)
|
|
54
|
+
|
|
55
|
+
# dest_path = None
|
|
56
|
+
|
|
57
|
+
# if module_path.is_relative_to(self.cwd):
|
|
58
|
+
# dest_path = self.run_path / module_path.relative_to(self.cwd)
|
|
59
|
+
# elif self.python_path:
|
|
60
|
+
# relative_path = get_relative_path(module_path, sys.path)
|
|
61
|
+
# dest_path = self.python_path / relative_path
|
|
62
|
+
|
|
63
|
+
# if dest_path:
|
|
64
|
+
# dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
65
|
+
# shutil.copy2(module_path, dest_path)
|
|
66
|
+
# copied_modules.add(module_name)
|
|
67
|
+
|
|
68
|
+
# return spec
|
|
69
|
+
|
|
70
|
+
# def patch_find_spec(cwd, run_path, python_path, original_find_spec):
|
|
71
|
+
# """Create a patched version of find_spec that copies .py files."""
|
|
72
|
+
# @wraps(original_find_spec)
|
|
73
|
+
# def patched_find_spec(fullname, path, target=None):
|
|
74
|
+
# spec = original_find_spec(fullname, path, target)
|
|
75
|
+
|
|
76
|
+
# if spec is not None and spec.origin and spec.origin != "built-in" and os.path.isfile(spec.origin):
|
|
77
|
+
# module_name = spec.name
|
|
78
|
+
# if module_name not in copied_modules:
|
|
79
|
+
# module_path = Path(spec.origin)
|
|
80
|
+
|
|
81
|
+
# dest_path = None
|
|
82
|
+
|
|
83
|
+
# if module_path.is_relative_to(cwd):
|
|
84
|
+
# dest_path = run_path / module_path.relative_to(cwd)
|
|
85
|
+
# elif python_path:
|
|
86
|
+
# relative_path = get_relative_path(module_path, sys.path)
|
|
87
|
+
# dest_path = python_path / relative_path
|
|
88
|
+
|
|
89
|
+
# if dest_path:
|
|
90
|
+
# dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
91
|
+
# shutil.copy2(module_path, dest_path)
|
|
92
|
+
# copied_modules.add(module_name)
|
|
93
|
+
|
|
94
|
+
# # elif python_path:
|
|
95
|
+
# # relative_path = get_relative_path(module_path, sys.path)
|
|
96
|
+
# # dest_path = python_path / relative_path
|
|
97
|
+
# # dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
|
|
99
|
+
# # shutil.copy2(module_path, dest_path)
|
|
100
|
+
# # copied_modules.add(module_name)
|
|
101
|
+
|
|
102
|
+
# # if module_path.suffix == '.py':
|
|
103
|
+
# # elif module_path.suffix == '.py':
|
|
104
|
+
# # relative_path = get_relative_path(module_path, sys.path)
|
|
105
|
+
# # dest_path = path / relative_path
|
|
106
|
+
# # dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
# # try:
|
|
108
|
+
# # shutil.copy2(module_path, dest_path)
|
|
109
|
+
# # print(f"Copied {module_path} to {dest_path} (relative: {relative_path})")
|
|
110
|
+
# # copied_modules.add(module_name)
|
|
111
|
+
# # except Exception as e:
|
|
112
|
+
# # print(f"Failed to copy {module_path}: {e}")
|
|
113
|
+
# # else:
|
|
114
|
+
# # print(f"Skipped copying {module_path} (not a .py file)")
|
|
115
|
+
|
|
116
|
+
# return spec
|
|
117
|
+
# return patched_find_spec
|