问题 为什么编译此代码会导致编译器堆栈溢出?


interface Pong<T> {}
class Ping<T> implements Pong<Pong<? super Ping<Ping<T>>>> {
    static void Ping() {
        Pong<? super Ping<Long>> Ping = new Ping<Long>();
    }
}

尝试编译这会给出错误:

The system is out of resources.
Consult the following stack trace for details.
java.lang.StackOverflowError
    at com.sun.tools.javac.code.Types$23.visitClassType(Types.java:2579)
    at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:554)
    at com.sun.tools.javac.code.Types$UnaryVisitor.visit(Types.java:3260)
    at com.sun.tools.javac.code.Types$23.visitClassType(Types.java:2592)
    at com.sun.tools.javac.code.Types$23.visitClassType(Types.java:2579)
    at com.sun.tools.javac.code.Type$ClassType.accept(Type.java:554)
    ...

代码由github上的etorreborre提供。


7868
2017-11-23 09:46


起源



答案:


显然,这是Java编译器中的一个错误。编译器不应该崩溃,尤其是在如此小的程序上。

可以 甚至是Java语言规范中的漏洞;即JLS作者未考虑的泛型中的一个模糊边缘案例。

但是(IMO)这只不过是一种好奇心,除非你能想出一个不太明显的例子 做作 打破编译器。我的意思是,这个示例代码并不完全有意义......


对Java编译器实现有深刻理解的人可能会弄清楚为什么会导致堆栈溢出。但除非那个人也要修复这个bug,否则它几乎不相关。除非有人能够提出一个触发相同问题的有意义的例子,否则我认为修复它没有任何价值。


6
2017-11-23 10:04



即使好奇心确实是一个错误,应该提交: bugreport.sun.com/bugreport - Matteo
@Matteo - 如果你这么认为,那就去吧。但是,如果错误被赋予了非常低的优先权并且没有保留,我不会感到惊讶。 - Stephen C
我会尝试,但我需要在不同的操作系统上进行测试(Oracle不支持Mac OS X:我应该通过Apple提交文件,但之后修复的可能性非常低:-) - Matteo
显然Java的类型系统是不可判定的。看到 cs.cornell.edu/~ross/publications/tamewild/...,这里讨论了这个例子。 - michid


答案:


显然,这是Java编译器中的一个错误。编译器不应该崩溃,尤其是在如此小的程序上。

可以 甚至是Java语言规范中的漏洞;即JLS作者未考虑的泛型中的一个模糊边缘案例。

但是(IMO)这只不过是一种好奇心,除非你能想出一个不太明显的例子 做作 打破编译器。我的意思是,这个示例代码并不完全有意义......


对Java编译器实现有深刻理解的人可能会弄清楚为什么会导致堆栈溢出。但除非那个人也要修复这个bug,否则它几乎不相关。除非有人能够提出一个触发相同问题的有意义的例子,否则我认为修复它没有任何价值。


6
2017-11-23 10:04



即使好奇心确实是一个错误,应该提交: bugreport.sun.com/bugreport - Matteo
@Matteo - 如果你这么认为,那就去吧。但是,如果错误被赋予了非常低的优先权并且没有保留,我不会感到惊讶。 - Stephen C
我会尝试,但我需要在不同的操作系统上进行测试(Oracle不支持Mac OS X:我应该通过Apple提交文件,但之后修复的可能性非常低:-) - Matteo
显然Java的类型系统是不可判定的。看到 cs.cornell.edu/~ross/publications/tamewild/...,这里讨论了这个例子。 - michid


因为编译器无法决定是否 Long 是一个 Pong 那就是 super 一个 Ping 一个 Ping 一个 Long 或者是否是 Ping 一个 Ping 延伸的东西 Pong 一个 Pong ......但我可能错了。


6
2017-11-23 10:08



你扭曲了我的舌头...... *ouch* - Shawn Chin
这是严重的编译问题.. - Fairoz


我有一个同事在实际代码中有类似的问题。在那里,他有一个带有2个类型参数的抽象基类,它有两个子类,将它们固定到具体类型。基本上,这允许在抽象类中定义完整的逻辑,而不必在具有交换的具体类型的两个子类中复制代码。

基本上,代码是这样的:

public abstract class AImpl<X extends A<Y>, Y extends A<X>> {
    public X foo(Y o) {
        return o.doStuff();
    }

    public Y bar(X o) {
        return o.doStuff();
    }
}

class VImpl extends AImpl<V, E> {}
class EImpl extends AImpl<E, V> {}

interface A<T> {
    T doStuff();
}

interface V extends A<E> {}
interface E extends A<V> {}

这段代码实际编译。实际上,不仅有2个子类,而是更深层次的类型,例如VImpl和EImpl的三个变体,每个变体都有任意多个子类。嗯,实际上,有3种类型参数,如上所示,限制有点复杂。

当VImpl和EImpl只有两个变种时,它仍然编译得很好。一旦添加了第三个变体,他就会在编译器中获得堆栈溢出。也就是说,Eclipse编译器仍然能够编译代码,因此它似乎很简单 javac的 递归地做一些事情,它应该更好地迭代地做。

不幸的是,我们无法将完整的代码库缩减为适合错误报告的一些最小示例...


2
2018-01-10 16:14





我在JDK 8_u25中使用了一些通用的东西, 更新到JDK 8u_65 解决了这个问题。


0
2018-01-13 08:41



似乎bug在jdk1.8.0_72中再次存在? - MGorgon