Skip to content

[Bug]: Load-time weaving resolves the class hierarchy through the system class loader, so weaves into application types fail to verify #14

Description

@Splatcrafter

Version

0.1.0

Driver

Java agent (load-time weaving)

Module

aether-weaver-engine

Diagnostic code

AW4001

What you expected

Weaving a load-time target whose method bodies merge a reference type belonging to the
application (one not visible to the system class loader) produces a class that verifies and
applies.

What happened instead

The woven class is rejected, in one of two forms that share a single root cause:

  • java.lang.classfile's StackMapGenerator throws
    IllegalArgumentException: Could not resolve class <T> out of ClassFile.transformClass(...)
    while generating frames; or
  • the class fails ClassFile.verify(...) with spurious VerifyErrors —
    <class> does not verify after weaving (N errors) (AW4001).

Root cause. On the load-time path the class-hierarchy resolver is derived from the
system class loader, not from the loader that defined the class being woven, so the
application's own types cannot be resolved when frames are merged at a branch. Two sites:

  1. Injection and merge build the woven class with a plain ClassFile.of()
    (WeavingPipeline, StructuralWeaver) — the default ClassHierarchyResolver, which
    reflects over the system class loader.
  2. Verifier.check(...) runs ClassFile.of().verify(...) — the same default resolver.

FrameSupport.forLoadTime(ClassLoader) already builds the correct resolver
(ClassHierarchyResolver.ofResourceParsing(loader).orElse(defaultResolver())), but nothing
on the load-time path calls it, and the defining ClassLoader handed to
WeavingTransformer.transform(...) is never threaded into Weaver.weave(...).

Under verification=report the failure is silent — the class loads unwoven, so a weave is
dropped without a word. Under verification=strict / onError=fail it surfaces as AW4001
or halts the JVM. Weaves whose signatures reference only JDK types are unaffected, which is
why the failure looks selective.

Minimal reproducer

// Target: an application class (not on the system class path at weave time) whose method
// merges a reference type at a branch -- a list built in a loop, a synchronized block, etc.
// Any @Inject at RETURN forces the frames to be regenerated, which needs the hierarchy.
@Weave(targets = "com.example.app.Service", require = Require.OPTIONAL)
public final class ServiceWeave {

    @Inject(method = "collect()", at = @At(Point.RETURN))
    private void onCollect(final ReturnableCallback<java.util.List> callback) {
        // body irrelevant; the mere injection triggers frame regeneration
    }
}

Configuration

<!-- load-time, application types not on the system class path -->
-javaagent:aether-weaver-agent.jar
-Xbootclasspath/a:weaves.jar:aether-weaver-api.jar:aether-weaver-engine.jar:aether-weaver-runtime.jar
-Daether.weaver.verification=strict

Output or stack trace

java.lang.IllegalArgumentException: Could not resolve class <ApplicationType>
    at java.base/jdk.internal.classfile.impl.ClassHierarchyImpl.resolve(ClassHierarchyImpl.java:76)
    at java.base/jdk.internal.classfile.impl.ClassHierarchyImpl.isInterface(ClassHierarchyImpl.java:86)
    at java.base/jdk.internal.classfile.impl.StackMapGenerator$Type.mergeReferenceFrom(StackMapGenerator.java:1363)
    at java.base/jdk.internal.classfile.impl.StackMapGenerator.checkJumpTarget(StackMapGenerator.java:283)

# or, under strict verification:
<class> does not verify after weaving (N errors)        # AW4001
    at de.splatgames.aether.weaver.engine.verify.Verifier.refuse(Verifier.java)
    at de.splatgames.aether.weaver.engine.verify.Verifier.check(Verifier.java)
    at de.splatgames.aether.weaver.engine.Weaver.apply(Weaver.java)

Environment

JDK: Corretto 25.0.2
OS: Windows 11

Is this a regression?

No, or I do not know

Suggested fix

Thread the defining ClassLoader — already available as the loader argument of
WeavingTransformer.transform(...) — through Weaver.weave(...) to the load-time build and
verify sites, and use the resolver FrameSupport already provides:

  • WeavingPipeline (injection) and StructuralWeaver (merge): build the class via
    FrameSupport.forLoadTime(loader) instead of ClassFile.of().
  • Verifier.check(...): verify via FrameSupport.forLoadTime(loader).verify(...) instead of
    ClassFile.of().verify(...).

FrameSupport.forLoadTime(ClassLoader) already exists and does exactly this; it simply has
no callers on the load-time path. Routing both stack-map generation and verification through
the same loader-aware context also keeps build-time and load-time output byte-identical,
which FrameSupportTest already asserts.

Checklist

  • I searched the existing issues and this is not a duplicate
  • I checked the diagnostic reference for the code this reported
  • I am on the latest released version, or I have said why I am not

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions