Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bad type on operand stack ... using jdk 8, lambdas with anonymous inner classes fails, why?

Running the code below results in the error message Bad type on operand stack.

public static void main(String args[]) {
        TransformService transformService = (inputs) -> {
            return new ArrayList<String>(3) {{
                add("one");
                add("two");
                add("three");
            }};
        };

        Collection<Integer> inputs = new HashSet<Integer>(2) {{
            add(5);
            add(7);
        }};
        Collection<String> results = transformService.transform(inputs);
        System.out.println(results.size());
    }

    public interface TransformService {
        Collection<String> transform(Collection<Integer> inputs);
    }

However removing the double brace initialization (anonymous inner classes) within the lamda allows the code to run as expected, why ? The below works :

public class SecondLambda {
    public static void main(String args[]) {
        TransformService transformService = (inputs) -> {
            Collection<String> results = new ArrayList<String>(3);
            results.add("one");
            results.add("two");
            results.add("three");

            return results;
        };

        Collection<Integer> inputs = new HashSet<Integer>(2) {{
            add(5);
            add(7);
        }};
        Collection<String> results = transformService.transform(inputs);
        System.out.println(results.size());
    }

    public interface TransformService {
        Collection<String> transform(Collection<Integer> inputs);
    }
}

Compiler bug ? It is the early access version after all ...

(This won't compile unless you have the latest jdk 8 lambda download.)

like image 743
NimChimpsky Avatar asked Nov 04 '12 14:11

NimChimpsky


3 Answers

It seems, that problem occurs not only in case when lambda returns anonymous type, but even if any anonymous class is constructed inside lambda. I.e.:

public class TestLambda {
    public static void main(String[] args) {
        xxx();
    }
    static void xxx() {
        Functional1 f  = () -> {
            Object o = new Object() { };
            return new A();
        };
    }
    static class A { }
    static interface Functional1 { A func(); }
}

This actually leads to Exception in thread "main" java.lang.VerifyError: Bad local variable type (...) Reason: Type top (current frame, locals[0]) is not assignable to reference type.

Further investigation shows, that if we will introduce parameter into method xxx, the reason for an exception will contains its type. E.g.:

Type 'java/lang/Integer' (current frame, stack[0]) is not assignable to 'lambda/TestLambda'

And this is already very interesting. Let's change type of xxx parameter (which is not actually used) to type of top class, i.e. TestLambda:

...
    xxx(new TestLambda());
}
private static void xxx(TestLambda x) {
...

And what do you think? This fixes the problem! Everything begin work well. Even, if we will change return A(); to return new A() {};. Check this!


My conclusion is that this is real JVM bug. It seems, that the problem is with stack of loaded classes. It occurs in combine with method, which Java uses for translating lambda expressions (http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html) - it produces synthetic methods inside top class. It seems, that when anonymous classes are introduced in lambda stack becomes broken. It could be fixed using mentioned workaround.

like image 120
Andremoniy Avatar answered Oct 19 '22 20:10

Andremoniy


Compiler bug ? It is the early access version after all ...

I would say that any error message that mentions the operand stack is highly likely to be a due to a compiler bug or a bug in the JVM. Especially if you can get it using a pure Java example.

(It looks like the JVM is reporting a typesafety problem that should have been detected by the compiler, and/or the bytecode verifier at class-load time.)

Report it via the recommended channel for Java 8 bugs.

like image 44
Stephen C Avatar answered Oct 19 '22 20:10

Stephen C


Not directly related to your issue, but I'd strongly recommend not using anonymous classes in this way. You're creating an entirely new HashSet subtype solely for the purpose of adding two values to it. Not only does this bloat up the system (it stays in memory forever) it can also confound the JVM's JIT since it never just sees HashSet at a call site...it sees one of many subtypes you've created.

like image 1
Charles Oliver Nutter Avatar answered Oct 19 '22 19:10

Charles Oliver Nutter