def cmp_code_objects(version, is_pypy, code_obj1, code_obj2, name=''): """ Compare two code-objects. This is the main part of this module. """ # print code_obj1, type(code_obj2) assert iscode(code_obj1), \ "cmp_code_object first object type is %s, not code" % type(code_obj1) assert iscode(code_obj2), \ "cmp_code_object second object type is %s, not code" % type(code_obj2) # print dir(code_obj1) if isinstance(code_obj1, object): # new style classes (Python 2.2) # assume _both_ code objects to be new stle classes assert dir(code_obj1) == dir(code_obj2) else: # old style classes assert dir(code_obj1) == code_obj1.__members__ assert dir(code_obj2) == code_obj2.__members__ assert code_obj1.__members__ == code_obj2.__members__ if name == '__main__': name = code_obj1.co_name else: name = '%s.%s' % (name, code_obj1.co_name) if name == '.?': name = '__main__' if isinstance(code_obj1, object) and code_equal(code_obj1, code_obj2): # use the new style code-classes' __cmp__ method, which # should be faster and more sophisticated # if this compare fails, we use the old routine to # find out, what exactly is nor equal # if this compare succeds, simply return # return pass if isinstance(code_obj1, object): members = [x for x in dir(code_obj1) if x.startswith('co_')] else: members = dir(code_obj1) members.sort() # ; members.reverse() tokens1 = None for member in members: if member in __IGNORE_CODE_MEMBERS__: pass elif member == 'co_code': if version == 2.3: import uncompyle6.scanners.scanner23 as scan scanner = scan.Scanner26() elif version == 2.4: import uncompyle6.scanners.scanner24 as scan scanner = scan.Scanner25() elif version == 2.5: import uncompyle6.scanners.scanner25 as scan scanner = scan.Scanner25() elif version == 2.6: import uncompyle6.scanners.scanner26 as scan scanner = scan.Scanner26() elif version == 2.7: if is_pypy: import uncompyle6.scanners.pypy27 as scan scanner = scan.ScannerPyPy27(show_asm=False) else: import uncompyle6.scanners.scanner27 as scan scanner = scan.Scanner27() elif version == 3.2: if is_pypy: import uncompyle6.scanners.pypy32 as scan scanner = scan.ScannerPyPy32() else: import uncompyle6.scanners.scanner32 as scan scanner = scan.Scanner32() elif version == 3.3: import uncompyle6.scanners.scanner33 as scan scanner = scan.Scanner33() elif version == 3.4: import uncompyle6.scanners.scanner34 as scan scanner = scan.Scanner34() elif version == 3.5: import uncompyle6.scanners.scanner35 as scan scanner = scan.Scanner35() elif version == 3.6: import uncompyle6.scanners.scanner36 as scan scanner = scan.Scanner36() global JUMP_OPs JUMP_OPs = list(scan.JUMP_OPs) + ['JUMP_BACK'] # use changed Token class # We (re)set this here to save exception handling, # which would get confusing. scanner.setTokenClass(Token) try: # disassemble both code-objects tokens1, customize = scanner.disassemble(code_obj1) del customize # save memory tokens2, customize = scanner.disassemble(code_obj2) del customize # save memory finally: scanner.resetTokenClass() # restore Token class targets1 = dis.findlabels(code_obj1.co_code) tokens1 = [t for t in tokens1 if t.type != 'COME_FROM'] tokens2 = [t for t in tokens2 if t.type != 'COME_FROM'] i1 = 0; i2 = 0 offset_map = {}; check_jumps = {} while i1 < len(tokens1): if i2 >= len(tokens2): if len(tokens1) == len(tokens2) + 2 \ and tokens1[-1].type == 'RETURN_VALUE' \ and tokens1[-2].type == 'LOAD_CONST' \ and tokens1[-2].pattr is None \ and tokens1[-3].type == 'RETURN_VALUE': break else: raise CmpErrorCodeLen(name, tokens1, tokens2) offset_map[tokens1[i1].offset] = tokens2[i2].offset for idx1, idx2, offset2 in check_jumps.get(tokens1[i1].offset, []): if offset2 != tokens2[i2].offset: raise CmpErrorCode(name, tokens1[idx1].offset, tokens1[idx1], tokens2[idx2], tokens1, tokens2) if tokens1[i1].type != tokens2[i2].type: if tokens1[i1].type == 'LOAD_CONST' == tokens2[i2].type: i = 1 while tokens1[i1+i].type == 'LOAD_CONST': i += 1 if tokens1[i1+i].type.startswith(('BUILD_TUPLE', 'BUILD_LIST')) \ and i == int(tokens1[i1+i].type.split('_')[-1]): t = tuple([ elem.pattr for elem in tokens1[i1:i1+i] ]) if t != tokens2[i2].pattr: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) i1 += i + 1 i2 += 1 continue elif i == 2 and tokens1[i1+i].type == 'ROT_TWO' and tokens2[i2+1].type == 'UNPACK_SEQUENCE_2': i1 += 3 i2 += 2 continue elif i == 2 and tokens1[i1+i].type in BIN_OP_FUNCS: f = BIN_OP_FUNCS[tokens1[i1+i].type] if f(tokens1[i1].pattr, tokens1[i1+1].pattr) == tokens2[i2].pattr: i1 += 3 i2 += 1 continue elif tokens1[i1].type == 'UNARY_NOT': if tokens2[i2].type == 'POP_JUMP_IF_TRUE': if tokens1[i1+1].type == 'POP_JUMP_IF_FALSE': i1 += 2 i2 += 1 continue elif tokens2[i2].type == 'POP_JUMP_IF_FALSE': if tokens1[i1+1].type == 'POP_JUMP_IF_TRUE': i1 += 2 i2 += 1 continue elif tokens1[i1].type in ('JUMP_FORWARD', 'JUMP_BACK') \ and tokens1[i1-1].type == 'RETURN_VALUE' \ and tokens2[i2-1].type in ('RETURN_VALUE', 'RETURN_END_IF') \ and int(tokens1[i1].offset) not in targets1: i1 += 1 continue elif tokens1[i1].type == 'JUMP_FORWARD' and tokens2[i2].type == 'JUMP_BACK' \ and tokens1[i1+1].type == 'JUMP_BACK' and tokens2[i2+1].type == 'JUMP_BACK' \ and int(tokens1[i1].pattr) == int(tokens1[i1].offset) + 3: if int(tokens1[i1].pattr) == int(tokens1[i1+1].offset): i1 += 2 i2 += 2 continue raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) elif tokens1[i1].type in JUMP_OPs and tokens1[i1].pattr != tokens2[i2].pattr: dest1 = int(tokens1[i1].pattr) dest2 = int(tokens2[i2].pattr) if tokens1[i1].type == 'JUMP_BACK': if offset_map[dest1] != dest2: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) else: # import pdb; pdb.set_trace() if dest1 in check_jumps: check_jumps[dest1].append((i1, i2, dest2)) else: check_jumps[dest1] = [(i1, i2, dest2)] i1 += 1 i2 += 1 del tokens1, tokens2 # save memory elif member == 'co_consts': # partial optimization can make the co_consts look different, # so we'll just compare the code consts codes1 = ( c for c in code_obj1.co_consts if hasattr(c, 'co_consts') ) codes2 = ( c for c in code_obj2.co_consts if hasattr(c, 'co_consts') ) for c1, c2 in zip(codes1, codes2): cmp_code_objects(version, is_pypy, c1, c2, name=name) else: # all other members must be equal if getattr(code_obj1, member) != getattr(code_obj2, member): raise CmpErrorMember(name, member, getattr(code_obj1, member), getattr(code_obj2, member))
def cmp_code_objects(version, is_pypy, code_obj1, code_obj2, name='', ignore_code=False): """ Compare two code-objects. This is the main part of this module. """ # print code_obj1, type(code_obj2) assert iscode(code_obj1), \ "cmp_code_object first object type is %s, not code" % type(code_obj1) assert iscode(code_obj2), \ "cmp_code_object second object type is %s, not code" % type(code_obj2) # print dir(code_obj1) if isinstance(code_obj1, object): # new style classes (Python 2.2) # assume _both_ code objects to be new stle classes assert dir(code_obj1) == dir(code_obj2) else: # old style classes assert dir(code_obj1) == code_obj1.__members__ assert dir(code_obj2) == code_obj2.__members__ assert code_obj1.__members__ == code_obj2.__members__ if name == '__main__': name = code_obj1.co_name else: name = '%s.%s' % (name, code_obj1.co_name) if name == '.?': name = '__main__' if isinstance(code_obj1, object) and code_equal(code_obj1, code_obj2): # use the new style code-classes' __cmp__ method, which # should be faster and more sophisticated # if this compare fails, we use the old routine to # find out, what exactly is nor equal # if this compare succeds, simply return # return pass if isinstance(code_obj1, object): members = [x for x in dir(code_obj1) if x.startswith('co_')] else: members = dir(code_obj1) members.sort() # ; members.reverse() tokens1 = None for member in members: if member in __IGNORE_CODE_MEMBERS__ or ignore_code: pass elif member == 'co_code' and not ignore_code: if version == 2.3: import uncompyle6.scanners.scanner23 as scan scanner = scan.Scanner23(show_asm=False) elif version == 2.4: import uncompyle6.scanners.scanner24 as scan scanner = scan.Scanner24(show_asm=False) elif version == 2.5: import uncompyle6.scanners.scanner25 as scan scanner = scan.Scanner25(show_asm=False) elif version == 2.6: import uncompyle6.scanners.scanner26 as scan scanner = scan.Scanner26(show_asm=False) elif version == 2.7: if is_pypy: import uncompyle6.scanners.pypy27 as scan scanner = scan.ScannerPyPy27(show_asm=False) else: import uncompyle6.scanners.scanner27 as scan scanner = scan.Scanner27() elif version == 3.0: import uncompyle6.scanners.scanner30 as scan scanner = scan.Scanner30() elif version == 3.1: import uncompyle6.scanners.scanner32 as scan scanner = scan.Scanner32() elif version == 3.2: if is_pypy: import uncompyle6.scanners.pypy32 as scan scanner = scan.ScannerPyPy32() else: import uncompyle6.scanners.scanner32 as scan scanner = scan.Scanner32() elif version == 3.3: import uncompyle6.scanners.scanner33 as scan scanner = scan.Scanner33() elif version == 3.4: import uncompyle6.scanners.scanner34 as scan scanner = scan.Scanner34() elif version == 3.5: import uncompyle6.scanners.scanner35 as scan scanner = scan.Scanner35() elif version == 3.6: import uncompyle6.scanners.scanner36 as scan scanner = scan.Scanner36() global JUMP_OPS JUMP_OPS = list(scan.JUMP_OPS) + ['JUMP_BACK'] # use changed Token class # We (re)set this here to save exception handling, # which would get confusing. scanner.setTokenClass(Token) try: # ingest both code-objects tokens1, customize = scanner.ingest(code_obj1) del customize # save memory tokens2, customize = scanner.ingest(code_obj2) del customize # save memory finally: scanner.resetTokenClass() # restore Token class targets1 = dis.findlabels(code_obj1.co_code) tokens1 = [t for t in tokens1 if t.kind != 'COME_FROM'] tokens2 = [t for t in tokens2 if t.kind != 'COME_FROM'] i1 = 0 i2 = 0 offset_map = {} check_jumps = {} while i1 < len(tokens1): if i2 >= len(tokens2): if len(tokens1) == len(tokens2) + 2 \ and tokens1[-1].kind == 'RETURN_VALUE' \ and tokens1[-2].kind == 'LOAD_CONST' \ and tokens1[-2].pattr is None \ and tokens1[-3].kind == 'RETURN_VALUE': break else: raise CmpErrorCodeLen(name, tokens1, tokens2) offset_map[tokens1[i1].offset] = tokens2[i2].offset for idx1, idx2, offset2 in check_jumps.get( tokens1[i1].offset, []): if offset2 != tokens2[i2].offset: raise CmpErrorCode(name, tokens1[idx1].offset, tokens1[idx1], tokens2[idx2], tokens1, tokens2) if tokens1[i1].kind != tokens2[i2].kind: if tokens1[i1].kind == 'LOAD_CONST' == tokens2[i2].kind: i = 1 while tokens1[i1 + i].kind == 'LOAD_CONST': i += 1 if tokens1[i1+i].kind.startswith(('BUILD_TUPLE', 'BUILD_LIST')) \ and i == int(tokens1[i1+i].kind.split('_')[-1]): t = tuple( [elem.pattr for elem in tokens1[i1:i1 + i]]) if t != tokens2[i2].pattr: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) i1 += i + 1 i2 += 1 continue elif i == 2 and tokens1[ i1 + i].kind == 'ROT_TWO' and tokens2[ i2 + 1].kind == 'UNPACK_SEQUENCE_2': i1 += 3 i2 += 2 continue elif i == 2 and tokens1[i1 + i].kind in BIN_OP_FUNCS: f = BIN_OP_FUNCS[tokens1[i1 + i].kind] if f(tokens1[i1].pattr, tokens1[i1 + 1].pattr) == tokens2[i2].pattr: i1 += 3 i2 += 1 continue elif tokens1[i1].kind == 'UNARY_NOT': if tokens2[i2].kind == 'POP_JUMP_IF_TRUE': if tokens1[i1 + 1].kind == 'POP_JUMP_IF_FALSE': i1 += 2 i2 += 1 continue elif tokens2[i2].kind == 'POP_JUMP_IF_FALSE': if tokens1[i1 + 1].kind == 'POP_JUMP_IF_TRUE': i1 += 2 i2 += 1 continue elif tokens1[i1].kind in ('JUMP_FORWARD', 'JUMP_BACK') \ and tokens1[i1-1].kind == 'RETURN_VALUE' \ and tokens2[i2-1].kind in ('RETURN_VALUE', 'RETURN_END_IF') \ and int(tokens1[i1].offset) not in targets1: i1 += 1 continue elif tokens1[i1].kind == 'JUMP_BACK' and tokens2[ i2].kind == 'CONTINUE': # FIXME: should make sure that offset is inside loop, not outside of it i1 += 2 i2 += 2 continue elif tokens1[i1].kind == 'JUMP_FORWARD' and tokens2[i2].kind == 'JUMP_BACK' \ and tokens1[i1+1].kind == 'JUMP_BACK' and tokens2[i2+1].kind == 'JUMP_BACK' \ and int(tokens1[i1].pattr) == int(tokens1[i1].offset) + 3: if int(tokens1[i1].pattr) == int(tokens1[i1 + 1].offset): i1 += 2 i2 += 2 continue elif tokens1[i1].kind == 'LOAD_NAME' and tokens2[i2].kind == 'LOAD_CONST' \ and tokens1[i1].pattr == 'None' and tokens2[i2].pattr is None: pass elif tokens1[i1].kind == 'LOAD_GLOBAL' and tokens2[i2].kind == 'LOAD_NAME' \ and tokens1[i1].pattr == tokens2[i2].pattr: pass elif tokens1[i1].kind == 'LOAD_ASSERT' and tokens2[i2].kind == 'LOAD_NAME' \ and tokens1[i1].pattr == tokens2[i2].pattr: pass elif (tokens1[i1].kind == 'RETURN_VALUE' and tokens2[i2].kind == 'RETURN_END_IF'): pass elif (tokens1[i1].kind == 'BUILD_TUPLE_0' and tokens2[i2].pattr == ()): pass else: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) elif tokens1[i1].kind in JUMP_OPS and tokens1[ i1].pattr != tokens2[i2].pattr: if tokens1[i1].kind == 'JUMP_BACK': dest1 = int(tokens1[i1].pattr) dest2 = int(tokens2[i2].pattr) if offset_map[dest1] != dest2: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) else: # import pdb; pdb.set_trace() try: dest1 = int(tokens1[i1].pattr) if dest1 in check_jumps: check_jumps[dest1].append((i1, i2, dest2)) else: check_jumps[dest1] = [(i1, i2, dest2)] except: pass i1 += 1 i2 += 1 del tokens1, tokens2 # save memory elif member == 'co_consts': # partial optimization can make the co_consts look different, # so we'll just compare the code consts codes1 = (c for c in code_obj1.co_consts if hasattr(c, 'co_consts')) codes2 = (c for c in code_obj2.co_consts if hasattr(c, 'co_consts')) for c1, c2 in zip(codes1, codes2): cmp_code_objects(version, is_pypy, c1, c2, name=name) elif member == 'co_flags': flags1 = code_obj1.co_flags flags2 = code_obj2.co_flags if is_pypy: # For PYPY for now we don't care about PYPY_SOURCE_IS_UTF8: flags2 &= ~0x0100 # PYPY_SOURCE_IS_UTF8 # We also don't care about COROUTINE or GENERATOR for now flags1 &= ~0x000000a0 flags2 &= ~0x000000a0 if flags1 != flags2: raise CmpErrorMember(name, 'co_flags', pretty_flags(flags1), pretty_flags(flags2)) else: # all other members must be equal if getattr(code_obj1, member) != getattr(code_obj2, member): raise CmpErrorMember(name, member, getattr(code_obj1, member), getattr(code_obj2, member))
def cmp_code_objects(version, code_obj1, code_obj2, name=''): """ Compare two code-objects. This is the main part of this module. """ # print code_obj1, type(code_obj2) assert code_obj1, hasattr('co_name') assert code_obj2, hasattr('co_name') # print dir(code_obj1) if isinstance(code_obj1, object): # new style classes (Python 2.2) # assume _both_ code objects to be new stle classes assert dir(code_obj1) == dir(code_obj2) else: # old style classes assert dir(code_obj1) == code_obj1.__members__ assert dir(code_obj2) == code_obj2.__members__ assert code_obj1.__members__ == code_obj2.__members__ if name == '__main__': name = code_obj1.co_name else: name = '%s.%s' % (name, code_obj1.co_name) if name == '.?': name = '__main__' if isinstance(code_obj1, object) and code_equal(code_obj1, code_obj2): # use the new style code-classes' __cmp__ method, which # should be faster and more sophisticated # if this compare fails, we use the old routine to # find out, what exactly is nor equal # if this compare succeds, simply return # return pass if isinstance(code_obj1, object): members = [x for x in dir(code_obj1) if x.startswith('co_')] else: members = dir(code_obj1) members.sort() # ; members.reverse() tokens1 = None for member in members: if member in __IGNORE_CODE_MEMBERS__: pass elif member == 'co_code': if version == 2.5: import uncompyle6.scanners.scanner25 as scan scanner = scan.Scanner25() elif version == 2.6: import uncompyle6.scanners.scanner26 as scan scanner = scan.Scanner26() elif version == 2.7: import uncompyle6.scanners.scanner27 as scan scanner = scan.Scanner27() elif version == 3.2: import uncompyle6.scanners.scanner32 as scan scanner = scan.Scanner32() elif version == 3.3: import uncompyle6.scanners.scanner33 as scan scanner = scan.Scanner33() elif version == 3.4: import uncompyle6.scanners.scanner34 as scan scanner = scan.Scanner34() global JUMP_OPs JUMP_OPs = list(scan.JUMP_OPs) + ['JUMP_BACK'] # use changed Token class # we (re)set this here to save exception handling, # which would get 'unubersichtlich' scanner.setTokenClass(Token) try: # disassemble both code-objects tokens1, customize = scanner.disassemble(code_obj1) del customize # save memory tokens2, customize = scanner.disassemble(code_obj2) del customize # save memory finally: scanner.resetTokenClass() # restore Token class targets1 = dis.findlabels(code_obj1.co_code) tokens1 = [t for t in tokens1 if t.type != 'COME_FROM'] tokens2 = [t for t in tokens2 if t.type != 'COME_FROM'] i1 = 0 i2 = 0 offset_map = {} check_jumps = {} while i1 < len(tokens1): if i2 >= len(tokens2): if len(tokens1) == len(tokens2) + 2 \ and tokens1[-1].type == 'RETURN_VALUE' \ and tokens1[-2].type == 'LOAD_CONST' \ and tokens1[-2].pattr is None \ and tokens1[-3].type == 'RETURN_VALUE': break else: raise CmpErrorCodeLen(name, tokens1, tokens2) offset_map[tokens1[i1].offset] = tokens2[i2].offset for idx1, idx2, offset2 in check_jumps.get( tokens1[i1].offset, []): if offset2 != tokens2[i2].offset: raise CmpErrorCode(name, tokens1[idx1].offset, tokens1[idx1], tokens2[idx2], tokens1, tokens2) if tokens1[i1].type != tokens2[i2].type: if tokens1[i1].type == 'LOAD_CONST' == tokens2[i2].type: i = 1 while tokens1[i1 + i].type == 'LOAD_CONST': i += 1 if tokens1[i1+i].type.startswith(('BUILD_TUPLE', 'BUILD_LIST')) \ and i == int(tokens1[i1+i].type.split('_')[-1]): t = tuple( [elem.pattr for elem in tokens1[i1:i1 + i]]) if t != tokens2[i2].pattr: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) i1 += i + 1 i2 += 1 continue elif i == 2 and tokens1[ i1 + i].type == 'ROT_TWO' and tokens2[ i2 + 1].type == 'UNPACK_SEQUENCE_2': i1 += 3 i2 += 2 continue elif i == 2 and tokens1[i1 + i].type in BIN_OP_FUNCS: f = BIN_OP_FUNCS[tokens1[i1 + i].type] if f(tokens1[i1].pattr, tokens1[i1 + 1].pattr) == tokens2[i2].pattr: i1 += 3 i2 += 1 continue elif tokens1[i1].type == 'UNARY_NOT': if tokens2[i2].type == 'POP_JUMP_IF_TRUE': if tokens1[i1 + 1].type == 'POP_JUMP_IF_FALSE': i1 += 2 i2 += 1 continue elif tokens2[i2].type == 'POP_JUMP_IF_FALSE': if tokens1[i1 + 1].type == 'POP_JUMP_IF_TRUE': i1 += 2 i2 += 1 continue elif tokens1[i1].type in ('JUMP_FORWARD', 'JUMP_BACK') \ and tokens1[i1-1].type == 'RETURN_VALUE' \ and tokens2[i2-1].type in ('RETURN_VALUE', 'RETURN_END_IF') \ and int(tokens1[i1].offset) not in targets1: i1 += 1 continue elif tokens1[i1].type == 'JUMP_FORWARD' and tokens2[i2].type == 'JUMP_BACK' \ and tokens1[i1+1].type == 'JUMP_BACK' and tokens2[i2+1].type == 'JUMP_BACK' \ and int(tokens1[i1].pattr) == int(tokens1[i1].offset) + 3: if int(tokens1[i1].pattr) == int(tokens1[i1 + 1].offset): i1 += 2 i2 += 2 continue raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) elif tokens1[i1].type in JUMP_OPs and tokens1[ i1].pattr != tokens2[i2].pattr: dest1 = int(tokens1[i1].pattr) dest2 = int(tokens2[i2].pattr) if tokens1[i1].type == 'JUMP_BACK': if offset_map[dest1] != dest2: raise CmpErrorCode(name, tokens1[i1].offset, tokens1[i1], tokens2[i2], tokens1, tokens2) else: # import pdb; pdb.set_trace() if dest1 in check_jumps: check_jumps[dest1].append((i1, i2, dest2)) else: check_jumps[dest1] = [(i1, i2, dest2)] i1 += 1 i2 += 1 del tokens1, tokens2 # save memory elif member == 'co_consts': # partial optimization can make the co_consts look different, # so we'll just compare the code consts codes1 = (c for c in code_obj1.co_consts if hasattr(c, 'co_consts')) codes2 = (c for c in code_obj2.co_consts if hasattr(c, 'co_consts')) for c1, c2 in zip(codes1, codes2): cmp_code_objects(version, c1, c2, name=name) else: # all other members must be equal if getattr(code_obj1, member) != getattr(code_obj2, member): raise CmpErrorMember(name, member, getattr(code_obj1, member), getattr(code_obj2, member))