Javassist: how to solve a duplicate class exception?

909 views Asked by At

Using Javassist I'm trying to insert a line of code into a method's body. It's a simple modification for a label's text color inside the intellij-IDE. I get the following error when trying to do so:

Caused by: java.lang.LinkageError: loader com.intellij.util.lang.UrlClassLoader @d2cc05a attempted duplicate class definition for com.intellij.ui.components.labels.LinkLabel. (com.intellij.ui.components.labels.LinkLabel is in unnamed module of loader com.intellij.util.lang.UrlClassLoader @d2cc05a, parent loader 'platform')

Which implies that the class I am trying to modify is already loaded by the Java class loader (at least I like to think so..). An example of the code is given below:

private static void test() {
        try {
            ClassPool cp = ClassPool.getDefault();
            CtClass cc = cp.get("com.intellij.ui.components.labels.LinkLabel");
            CtMethod cm = cc.getDeclaredMethod("getTextColor");
            cm.insertBefore("System.out.println(\"helloworld\");");

            cc.toClass(); // <--- the problem
        } catch (final Throwable e) {
            e.printStackTrace();
        }
    }

And for reference, here's the accessed class: intellij link


Rafael Winterhalter commented on a similar issue a couple of years ago (link). Unfortunately my knowledge with Javassist is limiting me in finding a proper solution.

Any ideas on how to resolve this issue?


UPDATE: A minimal, repoducible example can be found here: github link

1

There are 1 answers

0
kkflf On

I know that this is a old question, but it is the top result on google when you search "duplicate class definition javassist" so I might as well share the possible solution.

That problem that you are experiencing is because the class is already loaded into the class pool. You will have to modify/redefine the class before the class is loaded into the class pool.

In your particular problem, you are trying to redefine com.intellij.ui.components.labels.LinkLabel. This class has a static field called ourVisitedLinks. This static field will force the LinkLabel class to be loaded on the first mentioning of LinkLabel class.

When you call cc.toClass() then you are essentially making a reference the to LinkLabel class and thereby loading the class into the class pool because the static field ourVisitedLinks is initialized. The LinkLabel class is thereby implicit loaded into the class pool just before you load the modified version of the class.

I admittedly do not understand everything that is going on inside javassist. I just know that cc.toClass() will load the class into the class pool preemptively due to the static field being initialized.

You will have to instead call cc.toClass(SomeNeighbor.class). The SomeNeighbor.class is a fictive class that I made up for the example. It just have to be a class that is within the same package as the class that you are trying to redefine.

The LinkLabel class has the following neighbors that share the same package com.intellij.ui.components.labels:

  • ActionLink
  • BoldLabel
  • DropDownLink
  • LinkListener
  • SwingActionLink

You can pick any of those 5 classes as a neighbor as long as the class does not have any direct nor transitive static dependency of LinkLabel.

This is the corrected code of the example in the question. The github link that was shared in the question is unfortunately not longer accessible, so I can not provide a perfect executable example.

private static void test() {
   try {
        ClassPool cp = ClassPool.getDefault();
        CtClass cc = cp.get("com.intellij.ui.components.labels.LinkLabel");
        CtMethod cm = cc.getDeclaredMethod("getTextColor");
        cm.insertBefore("System.out.println(\"helloworld\");");

        cc.toClass(BoldLabel.class); // <--- the correction
    } catch (final Throwable e) {
            e.printStackTrace();
    }
}