Skip to content

ClassLoaderClassWriter silently falls back to Object; JarProcessor should accept a caller-provided resolver #1

Description

@Gedochao

Summary

ClassLoaderClassWriter.getCommonSuperClass returns java/lang/Object whenever it cannot read a type's class bytes / reflectively resolve it. That is a silent failure mode: ASM then writes incorrect stack map frames and the JVM rejects the patched class with VerifyError.

JarProcessor.process compounds this by building a URLClassLoader over only the input JAR (parented to sloth's own classloader). Hierarchy types that live in other classpath entries are invisible, so any merge across those types collapses to Object.

Impact (Scala CLI / GraalVM native image)

Scala CLI drives sloth to patch Scala 3.3.x compiler and library jars under --sloth. On the JVM launcher this often appears to work because the parent classloader accidentally sees scala-library (and HotSpot can serve JDK .class resources). Inside a GraalVM native image:

  1. There are no .class resources at all → getResourceAsStream fails for JDK types.
  2. Class.forName(name, false, customLoader) also fails to resolve JDK types reliably.
  3. Every reference-type merge in patched methods (e.g. FileZipArchive.openZipFile merging JarFile and ZipFile) becomes ObjectVerifyError: Bad return type.

We hit this on scala3-compiler_3-3.3.8.jar (dotty.tools.io.FileZipArchive.openZipFile) when starting scala-cli --power repl --sloth -S 3.3.8 with the native launcher.

Suggested fixes

  1. Do not silently fall back to Object in ClassLoaderClassWriter.getCommonSuperClass when a type cannot be resolved — fail loudly (or at least log) so callers notice corrupt frames.
  2. Let JarProcessor.process accept a caller-provided ClassLoader / resolver (or an explicit hierarchy classpath) instead of always building a URLClassLoader over only the input JAR.

Workaround

Scala CLI now reimplements jar processing on top of LazyValAnalyzer / BytecodePatcher with a classpath-backed resolver that serves jar/dir bytes via ZipFile / file I/O and synthesizes minimal JDK classfile stubs (superclass + interfaces) for native-image-safe frame computation.

Additional notes

Found while working on VirtusLab/scala-cli#4404

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions