You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
286 lines
11 KiB
286 lines
11 KiB
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright (C) 2011 Loic Jaquemet loic.jaquemet+python@gmail.com
|
|
#
|
|
|
|
import logging
|
|
import numbers
|
|
import sys
|
|
|
|
from haystack.outputters import Outputter
|
|
from haystack import types
|
|
from haystack import basicmodel
|
|
|
|
|
|
log = logging.getLogger('python')
|
|
|
|
|
|
class PythonOutputter(Outputter):
|
|
|
|
""" Parse a self._ctypes structure and outputs a pure python object."""
|
|
|
|
def parse(self, obj, prefix='', depth=50):
|
|
"""
|
|
Returns a Plain Old python object as a perfect copy of this self._ctypes object.
|
|
array would be lists, pointers, inner allocators, and circular
|
|
reference should be handled nicely.
|
|
"""
|
|
# get self class.
|
|
try:
|
|
obj_module_name = obj.__class__.__module__
|
|
obj_class_name = obj.__class__.__name__
|
|
try:
|
|
obj_module = self._model.get_pythoned_module(obj_module_name)
|
|
except KeyError:
|
|
# FIXME - ctypes modules should not be in sys.modules. what about reloading?
|
|
self._model.build_python_class_clones(sys.modules[obj_module_name])
|
|
obj_module = self._model.get_pythoned_module(obj_module_name)
|
|
my_class = getattr(obj_module, "%s_py" % obj_class_name)
|
|
except AttributeError as e:
|
|
log.warning('did you forget to register your python allocators ?')
|
|
raise
|
|
my_self = my_class()
|
|
my_address = self._ctypes.addressof(obj)
|
|
# keep ref of the POPO too.
|
|
if self._memory_handler.hasRef(my_class, my_address):
|
|
return self._memory_handler.getRef(my_class, my_address)
|
|
# save our POPO in a partially resolved state, to keep from loops.
|
|
self._memory_handler.keepRef(my_self, my_class, my_address)
|
|
for field, typ in basicmodel.get_fields(obj):
|
|
attr = getattr(obj, field)
|
|
try:
|
|
member = self._attrToPyObject(attr, field, typ)
|
|
except NameError as e:
|
|
raise NameError('%s %s\n%s' % (field, typ, e))
|
|
|
|
setattr(my_self, field, member)
|
|
# save the original type (me) and the field
|
|
setattr(my_self, '_ctype_', type(obj))
|
|
return my_self
|
|
|
|
def _attrToPyObject(self, attr, field, attrtype):
|
|
if self._ctypes.is_basic_type(attrtype):
|
|
if self._ctypes.is_basic_ctype(type(attr)):
|
|
obj = attr.value
|
|
else:
|
|
obj = attr
|
|
elif self._ctypes.is_struct_type(attrtype) or self._ctypes.is_union_type(attrtype):
|
|
attr._mappings_ = self._memory_handler
|
|
obj = self.parse(attr)
|
|
elif self._ctypes.is_array_of_basic_type(attrtype):
|
|
# return a list of int, float, or a char[] to str
|
|
obj = self._utils.ctypes_to_python_array(attr)
|
|
elif self._ctypes.is_array_type(attrtype):
|
|
# array of something else than int/byte
|
|
obj = []
|
|
eltyp = type(attr[0])
|
|
for i in range(0, len(attr)):
|
|
obj.append(self._attrToPyObject(attr[i], i, eltyp))
|
|
elif self._ctypes.is_cstring_type(attrtype):
|
|
obj = self._memory_handler.getRef(
|
|
self._ctypes.CString,
|
|
self._utils.get_pointee_address(
|
|
attr.ptr))
|
|
elif self._ctypes.is_function_type(attrtype):
|
|
obj = repr(attr)
|
|
elif self._ctypes.is_pointer_type(attrtype):
|
|
# get the cached Value of the LP.
|
|
_subtype = self._utils.get_subtype(attrtype)
|
|
_address = self._utils.get_pointee_address(attr)
|
|
#if field == 'ProcessHeaps':
|
|
# import code
|
|
# code.interact(local=locals())
|
|
if _address == 0:
|
|
# Null pointer
|
|
obj = None
|
|
elif self._ctypes.is_pointer_to_void_type(attrtype):
|
|
# TODO: make a prototype for c_void_p loading
|
|
# void types a rereturned as None
|
|
obj = None
|
|
elif self._ctypes.is_array_of_basic_type(attrtype):
|
|
log.error('basic Type array - %s' % (field))
|
|
obj = 'BasicType array'
|
|
else:
|
|
# FIXME we should NOT recurse
|
|
# get the cached Value of the LP.
|
|
cache = self._memory_handler.getRef(_subtype, _address)
|
|
if cache is not None: # struct, union...
|
|
obj = self._attrToPyObject(cache, field, _subtype)
|
|
else:
|
|
# you got here because your pointer is not loaded:
|
|
# did you ignore it in expectedValues ?
|
|
# is it in the middle of a struct ?
|
|
# is that a linked list ?
|
|
# is it a invalid instance ?
|
|
log.debug('Pointer for field:%s %s/%s not in cache '
|
|
'0x%x' % (field, attrtype, self._utils.get_subtype(attrtype),
|
|
_address))
|
|
return (None, None)
|
|
elif isinstance(attr, numbers.Number):
|
|
# case for int, long. But needs to be after c_void_p pointers case
|
|
obj = attr
|
|
else:
|
|
log.error('toPyObj default to return attr %s' % (type(attr)))
|
|
obj = attr
|
|
return obj
|
|
|
|
|
|
def json_encode_pyobj(obj):
|
|
if hasattr(obj, '_ctype_'):
|
|
return obj.__dict__
|
|
elif type(obj).__name__ == 'int':
|
|
log.warning('found an int')
|
|
return str(obj)
|
|
else:
|
|
return obj
|
|
|
|
|
|
class pyObj(object):
|
|
|
|
"""
|
|
Base class for a plain old python object.
|
|
all haystack/ctypes classes will be translated in this format before pickling.
|
|
|
|
Operations :
|
|
- toString(self, prefix): print a nicely formatted data structure
|
|
:param prefix: str to insert before each line (\t after that)
|
|
- findCtypes(self) : checks if a self._ctypes is to be found somewhere is the object.
|
|
Useful to check if the object can be pickled.
|
|
"""
|
|
|
|
def toString(self, prefix='', maxDepth=10):
|
|
if maxDepth < 0:
|
|
return '#(- not printed by Excessive recursion - )'
|
|
s = '{\n'
|
|
if hasattr(self, '_ctype_'):
|
|
items = [n for n, t in basicmodel.get_record_type_fields(self._ctype_)]
|
|
else:
|
|
log.warning('no _ctype_')
|
|
items = [n for n in self.__dict__.keys() if n != '_ctype_']
|
|
for attrname in items:
|
|
attr = getattr(self, attrname)
|
|
typ = type(attr)
|
|
s += "%s%s: %s\n" % (prefix,
|
|
attrname,
|
|
self._attrToString(
|
|
attr,
|
|
attrname,
|
|
typ,
|
|
prefix + '\t',
|
|
maxDepth=maxDepth - 1))
|
|
s += '}'
|
|
return s
|
|
|
|
def _attrToString(self, attr, attrname, typ, prefix, maxDepth):
|
|
s = ''
|
|
if isinstance(attr, tuple) or isinstance(attr, list):
|
|
for i in xrange(0, len(attr)):
|
|
s += '%s,' % (self._attrToString(attr[i],
|
|
i,
|
|
None,
|
|
prefix + '\t',
|
|
maxDepth))
|
|
s = "[%s]," % (s)
|
|
elif not hasattr(attr, '__dict__'):
|
|
s = '%s,' % (repr(attr))
|
|
elif isinstance(attr, pyObj):
|
|
s = '%s,' % (attr.toString(prefix, maxDepth))
|
|
else:
|
|
s = '%s,' % (repr(attr))
|
|
return s
|
|
|
|
def __len__(self):
|
|
return self._len_
|
|
|
|
def findCtypes(self, cache=None):
|
|
""" recurse on members to check for self._ctypes object. """
|
|
if cache is None:
|
|
cache = set()
|
|
ret = False
|
|
for attrname, attr in self.__dict__.items():
|
|
if id(attr) in cache: # do not recurse in already parsed
|
|
continue
|
|
# ignore _ctype_, it's a ctype class type, we know that.
|
|
if attrname == '_ctype_':
|
|
cache.add(id(attr))
|
|
continue
|
|
typ = type(attr)
|
|
attr = getattr(self, attrname)
|
|
log.debug('findCtypes on attr %s' % attrname)
|
|
if self._attrFindCtypes(attr, attrname, typ, cache):
|
|
log.warning('Found a self._ctypes in %s' % (attrname))
|
|
ret = True
|
|
return ret
|
|
|
|
def _attrFindCtypes(self, attr, attrname, typ, cache):
|
|
ret = False
|
|
cache.add(id(attr))
|
|
if hasattr(attr, '_ctype_'): # a pyobj
|
|
return attr.findCtypes(cache)
|
|
elif isinstance(attr, tuple) or isinstance(attr, list):
|
|
for el in attr:
|
|
if self._attrFindCtypes(el, 'element', None, cache):
|
|
log.warning('Found a self._ctypes in array/tuple')
|
|
return True
|
|
elif types.is_ctypes_instance(attr):
|
|
log.warning('Found a self._ctypes in self %s' % (attr))
|
|
return True
|
|
else: # int, long, str ...
|
|
ret = False
|
|
return ret
|
|
|
|
def __iter__(self):
|
|
""" iterate on a instance's type's _fields_ members following the original type field order """
|
|
for k, typ in basicmodel.get_fields(self._ctype_):
|
|
v = getattr(self, k)
|
|
yield (k, v, typ)
|
|
pass
|
|
|
|
# the python cannot contain a ref to a ctypes
|
|
def __getstate__(self):
|
|
d = self.__dict__.copy()
|
|
if '_ctype_' in d:
|
|
d['_ctype_'] = d['_ctype_'].__class__.__name__
|
|
return d
|
|
|
|
def __reduce__(self):
|
|
"""Explains how to rebuild this class once pickled."""
|
|
state = self.__dict__.copy()
|
|
if '_ctype_' in state:
|
|
state['_ctype_'] = state['_ctype_'].__class__.__name__
|
|
name = self.__class__.__name__
|
|
modulename = self.__module__
|
|
return (_pyObjBuilder(), # __call__
|
|
(modulename, name), # arg for builder
|
|
state
|
|
)
|
|
|
|
|
|
class _pyObjBuilder:
|
|
"""Builder of pickled instance of pyObj into properly named POPO"""
|
|
def __call__(self, modulename, classname):
|
|
# make a simple object which has no complex __init__ (this one will do)
|
|
obj = pyObj()
|
|
# class = getattr(containing_class, class_name)
|
|
# kpy = type('%s.%s' % (modulename, classname), (pyObj,), {})
|
|
kpy = type(classname, (pyObj,), {})
|
|
obj.__class__ = kpy
|
|
return obj
|
|
|
|
|
|
def findCtypesInPyObj(memory_handler, obj):
|
|
""" check function to help in unpickling errors correction """
|
|
if hasattr(obj, 'findCtypes'):
|
|
if obj.findCtypes():
|
|
log.warning('Found a self._ctypes in array/tuple')
|
|
return True
|
|
elif isinstance(obj, tuple) or isinstance(obj, list):
|
|
for el in obj:
|
|
if findCtypesInPyObj(memory_handler, el):
|
|
log.warning('Found a self._ctypes in array/tuple')
|
|
return True
|
|
elif types.is_ctypes_instance(obj):
|
|
return True
|
|
return False
|