11

I'm using JDiagram JAR like below

Diagram myDigram = new Diagram();
    myDigram.routeAllLinks();

This code works fine when run with JRE 7 however when it is run with JRE 8, following error is being thrown:

java.lang.StackOverflowError
    at java.util.Collections.sort(Unknown Source)
    at com.mindfusion.common.ExtendedArrayList.sort(Unknown Source)
    at java.util.Collections.sort(Unknown Source)
    at com.mindfusion.common.ExtendedArrayList.sort(Unknown Source)
    at java.util.Collections.sort(Unknown Source)
    at com.mindfusion.common.ExtendedArrayList.sort(Unknown Source)

I followed the stack trace to JDiagram decompiled code. Observed that routeAllLinks() calls RouteLinks() on another object (say router) and at one more level deep ExtendedArrayList.sort() which is appeared in error stack trace is called. The "ExtendedArrayList" in JDiagram extends ArrayList and contains a method named "sort()" which has following definition.

  public void sort(Comparator<? super T> paramComparator)
  {
    Collections.sort(this, paramComparator);
  }

On Google I found out that JRE 8 has introduced List.sort() and delegates the Collections.sort() calls to collection's (ExtendedArrayList in my case) sort method. And so library ExtendedArrayList.sort() became an override. And it creates an infinite recursion which results in stackoverflow. I could reproduce this issue even with small piece of code as well now.

Also

  • Our original class which creates JDiagram object, is being loaded at runtime by some other component in our product. We have very little control over the loading of our program.
  • We have found out that latest version of JDiagram has fixed this issue by replacing sort() with sortJ7() method. However, we cannot upgrade the library at this moment. JDiagram is a licensed API.
  • ExtendedArrayList is being instantiated by JDiagram internally and so we cannot alter it from our code.

We have tried following solutions which didn't work so far

  • Java Proxy: Because our code does not call ExtendedArrayList directly and also 'Diagram' does not have any interface.
  • Spring AOP: We are not using spring and also our program is loaded runtime by other component.
  • AspectJ: By now, this was apparently a solution. However, it also didn't work as we are not able to weave our program at runtime. Not sure if someone could make it work.

Kindly let me know if any point needs elaboration. Any help is welcome. Thanks.

UPDATE So far, javassist is the best approach however there JDiagram obfuscation is preventing the solution to work correctly. We have kind of assumed that it is impossible (have to say) to fix considering our release date on our head. We have started process to upgrade library. And meanwhile removed a small feature from our application which was being provided by routeAllLinks() method.. :-( thanks everyone for your help. I'll be continuing my research on this issue as I found it really intriguing and challenging.. I'll update the post if I could resolve it.. And I'll be giving bounty to @gontard for his javassist approach as I'm continuing my research with it. Thanks.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
Rahul Winner
  • 430
  • 3
  • 16
  • 2
    Which code is calling `sort`, your application or the library? – Holger Mar 16 '15 at 10:42
  • Holger, thanks for your help.. The library (JDiagram) is calling sort() method internally. As JAVA 8 has introduced sort() method in individual collection classes and delegated Collections.sort() to arraylist sort method, consequently the library sort() becomes an override and resulting in recursion... Hope it clarifies.. – Rahul Winner Mar 16 '15 at 17:52
  • 2
    Non technical option: ask the guys at JDiagram to recompile their code with the sortJ7 method in the version you are using. – assylias Mar 18 '15 at 14:20
  • Assylias: if that would have been possible, I'd not have posted question here!! JDiagram is a licensed api and getting patch or upgrade takes a little more time. We are close to our release and cannot afford time to go through all those legal process of getting upgrade.. By the way, we have initiated discussion with managers so that we could upgrade for our next release.. If you have any other solution, please put forth.. Thanks.. – Rahul Winner Mar 18 '15 at 14:29
  • 1
    I would be hesitant to go with Java 8 until you get an updated JDiagram library. If there is something that is definitely broken in Java 8, there may be more lurking. Also, if this is a licensed proprietary library, any sort of hacky methods and even the decompiling you have done might be a violation of their license. – Necreaux Mar 24 '15 at 17:38

3 Answers3

6

I have reproduced your problem with a basic example:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class ExtendedArrayList<E> extends ArrayList<E> {
    @Override
    public void sort(Comparator<? super E> c) {
        Collections.sort(this, c);
    }
}

import java.util.Arrays;

public class Main {
    public static void main(String[] args) throws Exception {
        ExtendedArrayList<String> arrayList = new ExtendedArrayList<String>();
        arrayList.addAll(Arrays.asList("z", "y", "x"));
        arrayList.sort(String::compareTo); // -> java.lang.StackOverflowError
    }
}

I was able to bypass the java.lang.StackOverflowError by renaming the method using javassist:

import java.util.Arrays;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class Main {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.get("ExtendedArrayList");
        CtClass[] sortParams = new CtClass[]{ pool.get("java.util.Comparator")};
        CtMethod sortMethod = ctClass.getDeclaredMethod("sort", sortParams);
        sortMethod.setName("sortV7"); // rename
        ctClass.toClass();

        ExtendedArrayList<String> arrayList = new ExtendedArrayList<String>();
        arrayList.addAll(Arrays.asList("z", "y", "x"));
        System.err.println(arrayList); // print [z, y, x]
        arrayList.sort(String::compareTo);
        System.err.println(arrayList); // print [x, y, z]
    }
}

I have not tried with your version of JDiagram because I get only the last (Java 8 compatible) version on their website.

gontard
  • 28,720
  • 11
  • 94
  • 117
  • thanks.. I have not tried javassist yet.. Could you please tell me if it also use some javaagent or something similar to AspectJ weaving which involves custom class loading. Because as I mentioned, our class is being loaded at runtime. Also the ExtendedArrayList is instantiated by JDiagram not by my code.. Meanwhile I'll try your code and update the thread.. Thanks again!! – Rahul Winner Mar 18 '15 at 16:59
  • one more doubt, will Javassist work on obfuscate JAR as well? – Rahul Winner Mar 18 '15 at 18:38
  • Javassist run without any agent, it's just a library. I don't know if the obfuscation will alter the behavior. – gontard Mar 18 '15 at 19:39
  • I am able to fix the stackoverflow in a sample program having my own main() method. However, my project application throws following exception: javassist.NotFoundException: com.mindfusion.common.ExtendedArrayList at javassist.ClassPool.get(ClassPool.java:450) Apparently this is because the way, our project application loading the class. As I already mentioned in my problem statement, the AspectJ could not work for me because we are loading our class at runtime by another component.. Nevertheless, the I found Javassist the best approach so far as it does not involve any javagent.thx – Rahul Winner Mar 19 '15 at 04:10
  • 1
    I am still playing around with Javassist and trying my luck. Thanks a lot. – Rahul Winner Mar 19 '15 at 04:14
  • You 're welcome. When you get time, please give us feedback on your tries. – gontard Mar 20 '15 at 09:33
  • Gontard: I got `java.lang.NoSuchMethodError: com.mindfusion.common.ExtendedArrayList.sort(Ljava/util/Comparator;)` when I called routeAllLinks() in my code. So it indicates that Javassist has worked. However,my application now could not pick the new method (sortv7) Problem here is the way our application is being launched. Just give more insight, our application is being launched via a JNLP. Although we have removed this method call from our application at this moment to close our release. And we also have started legal process to get latest JAR. However still I'm trying to crack this problem. – Rahul Winner Mar 25 '15 at 10:20
  • @HumanBeing I can really explain you everything. However, I cannot put forth every single detail about my application in a public forum. I'd really appreciate if you could provide something constructive, instead of finding errors in what I've stated..Thanks again for your precious time. – Rahul Winner Mar 25 '15 at 17:37
  • Sorry but I can't do much anymore, since you've just accepted an answer –  Mar 25 '15 at 17:39
2

Think about decompiling the library and fixing the issue by yourself. You could use this fixed package as a workaround.

An alternative would be, to place a fixed version of the class in your code. Same package like in the library and same class name of course:

com.mindfusion.common.ExtendedArrayList

Maybe you have to configure the classloader to load your class instead of looking up the faulty class in the library first. Options like "parent first" or simply accessing the class from your code once before calling the library could make the deal.

Peter Wippermann
  • 4,125
  • 5
  • 35
  • 48
  • Thanks Peter, however this is a licensed library and it would be considered a breach..moreover, the JAR is obfuscated and so the all references for sort() cannot be replaced.. – Rahul Winner Mar 25 '15 at 04:27
  • Ok Rahul, I understand. But wouldn't the 2nd alternative (replacing the class with a fixed version in your code base) still be a valid option? You have to find out the obfuscated name of the class and then place the same in your code. – Peter Wippermann Apr 08 '15 at 09:49
1

I see that the loading of your program is not controlled by you. Perform the following steps:

  1. Include the jar(javassist) of the program that could change the "sort" method name to any other name that could avoid overriding the sorting method

  2. Load the jar's (javassist) main class by reflection class.forName(""); in the beginning of your program's main method

  3. Call the jar's (javassist) method to perform the required changes you need on the methods

This way you could be sure that any jar (javassist)is loaded and is ready to be used.

  • Not sure if class.forName() can replace already loaded classes.. So far, javassist is best approach however there JDiagram obfuscation is preventing the solution to work correctly. – Rahul Winner Mar 25 '15 at 04:32
  • I understood your answer, though didn't elaborated my comment. I am able get javassist jar classes in my program. But using javassist or any such jar, I cannot intercept JDiagram class because it is obfuscated and we cannot control the way JDiagram is being loaded. – Rahul Winner Mar 25 '15 at 09:07
  • 1
    If it is a licensed jar, it means that you've payed for it, then they must provide with it the support, I think if you ask them and explain them the situation they will change code according to your needs. –  Mar 25 '15 at 10:10
  • I request you to read my question, other answers and comments thoroughly before commenting. Thanks a lot for your help. – Rahul Winner Mar 25 '15 at 10:14
  • 1
    I read your question already, so if you are upgrading the jar, then you would have the method sort already changed, so you will not be needing javassist, just saying, no need to research on that part anymore and waste your time. –  Mar 25 '15 at 10:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/73747/discussion-between-rahul-winner-and-human-being). – Rahul Winner Mar 25 '15 at 10:25