fix(native): retain NativeAOT frozen scan budget stops (#10094)

This commit is contained in:
morluto authored and GitHub committed 2026-11-11 18:34:39 +08:00
1 parent 2d80427422
commit d36ba58971
3 files changed
+49 -5

No files matched your search

@@ -371,3 +371,30 @@ it("retains the index memory refusal after successful hydration", () => {
expect(report.reason).toMatchObject({ code: "system_object_root_unproven" });
expect(report.coverage.truncation_reason).toBe("working-memory-budget");
});
it("reports a frozen-string scan stopped during a budgeted image read", () => {
const bytes = nativeAotPeFixture();
const sha = nativeAotPeDigest(bytes);
const limits = {
sourceBytes: bytes.length,
workUnits: 3260,
workingMemoryBytes: 64 * 1024,
};
const complete = parseNativeAotPe(bytes, sha, REPORT_BUDGET).summary;
expect(complete.status).toBe("complete");
expect(complete.frozen_strings).not.toHaveLength(0);
const partial = parseNativeAotPe(
bytes,
sha,
REPORT_BUDGET,
undefined,
limits,
).summary;
expect(partial).toMatchObject({
status: "partial",
truncated: true,
reason: { code: "truncated", truncation_reason: "work-unit-budget" },
coverage: { truncation_reason: "work-unit-budget" },
});
expect(partial.frozen_strings).toEqual(complete.frozen_strings);
});
+21 -4
View File
@@ -30,7 +30,7 @@ export const NATIVE_AOT_MAX_WORK_UNITS = 64 * 1024 * 1024;
export const NATIVE_AOT_MAX_WORKING_MEMORY_BYTES = 64 * 1024 * 1024;
/** Version of the built-in read-only PE metadata interpretation in profiles. */
export const NATIVE_AOT_PROFILE_CONTRACT_REVISION =
"rtr-9.1-x64-pe-read-only-v9";
"rtr-9.1-x64-pe-read-only-v10";
export type NativeAotParserLimits = {
readonly sourceBytes: number;
@@ -1706,14 +1706,27 @@ export const parseNativeAotPe = (
frozenScanTruncated = true;
break;
}
frozenScanBytesExamined += 8;
const table = readU64(image.read(address, 8, budget));
if (table === null || !isRecoveredStringTable(table)) {
if (budget.truncationReason !== undefined) {
frozenScanTruncated = true;
break;
}
frozenScanBytesExamined += 8;
const recovered = table !== null && isRecoveredStringTable(table);
if (budget.truncationReason !== undefined) {
frozenScanTruncated = true;
break;
}
if (!recovered) {
address += 8n;
continue;
}
frozenCandidates++;
const length = readU32(image.read(address + 8n, 4, budget));
if (budget.truncationReason !== undefined) {
frozenScanTruncated = true;
break;
}
if (length === null || address + 14n + BigInt(length) * 2n > frozen.end) {
address += 8n;
continue;
@@ -1723,12 +1736,16 @@ export const parseNativeAotPe = (
frozenScanTruncated = true;
break;
}
frozenStringDataBytesExamined += stringBytes;
// NativeAOT's System.String layout is the object header, 32-bit length,
// UTF-16 payload, then the terminating code unit. The payload starts at
// +12, not +16 (the latter silently shifts every decoded string by two
// characters and includes adjacent object padding).
const raw = image.read(address + 12n, stringBytes, budget);
if (budget.truncationReason !== undefined) {
frozenScanTruncated = true;
break;
}
frozenStringDataBytesExamined += stringBytes;
if (raw === null || raw.readUInt16LE(length * 2) !== 0) {
address += 8n;
continue;
+1 -1
View File
@@ -742,7 +742,7 @@ describe("Ghidra platform support", () => {
executable_role: target.executableRole,
managed: target.managed,
native_aot_metadata: {
contract_revision: "rtr-9.1-x64-pe-read-only-v9",
contract_revision: "rtr-9.1-x64-pe-read-only-v10",
source_bytes: 128 * 1024 * 1024,
work_units: 64 * 1024 * 1024,
working_memory_bytes: 64 * 1024 * 1024,