Files
rea/tests/boundary/filesystem/flutterElfNotes.test.ts
RRFZ 572d0c2340 fix(flutter): honor ELF note alignment when reading build IDs (#6423)
* fix(flutter): honor ELF note alignment when reading build IDs

* test(flutter): key shared note ranges by effective alignment

Cover a program and section declaration that share one note range: differing effective alignments (8 and 4) must both be read, while declarations that both normalize to four bytes (1 and 4) still share one read. 24-byte build IDs pad identically at both alignments, so only the range key decides the read count.
2026-10-23 17:53:43 +08:00

164 lines
5.3 KiB
TypeScript

import { open, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, vi } from "vitest";
import { readElfBuildIds } from "../../../src/flutter/ElfBuildId.js";
import {
elfNote,
elfNoteFixture,
} from "../../../src/native/elf/ElfNotes.fixture.js";
import {
appendElfRanges,
engineFixtureImage,
} from "../../../src/flutter/FlutterBuild.fixture.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const note = Buffer.from(
"040000000800000003000000474e55000123456789abcdef",
"hex",
);
it.each([32, 64] as const)(
"reads aligned consecutive notes in ELF%d",
async (bits) => {
for (const alignment of [4, 8]) {
for (const table of ["program", "section", "both"] as const) {
const ids = ["11".repeat(20), "22".repeat(20)];
const image = elfNoteFixture({
bits,
alignment,
ownership: table === "program" ? "program" : "both",
notes: Buffer.concat(
ids.map((id) =>
elfNote("GNU", 3, Buffer.from(id, "hex"), true, alignment),
),
),
});
if (table === "section") image.writeUInt32LE(1, bits === 64 ? 64 : 52);
const root = await createTestTempDirectory("rea-flutter-alignment-");
const path = join(root, "engine.so");
await writeFile(path, image);
const file = await open(path, "r");
try {
expect(
await readElfBuildIds(file, image.length, undefined),
`${bits}/${table}/${alignment}`,
).toEqual({ candidates: ids, failures: [] });
} finally {
await file.close();
}
}
}
},
);
it.each(
([32, 64] as const).flatMap((bits) => [
{ bits, program: 8, section: 4, reads: 5 },
{ bits, program: 1, section: 4, reads: 4 },
]),
)(
"keys shared $bits-bit note ranges by effective alignment (p_align $program, sh_addralign $section)",
async ({ bits, program, section, reads }) => {
// 24-byte IDs pad identically at four and eight bytes, so only the range
// key decides whether the second declaration is read.
const ids = ["11".repeat(24), "22".repeat(24)];
const image = elfNoteFixture({
bits,
alignment: 4,
ownership: "both",
notes: Buffer.concat(
ids.map((id) => elfNote("GNU", 3, Buffer.from(id, "hex"))),
),
});
const header = bits === 64 ? 64 : 52;
const noteSection =
header + (bits === 64 ? 56 : 32) + (bits === 64 ? 64 : 40);
const writeAlignment = (offset: number, value: number) => {
if (bits === 64) image.writeBigUInt64LE(BigInt(value), offset);
else image.writeUInt32LE(value, offset);
};
writeAlignment(header + (bits === 64 ? 48 : 28), program);
writeAlignment(noteSection + (bits === 64 ? 48 : 32), section);
const root = await createTestTempDirectory("rea-flutter-alignment-");
const path = join(root, "engine.so");
await writeFile(path, image);
const file = await open(path, "r");
const read = vi.spyOn(file, "read");
try {
expect(await readElfBuildIds(file, image.length, undefined)).toEqual({
candidates: ids,
failures: [],
});
// Header prefix, both tables, then one read per distinct note range.
expect(read.mock.calls.length).toBe(reads);
} finally {
await file.close();
}
},
);
it.each([
{ bits: 32, table: "program" },
{ bits: 64, table: "section" },
] as const)(
"bounds dense $bits-bit $table note ranges while retaining examined IDs",
async ({ bits, table }) => {
const count = 30_000;
const image = appendElfRanges(
engineFixtureImage({
bits,
notes: Buffer.concat([note, Buffer.alloc(count)]),
ownership: "both",
}),
table,
Array.from({ length: count }, (_, index) => ({
type: table === "program" ? 4 : 7,
offset: 0x400 + note.length + index,
size: 1,
})),
);
const root = await createTestTempDirectory("rea-flutter-notes-");
const path = join(root, "engine.so");
await writeFile(path, image);
const file = await open(path, "r");
const read = vi.spyOn(file, "read");
try {
const result = await readElfBuildIds(file, image.length, undefined);
expect(result.candidates).toEqual(["0123456789abcdef"]);
expect(result.failures).toContainEqual({
reason: expect.stringContaining("note-range"),
});
expect(read.mock.calls.length).toBeLessThanOrEqual(1027);
} finally {
await file.close();
}
},
);
it("deduplicates shared program/section notes and skips empty ranges before charging inventory", async () => {
const ranges = Array.from({ length: 2000 }, (_, index) => ({
type: 7,
offset: index % 2 === 0 ? 0x400 : index,
size: index % 2 === 0 ? note.length : 0,
}));
const image = appendElfRanges(
engineFixtureImage({ notes: note, ownership: "both" }),
"section",
ranges,
);
const root = await createTestTempDirectory("rea-flutter-notes-");
const path = join(root, "engine.so");
await writeFile(path, image);
const file = await open(path, "r");
const read = vi.spyOn(file, "read");
try {
expect(await readElfBuildIds(file, image.length, undefined)).toEqual({
candidates: ["0123456789abcdef"],
failures: [],
});
expect(read.mock.calls.length).toBe(4);
} finally {
await file.close();
}
});