sim-codec-classfile 0.1.0

Bounded, lossless Java Virtual Machine classfile codec for SIM
Documentation
#!/usr/bin/env python3
"""Generate classfile Index coverage and enforce its source-ownership policy."""

from __future__ import annotations

import argparse
import pathlib
import re
import sys
import tomllib


ROOT = pathlib.Path(__file__).resolve().parent
POLICY = ROOT / "index-coverage.toml"
OUTPUT = ROOT / "CLASSFILE_COVERAGE.md"


def policy(root: pathlib.Path) -> dict:
    document = tomllib.loads((root / "index-coverage.toml").read_text(encoding="utf-8"))
    if document.get("schema") != "sim.classfile-index-coverage/v1":
        raise ValueError("index-coverage.toml: unsupported schema")
    return document


def opcode_names(root: pathlib.Path, config: dict) -> list[str]:
    path = root / config["opcode_manifest"]
    lines = [line for line in path.read_text(encoding="utf-8").splitlines() if line and not line.startswith("#")]
    if not lines or "mnemonic" not in lines[0].split("\t"):
        raise ValueError(f"{path}: missing mnemonic column")
    fields = lines[0].split("\t")
    mnemonic = fields.index("mnemonic")
    names = [line.split("\t")[mnemonic] for line in lines[1:]]
    if len(names) != 256 or len(set(names)) != 256:
        raise ValueError(f"{path}: expected 256 unique opcode rows, found {len(names)}")
    return names


def braced_body(text: str, declaration: str, path: pathlib.Path) -> str:
    match = re.search(declaration, text)
    if match is None:
        raise ValueError(f"{path}: coverage declaration not found")
    start = text.find("{", match.start())
    depth = 0
    for offset in range(start, len(text)):
        if text[offset] == "{":
            depth += 1
        elif text[offset] == "}":
            depth -= 1
            if depth == 0:
                return text[start + 1 : offset]
    raise ValueError(f"{path}: unterminated coverage declaration")


def constant_names(root: pathlib.Path, config: dict) -> list[str]:
    path = root / config["constant_source"]
    body = braced_body(
        path.read_text(encoding="utf-8"),
        rf"\bpub\s+enum\s+{re.escape(config['constant_enum'])}\s*{{",
        path,
    )
    names = re.findall(r"(?m)^\s{4}([A-Z][A-Za-z0-9]*)\s*(?:\([^\n]*\)|\{|,)", body)
    if not names or len(names) != len(set(names)):
        raise ValueError(f"{path}: constant coverage is empty or ambiguous")
    return names


def attribute_names(root: pathlib.Path, config: dict) -> list[str]:
    path = root / config["attribute_source"]
    body = braced_body(
        path.read_text(encoding="utf-8"),
        rf"\bpub\s+fn\s+{re.escape(config['attribute_function'])}\s*\(",
        path,
    )
    names = sorted(set(re.findall(r'"([A-Z][A-Za-z0-9]+)"', body)))
    if not names:
        raise ValueError(f"{path}: standard attribute coverage is empty")
    return names


def projection(opcodes: list[str], constants: list[str], attributes: list[str]) -> str:
    rows = []
    for kind, names in (("opcode", opcodes), ("constant", constants), ("attribute", attributes)):
        rows.extend(f"| {kind} | `{name}` |" for name in names)
    return """<!-- @generated by generate_index_coverage.py; DO NOT EDIT. -->
# JVM classfile coverage

This Index coverage projection is generated from the opcode manifest and the
owning constant and standard-attribute source declarations. It is not an
independent classfile inventory.

| Kind | Identity |
|---|---|
""" + "\n".join(rows) + (
        f"\n\nTotals: {len(opcodes)} opcodes, {len(constants)} constants, "
        f"{len(attributes)} standard attributes; coverage difference: 0.\n"
    )


def source_files(workspace: pathlib.Path):
    excluded = {".git", "target", ".meta-workspace"}
    constellation = any(child.is_dir() and child.name.startswith("sim-") for child in workspace.iterdir())
    for path in sorted(workspace.rglob("*")):
        if not path.is_file() or path.suffix not in {".rs", ".toml", ".tsv", ".csv", ".json", ".yaml", ".yml"}:
            continue
        if any(part in excluded for part in path.parts):
            continue
        relative = path.relative_to(workspace)
        if constellation and (not relative.parts or not relative.parts[0].startswith("sim-")):
            continue
        yield path


def crate_name(path: pathlib.Path, workspace: pathlib.Path) -> str:
    for parent in (path.parent, *path.parents):
        manifest = parent / "Cargo.toml"
        if manifest.is_file() and workspace in (parent, *parent.parents):
            match = re.search(r'(?m)^name\s*=\s*"([^"]+)"', manifest.read_text(encoding="utf-8"))
            if match:
                return match.group(1)
    return ""


def guard_findings(root: pathlib.Path, workspace: pathlib.Path, config: dict, coverage: dict, opcodes: list[str], constants: list[str], attributes: list[str]) -> list[str]:
    findings: list[str] = []
    owner = config["owner_crate"]
    generated = {root / item for item in config["generated_files"]}
    authorities = {
        root / "opcode-manifest.tsv",
        root / coverage["constant_source"],
        root / coverage["attribute_source"],
        root / "src/opcode_generated.rs",
    }
    opcode_set = {name for name in opcodes if not name.startswith("reserved_")}
    for path in source_files(workspace):
        if path in generated or path in authorities:
            continue
        text = path.read_text(encoding="utf-8")
        relative = path.relative_to(workspace)
        named_inventory = re.search(
            r"(?is)(?:const|static)\s+[A-Za-z0-9_]*(?:opcode|instruction|constant|attribute)[A-Za-z0-9_]*.*?=.*?;",
            text,
        )
        if "tests" not in path.parts and named_inventory:
            findings.append(f"{relative} contains a duplicate classfile inventory")

        member = crate_name(path, workspace)
        runtime_or_verifier = "runtime" in member or "verif" in member
        parses_bytes = re.search(r"\b(?:ByteReader|read_u[124]|from_be_bytes|read_exact)\b", text)
        classfile_context = re.search(r"(?i)classfile|CAFEBABE|constant_pool", text)
        if member != owner and runtime_or_verifier and parses_bytes and classfile_context:
            findings.append(f"{relative} parses classfile bytes outside {owner}")

        production = "tests" not in path.parts and not path.name.endswith("_test.rs")
        if production and re.search(r"from_utf8_lossy", text) and re.search(r"(?i)modified[_ -]?utf|classfile", text):
            findings.append(f"{relative} performs a lossy modified UTF-8 crossing")
    return findings


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--check", action="store_true", help="fail unless the generated projection is current")
    parser.add_argument("--scan", action="store_true", help="enforce classfile source ownership guards")
    parser.add_argument("--workspace-root", type=pathlib.Path, help="root searched by ownership guards")
    args = parser.parse_args()
    try:
        document = policy(ROOT)
        coverage = document["coverage"]
        opcodes = opcode_names(ROOT, coverage)
        constants = constant_names(ROOT, coverage)
        attributes = attribute_names(ROOT, coverage)
        expected = projection(opcodes, constants, attributes)
        current = OUTPUT.is_file() and OUTPUT.read_text(encoding="utf-8") == expected
        if not args.check:
            OUTPUT.write_text(expected, encoding="utf-8")
            current = True
        workspace = (args.workspace_root or ROOT).resolve()
        findings = guard_findings(
            ROOT,
            workspace,
            document["guards"],
            coverage,
            opcodes,
            constants,
            attributes,
        ) if args.scan else []
    except (KeyError, OSError, ValueError, tomllib.TOMLDecodeError) as error:
        print(error, file=sys.stderr)
        return 1
    if not current:
        print("generated classfile coverage is stale; run ./generate_index_coverage.py", file=sys.stderr)
    for finding in findings:
        print(finding, file=sys.stderr)
    return 0 if current and not findings else 1


if __name__ == "__main__":
    raise SystemExit(main())