fix(python): preserve signed code-object line numbers

Fixes #6490
This commit is contained in:
morluto
2026-10-23 16:30:49 +08:00
parent e44899d510
commit b58aa63796
7 changed files with 127 additions and 9 deletions
+6 -1
View File
@@ -8,6 +8,11 @@ const u32 = (value: number): Buffer => {
bytes.writeUInt32LE(value);
return bytes;
};
const i32 = (value: number): Buffer => {
const bytes = Buffer.alloc(4);
bytes.writeInt32LE(value);
return bytes;
};
const concat = (...parts: Buffer[]): Buffer => Buffer.concat(parts);
/** 16-byte CPython 3.7+ pyc header. Hash-based pycs store an 8-byte source hash. */
@@ -136,7 +141,7 @@ export const marshalCode = (
marshalShortAscii(fields.qualname ?? fields.name ?? "<module>"),
);
parts.push(
u32(fields.firstlineno ?? 1),
i32(fields.firstlineno ?? 1),
marshalBytes(fields.linetable ?? Buffer.alloc(0)),
);
if (layout === "py311")
+40
View File
@@ -72,6 +72,46 @@ it("reads 3.7, 3.10 and 3.12 layouts including nested code", () => {
);
});
it("preserves signed co_firstlineno in code objects and code constants", () => {
const nested = marshalCode("py311", {
name: "inner",
firstlineno: -7,
});
const bytes = Buffer.concat([
pythonPycHeader(3531),
marshalCode("py311", {
firstlineno: -1,
consts: [nested],
}),
]);
const report = parse(bytes);
expect(root(report).firstlineno).toBe(-1);
expect(root(report).consts).toContainEqual({
kind: "code",
name: "inner",
firstlineno: -7,
});
expect(pythonPycSchema.safeParse(report).success).toBe(true);
expect(
pythonPycSchema.safeParse({
...report,
code_objects: [
{ ...root(report), firstlineno: -0x8000_0001 },
...report.code_objects.slice(1),
],
}).success,
).toBe(false);
expect(
pythonPycSchema.safeParse({
...report,
code_objects: [
{ ...root(report), firstlineno: 0x8000_0000 },
...report.code_objects.slice(1),
],
}).success,
).toBe(false);
});
it("decodes a hash-based header and a FLAG_REF top-level code object", () => {
const bytes = pythonPycFixture("py311", {
flaggedCode: true,
+1 -1
View File
@@ -457,7 +457,7 @@ class MarshalReader {
this.layout === "py311"
? asString(this.readObject(depth + 1), "co_qualname")
: null;
const firstlineno = this.u32();
const firstlineno = this.i32();
const linetable = asBytes(this.readObject(depth + 1), "co_linetable");
const exceptiontable =
this.layout === "py311"