Translate actual OutOfMemoryError at the bridge boundary before upstream stringifies it. Preserve observed heap bytes, missing reasons, coroutine context and cancellation; retain SessionHolder ownership and load options. Restrict heap-specific remediation to known heap reasons and parse named bridge failure contracts once. Coordinate bridge protocol v4, clarify doctor configuration versus target capacity, and preserve the memory primary across cleanup failure/retry. Verify controlled immediate/late/inline continuation outcomes with the pinned JAR and real JDK, focused tests, and tiny real CLI/MCP method workflows. Actual heap exhaustion, the reported APK, pre-handshake failures, Linux and Windows remain unverified; no stress or profiling run. Fixes #3517
138 lines
3.8 KiB
JavaScript
138 lines
3.8 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,
|
|
/LOAD_FAILURE_REGRESSION_PASS typed=true decoy=true suspension=true cancellation=true/u,
|
|
);
|
|
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,
|
|
/MERGED_METHOD_REGRESSION_PASS exact=true overloaded=true handlers=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 });
|
|
}
|
|
};
|