mirror of
https://github.com/morluto/rea.git
synced 2026-11-14 10:43:30 +00:00
* fix(managed): charge retained member facts instead of decoding bounds inspect_managed_members returned no types or methods for ordinary assemblies: Newtonsoft.Json 13.0.3 (netstandard2.0, 705 KB) exceeded the 64 MiB representation budget. Each method body reserved 256 bytes per IL byte and each signature 256 bytes per blob byte before decoding, and those worst-case bounds were never returned, so the budget scaled with total IL size rather than with the retained output. The final projection admission also charged every decoded string at six bytes per character and every scalar at 32 bytes, about eight times its JSON size. Bound IL, exception-clause and signature decoding with a transient admission that does not consume capacity, then reserve the decoded body or signature that is actually retained. Projection admission now charges the exact UTF-8 JSON bytes of decoded strings, keys and scalars; escaped and multibyte text still counts at its encoded size. Newtonsoft.Json now yields 496 types, 4185 methods and 18846 call edges. Assemblies whose retained facts exceed the budget, such as the 2.3 MB System.Private.ServiceModel 4.7.0, remain unavailable. * fix(managed): bound concurrent decoding and count JSON bytes in place Review follow-up. A method's IL bound and its exception-section bounds were each checked against the same remaining capacity although their bookkeeping is live at the same time, so 40 MiB of IL plus 40 MiB of exception clauses passed a 64 MiB budget. Transient admissions now accumulate until the method body or signature is built, and reserve respects bounds that are still live. Projection admission also serialized each decoded string to measure it, which could allocate about six times a near-limit string before the check. It now counts the exact escaped UTF-8 bytes without building the JSON text. * fix(managed): admit derived call and field edges as they are built Review follow-up. Released IL bounds no longer stand in for the call and field-access records that edges() derives from anchors. Each record is now admitted before the graph grows further, so a crafted assembly cannot materialize the whole derived graph before the representation guard fails. * fix(managed): admit derived caller names before building them Review follow-up. The caller name of call and field-access records was interpolated per anchor before any admission saw it. It is now built once per method, after its exact JSON size, counted from the type and member names, has been admitted.
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
admitManagedProjection,
|
|
ManagedDecodeBudget,
|
|
} from "./ManagedDecodeBudget.js";
|
|
import { ManagedReaderFailure } from "./ManagedReaderFailure.js";
|
|
|
|
describe("managed projection admission", () => {
|
|
it("charges decoded strings by their JSON bytes", () => {
|
|
const name = "a".repeat(12 * 1024 * 1024);
|
|
|
|
expect(admitManagedProjection({ name })).toEqual({ name });
|
|
});
|
|
|
|
it("charges escaped and multibyte characters at their encoded size", () => {
|
|
expect(() =>
|
|
admitManagedProjection({ name: "\u0001".repeat(12 * 1024 * 1024) }),
|
|
).toThrow(ManagedReaderFailure);
|
|
expect(() =>
|
|
admitManagedProjection({ name: "€".repeat(23 * 1024 * 1024) }),
|
|
).toThrow(ManagedReaderFailure);
|
|
});
|
|
|
|
it("counts escaped JSON bytes exactly at the budget boundary", () => {
|
|
// Control escapes, quotes, three-byte text, surrogate pairs and lone
|
|
// surrogates each have a distinct JSON size; fill the rest with ASCII.
|
|
const mixed = "\u0001".repeat(4096) + '"\\' + "€😀\ud800\udc00\ud800";
|
|
const used = Buffer.byteLength(JSON.stringify(mixed));
|
|
const filled = (extra: number) =>
|
|
mixed + "a".repeat(64 * 1024 * 1024 - used + extra);
|
|
|
|
expect(() => admitManagedProjection(filled(0))).not.toThrow();
|
|
expect(() => admitManagedProjection(filled(1))).toThrow(
|
|
ManagedReaderFailure,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("managed decoding bounds", () => {
|
|
it("keeps concurrent decoding bounds within one budget until released", () => {
|
|
const budget = new ManagedDecodeBudget();
|
|
const bound = 40 * 1024 * 1024;
|
|
|
|
budget.admitTransient(bound, null);
|
|
expect(() => budget.admitTransient(bound, null)).toThrow(
|
|
ManagedReaderFailure,
|
|
);
|
|
expect(() => budget.reserve(bound, null)).toThrow(ManagedReaderFailure);
|
|
budget.releaseTransient();
|
|
expect(() => budget.admitTransient(bound, null)).not.toThrow();
|
|
});
|
|
});
|