Why does compiling this code cause a compiler stac

2020-06-03 03:48发布

问题:

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

Trying to compile this gives the error:

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)
    ...

Code courtesy of etorreborre on github.

回答1:

Clearly, it is a bug in the Java compiler. The compiler shouldn't crash, especially on a program so small.

It could even be a hole in the Java Language Specification; i.e. an obscure edge case in generics that the JLS authors haven't considered.

But (IMO) this is nothing more than a curiosity, unless you can come up with an example that isn't so obviously contrived to break the compiler. I mean, this example code isn't exactly meaningful ...


Someone with a deep understanding of the Java compiler's implementation could probably figure out why this causes a stack overflow. But it is hardly relevant unless that person is also going to fix the bug. And unless someone can come up with a meaningful example that triggers the same problem, I can't see any value in fixing it.



回答2:

Because the compiler cannot decide whether a Long is a Pong that is the super of a Ping of a Ping of a Long or whether it is a Ping of a Ping of something that extends a Pong of a Pong ... but I may be wrong.



回答3:

I have a collegue which has a similar problem in real code. There, he has an abstract base class with 2 type parameters that has two subclasses fixing them to concrete types. Basically, this allows for defining the complete logic in the abstract class instead of having to duplicate the code in both subclasses with swapped concrete types.

Basically, the code is something along these lines:

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

This code actually compiles. In reality, there are not just 2 subclasses, but a deeper type hierarchy, e.g., three variants of VImpl and EImpl, each of which have arbitrary many subclasses. Well, and actually, there are 3 type parameters, and the restrictions are a bit more complicated as shown above.

When there were only two variants of VImpl and EImpl, it still compiled fine. As soon as the third variants were added, he got that stack overflow in the compiler. That said, the Eclipse compiler is still able to compile the code, so it seems that it's simply javac doing something recursively which it should better do iteratively.

Unfortunately, we are not able to reduce the complete code base to some minimal example suitable for a bug report...



回答4:

I experienced the same with some generic stuff in JDK 8_u25, updating to JDK 8u_65 resolved the issue.