feat(bytecode): add JVM and Python bytecode providers

Add execution-free providers for JVM bytecode/JAR/WAR and Python
bytecode/wheels/zipapps (#367).

- bytecodeProvider.ts: domain schemas for bytecode families (JVM,
  Python), artifact formats (class, jar, war, ear, pyc, pyo, whl,
  zipapp), symbol types, and provenance classification.
- detectBytecodeFamily: classifies artifacts from file extensions
- detectArtifactFormat: identifies artifact format from path
- classifyProvenance: distinguishes application, generated, vendored,
  bundled, and standard_library code
- analyzeBytecodeArtifacts: summarizes total and classified symbols

Closes #367
This commit is contained in:
morluto 2026-07-28 05:10:25 +00:00
parent 645668b70d
commit 4e0127d620
2 changed files with 358 additions and 0 deletions

View File

@ -0,0 +1,206 @@
import { z } from "zod";
/** Supported managed-runtime bytecode families. */
export const bytecodeFamilySchema = z.enum(["jvm", "python"]);
export type BytecodeFamily = z.infer<typeof bytecodeFamilySchema>;
/** Classification of bytecode provenance. */
export const bytecodeProvenanceSchema = z.enum([
"application",
"generated",
"vendored",
"bundled",
"standard_library",
]);
export type BytecodeProvenance = z.infer<typeof bytecodeProvenanceSchema>;
/** A bytecode symbol discovered through static analysis. */
export const bytecodeSymbolSchema = z.strictObject({
/** Fully qualified name. */
name: z.string().min(1),
/** Raw bytecode location or offset. */
raw_location: z.string().min(1),
/** Normalized symbol type. */
kind: z.enum([
"class",
"interface",
"method",
"field",
"function",
"module",
"variable",
]),
/** Bytecode version if known. */
bytecode_version: z.number().int().nullable(),
/** Provenance classification. */
provenance: bytecodeProvenanceSchema,
/** Whether the symbol is accessible (public). */
is_public: z.boolean().default(false),
});
export type BytecodeSymbol = z.infer<typeof bytecodeSymbolSchema>;
/** A discovered bytecode artifact (class file, wheel, etc). */
export const bytecodeArtifactSchema = z.strictObject({
/** Artifact path relative to the analysis root. */
path: z.string().min(1),
/** Bytecode family. */
family: bytecodeFamilySchema,
/** Artifact format. */
format: z.enum(["class", "jar", "war", "ear", "pyc", "pyo", "whl", "zipapp"]),
/** Size in bytes. */
size: z.number().int().nonnegative(),
/** SHA-256 digest. */
digest: z.string().regex(/^[a-f0-9]{64}$/u),
/** Discovered symbols. */
symbols: z.array(bytecodeSymbolSchema).default([]),
/** Whether this is a standard library artifact. */
is_standard_library: z.boolean().default(false),
/** Whether this is a generated artifact. */
is_generated: z.boolean().default(false),
/** Whether this is a vendored artifact. */
is_vendored: z.boolean().default(false),
});
export type BytecodeArtifact = z.infer<typeof bytecodeArtifactSchema>;
/** Static bytecode analysis result. */
export const bytecodeAnalysisSchema = z.strictObject({
/** Bytecode family. */
family: bytecodeFamilySchema,
/** All discovered artifacts. */
artifacts: z.array(bytecodeArtifactSchema).min(0).max(10_000),
/** Total symbols across all artifacts. */
total_symbols: z.number().int().nonnegative(),
/** Symbols classified as application code. */
application_symbols: z.number().int().nonnegative(),
/** Symbols classified as generated code. */
generated_symbols: z.number().int().nonnegative(),
/** Symbols classified as vendored code. */
vendored_symbols: z.number().int().nonnegative(),
/** Symbols classified as standard library. */
standard_library_symbols: z.number().int().nonnegative(),
});
export type BytecodeAnalysis = z.infer<typeof bytecodeAnalysisSchema>;
/** Detect bytecode family from file extension. */
export function detectBytecodeFamily(path: string): BytecodeFamily | null {
const lower = path.toLowerCase();
if (
lower.endsWith(".class") ||
lower.endsWith(".jar") ||
lower.endsWith(".war") ||
lower.endsWith(".ear")
) {
return "jvm";
}
if (
lower.endsWith(".pyc") ||
lower.endsWith(".pyo") ||
lower.endsWith(".whl") ||
lower.endsWith(".pyz") ||
lower.endsWith(".zipapp")
) {
return "python";
}
return null;
}
/** Detect artifact format from file path. */
export function detectArtifactFormat(
path: string,
): `class` | `jar` | `war` | `ear` | `pyc` | `pyo` | `whl` | `zipapp` | null {
const lower = path.toLowerCase();
if (lower.endsWith(".class")) return "class";
if (lower.endsWith(".jar")) return "jar";
if (lower.endsWith(".war")) return "war";
if (lower.endsWith(".ear")) return "ear";
if (lower.endsWith(".pyc")) return "pyc";
if (lower.endsWith(".pyo")) return "pyo";
if (lower.endsWith(".whl")) return "whl";
if (lower.endsWith(".pyz") || lower.endsWith(".zipapp")) return "zipapp";
return null;
}
/** Classify bytecode symbol provenance. */
export function classifyProvenance(
path: string,
symbolName: string,
): BytecodeProvenance {
const lower = path.toLowerCase();
// Standard library paths
if (
lower.includes("/stdlib/") ||
lower.includes("/site-packages/") ||
lower.includes("java/") ||
lower.includes("javax/") ||
lower.includes("sun/") ||
symbolName.startsWith("java.") ||
symbolName.startsWith("javax.") ||
symbolName.startsWith("sun.") ||
symbolName.startsWith("__")
) {
return "standard_library";
}
// Generated code
if (
lower.includes("/generated/") ||
lower.includes("_generated") ||
lower.includes("/build/") ||
lower.includes("/target/") ||
lower.includes("/dist-packages/") ||
lower.includes("/__pycache__/") ||
symbolName.includes("Generated") ||
symbolName.includes("_generated")
) {
return "generated";
}
// Vendored code
if (
lower.includes("/vendor/") ||
lower.includes("/vendored/") ||
lower.includes("/third_party/") ||
lower.includes("/3rdparty/") ||
lower.includes("/node_modules/") ||
lower.includes("/vendor/")
) {
return "vendored";
}
// Bundled code
if (
lower.includes("/bundle/") ||
lower.includes("/bundled/") ||
lower.includes(".jar!") ||
lower.includes(".war!")
) {
return "bundled";
}
return "application";
}
/** Analyze a set of bytecode artifacts and produce a summary. */
export function analyzeBytecodeArtifacts(
artifacts: readonly BytecodeArtifact[],
family: BytecodeFamily,
): BytecodeAnalysis {
const applicationSymbols = artifacts
.flatMap((a) => a.symbols)
.filter((s) => s.provenance === "application").length;
const generatedSymbols = artifacts
.flatMap((a) => a.symbols)
.filter((s) => s.provenance === "generated").length;
const vendoredSymbols = artifacts
.flatMap((a) => a.symbols)
.filter((s) => s.provenance === "vendored").length;
const standardLibrarySymbols = artifacts
.flatMap((a) => a.symbols)
.filter((s) => s.provenance === "standard_library").length;
return {
family,
artifacts: artifacts.slice(0, 10_000),
total_symbols: artifacts.reduce((sum, a) => sum + a.symbols.length, 0),
application_symbols: applicationSymbols,
generated_symbols: generatedSymbols,
vendored_symbols: vendoredSymbols,
standard_library_symbols: standardLibrarySymbols,
};
}

View File

@ -0,0 +1,152 @@
import { describe, expect, it } from "vitest";
import {
analyzeBytecodeArtifacts,
bytecodeAnalysisSchema,
classifyProvenance,
detectArtifactFormat,
detectBytecodeFamily,
type BytecodeArtifact,
type BytecodeSymbol,
} from "../src/domain/bytecodeProvider.js";
describe("bytecode provider", () => {
it("detects JVM family from extensions", () => {
expect(detectBytecodeFamily("App.class")).toBe("jvm");
expect(detectBytecodeFamily("app.jar")).toBe("jvm");
expect(detectBytecodeFamily("webapp.war")).toBe("jvm");
expect(detectBytecodeFamily("app.ear")).toBe("jvm");
});
it("detects Python family from extensions", () => {
expect(detectBytecodeFamily("module.pyc")).toBe("python");
expect(detectBytecodeFamily("module.pyo")).toBe("python");
expect(detectBytecodeFamily("package.whl")).toBe("python");
expect(detectBytecodeFamily("app.pyz")).toBe("python");
expect(detectBytecodeFamily("app.zipapp")).toBe("python");
});
it("returns null for unknown extensions", () => {
expect(detectBytecodeFamily("readme.txt")).toBeNull();
expect(detectBytecodeFamily("script.js")).toBeNull();
});
it("detects artifact format from path", () => {
expect(detectArtifactFormat("App.class")).toBe("class");
expect(detectArtifactFormat("lib.jar")).toBe("jar");
expect(detectArtifactFormat("web.war")).toBe("war");
expect(detectArtifactFormat("mod.pyc")).toBe("pyc");
expect(detectArtifactFormat("pkg.whl")).toBe("whl");
expect(detectArtifactFormat("unknown.xyz")).toBeNull();
});
it("classifies standard library provenance", () => {
expect(classifyProvenance("/usr/lib/stdlib", "java.lang.String")).toBe(
"standard_library",
);
expect(
classifyProvenance("/lib/python3.10/site-packages/numpy", "__init__"),
).toBe("standard_library");
});
it("classifies generated provenance", () => {
expect(classifyProvenance("/build/generated/src", "GeneratedClass")).toBe(
"generated",
);
expect(
classifyProvenance("/__pycache__/module.cpython-310.pyc", "module"),
).toBe("generated");
});
it("classifies vendored provenance", () => {
expect(classifyProvenance("/vendor/libs/lib.jar", "VendorLib")).toBe(
"vendored",
);
expect(classifyProvenance("/third_party/pkg/module.pyc", "module")).toBe(
"vendored",
);
});
it("classifies application provenance", () => {
expect(classifyProvenance("/src/main/App.java", "com.example.App")).toBe(
"application",
);
expect(classifyProvenance("/src/app.py", "main")).toBe("application");
});
it("analyzes bytecode artifacts and computes summary", () => {
const artifacts: BytecodeArtifact[] = [
{
path: "src/App.class",
family: "jvm",
format: "class",
size: 1000,
digest: "a".repeat(64),
symbols: [
{
name: "com.example.App",
raw_location: "0x1000",
kind: "class",
bytecode_version: 52,
provenance: "application",
is_public: true,
},
{
name: "java.lang.String",
raw_location: "0x2000",
kind: "class",
bytecode_version: 52,
provenance: "standard_library",
is_public: true,
},
],
is_standard_library: false,
is_generated: false,
is_vendored: false,
},
{
path: "lib/generated/Generated.class",
family: "jvm",
format: "class",
size: 500,
digest: "b".repeat(64),
symbols: [
{
name: "GeneratedClass",
raw_location: "0x3000",
kind: "class",
bytecode_version: 52,
provenance: "generated",
is_public: false,
},
],
is_standard_library: false,
is_generated: true,
is_vendored: false,
},
];
const result = analyzeBytecodeArtifacts(artifacts, "jvm");
expect(result.family).toBe("jvm");
expect(result.total_symbols).toBe(3);
expect(result.application_symbols).toBe(1);
expect(result.standard_library_symbols).toBe(1);
expect(result.generated_symbols).toBe(1);
expect(result.vendored_symbols).toBe(0);
// Validate schema
const parsed = bytecodeAnalysisSchema.safeParse(result);
expect(parsed.success).toBe(true);
});
it("uses BytecodeSymbol type", () => {
const sym: BytecodeSymbol = {
name: "com.example.Test",
raw_location: "0x100",
kind: "class",
bytecode_version: 52,
provenance: "application",
is_public: true,
};
expect(sym.provenance).toBe("application");
});
});