From d267d7236f7dd8e85402ab38d5b3d3ddaca99a82 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sun, 21 Jun 2026 11:46:50 +0800 Subject: [PATCH 1/3] gh-151830: Raise ValueError instead of SystemError for invalid code objects in marshal.loads() --- Lib/test/test_marshal.py | 22 +++++++++++++++++++ ...-06-21-11-46-23.gh-issue-151830.qvOe1f.rst | 3 +++ Python/marshal.c | 4 ++++ 3 files changed, 29 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-21-11-46-23.gh-issue-151830.qvOe1f.rst diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 9c4d91c456dc5d..3ed3bcb7f4016c 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -4,6 +4,7 @@ import array import io import marshal +import struct import sys import unittest import os @@ -140,6 +141,27 @@ def test_different_filenames(self): self.assertEqual(co1.co_filename, "f1") self.assertEqual(co2.co_filename, "f2") + def test_inconsistent_code_object(self): + # len(localsplusnames) must equal len(localspluskinds); marshal data that + # breaks this must raise ValueError, not a leaked SystemError (gh-151830). + co = compile("def f(a, b):\n x = a\n", "", "exec").co_consts[0] + n = len(co.co_varnames) + len(co.co_cellvars) + len(co.co_freevars) + blob = marshal.dumps(co) + # Find the localspluskinds record: the TYPE_STRING ('s') whose payload + # is exactly n bytes long (one kind byte per localsplus name). + for off in range(len(blob) - 5): + if blob[off] == ord('s'): + if struct.unpack_from(' Date: Sun, 21 Jun 2026 13:21:46 +0800 Subject: [PATCH 2/3] Keep test comment within 79 columns --- Lib/test/test_marshal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 3ed3bcb7f4016c..8fd7acc97b980c 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -142,8 +142,8 @@ def test_different_filenames(self): self.assertEqual(co2.co_filename, "f2") def test_inconsistent_code_object(self): - # len(localsplusnames) must equal len(localspluskinds); marshal data that - # breaks this must raise ValueError, not a leaked SystemError (gh-151830). + # localsplusnames and localspluskinds must have equal length; if + # not, marshal must raise ValueError, not SystemError (gh-151830). co = compile("def f(a, b):\n x = a\n", "", "exec").co_consts[0] n = len(co.co_varnames) + len(co.co_cellvars) + len(co.co_freevars) blob = marshal.dumps(co) From 0d21591483c303edb2e0f37dc4fe9653f5a7bac5 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Sun, 21 Jun 2026 22:33:58 +0800 Subject: [PATCH 3/3] gh-151830: Assert localspluskinds value and use assertRaisesRegex in test --- Lib/test/test_marshal.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py index 8fd7acc97b980c..cd53a77866d55c 100644 --- a/Lib/test/test_marshal.py +++ b/Lib/test/test_marshal.py @@ -144,22 +144,24 @@ def test_different_filenames(self): def test_inconsistent_code_object(self): # localsplusnames and localspluskinds must have equal length; if # not, marshal must raise ValueError, not SystemError (gh-151830). - co = compile("def f(a, b):\n x = a\n", "", "exec").co_consts[0] + co = compile("def f():\n a=1;b=2;c=3", "", "exec").co_consts[0] n = len(co.co_varnames) + len(co.co_cellvars) + len(co.co_freevars) blob = marshal.dumps(co) # Find the localspluskinds record: the TYPE_STRING ('s') whose payload # is exactly n bytes long (one kind byte per localsplus name). + kinds = None for off in range(len(blob) - 5): if blob[off] == ord('s'): if struct.unpack_from('