Python 3.10 support (work in progress).

Updated bytecode library (and other minors).
This commit is contained in:
Fabio Zadrozny 2021-08-06 07:18:09 -03:00
parent e59195d6a4
commit 3207466fc3
44 changed files with 559 additions and 454 deletions

View file

@ -45,7 +45,7 @@
"git": {
"Name": "bytecode",
"RepositoryUrl": "https://github.com/MatthieuDartiailh/bytecode",
"CommitHash": "826344ec533643fad068754e0b9799bfeac46634"
"CommitHash": "ccb8a7b4bf12a85ff53c3c42517d2633c5225829"
},
"DevelopmentDependency": false
}

View file

@ -1032,7 +1032,7 @@ class _NewThreadStartupWithTrace:
# Note: if this is a thread from threading.py, we're too early in the boostrap process (because we mocked
# the start_new_thread internal machinery and thread._bootstrap has not finished), so, the code below needs
# to make sure that we use the current thread bound to the original function and not use
# current_thread() unless we're sure it's a dummy thread.
# threading.current_thread() unless we're sure it's a dummy thread.
t = getattr(self.original_func, '__self__', getattr(self.original_func, 'im_self', None))
if not isinstance(t, threading.Thread):
# This is not a threading.Thread but a Dummy thread (so, get it as a dummy thread using

View file

@ -155,7 +155,7 @@ class CommunicationThread(threading.Thread):
def __init__(self, tests_queue):
threading.Thread.__init__(self)
self.setDaemon(True)
self.daemon = True
self.queue = tests_queue
self.finished = False
from _pydev_bundle.pydev_imports import SimpleXMLRPCServer
@ -243,7 +243,7 @@ class ClientThread(threading.Thread):
def __init__(self, job_id, port, verbosity, coverage_output_file=None, coverage_include=None):
threading.Thread.__init__(self)
self.setDaemon(True)
self.daemon = True
self.port = port
self.job_id = job_id
self.verbosity = verbosity

View file

@ -194,6 +194,11 @@ class _StackInterpreter(object):
on_DICT_UPDATE = on_POP_TOP
on_SET_UPDATE = on_POP_TOP
on_GEN_START = on_POP_TOP
def on_NOP(self, instr):
pass
def _handle_call_from_instr(self, func_name_instr, func_call_instr):
self.load_attrs.pop(_TargetIdHashable(func_name_instr), None)
call_name = self._getcallname(func_name_instr)

View file

@ -184,6 +184,7 @@ IS_PY36_OR_GREATER = False
IS_PY37_OR_GREATER = False
IS_PY38_OR_GREATER = False
IS_PY39_OR_GREATER = False
IS_PY310_OR_GREATER = False
IS_PY2 = True
IS_PY27 = False
IS_PY24 = False
@ -197,6 +198,7 @@ try:
IS_PY37_OR_GREATER = sys.version_info >= (3, 7)
IS_PY38_OR_GREATER = sys.version_info >= (3, 8)
IS_PY39_OR_GREATER = sys.version_info >= (3, 9)
IS_PY310_OR_GREATER = sys.version_info >= (3, 10)
elif sys.version_info[0] == 2 and sys.version_info[1] == 7:
IS_PY27 = True
elif sys.version_info[0] == 2 and sys.version_info[1] == 4:

View file

@ -148,7 +148,7 @@ def notify_error(*args):
#=======================================================================================================================
def code_objects_equal(code0, code1):
for d in dir(code0):
if d.startswith('_') or 'lineno' in d or d == 'replace':
if d.startswith('_') or 'line' in d or d == 'replace':
continue
if getattr(code0, d) != getattr(code1, d):
return False

View file

@ -65,7 +65,7 @@ def getVariable(dbg, thread_id, frame_id, scope, attrs):
not the frame (as we don't care about the frame in this case).
"""
if scope == 'BY_ID':
if thread_id != get_current_thread_id(current_thread()):
if thread_id != get_current_thread_id(threading.current_thread()):
raise VariableError("getVariable: must execute on same thread")
try:

View file

@ -8,9 +8,11 @@ pip install bytecode --target .
or from master (if needed for some early bugfix):
python -m pip install https://github.com/vstinner/bytecode/archive/master.zip --target .
python -m pip install https://github.com/MatthieuDartiailh/bytecode/archive/main.zip --target .
Then run 'pydevd_fix_code.py' to fix the imports on the vendored file, run its tests (to see
if things are still ok) and commit.
Then, to finish, apply the patch to add the offset to the instructions (bcb8a28669e9178f96f5d71af7259e0674acc47c)
Note: commit the egg-info as a note of the license (force if needed).

View file

@ -1,41 +0,0 @@
bytecode-0.12.0.dist-info/COPYING,sha256=nPUTV23AcD5UZcc9EtVTBt-Igoyj7IIRw97xuOGVgq8,1095
bytecode-0.12.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
bytecode-0.12.0.dist-info/METADATA,sha256=asu9c0mCLVYLxmfElINQCm0lAMBdVcgZqMIoCI1Zfnk,2589
bytecode-0.12.0.dist-info/RECORD,,
bytecode-0.12.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
bytecode-0.12.0.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
bytecode-0.12.0.dist-info/top_level.txt,sha256=9BhdB7HqYZ-PvHNoWX6ilwLYWQqcgEOLwdb3aXm5Gys,9
bytecode/__init__.py,sha256=8krUbp5Kcxe_oC3S5OXwB-VXoMP_V-13FV0eLLFPPs4,4011
bytecode/__pycache__/__init__.cpython-38.pyc,,
bytecode/__pycache__/bytecode.cpython-38.pyc,,
bytecode/__pycache__/cfg.cpython-38.pyc,,
bytecode/__pycache__/concrete.cpython-38.pyc,,
bytecode/__pycache__/flags.cpython-38.pyc,,
bytecode/__pycache__/instr.cpython-38.pyc,,
bytecode/__pycache__/peephole_opt.cpython-38.pyc,,
bytecode/bytecode.py,sha256=8eZ6vZTfCFEB0lwupJZsZds5u6Is7wlX6IlR8olG7QQ,6897
bytecode/cfg.py,sha256=NGBJ5lZuCQuAHvyWiyUHPXGBA_8ze3CzyXvJxmcYTWk,15239
bytecode/concrete.py,sha256=0Ojd-mED2COoB_fzEHL6OcW7zqtb76k6lEbkvJPNCQY,21281
bytecode/flags.py,sha256=opG0Yh8oQ0EUPIAA5S3TIHHaojYO_sV3RHc3WSFlopg,5956
bytecode/instr.py,sha256=Zavx4BNwJfWLNZUIGXukGPInxTsflyZeZ6cAcqq1ggk,11832
bytecode/peephole_opt.py,sha256=_XI45yErqZ4pGEUqRQVcMl5vS2IY4dqcrCGmHIHJdow,16203
bytecode/tests/__init__.py,sha256=tpG_2bYVAAFqVQz2ZbkZ-XQAX8k8rQjBbAUVhZqMLkA,5163
bytecode/tests/__pycache__/__init__.cpython-38.pyc,,
bytecode/tests/__pycache__/sanitytest.cpython-38.pyc,,
bytecode/tests/__pycache__/test_bytecode.cpython-38.pyc,,
bytecode/tests/__pycache__/test_cfg.cpython-38.pyc,,
bytecode/tests/__pycache__/test_code.cpython-38.pyc,,
bytecode/tests/__pycache__/test_concrete.cpython-38.pyc,,
bytecode/tests/__pycache__/test_flags.cpython-38.pyc,,
bytecode/tests/__pycache__/test_instr.cpython-38.pyc,,
bytecode/tests/__pycache__/test_misc.cpython-38.pyc,,
bytecode/tests/__pycache__/test_peephole_opt.cpython-38.pyc,,
bytecode/tests/sanitytest.py,sha256=-UiwWgJe9y6LC2Bim0ZdV6aKN47b3uJ_RWELbfzO_0g,711
bytecode/tests/test_bytecode.py,sha256=3dH08nPMs_T6sZXHybS0beA8pTvBeyUqLIS5WaVx1VQ,15150
bytecode/tests/test_cfg.py,sha256=h-Z7tZsWDqdW0BEzQBYmnKKlw2LqeeiKGpiUwPc4ADI,29056
bytecode/tests/test_code.py,sha256=8wqx8uli3_UXewDeh6wt52oDZcRp3L7SXYoGkmIP1Zg,1697
bytecode/tests/test_concrete.py,sha256=uamFKnciMljHHIL7U691KQemFE0EazvhSOV1ErcgUF4,49375
bytecode/tests/test_flags.py,sha256=ve1FXkaXO9mBly4EO0vTySeWZC4x17yyBMdfvHNtWqc,5838
bytecode/tests/test_instr.py,sha256=ucFNTYxuHN1S2-lIuIVl9QV0zL4DDnUGmcbz5Je2ONo,11652
bytecode/tests/test_misc.py,sha256=l5WOEITwedUz_PbZu3Z1E5qk4lSDpEAeIRNGKHONTJE,7704
bytecode/tests/test_peephole_opt.py,sha256=kPEUtsgpCkM889c1zV0GcaB1Iy3F75ypJMQYBpHQZdI,33620

View file

@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: bytecode
Version: 0.12.0
Version: 0.13.0.dev0
Summary: Python module to generate and modify bytecode
Home-page: https://github.com/MatthieuDartiailh/bytecode
Author: Victor Stinner
@ -15,8 +15,12 @@ Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Dist: aenum (>=2.0) ; python_version < "3.6"
Requires-Python: >=3.6
********
bytecode
@ -51,8 +55,10 @@ bytecode
* `Download latest bytecode release at the Python Cheeseshop (PyPI)
<https://pypi.python.org/pypi/bytecode>`_
Install bytecode: ``python3 -m pip install bytecode``. It requires Python 3.5
or newer.
Install bytecode: ``python3 -m pip install bytecode``. It requires Python 3.6
or newer. The latest release that supports Python 3.5 is 0.12.0. For Python 2.7
support, have a look at `dead-bytecode
<https://github.com/p403n1x87/dead-bytecode>`_ instead.
Example executing ``print('Hello World!')``:

View file

@ -0,0 +1,42 @@
bytecode-0.13.0.dev0.dist-info/COPYING,sha256=baWkm-Te2LLURwK7TL0zOkMSVjVCU_ezvObHBo298Tk,1074
bytecode-0.13.0.dev0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
bytecode-0.13.0.dev0.dist-info/METADATA,sha256=9XadDK6YTQ-FPowYI5DS4ieA7hRGnRP_fM5Z9ioPkEQ,2929
bytecode-0.13.0.dev0.dist-info/RECORD,,
bytecode-0.13.0.dev0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
bytecode-0.13.0.dev0.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
bytecode-0.13.0.dev0.dist-info/direct_url.json,sha256=s58Rb4KXRlMKxk-mzpvr_tJRQ-Hx8-DHsU6NdohCnAg,93
bytecode-0.13.0.dev0.dist-info/top_level.txt,sha256=9BhdB7HqYZ-PvHNoWX6ilwLYWQqcgEOLwdb3aXm5Gys,9
bytecode/__init__.py,sha256=d-yk4Xh4SwOWq9NgoD2rmBLG6RhUFNljeqs-NjMNSYM,3885
bytecode/__pycache__/__init__.cpython-38.pyc,,
bytecode/__pycache__/bytecode.cpython-38.pyc,,
bytecode/__pycache__/cfg.cpython-38.pyc,,
bytecode/__pycache__/concrete.cpython-38.pyc,,
bytecode/__pycache__/flags.cpython-38.pyc,,
bytecode/__pycache__/instr.cpython-38.pyc,,
bytecode/__pycache__/peephole_opt.cpython-38.pyc,,
bytecode/bytecode.py,sha256=IMCcatHMtQ7M31nwj4r3drcvQuGVJAOP0d7C0O8P_SE,6894
bytecode/cfg.py,sha256=RmJGJqwCxR-XYaPH9YGY4wNDycdtLvIBJb1OGSmxcN0,15274
bytecode/concrete.py,sha256=0eb6Yh_NDLmzJNcMs2TFom0EqFVSM1cO3inMH90YE-s,21683
bytecode/flags.py,sha256=hAvM_B2yQKRw44leHP0oCae0aaJraAbDDTpqIf4I1CM,5987
bytecode/instr.py,sha256=HYc65LjNSOB3GCWkNkCSkee1rRzUyr89rgdjbKBaTpE,11616
bytecode/peephole_opt.py,sha256=W-cFVPOZN-JKfDV3aImsYenDSZkSNBDTVQqeMrGPU18,15712
bytecode/tests/__init__.py,sha256=BAdOXXNRdMVX4D8TuRYPlG9PHU7Cb0bzvyfA9s435kM,4968
bytecode/tests/__pycache__/__init__.cpython-38.pyc,,
bytecode/tests/__pycache__/test_bytecode.cpython-38.pyc,,
bytecode/tests/__pycache__/test_cfg.cpython-38.pyc,,
bytecode/tests/__pycache__/test_code.cpython-38.pyc,,
bytecode/tests/__pycache__/test_concrete.cpython-38.pyc,,
bytecode/tests/__pycache__/test_flags.cpython-38.pyc,,
bytecode/tests/__pycache__/test_instr.cpython-38.pyc,,
bytecode/tests/__pycache__/test_misc.cpython-38.pyc,,
bytecode/tests/__pycache__/test_peephole_opt.cpython-38.pyc,,
bytecode/tests/__pycache__/util_annotation.cpython-38.pyc,,
bytecode/tests/test_bytecode.py,sha256=buvtlDC0NwoQ3zuZ7OENIIDngSqtiO9WkAa2-UvxGkI,15584
bytecode/tests/test_cfg.py,sha256=c0xT8OfV-mDHu-DIDWr6LVlZQyK4GfgLSmT5AsodbMk,28194
bytecode/tests/test_code.py,sha256=XCOH29rOXSoQz130s-AIC62r23e9qNjk8Y2xDB2LmSc,2100
bytecode/tests/test_concrete.py,sha256=qT2qvabkF0yC7inniNx53cMSDN-2Qi0IE3pwBZSzF8g,49253
bytecode/tests/test_flags.py,sha256=DY9U3c6tJdxJFm0jEm_To1Cc0I99EidQv_0guud-4oE,5684
bytecode/tests/test_instr.py,sha256=rYeF8u-L0aW8bLPBxTUSy_T7KP6SaXyJKv9OhC8k6aA,11295
bytecode/tests/test_misc.py,sha256=wyK1wpVPHRfaXgo-EqUI-F1nyB9-UACerHsHbExAo1U,6758
bytecode/tests/test_peephole_opt.py,sha256=niUfhgEbiFR7IAmdQ_N9Qgh7D3wdRQ_zS0V8mKC4EzI,32640
bytecode/tests/util_annotation.py,sha256=wKq6yPWrzkNlholl5Y10b3VjuCkoiYVgvcIjk_8jzf8,485

View file

@ -0,0 +1 @@
{"archive_info": {}, "url": "https://github.com/MatthieuDartiailh/bytecode/archive/main.zip"}

View file

@ -1,4 +1,4 @@
__version__ = "0.12.0"
__version__ = "0.13.0.dev"
__all__ = [
"Label",

View file

@ -198,8 +198,12 @@ class Bytecode(_InstrList, _BaseBytecodeList):
def to_code(
self, compute_jumps_passes=None, stacksize=None, *, check_pre_and_post=True
):
# Prevent reconverting the concrete bytecode to bytecode and cfg to do the
# calculation if we need to do it.
if stacksize is None:
stacksize = self.compute_stacksize(check_pre_and_post=check_pre_and_post)
bc = self.to_concrete_bytecode(compute_jumps_passes=compute_jumps_passes)
return bc.to_code(stacksize=stacksize, check_pre_and_post=check_pre_and_post)
return bc.to_code(stacksize=stacksize)
def to_concrete_bytecode(self, compute_jumps_passes=None):
converter = _bytecode._ConvertBytecodeToConcrete(self)

View file

@ -1,6 +1,9 @@
import sys
# alias to keep the 'bytecode' variable free
from _pydevd_frame_eval.vendored import bytecode as _bytecode
from _pydevd_frame_eval.vendored.bytecode.concrete import ConcreteInstr
from _pydevd_frame_eval.vendored.bytecode.flags import CompilerFlags
from _pydevd_frame_eval.vendored.bytecode.instr import Label, SetLineno, Instr
@ -219,8 +222,20 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
block.seen = False
block.startsize = -32768 # INT_MIN
# Starting with Python 3.10, generator and coroutines start with one object
# on the stack (None, anything is an error).
initial_stack_size = 0
if sys.version_info >= (3, 10) and self.flags & (
CompilerFlags.GENERATOR
| CompilerFlags.COROUTINE
| CompilerFlags.ASYNC_GENERATOR
):
initial_stack_size = 1
# Create a generator/coroutine responsible of dealing with the first block
coro = _compute_stack_size(self[0], 0, 0, check_pre_and_post=check_pre_and_post)
coro = _compute_stack_size(
self[0], initial_stack_size, 0, check_pre_and_post=check_pre_and_post
)
# Create a list of generator that have not yet been exhausted
coroutines = []

View file

@ -19,7 +19,11 @@ from _pydevd_frame_eval.vendored.bytecode.instr import (
_check_arg_int,
)
_WORDCODE = sys.version_info >= (3, 6)
# - jumps use instruction
# - lineno use bytes (dis.findlinestarts(code))
# - dis displays bytes
OFFSET_AS_INSTRUCTION = sys.version_info >= (3, 10)
def _set_docstring(code, consts):
@ -60,22 +64,13 @@ class ConcreteInstr(Instr):
def _set(self, name, arg, lineno):
super()._set(name, arg, lineno)
if _WORDCODE:
size = 2
if arg is not UNSET:
while arg > 0xFF:
size += 2
arg >>= 8
if self._extended_args is not None:
size = 2 + 2 * self._extended_args
else:
size = 1
if arg is not UNSET:
size = 2
if arg is not UNSET:
while arg > 0xFF:
size += 2
if arg > 0xFFFF:
size += 3
if self._extended_args is not None:
size = 1 + 3 * self._extended_args
arg >>= 8
if self._extended_args is not None:
size = 2 + 2 * self._extended_args
self._size = size
@property
@ -87,64 +82,41 @@ class ConcreteInstr(Instr):
def get_jump_target(self, instr_offset):
if self._opcode in _opcode.hasjrel:
return instr_offset + self._size + self._arg
s = (self._size // 2) if OFFSET_AS_INSTRUCTION else self._size
return instr_offset + s + self._arg
if self._opcode in _opcode.hasjabs:
return self._arg
return None
if _WORDCODE:
def assemble(self):
if self._arg is UNSET:
return bytes((self._opcode, 0))
def assemble(self):
if self._arg is UNSET:
return bytes((self._opcode, 0))
arg = self._arg
b = [self._opcode, arg & 0xFF]
while arg > 0xFF:
arg >>= 8
b[:0] = [_opcode.EXTENDED_ARG, arg & 0xFF]
arg = self._arg
b = [self._opcode, arg & 0xFF]
while arg > 0xFF:
arg >>= 8
b[:0] = [_opcode.EXTENDED_ARG, arg & 0xFF]
if self._extended_args:
while len(b) < self._size:
b[:0] = [_opcode.EXTENDED_ARG, 0x00]
if self._extended_args:
while len(b) < self._size:
b[:0] = [_opcode.EXTENDED_ARG, 0x00]
return bytes(b)
else:
def assemble(self):
if self._arg is UNSET:
return struct.pack("<B", self._opcode)
arg = self._arg
if arg > 0xFFFF:
b = struct.pack(
"<BHBH", _opcode.EXTENDED_ARG, arg >> 16, self._opcode, arg & 0xFFFF
)
else:
b = struct.pack("<BH", self._opcode, arg)
if self._extended_args:
while len(b) < self._size:
b = struct.pack("<BH", _opcode.EXTENDED_ARG, 0) + b
return b
return bytes(b)
@classmethod
def disassemble(cls, lineno, code, offset):
op = code[offset]
index = 2 * offset if OFFSET_AS_INSTRUCTION else offset
op = code[index]
if op >= _opcode.HAVE_ARGUMENT:
if _WORDCODE:
arg = code[offset + 1]
else:
arg = code[offset + 1] + code[offset + 2] * 256
arg = code[index + 1]
else:
arg = UNSET
name = _opcode.opname[op]
# fabioz: added offset to ConcreteBytecode
# Need to keep an eye on https://github.com/MatthieuDartiailh/bytecode/issues/48 in
# case the library decides to add this in some other way.
return cls(name, arg, lineno=lineno, offset=offset)
return cls(name, arg, lineno=lineno, offset=index)
class ConcreteBytecode(_bytecode._BaseBytecodeList):
@ -205,14 +177,15 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
instructions = []
offset = 0
lineno = code.co_firstlineno
while offset < len(code.co_code):
if offset in line_starts:
lineno = line_starts[offset]
while offset < (len(code.co_code) // (2 if OFFSET_AS_INSTRUCTION else 1)):
lineno_off = (2 * offset) if OFFSET_AS_INSTRUCTION else offset
if lineno_off in line_starts:
lineno = line_starts[lineno_off]
instr = ConcreteInstr.disassemble(lineno, code.co_code, offset)
instructions.append(instr)
offset += instr.size
offset += (instr.size // 2) if OFFSET_AS_INSTRUCTION else instr.size
bytecode = ConcreteBytecode()
@ -262,8 +235,11 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
linenos = []
for lineno, instr in self._normalize_lineno(self, self.first_lineno):
code_str.append(instr.assemble())
linenos.append((offset, lineno))
offset += instr.size
i_size = instr.size
linenos.append(
((offset * 2) if OFFSET_AS_INSTRUCTION else offset, i_size, lineno)
)
offset += (i_size // 2) if OFFSET_AS_INSTRUCTION else i_size
code_str = b"".join(code_str)
return (code_str, linenos)
@ -272,7 +248,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
lnotab = []
old_offset = 0
old_lineno = first_lineno
for offset, lineno in linenos:
for offset, _, lineno in linenos:
dlineno = lineno - old_lineno
if dlineno == 0:
continue
@ -291,11 +267,13 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
doff -= 255
while dlineno < -128:
lnotab.append(struct.pack("Bb", 0, -128))
lnotab.append(struct.pack("Bb", doff, -128))
doff = 0
dlineno -= -128
while dlineno > 127:
lnotab.append(struct.pack("Bb", 0, 127))
lnotab.append(struct.pack("Bb", doff, 127))
doff = 0
dlineno -= 127
assert 0 <= doff <= 255
@ -305,6 +283,53 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
return b"".join(lnotab)
@staticmethod
def _pack_linetable(doff, dlineno):
linetable = []
while doff > 254:
linetable.append(b"\xfe\x00")
doff -= 254
while dlineno < -127:
linetable.append(struct.pack("Bb", 0, -127))
dlineno -= -127
while dlineno > 127:
linetable.append(struct.pack("Bb", 0, 127))
dlineno -= 127
assert 0 <= doff <= 254
assert -127 <= dlineno <= 127
linetable.append(struct.pack("Bb", doff, dlineno))
return linetable
def _assemble_linestable(self, first_lineno, linenos):
if not linenos:
return b""
linetable = []
old_offset = 0
offset, i_size, old_lineno = linenos[0]
old_dlineno = old_lineno - first_lineno
for offset, i_size, lineno in linenos[1:]:
dlineno = lineno - old_lineno
if dlineno == 0:
continue
old_lineno = lineno
doff = offset - old_offset
old_offset = offset
linetable.extend(self._pack_linetable(doff, old_dlineno))
old_dlineno = dlineno
# Pack the line of the last instruction.
doff = offset + i_size - old_offset
linetable.extend(self._pack_linetable(doff, old_dlineno))
return b"".join(linetable)
@staticmethod
def _remove_extended_args(instructions):
# replace jump targets with blocks
@ -327,8 +352,6 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
if instr.name == "EXTENDED_ARG":
nb_extended_args += 1
if extended_arg is not None:
if not _WORDCODE:
raise ValueError("EXTENDED_ARG followed " "by EXTENDED_ARG")
extended_arg = (extended_arg << 8) + instr.arg
else:
extended_arg = instr.arg
@ -337,10 +360,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
continue
if extended_arg is not None:
if _WORDCODE:
arg = (extended_arg << 8) + instr.arg
else:
arg = (extended_arg << 16) + instr.arg
arg = (extended_arg << 8) + instr.arg
extended_arg = None
instr = ConcreteInstr(
@ -348,7 +368,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
arg,
lineno=instr.lineno,
extended_args=nb_extended_args,
offset=instr.offset
offset=instr.offset,
)
instructions[index] = instr
nb_extended_args = 0
@ -365,7 +385,11 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
def to_code(self, stacksize=None, *, check_pre_and_post=True):
code_str, linenos = self._assemble_code()
lnotab = self._assemble_lnotab(self.first_lineno, linenos)
lnotab = (
self._assemble_linestable(self.first_lineno, linenos)
if sys.version_info >= (3, 10)
else self._assemble_lnotab(self.first_lineno, linenos)
)
nlocals = len(self.varnames)
if stacksize is None:
stacksize = self.compute_stacksize(check_pre_and_post=check_pre_and_post)
@ -423,7 +447,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
target = instr.get_jump_target(offset)
if target is not None:
jump_targets.add(target)
offset += instr.size
offset += (instr.size // 2) if OFFSET_AS_INSTRUCTION else instr.size
# create labels
jumps = []
@ -464,7 +488,7 @@ class ConcreteBytecode(_bytecode._BaseBytecodeList):
else:
instr_index = len(instructions)
instructions.append(instr)
offset += size
offset += (size // 2) if OFFSET_AS_INSTRUCTION else size
if jump_target is not None:
jumps.append((instr_index, jump_target))
@ -586,7 +610,7 @@ class _ConvertBytecodeToConcrete:
offset = 0
for index, instr in enumerate(self.instructions):
offsets.append(offset)
offset += instr.size
offset += instr.size // 2 if OFFSET_AS_INSTRUCTION else instr.size
# needed if a label is at the end
offsets.append(offset)
@ -598,7 +622,9 @@ class _ConvertBytecodeToConcrete:
if instr.opcode in _opcode.hasjrel:
instr_offset = offsets[index]
target_offset -= instr_offset + instr.size
target_offset -= instr_offset + (
instr.size // 2 if OFFSET_AS_INSTRUCTION else instr.size
)
old_size = instr.size
# FIXME: better error report if target_offset is negative

View file

@ -1,11 +1,8 @@
# alias to keep the 'bytecode' variable free
import sys
from enum import IntFlag
from _pydevd_frame_eval.vendored import bytecode as _bytecode
try:
from enum import IntFlag
except ImportError:
from aenum import IntFlag
class CompilerFlags(IntFlag):
"""Possible values of the co_flags attribute of Code object.
@ -33,7 +30,14 @@ class CompilerFlags(IntFlag):
ASYNC_GENERATOR = 0x00200 # noqa
# __future__ flags
FUTURE_GENERATOR_STOP = 0x80000 # noqa
# future flags changed in Python 3.9
if sys.version_info < (3, 9):
FUTURE_GENERATOR_STOP = 0x80000 # noqa
if sys.version_info > (3, 6):
FUTURE_ANNOTATIONS = 0x100000
else:
FUTURE_GENERATOR_STOP = 0x800000 # noqa
FUTURE_ANNOTATIONS = 0x1000000
def infer_flags(bytecode, is_async=None):

View file

@ -42,6 +42,7 @@ def _pushes_back(opname):
return (
opname.startswith("UNARY_")
or opname.startswith("GET_")
# BUILD_XXX_UNPACK have been removed in 3.9
or opname.startswith("BINARY_")
or opname.startswith("INPLACE_")
or opname.startswith("BUILD_")
@ -318,14 +319,15 @@ class Instr:
def pre_and_post_stack_effect(self, jump=None):
_effect = self.stack_effect(jump=jump)
# To compute pre size and post size to avoid segfault cause by not enough stack element
# To compute pre size and post size to avoid segfault cause by not enough
# stack element
_opname = _opcode.opname[self._opcode]
if _opname.startswith("DUP_TOP"):
return _effect * -1, _effect * 2
if _pushes_back(_opname):
# if the op pushes value back to the stack, then the stack effect given by dis.stack_effect
# actually equals pre + post effect, therefore we need -1 from the stack effect as a pre
# condition
# if the op pushes value back to the stack, then the stack effect given
# by dis.stack_effect actually equals pre + post effect, therefore we need
# -1 from the stack effect as a pre condition
return _effect - 1, 1
if _opname.startswith("UNPACK_"):
# Instr(UNPACK_* , n) pops 1 and pushes n
@ -333,8 +335,11 @@ class Instr:
# hence we return -1, _effect + 1
return -1, _effect + 1
if _opname == "FOR_ITER" and not jump:
# Since FOR_ITER needs TOS to be an iterator, which basically means a prerequisite of 1 on the stack
# Since FOR_ITER needs TOS to be an iterator, which basically means
# a prerequisite of 1 on the stack
return -1, 2
if _opname == "ROT_N":
return (-self._arg, self._arg)
return {"ROT_TWO": (-2, 2), "ROT_THREE": (-3, 3), "ROT_FOUR": (-4, 4)}.get(
_opname, (_effect, 0)
)
@ -381,6 +386,7 @@ class Instr:
if self._name in {
"RETURN_VALUE",
"RAISE_VARARGS",
"RERAISE",
"BREAK_LOOP",
"CONTINUE_LOOP",
}:

View file

@ -14,8 +14,6 @@ from _pydevd_frame_eval.vendored.bytecode import (
ConcreteBytecode,
)
WORDCODE = sys.version_info >= (3, 6)
def _format_instr_list(block, labels, lineno):
instr_list = []

View file

@ -1,36 +0,0 @@
#!/bin/python
#author: tobias mueller 13.6.13
#byteplay test
from sys import version_info
from dis import dis
from _pydevd_frame_eval.vendored.bytecode import Bytecode, ConcreteBytecode, dump_bytecode
from pprint import pprint
def f(a, b):
# res = a + b
return
def g(a, b):
res = a + b if a < b else b + a
r = 0
for a in range(res):
r += 1
return r or 2
for x in (f, g):
#get byte code for f
dis(x)
print(f.__code__.co_code)
c = Bytecode.from_code(x.__code__)
cc = ConcreteBytecode.from_code(x.__code__)
dump_bytecode(c)
dump_bytecode(cc)
#generate byte code
cnew = c.to_code()
x.__code__ = cnew
dis(x)
print(x(3,5))

View file

@ -138,22 +138,41 @@ class BytecodeTests(TestCase):
bytecode = Bytecode.from_code(code)
label_else = Label()
label_exit = Label()
self.assertEqual(
bytecode,
[
Instr("LOAD_NAME", "test", lineno=1),
Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
Instr("LOAD_CONST", 1, lineno=2),
Instr("STORE_NAME", "x", lineno=2),
Instr("JUMP_FORWARD", label_exit, lineno=2),
label_else,
Instr("LOAD_CONST", 2, lineno=4),
Instr("STORE_NAME", "x", lineno=4),
label_exit,
Instr("LOAD_CONST", None, lineno=4),
Instr("RETURN_VALUE", lineno=4),
],
)
if sys.version_info < (3, 10):
self.assertEqual(
bytecode,
[
Instr("LOAD_NAME", "test", lineno=1),
Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
Instr("LOAD_CONST", 1, lineno=2),
Instr("STORE_NAME", "x", lineno=2),
Instr("JUMP_FORWARD", label_exit, lineno=2),
label_else,
Instr("LOAD_CONST", 2, lineno=4),
Instr("STORE_NAME", "x", lineno=4),
label_exit,
Instr("LOAD_CONST", None, lineno=4),
Instr("RETURN_VALUE", lineno=4),
],
)
# Control flow handling appears to have changed under Python 3.10
else:
self.assertEqual(
bytecode,
[
Instr("LOAD_NAME", "test", lineno=1),
Instr("POP_JUMP_IF_FALSE", label_else, lineno=1),
Instr("LOAD_CONST", 1, lineno=2),
Instr("STORE_NAME", "x", lineno=2),
Instr("LOAD_CONST", None, lineno=2),
Instr("RETURN_VALUE", lineno=2),
label_else,
Instr("LOAD_CONST", 2, lineno=4),
Instr("STORE_NAME", "x", lineno=4),
Instr("LOAD_CONST", None, lineno=4),
Instr("RETURN_VALUE", lineno=4),
],
)
def test_from_code_freevars(self):
ns = {}

View file

@ -17,7 +17,8 @@ from _pydevd_frame_eval.vendored.bytecode import (
BasicBlock,
ControlFlowGraph,
)
from _pydevd_frame_eval.vendored.bytecode.tests import disassemble as _disassemble, TestCase, WORDCODE
from _pydevd_frame_eval.vendored.bytecode.concrete import OFFSET_AS_INSTRUCTION
from _pydevd_frame_eval.vendored.bytecode.tests import disassemble as _disassemble, TestCase
def disassemble(
@ -566,20 +567,14 @@ class BytecodeBlocksFunctionalTests(TestCase):
]
)
if WORDCODE:
if OFFSET_AS_INSTRUCTION:
# The argument of the jump is divided by 2
expected = (
b"|\x05" b"r\x08" b"|\x00" b"}\x05" b"d\x01" b"}\x05" b"|\x05" b"S\x00"
b"|\x05" b"r\x04" b"|\x00" b"}\x05" b"d\x01" b"}\x05" b"|\x05" b"S\x00"
)
else:
expected = (
b"|\x05\x00"
b"r\x0c\x00"
b"|\x00\x00"
b"}\x05\x00"
b"d\x01\x00"
b"}\x05\x00"
b"|\x05\x00"
b"S"
b"|\x05" b"r\x08" b"|\x00" b"}\x05" b"d\x01" b"}\x05" b"|\x05" b"S\x00"
)
code = bytecode.to_code()

View file

@ -68,6 +68,26 @@ class CodeTests(unittest.TestCase):
function=True,
)
# Added because Python 3.10 added some special beahavior with respect to
# generators in term of stack size
def test_generator_func(self):
self.check(
"""
def func(arg, arg2):
yield
""",
function=True,
)
def test_async_func(self):
self.check(
"""
async def func(arg, arg2):
pass
""",
function=True,
)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -6,9 +6,10 @@ pytestmark = pytest.mark.skipif(not IS_PY36_OR_GREATER or not IS_CPYTHON or not
#!/usr/bin/env python3
import opcode
import sys
import textwrap
import types
import unittest
import textwrap
from _pydevd_frame_eval.vendored.bytecode import (
UNSET,
Label,
@ -21,7 +22,8 @@ from _pydevd_frame_eval.vendored.bytecode import (
ConcreteInstr,
ConcreteBytecode,
)
from _pydevd_frame_eval.vendored.bytecode.tests import get_code, TestCase, WORDCODE
from _pydevd_frame_eval.vendored.bytecode.concrete import OFFSET_AS_INSTRUCTION
from _pydevd_frame_eval.vendored.bytecode.tests import get_code, TestCase
class ConcreteInstrTests(TestCase):
@ -62,7 +64,7 @@ class ConcreteInstrTests(TestCase):
self.assertEqual(instr.opcode, 100)
self.assertEqual(instr.arg, 5)
self.assertEqual(instr.lineno, 12)
self.assertEqual(instr.size, 2 if WORDCODE else 3)
self.assertEqual(instr.size, 2)
def test_set(self):
instr = ConcreteInstr("LOAD_CONST", 5, lineno=3)
@ -103,12 +105,12 @@ class ConcreteInstrTests(TestCase):
# extended argument
instr.arg = 0x1234ABCD
self.assertEqual(instr.arg, 0x1234ABCD)
self.assertEqual(instr.size, 8 if WORDCODE else 6)
self.assertEqual(instr.size, 8)
# small argument
instr.arg = 0
self.assertEqual(instr.arg, 0)
self.assertEqual(instr.size, 2 if WORDCODE else 3)
self.assertEqual(instr.size, 2)
# invalid argument
self.assertRaises(ValueError, setattr, instr, "arg", -1)
@ -124,44 +126,40 @@ class ConcreteInstrTests(TestCase):
self.assertRaises(ValueError, setattr, instr, "lineno", -1)
def test_size(self):
self.assertEqual(ConcreteInstr("ROT_TWO").size, 2 if WORDCODE else 1)
self.assertEqual(ConcreteInstr("LOAD_CONST", 3).size, 2 if WORDCODE else 3)
self.assertEqual(
ConcreteInstr("LOAD_CONST", 0x1234ABCD).size, 8 if WORDCODE else 6
)
self.assertEqual(ConcreteInstr("ROT_TWO").size, 2)
self.assertEqual(ConcreteInstr("LOAD_CONST", 3).size, 2)
self.assertEqual(ConcreteInstr("LOAD_CONST", 0x1234ABCD).size, 8)
def test_disassemble(self):
code = b"\t\x00d\x03" if WORDCODE else b"\td\x03\x00"
code = b"\t\x00d\x03"
instr = ConcreteInstr.disassemble(1, code, 0)
self.assertEqual(instr, ConcreteInstr("NOP", lineno=1))
instr = ConcreteInstr.disassemble(2, code, 2 if WORDCODE else 1)
instr = ConcreteInstr.disassemble(2, code, 1 if OFFSET_AS_INSTRUCTION else 2)
self.assertEqual(instr, ConcreteInstr("LOAD_CONST", 3, lineno=2))
code = b"\x90\x12\x904\x90\xabd\xcd" if WORDCODE else b"\x904\x12d\xcd\xab"
code = b"\x90\x12\x904\x90\xabd\xcd"
instr = ConcreteInstr.disassemble(3, code, 0)
self.assertEqual(
instr, ConcreteInstr("EXTENDED_ARG", 0x12 if WORDCODE else 0x1234, lineno=3)
)
self.assertEqual(instr, ConcreteInstr("EXTENDED_ARG", 0x12, lineno=3))
def test_assemble(self):
instr = ConcreteInstr("NOP")
self.assertEqual(instr.assemble(), b"\t\x00" if WORDCODE else b"\t")
self.assertEqual(instr.assemble(), b"\t\x00")
instr = ConcreteInstr("LOAD_CONST", 3)
self.assertEqual(instr.assemble(), b"d\x03" if WORDCODE else b"d\x03\x00")
self.assertEqual(instr.assemble(), b"d\x03")
instr = ConcreteInstr("LOAD_CONST", 0x1234ABCD)
self.assertEqual(
instr.assemble(),
(b"\x90\x12\x904\x90\xabd\xcd" if WORDCODE else b"\x904\x12d\xcd\xab"),
(b"\x90\x12\x904\x90\xabd\xcd"),
)
instr = ConcreteInstr("LOAD_CONST", 3, extended_args=1)
self.assertEqual(
instr.assemble(),
(b"\x90\x00d\x03" if WORDCODE else b"\x90\x00\x00d\x03\x00"),
(b"\x90\x00d\x03"),
)
def test_get_jump_target(self):
@ -169,7 +167,9 @@ class ConcreteInstrTests(TestCase):
self.assertEqual(jump_abs.get_jump_target(100), 3)
jump_forward = ConcreteInstr("JUMP_FORWARD", 5)
self.assertEqual(jump_forward.get_jump_target(10), 17 if WORDCODE else 18)
self.assertEqual(
jump_forward.get_jump_target(10), 16 if OFFSET_AS_INSTRUCTION else 17
)
class ConcreteBytecodeTests(TestCase):
@ -241,43 +241,42 @@ class ConcreteBytecodeTests(TestCase):
ConcreteBytecode([Label()])
def test_to_code_lnotab(self):
# x = 7
# y = 8
# z = 9
# We use an actual function for the simple case to
# ensure we get lnotab right
def f():
#
#
x = 7 # noqa
y = 8 # noqa
z = 9 # noqa
fl = f.__code__.co_firstlineno
concrete = ConcreteBytecode()
concrete.consts = [7, 8, 9]
concrete.names = ["x", "y", "z"]
concrete.first_lineno = 3
concrete.consts = [None, 7, 8, 9]
concrete.varnames = ["x", "y", "z"]
concrete.first_lineno = fl
concrete.extend(
[
ConcreteInstr("LOAD_CONST", 0),
ConcreteInstr("STORE_NAME", 0),
SetLineno(4),
SetLineno(fl + 3),
ConcreteInstr("LOAD_CONST", 1),
ConcreteInstr("STORE_NAME", 1),
SetLineno(5),
ConcreteInstr("STORE_FAST", 0),
SetLineno(fl + 4),
ConcreteInstr("LOAD_CONST", 2),
ConcreteInstr("STORE_NAME", 2),
ConcreteInstr("STORE_FAST", 1),
SetLineno(fl + 5),
ConcreteInstr("LOAD_CONST", 3),
ConcreteInstr("STORE_FAST", 2),
ConcreteInstr("LOAD_CONST", 0),
ConcreteInstr("RETURN_VALUE"),
]
)
code = concrete.to_code()
if WORDCODE:
expected = b"d\x00Z\x00d\x01Z\x01d\x02Z\x02"
else:
expected = (
b"d\x00\x00"
b"Z\x00\x00"
b"d\x01\x00"
b"Z\x01\x00"
b"d\x02\x00"
b"Z\x02\x00"
)
self.assertEqual(code.co_code, expected)
self.assertEqual(code.co_firstlineno, 3)
self.assertEqual(
code.co_lnotab, b"\x04\x01\x04\x01" if WORDCODE else b"\x06\x01\x06\x01"
)
self.assertEqual(code.co_code, f.__code__.co_code)
self.assertEqual(code.co_lnotab, f.__code__.co_lnotab)
if sys.version_info >= (3, 10):
self.assertEqual(code.co_linetable, f.__code__.co_linetable)
def test_negative_lnotab(self):
# x = 7
@ -296,22 +295,15 @@ class ConcreteBytecodeTests(TestCase):
concrete.names = ["x", "y"]
concrete.first_lineno = 5
if sys.version_info >= (3, 6):
code = concrete.to_code()
expected = b"d\x00Z\x00d\x01Z\x01"
self.assertEqual(code.co_code, expected)
self.assertEqual(code.co_firstlineno, 5)
self.assertEqual(code.co_lnotab, b"\x04\xfd")
else:
with self.assertRaises(ValueError) as cm:
code = concrete.to_code()
self.assertEqual(
str(cm.exception),
"negative line number delta is not supported " "on Python < 3.6",
)
code = concrete.to_code()
expected = b"d\x00Z\x00d\x01Z\x01"
self.assertEqual(code.co_code, expected)
self.assertEqual(code.co_firstlineno, 5)
self.assertEqual(code.co_lnotab, b"\x04\xfd")
def test_extended_lnotab(self):
# x = 7
# 200 blank lines
# y = 8
concrete = ConcreteBytecode(
[
@ -329,21 +321,38 @@ class ConcreteBytecodeTests(TestCase):
concrete.names = ["x", "y"]
concrete.first_lineno = 1
if sys.version_info >= (3, 6):
code = concrete.to_code()
expected = b"d\x00Z\x00d\x01Z\x01"
self.assertEqual(code.co_code, expected)
self.assertEqual(code.co_firstlineno, 1)
self.assertEqual(
code.co_lnotab, b"\x00\x7f\x02\x01\x02\x01\x00\x80\x02\xff"
)
else:
with self.assertRaises(ValueError) as cm:
code = concrete.to_code()
self.assertEqual(
str(cm.exception),
"negative line number delta is not supported " "on Python < 3.6",
)
code = concrete.to_code()
expected = b"d\x00Z\x00d\x01Z\x01"
self.assertEqual(code.co_code, expected)
self.assertEqual(code.co_firstlineno, 1)
self.assertEqual(code.co_lnotab, b"\x02\x7f\x00\x01\x02\x01\x02\x80\x00\xff")
def test_extended_lnotab2(self):
# x = 7
# 200 blank lines
# y = 8
base_code = compile("x = 7" + "\n" * 200 + "y = 8", "", "exec")
concrete = ConcreteBytecode(
[
ConcreteInstr("LOAD_CONST", 0),
ConcreteInstr("STORE_NAME", 0),
SetLineno(201),
ConcreteInstr("LOAD_CONST", 1),
ConcreteInstr("STORE_NAME", 1),
ConcreteInstr("LOAD_CONST", 2),
ConcreteInstr("RETURN_VALUE"),
]
)
concrete.consts = [None, 7, 8]
concrete.names = ["x", "y"]
concrete.first_lineno = 1
code = concrete.to_code()
self.assertEqual(code.co_code, base_code.co_code)
self.assertEqual(code.co_firstlineno, base_code.co_firstlineno)
self.assertEqual(code.co_lnotab, base_code.co_lnotab)
if sys.version_info >= (3, 10):
self.assertEqual(code.co_linetable, base_code.co_linetable)
def test_to_bytecode_consts(self):
# x = -0.0
@ -476,7 +485,7 @@ class ConcreteBytecodeTests(TestCase):
self.assertEqual(code.co_cellvars, ("__class__",))
self.assertEqual(
code.co_code,
b"\x94\x01\x89\x01" if WORDCODE else b"\x94\x01\x00\x89\x01\x00",
b"\x94\x01\x89\x01",
)
def test_explicit_stacksize(self):
@ -571,7 +580,7 @@ class ConcreteBytecodeTests(TestCase):
class ConcreteFromCodeTests(TestCase):
def test_extended_arg(self):
# Create a code object from arbitrary bytecode
co_code = b"\x90\x12\x904\x90\xabd\xcd" if WORDCODE else b"\x904\x12d\xcd\xab"
co_code = b"\x90\x12\x904\x90\xabd\xcd"
code = get_code("x=1")
args = (
(code.co_argcount,)
@ -590,7 +599,7 @@ class ConcreteFromCodeTests(TestCase):
code.co_filename,
code.co_name,
code.co_firstlineno,
code.co_lnotab,
code.co_linetable if sys.version_info >= (3, 10) else code.co_lnotab,
code.co_freevars,
code.co_cellvars,
)
@ -605,78 +614,106 @@ class ConcreteFromCodeTests(TestCase):
# with EXTENDED_ARG opcode
bytecode = ConcreteBytecode.from_code(code, extended_arg=True)
if WORDCODE:
expected = [
ConcreteInstr("EXTENDED_ARG", 0x12, lineno=1),
ConcreteInstr("EXTENDED_ARG", 0x34, lineno=1),
ConcreteInstr("EXTENDED_ARG", 0xAB, lineno=1),
ConcreteInstr("LOAD_CONST", 0xCD, lineno=1),
]
else:
expected = [
ConcreteInstr("EXTENDED_ARG", 0x1234, lineno=1),
ConcreteInstr("LOAD_CONST", 0xABCD, lineno=1),
]
expected = [
ConcreteInstr("EXTENDED_ARG", 0x12, lineno=1),
ConcreteInstr("EXTENDED_ARG", 0x34, lineno=1),
ConcreteInstr("EXTENDED_ARG", 0xAB, lineno=1),
ConcreteInstr("LOAD_CONST", 0xCD, lineno=1),
]
self.assertListEqual(list(bytecode), expected)
def test_extended_arg_make_function(self):
code_obj = get_code(
"""
def foo(x: int, y: int):
pass
"""
)
if (3, 9) <= sys.version_info < (3, 10):
from _pydevd_frame_eval.vendored.bytecode.tests.util_annotation import get_code as get_code_future
code_obj = get_code_future(
"""
def foo(x: int, y: int):
pass
"""
)
else:
code_obj = get_code(
"""
def foo(x: int, y: int):
pass
"""
)
# without EXTENDED_ARG
concrete = ConcreteBytecode.from_code(code_obj)
func_code = concrete.consts[1]
self.assertEqual(concrete.names, ["int", "foo"])
self.assertEqual(concrete.consts, [("x", "y"), func_code, "foo", None])
if WORDCODE:
expected = [
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("LOAD_NAME", 0, lineno=1),
if sys.version_info >= (3, 10):
func_code = concrete.consts[2]
names = ["int", "foo"]
consts = ["x", "y", func_code, "foo", None]
const_offset = 1
name_offset = 1
first_instrs = [
ConcreteInstr("LOAD_CONST", 0, lineno=1),
ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1),
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 1, lineno=1),
ConcreteInstr("LOAD_CONST", 2, lineno=1),
ConcreteInstr("MAKE_FUNCTION", 4, lineno=1),
ConcreteInstr("STORE_NAME", 1, lineno=1),
ConcreteInstr("LOAD_CONST", 3, lineno=1),
ConcreteInstr("RETURN_VALUE", lineno=1),
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("BUILD_TUPLE", 4, lineno=1),
]
elif (
sys.version_info >= (3, 7)
and concrete.flags & CompilerFlags.FUTURE_ANNOTATIONS
):
func_code = concrete.consts[2]
names = ["foo"]
consts = ["int", ("x", "y"), func_code, "foo", None]
const_offset = 1
name_offset = 0
first_instrs = [
ConcreteInstr("LOAD_CONST", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 0 + const_offset, lineno=1),
ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1),
]
else:
expected = [
func_code = concrete.consts[1]
names = ["int", "foo"]
consts = [("x", "y"), func_code, "foo", None]
const_offset = 0
name_offset = 1
first_instrs = [
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 1, lineno=1),
ConcreteInstr("LOAD_CONST", 2, lineno=1),
ConcreteInstr("MAKE_FUNCTION", 3 << 16, lineno=1),
ConcreteInstr("STORE_NAME", 1, lineno=1),
ConcreteInstr("LOAD_CONST", 3, lineno=1),
ConcreteInstr("RETURN_VALUE", lineno=1),
ConcreteInstr("LOAD_CONST", 0 + const_offset, lineno=1),
ConcreteInstr("BUILD_CONST_KEY_MAP", 2, lineno=1),
]
self.assertEqual(concrete.names, names)
self.assertEqual(concrete.consts, consts)
expected = first_instrs + [
ConcreteInstr("LOAD_CONST", 1 + const_offset, lineno=1),
ConcreteInstr("LOAD_CONST", 2 + const_offset, lineno=1),
ConcreteInstr("MAKE_FUNCTION", 4, lineno=1),
ConcreteInstr("STORE_NAME", name_offset, lineno=1),
ConcreteInstr("LOAD_CONST", 3 + const_offset, lineno=1),
ConcreteInstr("RETURN_VALUE", lineno=1),
]
self.assertListEqual(list(concrete), expected)
# with EXTENDED_ARG
concrete = ConcreteBytecode.from_code(code_obj, extended_arg=True)
func_code = concrete.consts[1]
self.assertEqual(concrete.names, ["int", "foo"])
self.assertEqual(concrete.consts, [("x", "y"), func_code, "foo", None])
if not WORDCODE:
expected = [
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 0, lineno=1),
ConcreteInstr("LOAD_CONST", 1, lineno=1),
ConcreteInstr("LOAD_CONST", 2, lineno=1),
ConcreteInstr("EXTENDED_ARG", 3, lineno=1),
ConcreteInstr("MAKE_FUNCTION", 0, lineno=1),
ConcreteInstr("STORE_NAME", 1, lineno=1),
ConcreteInstr("LOAD_CONST", 3, lineno=1),
ConcreteInstr("RETURN_VALUE", lineno=1),
]
# With future annotation the int annotation is stringified and
# stored as constant this the default behavior under Python 3.10
if sys.version_info >= (3, 10):
func_code = concrete.consts[2]
names = ["int", "foo"]
consts = ["x", "y", func_code, "foo", None]
elif concrete.flags & CompilerFlags.FUTURE_ANNOTATIONS:
func_code = concrete.consts[2]
names = ["foo"]
consts = ["int", ("x", "y"), func_code, "foo", None]
else:
func_code = concrete.consts[1]
names = ["int", "foo"]
consts = [("x", "y"), func_code, "foo", None]
self.assertEqual(concrete.names, names)
self.assertEqual(concrete.consts, consts)
self.assertListEqual(list(concrete), expected)
# The next three tests ensure we can round trip ConcreteBytecode generated
@ -1093,10 +1130,12 @@ class BytecodeToConcreteTests(TestCase):
concrete = bytecode.to_concrete_bytecode()
expected = [
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("POP_JUMP_IF_FALSE", 14 if WORDCODE else 21, lineno=1),
ConcreteInstr(
"POP_JUMP_IF_FALSE", 7 if OFFSET_AS_INSTRUCTION else 14, lineno=1
),
ConcreteInstr("LOAD_CONST", 0, lineno=2),
ConcreteInstr("STORE_NAME", 1, lineno=2),
ConcreteInstr("JUMP_FORWARD", 4 if WORDCODE else 6, lineno=2),
ConcreteInstr("JUMP_FORWARD", 2 if OFFSET_AS_INSTRUCTION else 4, lineno=2),
ConcreteInstr("LOAD_CONST", 1, lineno=4),
ConcreteInstr("STORE_NAME", 1, lineno=4),
ConcreteInstr("LOAD_CONST", 2, lineno=4),
@ -1232,10 +1271,10 @@ class BytecodeToConcreteTests(TestCase):
)
code_obj = code.to_code()
if WORDCODE:
expected = b"\x90\x01\x90\x00q\x06" + NOP * nb_nop + b"d\x00S\x00"
if OFFSET_AS_INSTRUCTION:
expected = b"\x90\x80q\x02" + NOP * nb_nop + b"d\x00S\x00"
else:
expected = b"\x90\x01\x00q\x06\x00" + NOP * nb_nop + b"d\x00\x00S"
expected = b"\x90\x01\x90\x00q\x06" + NOP * nb_nop + b"d\x00S\x00"
self.assertEqual(code_obj.co_code, expected)
def test_jumps(self):
@ -1265,10 +1304,12 @@ class BytecodeToConcreteTests(TestCase):
code = code.to_concrete_bytecode()
expected = [
ConcreteInstr("LOAD_NAME", 0, lineno=1),
ConcreteInstr("POP_JUMP_IF_FALSE", 10 if WORDCODE else 15, lineno=1),
ConcreteInstr(
"POP_JUMP_IF_FALSE", 5 if OFFSET_AS_INSTRUCTION else 10, lineno=1
),
ConcreteInstr("LOAD_CONST", 0, lineno=2),
ConcreteInstr("STORE_NAME", 1, lineno=2),
ConcreteInstr("JUMP_FORWARD", 4 if WORDCODE else 6, lineno=2),
ConcreteInstr("JUMP_FORWARD", 2 if OFFSET_AS_INSTRUCTION else 4, lineno=2),
ConcreteInstr("LOAD_CONST", 1, lineno=4),
ConcreteInstr("STORE_NAME", 1, lineno=4),
ConcreteInstr("LOAD_CONST", 2, lineno=4),
@ -1340,11 +1381,9 @@ class BytecodeToConcreteTests(TestCase):
#
# Thus we need to make an additional pass. This test only verifies
# case where 2 passes is insufficient but three is enough.
if not WORDCODE:
# Could be done pre-WORDCODE, but that requires 2**16 bytes of
# code.
return
#
# On Python > 3.10 we need to double the number since the offset is now
# in term of instructions and not bytes.
# Create code from comment above.
code = Bytecode()
@ -1353,11 +1392,17 @@ class BytecodeToConcreteTests(TestCase):
nop = "NOP"
code.append(Instr("JUMP_ABSOLUTE", label1))
code.append(Instr("JUMP_ABSOLUTE", label2))
for x in range(4, 254, 2):
# Need 254 * 2 + 2 since the arg will change by 1 instruction rather than 2
# bytes.
for x in range(4, 510 if OFFSET_AS_INSTRUCTION else 254, 2):
code.append(Instr(nop))
code.append(label1)
code.append(Instr(nop))
for x in range(256, 300, 2):
for x in range(
514 if OFFSET_AS_INSTRUCTION else 256,
600 if OFFSET_AS_INSTRUCTION else 300,
2,
):
code.append(Instr(nop))
code.append(label2)
code.append(Instr(nop))
@ -1381,18 +1426,20 @@ class BytecodeToConcreteTests(TestCase):
instead generate a series of many jumps. Each pass of compute_jumps()
extends one more instruction, which in turn causes the one behind it
to be extended on the next pass.
"""
if not WORDCODE:
return
# N: the number of unextended instructions that can be squeezed into a
# set of bytes adressable by the arg of an unextended instruction.
# The answer is "128", but here's how we arrive at it (and it also
# hints at how to make this work for pre-WORDCODE).
# The answer is "128", but here's how we arrive at it.
max_unextended_offset = 1 << 8
unextended_branch_instr_size = 2
N = max_unextended_offset // unextended_branch_instr_size
# When using instruction rather than bytes in the offset multiply by 2
if OFFSET_AS_INSTRUCTION:
N *= 2
nop = "UNARY_POSITIVE" # don't use NOP, dis.stack_effect will raise
# The number of jumps will be equal to the number of labels. The

View file

@ -6,12 +6,14 @@ pytestmark = pytest.mark.skipif(not IS_PY36_OR_GREATER or not IS_CPYTHON or not
#!/usr/bin/env python3
import contextlib
import io
import sys
import textwrap
import unittest
from _pydevd_frame_eval.vendored import bytecode
from _pydevd_frame_eval.vendored.bytecode import Label, Instr, Bytecode, BasicBlock, ControlFlowGraph
from _pydevd_frame_eval.vendored.bytecode.tests import disassemble, WORDCODE
from _pydevd_frame_eval.vendored.bytecode.concrete import OFFSET_AS_INSTRUCTION
from _pydevd_frame_eval.vendored.bytecode.tests import disassemble
class DumpCodeTests(unittest.TestCase):
@ -39,10 +41,11 @@ class DumpCodeTests(unittest.TestCase):
code = disassemble(source, function=True)
# without line numbers
expected = """
enum_repr = "<Compare.EQ: 2>"
expected = f"""
LOAD_FAST 'test'
LOAD_CONST 1
COMPARE_OP <Compare.EQ: 2>
COMPARE_OP {enum_repr}
POP_JUMP_IF_FALSE <label_instr6>
LOAD_CONST 1
RETURN_VALUE
@ -50,7 +53,7 @@ class DumpCodeTests(unittest.TestCase):
label_instr6:
LOAD_FAST 'test'
LOAD_CONST 2
COMPARE_OP <Compare.EQ: 2>
COMPARE_OP {enum_repr}
POP_JUMP_IF_FALSE <label_instr13>
LOAD_CONST 2
RETURN_VALUE
@ -67,10 +70,10 @@ label_instr13:
self.check_dump_bytecode(code, expected)
# with line numbers
expected = """
expected = f"""
L. 2 0: LOAD_FAST 'test'
1: LOAD_CONST 1
2: COMPARE_OP <Compare.EQ: 2>
2: COMPARE_OP {enum_repr}
3: POP_JUMP_IF_FALSE <label_instr6>
L. 3 4: LOAD_CONST 1
5: RETURN_VALUE
@ -78,7 +81,7 @@ label_instr13:
label_instr6:
L. 4 7: LOAD_FAST 'test'
8: LOAD_CONST 2
9: COMPARE_OP <Compare.EQ: 2>
9: COMPARE_OP {enum_repr}
10: POP_JUMP_IF_FALSE <label_instr13>
L. 5 11: LOAD_CONST 2
12: RETURN_VALUE
@ -128,12 +131,13 @@ label_instr13:
code = ControlFlowGraph.from_bytecode(code)
# without line numbers
enum_repr = "<Compare.EQ: 2>"
expected = textwrap.dedent(
"""
f"""
block1:
LOAD_FAST 'test'
LOAD_CONST 1
COMPARE_OP <Compare.EQ: 2>
COMPARE_OP {enum_repr}
POP_JUMP_IF_FALSE <block3>
-> block2
@ -144,7 +148,7 @@ label_instr13:
block3:
LOAD_FAST 'test'
LOAD_CONST 2
COMPARE_OP <Compare.EQ: 2>
COMPARE_OP {enum_repr}
POP_JUMP_IF_FALSE <block5>
-> block4
@ -162,11 +166,11 @@ label_instr13:
# with line numbers
expected = textwrap.dedent(
"""
f"""
block1:
L. 2 0: LOAD_FAST 'test'
1: LOAD_CONST 1
2: COMPARE_OP <Compare.EQ: 2>
2: COMPARE_OP {enum_repr}
3: POP_JUMP_IF_FALSE <block3>
-> block2
@ -177,7 +181,7 @@ label_instr13:
block3:
L. 4 0: LOAD_FAST 'test'
1: LOAD_CONST 2
2: COMPARE_OP <Compare.EQ: 2>
2: COMPARE_OP {enum_repr}
3: POP_JUMP_IF_FALSE <block5>
-> block4
@ -206,85 +210,45 @@ label_instr13:
code = code.to_concrete_bytecode()
# without line numbers
if WORDCODE:
expected = """
expected = f"""
0 LOAD_FAST 0
2 LOAD_CONST 1
4 COMPARE_OP 2
6 POP_JUMP_IF_FALSE 12
6 POP_JUMP_IF_FALSE {6 if OFFSET_AS_INSTRUCTION else 12}
8 LOAD_CONST 1
10 RETURN_VALUE
12 LOAD_FAST 0
14 LOAD_CONST 2
16 COMPARE_OP 2
18 POP_JUMP_IF_FALSE 24
18 POP_JUMP_IF_FALSE {12 if OFFSET_AS_INSTRUCTION else 24}
20 LOAD_CONST 2
22 RETURN_VALUE
24 LOAD_CONST 3
26 RETURN_VALUE
""".lstrip(
"\n"
)
else:
expected = """
0 LOAD_FAST 0
3 LOAD_CONST 1
6 COMPARE_OP 2
9 POP_JUMP_IF_FALSE 16
12 LOAD_CONST 1
15 RETURN_VALUE
16 LOAD_FAST 0
19 LOAD_CONST 2
22 COMPARE_OP 2
25 POP_JUMP_IF_FALSE 32
28 LOAD_CONST 2
31 RETURN_VALUE
32 LOAD_CONST 3
35 RETURN_VALUE
""".lstrip(
"\n"
)
"\n"
)
self.check_dump_bytecode(code, expected)
# with line numbers
if WORDCODE:
expected = """
expected = f"""
L. 2 0: LOAD_FAST 0
2: LOAD_CONST 1
4: COMPARE_OP 2
6: POP_JUMP_IF_FALSE 12
6: POP_JUMP_IF_FALSE {6 if OFFSET_AS_INSTRUCTION else 12}
L. 3 8: LOAD_CONST 1
10: RETURN_VALUE
L. 4 12: LOAD_FAST 0
14: LOAD_CONST 2
16: COMPARE_OP 2
18: POP_JUMP_IF_FALSE 24
18: POP_JUMP_IF_FALSE {12 if OFFSET_AS_INSTRUCTION else 24}
L. 5 20: LOAD_CONST 2
22: RETURN_VALUE
L. 6 24: LOAD_CONST 3
26: RETURN_VALUE
""".lstrip(
"\n"
)
else:
expected = """
L. 2 0: LOAD_FAST 0
3: LOAD_CONST 1
6: COMPARE_OP 2
9: POP_JUMP_IF_FALSE 16
L. 3 12: LOAD_CONST 1
15: RETURN_VALUE
L. 4 16: LOAD_FAST 0
19: LOAD_CONST 2
22: COMPARE_OP 2
25: POP_JUMP_IF_FALSE 32
L. 5 28: LOAD_CONST 2
31: RETURN_VALUE
L. 6 32: LOAD_CONST 3
35: RETURN_VALUE
""".lstrip(
"\n"
)
"\n"
)
self.check_dump_bytecode(code, expected, lineno=True)
def test_type_validation(self):

View file

@ -0,0 +1,17 @@
from __future__ import annotations
import textwrap
import types
def get_code(source, *, filename="<string>", function=False):
source = textwrap.dedent(source).strip()
code = compile(source, filename, "exec")
if function:
sub_code = [
const for const in code.co_consts if isinstance(const, types.CodeType)
]
if len(sub_code) != 1:
raise ValueError("unable to find function code")
code = sub_code[0]
return code

View file

@ -92,7 +92,7 @@ def _start_monitoring_threads():
os._exit(123)
dump_current_frames_thread = DumpThreads()
dump_current_frames_thread.setDaemon(True) # Daemon so that this thread doesn't halt it!
dump_current_frames_thread.daemon = True # Daemon so that this thread doesn't halt it!
dump_current_frames_thread.start()

View file

@ -131,7 +131,7 @@ if __name__ == '__main__':
server_thread = threading.Thread(target=start_console_server,
name='ServerThread',
args=(host, int(port), interpreter))
server_thread.setDaemon(True)
server_thread.daemon = True
server_thread.start()
sys.stdin = StdIn(interpreter, host, client_port, sys.stdin)

View file

@ -318,5 +318,5 @@ if __name__ == '__main__':
dump_current_frames_thread = DumpThreads()
dump_current_frames_thread.setDaemon(True) # Daemon so that this thread doesn't halt it!
dump_current_frames_thread.daemon = True # Daemon so that this thread doesn't halt it!
dump_current_frames_thread.start()

View file

@ -57,7 +57,7 @@ class Test(unittest.TestCase):
client_thread = ClientThread(client_port)
client_thread.requested_input = False
client_thread.notified_finished = 0
client_thread.setDaemon(True)
client_thread.daemon = True
client_thread.start()
return client_thread
@ -84,7 +84,7 @@ class Test(unittest.TestCase):
print('Starting server with:', pydev_localhost.get_localhost(), self.server_port, self.client_port)
pydevconsole.start_server(pydev_localhost.get_localhost(), self.server_port, self.client_port)
server_thread = ServerThread(client_port, server_port)
server_thread.setDaemon(True)
server_thread.daemon = True
server_thread.start()
client_thread = self.start_client_thread(client_port) #@UnusedVariable

View file

@ -238,7 +238,7 @@ class TestRunningCode(TestBase):
self.client_server.shutdown()
client_thread = ClientThread(client_port)
client_thread.setDaemon(True)
client_thread.daemon = True
client_thread.start()
return client_thread

View file

@ -163,7 +163,7 @@ class Test(unittest.TestCase):
client_thread = ClientThread(client_port)
client_thread.requested_input = False
client_thread.notified_finished = 0
client_thread.setDaemon(True)
client_thread.daemon = True
client_thread.start()
return client_thread
@ -185,7 +185,7 @@ class Test(unittest.TestCase):
socket_code(socket)
debugger_thread = DebuggerServerThread(debugger_port, socket_code)
debugger_thread.setDaemon(True)
debugger_thread.daemon = True
debugger_thread.start()
return debugger_thread
@ -219,7 +219,7 @@ class Test(unittest.TestCase):
pydevconsole.start_server(pydev_localhost.get_localhost(), self.server_port, self.client_port)
server_thread = ServerThread(client_port, server_port)
server_thread.setDaemon(True)
server_thread.daemon = True
server_thread.start()
client_thread = self.start_client_thread(client_port) # @UnusedVariable

View file

@ -201,7 +201,7 @@ class ReaderThread(threading.Thread):
except ImportError:
from Queue import Queue
self.setDaemon(True)
self.daemon = True
self._buffer = b''
self.sock = sock
self._queue = Queue()
@ -382,7 +382,7 @@ def read_process(stream, buffer, debug_stream, stream_name, finish):
def start_in_daemon_thread(target, args):
t0 = threading.Thread(target=target, args=args)
t0.setDaemon(True)
t0.daemon = True
t0.start()
@ -671,7 +671,7 @@ class AbstractWriterThread(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self.process = None # Set after the process is created.
self.setDaemon(True)
self.daemon = True
self.finished_ok = False
self.finished_initialization = False
self._next_breakpoint_id = 0

View file

@ -1,10 +1,11 @@
def Call():
b = True
while b: # expected
pass # requested
while b: # expected
# requested
pass # Note: until 3.10 a pass didn't generate a line event, but starting at 3.10, it does...
break
if __name__ == '__main__':
Call()
print('TEST SUCEEDED!')
print('TEST SUCEEDED!')

View file

@ -68,7 +68,7 @@ def thread_func():
th = threading.Thread(target=thread_func)
th.setDaemon(True)
th.daemon = True
th.start()
event = threading.Event()

View file

@ -9,6 +9,6 @@ async def run():
print(p)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop = asyncio.get_event_loop_policy().get_event_loop()
loop.run_until_complete(run())
print('TEST SUCEEDED')

View file

@ -35,7 +35,7 @@ def thread_func2(n):
th = threading.Thread(target=lambda: thread_func2(1))
th.setDaemon(True)
th.daemon = True
th.start()
th.join()

View file

@ -41,7 +41,7 @@ def thread_func2(n):
th = threading.Thread(target=lambda: thread_func2(1))
th.setDaemon(True)
th.daemon = True
th.start()
th.join()

View file

@ -22,13 +22,16 @@ def check(found, expected):
last_offset = -1
for f, e in zip(found, expected):
if isinstance(e.name, (list, tuple, set)):
assert f.name in e.name
else:
assert f.name == e.name
assert f.is_visited == e.is_visited
assert f.line == e.line
assert f.call_order == e.call_order
try:
if isinstance(e.name, (list, tuple, set)):
assert f.name in e.name
else:
assert f.name == e.name
assert f.is_visited == e.is_visited
assert f.line == e.line
assert f.call_order == e.call_order
except AssertionError as exc:
raise AssertionError('%s\nError with: %s - %s' % (exc, f, e))
# We can't check the offset because it may be different among different python versions
# so, just check that it's always in order.
@ -1098,18 +1101,23 @@ def _test_find_bytecode():
import glob
import dis
from io import StringIO
root_dir = 'C:\\bin\\Miniconda\\envs\\py36_tests\\Lib\\site-packages\\'
root_dir = 'C:\\bin\\Python310\\Lib\\site-packages\\'
i = 0
for filename in glob.iglob(root_dir + '**/*.py', recursive=True):
print(filename)
with open(filename, 'r', encoding='utf-8') as stream:
contents = stream.read()
try:
contents = stream.read()
except:
sys.stderr.write('Unable to read file: %s' % (filename,))
continue
code_obj = compile(contents, filename, 'exec')
s = StringIO()
dis.dis(code_obj, file=s)
if 'EXTENDED_ARG' in s.getvalue():
# https://docs.python.org/3.10/library/dis.html has references to the new opcodes added.
if 'COPY_DICT_WITHOUT_KEYS' in s.getvalue():
dis.dis(code_obj)
raise AssertionError('Found bytecode in: %s' % filename)