From 2caa2598d339fa015f0285f3cbcf0166d941458e Mon Sep 17 00:00:00 2001 From: gensokyo-dev Date: Sat, 14 Nov 2026 12:08:18 +0000 Subject: [PATCH] fix(python): walk slice members in the code hashability stack The key resolver followed slice members, but the explicit-stack hashability walk skipped them, so a code object reached through a slice constant started a recursive hashability check. A 3,000-link slice-wrapped TYPE_REF code chain overflowed the JavaScript stack. Push slice members onto the same explicit stack as tuple and frozenset members. Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- src/python/PythonPycParser.test.ts | 41 ++++++++++++++++++++++++++++++ src/python/PythonPycParser.ts | 4 ++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/python/PythonPycParser.test.ts b/src/python/PythonPycParser.test.ts index e06ef1a274..db176068f1 100644 --- a/src/python/PythonPycParser.test.ts +++ b/src/python/PythonPycParser.test.ts @@ -1668,6 +1668,47 @@ it("hashes shallow TYPE_REF code chains without using the JavaScript stack", () }); }); +it("hashes slice-wrapped TYPE_REF code chains without using the JavaScript stack", () => { + const links = 3000; + const none = Buffer.from([0x4e]); + const codes = Array.from({ length: links }, (_, index) => + marshalCode("py311", { + flagged: true, + name: "f", + consts: + index === 0 + ? [none] + : [ + Buffer.concat([ + Buffer.from([0x3a]), + marshalRef(index - 1), + none, + none, + ]), + ], + }), + ); + const bytes = Buffer.concat([ + pythonPycHeader(3627), + marshalCode("py311", { + constsObject: Buffer.concat([ + Buffer.from([0x28]), + u32(links + 1), + ...codes, + Buffer.from([0x3c]), + u32(1), + marshalRef(links - 1), + ]), + }), + ]); + const report = parseWith(bytes, { max_objects: 65_536 }); + expect(root(report).consts.at(-1)).toEqual({ kind: "set", length: 1 }); + expect(report.coverage).toMatchObject({ + status: "complete", + code_objects: links + 1, + }); +}); + it("rejects code CPython treats as corrupt instead of complete coverage", () => { const file = ( layout: "py37" | "py38" | "py311", diff --git a/src/python/PythonPycParser.ts b/src/python/PythonPycParser.ts index d6b3d8cf7c..9da9d01aeb 100644 --- a/src/python/PythonPycParser.ts +++ b/src/python/PythonPycParser.ts @@ -1413,7 +1413,9 @@ class MarshalReader { malformed("Code object is missing equality material.") ).constsTuple, ] - : value.kind === "tuple" || value.kind === "frozenset" + : value.kind === "tuple" || + value.kind === "frozenset" || + value.kind === "slice" ? value.items : []; gray.add(identity);