Выполнение нарезанных байтовых кодов Python иногда приводит к «SystemError: unknown opcode» - PullRequest
2 голосов
/ 16 марта 2019

Учитывая объект кода, скомпилированный из следующих 3 строк кода:

code = compile('''a = 1 / 0 # bad stuff. avoid running this!
b = 'good stuff'
c = True''', '', 'exec')

, вызов которого dis.dis(code) будет разбираться на:

  1           0 LOAD_CONST               0 (1)
              2 LOAD_CONST               1 (0)
              4 BINARY_TRUE_DIVIDE
              6 STORE_NAME               0 (a)

  2           8 LOAD_CONST               2 ('good stuff')
             10 STORE_NAME               1 (b)

  3          12 LOAD_CONST               3 (True)
             14 STORE_NAME               2 (c)
             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

Как мне просто извлечь и запуститьбайтовые коды для второй строки, b = 'good stuff'?

Например, если я хочу извлечь и запустить только байтовые коды для последней строки, c = True, которая начинается с байтового индекса 12Я могу нарезать атрибут co_code объекта кода, который содержит необработанные байтовые коды из индекса 12, для создания объекта types.CodeType, а затем вызвать exec с ним:

import types
code3 = types.CodeType(
    code.co_argcount,
    code.co_kwonlyargcount,
    code.co_nlocals,
    code.co_stacksize,
    code.co_flags,
    code.co_code[12:],
    code.co_consts,
    code.co_names,
    code.co_varnames,
    code.co_filename,
    code.co_name,
    code.co_firstlineno,
    code.co_lnotab,
    code.co_freevars,
    code.co_cellvars)
exec(code3)
print(eval('c'))

чтобы он правильно выводил значение c как присвоено:

True

Однако, если я попытаюсь извлечь и запустить только байтовые коды для второй строки, b = 'good stuff', которая варьируется от индекса8 до 12 (не включая 12):

code2 = types.CodeType(
    code.co_argcount,
    code.co_kwonlyargcount,
    code.co_nlocals,
    code.co_stacksize,
    code.co_flags,
    code.co_code[8:12],
    code.co_consts,
    code.co_names,
    code.co_varnames,
    code.co_filename,
    code.co_name,
    code.co_firstlineno,
    code.co_lnotab,
    code.co_freevars,
    code.co_cellvars)
exec(code2)
print(eval('b'))

он производит:

XXX lineno: 1, opcode: 0
Traceback (most recent call last):
  File "/path/file.py", line 21, in <module>
    exec(code2)
  File "", line 1, in <module>
SystemError: unknown opcode

Вызов dis.dis(code2) покажет, что новый объект кода содержитправильные байтовые коды для b = 'good stuff':

  1           0 LOAD_CONST               2 ('good stuff')
              2 STORE_NAME               1 (b)

Так чего мне не хватает?

1 Ответ

4 голосов
/ 16 марта 2019

Я отвечаю на свой вопрос, потому что я не смог найти документацию по этой теме, и мне потребовалось некоторое время, чтобы выяснить, чего мне не хватало, чтобы это могло принести пользу другим, кто случайно столкнулся с той же проблемой.

Оказывается, что для каждого блока кода требуется для возврата значения - не вариант не возвращать значение. Если нет явного оператора return, то None будет возвращено неявно, как видно из последних двух байтовых кодов, показанных в вопросе:

             16 LOAD_CONST               4 (None)
             18 RETURN_VALUE

Таким образом, вырезая байтовые коды из индекса 12 для последней строки c = True, я случайно включил завершающий неявный возврат None, к счастью удовлетворяя требованию, чтобы блок кода возвращал значение.

Такого не было, когда я пытался нарезать байтовые коды от индекса 8 до 12 для второй строки b = 'good stuff', так как он пропускал последние два байтовых кода для возврата None, тем самым вызывая исключение SystemError: unknown opcode.

Таким образом, чтобы исправить это, все, что было необходимо, это добавить последние два байтовых кода (на самом деле всего 4 байта, поскольку байтовые коды фактически стали "словесными" кодами в Python 3) к слайсу:

code2 = types.CodeType(
    code.co_argcount,
    code.co_kwonlyargcount,
    code.co_nlocals,
    code.co_stacksize,
    code.co_flags,
    code.co_code[8:12] + code.co_code[-4:],
    code.co_consts,
    code.co_names,
    code.co_varnames,
    code.co_filename,
    code.co_name,
    code.co_firstlineno,
    code.co_lnotab,
    code.co_freevars,
    code.co_cellvars)
exec(code2)
print(eval('b'))

Это будет правильно выводить:

good stuff
...