fix(android): align runtime suffix inference (#977)

This commit is contained in:
morluto 2026-10-07 23:00:22 +08:00 committed by GitHub
parent f26c8b5b8c
commit fb3749aff6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 53 additions and 2 deletions

View File

@ -118,6 +118,7 @@ export const projectAndroidApplication = (
? ["Source inventory pages are incomplete; absence is unknown."]
: []),
"Manifest, resource, signing, and bytecode semantics require a dedicated Android provider; this projection reports exact inventory paths and hashes only.",
"Runtime families are inferred from inventory formats and paths; filename suffixes do not establish valid DEX or JVM class bytes.",
"Bridge candidates are path-based hypotheses, not decoded JNI declarations or observed runtime calls.",
];
const withoutId = {
@ -176,11 +177,17 @@ const runtimeFamilies = (all: readonly Component[]) => {
const families = new Set<
AndroidApplicationProjectionResult["runtime_families"][number]
>();
if (all.some(({ path, format }) => format === "dex" || path.endsWith(".dex")))
if (
all.some(
({ path, format }) =>
format === "dex" || path.toLowerCase().endsWith(".dex"),
)
)
families.add("dalvik-art");
if (
all.some(
({ path, format }) => format === "jvm-class" || path.endsWith(".class"),
({ path, format }) =>
format === "jvm-class" || path.toLowerCase().endsWith(".class"),
)
)
families.add("java-kotlin");

View File

@ -99,6 +99,50 @@ describe("Android application projection", () => {
});
});
it("keeps uppercase path-derived bytecode families distinct from byte validity", async () => {
const root = await createTestTempDirectory("rea-android-suffixes-");
const path = join(root, "Suffixes.apk");
const writer = new ZipWriter(new Uint8ArrayWriter());
for (const entry of [
"classes.DEX",
"extra.dex",
"Main.CLASS",
"Other.class",
])
await writer.add(entry, new TextReader("unrecognized bytes"));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const result = projectAndroidApplicationEvidence({
inventory_evidence: [inventory],
});
const projection = androidApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(result).normalized_result,
);
expect(projection.components.dex.map(({ path }) => path).sort()).toEqual([
"classes.DEX",
"extra.dex",
]);
expect(
projection.components.jvm_classes.map(({ path }) => path).sort(),
).toEqual(["Main.CLASS", "Other.class"]);
expect(
projection.components.dex.every(({ format }) => format === "file"),
).toBe(true);
expect(
projection.components.jvm_classes.every(
({ format }) => format === "file",
),
).toBe(true);
expect(projection.runtime_families).toEqual(["dalvik-art", "java-kotlin"]);
expect(projection.limitations).toContain(
"Runtime families are inferred from inventory formats and paths; filename suffixes do not establish valid DEX or JVM class bytes.",
);
});
it("returns every component and bridge candidate from the inventory", async () => {
const root = await createTestTempDirectory("rea-android-complete-");
const path = join(root, "Fixture.apk");