Fix 3.5 if..pass bug

Update HISTORY.MD to include Dan Pascu. Some minor doc corrections
This commit is contained in:
rocky
2016-05-08 10:22:29 -04:00
parent 61c4a711a2
commit 4a79082872
7 changed files with 63 additions and 17 deletions

View File

@@ -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

Binary file not shown.

View File

@@ -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

View File

@@ -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.
'''

View File

@@ -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.
'''

View File

@@ -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,

View File

@@ -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