360 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			360 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
 | 
						|
import py
 | 
						|
from py.__.apigen.tracer import model
 | 
						|
from py.__.code.source import getsource
 | 
						|
 | 
						|
import types
 | 
						|
import inspect
 | 
						|
import copy
 | 
						|
 | 
						|
MAX_CALL_SITES = 20
 | 
						|
 | 
						|
set = py.builtin.set
 | 
						|
 | 
						|
def is_private(name):
 | 
						|
    return name.startswith('_') and not name.startswith('__')
 | 
						|
 | 
						|
class CallFrame(object):
 | 
						|
    def __init__(self, frame):
 | 
						|
        self.filename = frame.code.raw.co_filename
 | 
						|
        self.lineno = frame.lineno
 | 
						|
        self.firstlineno = frame.code.firstlineno
 | 
						|
        self.source = getsource(frame.code.raw)
 | 
						|
 | 
						|
    def _getval(self):
 | 
						|
        return (self.filename, self.lineno)
 | 
						|
 | 
						|
    def __hash__(self):
 | 
						|
        return hash(self._getval())
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        return self._getval() == other._getval()
 | 
						|
 | 
						|
    def __ne__(self, other):
 | 
						|
        return not self == other
 | 
						|
 | 
						|
class CallStack(object):
 | 
						|
    def __init__(self, tb):
 | 
						|
        #if isinstance(tb, py.code.Traceback):
 | 
						|
        #    self.tb = tb
 | 
						|
        #else:
 | 
						|
        #    self.tb = py.code.Traceback(tb)
 | 
						|
        self.tb = [CallFrame(frame) for frame in tb]
 | 
						|
    
 | 
						|
    #def _getval(self):
 | 
						|
    #    return [(frame.code.raw.co_filename, frame.lineno+1) for frame
 | 
						|
    #        in self]
 | 
						|
    
 | 
						|
    def __hash__(self):
 | 
						|
        return hash(tuple(self.tb))
 | 
						|
    
 | 
						|
    def __eq__(self, other):
 | 
						|
        return self.tb == other.tb
 | 
						|
    
 | 
						|
    def __ne__(self, other):
 | 
						|
        return not self == other
 | 
						|
    
 | 
						|
    #def __getattr__(self, attr):
 | 
						|
    #    return getattr(self.tb, attr)
 | 
						|
    
 | 
						|
    def __iter__(self):
 | 
						|
        return iter(self.tb)
 | 
						|
    
 | 
						|
    def __getitem__(self, item):
 | 
						|
        return self.tb[item]
 | 
						|
    
 | 
						|
    def __len__(self):
 | 
						|
        return len(self.tb)
 | 
						|
    
 | 
						|
    def __cmp__(self, other):
 | 
						|
        return cmp(self.tb, other.tb)
 | 
						|
 | 
						|
def cut_stack(stack, frame, upward_frame=None):
 | 
						|
    if hasattr(frame, 'raw'):
 | 
						|
        frame = frame.raw
 | 
						|
    if upward_frame:
 | 
						|
        if hasattr(upward_frame, 'raw'):
 | 
						|
            upward_frame = upward_frame.raw
 | 
						|
        return CallStack([py.code.Frame(i) for i in stack[stack.index(frame):\
 | 
						|
                stack.index(upward_frame)+1]])
 | 
						|
    return CallStack([py.code.Frame(i) for i in stack[stack.index(frame):]])
 | 
						|
 | 
						|
##class CallSite(object):
 | 
						|
##    def __init__(self, filename, lineno):
 | 
						|
##        self.filename = filename
 | 
						|
##        self.lineno = lineno
 | 
						|
##    
 | 
						|
##    def get_tuple(self):
 | 
						|
##        return self.filename, self.lineno
 | 
						|
##    
 | 
						|
##    def __hash__(self):
 | 
						|
##        return hash((self.filename, self.lineno))
 | 
						|
##    
 | 
						|
##    def __eq__(self, other):
 | 
						|
##        return (self.filename, self.lineno) == (other.filename, other.lineno)
 | 
						|
##    
 | 
						|
##    def __ne__(self, other):
 | 
						|
##        return not self == other
 | 
						|
##    
 | 
						|
##    def __cmp__(self, other):
 | 
						|
##        if self.filename < other.filename:
 | 
						|
##            return -1
 | 
						|
##        if self.filename > other.filename:
 | 
						|
##            return 1
 | 
						|
##        if self.lineno < other.lineno:
 | 
						|
##            return -1
 | 
						|
##        if self.lineno > other.lineno:
 | 
						|
##            return 1
 | 
						|
##        return 0
 | 
						|
    
 | 
						|
class NonHashableObject(object):
 | 
						|
    def __init__(self, cls):
 | 
						|
        self.cls = cls
 | 
						|
    
 | 
						|
    def __hash__(self):
 | 
						|
        raise NotImplementedError("Object of type %s are unhashable" % self.cls)
 | 
						|
 | 
						|
class Desc(object):
 | 
						|
    def __init__(self, name, pyobj, **kwargs):
 | 
						|
        self.pyobj = pyobj
 | 
						|
        self.is_degenerated = False
 | 
						|
        self.name = name
 | 
						|
        if type(self) is Desc:
 | 
						|
            # do not override property...
 | 
						|
            self.code = NonHashableObject(self.__class__) # dummy think that makes code unhashable
 | 
						|
    # we make new base class instead of using pypy's one because
 | 
						|
    # of type restrictions of pypy descs
 | 
						|
    
 | 
						|
    def __hash__(self):
 | 
						|
        return hash(self.code)
 | 
						|
    
 | 
						|
    def __eq__(self, other):
 | 
						|
        if isinstance(other, Desc):
 | 
						|
            return self.code == other.code
 | 
						|
        if isinstance(other, types.CodeType):
 | 
						|
            return self.code == other
 | 
						|
        if isinstance(other, tuple) and len(other) == 2:
 | 
						|
            return self.code == other
 | 
						|
        return False
 | 
						|
    
 | 
						|
    def __ne__(self, other):
 | 
						|
        return not self == other
 | 
						|
    # This set of functions will not work on Desc, because we need to
 | 
						|
    # define code somehow
 | 
						|
 | 
						|
class FunctionDesc(Desc):
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        super(FunctionDesc, self).__init__(*args, **kwargs)
 | 
						|
        self.inputcells = [model.s_ImpossibleValue for i in xrange(self.\
 | 
						|
            code.co_argcount)]
 | 
						|
        self.call_sites = {}
 | 
						|
        self.keep_frames = kwargs.get('keep_frames', False)
 | 
						|
        self.frame_copier = kwargs.get('frame_copier', lambda x:x)
 | 
						|
        self.retval = model.s_ImpossibleValue
 | 
						|
        self.exceptions = {}
 | 
						|
    
 | 
						|
    def consider_call(self, inputcells):
 | 
						|
        for cell_num, cell in enumerate(inputcells):
 | 
						|
            self.inputcells[cell_num] = model.unionof(cell, self.inputcells[cell_num])
 | 
						|
 | 
						|
    def consider_call_site(self, frame, cut_frame):
 | 
						|
        if len(self.call_sites) > MAX_CALL_SITES:
 | 
						|
            return
 | 
						|
        stack = [i[0] for i in inspect.stack()]
 | 
						|
        cs = cut_stack(stack, frame, cut_frame)
 | 
						|
        self.call_sites[cs] = cs
 | 
						|
    
 | 
						|
    def consider_exception(self, exc, value):
 | 
						|
        self.exceptions[exc] = True
 | 
						|
    
 | 
						|
    def get_call_sites(self):
 | 
						|
        # convinient accessor for various data which we keep there
 | 
						|
        if not self.keep_frames:
 | 
						|
            return [(key, val) for key, val in self.call_sites.iteritems()]
 | 
						|
        else:
 | 
						|
            lst = []
 | 
						|
            for key, val in self.call_sites.iteritems():
 | 
						|
                for frame in val:
 | 
						|
                    lst.append((key, frame))
 | 
						|
            return lst
 | 
						|
    
 | 
						|
    def consider_return(self, arg):
 | 
						|
        self.retval = model.unionof(arg, self.retval)
 | 
						|
 | 
						|
    def consider_start_locals(self, frame):
 | 
						|
        pass
 | 
						|
 | 
						|
    def consider_end_locals(self, frame):
 | 
						|
        pass
 | 
						|
    
 | 
						|
    def getcode(self):
 | 
						|
        return self.pyobj.func_code
 | 
						|
    code = property(getcode)
 | 
						|
    
 | 
						|
    def get_local_changes(self):
 | 
						|
        return {}
 | 
						|
    
 | 
						|
class ClassDesc(Desc):
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        super(ClassDesc, self).__init__(*args, **kwargs)
 | 
						|
        self.fields = {}
 | 
						|
        # we'll gather informations about methods and possibly
 | 
						|
        # other variables encountered here
 | 
						|
    
 | 
						|
    def getcode(self):
 | 
						|
        # This is a hack. We're trying to return as much close to __init__
 | 
						|
        # of us as possible, but still hashable object
 | 
						|
        if hasattr(self.pyobj, '__init__'):
 | 
						|
            if hasattr(self.pyobj.__init__, 'im_func') and \
 | 
						|
                hasattr(self.pyobj.__init__.im_func, 'func_code'):
 | 
						|
                result = self.pyobj.__init__.im_func.func_code
 | 
						|
            else:
 | 
						|
                result = self.pyobj.__init__
 | 
						|
        else:
 | 
						|
            result = self.pyobj
 | 
						|
        try:
 | 
						|
            hash(result)
 | 
						|
        except KeyboardInterrupt, SystemExit:
 | 
						|
            raise
 | 
						|
        except: # XXX UUuuuu bare except here. What can it really rise???
 | 
						|
            try:
 | 
						|
                hash(self.pyobj)
 | 
						|
                result = self.pyobj
 | 
						|
            except:
 | 
						|
                result = self
 | 
						|
        return result
 | 
						|
    code = property(getcode)
 | 
						|
    
 | 
						|
    def consider_call(self, inputcells):
 | 
						|
        if '__init__' in self.fields:
 | 
						|
            md = self.fields['__init__']
 | 
						|
        else:
 | 
						|
            md = MethodDesc(self.name + '.__init__', self.pyobj.__init__)
 | 
						|
            self.fields['__init__'] = md
 | 
						|
        md.consider_call(inputcells)
 | 
						|
    
 | 
						|
    def consider_return(self, arg):
 | 
						|
        pass # we *know* what return value we do have
 | 
						|
    
 | 
						|
    def consider_exception(self, exc, value):
 | 
						|
        if '__init__' in self.fields:
 | 
						|
            md = self.fields['__init__']
 | 
						|
        else:
 | 
						|
            md = MethodDesc(self.name + '.__init__', self.pyobj.__init__)
 | 
						|
            self.fields['__init__'] = md
 | 
						|
        md.consider_exception(exc, value)
 | 
						|
 | 
						|
    def consider_start_locals(self, frame):
 | 
						|
        if '__init__' in self.fields:
 | 
						|
            md = self.fields['__init__']
 | 
						|
            md.consider_start_locals(frame)
 | 
						|
 | 
						|
    def consider_end_locals(self, frame):
 | 
						|
        if '__init__' in self.fields:
 | 
						|
            md = self.fields['__init__']
 | 
						|
            md.consider_end_locals(frame)
 | 
						|
    
 | 
						|
    def consider_call_site(self, frame, cut_frame):
 | 
						|
        self.fields['__init__'].consider_call_site(frame, cut_frame)
 | 
						|
    
 | 
						|
    def add_method_desc(self, name, methoddesc):
 | 
						|
        self.fields[name] = methoddesc
 | 
						|
    
 | 
						|
    def getfields(self):
 | 
						|
        # return fields of values that has been used
 | 
						|
        l = [i for i, v in self.fields.iteritems() if not is_private(i)]
 | 
						|
        return l
 | 
						|
 | 
						|
    def getbases(self):
 | 
						|
        bases = []
 | 
						|
        tovisit = [self.pyobj]
 | 
						|
        while tovisit:
 | 
						|
            current = tovisit.pop()
 | 
						|
            if current is not self.pyobj:
 | 
						|
                bases.append(current)
 | 
						|
            tovisit += [b for b in current.__bases__ if b not in bases]
 | 
						|
        return bases
 | 
						|
    bases = property(getbases)
 | 
						|
    
 | 
						|
##    def has_code(self, code):
 | 
						|
##        # check __init__ method
 | 
						|
##        return self.pyobj.__init__.im_func.func_code is code
 | 
						|
##    
 | 
						|
##    def consider_call(self, inputcells):
 | 
						|
##        # special thing, make MethodDesc for __init__
 | 
						|
##        
 | 
						|
##
 | 
						|
class MethodDesc(FunctionDesc):
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        super(MethodDesc, self).__init__(*args, **kwargs)
 | 
						|
        self.old_dict = {}
 | 
						|
        self.changeset = {}
 | 
						|
 | 
						|
    # right now it's not different than method desc, only code is different
 | 
						|
    def getcode(self):
 | 
						|
        return self.pyobj.im_func.func_code
 | 
						|
    code = property(getcode)
 | 
						|
##    def has_code(self, code):
 | 
						|
##        return self.pyobj.im_func.func_code is code
 | 
						|
 | 
						|
    def __hash__(self):
 | 
						|
        return hash((self.code, self.pyobj.im_class))
 | 
						|
    
 | 
						|
    def __eq__(self, other):
 | 
						|
        if isinstance(other, tuple):
 | 
						|
            return self.code is other[0] and self.pyobj.im_class is other[1]
 | 
						|
        if isinstance(other, MethodDesc):
 | 
						|
            return self.pyobj is other.pyobj
 | 
						|
        return False
 | 
						|
 | 
						|
    def consider_start_locals(self, frame):
 | 
						|
        # XXX recursion issues?
 | 
						|
        obj = frame.f_locals[self.pyobj.im_func.func_code.co_varnames[0]]
 | 
						|
        try:
 | 
						|
            if not obj:
 | 
						|
                # static method
 | 
						|
                return
 | 
						|
        except AttributeError:
 | 
						|
            return
 | 
						|
        self.old_dict = self.perform_dict_copy(obj.__dict__)
 | 
						|
 | 
						|
    def perform_dict_copy(self, d):
 | 
						|
        if d is None:
 | 
						|
            return {}
 | 
						|
        return d.copy()
 | 
						|
 | 
						|
    def consider_end_locals(self, frame):
 | 
						|
        obj = frame.f_locals[self.pyobj.im_func.func_code.co_varnames[0]]
 | 
						|
        try:
 | 
						|
            if not obj:
 | 
						|
                # static method
 | 
						|
                return
 | 
						|
        except AttributeError:
 | 
						|
            return
 | 
						|
        # store the local changes
 | 
						|
        # update self.changeset
 | 
						|
        self.update_changeset(obj.__dict__)
 | 
						|
 | 
						|
    def get_local_changes(self):
 | 
						|
        return self.changeset
 | 
						|
    
 | 
						|
    def set_changeset(changeset, key, value):
 | 
						|
        if key not in changeset:
 | 
						|
            changeset[key] = set([value])
 | 
						|
        else:
 | 
						|
            changeset[key].add(value)
 | 
						|
    set_changeset = staticmethod(set_changeset)
 | 
						|
    
 | 
						|
    def update_changeset(self, new_dict):
 | 
						|
        changeset = self.changeset
 | 
						|
        for k, v in self.old_dict.iteritems():
 | 
						|
            if k not in new_dict:
 | 
						|
                self.set_changeset(changeset, k, "deleted")
 | 
						|
            elif new_dict[k] != v:
 | 
						|
                self.set_changeset(changeset, k, "changed")
 | 
						|
        for k, v in new_dict.iteritems():
 | 
						|
            if k not in self.old_dict:
 | 
						|
                self.set_changeset(changeset, k, "created")
 | 
						|
        return changeset
 | 
						|
 |