rocky / python-uncompyle6

A cross-version Python bytecode decompiler
GNU General Public License v3.0
3.74k stars 408 forks source link

lambda + dict comprehension is not decompiled properly #469

Closed Monstrofil closed 1 year ago

Monstrofil commented 1 year ago

Description

I'm trying to decompile python2.7 code, but uncompyle6 fails near lambda-style instructions. I was able to prepare small example which reproduces the behaviour.

How to Reproduce

The following code is absolutely valid and can be executed:

def func(self):
    self.oldVehicleValue = (lambda variable0: {
        variable1: 123
        for variable1 in variable0
    })([1, 2, 3])

The bytecode that python produces for me is the next:

  55           0 LOAD_CONST               1 (<code object <lambda> at 00000000031851B0, file "E:\workprojects\bytecode_simplifier\recovered.py", line 55>)
              3 MAKE_FUNCTION            0

 58           6 LOAD_CONST               2 (1)
              9 LOAD_CONST               3 (2)
             12 LOAD_CONST               4 (3)
             15 BUILD_LIST               3
             18 CALL_FUNCTION            1
             21 LOAD_FAST                0 (self)
             24 STORE_ATTR               0 (oldVehicleValue)
             27 LOAD_CONST               0 (None)
             30 RETURN_VALUE    

Output Given


# file E:\workprojects\bytecode_simplifier\orig\lambdas.pyc
# --- This code section failed: ---

(001) L.  56         0  BUILD_MAP_0           0  None
(002)                3  LOAD_FAST             0  '.0'
(003)                6  FOR_ITER             15  'to 24'

(004) L.  57         9  STORE_FAST            1  'variable1'
(005)               12  LOAD_CONST               123
(006)               15  LOAD_FAST             1  'variable1'
(007)               18  MAP_ADD               2  None
(008)               21  JUMP_BACK             6  'to 6'
(009)               24  RETURN_VALUE_LAMBDA
(010)               -1  LAMBDA_MARKER    

Parse error at or near `RETURN_VALUE_LAMBDA' instruction at offset 24

Process finished with exit code 0

Expected behavior

Successfull decomplation or at lease skipped block.

Please modify for your setup

I tried to fix the issue myself, but it appears to be much harder than I thought about it.

Monstrofil commented 1 year ago

Is there actually any way to "tell" uncompyle6 to skip such blocks in order to decompyle at least everything else?

e.g. here is how decompyle++ handles this:

# Source Generated with Decompyle++
# File: lambdas.pyc (Python 2.7)

Unsupported opcode: MAP_ADD
self.oldVehicleValue = (lambda variable0: (lambda .0: pass# WARNING: Decompyle incomplete
)(variable0)
)([
    1,
    2,
    3])

Not perfect, but in big functions it's much better to see at least something:

  self.vehicleVelocity = Vector3(0, 0, 0)
    self.torpedoIndex = 0
    self.selectedTorpedoAngle = 0
    self.stateWeaponLocks = []
    self.inBlueLineZone = False
    self.selectedTorpedoGroup = 0
    self.selectedTorpedoLaunchers = 0
    self.gameRoomInfo = None
    self.arenaUniqueId = None
    self.oldVehicleValue = self.vehicle
Unsupported opcode: MAP_ADD
    self.weaponsReloadAmmoID = (lambda variable0: pass# WARNING: Decompyle incomplete
)(WeaponType.ALL_WEAPONS)
Unsupported opcode: MAP_ADD
    self.selectedWeaponsAmmo = (lambda variable0: pass# WARNING: Decompyle incomplete
)(WeaponType.ALL_WEAPONS)
    self.shipHasWeapon = { }
    self.shipHasAliveGuns = { }
    self.GUISpeed = 0
    self.minimapVaryId = None
    self.updVarId = None
    self.updateCellDataVaryId = None
    self.fogHornVary = None
    self.updateBlueLineVaryId = None
    self.movementControl = None
    self.oldFov = ConstantsShip.MIN_FOV
    self.keysLastPressTime = None
    self.projTraces = { }
    self.damageStatLog = None
    self.damageReportLogDirect = None
    self.damageReportLogSplash = None
    if devMode():
rocky commented 1 year ago

uncompyle6 works by function and if a particular function fails the other functions are still compiled. This is open source, so feel free to think up something and submit a PR.

I am open to adding an option that indicates putting in comments the errors or omitting the errors altogether and putting in a dummy function.

As for the original problem, that's is easily fixed. However while easily fixed seems to be more pervasive and appears in more bytecode versions than just 2.7.

When I get a chance, there will be a fix.