mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
fix(ghidra): reject address names owned by another symbol
Check function and label ownership in the existing namespace before renaming. Keep same-address renames idempotent and reject duplicate batch members inside their individual transactions. Extend real-provider namespace checks for single and batch collisions without conflating function and label fixture names. Validation: real Ghidra 12.1.4 host-native verification passed on macOS ARM64 with JDK 21.0.12.1, including public CLI/stdIO MCP, qualified-name round trips, rollback, source-byte immutability, and complete cleanup. Windows mutation authority remains unchanged. Fixes #1649
This commit is contained in:
@@ -62,6 +62,7 @@ export async function verifyGhidraNamespaceAnnotations({
|
||||
assert.ok(symbol, `Missing demangled fixture global ${namespace}::value`);
|
||||
return { namespace, address: symbol.address };
|
||||
});
|
||||
await verifyDuplicateAddressNames(call, procedures, names);
|
||||
const ambiguous = await reject("procedure_address", { procedure: "same" });
|
||||
assert.equal(ambiguous.code, "invalid_request");
|
||||
for (const { address } of entries)
|
||||
@@ -133,7 +134,8 @@ export async function verifyGhidraNamespaceAnnotations({
|
||||
});
|
||||
assert.deepEqual(unchanged.annotations, qualified.annotations);
|
||||
}
|
||||
await verifyAddressNames(call, [entries[0], entries[2], ...globals]);
|
||||
await verifyAddressNames(call, [entries[0], entries[2]], "function");
|
||||
await verifyAddressNames(call, globals, "label");
|
||||
for (const { namespace, address } of entries) {
|
||||
assert.equal(
|
||||
await call("set_address_name", {
|
||||
@@ -177,24 +179,107 @@ export async function verifyGhidraNamespaceAnnotations({
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyAddressNames(call, symbols) {
|
||||
for (const { namespace, address } of symbols) {
|
||||
async function verifyDuplicateAddressNames(call, procedures, names) {
|
||||
const functionEntries = [
|
||||
"rea_duplicate_function_a",
|
||||
"rea_duplicate_function_b",
|
||||
].map((name) => {
|
||||
const procedure = procedures.find(
|
||||
({ value }) => value === name || value === `_${name}`,
|
||||
);
|
||||
assert.ok(procedure, `Missing duplicate-name fixture function ${name}`);
|
||||
return { address: procedure.address, original: procedure.value };
|
||||
});
|
||||
const labelEntries = ["rea_duplicate_label_a", "rea_duplicate_label_b"].map(
|
||||
(name) => {
|
||||
const symbol = names.find(
|
||||
(item) =>
|
||||
(item.value === name || item.value === `_${name}`) &&
|
||||
item.symbol.primary,
|
||||
);
|
||||
assert.ok(symbol, `Missing duplicate-name fixture label ${name}`);
|
||||
return { address: symbol.address, original: symbol.value };
|
||||
},
|
||||
);
|
||||
|
||||
for (const [kind, entries] of [
|
||||
["function", functionEntries],
|
||||
["label", labelEntries],
|
||||
]) {
|
||||
const [owner, duplicate] = entries;
|
||||
const name = `rea_duplicate_${kind}`;
|
||||
assert.equal(
|
||||
await call("set_address_name", { address, name: "address_renamed" }),
|
||||
await call("set_address_name", { address: owner.address, name }),
|
||||
true,
|
||||
);
|
||||
const qualified = `${namespace}::address_qualified`;
|
||||
assert.equal(
|
||||
await call("set_address_name", { address: owner.address, name }),
|
||||
true,
|
||||
"Renaming the current owner to its existing name remains idempotent",
|
||||
);
|
||||
assert.equal(
|
||||
await call("set_address_name", { address: duplicate.address, name }),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
await call("address_name", { address: duplicate.address }),
|
||||
duplicate.original,
|
||||
"A rejected duplicate leaves the other address unchanged",
|
||||
);
|
||||
|
||||
const batchName = `rea_batch_duplicate_${kind}`;
|
||||
assert.deepEqual(
|
||||
await call("set_addresses_names", {
|
||||
names: {
|
||||
[owner.address]: batchName,
|
||||
[duplicate.address]: batchName,
|
||||
},
|
||||
}),
|
||||
{ [owner.address]: true, [duplicate.address]: false },
|
||||
);
|
||||
assert.equal(
|
||||
await call("address_name", { address: owner.address }),
|
||||
batchName,
|
||||
);
|
||||
assert.equal(
|
||||
await call("address_name", { address: duplicate.address }),
|
||||
duplicate.original,
|
||||
"A failed batch member rolls back only its own address",
|
||||
);
|
||||
const owners = (await call("list_names")).filter(
|
||||
(item) => item.value === batchName && item.symbol.primary,
|
||||
);
|
||||
assert.deepEqual(
|
||||
owners.map(({ address }) => address),
|
||||
[owner.address],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyAddressNames(call, symbols, kind) {
|
||||
for (const { namespace, address } of symbols) {
|
||||
assert.equal(
|
||||
await call("set_address_name", {
|
||||
address,
|
||||
name: `address_renamed_${kind}`,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
const qualified = `${namespace}::address_qualified_${kind}`;
|
||||
assert.equal(
|
||||
await call("set_address_name", { address, name: qualified }),
|
||||
true,
|
||||
);
|
||||
assert.equal(await call("address_name", { address }), qualified);
|
||||
assert.equal(
|
||||
await call("set_address_name", { address, name: "other::literal" }),
|
||||
await call("set_address_name", {
|
||||
address,
|
||||
name: `other::literal_${kind}`,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
const literal = await call("address_name", { address });
|
||||
assert.equal(literal, `${namespace}::other::literal`);
|
||||
assert.equal(literal, `${namespace}::other::literal_${kind}`);
|
||||
assert.equal(
|
||||
await call("set_address_name", { address, name: literal }),
|
||||
true,
|
||||
@@ -235,7 +320,7 @@ async function verifyAddressNames(call, symbols) {
|
||||
assert.deepEqual(
|
||||
await call("set_addresses_names", {
|
||||
names: {
|
||||
[valid.address]: `${valid.namespace}::batch_renamed`,
|
||||
[valid.address]: `${valid.namespace}::batch_renamed_${kind}`,
|
||||
[invalid.address]: `${invalid.namespace}::`,
|
||||
},
|
||||
}),
|
||||
@@ -243,18 +328,18 @@ async function verifyAddressNames(call, symbols) {
|
||||
);
|
||||
assert.equal(
|
||||
await call("address_name", { address: valid.address }),
|
||||
`${valid.namespace}::batch_renamed`,
|
||||
`${valid.namespace}::batch_renamed_${kind}`,
|
||||
);
|
||||
assert.equal(
|
||||
await call("address_name", { address: invalid.address }),
|
||||
`${invalid.namespace}::address_qualified`,
|
||||
`${invalid.namespace}::address_qualified_${kind}`,
|
||||
);
|
||||
const names = await call("list_names");
|
||||
for (const { namespace, address } of symbols) {
|
||||
const expected =
|
||||
address === valid.address
|
||||
? `${namespace}::batch_renamed`
|
||||
: `${namespace}::address_qualified`;
|
||||
? `${namespace}::batch_renamed_${kind}`
|
||||
: `${namespace}::address_qualified_${kind}`;
|
||||
assert.ok(
|
||||
names.some(
|
||||
(item) =>
|
||||
|
||||
Reference in New Issue
Block a user