diff --git a/uncompyle6/parsers/parse38.py b/uncompyle6/parsers/parse38.py new file mode 100644 index 00000000..2b6ea9b8 --- /dev/null +++ b/uncompyle6/parsers/parse38.py @@ -0,0 +1,54 @@ +# Copyright (c) 2017-2019 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +spark grammar differences over Python 3.7 for Python 3.8 +""" +from __future__ import print_function + +from uncompyle6.parser import PythonParserSingle +from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG +from uncompyle6.parsers.parse37 import Python37Parser + +class Python38Parser(Python37Parser): + + def __init__(self, debug_parser=PARSER_DEFAULT_DEBUG): + super(Python38Parser, self).__init__(debug_parser) + self.customized = {} + + +class Python38ParserSingle(Python38Parser, PythonParserSingle): + pass + +if __name__ == '__main__': + # Check grammar + p = Python38Parser() + p.check_grammar() + from uncompyle6 import PYTHON_VERSION, IS_PYPY + if PYTHON_VERSION == 3.8: + lhs, rhs, tokens, right_recursive = p.check_sets() + from uncompyle6.scanner import get_scanner + s = get_scanner(PYTHON_VERSION, IS_PYPY) + opcode_set = set(s.opc.opname).union(set( + """JUMP_BACK CONTINUE RETURN_END_IF COME_FROM + LOAD_GENEXPR LOAD_ASSERT LOAD_SETCOMP LOAD_DICTCOMP LOAD_CLASSNAME + LAMBDA_MARKER RETURN_LAST + """.split())) + remain_tokens = set(tokens) - opcode_set + import re + remain_tokens = set([re.sub(r'_\d+$', '', t) for t in remain_tokens]) + remain_tokens = set([re.sub('_CONT$', '', t) for t in remain_tokens]) + remain_tokens = set(remain_tokens) - opcode_set + print(remain_tokens) + # print(sorted(p.rule2name.items())) diff --git a/uncompyle6/scanner.py b/uncompyle6/scanner.py index c9318d8b..43bdf151 100755 --- a/uncompyle6/scanner.py +++ b/uncompyle6/scanner.py @@ -1,4 +1,4 @@ -# Copyright (c) 2016, 2018 by Rocky Bernstein +# Copyright (c) 2016, 2018-2019 by Rocky Bernstein # Copyright (c) 2005 by Dan Pascu # Copyright (c) 2000-2002 by hartmut Goebel # Copyright (c) 1999 John Aycock @@ -39,7 +39,7 @@ from xdis.util import code2num # Note: these all have to be floats PYTHON_VERSIONS = frozenset((1.3, 1.4, 1.5, 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.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) diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 343944c6..693a6d0f 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -64,8 +64,13 @@ class Scanner3(Scanner): # Ops that start SETUP_ ... We will COME_FROM with these names # Some blocks and END_ statements. And they can start # a new statement - setup_ops = [self.opc.SETUP_LOOP, self.opc.SETUP_EXCEPT, - self.opc.SETUP_FINALLY] + if self.version < 3.8: + setup_ops = [self.opc.SETUP_LOOP, self.opc.SETUP_EXCEPT, + self.opc.SETUP_FINALLY] + self.setup_ops_no_loop = frozenset(setup_ops) - frozenset([self.opc.SETUP_LOOP]) + else: + setup_ops = [self.opc.SETUP_FINALLY] + self.setup_ops_no_loop = frozenset(setup_ops) if self.version >= 3.2: setup_ops.append(self.opc.SETUP_WITH) @@ -78,11 +83,9 @@ class Scanner3(Scanner): self.pop_jump_tf = frozenset([self.opc.PJIF, self.opc.PJIT]) self.not_continue_follow = ('END_FINALLY', 'POP_BLOCK') - self.setup_ops_no_loop = frozenset(setup_ops) - frozenset([self.opc.SETUP_LOOP]) # Opcodes that can start a statement. statement_opcodes = [ - self.opc.BREAK_LOOP, self.opc.CONTINUE_LOOP, self.opc.POP_BLOCK, self.opc.STORE_FAST, self.opc.DELETE_FAST, self.opc.STORE_DEREF, @@ -97,6 +100,9 @@ class Scanner3(Scanner): self.opc.PRINT_EXPR, self.opc.JUMP_ABSOLUTE ] + 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 # Opcodes that can start a "store" non-terminal. @@ -628,7 +634,7 @@ class Scanner3(Scanner): end = current_end parent = struct - if 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. @@ -857,6 +863,11 @@ 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: + 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): if_end = self.get_target(pre_rtarget) @@ -891,8 +902,7 @@ class Scanner3(Scanner): 'start': start, 'end': pre_rtarget}) self.not_continue.add(pre_rtarget) - elif code[pre_rtarget] in (self.opc.RETURN_VALUE, - self.opc.BREAK_LOOP): + elif code[pre_rtarget] in rtarget_break: self.structs.append({'type': 'if-then', 'start': start, 'end': rtarget}) @@ -957,7 +967,7 @@ class Scanner3(Scanner): if rtarget > offset: self.fixed_jumps[offset] = rtarget - elif 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 diff --git a/uncompyle6/scanners/scanner38.py b/uncompyle6/scanners/scanner38.py new file mode 100644 index 00000000..12ba67b9 --- /dev/null +++ b/uncompyle6/scanners/scanner38.py @@ -0,0 +1,51 @@ +# Copyright (c) 2019 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +""" +Python 3.8 bytecode decompiler scanner + +Does some additional massaging of xdis-disassembled instructions to +make things easier for decompilation. + +This sets up opcodes Python's 3.8 and calls a generalized +scanner routine for Python 3. +""" + +from __future__ import print_function + +from uncompyle6.scanners.scanner37 import Scanner3 + +# bytecode verification, verify(), uses JUMP_OPs from here +from xdis.opcodes import opcode_37 as opc +JUMP_OPs = opc.JUMP_OPS + +class Scanner38(Scanner3): + + def __init__(self, show_asm=None): + Scanner3.__init__(self, 3.8, show_asm) + return + pass + +if __name__ == "__main__": + from uncompyle6 import PYTHON_VERSION + if PYTHON_VERSION == 3.8: + import inspect + co = inspect.currentframe().f_code + tokens, customize = Scanner38().ingest(co) + for t in tokens: + print(t.format()) + pass + else: + print("Need to be Python 3.8 to demo; I am %s." % + PYTHON_VERSION)