Search code examples
javasuperclassjavaagents

ClassCircularityError when the superclass hierarchy is normal


I'm working on a java agent for a personal project of mine and encountered a really peculiar error. When I run the method "register" in my IDE it works fine. No circularity error. But when I export the agent and use it with launch args javaagent:"agent.jar" it crashes giving ClassCircularityError (Looping superclass hierarchy).

public class Refactorer implements ClassFileTransformer {
    private static final Set<AbstractMatcher<String>> matchers = new HashSet<AbstractMatcher<String>>();

    public static void register(AbstractMatcher<String> matcher) {
        matchers.add(matcher);
    }

    public byte[] transform(ClassLoader loader, String name, Class<?> clazz, ProtectionDomain domain, byte[] bytes) throws IllegalClassFormatException {}
}

Stacktrace

java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:386)
    at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:401)
Caused by: java.lang.ClassCircularityError: java/util/Set
    at me.vader.Refactorer.register(Refactorer.java:42)
    at me.vader.Setup.registerModders(Setup.java:30)
    at me.vader.Agent.setAndAddTransformer(Agent.java:37)
    at me.vader.Agent.premain(Agent.java:18)

What's really confusing me is that when I checked the superclass hierarchy in the compiled agent it printed out fine.

Tested with:

Class c = matchers.getClass();
int i = 0;
while (c != null){
    System.out.println("Class (Sup x" + i+ "): " + c.getName());
    c = c.getSuperclass();
    i++;
}

i = 0;
c = matcher.getClass();
while (c != null){
    System.out.println("Class (Sup x" + i+ "): " + c.getName());
    c = c.getSuperclass();
    i++;
}

Which outputs:

// The "matchers"
Class (Sup x0): java.util.HashSet
Class (Sup x1): java.util.AbstractSet
Class (Sup x2): java.util.AbstractCollection
Class (Sup x3): java.lang.Object

// The matcher being added to "matchers"
Class (Sup x0): me.vader.match.ClassMatcher
Class (Sup x1): me.vader.match.AbstractMatcher
Class (Sup x2): java.lang.Object

Solution

  • It seems that classes weren't being loaded in the right order. I'm not sure why, but that seems to be it.

    My fix is slapping the folllowing in the Refactorer class:

    static {
        Serializable.class.getName();
        Cloneable.class.getName();
        Iterable .class.getName();
        Collection.class.getName();
        AbstractCollection.class.getName();
        Set.class.getName();
        AbstractSet.class.getName();
        HashSet.class.getName();
    }
    

    Forcing them to be loaded in the correct order. It's ugly but it works.