|
4 | 4 | import array |
5 | 5 | import io |
6 | 6 | import marshal |
| 7 | +import struct |
7 | 8 | import sys |
8 | 9 | import unittest |
9 | 10 | import os |
@@ -140,6 +141,27 @@ def test_different_filenames(self): |
140 | 141 | self.assertEqual(co1.co_filename, "f1") |
141 | 142 | self.assertEqual(co2.co_filename, "f2") |
142 | 143 |
|
| 144 | + def test_inconsistent_code_object(self): |
| 145 | + # len(localsplusnames) must equal len(localspluskinds); marshal data that |
| 146 | + # breaks this must raise ValueError, not a leaked SystemError (gh-151830). |
| 147 | + co = compile("def f(a, b):\n x = a\n", "<test>", "exec").co_consts[0] |
| 148 | + n = len(co.co_varnames) + len(co.co_cellvars) + len(co.co_freevars) |
| 149 | + blob = marshal.dumps(co) |
| 150 | + # Find the localspluskinds record: the TYPE_STRING ('s') whose payload |
| 151 | + # is exactly n bytes long (one kind byte per localsplus name). |
| 152 | + for off in range(len(blob) - 5): |
| 153 | + if blob[off] == ord('s'): |
| 154 | + if struct.unpack_from('<i', blob, off + 1)[0] == n: |
| 155 | + kinds = blob[off + 5:off + 5 + n] |
| 156 | + break |
| 157 | + else: |
| 158 | + self.fail("could not locate localspluskinds in marshal data") |
| 159 | + # Rewrite it with one fewer kind byte than there are names. |
| 160 | + corrupt = (blob[:off] + b's' + struct.pack('<i', n - 1) |
| 161 | + + kinds[:n - 1] + blob[off + 5 + n:]) |
| 162 | + with self.assertRaises(ValueError): |
| 163 | + marshal.loads(corrupt) |
| 164 | + |
143 | 165 | def test_no_allow_code(self): |
144 | 166 | data = {'a': [({0},)]} |
145 | 167 | dump = marshal.dumps(data, allow_code=False) |
|
0 commit comments