Причина исключения java.lang.VerifyError: Плохой тип в стеке операнда

Следующий простой Java-код отправляет java.lang.VerifyError: неверный тип в стеке операнда

public class TestJavaCodes {

    int parentData = 0;

    public void init() {
        A ob = new B();
    }

    public static void main(String[] args) {

        TestJavaCodes testJavaCodes = new TestJavaCodes();
        testJavaCodes.init();
    }

    public static class A {
        public A(MyLambdaFunc lambdaFunc) {
        }
    }

    public class B extends A {

        public B() {
            super((data1, type) -> {
                parentData = 1;
            });
        }
    }

    @FunctionalInterface
    public static interface MyLambdaFunc {
        public void onData(String data, int type);
    }
}

Если я удалю код

parentData = 1

из конструктора B, исключение не наступит.

Может ли кто-нибудь объяснить причину этого?

Ответ 1

Проблема возникает из-за того, что выражение лямбда не ссылается на this или член this, а на элемент внешнего this. Если бы вы написали класс B как

public class B extends A {
    int innerData;
    public B() {
        super((data1, type) -> innerData = 1);
    }
}

компилятор отклонил его без каких-либо сомнений, поскольку доступ к innerData подразумевает доступ к this.

Пункт внешнего экземпляра заключается в том, что он является константой, которая даже доступна, когда внутренний экземпляр еще не был полностью сконструирован. Так что правильно принять код, но, к сожалению, компилятор генерирует код, который пытается получить доступ к внешнему экземпляру через неявное поле внутреннего экземпляра класса, поэтому выражение лямбда требует экземпляра внутреннего класса и пытается использовать не полностью построенный внутренний экземпляр класса создает ошибку.

Можно легко продемонстрировать, что код может быть скомпилирован правильно:

public class B extends A {
    public B() {
        this(TestJavaCodes.this);
    }
    private B(TestJavaCodes outer) {
        super((data1, type) -> outer.parentData = 1);
    }
}

с этим небольшим изменением, лямбда-выражение относится к внешнему экземпляру, не обращаясь к внутреннему экземпляру, и ошибка не возникает.

Ответ 2

Кажется, что такой код вообще не должен компилироваться. Я минимизировал ваш код:

public class CompilerBug {
    int var = 0;

    public static void main(String[] args) {
        new CompilerBug().new Inner();
    }

    public class Inner {
        public Inner(Runnable r) {}

        public Inner() {
            this(() -> {
                var = 1;
            });
        }
    }
}

Он скомпилирован без проблем javac 1.8.0.25, 1.8.0.40 и 1.9b57. Каждая скомпилированная версия производит тот же вывод при запуске:

Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    CompilerBug$Inner.<init>(LCompilerBug;)V @3: invokedynamic
  Reason:
    Type uninitializedThis (current frame, stack[2]) is not assignable to 'CompilerBug$Inner'
  Current Frame:
    bci: @3
    flags: { flagThisUninit }
    locals: { uninitializedThis, 'CompilerBug' }
    stack: { uninitializedThis, 'CompilerBug', uninitializedThis }
  Bytecode:
    0000000: 2a2b 2aba 0003 0000 b700 04b1

        at CompilerBug.main(CompilerBug.java:5)

Этот код не компилируется компилятором ECJ. Он сообщает об ошибке компиляции:

----------
1. ERROR in C:\projects\Test\src\CompilerBug.java (at line 12)
    this(() -> {
         ^^^^^
Cannot refer to 'this' nor 'super' while explicitly invoking a constructor
----------
1 problem (1 error)

Таким образом, он выглядит как ошибка в компиляторе javac: вместо этого он должен возвращать ошибку компиляции (например, ECJ).

Я не нашел подобную ошибку в OpenJDK-трекер ошибок, поэтому представил новый отчет об ошибке через веб-форму. Если люди Java читают это, идентификатор внутреннего обзора назначается JI-9021379.

Обновление: Отчет об ошибке принимается (JDK-8129740)