bytecode 0.17.0__tar.gz → 0.18.0__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.0}/.github/workflows/cis.yml +6 -10
- {bytecode-0.17.0 → bytecode-0.18.0}/.github/workflows/docs.yml +2 -2
- {bytecode-0.17.0 → bytecode-0.18.0}/.github/workflows/frameworks.yml +3 -3
- {bytecode-0.17.0 → bytecode-0.18.0}/.github/workflows/release.yml +10 -10
- {bytecode-0.17.0 → bytecode-0.18.0}/.readthedocs.yaml +2 -2
- {bytecode-0.17.0/src/bytecode.egg-info → bytecode-0.18.0}/PKG-INFO +2 -4
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/changelog.rst +22 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/pyproject.toml +1 -3
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/bytecode.py +32 -18
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/cfg.py +80 -54
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/concrete.py +246 -385
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/flags.py +21 -28
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/instr.py +281 -207
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/utils.py +0 -2
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/version.py +2 -2
- {bytecode-0.17.0 → bytecode-0.18.0/src/bytecode.egg-info}/PKG-INFO +2 -4
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/__init__.py +13 -54
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/exception_handling_cases.py +2 -19
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/frameworks/module.py +3 -1
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_bytecode.py +28 -160
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_cfg.py +73 -108
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_code.py +13 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_concrete.py +61 -217
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_flags.py +12 -43
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_instr.py +12 -22
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_misc.py +7 -148
- {bytecode-0.17.0 → bytecode-0.18.0}/.coveragerc +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/.github/FUNDING.yml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/.github/dependabot.yml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/.gitignore +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/.pre-commit-config.yaml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/COPYING +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/MANIFEST.in +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/README.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/TODO.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/codecov.yml +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/Makefile +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/api.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/byteplay_codetransformer.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/cfg.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/conf.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/index.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/make.bat +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/requirements.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/todo.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/doc/usage.rst +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/scripts/frameworks/boto3/run.sh +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/scripts/frameworks/boto3/setup.sh +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/setup.cfg +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/__init__.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/py.typed +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/SOURCES.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/dependency_links.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/requires.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/top_level.txt +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/cell_free_vars_cases.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/frameworks/function.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/frameworks/sitecustomize.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/long_lines_example.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/tests/util_annotation.py +0 -0
- {bytecode-0.17.0 → bytecode-0.18.0}/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.0
|
|
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,28 @@
|
|
|
1
1
|
ChangeLog
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
03-06-2026: Version 0.18.0
|
|
5
|
+
--------------------------
|
|
6
|
+
|
|
7
|
+
- drop support for Python 3.9 and 3.10 PR #180
|
|
8
|
+
- Replace string literal type annotations with postponed evaluation using
|
|
9
|
+
``from __future__ import annotations`` PR #191
|
|
10
|
+
- multiple performance improvements
|
|
11
|
+
PRs #172, #193, #196, #194, #197, #198, #199, #200, #201,
|
|
12
|
+
#202, #203
|
|
13
|
+
|
|
14
|
+
Breaking changes:
|
|
15
|
+
|
|
16
|
+
- ``BasicBlock``, ``Bytecode``, and ``ConcreteBytecode`` now validate inserted
|
|
17
|
+
instructions at insertion time (``append``, ``extend``, ``insert``,
|
|
18
|
+
``__setitem__``) rather than during iteration. Code that relied on catching
|
|
19
|
+
``ValueError`` from ``list(block)`` or ``for instr in block:`` must wrap the
|
|
20
|
+
insertion call instead. PR #199
|
|
21
|
+
|
|
22
|
+
Bugfixes:
|
|
23
|
+
|
|
24
|
+
- Fix handling of END_ASYNC_FOR which is a backward jump PR #179
|
|
25
|
+
|
|
4
26
|
03-09-2025: Version 0.17.0
|
|
5
27
|
--------------------------
|
|
6
28
|
|
|
@@ -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)
|
|
63
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)
|
|
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
|
|
@@ -344,9 +380,11 @@ class _StackSizeComputer:
|
|
|
344
380
|
None,
|
|
345
381
|
# Do not propagate the TryBegin if a final instruction is followed
|
|
346
382
|
# by a TryEnd.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
383
|
+
(
|
|
384
|
+
None
|
|
385
|
+
if instr.is_final() and self.block.get_trailing_try_end(i)
|
|
386
|
+
else self._current_try_begin
|
|
387
|
+
),
|
|
350
388
|
)
|
|
351
389
|
|
|
352
390
|
# Update the maximum used size by the usage implied by the following
|
|
@@ -363,8 +401,10 @@ class _StackSizeComputer:
|
|
|
363
401
|
# start with a TryEnd relevant only when reaching this block
|
|
364
402
|
# through a particular jump. So we are lenient here.
|
|
365
403
|
if (
|
|
366
|
-
te := self.block.get_trailing_try_end(i)
|
|
367
|
-
|
|
404
|
+
(te := self.block.get_trailing_try_end(i))
|
|
405
|
+
and self._current_try_begin is not None
|
|
406
|
+
and te.entry is self._current_try_begin
|
|
407
|
+
):
|
|
368
408
|
assert isinstance(te.entry.target, BasicBlock)
|
|
369
409
|
yield from self._compute_exception_handler_stack_usage(
|
|
370
410
|
te.entry.target,
|
|
@@ -427,7 +467,7 @@ class _StackSizeComputer:
|
|
|
427
467
|
|
|
428
468
|
def _compute_exception_handler_stack_usage(
|
|
429
469
|
self, block: BasicBlock, push_lasti: bool
|
|
430
|
-
) -> Generator[Union[
|
|
470
|
+
) -> Generator[Union[_StackSizeComputer, int], int, None]:
|
|
431
471
|
b_id = id(block)
|
|
432
472
|
if self.minsize < self.common.exception_block_startsize[b_id]:
|
|
433
473
|
block_size = yield _StackSizeComputer(
|
|
@@ -447,18 +487,10 @@ class _StackSizeComputer:
|
|
|
447
487
|
def _is_stacksize_computation_relevant(
|
|
448
488
|
self, block_id: int, fingerprint: Tuple[int, Optional[bool]]
|
|
449
489
|
) -> 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
|
|
490
|
+
# The computation is relevant if the block was not visited previously
|
|
491
|
+
# with the same starting size and exception handler status than the
|
|
492
|
+
# one in use
|
|
493
|
+
return fingerprint not in self.common.blocks_startsizes[block_id]
|
|
462
494
|
|
|
463
495
|
|
|
464
496
|
class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
@@ -521,11 +553,10 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
521
553
|
)
|
|
522
554
|
|
|
523
555
|
# Starting with Python 3.10, generator and coroutines start with one object
|
|
524
|
-
# on the stack (None, anything is an error).
|
|
556
|
+
# on the stack (None, anything else is an error).
|
|
525
557
|
initial_stack_size = 0
|
|
526
558
|
if (
|
|
527
559
|
not PY313 # under 3.13+ RETURN_GENERATOR make this explicit
|
|
528
|
-
and PY310
|
|
529
560
|
and self.flags
|
|
530
561
|
& (
|
|
531
562
|
CompilerFlags.GENERATOR
|
|
@@ -588,7 +619,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
588
619
|
if compute_exception_stack_depths:
|
|
589
620
|
for tb in common.try_begins:
|
|
590
621
|
size = common.exception_block_startsize[id(tb.target)]
|
|
591
|
-
assert size >= 0
|
|
592
622
|
tb.stack_depth = size
|
|
593
623
|
|
|
594
624
|
return args
|
|
@@ -629,7 +659,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
629
659
|
# argument rather than a Label. This is fine for comparison
|
|
630
660
|
# purposes which is our sole goal here.
|
|
631
661
|
c_instr = ConcreteInstr(
|
|
632
|
-
instr.
|
|
662
|
+
instr._name,
|
|
633
663
|
self.get_block_index(target_block),
|
|
634
664
|
location=instr.location,
|
|
635
665
|
)
|
|
@@ -748,7 +778,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
748
778
|
return [b for b in self if id(b) not in seen_block_ids]
|
|
749
779
|
|
|
750
780
|
@staticmethod
|
|
751
|
-
def from_bytecode(bytecode: _bytecode.Bytecode) ->
|
|
781
|
+
def from_bytecode(bytecode: _bytecode.Bytecode) -> ControlFlowGraph:
|
|
752
782
|
# label => instruction index
|
|
753
783
|
label_to_block_index = {}
|
|
754
784
|
jumps = []
|
|
@@ -841,7 +871,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
841
871
|
# The last instruction is final, if the current instruction is a
|
|
842
872
|
# TryEnd insert it in the same block and move to the next instruction
|
|
843
873
|
if last_instr.is_final() and isinstance(instr, TryEnd):
|
|
844
|
-
assert active_try_begin
|
|
874
|
+
assert active_try_begin is not None
|
|
845
875
|
nte = instr.copy()
|
|
846
876
|
nte.entry = try_begins[active_try_begin][-1]
|
|
847
877
|
old_block.append(nte)
|
|
@@ -888,7 +918,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
888
918
|
if isinstance(instr, (Instr, TryBegin, TryEnd)):
|
|
889
919
|
new = instr.copy()
|
|
890
920
|
if isinstance(instr, TryBegin):
|
|
891
|
-
assert active_try_begin is None
|
|
892
921
|
active_try_begin = instr
|
|
893
922
|
try_begin_inserted_in_block = True
|
|
894
923
|
assert isinstance(new, TryBegin)
|
|
@@ -982,9 +1011,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
982
1011
|
# If due to jumps and split TryBegin, we encounter a TryBegin
|
|
983
1012
|
# while we still have a TryBegin ensure they can be fused.
|
|
984
1013
|
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
|
|
1014
|
+
_, byt_tb = last_try_begin
|
|
988
1015
|
byt_tb.stack_depth = min(
|
|
989
1016
|
byt_tb.stack_depth, instr.stack_depth
|
|
990
1017
|
)
|
|
@@ -1003,7 +1030,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
|
|
|
1003
1030
|
# If we did not yet compute the required stack depth
|
|
1004
1031
|
# keep the value as UNSET
|
|
1005
1032
|
if entry.stack_depth is UNSET:
|
|
1006
|
-
assert instr.stack_depth is UNSET
|
|
1007
1033
|
byt_te.entry.stack_depth = UNSET
|
|
1008
1034
|
else:
|
|
1009
1035
|
byt_te.entry.stack_depth = min(
|