diff --git a/.travis.yml b/.travis.yml index 58d27096..fbea358a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ matrix: install: # Remove the next line when xdis 6.0.0 is released -- pip install git://github.com/rocky/python-xdis.git#egg=xdis +# - pip install git://github.com/rocky/python-xdis.git#egg=xdis - pip install -e . - pip install -r requirements-dev.txt diff --git a/__pkginfo__.py b/__pkginfo__.py index 40d8c64f..0a0d542e 100644 --- a/__pkginfo__.py +++ b/__pkginfo__.py @@ -34,7 +34,7 @@ # Things that change more often go here. copyright = """ -Copyright (C) 2015-2020 Rocky Bernstein . +Copyright (C) 2015-2021 Rocky Bernstein . """ classifiers = [ @@ -70,7 +70,7 @@ entry_points = { ] } ftp_url = None -install_requires = ["spark-parser >= 1.8.9, < 1.9.0", "xdis >= 5.9.0, <= 6.0.1"] +install_requires = ["spark-parser >= 1.8.9, < 1.9.0", "xdis >= 6.0.0, < 6.1.0"] license = "GPL3" mailing_list = "python-debugger@googlegroups.com" diff --git a/uncompyle6/parser.py b/uncompyle6/parser.py index bbfe916a..bba57f41 100644 --- a/uncompyle6/parser.py +++ b/uncompyle6/parser.py @@ -649,30 +649,30 @@ def get_python_parser( # in import all of the parsers all of the time. Perhaps there is # a lazy way of doing the import? - if version < 3.0: - if version < 2.2: - if version == 1.0: + if version < (3, 0): + if version < (2, 2): + if version[:2] == (1, 0): import uncompyle6.parsers.parse10 as parse10 if compile_mode == "exec": p = parse10.Python10Parser(debug_parser) else: p = parse10.Python01ParserSingle(debug_parser) - elif version == 1.1: + elif version[:2] == (1, 1): import uncompyle6.parsers.parse11 as parse11 if compile_mode == "exec": p = parse11.Python11Parser(debug_parser) else: p = parse11.Python11ParserSingle(debug_parser) - if version == 1.2: + if version[:2] == (1, 2): import uncompyle6.parsers.parse12 as parse12 if compile_mode == "exec": p = parse12.Python12Parser(debug_parser) else: p = parse12.Python12ParserSingle(debug_parser) - if version == 1.3: + if version[:2] == (1, 3): import uncompyle6.parsers.parse13 as parse13 if compile_mode == "exec": diff --git a/uncompyle6/parsers/parse3.py b/uncompyle6/parsers/parse3.py index 77eb18e8..f3841846 100644 --- a/uncompyle6/parsers/parse3.py +++ b/uncompyle6/parsers/parse3.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2020 Rocky Bernstein +# Copyright (c) 2015-2021 Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock @@ -536,7 +536,7 @@ class Python3Parser(PythonParser): # FIXME: What's the deal with the two rules? Different Python versions? # Different situations? Note that the above rule is based on the CALL_FUNCTION # token found, while this one doesn't. - if self.version < 3.6: + if self.version < (3, 6): call_function = self.call_fn_name(call_fn_tok) args_pos, args_kw = self.get_pos_kw(call_fn_tok) rule = "build_class ::= LOAD_BUILD_CLASS mkfunc %s" "%s" % ( @@ -591,7 +591,7 @@ class Python3Parser(PythonParser): # Note: 3.5+ have subclassed this method; so we don't handle # 'CALL_FUNCTION_VAR' or 'CALL_FUNCTION_EX' here. - if is_pypy and self.version >= 3.6: + if is_pypy and self.version >= (3, 6): if token == "CALL_FUNCTION": token.kind = self.call_fn_name(token) rule = ( @@ -625,7 +625,7 @@ class Python3Parser(PythonParser): """Python 3.3 added a an addtional LOAD_STR before MAKE_FUNCTION and this has an effect on many rules. """ - if self.version >= 3.3: + if self.version >= (3, 3): if PYTHON3 or not self.is_pypy: load_op = "LOAD_STR " else: @@ -774,7 +774,7 @@ class Python3Parser(PythonParser): rule = "kvlist_n ::=" self.add_unique_rule(rule, "kvlist_n", 1, customize) rule = "dict ::= BUILD_MAP_n kvlist_n" - elif self.version >= 3.5: + elif self.version >= (3, 5): if not opname.startswith("BUILD_MAP_WITH_CALL"): # FIXME: Use the attr # so this doesn't run into exponential parsing time. @@ -1059,7 +1059,7 @@ class Python3Parser(PythonParser): args_pos, args_kw, annotate_args = token.attr # FIXME: Fold test into add_make_function_rule - if self.version < 3.3: + if self.version < (3, 3): j = 1 else: j = 2 @@ -1121,7 +1121,7 @@ class Python3Parser(PythonParser): kwargs_str = "" # Note order of kwargs and pos args changed between 3.3-3.4 - if self.version <= 3.2: + if self.version <= (3, 2): if annotate_args > 0: rule = ( "mkfunc_annotate ::= %s%s%sannotate_tuple load_closure LOAD_CODE %s" @@ -1138,7 +1138,7 @@ class Python3Parser(PythonParser): "pos_arg " * args_pos, opname, ) - elif self.version == 3.3: + elif self.version == (3, 3): if annotate_args > 0: rule = ( "mkfunc_annotate ::= %s%s%sannotate_tuple load_closure LOAD_CODE LOAD_STR %s" @@ -1156,7 +1156,7 @@ class Python3Parser(PythonParser): opname, ) - elif self.version >= 3.4: + elif self.version >= (3, 4): if PYTHON3 or not self.is_pypy: load_op = "LOAD_STR" else: @@ -1190,7 +1190,7 @@ class Python3Parser(PythonParser): ) self.add_unique_rule(rule, opname, token.attr, customize) - if self.version < 3.4: + if self.version < (3, 4): rule = "mkfunc ::= %sload_closure LOAD_CODE %s" % ( "expr " * args_pos, opname, @@ -1200,7 +1200,7 @@ class Python3Parser(PythonParser): pass elif opname_base.startswith("MAKE_FUNCTION"): # DRY with MAKE_CLOSURE - if self.version >= 3.6: + if self.version >= (3, 6): # The semantics of MAKE_FUNCTION in 3.6 are totally different from # before. args_pos, args_kw, annotate_args, closure = token.attr @@ -1262,7 +1262,7 @@ class Python3Parser(PythonParser): if self.is_pypy or ( i >= 2 and tokens[i - 2] == "LOAD_LISTCOMP" ): - if self.version >= 3.6: + if self.version >= (3, 6): # 3.6+ sometimes bundles all of the # 'exprs' in the rule above into a # tuple. @@ -1293,12 +1293,12 @@ class Python3Parser(PythonParser): ) continue - if self.version < 3.6: + if self.version < (3, 6): args_pos, args_kw, annotate_args = token.attr else: args_pos, args_kw, annotate_args, closure = token.attr - if self.version < 3.3: + if self.version < (3, 3): j = 1 else: j = 2 @@ -1339,7 +1339,7 @@ class Python3Parser(PythonParser): else: kwargs = "kwargs" - if self.version < 3.3: + if self.version < (3, 3): # positional args after keyword args rule = "mkfunc ::= %s %s%s%s" % ( kwargs, @@ -1353,7 +1353,7 @@ class Python3Parser(PythonParser): "LOAD_CODE ", opname, ) - elif self.version == 3.3: + elif self.version == (3, 3): # positional args after keyword args rule = "mkfunc ::= %s %s%s%s" % ( kwargs, @@ -1361,7 +1361,7 @@ class Python3Parser(PythonParser): "LOAD_CODE LOAD_STR ", opname, ) - elif self.version > 3.5: + elif self.version > (3, 5): # positional args before keyword args rule = "mkfunc ::= %s%s %s%s" % ( "pos_arg " * args_pos, @@ -1369,7 +1369,7 @@ class Python3Parser(PythonParser): "LOAD_CODE LOAD_STR ", opname, ) - elif self.version > 3.3: + elif self.version > (3, 3): # positional args before keyword args rule = "mkfunc ::= %s%s %s%s" % ( "pos_arg " * args_pos, @@ -1386,7 +1386,7 @@ class Python3Parser(PythonParser): self.add_unique_rule(rule, opname, token.attr, customize) if re.search("^MAKE_FUNCTION.*_A", opname): - if self.version >= 3.6: + if self.version >= (3, 6): rule = ( "mkfunc_annotate ::= %s%sannotate_tuple LOAD_CODE LOAD_STR %s" % ( @@ -1404,11 +1404,11 @@ class Python3Parser(PythonParser): opname, ) ) - if self.version >= 3.3: + if self.version >= (3, 3): # Normally we remove EXTENDED_ARG from the opcodes, but in the case of # annotated functions can use the EXTENDED_ARG tuple to signal we have an annotated function. # Yes this is a little hacky - if self.version == 3.3: + if self.version == (3, 3): # 3.3 puts kwargs before pos_arg pos_kw_tuple = ( ("kwargs " * args_kw), @@ -1548,7 +1548,7 @@ class Python3Parser(PythonParser): "try_except": tryexcept, } - if self.version == 3.6: + if self.version == (3, 6): self.reduce_check_table["and"] = and_check self.check_reduce["and"] = "AST" @@ -1560,7 +1560,7 @@ class Python3Parser(PythonParser): self.check_reduce["ifelsestmtc"] = "AST" self.check_reduce["ifstmt"] = "AST" self.check_reduce["ifstmtl"] = "AST" - if self.version == 3.6: + if self.version == (3, 6): self.reduce_check_table["iflaststmtl"] = iflaststmt self.check_reduce["iflaststmt"] = "AST" self.check_reduce["iflaststmtl"] = "AST" @@ -1568,7 +1568,7 @@ class Python3Parser(PythonParser): self.check_reduce["testtrue"] = "tokens" if not PYTHON3: self.check_reduce["kwarg"] = "noAST" - if self.version < 3.6 and not self.is_pypy: + if self.version < (3, 6) and not self.is_pypy: # 3.6+ can remove a JUMP_FORWARD which messes up our testing here # Pypy we need to go over in better detail self.check_reduce["try_except"] = "AST" @@ -1595,11 +1595,11 @@ class Python3Parser(PythonParser): elif lhs == "kwarg": arg = tokens[first].attr return not (isinstance(arg, str) or isinstance(arg, unicode)) - elif lhs in ("iflaststmt", "iflaststmtl") and self.version == 3.6: + elif lhs in ("iflaststmt", "iflaststmtl") and self.version == (3, 6): return ifstmt(self, lhs, n, rule, ast, tokens, first, last) elif rule == ("ifstmt", ("testexpr", "_ifstmts_jump")): # FIXME: go over what's up with 3.0. Evetually I'd like to remove RETURN_END_IF - if self.version <= 3.0 or tokens[last] == "RETURN_END_IF": + if self.version <= (3, 0) or tokens[last] == "RETURN_END_IF": return False if ifstmt(self, lhs, n, rule, ast, tokens, first, last): return True @@ -1641,7 +1641,7 @@ class Python3Parser(PythonParser): if while1stmt(self, lhs, n, rule, ast, tokens, first, last): return True - if self.version == 3.0: + if self.version == (3, 0): return False if 0 <= last < len(tokens) and tokens[last] in ( @@ -1683,7 +1683,7 @@ class Python3Parser(PythonParser): if last == n: return False # 3.8+ Doesn't have SETUP_LOOP - return self.version < 3.8 and tokens[first].attr > tokens[last].offset + return self.version < (3, 8) and tokens[first].attr > tokens[last].offset elif rule == ( "ifelsestmt", ( diff --git a/uncompyle6/parsers/reducecheck/ifelsestmt.py b/uncompyle6/parsers/reducecheck/ifelsestmt.py index 8ec252e2..29c52673 100644 --- a/uncompyle6/parsers/reducecheck/ifelsestmt.py +++ b/uncompyle6/parsers/reducecheck/ifelsestmt.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020 Rocky Bernstein +# Copyright (c) 2020-2021 Rocky Bernstein from uncompyle6.scanners.tok import Token @@ -158,7 +158,7 @@ def ifelsestmt(self, lhs, n, rule, ast, tokens, first, last): # just the last one. if len(ast) == 5: end_come_froms = ast[-1] - if end_come_froms.kind != "else_suite" and self.version >= 3.0: + if end_come_froms.kind != "else_suite" and self.version >= (3, 0): if end_come_froms == "opt_come_from_except" and len(end_come_froms) > 0: end_come_froms = end_come_froms[0] if not isinstance(end_come_froms, Token): @@ -169,12 +169,12 @@ def ifelsestmt(self, lhs, n, rule, ast, tokens, first, last): # FIXME: There is weirdness in the grammar we need to work around. # we need to clean up the grammar. - if self.version < 3.0: + if self.version < (3, 0): last_token = ast[-1] else: last_token = tokens[last] if last_token == "COME_FROM" and tokens[first].offset > last_token.attr: - if self.version < 3.0 and self.insts[self.offset2inst_index[last_token.attr]].opname != "SETUP_LOOP": + if self.version < (3, 0) and self.insts[self.offset2inst_index[last_token.attr]].opname != "SETUP_LOOP": return True testexpr = ast[0] @@ -191,7 +191,7 @@ def ifelsestmt(self, lhs, n, rule, ast, tokens, first, last): if last == n: last -= 1 jmp = if_condition[1] - if self.version > 2.6: + if self.version > (2, 6): jmp_target = jmp[0].attr else: jmp_target = int(jmp[0].pattr) diff --git a/uncompyle6/scanner.py b/uncompyle6/scanner.py index 0a4bd595..5896266e 100755 --- a/uncompyle6/scanner.py +++ b/uncompyle6/scanner.py @@ -33,36 +33,38 @@ import xdis from xdis import Bytecode, canonic_python_version, code2num, instruction_size, extended_arg_val, next_offset # The byte code versions we support. -# Note: these all have to be floats +# Note: these all have to be tuples of 2 ints PYTHON_VERSIONS = frozenset( ( - 1.0, - 1.1, - 1.3, - 1.4, - 1.5, - 1.6, - 2.1, - 2.2, - 2.3, - 2.4, - 2.5, - 2.6, - 2.7, - 3.0, - 3.1, - 3.2, - 3.3, - 3.4, - 3.5, - 3.6, - 3.7, - 3.8, - 3.9, + (1, 0), + (1, 1), + (1, 3), + (1, 4), + (1, 5), + (1, 6), + (2, 1), + (2, 2), + (2, 3), + (2, 4), + (2, 5), + (2, 6), + (2, 7), + (3, 0), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (3, 5), + (3, 6), + (3, 7), + (3, 8), ) ) -CANONIC2VERSION = dict((canonic_python_version[str(v)], v) for v in PYTHON_VERSIONS) +CANONIC2VERSION = dict( + (canonic_python_version[".".join(str(v) for v in python_version)], python_version) + for python_version in PYTHON_VERSIONS +) # Magic changed mid version for Python 3.5.2. Compatibility was added for # the older 3.5 interpreter magic. @@ -105,9 +107,9 @@ class Scanner(object): if version in PYTHON_VERSIONS: if is_pypy: - v_str = "opcode_%spypy" % (int(version * 10)) + v_str = "opcode_%spypy" % ("".join([str(v) for v in version])) else: - v_str = "opcode_%s" % (int(version * 10)) + v_str = "opcode_%s" % ("".join([str(v) for v in version])) exec("from xdis.opcodes import %s" % v_str) exec("self.opc = %s" % v_str) else: @@ -144,7 +146,7 @@ class Scanner(object): # Offset: lineno pairs, only for offsets which start line. # Locally we use list for more convenient iteration using indices - if self.version > 1.4: + if self.version > (1, 4): linestarts = list(self.opc.findlinestarts(code_obj)) else: linestarts = [[0, 1]] @@ -533,8 +535,8 @@ def get_scanner(version, is_pypy=False, show_asm=None): version = CANONIC2VERSION[canonic_version] # Pick up appropriate scanner - if version in PYTHON_VERSIONS: - v_str = "%s" % (int(version * 10)) + if version[:2] in PYTHON_VERSIONS: + v_str = "".join([str(v) for v in version[:2]]) try: import importlib diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 4ece378d..aa89dd00 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2019 by Rocky Bernstein +# Copyright (c) 2015-2019, 2021 by Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # @@ -66,7 +66,7 @@ class Scanner3(Scanner): # at the their targets. # Some blocks and END_ statements. And they can start # a new statement - if self.version < 3.8: + if self.version < (3, 8): setup_ops = [ self.opc.SETUP_LOOP, self.opc.SETUP_EXCEPT, @@ -79,11 +79,11 @@ class Scanner3(Scanner): setup_ops = [self.opc.SETUP_FINALLY] self.setup_ops_no_loop = frozenset(setup_ops) - if self.version >= 3.2: + if self.version >= (3, 2): setup_ops.append(self.opc.SETUP_WITH) self.setup_ops = frozenset(setup_ops) - if self.version == 3.0: + if self.version[:2] == (3, 0): self.pop_jump_tf = frozenset( [self.opc.JUMP_IF_FALSE, self.opc.JUMP_IF_TRUE] ) @@ -114,7 +114,7 @@ class Scanner3(Scanner): self.opc.JUMP_ABSOLUTE, ] - if self.version < 3.8: + if self.version < (3, 8): statement_opcodes += [self.opc.BREAK_LOOP, self.opc.CONTINUE_LOOP] self.statement_opcodes = frozenset(statement_opcodes) | self.setup_ops_no_loop @@ -135,7 +135,7 @@ class Scanner3(Scanner): ] ) - if self.version > 3.0: + if self.version > (3, 0): self.jump_if_pop = frozenset( [self.opc.JUMP_IF_FALSE_OR_POP, self.opc.JUMP_IF_TRUE_OR_POP] ) @@ -182,9 +182,9 @@ class Scanner3(Scanner): ] ) - if is_pypy or self.version >= 3.7: + if is_pypy or self.version >= (3, 7): varargs_ops.add(self.opc.CALL_METHOD) - if self.version >= 3.5: + if self.version >= (3, 5): varargs_ops |= set( [ self.opc.BUILD_SET_UNPACK, @@ -193,7 +193,7 @@ class Scanner3(Scanner): self.opc.BUILD_TUPLE_UNPACK, ] ) - if self.version >= 3.6: + if self.version >= (3, 6): varargs_ops.add(self.opc.BUILD_CONST_KEY_MAP) # Below is in bit order, "default = bit 0, closure = bit 3 self.MAKE_FUNCTION_FLAGS = tuple( @@ -257,7 +257,7 @@ class Scanner3(Scanner): # If we have a JUMP_FORWARD after the # RAISE_VARARGS then we have a "raise" statement # else we have an "assert" statement. - if self.version == 3.0: + if self.version[:2] == (3, 0): # Like 2.6, 3.0 doesn't have POP_JUMP_IF... so we have # to go through more machinations assert_can_follow = inst.opname == "POP_TOP" and i + 1 < n @@ -390,7 +390,7 @@ class Scanner3(Scanner): pattr = const pass elif opname in ("MAKE_FUNCTION", "MAKE_CLOSURE"): - if self.version >= 3.6: + if self.version >= (3, 6): # 3.6+ doesn't have MAKE_CLOSURE, so opname == 'MAKE_FUNCTION' flags = argval opname = "MAKE_FUNCTION_%d" % (flags) @@ -444,7 +444,7 @@ class Scanner3(Scanner): # as JUMP_IF_NOT_DEBUG. The value is not used in these cases, so we put # in arbitrary value 0. customize[opname] = 0 - elif self.version >= 3.6 and argval > 255: + elif self.version >= (3, 6) and argval > 255: opname = "CALL_FUNCTION_KW" pass @@ -483,7 +483,7 @@ class Scanner3(Scanner): and self.insts[i + 1].opname == "JUMP_FORWARD" ) - if (self.version == 3.0 and self.insts[i + 1].opname == "JUMP_FORWARD" + if (self.version[:2] == (3, 0) and self.insts[i + 1].opname == "JUMP_FORWARD" and not is_continue): target_prev = self.offset2inst_index[self.prev_op[target]] is_continue = ( @@ -585,7 +585,7 @@ class Scanner3(Scanner): if inst.has_arg: label = self.fixed_jumps.get(offset) oparg = inst.arg - if self.version >= 3.6 and self.code[offset] == self.opc.EXTENDED_ARG: + if self.version >= (3, 6) and self.code[offset] == self.opc.EXTENDED_ARG: j = xdis.next_offset(op, self.opc, offset) next_offset = xdis.next_offset(op, self.opc, j) else: @@ -732,7 +732,7 @@ class Scanner3(Scanner): end = current_end parent = struct - if self.version < 3.8 and op == self.opc.SETUP_LOOP: + if self.version < (3, 8) and op == self.opc.SETUP_LOOP: # We categorize loop types: 'for', 'while', 'while 1' with # possibly suffixes '-loop' and '-else' # Try to find the jump_back instruction of the loop. @@ -863,7 +863,7 @@ class Scanner3(Scanner): # In some cases the pretarget can be a jump to the next instruction # and these aren't and/or's either. We limit to 3.5+ since we experienced there # but it might be earlier versions, or might be a general principle. - if self.version < 3.5 or pretarget.argval != target: + if self.version < (3, 5) or pretarget.argval != target: # FIXME: this is not accurate The commented out below # is what it should be. However grammar rules right now # assume the incorrect offsets. @@ -957,7 +957,7 @@ class Scanner3(Scanner): ) ): pass - elif self.version <= 3.2: + elif self.version <= (3, 2): fix = None jump_ifs = self.inst_matches( start, @@ -976,7 +976,7 @@ class Scanner3(Scanner): self.fixed_jumps[offset] = fix or match[-1] return else: - if self.version < 3.6: + if self.version < (3, 6): # FIXME: this is putting in COME_FROMs in the wrong place. # Fix up grammar so we don't need to do this. # See cf_for_iter use in parser36.py @@ -1044,20 +1044,20 @@ class Scanner3(Scanner): # if the condition jump is to a forward location. # Also the existence of a jump to the instruction after "END_FINALLY" # will distinguish "try/else" from "try". - if self.version < 3.8: + if self.version < (3, 8): rtarget_break = (self.opc.RETURN_VALUE, self.opc.BREAK_LOOP) else: rtarget_break = (self.opc.RETURN_VALUE,) if self.is_jump_forward(pre_rtarget) or ( - rtarget_is_ja and self.version >= 3.5 + rtarget_is_ja and self.version >= (3, 5) ): if_end = self.get_target(pre_rtarget) # If the jump target is back, we are looping if ( if_end < pre_rtarget - and self.version < 3.8 + and self.version < (3, 8) and (code[prev_op[if_end]] == self.opc.SETUP_LOOP) ): if if_end > start: @@ -1094,11 +1094,11 @@ class Scanner3(Scanner): if self.is_pypy and code[jump_prev] == self.opc.COMPARE_OP: if self.opc.cmp_op[code[jump_prev + 1]] == "exception-match": return - if self.version >= 3.5: + if self.version >= (3, 5): # Python 3.5 may remove as dead code a JUMP # instruction after a RETURN_VALUE. So we check # based on seeing SETUP_EXCEPT various places. - if self.version < 3.6 and code[rtarget] == self.opc.SETUP_EXCEPT: + if self.version < (3, 6) and code[rtarget] == self.opc.SETUP_EXCEPT: return # Check that next instruction after pops and jump is # not from SETUP_EXCEPT @@ -1111,14 +1111,14 @@ class Scanner3(Scanner): for try_op in targets[next_op]: come_from_op = code[try_op] if ( - self.version < 3.8 + self.version < (3, 8) and come_from_op == self.opc.SETUP_EXCEPT ): return pass pass - if self.version >= 3.4: + if self.version >= (3, 4): self.fixed_jumps[offset] = rtarget if code[pre_rtarget] == self.opc.RETURN_VALUE: @@ -1137,8 +1137,8 @@ class Scanner3(Scanner): # FIXME: this is very convoluted and based on rather hacky # empirical evidence. It should go a way when # we have better control-flow analysis - normal_jump = self.version >= 3.6 - if self.version == 3.5: + normal_jump = self.version >= (3, 6) + if self.version[:2] == (3, 5): j = self.offset2inst_index[target] if j + 2 < len(self.insts) and self.insts[j + 2].is_jump_target: normal_jump = self.insts[j + 1].opname == "POP_BLOCK" @@ -1155,7 +1155,7 @@ class Scanner3(Scanner): if rtarget > offset: self.fixed_jumps[offset] = rtarget - elif self.version < 3.8 and op == self.opc.SETUP_EXCEPT: + elif self.version < (3, 8) and op == self.opc.SETUP_EXCEPT: target = self.get_target(offset) end = self.restrict_to_parent(target, parent) self.fixed_jumps[offset] = end @@ -1188,7 +1188,7 @@ class Scanner3(Scanner): self.fixed_jumps[offset] = self.restrict_to_parent(target, parent) pass pass - elif self.version >= 3.5: + elif self.version >= (3, 5): # 3.5+ has Jump optimization which too often causes RETURN_VALUE to get # misclassified as RETURN_END_IF. Handle that here. # In RETURN_VALUE, JUMP_ABSOLUTE, RETURN_VALUE is never RETURN_END_IF @@ -1278,7 +1278,7 @@ class Scanner3(Scanner): start, end, instr, target, include_beyond_target ) # Get all POP_JUMP_IF_TRUE (or) offsets - if self.version == 3.0: + if self.version[:2] == (3, 0): jump_true_op = self.opc.JUMP_IF_TRUE else: jump_true_op = self.opc.POP_JUMP_IF_TRUE @@ -1295,17 +1295,16 @@ class Scanner3(Scanner): if __name__ == "__main__": - from uncompyle6 import PYTHON_VERSION + from xdis.version_info import PYTHON_VERSION_TRIPLE - if PYTHON_VERSION >= 3.2: + if PYTHON_VERSION_TRIPLE >= (3, 2): import inspect co = inspect.currentframe().f_code - from uncompyle6 import PYTHON_VERSION - tokens, customize = Scanner3(PYTHON_VERSION).ingest(co) + tokens, customize = Scanner3(PYTHON_VERSION_TRIPLE).ingest(co) for t in tokens: print(t) else: - print("Need to be Python 3.2 or greater to demo; I am %s." % PYTHON_VERSION) + print("Need to be Python 3.2 or greater to demo; I am %s." % sys.version) pass diff --git a/uncompyle6/scanners/scanner34.py b/uncompyle6/scanners/scanner34.py index da78c2fe..c54a7776 100644 --- a/uncompyle6/scanners/scanner34.py +++ b/uncompyle6/scanners/scanner34.py @@ -34,7 +34,7 @@ from uncompyle6.scanners.scanner3 import Scanner3 class Scanner34(Scanner3): def __init__(self, show_asm=None): - Scanner3.__init__(self, 3.4, show_asm) + Scanner3.__init__(self, (3, 4), show_asm) return pass diff --git a/uncompyle6/semantics/customize.py b/uncompyle6/semantics/customize.py index e4aa2e56..afe1cd06 100644 --- a/uncompyle6/semantics/customize.py +++ b/uncompyle6/semantics/customize.py @@ -60,8 +60,8 @@ def customize_for_version(self, is_pypy, version): "assign3": ("%|%c, %c, %c = %c, %c, %c\n", 5, 6, 7, 0, 1, 2), "try_except": ("%|try:\n%+%c%-%c\n\n", 1, 3), }) - if version >= 3.0: - if version >= 3.2: + if version >= (3, 0): + if version >= (3, 2): TABLE_DIRECT.update( {"del_deref_stmt": ("%|del %c\n", 0), "DELETE_DEREF": ("%{pattr}", 0)} ) @@ -72,20 +72,20 @@ def customize_for_version(self, is_pypy, version): TABLE_DIRECT.update( {"except_cond3": ("%|except %c, %c:\n", (1, "expr"), (-2, "store"))} ) - if version <= 2.6: + if version <= (2, 6): TABLE_DIRECT["testtrue_then"] = TABLE_DIRECT["testtrue"] - if 2.4 <= version <= 2.6: + if (2, 4) <= version <= (2, 6): TABLE_DIRECT.update({"comp_for": (" for %c in %c", 3, 1)}) else: TABLE_DIRECT.update({"comp_for": (" for %c in %c%c", 2, 0, 3)}) - if version >= 2.5: + if version >= (2, 5): from uncompyle6.semantics.customize25 import customize_for_version25 customize_for_version25(self, version) - if version >= 2.6: + if version >= (2, 6): from uncompyle6.semantics.customize26_27 import ( customize_for_version26_27, ) @@ -137,7 +137,7 @@ def customize_for_version(self, is_pypy, version): ), } ) - if version == 2.4: + if version == (2, 4): def n_iftrue_stmt24(node): self.template_engine(("%c", 0), node) self.default(node) @@ -146,7 +146,7 @@ def customize_for_version(self, is_pypy, version): self.n_iftrue_stmt24 = n_iftrue_stmt24 else: # version <= 2.3: TABLE_DIRECT.update({"if1_stmt": ("%|if 1\n%+%c%-", 5)}) - if version <= 2.1: + if version <= (2, 1): TABLE_DIRECT.update( { "importmultiple": ("%c", 2), diff --git a/uncompyle6/semantics/customize3.py b/uncompyle6/semantics/customize3.py index 7cb8e21a..b7812407 100644 --- a/uncompyle6/semantics/customize3.py +++ b/uncompyle6/semantics/customize3.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2020 by Rocky Bernstein +# Copyright (c) 2018-2021 by Rocky Bernstein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -58,7 +58,7 @@ def customize_for_version3(self, version): } ) - assert version >= 3.0 + assert version >= (3, 0) # In 2.5+ and 3.0+ "except" handlers and the "finally" can appear in one # "try" statement. So the below has the effect of combining the @@ -107,7 +107,7 @@ def customize_for_version3(self, version): collections = [node[-3]] list_ifs = [] - if self.version == 3.0 and n != "list_iter": + if self.version == (3, 0) and n != "list_iter": # FIXME 3.0 is a snowflake here. We need # special code for this. Not sure if this is totally # correct. @@ -207,7 +207,7 @@ def customize_for_version3(self, version): """Handle "classdef" nonterminal for 3.0 >= version 3.0 <= 3.5 """ - assert 3.0 <= self.version <= 3.5 + assert (3, 0) <= self.version <= (3, 5) # class definition ('class X(A,B,C):') cclass = self.currentclass @@ -220,7 +220,7 @@ def customize_for_version3(self, version): # * subclass_code - the code for the subclass body subclass_info = None if node == "classdefdeco2": - if self.version <= 3.3: + if self.version <= (3, 3): class_name = node[2][0].attr else: class_name = node[1][2].attr @@ -233,7 +233,7 @@ def customize_for_version3(self, version): assert "mkfunc" == build_class[1] mkfunc = build_class[1] if mkfunc[0] in ("kwargs", "no_kwargs"): - if 3.0 <= self.version <= 3.2: + if (3, 0) <= self.version <= (3, 2): for n in mkfunc: if hasattr(n, "attr") and iscode(n.attr): subclass_code = n.attr @@ -355,7 +355,7 @@ def customize_for_version3(self, version): self.n_yield_from = n_yield_from - if 3.2 <= version <= 3.4: + if (3, 2) <= version <= (3, 4): def n_call(node): @@ -416,9 +416,9 @@ def customize_for_version3(self, version): # Handling EXTENDED_ARG before MAKE_FUNCTION ... i = -1 if node[-2] == "EXTENDED_ARG" else 0 - if self.version <= 3.2: + if self.version <= (3, 2): code = node[-2 + i] - elif self.version >= 3.3 or node[-2] == "kwargs": + elif self.version >= (3, 3) or node[-2] == "kwargs": # LOAD_CONST code object .. # LOAD_CONST 'x0' if >= 3.3 # EXTENDED_ARG @@ -468,7 +468,7 @@ def customize_for_version3(self, version): "LOAD_CLASSDEREF": ("%{pattr}",), } ) - if version >= 3.4: + if version >= (3, 4): ####################### # Python 3.4+ Changes # ####################### @@ -478,13 +478,13 @@ def customize_for_version3(self, version): "yield_from": ("yield from %c", (0, "expr")), } ) - if version >= 3.5: + if version >= (3, 5): customize_for_version35(self, version) - if version >= 3.6: + if version >= (3, 6): customize_for_version36(self, version) - if version >= 3.7: + if version >= (3, 7): customize_for_version37(self, version) - if version >= 3.8: + if version >= (3, 8): customize_for_version38(self, version) pass # version >= 3.8 pass # 3.7 diff --git a/uncompyle6/semantics/helper.py b/uncompyle6/semantics/helper.py index d0500aba..4b447288 100644 --- a/uncompyle6/semantics/helper.py +++ b/uncompyle6/semantics/helper.py @@ -78,7 +78,7 @@ def find_globals_and_nonlocals(node, globs, nonlocals, code, version): code, version) elif n.kind in read_global_ops: globs.add(n.pattr) - elif (version >= 3.0 + elif (version >= (3, 0) and n.kind in nonglobal_ops and n.pattr in code.co_freevars and n.pattr != code.co_name diff --git a/uncompyle6/semantics/make_function3.py b/uncompyle6/semantics/make_function3.py index 4ec89889..39603b5f 100644 --- a/uncompyle6/semantics/make_function3.py +++ b/uncompyle6/semantics/make_function3.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2020 by Rocky Bernstein +# Copyright (c) 2015-2021 by Rocky Bernstein # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -366,9 +366,9 @@ def make_function3(self, node, is_lambda, nested=1, code_node=None): # Python 3.3+ adds a qualified name at TOS (-1) # moving down the LOAD_LAMBDA instruction - if 3.0 <= self.version <= 3.2: + if (3, 0) <= self.version <= (3, 2): lambda_index = -2 - elif 3.03 <= self.version: + elif (3, 3) <= self.version: lambda_index = -3 else: lambda_index = None @@ -590,7 +590,7 @@ def make_function3(self, node, is_lambda, nested=1, code_node=None): ends_in_comma = True kw_args = [None] * kwonlyargcount - if self.version <= 3.3: + if self.version <= (3, 3): kw_nodes = node[0] else: kw_nodes = node[args_node.attr[0]] diff --git a/uncompyle6/semantics/pysource.py b/uncompyle6/semantics/pysource.py index 1c2dcdb4..232bbf7b 100644 --- a/uncompyle6/semantics/pysource.py +++ b/uncompyle6/semantics/pysource.py @@ -483,7 +483,7 @@ class SourceWalker(GenericASTTraversal, object): and node[0][0][0] == "LOAD_CONST" and node[0][0][0].pattr is None ) - if self.version <= 2.6: + if self.version <= (2, 6): return ret else: # FIXME: should the SyntaxTree expression be folded into @@ -537,7 +537,7 @@ class SourceWalker(GenericASTTraversal, object): def n_yield(self, node): if node != SyntaxTree("yield", [NONE, Token("YIELD_VALUE")]): self.template_engine(("yield %c", 0), node) - elif self.version <= 2.4: + elif self.version <= (2, 4): # Early versions of Python don't allow a plain "yield" self.write("yield None") else: @@ -823,7 +823,7 @@ class SourceWalker(GenericASTTraversal, object): self.prune() def n_alias(self, node): - if self.version <= 2.1: + if self.version <= (2, 1): if len(node) == 2: store = node[1] assert store == "store" @@ -848,10 +848,10 @@ class SourceWalker(GenericASTTraversal, object): def n_import_from(self, node): relative_path_index = 0 - if self.version >= 2.5: + if self.version >= (2, 5): if node[relative_path_index].pattr > 0: node[2].pattr = ("." * node[relative_path_index].pattr) + node[2].pattr - if self.version > 2.7: + if self.version > (2, 7): if isinstance(node[1].pattr, tuple): imports = node[1].pattr for pattr in imports: @@ -882,11 +882,11 @@ class SourceWalker(GenericASTTraversal, object): # Python changes make function this much that we need at least 3 different routines, # and probably more in the future. def make_function(self, node, is_lambda, nested=1, code_node=None, annotate=None): - if self.version <= 2.7: + if self.version <= (2, 7): make_function2(self, node, is_lambda, nested, code_node) - elif 3.0 <= self.version <= 3.5: + elif (3, 0) <= self.version <= (3, 5): make_function3(self, node, is_lambda, nested, code_node) - elif self.version >= 3.6: + elif self.version >= (3, 6): make_function36(self, node, is_lambda, nested, code_node) def n_docstring(self, node): @@ -978,7 +978,7 @@ class SourceWalker(GenericASTTraversal, object): """List comprehensions""" p = self.prec self.prec = 100 - if self.version >= 2.7: + if self.version >= (2, 7): if self.is_pypy: self.n_list_comp_pypy27(node) return @@ -1005,7 +1005,7 @@ class SourceWalker(GenericASTTraversal, object): assert n == "lc_body" self.write("[ ") - if self.version >= 2.7: + if self.version >= (2, 7): expr = n[0] list_iter = node[-1] else: @@ -1090,15 +1090,15 @@ class SourceWalker(GenericASTTraversal, object): self.prec = 27 # FIXME: clean this up - if self.version >= 3.0 and node == "dict_comp": + if self.version >= (3, 0) and node == "dict_comp": cn = node[1] - elif self.version <= 2.7 and node == "generator_exp": + elif self.version <= (2, 7) and node == "generator_exp": if node[0] == "LOAD_GENEXPR": cn = node[0] elif node[0] == "load_closure": cn = node[1] - elif self.version >= 3.0 and node in ("generator_exp", "generator_exp_async"): + elif self.version >= (3, 0) and node in ("generator_exp", "generator_exp_async"): if node[0] == "load_genexpr": load_genexpr = node[0] elif node[1] == "load_genexpr": @@ -1167,9 +1167,9 @@ class SourceWalker(GenericASTTraversal, object): def n_generator_exp(self, node): self.write("(") iter_index = 3 - if self.version > 3.2: + if self.version > (3, 2): code_index = -6 - if self.version > 3.6: + if self.version > (3, 6): # Python 3.7+ adds optional "come_froms" at node[0] iter_index = 4 else: @@ -1184,7 +1184,7 @@ class SourceWalker(GenericASTTraversal, object): self.write("{") if node[0] in ["LOAD_SETCOMP", "LOAD_DICTCOMP"]: self.comprehension_walk_newer(node, 1, 0) - elif node[0].kind == "load_closure" and self.version >= 3.0: + elif node[0].kind == "load_closure" and self.version >= (3, 0): self.setcomprehension_walk3(node, collection_index=4) else: self.comprehension_walk(node, iter_index=4) @@ -1241,7 +1241,7 @@ class SourceWalker(GenericASTTraversal, object): pass pass elif ast in ("dict_comp", "set_comp"): - assert self.version == 3.0 + assert self.version == (3, 0) for k in ast: if k in ("dict_comp_header", "set_comp_header"): n = k @@ -1313,7 +1313,7 @@ class SourceWalker(GenericASTTraversal, object): # Python 2.7+ starts including set_comp_body # Python 3.5+ starts including set_comp_func # Python 3.0 is yet another snowflake - if self.version != 3.0 and self.version < 3.7: + if self.version != (3, 0) and self.version < (3, 7): assert n.kind in ( "lc_body", "list_if37", @@ -1356,7 +1356,7 @@ class SourceWalker(GenericASTTraversal, object): self.preorder(node[in_node_index]) # Here is where we handle nested list iterations. - if ast == "list_comp" and self.version != 3.0: + if ast == "list_comp" and self.version != (3, 0): list_iter = ast[1] assert list_iter == "list_iter" if list_iter[0] == "list_for": @@ -1379,7 +1379,7 @@ class SourceWalker(GenericASTTraversal, object): def n_listcomp(self, node): self.write("[") if node[0].kind == "load_closure": - assert self.version >= 3.0 + assert self.version >= (3, 0) self.listcomp_closure3(node) else: if node == "listcomp_async": @@ -1447,9 +1447,9 @@ class SourceWalker(GenericASTTraversal, object): def n_classdef(self, node): - if self.version >= 3.6: + if self.version >= (3, 6): self.n_classdef36(node) - elif self.version >= 3.0: + elif self.version >= (3, 0): self.n_classdef3(node) # class definition ('class X(A,B,C):') @@ -1514,7 +1514,7 @@ class SourceWalker(GenericASTTraversal, object): return n_subclasses = len(node[:-1]) - if n_subclasses > 0 or self.version > 2.4: + if n_subclasses > 0 or self.version > (2, 4): # Not an old-style pre-2.2 class self.write("(") @@ -1525,7 +1525,7 @@ class SourceWalker(GenericASTTraversal, object): self.write(sep, value) sep = line_separator - if n_subclasses > 0 or self.version > 2.4: + if n_subclasses > 0 or self.version > (2, 4): # Not an old-style pre-2.2 class self.write(")") @@ -1674,7 +1674,7 @@ class SourceWalker(GenericASTTraversal, object): self.write("{") line_number = self.line_number - if self.version >= 3.0 and not self.is_pypy: + if self.version >= (3, 0) and not self.is_pypy: if node[0].kind.startswith("kvlist"): # Python 3.5+ style key/value list in dict kv_node = node[0] @@ -1760,14 +1760,14 @@ class SourceWalker(GenericASTTraversal, object): self.write(sep[1:]) pass elif node[0].kind.startswith("dict_entry"): - assert self.version >= 3.5 + assert self.version >= (3, 5) template = ("%C", (0, len(node[0]), ", **")) self.template_engine(template, node[0]) sep = "" elif node[-1].kind.startswith("BUILD_MAP_UNPACK") or node[ -1 ].kind.startswith("dict_entry"): - assert self.version >= 3.5 + assert self.version >= (3, 5) # FIXME: I think we can intermingle dict_comp's with other # dictionary kinds of things. The most common though is # a sequence of dict_comp's @@ -1790,7 +1790,7 @@ class SourceWalker(GenericASTTraversal, object): else: sep = "" opname = node[-1].kind - if self.is_pypy and self.version >= 3.5: + if self.is_pypy and self.version >= (3, 5): if opname.startswith("BUILD_CONST_KEY_MAP"): keys = node[-2].attr # FIXME: DRY this and the above @@ -1966,7 +1966,7 @@ class SourceWalker(GenericASTTraversal, object): # In Python 2.4, unpack is used in (a, b, c) of: # except RuntimeError, (a, b, c): - if self.version < 2.7: + if self.version < (2, 7): node.kind = "unpack_w_parens" self.default(node) @@ -1984,7 +1984,7 @@ class SourceWalker(GenericASTTraversal, object): def n_assign(self, node): # A horrible hack for Python 3.0 .. 3.2 - if 3.0 <= self.version <= 3.2 and len(node) == 2: + if (3, 0) <= self.version <= (3, 2) and len(node) == 2: if ( node[0][0] == "LOAD_FAST" and node[0][0].pattr == "__locals__" @@ -2197,7 +2197,7 @@ class SourceWalker(GenericASTTraversal, object): if k.startswith("CALL_METHOD"): # This happens in PyPy and Python 3.7+ TABLE_R[k] = ("%c(%P)", 0, (1, -1, ", ", 100)) - elif self.version >= 3.6 and k.startswith("CALL_FUNCTION_KW"): + elif self.version >= (3, 6) and k.startswith("CALL_FUNCTION_KW"): TABLE_R[k] = ("%c(%P)", 0, (1, -1, ", ", 100)) elif op == "CALL_FUNCTION": TABLE_R[k] = ("%c(%P)", (0, "expr"), (1, -1, ", ", PRECEDENCE["yield"]-1)) @@ -2219,12 +2219,12 @@ class SourceWalker(GenericASTTraversal, object): if op == "CALL_FUNCTION_VAR": # Python 3.5 only puts optional args (the VAR part) # lowest down the stack - if self.version == 3.5: + if self.version == (3, 5): if str == "%c(%C, ": entry = ("%c(*%C, %c)", 0, p2, -2) elif str == "%c(%C": entry = ("%c(*%C)", 0, (1, 100, "")) - elif self.version == 3.4: + elif self.version == (3, 4): # CALL_FUNCTION_VAR's top element of the stack contains # the variable argument list if v == 0: @@ -2244,7 +2244,7 @@ class SourceWalker(GenericASTTraversal, object): # Python 3.5 only puts optional args (the VAR part) # lowest down the stack na = v & 0xFF # positional parameters - if self.version == 3.5 and na == 0: + if self.version == (3, 5) and na == 0: if p2[2]: p2 = (2, -2, ", ") entry = (str, 0, p2, 1, -2) @@ -2326,7 +2326,7 @@ class SourceWalker(GenericASTTraversal, object): del ast[0] first_stmt = ast[0] - if 3.0 <= self.version <= 3.3: + if (3, 0) <= self.version <= (3, 3): try: if first_stmt == "store_locals": if self.hide_internal: @@ -2352,7 +2352,7 @@ class SourceWalker(GenericASTTraversal, object): ast[0] = ast[0][0] first_stmt = ast[0] - if self.version < 3.0: + if self.version < (3, 0): # Should we ditch this in favor of the "else" case? qualname = ".".join(self.classes) QUAL_NAME = SyntaxTree( @@ -2612,7 +2612,7 @@ def code_deparse( assert not nonlocals - if version >= 3.0: + if version >= (3, 0): load_op = "LOAD_STR" else: load_op = "LOAD_CONST" diff --git a/uncompyle6/semantics/transform.py b/uncompyle6/semantics/transform.py index 27136c6c..3ac5edfb 100644 --- a/uncompyle6/semantics/transform.py +++ b/uncompyle6/semantics/transform.py @@ -1,4 +1,4 @@ -# Copyright (c) 2019-2020 by Rocky Bernstein +# Copyright (c) 2019-2021 by Rocky Bernstein # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -48,7 +48,7 @@ def is_docstring(node, version, co_consts): # return node.kind == "assign" and node[1][0].pattr == "__doc__" # except: # return False - if version <= 2.7: + if version <= (2, 7): doc_load = "LOAD_CONST" else: doc_load = "LOAD_STR" @@ -110,7 +110,7 @@ class TreeTransform(GenericASTTraversal, object): than the code field is seen and used. """ - if self.version >= 3.7: + if self.version >= (3, 7): code_index = -3 else: code_index = -2