问题 JDiagram旧版本在ExtendedArrayList.sort中使用JRE 8抛出StackOverflowError


我正在使用JDiagram JAR,如下所示

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

使用JRE 7运行时此代码可以正常工作,但是当它与JRE 8一起运行时,会抛出以下错误:

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)

我跟着堆栈跟踪到JDiagram反编译代码。观察到routeAllLinks()在另一个对象(比如路由器)上调用RouteLinks(),并且在另一个级别深度调用错误堆栈跟踪中出现的ExtendedArrayList.sort()。 JDiagram中的“ExtendedArrayList”扩展了ArrayList,并包含一个名为“sort()”的方法,该方法具有以下定义。

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

在Google上,我发现JRE 8引入了List.sort()并将Collections.sort()调用委托给集合(在我的情况下为ExtendedArrayList)排序方法。因此,库ExtendedArrayList.sort()成为了一个覆盖。它会创建一个无限递归,从而导致堆栈溢出。我现在可以用一小段代码重现这个问题。

  • 我们创建JDiagram对象的原始类在运行时由我们产品中的其他组件加载。我们对程序的加载几乎没有控制权。
  • 我们发现最新版本的JDiagram通过用sortJ7()方法替换sort()来解决这个问题。但是,此时我们无法升级库。 JDiagram是一个许可的API。
  • ExtendedArrayList由JDiagram在内部实例化,因此我们无法从代码中更改它。

我们尝试过以下目前无效的解决方案

  • Java代理:因为我们的代码不直接调用ExtendedArrayList 而且'Diagram'没有任何界面。
  • Spring AOP:我们是 不使用spring,我们的程序也由其他程序加载运行时 零件。
  • AspectJ:到目前为止,这显然是一个解决方案。然而, 它也没有用,因为我们无法编织我们的程序 运行。不确定是否有人可以使它工作。

如果有任何要点需要详细说明,请告诉我。 欢迎任何帮助。谢谢。

UPDATE 到目前为止,javassist是最好的方法,但JDiagram混淆阻止解决方案正常工作。考虑到我们的发布日期,我们有点认为不可能(不得不说)解决。我们已经开始升级库的过程。同时从我们的应用程序中删除了一个由routeAllLinks()方法提供的小功能.. :-( 谢谢大家的帮助。我将继续研究这个问题,因为我发现它真的很有趣并且具有挑战性。如果我能解决它,我会更新帖子。我会给@gontard奖励我的javassist方法,因为我'继续我的研究。谢谢。


2921
2018-03-16 03:47


起源

哪个代码正在调用 sort,您的应用程序或图书馆? - Holger
Holger,感谢您的帮助..库(JDiagram)在内部调用sort()方法。由于JAVA 8在各个集合类中引入了sort()方法,并将Collections.sort()委托给了arraylist排序方法,因此库sort()变为覆盖并导致递归...希望它澄清.. - Rahul Winner
非技术选项:请JDiagram的人员使用您正在使用的版本中的sortJ7方法重新编译他们的代码。 - assylias
Assylias:如果可能的话,我不会在这里发布问题!! JDiagram是一个获得许可的api,获得补丁或升级需要更多的时间。我们接近我们的发布,没有时间经历所有升级的法律程序..顺便说一句,我们已经开始与经理讨论,以便我们可以升级我们的下一个版本..如果你有任何其他解决方案,请提出..谢谢.. - Rahul Winner
在获得更新的JDiagram库之前,我会犹豫是否使用Java 8。如果Java 8中肯定存在某些问题,那么可能会有更多潜伏。此外,如果这是一个许可的专有库,任何类型的hacky方法,甚至你所做的反编译可能违反了他们的许可。 - Necreaux


答案:


我用一个基本的例子重现了你的问题:

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
    }
}

我能绕过了 java.lang.StackOverflowError 通过使用重命名方法 了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]
    }
}

我没试过你的版本 JDiagram 因为我的网站上只有最后一个(Java 8兼容)版本。


6
2018-03-18 14:48



谢谢..我还没有尝试过javassist ..你能告诉我它是否也使用了一些javaagent或类似于AspectJ编织的东西,涉及自定义类加载。因为正如我所提到的,我们的类正在运行时加载。此外,ExtendedArrayList由JDiagram实例化,而不是由我的代码实例化。同时我将尝试你的代码并更新线程..再次感谢!! - Rahul Winner
还有一个疑问,Javassist也会在混淆JAR吗? - Rahul Winner
Javassist在没有任何代理的情况下运行,它只是一个库。我不知道混淆是否会改变行为。 - gontard
我能够在具有自己的main()方法的示例程序中修复stackoverflow。但是,我的项目应用程序抛出以下异常:javassist.NotFoundException:com.mindfusion.common.ExtendedArrayList at javassist.ClassPool.get(ClassPool.java:450)显然这是因为我们的项目应用程序加载类的方式。正如我在问题陈述中已经提到的,AspectJ无法为我工作,因为我们在运行时通过另一个组件加载我们的类。然而,我发现Javassist是最好的方法,因为它不涉及任何javagent.thx - Rahul Winner
我还在玩Javassist并试着运气。非常感谢。 - Rahul Winner


想一想 反编译库 并自己解决问题。您可以使用此固定包作为解决方法。

替代 会是的 在您的代码中放置类的固定版本。与库中相同的包和当然相同的类名:

com.mindfusion.common.ExtendedArrayList

也许你必须配置类加载器来加载你的类,而不是先在库中查找错误的类。像“父亲优先”这样的选项或者只是在调用库之前从代码中访问一次类就可以达成交易。


2
2018-03-24 12:23



谢谢Peter,但是这是一个许可的库,它将被视为违规。此外,JAR被混淆,因此sort()的所有引用都无法替换。 - Rahul Winner
好的Rahul,我明白了。但是第二种替代方法(用代码库中的固定版本替换类)是不是仍然有效?您必须找出该类的混淆名称,然后将其放在代码中。 - Peter Wippermann


我看到您的程序加载不受您控制。 执行以下步骤:

  1. 包括 (javassist)该程序可以改变 "sort" 方法名称可以避免覆盖排序方法的任何其他名称

  2. 加载 罐子的 (javassist)主类通过反射 class.forName(""); 在程序的主要方法的开头

  3. 打电话给 罐子的 (javassist)方法在方法上执行所需的更改

这样你就可以确定任何jar(javassist)已经加载并且可以使用了。


1
2018-03-24 15:53



不确定class.forName()是否可以替换已经加载的类..到目前为止,javassist是最好的方法,但JDiagram混淆是阻止解决方案正常工作。 - Rahul Winner
我理解你的答案,但没有详细说明我的评论。我可以在我的程序中获取javassist jar类。但是使用javassist或任何这样的jar,我无法拦截JDiagram类,因为它被混淆了,我们无法控制JDiagram的加载方式。 - Rahul Winner
这是一个有执照的JAR。请完整阅读我的问题。 - Rahul Winner
如果它是一个许可的罐子,这意味着你已经支付了它,那么他们必须提供支持,我想如果你问他们并解释他们将根据你的需要改变代码的情况。
我已经阅读了你的问题,所以如果你要升级jar,那么你的方法排序已经改变了,所以你不需要javassist,只是说,不再需要研究那个部分而浪费你的时间。