mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(managed): retain raw CIL changes in member comparison (#2912)
This commit is contained in:
committed by
GitHub
parent
4758ba30b6
commit
a9195c3a98
@@ -209,9 +209,11 @@ native/runtime questions retain their own authority and coverage.
|
||||
Names and semantic labels do not establish durable member identity. Comparison
|
||||
preserves observed differences, structural inferences, and unknowns separately.
|
||||
|
||||
Cross-version matching first prefers exact CIL/signature identity. When that
|
||||
does not match, it pairs an exact declared type, method name, and raw signature
|
||||
before trying structural body shape. The exact-signature key uses names only
|
||||
Cross-version matching first prefers exact raw CIL/signature identity, with
|
||||
equal decoded fingerprints. Equal decoded fingerprints alone cannot establish
|
||||
an exact match: they omit switch targets and collapse distinct NaN encodings.
|
||||
When that does not match, it pairs an exact declared type, method name, and raw
|
||||
signature before trying structural body shape. The exact-signature key uses names only
|
||||
as part of that full tuple; names alone never select a pair. Duplicate tuples
|
||||
remain ambiguous rather than being paired by token order. Fields follow the
|
||||
same exact tiers. The raw signature is exact whether or not REA decoded it, so
|
||||
@@ -222,8 +224,11 @@ unpaired member, one-sided or ambiguous, with the same declared type and name
|
||||
whose signature was not decoded.
|
||||
|
||||
For a matched method, an unavailable or partial body makes body-shape facets
|
||||
unknown while preserving observed signature differences. Structural identity
|
||||
compares normalized signatures, the limited decoded-CIL tuple fingerprint,
|
||||
unknown while preserving observed signature differences. For complete bodies,
|
||||
a difference in either the raw CIL digest or the decoded fingerprint is reported
|
||||
in the `cil` dimension, including reordered switch targets with equal decoded
|
||||
fingerprints. Structural identity compares normalized signatures, the limited
|
||||
decoded-CIL tuple fingerprint,
|
||||
constants, and bounded shape context. It reports all candidates at the winning
|
||||
score and remains ambiguous when the evidence does not distinguish them. The
|
||||
digest does not itself remap metadata tokens.
|
||||
@@ -360,8 +365,8 @@ Opcode names retain short/long forms and prefix opcodes. This makes the value a
|
||||
reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity:
|
||||
it does not resolve tokens across MVIDs, does not commit full control flow or
|
||||
exception semantics, and does not replace the raw CIL digest. Cross-build
|
||||
comparison uses the value only as one unique-only structural tier and reports
|
||||
ambiguity when that evidence is insufficient.
|
||||
comparison requires equal raw CIL digests as well for exact CIL/signature
|
||||
matches and reports ambiguity when structural evidence is insufficient.
|
||||
|
||||
For example, a decoded `nop; ret` body serializes exactly as
|
||||
`[["nop","none",null],["ret","none",null]]` and has digest
|
||||
|
||||
@@ -73,7 +73,11 @@ const methodDimensions = (
|
||||
if (bothAbsent) return { dimensions, bodyUnknown: false };
|
||||
if (!leftBodyComplete || !rightBodyComplete)
|
||||
return { dimensions, bodyUnknown: true };
|
||||
if (left.body.normalized_il_sha256 !== right.body.normalized_il_sha256)
|
||||
// The decoded fingerprint omits switch targets and is not exact CIL identity.
|
||||
if (
|
||||
left.body.il_sha256 !== right.body.il_sha256 ||
|
||||
left.body.normalized_il_sha256 !== right.body.normalized_il_sha256
|
||||
)
|
||||
dimensions.push("cil");
|
||||
if (
|
||||
canonicalize(left.body.opcode_counts) !==
|
||||
|
||||
@@ -49,10 +49,13 @@ interface Ambiguous<Item> {
|
||||
const keyMethod = (item: Method): Keyed<Method> => ({
|
||||
item,
|
||||
exactKey:
|
||||
item.body.status === "present" && item.body.normalized_il_sha256 !== null
|
||||
item.body.status === "present" &&
|
||||
item.body.il_sha256 !== null &&
|
||||
item.body.normalized_il_sha256 !== null
|
||||
? sha256([
|
||||
"method-exact",
|
||||
item.signature.raw_sha256,
|
||||
item.body.il_sha256,
|
||||
item.body.normalized_il_sha256,
|
||||
])
|
||||
: null,
|
||||
|
||||
@@ -116,6 +116,63 @@ describe("managed member comparison", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed member comparison with equal decoded fingerprints", () => {
|
||||
it.each([
|
||||
{ name: "Main", basis: "exact-signature" },
|
||||
{ name: "Renamed", basis: "structural-method-shape" },
|
||||
])("detects reordered switch targets in $name", ({ name, basis }) => {
|
||||
const observe = (targets: readonly number[], methodName: string) => {
|
||||
// ldarg.0; switch (case 0, case 1); ldc.i4.0; ret; ldc.i4.1; ret.
|
||||
// Both cases target instruction starts, relative to the end of the table.
|
||||
const il = Buffer.alloc(18);
|
||||
il[0] = 0x02;
|
||||
il[1] = 0x45;
|
||||
il.writeUInt32LE(2, 2);
|
||||
targets.forEach((target, index) =>
|
||||
il.writeInt32LE(target, 6 + index * 4),
|
||||
);
|
||||
Buffer.from([0x16, 0x2a, 0x17, 0x2a]).copy(il, 14);
|
||||
return inspect(
|
||||
buildManagedPeFixture({
|
||||
methodName,
|
||||
methodSignature: Buffer.from([0x00, 0x01, 0x08, 0x08]),
|
||||
ilBody: Buffer.concat([Buffer.from([(il.length << 2) | 2]), il]),
|
||||
}),
|
||||
`/tmp/${methodName}-${targets.join("-")}.dll`,
|
||||
);
|
||||
};
|
||||
const left = observe([0, 2], "Main");
|
||||
const right = observe([2, 0], name);
|
||||
const leftBody = left.result.methods[0]?.body;
|
||||
const rightBody = right.result.methods[0]?.body;
|
||||
expect(leftBody?.status).toBe("present");
|
||||
expect(rightBody?.status).toBe("present");
|
||||
expect(leftBody?.normalized_il_sha256).toBe(
|
||||
rightBody?.normalized_il_sha256,
|
||||
);
|
||||
expect(leftBody?.il_sha256).not.toBe(rightBody?.il_sha256);
|
||||
|
||||
const result = compareManagedMembers(left, right);
|
||||
expect(result.matching.exact_il_signature).toBe(0);
|
||||
expect(result.methods[0]).toMatchObject({
|
||||
status: "changed",
|
||||
match: { status: "matched", basis },
|
||||
dimensions: ["cil"],
|
||||
});
|
||||
expect(
|
||||
compareManagedMembers(right, observe([2, 0], name)).methods[0],
|
||||
).toMatchObject({
|
||||
status: "unchanged",
|
||||
match: {
|
||||
status: "matched",
|
||||
basis: "exact-il-signature",
|
||||
confidence: "exact",
|
||||
},
|
||||
dimensions: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed member comparison floating-point constants", () => {
|
||||
it.each([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user