fix(python): preserve historical signed code fields

Keep signed co_stacksize and co_flags for historical magic entries whose constructors accepted them. Preserve magic 3410 with a constructor-revision limitation; reject negative values from 3411 onward.

Fixes #6487
This commit is contained in:
morluto committed 2026-10-23 18:31:02 +08:00
1 parent 667402846c
commit c6ab965062
7 files changed
+253 -79

No files matched your search

+9
View File
@@ -10,6 +10,7 @@ it("maps pinned CPython 3.7–3.13 magics and leaves gaps unknown", () => {
python_version: "3.7",
code_layout: "py37",
header_bytes: 16,
negative_code_fields: "accepted",
});
expect(pythonPycMagicEntry(3390)).toMatchObject({
python_version: "3.7",
@@ -23,16 +24,24 @@ it("maps pinned CPython 3.7–3.13 magics and leaves gaps unknown", () => {
python_version: "3.8",
code_layout: "py38",
header_bytes: 16,
negative_code_fields: "rejected",
});
expect(pythonPycMagicEntry(3401)?.negative_code_fields).toBe("accepted");
expect(pythonPycMagicEntry(3410)?.negative_code_fields).toBe(
"revision-dependent",
);
expect(pythonPycMagicEntry(3411)?.negative_code_fields).toBe("rejected");
expect(pythonPycMagicEntry(3531)).toEqual({
python_version: "3.12",
code_layout: "py311",
header_bytes: 16,
negative_code_fields: "rejected",
});
expect(pythonPycMagicEntry(3571)).toEqual({
python_version: "3.13",
code_layout: "py311",
header_bytes: 16,
negative_code_fields: "rejected",
});
expect(pythonPycMagicEntry(3450)).toBeNull();
expect(pythonPycMagicEntry(62171)).toBeNull();
+14 -6
View File
@@ -20,6 +20,8 @@ type MagicEntry = {
readonly python_version: string;
readonly code_layout: PythonPycCodeLayout;
readonly header_bytes: 12 | 16;
/** CPython constructor policy for negative co_stacksize/co_flags fields. */
readonly negative_code_fields: "accepted" | "revision-dependent" | "rejected";
};
const magics = (
@@ -27,8 +29,12 @@ const magics = (
python_version: string,
code_layout: PythonPycCodeLayout,
header_bytes: 12 | 16 = PYTHON_PYC_HEADER_BYTES,
negative_code_fields: MagicEntry["negative_code_fields"] = "rejected",
): ReadonlyArray<readonly [number, MagicEntry]> =>
values.map((magic) => [magic, { python_version, code_layout, header_bytes }]);
values.map((magic) => [
magic,
{ python_version, code_layout, header_bytes, negative_code_fields },
]);
const consecutive = (
start: number,
@@ -48,10 +54,12 @@ const consecutive = (
* bytecode opcode tables.
*/
export const PYTHON_PYC_MAGIC_TABLE: ReadonlyMap<number, MagicEntry> = new Map([
...magics([3390, 3391], "3.7", "py37", 12),
...magics([3392, 3393, 3394], "3.7", "py37"),
...magics([3400, 3401], "3.8", "py37"),
...magics([3410, 3411, 3412, 3413], "3.8", "py38"),
...magics([3390, 3391], "3.7", "py37", 12, "accepted"),
...magics([3392, 3393, 3394], "3.7", "py37", 16, "accepted"),
...magics([3400, 3401], "3.8", "py37", 16, "accepted"),
// 3410 spans 3.8.0a4 and b1; a9f05d69 added constructor sign checks between.
...magics([3410], "3.8", "py38", 16, "revision-dependent"),
...magics([3411, 3412, 3413], "3.8", "py38", 16, "rejected"),
...magics([3420, 3421, 3422, 3423, 3424, 3425], "3.9", "py38"),
...magics(
[3430, 3431, 3432, 3433, 3434, 3435, 3436, 3437, 3438, 3439],
@@ -166,7 +174,7 @@ const pythonCodeFields = {
posonlyargcount: nonNegativeInt32.nullable(),
kwonlyargcount: nonNegativeInt32,
nlocals: nonNegativeInt32.nullable(),
stacksize: nonNegativeInt32,
stacksize: signedInt32,
flags: signedInt32,
names: z.array(z.string()),
varnames: z.array(z.string()).nullable(),