mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
refactor(providers): retire obsolete decoding paths
Remove the test-only Objective-C property facade that discarded completeness. Verify its remaining semantics through portable Mach-O metadata decoding, including retained partial properties and located issues. Decode the single IDA text result directly while preserving native JSON and structured-content precedence. Update the stale SDK deadline assertion to check the busy tool through its diagnostic owner.
This commit is contained in:
1 parent
e8b2273ab6
commit
1c07bee5b6
5 files changed
+127
-96
No files matched your search
@@ -335,6 +335,7 @@ it("bounds a stalled health poll by the remaining readiness deadline", async ()
|
||||
});
|
||||
if (!(failure instanceof AnalysisTimeoutError))
|
||||
throw new Error("Expected the readiness deadline to expire");
|
||||
expect(failure.userMessage).toContain("config_json_get");
|
||||
expect(failure.capturedOutput).toBeDefined();
|
||||
} finally {
|
||||
await connection.close();
|
||||
|
||||
@@ -126,12 +126,13 @@ export const decodeIdaToolResult = (
|
||||
);
|
||||
// Native MCP retains the complete JSON text while structuredContent may
|
||||
// contain a shortened preview. Parse that producer-owned representation once.
|
||||
const text = blocks.length === 1 ? blocks[0] : undefined;
|
||||
if (protocol === "native") {
|
||||
if (blocks.length !== 1)
|
||||
if (text === undefined)
|
||||
throw new AnalysisProtocolError(
|
||||
"Native IDA MCP omitted its complete JSON text result.",
|
||||
);
|
||||
return parseIdaJsonText(blocks[0] ?? "");
|
||||
return parseIdaJsonText(text);
|
||||
}
|
||||
if (result.structuredContent !== undefined) {
|
||||
const value = result.structuredContent;
|
||||
@@ -141,18 +142,15 @@ export const decodeIdaToolResult = (
|
||||
: value,
|
||||
);
|
||||
}
|
||||
if (blocks.length !== 1)
|
||||
if (text === undefined)
|
||||
throw new AnalysisProtocolError(
|
||||
"IDA MCP omitted a single structured or text result; unsupported content cannot establish an empty observation.",
|
||||
);
|
||||
const values = blocks.map((text): JsonValue => {
|
||||
try {
|
||||
return jsonValueSchema.parse(JSON.parse(text));
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
});
|
||||
return values[0] ?? null;
|
||||
try {
|
||||
return jsonValueSchema.parse(JSON.parse(text));
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
// Retain only producer-sanitized JSON diagnostics, never a native parser cause.
|
||||
|
||||
@@ -120,6 +120,123 @@ const fixture = (
|
||||
};
|
||||
const provenance = { path: "/fixture/app", sha256: "a".repeat(64) };
|
||||
|
||||
describe("decoded Objective-C property attributes", () => {
|
||||
it("retains quoted class names, ownership and accessors", () => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
machoWithPropertyAttributes(
|
||||
'T@"NSString<NSCopying, NSSecureCoding>",C,N,GcustomTitle,V_title',
|
||||
{ firstName: "title" },
|
||||
),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
expect(result.objc_classes[0]?.properties).toEqual(
|
||||
["title", "second"].map((name) => ({
|
||||
name,
|
||||
type_encoding: '@"NSString<NSCopying, NSSecureCoding>"',
|
||||
atomicity: "nonatomic",
|
||||
attributes: [
|
||||
{ name: "T", value: '@"NSString<NSCopying, NSSecureCoding>"' },
|
||||
{ name: "C", value: "" },
|
||||
{ name: "N", value: "" },
|
||||
{ name: "G", value: "customTitle" },
|
||||
{ name: "V", value: "_title" },
|
||||
],
|
||||
is_readonly: false,
|
||||
getter: "customTitle",
|
||||
setter: null,
|
||||
})),
|
||||
);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_properties_categories",
|
||||
),
|
||||
).toMatchObject({ status: "complete", reason: null });
|
||||
});
|
||||
|
||||
it.each([
|
||||
["Tq,R", "q", "atomic"],
|
||||
["Tq,R,N", "q", "nonatomic"],
|
||||
["R", null, null],
|
||||
] as const)(
|
||||
"preserves readonly flags and established atomicity for %s",
|
||||
(attributes, typeEncoding, atomicity) => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
machoWithPropertyAttributes(attributes),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
expect(result.objc_classes[0]?.properties).toMatchObject(
|
||||
["first", "second"].map((name) => ({
|
||||
name,
|
||||
type_encoding: typeEncoding,
|
||||
atomicity,
|
||||
is_readonly: true,
|
||||
})),
|
||||
);
|
||||
expect(
|
||||
result.coverage.find(
|
||||
({ facet }) => facet === "objc_properties_categories",
|
||||
),
|
||||
).toMatchObject({ status: "complete", reason: null });
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[
|
||||
"an unfinished quoted class name",
|
||||
'T@"NSString,N,R',
|
||||
'@"NSString,N,R',
|
||||
null,
|
||||
null,
|
||||
],
|
||||
[
|
||||
"flags before an unfinished quote",
|
||||
'N,R,T@"NSString',
|
||||
'@"NSString',
|
||||
"nonatomic",
|
||||
true,
|
||||
],
|
||||
[
|
||||
"an unfinished template argument",
|
||||
"T{vector<float, float,N,R",
|
||||
"{vector<float, float,N,R",
|
||||
null,
|
||||
null,
|
||||
],
|
||||
] as const)(
|
||||
"retains established flags and reports both properties with %s",
|
||||
(_, attributes, typeEncoding, atomicity, readonly) => {
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
machoWithPropertyAttributes(attributes),
|
||||
100,
|
||||
provenance,
|
||||
);
|
||||
expect(result.objc_classes[0]?.properties).toMatchObject(
|
||||
["first", "second"].map((name) => ({
|
||||
name,
|
||||
type_encoding: typeEncoding,
|
||||
atomicity,
|
||||
is_readonly: readonly,
|
||||
})),
|
||||
);
|
||||
const coverage = result.coverage.find(
|
||||
({ facet }) => facet === "objc_properties_categories",
|
||||
);
|
||||
expect(coverage).toMatchObject({ status: "partial" });
|
||||
expect(coverage?.reason).toContain(
|
||||
"property_attributes_incomplete at 0x100000508: Fixture: property first",
|
||||
);
|
||||
expect(coverage?.reason).toContain(
|
||||
"property_attributes_incomplete at 0x100000518: Fixture: property second",
|
||||
);
|
||||
expect(
|
||||
coverage?.reason?.match(/property_attributes_incomplete at /gu),
|
||||
).toHaveLength(2);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("retains Objective-C++ template commas in decoded property facts", () => {
|
||||
const attributes = "T{vector<float, float, float>=},N,V_position";
|
||||
const result = decodeAppleDispatchMetadata(
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
decodeChainedPointer,
|
||||
parsePointerFixups,
|
||||
} from "./AppleMachoFixups.js";
|
||||
import { parsePropertyAttributes } from "./AppleObjcProperties.js";
|
||||
import type { Segment } from "./AppleMachoSelection.js";
|
||||
|
||||
const BASE = 0x100000000n;
|
||||
@@ -1685,81 +1684,6 @@ describe("legacy bind operand decoding", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Objective-C property attributes", () => {
|
||||
it("parses type, ownership, accessors and quoted class names", () => {
|
||||
expect(
|
||||
parsePropertyAttributes(
|
||||
"title",
|
||||
'T@"NSString<NSCopying, NSSecureCoding>",C,N,GcustomTitle,V_title',
|
||||
),
|
||||
).toEqual({
|
||||
name: "title",
|
||||
type_encoding: '@"NSString<NSCopying, NSSecureCoding>"',
|
||||
atomicity: "nonatomic",
|
||||
attributes: [
|
||||
{ name: "T", value: '@"NSString<NSCopying, NSSecureCoding>"' },
|
||||
{ name: "C", value: "" },
|
||||
{ name: "N", value: "" },
|
||||
{ name: "G", value: "customTitle" },
|
||||
{ name: "V", value: "_title" },
|
||||
],
|
||||
is_readonly: false,
|
||||
getter: "customTitle",
|
||||
setter: null,
|
||||
});
|
||||
expect(parsePropertyAttributes("count", "Tq,R")).toMatchObject({
|
||||
type_encoding: "q",
|
||||
is_readonly: true,
|
||||
atomicity: "atomic",
|
||||
});
|
||||
expect(parsePropertyAttributes("count", "Tq,R,N").atomicity).toBe(
|
||||
"nonatomic",
|
||||
);
|
||||
expect(parsePropertyAttributes("count", "").atomicity).toBeNull();
|
||||
expect(parsePropertyAttributes("count", "R").atomicity).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps template argument commas inside an Objective-C++ type", () => {
|
||||
expect(
|
||||
parsePropertyAttributes(
|
||||
"position",
|
||||
"T{vector<float, float, float>=},N,V_position",
|
||||
),
|
||||
).toMatchObject({
|
||||
type_encoding: "{vector<float, float, float>=}",
|
||||
atomicity: "nonatomic",
|
||||
attributes: [
|
||||
{ name: "T", value: "{vector<float, float, float>=}" },
|
||||
{ name: "N", value: "" },
|
||||
{ name: "V", value: "_position" },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("establishes no defaults from an unfinished quoted class name", () => {
|
||||
// `,N,R` here lie inside the unterminated quote, so they are not flags.
|
||||
expect(parsePropertyAttributes("title", 'T@"NSString,N,R')).toMatchObject({
|
||||
type_encoding: '@"NSString,N,R',
|
||||
atomicity: null,
|
||||
is_readonly: null,
|
||||
});
|
||||
expect(parsePropertyAttributes("title", 'N,R,T@"NSString')).toMatchObject({
|
||||
atomicity: "nonatomic",
|
||||
is_readonly: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps defaults unknown when a template argument is unfinished", () => {
|
||||
expect(
|
||||
parsePropertyAttributes("position", "T{vector<float, float,N,R"),
|
||||
).toMatchObject({
|
||||
type_encoding: "{vector<float, float,N,R",
|
||||
atomicity: null,
|
||||
is_readonly: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves classic special ordinals and returns unsupported at invalid bind locations", () => {
|
||||
const parseOrdinal = (ordinal: number, dylibs: readonly string[]) => {
|
||||
const immediate = ordinal < 0 ? ordinal + 16 : ordinal;
|
||||
|
||||
@@ -12,15 +12,6 @@ export interface ObjcPropertyReaders {
|
||||
string(address: bigint): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an `@property` attribute string such as `T@"NSString",C,N,V_name`.
|
||||
* Each comma-separated token is one attribute; `T` is the type encoding.
|
||||
*/
|
||||
export const parsePropertyAttributes = (
|
||||
name: string,
|
||||
attributes: string,
|
||||
): ObjcProperty => decodePropertyAttributes(name, attributes).property;
|
||||
|
||||
/**
|
||||
* Decode one attribute string. An unfinished quoted or templated type leaves
|
||||
* later attributes ambiguous, so a missing `N` or `R` does not establish the
|
||||
|
||||
Reference in new issue
Block a user