retracesoftware-proxy 0.1.22__py3-none-any.whl → 0.2.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.
@@ -5,10 +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
8
+ from retracesoftware.install.typeutils import modify, WithFlags, WithoutFlags
9
9
 
10
+ from retracesoftware.proxy.serializer import serializer
11
+ from retracesoftware.install.tracer import Tracer
10
12
  import sys
11
13
  import gc
14
+ import weakref
15
+ import enum
16
+ import functools
17
+ import re
12
18
 
13
19
  class RetraceError(Exception):
14
20
  pass
@@ -25,6 +31,12 @@ def maybe_proxy(proxytype):
25
31
  utils.unwrap,
26
32
  proxy(functional.memoize_one_arg(proxytype)))
27
33
 
34
+ class Patched:
35
+ __slots__ = ()
36
+
37
+ class RetraceBase:
38
+ pass
39
+
28
40
  unproxy_execute = functional.mapargs(starting = 1,
29
41
  transform = functional.walker(utils.try_unwrap),
30
42
  function = functional.apply)
@@ -45,9 +57,77 @@ method_types = (types.MethodDescriptorType,
45
57
  def is_instance_method(obj):
46
58
  return isinstance(obj, method_types)
47
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
+
48
128
  class ProxySystem:
49
129
 
50
- def bind(self, obj): pass
130
+ # def bind(self, obj): pass
51
131
 
52
132
  def wrap_int_to_ext(self, obj): return obj
53
133
 
@@ -62,40 +142,67 @@ class ProxySystem:
62
142
  def on_ext_error(self, err_type, err_value, err_traceback):
63
143
  pass
64
144
 
145
+ def on_ext_call(self, func, *args, **kwargs):
146
+ pass
147
+
65
148
  # def stacktrace(self):
66
149
  # self.tracer.stacktrace()
67
150
 
68
151
  def set_thread_id(self, id):
69
152
  utils.set_thread_id(id)
70
153
 
71
- def __init__(self, thread_state, immutable_types, tracer):
154
+ @property
155
+ def ext_apply(self): return functional.apply
156
+
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)
72
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):
171
+
172
+ self.patched_types = set()
73
173
  self.thread_state = thread_state
74
174
  self.fork_counter = 0
75
175
  self.tracer = tracer
76
176
  self.immutable_types = immutable_types
77
- self.on_proxytype = None
78
-
79
- def is_immutable_type(cls):
80
- return cls is object or issubclass(cls, tuple(immutable_types))
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
81
183
 
82
- is_immutable = functional.sequence(functional.typeof, functional.memoize_one_arg(is_immutable_type))
184
+ should_proxy = functional.sequence(functional.typeof, functional.memoize_one_arg(should_proxy_type))
83
185
 
84
186
  def proxyfactory(proxytype):
85
- return functional.walker(functional.when_not(is_immutable, maybe_proxy(proxytype)))
187
+ return functional.walker(functional.when(should_proxy, maybe_proxy(proxytype)))
86
188
 
87
189
  int_spec = SimpleNamespace(
88
- apply = thread_state.wrap('internal', functional.apply),
190
+ apply = thread_state.wrap('internal', self.int_apply),
89
191
  proxy = proxyfactory(thread_state.wrap('disabled', self.int_proxytype)),
90
192
  on_call = tracer('proxy.int.call', self.on_int_call),
91
193
  on_result = tracer('proxy.int.result'),
92
194
  on_error = tracer('proxy.int.error'),
93
195
  )
94
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}))
200
+
95
201
  ext_spec = SimpleNamespace(
96
- apply = thread_state.wrap('external', functional.apply),
202
+ apply = thread_state.wrap('external', self.ext_apply),
97
203
  proxy = proxyfactory(thread_state.wrap('disabled', self.ext_proxytype)),
98
- on_call = tracer('proxy.ext.call'),
204
+
205
+ on_call = trace_ext_call if traceargs else self.on_ext_call,
99
206
  on_result = self.on_ext_result,
100
207
  on_error = self.on_ext_error,
101
208
  )
@@ -112,13 +219,19 @@ class ProxySystem:
112
219
  self.ext_dispatch = gateway('proxy.int.disabled.event', internal = self.ext_handler)
113
220
  self.int_dispatch = gateway('proxy.ext.disabled.event', external = self.int_handler)
114
221
 
115
- if 'systrace' in tracer.config:
116
- func = thread_state.wrap(desired_state = 'disabled', function = tracer.systrace)
117
- func = self.thread_state.dispatch(lambda *args: None, internal = func)
118
- sys.settrace(func)
222
+ self.exclude_from_stacktrace(Tracer._write_call)
119
223
 
120
- tracer.trace_calls(thread_state)
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)
121
231
 
232
+ def disable_for(self, func):
233
+ return self.thread_state.wrap('disabled', func)
234
+
122
235
  def new_child_path(self, path):
123
236
  return path.parent / f'fork-{self.fork_counter}' / path.name
124
237
 
@@ -143,70 +256,172 @@ class ProxySystem:
143
256
  if cls is object:
144
257
  breakpoint()
145
258
 
146
- proxytype = dynamic_int_proxytype(
259
+ return dynamic_int_proxytype(
147
260
  handler = self.int_dispatch,
148
261
  cls = cls,
149
262
  bind = self.bind)
150
263
 
151
- if self.on_proxytype: self.on_proxytype(proxytype)
152
- return proxytype
153
-
154
264
  def ext_proxytype(self, cls):
155
- if cls is object:
156
- breakpoint()
157
265
 
158
266
  proxytype = dynamic_proxytype(handler = self.ext_dispatch, cls = cls)
159
267
  proxytype.__retrace_source__ = 'external'
160
268
 
161
- if self.on_proxytype: self.on_proxytype(proxytype)
162
-
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)
287
+
163
288
  return proxytype
164
289
 
165
290
  def function_target(self, obj): return obj
166
291
 
167
- def proxy_function(self, obj):
168
- return utils.wrapped_function(handler = self.ext_handler, target = obj)
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)
169
308
 
170
- def proxy__new__(self, *args, **kwargs):
171
- return self.ext_handler(*args, **kwargs)
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)
172
312
 
173
- def patchtype(self, cls):
174
- if not utils.is_extendable(cls):
175
- with WithFlags(cls, "Py_TPFLAGS_BASETYPE"):
176
- assert utils.is_extendable(cls)
177
- return self.patchtype(cls)
313
+ def proxy_ext_member(self, member):
314
+ return utils.wrapped_member(handler = self.ext_dispatch, target = member)
178
315
 
179
- if cls in self.immutable_types or issubclass(cls, tuple):
180
- return cls
316
+ def proxy_int_member(self, member):
317
+ return utils.wrapped_member(handler = self.int_dispatch, target = member)
181
318
 
182
- def wrap(func):
183
- return self.thread_state.dispatch(func, internal = self.proxy_function(func))
319
+ # def proxy__new__(self, *args, **kwargs):
320
+ # return self.ext_handler(*args, **kwargs)
184
321
 
185
- def wrap_new(func):
186
- proxied = self.proxy_function(func)
322
+ # def on_new_ext_patched(self, obj):
323
+ # print(f'HWE!!!!!!!!!!! 2')
324
+ # print(f'HWE!!!!!!!!!!! 3')
325
+ # return id(obj)
187
326
 
188
- def new(cls, *args, **kwargs):
189
- instance = proxied(cls, *args, **kwargs)
190
- instance.__init__(*args, **kwargs)
191
- return instance
192
-
193
- return self.thread_state.dispatch(func, internal = new)
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()
340
+
341
+ assert isinstance(cls, type)
342
+
343
+ if issubclass(cls, BaseException): breakpoint()
344
+
345
+ assert not issubclass(cls, BaseException)
346
+
347
+ assert cls not in self.patched_types
348
+
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
360
+
361
+ def proxy_attrs(cls, dict, proxy_function, proxy_member):
362
+
363
+ blacklist = ['__new__', '__getattribute__', '__del__', '__dict__']
194
364
 
195
- # slots = {'__module__': cls.__module__, '__slots__': []}
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))
196
371
 
197
- extended = utils.extend_type(cls)
372
+ with WithoutFlags(cls, "Py_TPFLAGS_IMMUTABLETYPE"):
198
373
 
199
- for name, value in superdict(cls).items():
200
- if callable(value) and not is_instance_method(value):
201
- setattr(extended, name, wrap_new(value) if name == '__new__' else wrap(value))
374
+ proxy_attrs(
375
+ cls,
376
+ dict = superdict(cls),
377
+ proxy_function = self.proxy_ext_function,
378
+ proxy_member = self.proxy_ext_member)
202
379
 
203
- # slots[name] = wrap_new(value) if name == '__new__' else wrap(value)
204
- extended.__module__ = cls.__module__
380
+ on_alloc = self.thread_state.dispatch(
381
+ utils.noop,
382
+ internal = self.bind,
383
+ external = self.create_from_external)
205
384
 
206
- return extended
385
+ utils.set_on_alloc(cls, on_alloc)
207
386
 
208
- def is_entry_frame(self, frame):
209
- return frame.globals.get("__name__", None) == "__main__"
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)
210
425
 
211
426
  def __call__(self, obj):
212
427
  assert not isinstance(obj, BaseException)
@@ -214,22 +429,26 @@ class ProxySystem:
214
429
  assert not isinstance(obj, utils.wrapped_function)
215
430
 
216
431
  if type(obj) == type:
217
- return self.patchtype(obj)
432
+ return self.patch_type(obj)
218
433
 
219
434
  elif type(obj) in self.immutable_types:
220
435
  return obj
221
436
 
222
437
  elif is_function_type(type(obj)):
223
- return self.thread_state.dispatch(obj, internal = self.proxy_function(obj))
438
+ return self.thread_state.dispatch(obj, internal = self.proxy_ext_function(obj))
224
439
 
225
440
  elif type(obj) == types.ClassMethodDescriptorType:
226
- func = self.thread_state.dispatch(obj, internal = self.proxy_function(obj))
441
+ func = self.thread_state.dispatch(obj, internal = self.proxy_ext_function(obj))
227
442
  return classmethod(func)
228
443
  else:
229
- proxytype = dynamic_proxytype(handler = self.ext_dispatch, cls = type(obj))
230
- proxytype.__retrace_source__ = 'external'
444
+ return self.proxy_value(obj)
445
+
446
+ # def write_trace(self, obj):
447
+
448
+ # exclude = set([
449
+ # "ABCMeta.__subclasscheck__"
450
+ # # "__subclasscheck__"
451
+ # ])
231
452
 
232
- if self.on_proxytype: self.on_proxytype(proxytype)
233
453
 
234
- return utils.create_wrapped(proxytype, obj)
235
- # raise Exception(f'object {obj} was not proxied as its not a extensible type and is not callable')
454
+
@@ -198,12 +198,18 @@ class DescriptorProxy:
198
198
 
199
199
  def dynamic_proxytype(handler, cls):
200
200
 
201
+ if cls.__module__.startswith('retracesoftware'):
202
+ print(cls)
203
+ utils.sigtrap('HERE5')
204
+
205
+ assert not cls.__module__.startswith('retracesoftware')
206
+
201
207
  # print(f'In dynamic_proxytype: {cls}')
202
208
 
203
209
  assert not issubclass(cls, Proxy)
204
210
  assert not issubclass(cls, BaseException)
205
211
 
206
- blacklist = ['__getattribute__', '__hash__', '__del__', '__call__']
212
+ blacklist = ['__getattribute__', '__hash__', '__del__', '__call__', '__new__']
207
213
 
208
214
  spec = {}
209
215
 
@@ -211,15 +217,27 @@ def dynamic_proxytype(handler, cls):
211
217
 
212
218
  for name in superdict(cls).keys():
213
219
  if name not in blacklist:
214
- try:
215
- value = getattr(cls, name)
216
- if is_descriptor(value):
217
- if utils.is_method_descriptor(value):
218
- spec[name] = wrap(value)
219
- except Exception as error:
220
- print(f'FOO! {cls} {name} {error}')
221
- breakpoint()
222
- raise
220
+ value = getattr(cls, name)
221
+
222
+ if issubclass(type(value), utils.dispatch):
223
+ value = utils.dispatch.table(value)['disabled']
224
+
225
+ # if is_descriptor(value):
226
+ if utils.is_method_descriptor(value):
227
+ spec[name] = wrap(value)
228
+
229
+ # try:
230
+ # value = getattr(cls, name)
231
+
232
+ # assert type(value) is not utils.dispatch
233
+
234
+ # if is_descriptor(value):
235
+ # if utils.is_method_descriptor(value):
236
+ # spec[name] = wrap(value)
237
+ # except Exception as error:
238
+ # print(f'FOO! {cls} {name} {error}')
239
+ # breakpoint()
240
+ # raise
223
241
 
224
242
  # else:
225
243
  # spec[name] = DescriptorProxy(handler = handler, target = value)
@@ -230,6 +248,12 @@ def dynamic_proxytype(handler, cls):
230
248
 
231
249
  # spec = { name: wrap(getattr(cls, name)) for name in to_proxy }
232
250
 
251
+ # def foo(obj, name, default = None):
252
+ # print(f'dynamic_proxytype.__getattr__: {type(obj).__mro__} {name}')
253
+ # utils.sigtrap(obj)
254
+ # return getattr(obj, name, default)
255
+
256
+ # spec['__getattr__'] = wrap(foo)
233
257
  spec['__getattr__'] = wrap(getattr)
234
258
  spec['__setattr__'] = wrap(setattr)
235
259