fix(fls): Gnutella's peer limit is random in Java, not a default
PeerToPeerAnwendung's constructor rolls its peer limit (3, 4 or 5). The
getter defaults measured it once each run — 4, then 5 — and c48d15d
committed 5 as a default. GetterDefaults now constructs every class 64
times and writes a value that varies as {"random": [...]}; the decoder
answers a reference to it as unresolved, and the topology layer reports
a peer limit the file does not set as null.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -217,15 +217,30 @@ export function decodeXmlBytes(bytes: Uint8Array): string {
|
||||
// ── Decoder ────────────────────────────────────────────────────────────────
|
||||
|
||||
const PRIMITIVE_ID_PREFIXES = new Set(['Integer', 'Boolean', 'Long', 'Short', 'Byte', 'Float', 'Double', 'Character']);
|
||||
const DEFAULTS = javaDefaults as Readonly<Record<string, string | number | boolean | null>>;
|
||||
type MeasuredValue = string | number | boolean | null;
|
||||
/** A value a constructor rolls at random: measured as the set of values seen, never a default. */
|
||||
type RandomValue = { random: string[] };
|
||||
const MEASURED = javaDefaults as Readonly<Record<string, MeasuredValue | RandomValue>>;
|
||||
|
||||
/** The measured constant under `key`; undefined if nothing was measured or Java rolls it. */
|
||||
function measuredConstant(key: string): MeasuredValue | undefined {
|
||||
if (!Object.hasOwn(MEASURED, key)) return undefined;
|
||||
const value = MEASURED[key]!;
|
||||
return typeof value === 'object' && value !== null ? undefined : value;
|
||||
}
|
||||
|
||||
function isRandomInJava(key: string): boolean {
|
||||
const value = MEASURED[key];
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* What a getter returns on a freshly constructed object of `simpleClass`, as
|
||||
* measured (javaDefaults.json) — undefined where nothing was measured.
|
||||
* measured (javaDefaults.json) — undefined where nothing was measured or
|
||||
* where Java's constructor rolls the value.
|
||||
*/
|
||||
export function javaDefault(simpleClass: string, property: string): string | number | boolean | null | undefined {
|
||||
const key = `${simpleClass}#${property}`;
|
||||
return Object.hasOwn(DEFAULTS, key) ? DEFAULTS[key] : undefined;
|
||||
return measuredConstant(`${simpleClass}#${property}`);
|
||||
}
|
||||
|
||||
/** `getFoo`/`isFoo` → `foo`, as java.beans.Introspector derives it. */
|
||||
@@ -356,7 +371,8 @@ class Decoder {
|
||||
// A static constant: XMLEncoder writes a shared Integer 0 as
|
||||
// SwingConstants.CENTER when that was the first 0 it met.
|
||||
const constantKey = `${simpleClassName(className)}.${attributes.field}`;
|
||||
if (Object.hasOwn(DEFAULTS, constantKey)) return this.primitive(element, DEFAULTS[constantKey]!);
|
||||
const constant = measuredConstant(constantKey);
|
||||
if (constant !== undefined) return this.primitive(element, constant);
|
||||
}
|
||||
const args = element.children.filter((child) => child.name !== 'void');
|
||||
const object = newObject({
|
||||
@@ -457,7 +473,9 @@ class Decoder {
|
||||
}
|
||||
if (target.properties.has(property)) return target.properties.get(property)!;
|
||||
const defaultKey = `${simpleClassName(target.className)}#${property}`;
|
||||
if (Object.hasOwn(DEFAULTS, defaultKey)) return DEFAULTS[defaultKey]!;
|
||||
const measured = measuredConstant(defaultKey);
|
||||
if (measured !== undefined) return measured;
|
||||
if (isRandomInJava(defaultKey)) return { kind: 'unresolved', reason: `${defaultKey} is random in Java` };
|
||||
const prefix = id?.replace(/\d+$/, '');
|
||||
if (prefix !== undefined && PRIMITIVE_ID_PREFIXES.has(prefix)) {
|
||||
return { kind: 'unresolved', reason: `no measured default for ${defaultKey}` };
|
||||
|
||||
Reference in New Issue
Block a user