scanner36.py 3.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. # Copyright (c) 2016-2018, 2021-2022 by Rocky Bernstein
  2. """
  3. Python 3.6 bytecode decompiler scanner
  4. Does some additional massaging of xdis-disassembled instructions to
  5. make things easier for decompilation.
  6. This sets up opcodes Python's 3.6 and calls a generalized
  7. scanner routine for Python 3.
  8. """
  9. from __future__ import print_function
  10. from uncompyle6.scanners.scanner3 import Scanner3
  11. # bytecode verification, verify(), uses JUMP_OPS from here
  12. from xdis.opcodes import opcode_36 as opc
  13. JUMP_OPS = opc.JUMP_OPS
  14. class Scanner36(Scanner3):
  15. def __init__(self, show_asm=None, is_pypy=False):
  16. Scanner3.__init__(self, (3, 6), show_asm, is_pypy)
  17. return
  18. def ingest(self, co, classname=None, code_objects={}, show_asm=None):
  19. """
  20. Create "tokens" the bytecode of an Python code object. Largely these
  21. are the opcode name, but in some cases that has been modified to make parsing
  22. easier.
  23. returning a list of uncompyle6 Token's.
  24. Some transformations are made to assist the deparsing grammar:
  25. - various types of LOAD_CONST's are categorized in terms of what they load
  26. - COME_FROM instructions are added to assist parsing control structures
  27. - operands with stack argument counts or flag masks are appended to the opcode name, e.g.:
  28. * BUILD_LIST, BUILD_SET
  29. * MAKE_FUNCTION and FUNCTION_CALLS append the number of positional arguments
  30. - EXTENDED_ARGS instructions are removed
  31. Also, when we encounter certain tokens, we add them to a set which will cause custom
  32. grammar rules. Specifically, variable arg tokens like MAKE_FUNCTION or BUILD_LIST
  33. cause specific rules for the specific number of arguments they take.
  34. """
  35. tokens, customize = Scanner3.ingest(self, co, classname, code_objects, show_asm)
  36. not_pypy36 = not (self.version[:2] == (3, 6) and self.is_pypy)
  37. for t in tokens:
  38. # The lowest bit of flags indicates whether the
  39. # var-keyword argument is placed at the top of the stack
  40. if ( not_pypy36 and
  41. t.op == self.opc.CALL_FUNCTION_EX and t.attr & 1):
  42. t.kind = 'CALL_FUNCTION_EX_KW'
  43. pass
  44. elif t.op == self.opc.BUILD_STRING:
  45. t.kind = 'BUILD_STRING_%s' % t.attr
  46. elif t.op == self.opc.CALL_FUNCTION_KW:
  47. t.kind = 'CALL_FUNCTION_KW_%s' % t.attr
  48. elif t.op == self.opc.FORMAT_VALUE:
  49. if (t.attr & 0x4):
  50. t.kind = 'FORMAT_VALUE_ATTR'
  51. pass
  52. elif ( not_pypy36 and
  53. t.op == self.opc.BUILD_MAP_UNPACK_WITH_CALL ):
  54. t.kind = 'BUILD_MAP_UNPACK_WITH_CALL_%d' % t.attr
  55. elif ( not_pypy36 and t.op == self.opc.BUILD_TUPLE_UNPACK_WITH_CALL ):
  56. t.kind = 'BUILD_TUPLE_UNPACK_WITH_CALL_%d' % t.attr
  57. pass
  58. return tokens, customize
  59. if __name__ == "__main__":
  60. from xdis.version_info import PYTHON_VERSION_TRIPLE, version_tuple_to_str
  61. if PYTHON_VERSION_TRIPLE[:2] == (3, 6):
  62. import inspect
  63. co = inspect.currentframe().f_code # type: ignore
  64. tokens, customize = Scanner36().ingest(co)
  65. for t in tokens:
  66. print(t.format())
  67. pass
  68. else:
  69. print("Need to be Python 3.6 to demo; I am version %s" % version_tuple_to_str())