bytecode 0.18.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.
Files changed (61) hide show
  1. {bytecode-0.18.0/src/bytecode.egg-info → bytecode-0.18.1}/PKG-INFO +1 -1
  2. {bytecode-0.18.0 → bytecode-0.18.1}/doc/changelog.rst +11 -0
  3. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/cfg.py +12 -2
  4. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/version.py +2 -2
  5. {bytecode-0.18.0 → bytecode-0.18.1/src/bytecode.egg-info}/PKG-INFO +1 -1
  6. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_cfg.py +73 -0
  7. {bytecode-0.18.0 → bytecode-0.18.1}/.coveragerc +0 -0
  8. {bytecode-0.18.0 → bytecode-0.18.1}/.github/FUNDING.yml +0 -0
  9. {bytecode-0.18.0 → bytecode-0.18.1}/.github/dependabot.yml +0 -0
  10. {bytecode-0.18.0 → bytecode-0.18.1}/.github/workflows/cis.yml +0 -0
  11. {bytecode-0.18.0 → bytecode-0.18.1}/.github/workflows/docs.yml +0 -0
  12. {bytecode-0.18.0 → bytecode-0.18.1}/.github/workflows/frameworks.yml +0 -0
  13. {bytecode-0.18.0 → bytecode-0.18.1}/.github/workflows/release.yml +0 -0
  14. {bytecode-0.18.0 → bytecode-0.18.1}/.gitignore +0 -0
  15. {bytecode-0.18.0 → bytecode-0.18.1}/.pre-commit-config.yaml +0 -0
  16. {bytecode-0.18.0 → bytecode-0.18.1}/.readthedocs.yaml +0 -0
  17. {bytecode-0.18.0 → bytecode-0.18.1}/COPYING +0 -0
  18. {bytecode-0.18.0 → bytecode-0.18.1}/MANIFEST.in +0 -0
  19. {bytecode-0.18.0 → bytecode-0.18.1}/README.rst +0 -0
  20. {bytecode-0.18.0 → bytecode-0.18.1}/TODO.rst +0 -0
  21. {bytecode-0.18.0 → bytecode-0.18.1}/codecov.yml +0 -0
  22. {bytecode-0.18.0 → bytecode-0.18.1}/doc/Makefile +0 -0
  23. {bytecode-0.18.0 → bytecode-0.18.1}/doc/api.rst +0 -0
  24. {bytecode-0.18.0 → bytecode-0.18.1}/doc/byteplay_codetransformer.rst +0 -0
  25. {bytecode-0.18.0 → bytecode-0.18.1}/doc/cfg.rst +0 -0
  26. {bytecode-0.18.0 → bytecode-0.18.1}/doc/conf.py +0 -0
  27. {bytecode-0.18.0 → bytecode-0.18.1}/doc/index.rst +0 -0
  28. {bytecode-0.18.0 → bytecode-0.18.1}/doc/make.bat +0 -0
  29. {bytecode-0.18.0 → bytecode-0.18.1}/doc/requirements.txt +0 -0
  30. {bytecode-0.18.0 → bytecode-0.18.1}/doc/todo.rst +0 -0
  31. {bytecode-0.18.0 → bytecode-0.18.1}/doc/usage.rst +0 -0
  32. {bytecode-0.18.0 → bytecode-0.18.1}/pyproject.toml +0 -0
  33. {bytecode-0.18.0 → bytecode-0.18.1}/scripts/frameworks/boto3/run.sh +0 -0
  34. {bytecode-0.18.0 → bytecode-0.18.1}/scripts/frameworks/boto3/setup.sh +0 -0
  35. {bytecode-0.18.0 → bytecode-0.18.1}/setup.cfg +0 -0
  36. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/__init__.py +0 -0
  37. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/bytecode.py +0 -0
  38. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/concrete.py +0 -0
  39. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/flags.py +0 -0
  40. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/instr.py +0 -0
  41. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/py.typed +0 -0
  42. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode/utils.py +0 -0
  43. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode.egg-info/SOURCES.txt +0 -0
  44. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode.egg-info/dependency_links.txt +0 -0
  45. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode.egg-info/requires.txt +0 -0
  46. {bytecode-0.18.0 → bytecode-0.18.1}/src/bytecode.egg-info/top_level.txt +0 -0
  47. {bytecode-0.18.0 → bytecode-0.18.1}/tests/__init__.py +0 -0
  48. {bytecode-0.18.0 → bytecode-0.18.1}/tests/cell_free_vars_cases.py +0 -0
  49. {bytecode-0.18.0 → bytecode-0.18.1}/tests/exception_handling_cases.py +0 -0
  50. {bytecode-0.18.0 → bytecode-0.18.1}/tests/frameworks/function.py +0 -0
  51. {bytecode-0.18.0 → bytecode-0.18.1}/tests/frameworks/module.py +0 -0
  52. {bytecode-0.18.0 → bytecode-0.18.1}/tests/frameworks/sitecustomize.py +0 -0
  53. {bytecode-0.18.0 → bytecode-0.18.1}/tests/long_lines_example.py +0 -0
  54. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_bytecode.py +0 -0
  55. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_code.py +0 -0
  56. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_concrete.py +0 -0
  57. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_flags.py +0 -0
  58. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_instr.py +0 -0
  59. {bytecode-0.18.0 → bytecode-0.18.1}/tests/test_misc.py +0 -0
  60. {bytecode-0.18.0 → bytecode-0.18.1}/tests/util_annotation.py +0 -0
  61. {bytecode-0.18.0 → bytecode-0.18.1}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bytecode
3
- Version: 0.18.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>
@@ -1,6 +1,17 @@
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
+
4
15
  03-06-2026: Version 0.18.0
5
16
  --------------------------
6
17
 
@@ -343,7 +343,17 @@ class _StackSizeComputer:
343
343
  # current try begin. However inside the CFG some blocks may
344
344
  # start with a TryEnd relevant only when reaching this block
345
345
  # through a particular jump. So we are lenient here.
346
- if instr.entry is not self._current_try_begin:
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
+ ):
347
357
  continue
348
358
 
349
359
  # Compute the stack usage of the exception handler
@@ -403,7 +413,7 @@ class _StackSizeComputer:
403
413
  if (
404
414
  (te := self.block.get_trailing_try_end(i))
405
415
  and self._current_try_begin is not None
406
- and te.entry is self._current_try_begin
416
+ and te.entry.target is self._current_try_begin.target
407
417
  ):
408
418
  assert isinstance(te.entry.target, BasicBlock)
409
419
  yield from self._compute_exception_handler_stack_usage(
@@ -5,7 +5,7 @@ from collections import namedtuple
5
5
  #: A namedtuple of the version info for the current release.
6
6
  _version_info = namedtuple("_version_info", "major minor micro status")
7
7
 
8
- parts = "0.18.0".split(".", 3)
8
+ parts = "0.18.1".split(".", 3)
9
9
  version_info = _version_info(
10
10
  int(parts[0]),
11
11
  int(parts[1]),
@@ -16,4 +16,4 @@ version_info = _version_info(
16
16
  # Remove everything but the 'version_info' from this module.
17
17
  del namedtuple, _version_info, parts
18
18
 
19
- __version__ = "0.18.0"
19
+ __version__ = "0.18.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bytecode
3
- Version: 0.18.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>
@@ -18,6 +18,7 @@ from bytecode import (
18
18
  Label,
19
19
  SetLineno,
20
20
  TryBegin,
21
+ TryEnd,
21
22
  dump_bytecode,
22
23
  )
23
24
  from bytecode.utils import PY312, PY313, PY314
@@ -903,6 +904,78 @@ class CFGStacksizeComputationTests(TestCase):
903
904
  self.assertEqual(test([], name=None), -1)
904
905
  self.assertEqual(stdout.getvalue(), "second finally\nfirst finally\n")
905
906
 
907
+ def test_stack_size_computation_shared_handler_split_regions(self):
908
+ # Regression test for an assertion failure in the stack-size
909
+ # computation. A bytecode-rewriting transform (e.g. an instrumentation
910
+ # tool) may wrap a whole function body in a single exception handler
911
+ # whose coverage is split around the body's own try blocks, so that the
912
+ # regions never nest but all share the *same* handler block. A single
913
+ # logical region then maps to several TryBegin instances.
914
+ #
915
+ # When such a region is exited through a forward jump (here the branch
916
+ # inside the first handler body), the jump target block is reached both
917
+ # by fall-through and by the jump, and ends up carrying one TryBegin
918
+ # copy as ``pending_try_begin`` while its own leading TryEnd references
919
+ # a *different* copy of the same region. Matching TryEnd against the
920
+ # current TryBegin by identity then failed to close the region, leaving
921
+ # it spuriously open and tripping ``assert self._current_try_begin is
922
+ # None`` at the following TryBegin. Matching must be done on the handler
923
+ # (the TryBegin target block) instead.
924
+ if sys.version_info < (3, 11):
925
+ self.skipTest("exception tables (TryBegin/TryEnd) require 3.11+")
926
+
927
+ def trigger(d, x): # pragma: no cover
928
+ try:
929
+ d["a"]
930
+ except KeyError:
931
+ if x: # a branch *inside* the handler body
932
+ pass
933
+ else:
934
+ d["b"] = 1
935
+ try: # ... followed by another try block
936
+ del d["c"]
937
+ except KeyError:
938
+ pass
939
+ return 99
940
+
941
+ bc = Bytecode.from_code(trigger.__code__)
942
+
943
+ # Wrap the whole body in one shared handler, splitting the covering
944
+ # region around the body's existing try blocks.
945
+ except_label = Label()
946
+ first_tb = last_tb = TryBegin(except_label, push_lasti=True)
947
+ i = 0
948
+ while i < len(bc):
949
+ instr = bc[i]
950
+ if isinstance(instr, TryBegin) and last_tb is not None:
951
+ bc.insert(i, TryEnd(last_tb))
952
+ last_tb = None
953
+ i += 1
954
+ elif isinstance(instr, TryEnd):
955
+ j = i + 1
956
+ while j < len(bc) and not isinstance(bc[j], TryBegin):
957
+ if isinstance(bc[j], Instr):
958
+ last_tb = TryBegin(except_label, push_lasti=True)
959
+ bc.insert(i + 1, last_tb)
960
+ break
961
+ j += 1
962
+ i += 1
963
+ i += 1
964
+ bc.insert(0, first_tb)
965
+ bc.append(TryEnd(last_tb))
966
+ bc.append(except_label)
967
+ bc.append(Instr("PUSH_EXC_INFO"))
968
+ bc.append(Instr("RERAISE", 2))
969
+
970
+ cfg = ControlFlowGraph.from_bytecode(bc)
971
+ # Used to raise AssertionError on 3.12.
972
+ cfg.compute_stacksize()
973
+
974
+ # The rewritten code object is valid and still runs correctly.
975
+ trigger.__code__ = cfg.to_code()
976
+ self.assertEqual(trigger({}, True), 99)
977
+ self.assertEqual(trigger({"c": 1}, False), 99)
978
+
906
979
  def test_stack_size_with_dead_code(self):
907
980
  # Simply demonstrate more directly the previously mentioned issue.
908
981
  def test(*args): # pragma: no cover
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes