bytecode 0.17.0__tar.gz → 0.18.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/cis.yml +6 -10
- {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/docs.yml +2 -2
- {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/frameworks.yml +3 -3
- {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/release.yml +10 -10
- {bytecode-0.17.0 → bytecode-0.18.1}/.readthedocs.yaml +2 -2
- {bytecode-0.17.0/src/bytecode.egg-info → bytecode-0.18.1}/PKG-INFO +2 -4
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/changelog.rst +33 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/pyproject.toml +1 -3
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/bytecode.py +32 -18
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/cfg.py +91 -55
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/concrete.py +246 -385
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/flags.py +21 -28
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/instr.py +281 -207
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/utils.py +0 -2
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/version.py +2 -2
- {bytecode-0.17.0 → bytecode-0.18.1/src/bytecode.egg-info}/PKG-INFO +2 -4
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/__init__.py +13 -54
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/exception_handling_cases.py +2 -19
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/frameworks/module.py +3 -1
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_bytecode.py +28 -160
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_cfg.py +146 -108
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_code.py +13 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_concrete.py +61 -217
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_flags.py +12 -43
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_instr.py +12 -22
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_misc.py +7 -148
- {bytecode-0.17.0 → bytecode-0.18.1}/.coveragerc +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/.github/FUNDING.yml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/.github/dependabot.yml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/.gitignore +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/.pre-commit-config.yaml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/COPYING +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/MANIFEST.in +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/README.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/TODO.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/codecov.yml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/Makefile +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/api.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/byteplay_codetransformer.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/cfg.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/conf.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/index.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/make.bat +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/requirements.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/todo.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/doc/usage.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/scripts/frameworks/boto3/run.sh +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/scripts/frameworks/boto3/setup.sh +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/setup.cfg +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/__init__.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/py.typed +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/SOURCES.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/dependency_links.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/requires.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/top_level.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/cell_free_vars_cases.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/frameworks/function.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/frameworks/sitecustomize.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/long_lines_example.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tests/util_annotation.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.1}/tox.ini +0 -0
|
@@ -20,9 +20,9 @@ jobs:
|
|
|
20
20
|
name: Lint code
|
|
21
21
|
runs-on: ubuntu-latest
|
|
22
22
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
24
|
- name: Set up Python
|
|
25
|
-
uses: actions/setup-python@
|
|
25
|
+
uses: actions/setup-python@v6
|
|
26
26
|
with:
|
|
27
27
|
python-version: "3.12"
|
|
28
28
|
- name: Install tools
|
|
@@ -42,26 +42,22 @@ jobs:
|
|
|
42
42
|
fail-fast: false
|
|
43
43
|
matrix:
|
|
44
44
|
include:
|
|
45
|
-
- python-version: "3.9"
|
|
46
|
-
toxenv: py39
|
|
47
|
-
- python-version: "3.10"
|
|
48
|
-
toxenv: py310
|
|
49
45
|
- python-version: "3.11"
|
|
50
46
|
toxenv: py311
|
|
51
47
|
- python-version: "3.12"
|
|
52
48
|
toxenv: py312
|
|
53
49
|
- python-version: "3.13"
|
|
54
50
|
toxenv: py313
|
|
55
|
-
- python-version: "3.14
|
|
51
|
+
- python-version: "3.14"
|
|
56
52
|
toxenv: py314
|
|
57
53
|
steps:
|
|
58
|
-
- uses: actions/checkout@
|
|
54
|
+
- uses: actions/checkout@v6
|
|
59
55
|
- name: Get history and tags for SCM versioning to work
|
|
60
56
|
run: |
|
|
61
57
|
git fetch --prune --unshallow
|
|
62
58
|
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
|
63
59
|
- name: Set up Python ${{ matrix.python-version }}
|
|
64
|
-
uses: actions/setup-python@
|
|
60
|
+
uses: actions/setup-python@v6
|
|
65
61
|
with:
|
|
66
62
|
python-version: ${{ matrix.python-version }}
|
|
67
63
|
- name: Install dependencies
|
|
@@ -74,7 +70,7 @@ jobs:
|
|
|
74
70
|
run: |
|
|
75
71
|
tox
|
|
76
72
|
- name: Upload coverage to Codecov
|
|
77
|
-
uses: codecov/codecov-action@
|
|
73
|
+
uses: codecov/codecov-action@v6
|
|
78
74
|
if: github.event_name != 'schedule'
|
|
79
75
|
with:
|
|
80
76
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -19,13 +19,13 @@ jobs:
|
|
|
19
19
|
name: Docs building
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@v6
|
|
23
23
|
- name: Get history and tags for SCM versioning to work
|
|
24
24
|
run: |
|
|
25
25
|
git fetch --prune --unshallow
|
|
26
26
|
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
|
27
27
|
- name: Set up Python
|
|
28
|
-
uses: actions/setup-python@
|
|
28
|
+
uses: actions/setup-python@v6
|
|
29
29
|
with:
|
|
30
30
|
python-version: '3.x'
|
|
31
31
|
- name: Install dependencies
|
|
@@ -21,15 +21,15 @@ jobs:
|
|
|
21
21
|
strategy:
|
|
22
22
|
fail-fast: false
|
|
23
23
|
matrix:
|
|
24
|
-
python-version: ["3.
|
|
24
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
25
25
|
|
|
26
26
|
steps:
|
|
27
|
-
- uses: actions/checkout@
|
|
27
|
+
- uses: actions/checkout@v6
|
|
28
28
|
with:
|
|
29
29
|
fetch-depth: 0
|
|
30
30
|
|
|
31
31
|
- name: Set up Python
|
|
32
|
-
uses: actions/setup-python@
|
|
32
|
+
uses: actions/setup-python@v6
|
|
33
33
|
with:
|
|
34
34
|
python-version: ${{ matrix.python-version }}-dev
|
|
35
35
|
|
|
@@ -13,13 +13,13 @@ jobs:
|
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
15
|
- name: Checkout
|
|
16
|
-
uses: actions/checkout@
|
|
16
|
+
uses: actions/checkout@v6
|
|
17
17
|
- name: Get history and tags for SCM versioning to work
|
|
18
18
|
run: |
|
|
19
19
|
git fetch --prune --unshallow
|
|
20
20
|
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
|
21
21
|
- name: Setup Python
|
|
22
|
-
uses: actions/setup-python@
|
|
22
|
+
uses: actions/setup-python@v6
|
|
23
23
|
with:
|
|
24
24
|
python-version: '3.x'
|
|
25
25
|
- name: Build sdist
|
|
@@ -33,7 +33,7 @@ jobs:
|
|
|
33
33
|
pip install dist/*.tar.gz
|
|
34
34
|
python -X dev -m pytest tests
|
|
35
35
|
- name: Store artifacts
|
|
36
|
-
uses: actions/upload-artifact@
|
|
36
|
+
uses: actions/upload-artifact@v7
|
|
37
37
|
with:
|
|
38
38
|
name: cibw-sdist
|
|
39
39
|
path: dist/*
|
|
@@ -43,13 +43,13 @@ jobs:
|
|
|
43
43
|
runs-on: ubuntu-latest
|
|
44
44
|
steps:
|
|
45
45
|
- name: Checkout
|
|
46
|
-
uses: actions/checkout@
|
|
46
|
+
uses: actions/checkout@v6
|
|
47
47
|
- name: Get history and tags for SCM versioning to work
|
|
48
48
|
run: |
|
|
49
49
|
git fetch --prune --unshallow
|
|
50
50
|
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
|
|
51
51
|
- name: Setup Python
|
|
52
|
-
uses: actions/setup-python@
|
|
52
|
+
uses: actions/setup-python@v6
|
|
53
53
|
with:
|
|
54
54
|
python-version: '3.x'
|
|
55
55
|
- name: Build wheels
|
|
@@ -63,7 +63,7 @@ jobs:
|
|
|
63
63
|
pip install dist/*.whl
|
|
64
64
|
python -X dev -m pytest tests
|
|
65
65
|
- name: Store artifacts
|
|
66
|
-
uses: actions/upload-artifact@
|
|
66
|
+
uses: actions/upload-artifact@v7
|
|
67
67
|
with:
|
|
68
68
|
name: cibw-wheel
|
|
69
69
|
path: dist/*.whl
|
|
@@ -74,12 +74,12 @@ jobs:
|
|
|
74
74
|
runs-on: ubuntu-latest
|
|
75
75
|
environment:
|
|
76
76
|
name: pypi
|
|
77
|
-
url: https://pypi.org/p/
|
|
77
|
+
url: https://pypi.org/p/bytecode
|
|
78
78
|
permissions:
|
|
79
79
|
id-token: write
|
|
80
80
|
steps:
|
|
81
81
|
- name: Download all the dists
|
|
82
|
-
uses: actions/download-artifact@
|
|
82
|
+
uses: actions/download-artifact@v8.0.1
|
|
83
83
|
with:
|
|
84
84
|
pattern: cibw-*
|
|
85
85
|
path: dist
|
|
@@ -101,13 +101,13 @@ jobs:
|
|
|
101
101
|
|
|
102
102
|
steps:
|
|
103
103
|
- name: Download all the dists
|
|
104
|
-
uses: actions/download-artifact@
|
|
104
|
+
uses: actions/download-artifact@v8.0.1
|
|
105
105
|
with:
|
|
106
106
|
pattern: cibw-*
|
|
107
107
|
path: dist
|
|
108
108
|
merge-multiple: true
|
|
109
109
|
- name: Sign the dists with Sigstore
|
|
110
|
-
uses: sigstore/gh-action-sigstore-python@v3.0
|
|
110
|
+
uses: sigstore/gh-action-sigstore-python@v3.3.0
|
|
111
111
|
with:
|
|
112
112
|
inputs: >-
|
|
113
113
|
./dist/*.tar.gz
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bytecode
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.1
|
|
4
4
|
Summary: Python module to generate and modify bytecode
|
|
5
5
|
Author-email: Victor Stinner <victor.stinner@gmail.com>
|
|
6
6
|
Maintainer-email: "Matthieu C. Dartiailh" <m.dartiailh@gmail.com>
|
|
@@ -36,14 +36,12 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
36
36
|
Classifier: Natural Language :: English
|
|
37
37
|
Classifier: Operating System :: OS Independent
|
|
38
38
|
Classifier: Programming Language :: Python :: 3
|
|
39
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
41
39
|
Classifier: Programming Language :: Python :: 3.11
|
|
42
40
|
Classifier: Programming Language :: Python :: 3.12
|
|
43
41
|
Classifier: Programming Language :: Python :: 3.13
|
|
44
42
|
Classifier: Programming Language :: Python :: 3.14
|
|
45
43
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
46
|
-
Requires-Python: >=3.
|
|
44
|
+
Requires-Python: >=3.11
|
|
47
45
|
Description-Content-Type: text/x-rst
|
|
48
46
|
License-File: COPYING
|
|
49
47
|
Requires-Dist: typing_extensions; python_version < "3.10"
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
ChangeLog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
03-06-2026: Version 0.18.1
|
|
5
|
+
--------------------------
|
|
6
|
+
|
|
7
|
+
Bugfixes:
|
|
8
|
+
|
|
9
|
+
- Fix an ``AssertionError`` in stack-size computation when a single exception
|
|
10
|
+
region is split into multiple ``TryBegin`` instances sharing one handler (as
|
|
11
|
+
produced by bytecode-rewriting tools that wrap a whole function body in a
|
|
12
|
+
single handler). ``TryBegin``/``TryEnd`` are now matched on the handler block
|
|
13
|
+
rather than on ``TryBegin`` identity.
|
|
14
|
+
|
|
15
|
+
03-06-2026: Version 0.18.0
|
|
16
|
+
--------------------------
|
|
17
|
+
|
|
18
|
+
- drop support for Python 3.9 and 3.10 PR #180
|
|
19
|
+
- Replace string literal type annotations with postponed evaluation using
|
|
20
|
+
``from __future__ import annotations`` PR #191
|
|
21
|
+
- multiple performance improvements
|
|
22
|
+
PRs #172, #193, #196, #194, #197, #198, #199, #200, #201,
|
|
23
|
+
#202, #203
|
|
24
|
+
|
|
25
|
+
Breaking changes:
|
|
26
|
+
|
|
27
|
+
- ``BasicBlock``, ``Bytecode``, and ``ConcreteBytecode`` now validate inserted
|
|
28
|
+
instructions at insertion time (``append``, ``extend``, ``insert``,
|
|
29
|
+
``__setitem__``) rather than during iteration. Code that relied on catching
|
|
30
|
+
``ValueError`` from ``list(block)`` or ``for instr in block:`` must wrap the
|
|
31
|
+
insertion call instead. PR #199
|
|
32
|
+
|
|
33
|
+
Bugfixes:
|
|
34
|
+
|
|
35
|
+
- Fix handling of END_ASYNC_FOR which is a backward jump PR #179
|
|
36
|
+
|
|
4
37
|
03-09-2025: Version 0.17.0
|
|
5
38
|
--------------------------
|
|
6
39
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "bytecode"
|
|
3
3
|
description = "Python module to generate and modify bytecode"
|
|
4
4
|
readme = "README.rst"
|
|
5
|
-
requires-python = ">=3.
|
|
5
|
+
requires-python = ">=3.11"
|
|
6
6
|
license = { file = "COPYING" }
|
|
7
7
|
authors = [{ name = "Victor Stinner", email = "victor.stinner@gmail.com" }]
|
|
8
8
|
maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }]
|
|
@@ -13,8 +13,6 @@
|
|
|
13
13
|
"Natural Language :: English",
|
|
14
14
|
"Operating System :: OS Independent",
|
|
15
15
|
"Programming Language :: Python :: 3",
|
|
16
|
-
"Programming Language :: Python :: 3.9",
|
|
17
|
-
"Programming Language :: Python :: 3.10",
|
|
18
16
|
"Programming Language :: Python :: 3.11",
|
|
19
17
|
"Programming Language :: Python :: 3.12",
|
|
20
18
|
"Programming Language :: Python :: 3.13",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
import types
|
|
4
4
|
from abc import abstractmethod
|
|
5
5
|
from typing import (
|
|
@@ -16,6 +16,7 @@ from typing import (
|
|
|
16
16
|
overload,
|
|
17
17
|
)
|
|
18
18
|
|
|
19
|
+
# alias to keep the 'bytecode' variable free
|
|
19
20
|
import bytecode as _bytecode
|
|
20
21
|
from bytecode.flags import CompilerFlags, infer_flags
|
|
21
22
|
from bytecode.instr import (
|
|
@@ -28,7 +29,6 @@ from bytecode.instr import (
|
|
|
28
29
|
TryBegin,
|
|
29
30
|
TryEnd,
|
|
30
31
|
)
|
|
31
|
-
from bytecode.utils import PY311
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class BaseBytecode:
|
|
@@ -50,7 +50,7 @@ class BaseBytecode:
|
|
|
50
50
|
self.freevars: List[str] = []
|
|
51
51
|
self._flags: CompilerFlags = CompilerFlags(0)
|
|
52
52
|
|
|
53
|
-
def _copy_attr_from(self, bytecode:
|
|
53
|
+
def _copy_attr_from(self, bytecode: BaseBytecode) -> None:
|
|
54
54
|
self.argcount = bytecode.argcount
|
|
55
55
|
self.posonlyargcount = bytecode.posonlyargcount
|
|
56
56
|
self.kwonlyargcount = bytecode.kwonlyargcount
|
|
@@ -164,15 +164,33 @@ class _BaseBytecodeList(BaseBytecode, list, Generic[U]):
|
|
|
164
164
|
for i in reversed(lineno_pos):
|
|
165
165
|
del self[i]
|
|
166
166
|
|
|
167
|
-
def __iter__(self) -> Iterator[U]:
|
|
168
|
-
instructions = super().__iter__()
|
|
169
|
-
for instr in instructions:
|
|
170
|
-
self._check_instr(instr)
|
|
171
|
-
yield instr
|
|
172
|
-
|
|
173
167
|
def _check_instr(self, instr):
|
|
174
168
|
raise NotImplementedError()
|
|
175
169
|
|
|
170
|
+
def append(self, instr: U) -> None: # type: ignore[override]
|
|
171
|
+
self._check_instr(instr)
|
|
172
|
+
super().append(instr)
|
|
173
|
+
|
|
174
|
+
def insert(self, index: SupportsIndex, instr: U) -> None: # type: ignore[override]
|
|
175
|
+
self._check_instr(instr)
|
|
176
|
+
super().insert(index, instr)
|
|
177
|
+
|
|
178
|
+
def extend(self, instrs) -> None: # type: ignore[override]
|
|
179
|
+
instrs = list(instrs)
|
|
180
|
+
for instr in instrs:
|
|
181
|
+
self._check_instr(instr)
|
|
182
|
+
super().extend(instrs)
|
|
183
|
+
|
|
184
|
+
def __setitem__(self, index, value):
|
|
185
|
+
if isinstance(index, slice):
|
|
186
|
+
values = list(value)
|
|
187
|
+
for v in values:
|
|
188
|
+
self._check_instr(v)
|
|
189
|
+
super().__setitem__(index, values)
|
|
190
|
+
else:
|
|
191
|
+
self._check_instr(value)
|
|
192
|
+
super().__setitem__(index, value)
|
|
193
|
+
|
|
176
194
|
|
|
177
195
|
V = TypeVar("V")
|
|
178
196
|
|
|
@@ -242,15 +260,11 @@ class Bytecode(
|
|
|
242
260
|
) -> None:
|
|
243
261
|
BaseBytecode.__init__(self)
|
|
244
262
|
self.argnames: List[str] = []
|
|
245
|
-
for instr in instructions:
|
|
246
|
-
self._check_instr(instr)
|
|
247
263
|
self.extend(instructions)
|
|
248
264
|
|
|
249
265
|
def __iter__(self) -> Iterator[Union[Instr, Label, TryBegin, TryEnd, SetLineno]]:
|
|
250
|
-
instructions = super().__iter__()
|
|
251
266
|
seen_try_begin = False
|
|
252
|
-
for instr in
|
|
253
|
-
self._check_instr(instr)
|
|
267
|
+
for instr in super().__iter__():
|
|
254
268
|
if isinstance(instr, TryBegin):
|
|
255
269
|
if seen_try_begin:
|
|
256
270
|
raise RuntimeError("TryBegin pseudo instructions cannot be nested.")
|
|
@@ -277,7 +291,7 @@ class Bytecode(
|
|
|
277
291
|
code: types.CodeType,
|
|
278
292
|
prune_caches: bool = True,
|
|
279
293
|
conserve_exception_block_stackdepth: bool = False,
|
|
280
|
-
) ->
|
|
294
|
+
) -> Bytecode:
|
|
281
295
|
concrete = _bytecode.ConcreteBytecode.from_code(code)
|
|
282
296
|
return concrete.to_bytecode(
|
|
283
297
|
prune_caches=prune_caches,
|
|
@@ -298,7 +312,7 @@ class Bytecode(
|
|
|
298
312
|
) -> types.CodeType:
|
|
299
313
|
# Prevent reconverting the concrete bytecode to bytecode and cfg to do the
|
|
300
314
|
# calculation if we need to do it.
|
|
301
|
-
if stacksize is None or
|
|
315
|
+
if stacksize is None or compute_exception_stack_depths:
|
|
302
316
|
cfg = _bytecode.ControlFlowGraph.from_bytecode(self)
|
|
303
317
|
stacksize = cfg.compute_stacksize(
|
|
304
318
|
check_pre_and_post=check_pre_and_post,
|
|
@@ -319,7 +333,7 @@ class Bytecode(
|
|
|
319
333
|
self,
|
|
320
334
|
compute_jumps_passes: Optional[int] = None,
|
|
321
335
|
compute_exception_stack_depths: bool = True,
|
|
322
|
-
) ->
|
|
336
|
+
) -> _bytecode.ConcreteBytecode:
|
|
323
337
|
converter = _bytecode._ConvertBytecodeToConcrete(self)
|
|
324
338
|
return converter.to_concrete_bytecode(
|
|
325
339
|
compute_jumps_passes=compute_jumps_passes,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import types
|
|
3
4
|
from collections import defaultdict
|
|
4
5
|
from dataclasses import dataclass
|
|
@@ -23,7 +24,7 @@ import bytecode as _bytecode
|
|
|
23
24
|
from bytecode.concrete import ConcreteInstr
|
|
24
25
|
from bytecode.flags import CompilerFlags
|
|
25
26
|
from bytecode.instr import UNSET, Instr, Label, SetLineno, TryBegin, TryEnd
|
|
26
|
-
from bytecode.utils import
|
|
27
|
+
from bytecode.utils import PY313
|
|
27
28
|
|
|
28
29
|
T = TypeVar("T", bound="BasicBlock")
|
|
29
30
|
U = TypeVar("U", bound="ControlFlowGraph")
|
|
@@ -37,43 +38,79 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
|
|
|
37
38
|
] = None,
|
|
38
39
|
) -> None:
|
|
39
40
|
# a BasicBlock object, or None
|
|
40
|
-
self.next_block: Optional[
|
|
41
|
+
self.next_block: Optional[BasicBlock] = None
|
|
41
42
|
if instructions:
|
|
42
43
|
super().__init__(instructions)
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
_VALID_TYPES = (SetLineno, Instr, TryBegin, TryEnd)
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def _check_instr(instr: Any) -> None:
|
|
49
|
+
if not isinstance(instr, (SetLineno, Instr, TryBegin, TryEnd)):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
"BasicBlock must only contain SetLineno and Instr objects, "
|
|
52
|
+
"but %s was found" % instr.__class__.__name__
|
|
53
|
+
)
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
def append(self, instr: Union[Instr, SetLineno, TryBegin, TryEnd]) -> None:
|
|
56
|
+
self._check_instr(instr)
|
|
57
|
+
if isinstance(instr, Instr):
|
|
58
|
+
last = self.get_last_non_artificial_instruction()
|
|
59
|
+
if last is not None and last.has_jump():
|
|
51
60
|
raise ValueError(
|
|
52
|
-
"
|
|
53
|
-
"but %s was found" % instr.__class__.__name__
|
|
61
|
+
"Only the last instruction of a basic block can be a jump"
|
|
54
62
|
)
|
|
63
|
+
super().append(instr)
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
65
|
+
def insert(
|
|
66
|
+
self, index: SupportsIndex, instr: Union[Instr, SetLineno, TryBegin, TryEnd]
|
|
67
|
+
) -> None:
|
|
68
|
+
self._check_instr(instr)
|
|
69
|
+
super().insert(index, instr)
|
|
70
|
+
|
|
71
|
+
def extend(
|
|
72
|
+
self, instrs: Iterable[Union[Instr, SetLineno, TryBegin, TryEnd]]
|
|
73
|
+
) -> None:
|
|
74
|
+
instrs = list(instrs)
|
|
75
|
+
for instr in instrs:
|
|
76
|
+
self._check_instr(instr)
|
|
77
|
+
existing_last = self.get_last_non_artificial_instruction()
|
|
78
|
+
last_new_instr: Optional[Instr] = None
|
|
79
|
+
for instr in instrs:
|
|
80
|
+
if isinstance(instr, Instr):
|
|
81
|
+
if (existing_last is not None and existing_last.has_jump()) or (
|
|
82
|
+
last_new_instr is not None and last_new_instr.has_jump()
|
|
59
83
|
):
|
|
60
84
|
raise ValueError(
|
|
61
85
|
"Only the last instruction of a basic block can be a jump"
|
|
62
86
|
)
|
|
87
|
+
last_new_instr = instr
|
|
88
|
+
super().extend(instrs)
|
|
89
|
+
|
|
90
|
+
def __setitem__(self, index, value):
|
|
91
|
+
if isinstance(index, slice):
|
|
92
|
+
values = list(value)
|
|
93
|
+
for instr in values:
|
|
94
|
+
self._check_instr(instr)
|
|
95
|
+
super().__setitem__(index, values)
|
|
96
|
+
else:
|
|
97
|
+
self._check_instr(value)
|
|
98
|
+
super().__setitem__(index, value)
|
|
63
99
|
|
|
100
|
+
def __iter__(self) -> Iterator[Union[Instr, SetLineno, TryBegin, TryEnd]]:
|
|
101
|
+
for instr in super().__iter__():
|
|
102
|
+
if isinstance(instr, Instr) and instr.has_jump():
|
|
64
103
|
if not isinstance(instr.arg, BasicBlock):
|
|
65
104
|
raise ValueError(
|
|
66
|
-
"Jump target must a BasicBlock, got %s"
|
|
67
|
-
type(instr.arg).__name__
|
|
105
|
+
"Jump target must a BasicBlock, got %s"
|
|
106
|
+
% type(instr.arg).__name__
|
|
68
107
|
)
|
|
69
|
-
|
|
70
|
-
if isinstance(instr, TryBegin):
|
|
108
|
+
elif isinstance(instr, TryBegin):
|
|
71
109
|
if not isinstance(instr.target, BasicBlock):
|
|
72
110
|
raise ValueError(
|
|
73
|
-
"TryBegin target must a BasicBlock, got %s"
|
|
74
|
-
type(instr.target).__name__
|
|
111
|
+
"TryBegin target must a BasicBlock, got %s"
|
|
112
|
+
% type(instr.target).__name__
|
|
75
113
|
)
|
|
76
|
-
|
|
77
114
|
yield instr
|
|
78
115
|
|
|
79
116
|
@overload
|
|
@@ -93,10 +130,9 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
|
|
|
93
130
|
return value
|
|
94
131
|
|
|
95
132
|
def get_last_non_artificial_instruction(self) -> Optional[Instr]:
|
|
96
|
-
for instr in
|
|
133
|
+
for instr in super().__reversed__():
|
|
97
134
|
if isinstance(instr, Instr):
|
|
98
135
|
return instr
|
|
99
|
-
|
|
100
136
|
return None
|
|
101
137
|
|
|
102
138
|
def copy(self: T) -> T:
|
|
@@ -130,7 +166,7 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
|
|
|
130
166
|
|
|
131
167
|
return current_lineno
|
|
132
168
|
|
|
133
|
-
def get_jump(self) -> Optional[
|
|
169
|
+
def get_jump(self) -> Optional[BasicBlock]:
|
|
134
170
|
if not self:
|
|
135
171
|
return None
|
|
136
172
|
|
|
@@ -244,7 +280,7 @@ class _StackSizeComputer:
|
|
|
244
280
|
self.pending_try_begin = pending_try_begin
|
|
245
281
|
self._current_try_begin = pending_try_begin
|
|
246
282
|
|
|
247
|
-
def run(self) -> Generator[Union[
|
|
283
|
+
def run(self) -> Generator[Union[_StackSizeComputer, int], int, None]:
|
|
248
284
|
"""Iterate over the block instructions to compute stack usage."""
|
|
249
285
|
# Blocks are not hashable but in this particular context we know we won't be
|
|
250
286
|
# modifying blocks in place so we can safely use their id as hash rather than
|
|
@@ -307,7 +343,17 @@ class _StackSizeComputer:
|
|
|
307
343
|
# current try begin. However inside the CFG some blocks may
|
|
308
344
|
# start with a TryEnd relevant only when reaching this block
|
|
309
345
|
# through a particular jump. So we are lenient here.
|
|
310
|
-
|
|
346
|
+
#
|
|
347
|
+
# We match on the exception handler (the TryBegin target block)
|
|
348
|
+
# rather than on the TryBegin instance: a single exception region
|
|
349
|
+
# can be split into several TryBegin copies that share the same
|
|
350
|
+
# handler (see ``from_bytecode``), and the copy carried over as
|
|
351
|
+
# ``pending_try_begin`` through a jump is not necessarily the same
|
|
352
|
+
# instance as the one referenced by this block's leading TryEnd.
|
|
353
|
+
if (
|
|
354
|
+
self._current_try_begin is None
|
|
355
|
+
or instr.entry.target is not self._current_try_begin.target
|
|
356
|
+
):
|
|
311
357
|
continue
|
|
312
358
|
|
|
313
359
|
# Compute the stack usage of the exception handler
|
|
@@ -344,9 +390,11 @@ class _StackSizeComputer:
|
|
|
344
390
|
None,
|
|
345
391
|
# Do not propagate the TryBegin if a final instruction is followed
|
|
346
392
|
# by a TryEnd.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
393
|
+
(
|
|
394
|
+
None
|
|
395
|
+
if instr.is_final() and self.block.get_trailing_try_end(i)
|
|
396
|
+
else self._current_try_begin
|
|
397
|
+
),
|
|
350
398
|
)
|
|
351
399
|
|
|
352
400
|
# Update the maximum used size by the usage implied by the following
|
|
@@ -363,8 +411,10 @@ class _StackSizeComputer:
|
|
|
363
411
|
# start with a TryEnd relevant only when reaching this block
|
|
364
412
|
# through a particular jump. So we are lenient here.
|
|
365
413
|
if (
|
|
366
|
-
te := self.block.get_trailing_try_end(i)
|
|
367
|
-
|
|
414
|
+
(te := self.block.get_trailing_try_end(i))
|
|
415
|
+
and self._current_try_begin is not None
|
|
416
|
+
and te.entry.target is self._current_try_begin.target
|
|
417
|
+
):
|
|
368
418
|
assert isinstance(te.entry.target, BasicBlock)
|
|
369
419
|
yield from self._compute_exception_handler_stack_usage(
|
|
370
420
|
te.entry.target,
|
|
@@ -427,7 +477,7 @@ class _StackSizeComputer:
|
|
|
427
477
|
|
|
428
478
|
def _compute_exception_handler_stack_usage(
|
|
429
479
|
self, block: BasicBlock, push_lasti: bool
|
|
430
|
-
) -> Generator[Union[
|
|
480
|
+
) -> Generator[Union[_StackSizeComputer, int], int, None]:
|
|
431
481
|
b_id = id(block)
|
|
432
482
|
if self.minsize < self.common.exception_block_startsize[b_id]:
|
|
433
483
|
block_size = yield _StackSizeComputer(
|
|
@@ -447,18 +497,10 @@ class _StackSizeComputer:
|
|
|
447
497
|
def _is_stacksize_computation_relevant(
|
|
448
498
|
self, block_id: int, fingerprint: Tuple[int, Optional[bool]]
|
|
449
499
|
) -> bool:
|
|
450
|
-
if
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return fingerprint not in self.common.blocks_startsizes[block_id]
|
|
455
|
-
else:
|
|
456
|
-
# The computation is relevant if the block was only visited with smaller
|
|
457
|
-
# starting sizes than the one in use
|
|
458
|
-
if sizes := self.common.blocks_startsizes[block_id]:
|
|
459
|
-
return fingerprint[0] > max(f[0] for f in sizes)
|
|
460
|
-
else:
|
|
461
|
-
return True
|
|
500
|
+
# The computation is relevant if the block was not visited previously
|
|
501
|
+
# with the same starting size and exception handler status than the
|
|
502
|
+
# one in use
|
|
503
|
+
return fingerprint not in self.common.blocks_startsizes[block_id]
|
|
462
504
|
|
|
463
505
|
|
|
464
506
|
class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
@@ -521,11 +563,10 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
521
563
|
)
|
|
522
564
|
|
|
523
565
|
# Starting with Python 3.10, generator and coroutines start with one object
|
|
524
|
-
# on the stack (None, anything is an error).
|
|
566
|
+
# on the stack (None, anything else is an error).
|
|
525
567
|
initial_stack_size = 0
|
|
526
568
|
if (
|
|
527
569
|
not PY313 # under 3.13+ RETURN_GENERATOR make this explicit
|
|
528
|
-
and PY310
|
|
529
570
|
and self.flags
|
|
530
571
|
& (
|
|
531
572
|
CompilerFlags.GENERATOR
|
|
@@ -588,7 +629,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
588
629
|
if compute_exception_stack_depths:
|
|
589
630
|
for tb in common.try_begins:
|
|
590
631
|
size = common.exception_block_startsize[id(tb.target)]
|
|
591
|
-
assert size >= 0
|
|
592
632
|
tb.stack_depth = size
|
|
593
633
|
|
|
594
634
|
return args
|
|
@@ -629,7 +669,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
629
669
|
# argument rather than a Label. This is fine for comparison
|
|
630
670
|
# purposes which is our sole goal here.
|
|
631
671
|
c_instr = ConcreteInstr(
|
|
632
|
-
instr.
|
|
672
|
+
instr._name,
|
|
633
673
|
self.get_block_index(target_block),
|
|
634
674
|
location=instr.location,
|
|
635
675
|
)
|
|
@@ -748,7 +788,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
748
788
|
return [b for b in self if id(b) not in seen_block_ids]
|
|
749
789
|
|
|
750
790
|
@staticmethod
|
|
751
|
-
def from_bytecode(bytecode: _bytecode.Bytecode) ->
|
|
791
|
+
def from_bytecode(bytecode: _bytecode.Bytecode) -> ControlFlowGraph:
|
|
752
792
|
# label => instruction index
|
|
753
793
|
label_to_block_index = {}
|
|
754
794
|
jumps = []
|
|
@@ -841,7 +881,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
841
881
|
# The last instruction is final, if the current instruction is a
|
|
842
882
|
# TryEnd insert it in the same block and move to the next instruction
|
|
843
883
|
if last_instr.is_final() and isinstance(instr, TryEnd):
|
|
844
|
-
assert active_try_begin
|
|
884
|
+
assert active_try_begin is not None
|
|
845
885
|
nte = instr.copy()
|
|
846
886
|
nte.entry = try_begins[active_try_begin][-1]
|
|
847
887
|
old_block.append(nte)
|
|
@@ -888,7 +928,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
888
928
|
if isinstance(instr, (Instr, TryBegin, TryEnd)):
|
|
889
929
|
new = instr.copy()
|
|
890
930
|
if isinstance(instr, TryBegin):
|
|
891
|
-
assert active_try_begin is None
|
|
892
931
|
active_try_begin = instr
|
|
893
932
|
try_begin_inserted_in_block = True
|
|
894
933
|
assert isinstance(new, TryBegin)
|
|
@@ -982,9 +1021,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
982
1021
|
# If due to jumps and split TryBegin, we encounter a TryBegin
|
|
983
1022
|
# while we still have a TryBegin ensure they can be fused.
|
|
984
1023
|
if last_try_begin is not None:
|
|
985
|
-
|
|
986
|
-
assert instr.target is cfg_tb.target
|
|
987
|
-
assert instr.push_lasti == cfg_tb.push_lasti
|
|
1024
|
+
_, byt_tb = last_try_begin
|
|
988
1025
|
byt_tb.stack_depth = min(
|
|
989
1026
|
byt_tb.stack_depth, instr.stack_depth
|
|
990
1027
|
)
|
|
@@ -1003,7 +1040,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
1003
1040
|
# If we did not yet compute the required stack depth
|
|
1004
1041
|
# keep the value as UNSET
|
|
1005
1042
|
if entry.stack_depth is UNSET:
|
|
1006
|
-
assert instr.stack_depth is UNSET
|
|
1007
1043
|
byt_te.entry.stack_depth = UNSET
|
|
1008
1044
|
else:
|
|
1009
1045
|
byt_te.entry.stack_depth = min(
|