Merge branch 'controlflow'

This commit is contained in:
rocky
2016-10-06 03:26:44 -04:00
14 changed files with 231 additions and 183 deletions

View File

@@ -29,7 +29,9 @@ augmented with pseudo instruction COME_FROM. This code introduced
another clever idea: using table-driven semantics routines, using
format specifiers.
The last mention of a release of SPARK from John is around 2002.
The last mention of a release of SPARK from John is around 2002. As
released, although the Early Algorithm parser was in good shape, this
code was woefully lacking as serious Python deparser.
In the fall of 2000, Hartmut Goebel
[took over maintaining the code](https://groups.google.com/forum/#!searchin/comp.lang.python/hartmut$20goebel/comp.lang.python/35s3mp4-nuY/UZALti6ujnQJ). The
@@ -112,12 +114,18 @@ Fenx's uncompyle3 which I used for inspiration for Python3 support.
I started working on this late 2015, mostly to add fragment support.
In that, I decided to make this runnable on Python 3.2+ and Python 2.6+
while, handling Python bytecodes from Python versions 2.5+ and
3.2+.
3.2+. In doing so, it has been expedient to separate this into three
projects: load loading and disassembly (xdis), parsing and tree
building (spark_parser), and grammar and semantic actions for
decompiling (uncompyle6).
Over the many years, code styles and Python features have
changed. However brilliant the code was and still is, it hasn't really
had a single public active maintainer. And there have been many forks
of the code.
of the code. I have spent a great deal of time trying to organize and
modularize the code so that it can handle more Python versions more
gracefully (with still only moderate success).
That it has been in need of an overhaul has been recognized by the
Hartmut a decade an a half ago:

View File

@@ -31,9 +31,12 @@ def test_grammar():
assert expect_right_recursive == right_recursive
s = get_scanner(PYTHON_VERSION, IS_PYPY)
ignore_set = set(
"""JUMP_BACK CONTINUE RETURN_END_IF COME_FROM
LOAD_GENEXPR LOAD_ASSERT LOAD_SETCOMP LOAD_DICTCOMP
LAMBDA_MARKER RETURN_LAST
"""
JUMP_BACK CONTINUE RETURN_END_IF
COME_FROM COME_FROM_EXCEPT COME_FROM_LOOP COME_FROM_WITH
COME_FROM_FINALLY
LOAD_GENEXPR LOAD_ASSERT LOAD_SETCOMP LOAD_DICTCOMP
LAMBDA_MARKER RETURN_LAST
""".split())
if 2.6 <= PYTHON_VERSION <= 2.7:
opcode_set = set(s.opc.opname).union(ignore_set)

View File

@@ -9,8 +9,6 @@ def test_single_mode():
'i = j % 4',
'i = {}',
'i = []',
'while i < 1 or stop:\n i\n',
'while i < 1 or stop:\n print%s\n' % ('(i)' if PYTHON3 else ' i'),
'for i in range(10):\n i\n',
'for i in range(10):\n for j in range(10):\n i + j\n',
'try:\n i\nexcept Exception:\n j\nelse:\n k\n'

View File

@@ -24,7 +24,7 @@ check-2.6 check-2.7: check-bytecode-2 check-bytecode-3 check-2.7-ok
#: Run working tests from Python 3.1
check-3.1: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.1 --verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.1 --weak-verify $(COMPILE)
#: Run working tests from Python 3.2
check-3.2: check-bytecode
@@ -32,11 +32,11 @@ check-3.2: check-bytecode
#: Run working tests from Python 3.3
check-3.3: check-bytecode
$(PYTHON) test_pythonlib.py --bytecode-3.3 --verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.3 --weak-verify $(COMPILE)
#: Run working tests from Python 3.4
check-3.4: check-bytecode check-3.4-ok check-2.7-ok
$(PYTHON) test_pythonlib.py --bytecode-3.4 --verify $(COMPILE)
$(PYTHON) test_pythonlib.py --bytecode-3.4 --weak-verify $(COMPILE)
#: Run working tests from Python 3.5
check-3.5: check-bytecode

Binary file not shown.

View File

@@ -1,92 +0,0 @@
"""Bisection algorithms."""
def insort_right(a, x, lo=0, hi=None):
"""Insert item x in list a, and keep it sorted assuming a is sorted.
If x is already in a, insert it to the right of the rightmost x.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if x < a[mid]: hi = mid
else: lo = mid+1
a.insert(lo, x)
insort = insort_right # backward compatibility
def bisect_right(a, x, lo=0, hi=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e <= x, and all e in
a[i:] have e > x. So if x already appears in the list, a.insert(x) will
insert just after the rightmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if x < a[mid]: hi = mid
else: lo = mid+1
return lo
bisect = bisect_right # backward compatibility
def insort_left(a, x, lo=0, hi=None):
"""Insert item x in list a, and keep it sorted assuming a is sorted.
If x is already in a, insert it to the left of the leftmost x.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if a[mid] < x: lo = mid+1
else: hi = mid
a.insert(lo, x)
def bisect_left(a, x, lo=0, hi=None):
"""Return the index where to insert item x in list a, assuming a is sorted.
The return value i is such that all e in a[:i] have e < x, and all e in
a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
insert just before the leftmost x already there.
Optional args lo (default 0) and hi (default len(a)) bound the
slice of a to be searched.
"""
if lo < 0:
raise ValueError('lo must be non-negative')
if hi is None:
hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if a[mid] < x: lo = mid+1
else: hi = mid
return lo
# Overwrite above definitions with a fast C implementation
try:
from _bisect import *
except ImportError:
pass

View File

@@ -0,0 +1,28 @@
# From python 3.4 sre.pyc
while 1:
if __file__:
while 1:
if __file__:
break
raise RuntimeError
else:
raise RuntimeError
while 1:
if __file__:
if __name__:
raise RuntimeError
else:
# flags
while __name__:
group = 5
while 1:
if __name__:
while 1:
if y:
break
raise RuntimeError
elif __file__:
x = 2
else:
raise RuntimeError

View File

@@ -174,9 +174,33 @@ class PythonParser(GenericASTBuilder):
else_suitec ::= c_stmts
else_suitec ::= return_stmts
stmt ::= assert
stmt ::= assert2
stmt ::= classdef
stmt ::= call_stmt
stmt ::= ifstmt
stmt ::= ifelsestmt
stmt ::= whilestmt
stmt ::= while1stmt
stmt ::= whileelsestmt
stmt ::= while1elsestmt
stmt ::= forstmt
stmt ::= forelsestmt
stmt ::= trystmt
stmt ::= tryelsestmt
stmt ::= tryfinallystmt
stmt ::= withstmt
stmt ::= withasstmt
stmt ::= del_stmt
del_stmt ::= DELETE_FAST
del_stmt ::= DELETE_NAME
del_stmt ::= DELETE_GLOBAL
stmt ::= return_stmt
return_stmt ::= ret_expr RETURN_VALUE
return_stmts ::= return_stmt
@@ -287,20 +311,12 @@ class PythonParser(GenericASTBuilder):
def p_whilestmt(self, args):
"""
whilestmt ::= SETUP_LOOP
testexpr
l_stmts_opt JUMP_BACK
POP_BLOCK _come_from
whilestmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK
POP_BLOCK _come_from
whilestmt ::= SETUP_LOOP
testexpr
return_stmts
POP_BLOCK COME_FROM
whilestmt ::= SETUP_LOOP testexpr return_stmts
POP_BLOCK COME_FROM
while1stmt ::= SETUP_LOOP l_stmts JUMP_BACK COME_FROM
while1stmt ::= SETUP_LOOP l_stmts JUMP_BACK POP_BLOCK COME_FROM
while1elsestmt ::= SETUP_LOOP l_stmts JUMP_BACK else_suite COME_FROM
whileelsestmt ::= SETUP_LOOP testexpr
l_stmts_opt JUMP_BACK
POP_BLOCK

View File

@@ -42,7 +42,11 @@ class Python2Parser(PythonParser):
def p_stmt2(self, args):
"""
while1stmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK COME_FROM
while1stmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK COME_FROM
while1stmt ::= SETUP_LOOP l_stmts JUMP_BACK COME_FROM
while1stmt ::= SETUP_LOOP l_stmts JUMP_BACK POP_BLOCK COME_FROM
while1elsestmt ::= SETUP_LOOP l_stmts JUMP_BACK else_suite COME_FROM
exec_stmt ::= expr exprlist DUP_TOP EXEC_STMT
exec_stmt ::= expr exprlist EXEC_STMT
@@ -94,27 +98,6 @@ class Python2Parser(PythonParser):
stmt ::= exec_stmt
stmt ::= assert
stmt ::= assert2
stmt ::= ifstmt
stmt ::= ifelsestmt
stmt ::= whilestmt
stmt ::= while1stmt
stmt ::= whileelsestmt
stmt ::= while1elsestmt
stmt ::= forstmt
stmt ::= forelsestmt
stmt ::= trystmt
stmt ::= tryelsestmt
stmt ::= tryfinallystmt
stmt ::= withstmt
stmt ::= withasstmt
stmt ::= del_stmt
del_stmt ::= DELETE_FAST
del_stmt ::= DELETE_NAME
del_stmt ::= DELETE_GLOBAL
del_stmt ::= expr DELETE_SLICE+0
del_stmt ::= expr expr DELETE_SLICE+1
del_stmt ::= expr expr DELETE_SLICE+2

View File

@@ -95,27 +95,6 @@ class Python3Parser(PythonParser):
raise_stmt2 ::= expr expr RAISE_VARARGS_2
raise_stmt3 ::= expr expr expr RAISE_VARARGS_3
stmt ::= assert
stmt ::= assert2
stmt ::= ifstmt
stmt ::= ifelsestmt
stmt ::= whilestmt
stmt ::= while1stmt
stmt ::= whileelsestmt
stmt ::= while1elsestmt
stmt ::= forstmt
stmt ::= forelsestmt
stmt ::= trystmt
stmt ::= tryelsestmt
stmt ::= tryfinallystmt
stmt ::= withstmt
stmt ::= withasstmt
stmt ::= del_stmt
del_stmt ::= DELETE_FAST
del_stmt ::= DELETE_NAME
del_stmt ::= DELETE_GLOBAL
del_stmt ::= delete_subscr
delete_subscr ::= expr expr DELETE_SUBSCR
del_stmt ::= expr DELETE_ATTR
@@ -158,6 +137,7 @@ class Python3Parser(PythonParser):
iflaststmt ::= testexpr c_stmts_opt JUMP_ABSOLUTE
iflaststmtl ::= testexpr c_stmts_opt JUMP_BACK
iflaststmtl ::= testexpr c_stmts_opt JUMP_BACK COME_FROM_LOOP
ifelsestmt ::= testexpr c_stmts_opt JUMP_FORWARD else_suite COME_FROM
@@ -166,6 +146,8 @@ class Python3Parser(PythonParser):
ifelsestmtr ::= testexpr return_if_stmts return_stmts
ifelsestmtl ::= testexpr c_stmts_opt JUMP_BACK else_suitel
ifelsestmtl ::= testexpr c_stmts_opt JUMP_BACK else_suitel JUMP_BACK COME_FROM_LOOP
ifelsestmtl ::= testexpr c_stmts_opt JUMP_BACK else_suitel COME_FROM_LOOP
# FIXME: this feels like a hack. Is it just 1 or two
@@ -174,12 +156,12 @@ class Python3Parser(PythonParser):
# COME_FROM targets from the wrong places
trystmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
try_middle _come_from
try_middle opt_come_from_except
# this is nested inside a trystmt
tryfinallystmt ::= SETUP_FINALLY suite_stmts_opt
POP_BLOCK LOAD_CONST
COME_FROM suite_stmts_opt END_FINALLY
come_from_or_finally suite_stmts_opt END_FINALLY
tryelsestmt ::= SETUP_EXCEPT suite_stmts_opt POP_BLOCK
try_middle else_suite come_froms
@@ -192,8 +174,14 @@ class Python3Parser(PythonParser):
try_middle ::= jmp_abs COME_FROM except_stmts
END_FINALLY
try_middle ::= jmp_abs COME_FROM_EXCEPT except_stmts
END_FINALLY
# FIXME: remove this
try_middle ::= JUMP_FORWARD COME_FROM except_stmts
END_FINALLY COME_FROM
try_middle ::= JUMP_FORWARD COME_FROM except_stmts
END_FINALLY COME_FROM_EXCEPT
except_stmts ::= except_stmts except_stmt
except_stmts ::= except_stmt
@@ -218,8 +206,8 @@ class Python3Parser(PythonParser):
except_suite_finalize ::= SETUP_FINALLY c_stmts_opt except_var_finalize
END_FINALLY _jump
except_var_finalize ::= POP_BLOCK POP_EXCEPT LOAD_CONST COME_FROM LOAD_CONST
designator del_stmt
except_var_finalize ::= POP_BLOCK POP_EXCEPT LOAD_CONST come_from_or_finally
LOAD_CONST designator del_stmt
except_suite ::= return_stmts
@@ -236,11 +224,11 @@ class Python3Parser(PythonParser):
jmp_abs ::= JUMP_BACK
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt
POP_BLOCK LOAD_CONST COME_FROM
POP_BLOCK LOAD_CONST COME_FROM_WITH
WITH_CLEANUP END_FINALLY
withasstmt ::= expr SETUP_WITH designator suite_stmts_opt
POP_BLOCK LOAD_CONST COME_FROM
POP_BLOCK LOAD_CONST COME_FROM_WITH
WITH_CLEANUP END_FINALLY
and ::= expr jmp_false expr COME_FROM
@@ -249,17 +237,33 @@ class Python3Parser(PythonParser):
def p_misc3(self, args):
"""
try_middle ::= JUMP_FORWARD COME_FROM except_stmts END_FINALLY NOP COME_FROM
try_middle ::= JUMP_FORWARD COME_FROM_EXCEPT except_stmts END_FINALLY COME_FROM
for_block ::= l_stmts_opt opt_come_from_loop JUMP_BACK
for_block ::= l_stmts
iflaststmtl ::= testexpr c_stmts_opt
iflaststmt ::= testexpr c_stmts_opt34
c_stmts_opt34 ::= JUMP_BACK JUMP_ABSOLUTE c_stmts_opt
"""
def p_come_from3(self, args):
"""
opt_come_from_except ::= COME_FROM_EXCEPT
opt_come_from_except ::= come_froms
come_froms ::= come_froms COME_FROM
come_froms ::=
opt_come_from_loop ::= opt_come_from_loop COME_FROM_LOOP
opt_come_from_loop ::=
come_from_or_finally ::= COME_FROM_FINALLY
come_from_or_finally ::= COME_FROM
"""
def p_jump3(self, args):
"""
come_froms ::= come_froms COME_FROM
come_froms ::= COME_FROM
jmp_false ::= POP_JUMP_IF_FALSE
jmp_true ::= POP_JUMP_IF_TRUE
@@ -285,16 +289,59 @@ class Python3Parser(PythonParser):
stmt ::= LOAD_CLOSURE RETURN_VALUE RETURN_LAST
stmt ::= whileTruestmt
ifelsestmt ::= testexpr c_stmts_opt JUMP_FORWARD else_suite _come_from
"""
forstmt ::= SETUP_LOOP expr _for designator for_block POP_BLOCK NOP _come_from
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK _come_from
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK NOP _come_from
def p_loop_stmt3(self, args):
"""
forstmt ::= SETUP_LOOP expr _for designator for_block POP_BLOCK
opt_come_from_loop
forelsestmt ::= SETUP_LOOP expr _for designator for_block POP_BLOCK else_suite
COME_FROM_LOOP
forelselaststmt ::= SETUP_LOOP expr _for designator for_block POP_BLOCK else_suitec
COME_FROM_LOOP
forelselaststmtl ::= SETUP_LOOP expr _for designator for_block POP_BLOCK else_suitel
COME_FROM_LOOP
whilestmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK POP_BLOCK
COME_FROM_LOOP
# The JUMP_ABSOLUTE below comes from escaping an "if" block which surrounds
# the while. This is messy
whilestmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK POP_BLOCK
JUMP_ABSOLUTE COME_FROM_LOOP
whilestmt ::= SETUP_LOOP testexpr return_stmts POP_BLOCK
COME_FROM_LOOP
whileelsestmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK POP_BLOCK
else_suite COME_FROM_LOOP
while1elsestmt ::= SETUP_LOOP l_stmts JUMP_BACK
else_suite COME_FROM_LOOP
whileelselaststmt ::= SETUP_LOOP testexpr l_stmts_opt JUMP_BACK POP_BLOCK
else_suitec COME_FROM_LOOP
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK
COME_FROM_LOOP
while1stmt ::= SETUP_LOOP l_stmts
# Python < 3.5 no POP BLOCK
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK _come_from
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK NOP _come_from
whileTruestmt ::= SETUP_LOOP return_stmts _come_from
while1stmt ::= SETUP_LOOP l_stmts _come_from JUMP_BACK _come_from
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK
COME_FROM_LOOP
whileTruestmt ::= SETUP_LOOP return_stmts
COME_FROM_LOOP
# FIXME: investigate - can code really produce a NOP?
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK NOP
COME_FROM_LOOP
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK NOP
COME_FROM_LOOP
whileTruestmt ::= SETUP_LOOP l_stmts_opt JUMP_BACK POP_BLOCK NOP
COME_FROM_LOOP
forstmt ::= SETUP_LOOP expr _for designator for_block POP_BLOCK NOP
COME_FROM_LOOP
"""
def p_genexpr3(self, args):

View File

@@ -19,15 +19,15 @@ class Python35Parser(Python3Parser):
# Python 3.5+ has WITH_CLEANUP_START/FINISH
withstmt ::= expr SETUP_WITH exprlist suite_stmts_opt
POP_BLOCK LOAD_CONST COME_FROM
POP_BLOCK LOAD_CONST COME_FROM_WITH
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
withstmt ::= expr SETUP_WITH POP_TOP suite_stmts_opt
POP_BLOCK LOAD_CONST COME_FROM
POP_BLOCK LOAD_CONST COME_FROM_WITH
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
withasstmt ::= expr SETUP_WITH designator suite_stmts_opt
POP_BLOCK LOAD_CONST COME_FROM
POP_BLOCK LOAD_CONST COME_FROM_WITH
WITH_CLEANUP_START WITH_CLEANUP_FINISH END_FINALLY
inplace_op ::= INPLACE_MATRIX_MULTIPLY

View File

@@ -0,0 +1,38 @@
"""
Detect control flow as much as possible.
The basic idea here is to put in explicit end instructions that make
grammar parsing simpler and more precise.
"""
from collections import namedtuple
from xdis.bytecode import Bytecode
control_flow_start = namedtuple('control_flow_start', ['name', 'type', 'offset'])
control_flow_end = namedtuple('control_flow_end', ['name', 'type', 'offset'])
class ControlFlow():
def __init__(self, scanner):
self.scanner = scanner
self.opc = self.scanner.opc
self.setup_ops = self.scanner.setup_ops
self.op_range = self.scanner.op_range
# Control-flow nesting
self.offset_action = {}
self.cf_end = []
def detect_control_flow(self, co):
self.bytecode = Bytecode(co, self.opc)
for inst in self.bytecode:
if inst.opcode in self.setup_ops:
# Use part after SETUP_
name = inst.opname[len('SETUP_'):]
self.offset_action[inst.offset] = control_flow_start(name, 'start', inst.offset)
self.offset_action[inst.argval] = control_flow_end(name, 'end', inst.offset)
pass
pass
# import pprint
# pp = pprint.PrettyPrinter(indent=4)
# pp.pprint(self.offset_action)
return self.offset_action

View File

@@ -29,6 +29,7 @@ from uncompyle6.scanner import Scanner, op_has_argument
from xdis.code import iscode
from xdis.bytecode import Bytecode
from uncompyle6.scanner import Token, parse_fn_counts
from uncompyle6.scanners.controlflow import ControlFlow
# Get all the opcodes into globals
import xdis.opcodes.opcode_33 as op3
@@ -119,6 +120,9 @@ class Scanner3(Scanner):
(self.opc.POP_JUMP_IF_TRUE, self.opc.JUMP_ABSOLUTE)]
def opName(self, offset):
return self.opc.opname[self.code[offset]]
def ingest(self, co, classname=None, code_objects={}, show_asm=None):
"""
Pick out tokens from an uncompyle6 code object, and transform them,
@@ -136,7 +140,7 @@ class Scanner3(Scanner):
"""
show_asm = self.show_asm if not show_asm else show_asm
# show_asm = 'both'
# show_asm = 'after'
if show_asm in ('both', 'before'):
bytecode = Bytecode(co, self.opc)
for instr in bytecode.get_instructions(co):
@@ -188,13 +192,27 @@ class Scanner3(Scanner):
# Format: {target offset: [jump offsets]}
jump_targets = self.find_jump_targets()
offset_action = ControlFlow(self).detect_control_flow(co)
for inst in bytecode:
argval = inst.argval
if inst.offset in jump_targets:
jump_idx = 0
for jump_offset in jump_targets[inst.offset]:
tokens.append(Token('COME_FROM', None, repr(jump_offset),
# We want to process COME_FROMs to the same offset to be in *descending*
# offset order so we have the larger range or biggest instruction interval
# last. (I think they are sorted in increasing order, but for safety
# we sort them). That way, specific COME_FROM tags will match up
# properly. For example, a "loop" with an "if" nested in it should have the
# "loop" tag last so the grammar rule matches that properly.
for jump_offset in sorted(jump_targets[inst.offset], reverse=True):
come_from_name = 'COME_FROM'
opname = self.opName(jump_offset)
if opname.startswith('SETUP_'):
come_from_type = opname[len('SETUP_'):]
come_from_name = 'COME_FROM_%s' % come_from_type
pass
tokens.append(Token(come_from_name,
None, repr(jump_offset),
offset='%s_%s' % (inst.offset, jump_idx),
has_arg = True, opc=self.opc))
jump_idx += 1

View File

@@ -400,9 +400,10 @@ def compare_code_with_srcfile(pyc_filename, src_filename, weak_verify=False):
def compare_files(pyc_filename1, pyc_filename2, weak_verify=False):
"""Compare two .pyc files."""
version, timestamp, magic_int1, code_obj1, is_pypy = uncompyle6.load_module(pyc_filename1)
version, timestamp, magic_int2, code_obj2, is_pypy = uncompyle6.load_module(pyc_filename2)
cmp_code_objects(version, is_pypy, code_obj1, code_obj2, ignore_code=weak_verify)
version1, timestamp, magic_int1, code_obj1, is_pypy = uncompyle6.load_module(pyc_filename1)
version2, timestamp, magic_int2, code_obj2, is_pypy = uncompyle6.load_module(pyc_filename2)
weak_verify = weak_verify or (magic_int1 != magic_int2)
cmp_code_objects(version1, is_pypy, code_obj1, code_obj2, ignore_code=weak_verify)
if __name__ == '__main__':
t1 = Token('LOAD_CONST', None, 'code_object _expandLang', 52)