366 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			366 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
 | |
|         try:
 | |
|             self.source = getsource(frame.code.raw)
 | |
|         except IOError:
 | |
|             self.source = "could not get to source"
 | |
| 
 | |
|     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
 | |
|         lst = [py.code.Frame(i) for i in stack[stack.index(frame):\
 | |
|                 stack.index(upward_frame)+1]]
 | |
|         if len(lst) > 1:
 | |
|             return CallStack(lst[:-1])
 | |
|         return CallStack(lst)
 | |
|     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
 | |
| 
 |