mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 19:35:56 +00:00
Remove getter, enum, cloned-projection, cache-identity, and obsolete profile assertions along with the shadow JSON Schema engine and dead test-only APIs. Retain producer, filesystem, SDK, lifecycle, integrity, and malformed-format coverage. Document the priority of real end-to-end workflows, boundary integration, and producer goldens.
225 lines
7.1 KiB
TypeScript
225 lines
7.1 KiB
TypeScript
import { z } from "zod";
|
|
|
|
const addressSchema = z.string().regex(/^0x[0-9a-f]+$/iu);
|
|
const identitySchema = z.object({ address: addressSchema, name: z.string() });
|
|
/** Extract one real procedure address from the complete inline inventory. */
|
|
export const firstProcedureAddress = (input: unknown): string => {
|
|
const first = z
|
|
.array(z.object({ address: addressSchema }))
|
|
.min(1)
|
|
.parse(input)[0];
|
|
if (first === undefined) throw new TypeError("Procedure inventory was empty");
|
|
return first.address;
|
|
};
|
|
|
|
/** Reject empty and success-shaped embedded decompilation failures. */
|
|
export const requirePseudocode = (
|
|
input: unknown,
|
|
operation: string,
|
|
): string => {
|
|
if (typeof input !== "string" || input.trim().length === 0)
|
|
throw new TypeError(`${operation} returned empty pseudocode`);
|
|
if (input === "No output" || input.startsWith("Error:"))
|
|
throw new TypeError(`${operation} returned an embedded failure: ${input}`);
|
|
return input;
|
|
};
|
|
|
|
/** Validate a complete Hopper function dossier semantically. */
|
|
export const requireFunctionDossier = (
|
|
input: unknown,
|
|
expectedAddress: string,
|
|
): Record<string, unknown> => {
|
|
const value = objectValue(
|
|
input,
|
|
"analyze_function returned no function dossier",
|
|
);
|
|
const procedure = identitySchema.parse(value.procedure);
|
|
if (procedure.address !== expectedAddress)
|
|
throw new TypeError("analyze_function returned the wrong procedure");
|
|
if (procedure.name.trim().length === 0)
|
|
throw new TypeError("analyze_function omitted the procedure name");
|
|
requirePseudocode(value.pseudocode, "analyze_function");
|
|
for (const field of COLLECTION_FIELDS) list(value, field);
|
|
for (const field of ["callers", "callees"] as const)
|
|
z.array(identitySchema).parse(list(value, field));
|
|
validateBlocks(list(value, "basic_blocks"));
|
|
return value;
|
|
};
|
|
|
|
/** Positive semantic facts required from one source-owned Hopper fixture. */
|
|
export interface FunctionDossierOracle {
|
|
readonly procedure_address: string;
|
|
readonly callee_address?: string;
|
|
readonly caller_address?: string;
|
|
readonly referenced_string?: string;
|
|
readonly referenced_name?: string;
|
|
readonly comment?: string;
|
|
readonly require_cfg_successor?: boolean;
|
|
readonly require_assembly?: boolean;
|
|
}
|
|
|
|
/** Reject a structurally valid dossier that omits fixture-proven semantics. */
|
|
export const requireFunctionDossierOracle = (
|
|
input: unknown,
|
|
oracle: FunctionDossierOracle,
|
|
): Record<string, unknown> => {
|
|
const value = requireFunctionDossier(input, oracle.procedure_address);
|
|
if (oracle.callee_address !== undefined) {
|
|
const callees = z.array(identitySchema).parse(list(value, "callees"));
|
|
if (!callees.some(({ address }) => address === oracle.callee_address))
|
|
throw new TypeError(
|
|
"analyze_function omitted the expected fixture callee",
|
|
);
|
|
|
|
const references = z
|
|
.array(
|
|
z.object({
|
|
source_address: addressSchema,
|
|
target_address: addressSchema,
|
|
}),
|
|
)
|
|
.parse(list(value, "outgoing_references"));
|
|
if (
|
|
!references.some(
|
|
({ target_address }) => target_address === oracle.callee_address,
|
|
)
|
|
)
|
|
throw new TypeError(
|
|
"analyze_function omitted the expected fixture reference",
|
|
);
|
|
}
|
|
|
|
if (oracle.caller_address !== undefined) {
|
|
const callers = z.array(identitySchema).parse(list(value, "callers"));
|
|
if (!callers.some(({ address }) => address === oracle.caller_address))
|
|
throw new TypeError(
|
|
"analyze_function omitted the expected fixture caller",
|
|
);
|
|
}
|
|
|
|
if (oracle.referenced_string !== undefined)
|
|
requireReferencedValue(
|
|
value,
|
|
"referenced_strings",
|
|
oracle.referenced_string,
|
|
(candidate) => candidate,
|
|
);
|
|
|
|
if (oracle.referenced_name !== undefined)
|
|
requireReferencedValue(
|
|
value,
|
|
"referenced_names",
|
|
oracle.referenced_name,
|
|
(candidate) => candidate.replace(/^_+/u, ""),
|
|
);
|
|
|
|
if (oracle.comment !== undefined) {
|
|
const comments = z
|
|
.array(
|
|
z.object({
|
|
address: addressSchema,
|
|
kind: z.enum(["comment", "inline"]),
|
|
text: z.string(),
|
|
}),
|
|
)
|
|
.parse(list(value, "comments"));
|
|
if (
|
|
!comments.some(
|
|
({ address, kind, text }) =>
|
|
address === oracle.procedure_address &&
|
|
kind === "comment" &&
|
|
text === oracle.comment,
|
|
)
|
|
)
|
|
throw new TypeError("analyze_function omitted the verifier comment");
|
|
}
|
|
|
|
if (oracle.require_assembly === true && list(value, "assembly").length === 0)
|
|
throw new TypeError("analyze_function omitted fixture assembly");
|
|
|
|
if (oracle.require_cfg_successor) {
|
|
const blocks = z
|
|
.array(z.object({ successors: z.array(addressSchema) }))
|
|
.parse(list(value, "basic_blocks"));
|
|
if (!blocks.some(({ successors }) => successors.length > 0))
|
|
throw new TypeError("analyze_function omitted a real CFG successor");
|
|
}
|
|
return value;
|
|
};
|
|
|
|
const referencedValueSchema = z.object({
|
|
address: addressSchema,
|
|
value: z.string(),
|
|
source_address: addressSchema,
|
|
});
|
|
|
|
const referenceEdgeSchema = z.object({
|
|
source_address: addressSchema,
|
|
target_address: addressSchema,
|
|
});
|
|
|
|
const requireReferencedValue = (
|
|
dossier: Record<string, unknown>,
|
|
field: "referenced_strings" | "referenced_names",
|
|
expected: string,
|
|
normalize: (value: string) => string,
|
|
): void => {
|
|
const values = z.array(referencedValueSchema).parse(list(dossier, field));
|
|
const references = z
|
|
.array(referenceEdgeSchema)
|
|
.parse(list(dossier, "outgoing_references"));
|
|
const found = values.some(
|
|
({ address, value, source_address: sourceAddress }) =>
|
|
normalize(value) === expected &&
|
|
references.some(
|
|
({ source_address, target_address }) =>
|
|
source_address === sourceAddress && target_address === address,
|
|
),
|
|
);
|
|
if (!found)
|
|
throw new TypeError(
|
|
`analyze_function omitted the expected fixture ${field === "referenced_strings" ? "string" : "name"}`,
|
|
);
|
|
};
|
|
|
|
/** Require every relationship to be an actual hexadecimal address. */
|
|
export const requireAddressArray = (
|
|
input: unknown,
|
|
operation: string,
|
|
): string[] => {
|
|
const parsed = z.array(addressSchema).parse(input);
|
|
if (parsed.some((address) => address.length === 0))
|
|
throw new TypeError(`${operation} returned an invalid address list`);
|
|
return parsed;
|
|
};
|
|
|
|
const COLLECTION_FIELDS = [
|
|
"assembly",
|
|
"comments",
|
|
"callers",
|
|
"callees",
|
|
"incoming_references",
|
|
"outgoing_references",
|
|
"referenced_strings",
|
|
"referenced_names",
|
|
"basic_blocks",
|
|
] as const;
|
|
|
|
const list = (value: Record<string, unknown>, field: string): unknown[] =>
|
|
z.array(z.unknown()).parse(value[field]);
|
|
|
|
const validateBlocks = (items: readonly unknown[]): void => {
|
|
for (const block of items)
|
|
if (!Array.isArray(objectValue(block, "Invalid basic block").successors))
|
|
throw new TypeError("analyze_function omitted CFG successor evidence");
|
|
};
|
|
|
|
const objectValue = (
|
|
input: unknown,
|
|
message: string,
|
|
): Record<string, unknown> => {
|
|
const parsed = z.record(z.string(), z.unknown()).safeParse(input);
|
|
if (!parsed.success) throw new TypeError(message);
|
|
return parsed.data;
|
|
};
|