diff --git a/HISTORY.md b/HISTORY.md index 59c90c7b..9672a1db 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -36,7 +36,7 @@ first subsequent public release announcement that I can find is From the CHANGES file found in [the tarball for that release](http://old-releases.ubuntu.com/ubuntu/pool/universe/d/decompyle2.2/decompyle2.2_2.2beta1.orig.tar.gz), it appears that Hartmut did most of the work to get this code to -accept the full Python language. He added precidence to the table +accept the full Python language. He added precedence to the table specifiers, support for multiple versions of Python, the pretty-printing of docstrings, lists, and hashes. He also wrote test and verification routines of deparsed bytecode, and used this in an extensive set of tests that he also wrote. He could verify against the entire Python library. @@ -55,6 +55,11 @@ it doesn't look like he's done anything compiler-wise since SPARK). So I hope people will use the crazy-compilers service. I wish them the success that his good work deserves. +Also looking at code I see Dan Pascu did a bit of work around 2005 on +the Python scanner, parser, and marshaling routines. For example I +see a bit code to massage disassembly output to make it more amenable +for deparsing. 2005 would put his work around the Python 2.4 releases. + Next we get to ["uncompyle" and PyPI](https://pypi.python.org/pypi/uncompyle/1.1) and the era of git repositories. In contrast to decompyle, this now runs diff --git a/test/bytecode_3.5/10_if_pass.pyc b/test/bytecode_3.5/10_if_pass.pyc new file mode 100644 index 00000000..7adab054 Binary files /dev/null and b/test/bytecode_3.5/10_if_pass.pyc differ diff --git a/test/simple_source/branching/10_if_pass.py b/test/simple_source/branching/10_if_pass.py new file mode 100644 index 00000000..a4944d1d --- /dev/null +++ b/test/simple_source/branching/10_if_pass.py @@ -0,0 +1,23 @@ +# Bug in Python 3.5 in disentangling jump "over" a "pass" statement +# or a jump to the next instruction. + +# On Python 3.5 you should get +# compare ::= expr expr COMPARE_OP +# ... +# jmp_false ::= POP_JUMP_IF_FALSE +# ... + +from weakref import ref + +class _localimpl: + + def create_dict(self, thread): + """Create a new dict for the current thread, and return it.""" + localdict = {} + idt = id(thread) + def thread_deleted(_, idt=idt): + local = wrlocal() + if local is not None: # bug is here + pass # jumping over here + wrlocal = ref(self, local_deleted) + return localdict diff --git a/uncompyle6/scanners/scanner26.py b/uncompyle6/scanners/scanner26.py index 39bc352b..58b30b2c 100755 --- a/uncompyle6/scanners/scanner26.py +++ b/uncompyle6/scanners/scanner26.py @@ -868,7 +868,7 @@ class Scanner26(scan.Scanner): Return the list of offsets. - This procedure is modelled after dis.findlables(), but here + This procedure is modelled after dis.findlabels(), but here for each target the number of jumps are counted. ''' diff --git a/uncompyle6/scanners/scanner27.py b/uncompyle6/scanners/scanner27.py index 8bbf81a6..18863f87 100755 --- a/uncompyle6/scanners/scanner27.py +++ b/uncompyle6/scanners/scanner27.py @@ -581,7 +581,7 @@ class Scanner27(scan.Scanner): Return the list of offsets. - This procedure is modelled after dis.findlables(), but here + This procedure is modelled after dis.findlabels(), but here for each target the number of jumps are counted. ''' diff --git a/uncompyle6/scanners/scanner3.py b/uncompyle6/scanners/scanner3.py index 5d4a10ef..d21e56e8 100644 --- a/uncompyle6/scanners/scanner3.py +++ b/uncompyle6/scanners/scanner3.py @@ -1,10 +1,21 @@ -# Copyright (c) 2015 by Rocky Bernstein +# Copyright (c) 2015, 2016 by Rocky Bernstein """ Python 3 Generic bytecode scanner/deparser This overlaps various Python3's dis module, but it can be run from -Python 2 and other versions of Python. Also, we save token information -for later use in deparsing. +Python versions other than the version running this code. Notably, +run from Python version 2. + +Also we *modify* the instruction sequence to assist deparsing code. +For example: + - we add "COME_FROM" instructions to help in figuring out + conditional branching and looping. + - LOAD_CONSTs are classified further into the type of thing + they load: + lambda's, genexpr's, {dict,set,list} comprehension's, + - PARAMETER counts appended {CALL,MAKE}_FUNCTION, BUILD_{TUPLE,SET,SLICE} + +Finally we save token information. """ from __future__ import print_function @@ -66,10 +77,10 @@ class Scanner3(scan.Scanner): # Scan for assertions. Later we will # turn 'LOAD_GLOBAL' to 'LOAD_ASSERT' for those # assertions - self.load_asserts = set() for i in self.op_range(0, codelen): - if self.code[i] == POP_JUMP_IF_TRUE and self.code[i+3] == LOAD_GLOBAL: + if (self.code[i] == POP_JUMP_IF_TRUE and + self.code[i+3] == LOAD_GLOBAL): if names[self.get_argument(i+3)] == 'AssertionError': self.load_asserts.add(i+3) @@ -258,7 +269,7 @@ class Scanner3(scan.Scanner): Return the list of offsets. - This procedure is modelled after dis.findlables(), but here + This procedure is modelled after dis.findlabels(), but here for each target the number of jumps is counted. """ code = self.code @@ -448,15 +459,20 @@ class Scanner3(scan.Scanner): self.fixed_jumps[offset] = rtarget return - # Does this jump to right after another cond jump? - # If so, it's part of a larger conditional - if (code[prev_op[target]] in (JUMP_IF_FALSE_OR_POP, JUMP_IF_TRUE_OR_POP, - POP_JUMP_IF_FALSE, POP_JUMP_IF_TRUE)) and (target > offset): + # Does this jump to right after another cond jump that is + # not myself? If so, it's part of a larger conditional. + # rocky: if we have a conditional jump to the next instruction, then + # possibly I am "skipping over" a "pass" or null statement. + if ((code[prev_op[target]] in + (JUMP_IF_FALSE_OR_POP, JUMP_IF_TRUE_OR_POP, + POP_JUMP_IF_FALSE, POP_JUMP_IF_TRUE)) and + (target > offset) and prev_op[target] != offset): self.fixed_jumps[offset] = prev_op[target] self.structs.append({'type': 'and/or', 'start': start, 'end': prev_op[target]}) return + # Is it an and inside if block if op == POP_JUMP_IF_FALSE: # Search for other POP_JUMP_IF_FALSE targetting the same op, diff --git a/uncompyle6/scanners/scanner35.py b/uncompyle6/scanners/scanner35.py index a8182cf6..469c31cb 100644 --- a/uncompyle6/scanners/scanner35.py +++ b/uncompyle6/scanners/scanner35.py @@ -37,9 +37,6 @@ class Scanner35(scan3.Scanner3): self.build_lines_data(co) self.build_prev_op() - # Get jump targets - # Format: {target offset: [jump offsets]} - jump_targets = self.find_jump_targets() bytecode = dis35.Bytecode(co) # self.lines contains (block,addrLastInstr) @@ -61,12 +58,17 @@ class Scanner35(scan3.Scanner3): n = len(bs) for i in range(n): inst = bs[i] - if inst.opname == 'POP_JUMP_IF_TRUE' and i+1 < n: + + if inst.opname == 'POP_JUMP_IF_TRUE' and i+1 < n: next_inst = bs[i+1] if (next_inst.opname == 'LOAD_GLOBAL' and next_inst.argval == 'AssertionError'): self.load_asserts.add(next_inst.offset) + # Get jump targets + # Format: {target offset: [jump offsets]} + jump_targets = self.find_jump_targets() + for inst in bytecode: if inst.offset in jump_targets: jump_idx = 0