Files
reverse-engineering-anything/scripts/verify/android/metadata.mjs
T
morlutoandbalkan16 742b6f587f fix(android): preserve native and abstract body absence
Freeze body-absence access flags with stable parsed method identity, before
code generation can rewrite method metadata. Skip source slicing for these
methods and return unavailable empty source with explicit native/abstract
markers, rather than attributing neighbouring members to the selected method.

Adapt the access-flag guard and tiny Smali fixtures from PR #3527. Extend real
metadata and public CLI/MCP regressions for native, static native, abstract,
concrete overloads and re-reading frozen facts after neighbouring generation.
Rename the expanded method-body verifier and remove its old entry point.

Validation: 72 focused tests in 3 files; real pinned-JAR baseline control
fails and candidate passes without triggering code generation; 6 real CLI
and 6 MCP fixture calls with parity, identity and unchanged APK; docs/static
checks and two source reviews. Six measured real MCP workflows, no speedup
claim. Tiny authored APK only; reported BinaryEye, Linux/Windows and browser
workflows unverified. Heavy profiling/stress remain stopped.

Fixes #3488

Co-authored-by: balkan16 <57933796+balkan16@users.noreply.github.com>
2026-10-17 23:53:23 +08:00

130 lines
3.5 KiB
JavaScript

import assert from "node:assert/strict";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { delimiter, join, resolve } from "node:path";
import { verifyAndroidMethodBodies } from "./method-bodies.mjs";
import { verifyAndroidDirectInvokes } from "./direct-invokes.mjs";
/** Prove cold metadata stays unmaterialized and overload identities survive code generation. */
export const verifyAndroidMetadata = async ({
java,
jar,
apk,
bridge,
repository,
execute,
entrypoint,
environment,
jvmArguments = [],
}) => {
const javac =
process.env.JAVA_HOME === undefined
? "javac"
: resolve(
process.env.JAVA_HOME,
"bin",
process.platform === "win32" ? "javac.exe" : "javac",
);
try {
await execute(javac, ["-version"], { timeout: 10_000 });
} catch (cause) {
throw new Error(
"verify:android metadata requires the full selected JDK, including javac",
{ cause },
);
}
const classes = await mkdtemp(join(tmpdir(), "rea-android-metadata-"));
try {
await execute(
javac,
[
...jvmArguments.map((argument) => `-J${argument}`),
"-classpath",
jar,
"-d",
classes,
bridge,
resolve(
repository,
"tests/conformance/android/ReaJadxMetadataRegression.java",
),
],
{ timeout: 30_000 },
);
const result = await execute(
java,
[
...jvmArguments,
"-classpath",
`${classes}${delimiter}${jar}`,
"ReaJadxMetadataRegression",
apk,
resolve(repository, "tests/fixtures/android/static-initializers"),
classes,
resolve(repository, "tests/fixtures/android/direct-invokes"),
],
{ timeout: 120_000, maxBuffer: 8 * 1024 * 1024 },
);
assert.match(
result.stdout,
/METADATA_REGRESSION_PASS classes=\d+ overloads=3/u,
);
assert.match(
result.stdout,
/CLASS_SEARCH_REGRESSION_PASS pages=\d+ nonzero=true/u,
);
assert.match(
result.stdout,
/INITIALIZER_REGRESSION_PASS folded=2 bounded=true/u,
);
assert.match(
result.stdout,
/NO_BODY_REGRESSION_PASS native=true abstract=true frozen=true/u,
);
assert.match(
result.stdout,
/DIRECT_INVOKE_REGRESSION_PASS nodes=1 offsets=0,3 cross_dex=true/u,
);
console.log(
"PASS real cold metadata, full generic types and stable overloaded method selection",
);
console.log(
"PASS real Java filtered pages, nonzero inventory cursors and complete combined results",
);
console.log(
"PASS real folded and mixed static initializers, absent bodies and bounded smali",
);
console.log(
"PASS real native/abstract absence before and after neighbouring code generation",
);
console.log(
"PASS real raw DEX direct-invoke offsets and cross-DEX source identity",
);
await verifyAndroidMethodBodies({
apk: resolve(classes, "initializers.apk"),
repository,
entrypoint,
environment,
execute,
});
await verifyAndroidDirectInvokes({
apk: resolve(classes, "direct-invokes.apk"),
dexEntry: "classes.dex",
repository,
entrypoint,
environment,
execute,
});
await verifyAndroidDirectInvokes({
apk: resolve(classes, "direct-invokes-multidex.apk"),
dexEntry: "classes2.dex",
repository,
entrypoint,
environment,
execute,
});
} finally {
await rm(classes, { recursive: true, force: true });
}
};