Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Incompatible expression types" or verification errors when using ternary expressions with null in one branch #90

Closed
JoshRosen opened this issue May 19, 2019 · 10 comments

Comments

@JoshRosen
Copy link

JoshRosen commented May 19, 2019

Attempting to compile

Integer result = false ? null : 1;

with ScriptEvaluator results in

org.codehaus.commons.compiler.CompileException: Line 1, Column 24: Incompatible expression types "void" and "int"
  org.codehaus.janino.UnitCompiler.compileError(UnitCompiler.java:11877)
  org.codehaus.janino.UnitCompiler.getType2(UnitCompiler.java:6912)
  org.codehaus.janino.UnitCompiler.access$15600(UnitCompiler.java:218)
  org.codehaus.janino.UnitCompiler$22$2.visitConditionalExpression(UnitCompiler.java:6438)
  org.codehaus.janino.UnitCompiler$22$2.visitConditionalExpression(UnitCompiler.java:6414)
  org.codehaus.janino.Java$ConditionalExpression.accept(Java.java:4570)
  org.codehaus.janino.UnitCompiler$22.visitRvalue(UnitCompiler.java:6414)
  org.codehaus.janino.UnitCompiler$22.visitRvalue(UnitCompiler.java:6393)
  org.codehaus.janino.Java$Rvalue.accept(Java.java:4171)
  org.codehaus.janino.UnitCompiler.getType(UnitCompiler.java:6393)
  org.codehaus.janino.UnitCompiler.compileGetValue(UnitCompiler.java:5585)
  org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:2581)
  org.codehaus.janino.UnitCompiler.access$2700(UnitCompiler.java:218)
  org.codehaus.janino.UnitCompiler$6.visitLocalVariableDeclarationStatement(UnitCompiler.java:1506)
  org.codehaus.janino.UnitCompiler$6.visitLocalVariableDeclarationStatement(UnitCompiler.java:1490)
  org.codehaus.janino.Java$LocalVariableDeclarationStatement.accept(Java.java:3577)
  org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:1490)
  org.codehaus.janino.UnitCompiler.compileStatements(UnitCompiler.java:1570)
  org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:3398)
  org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1360)
  org.codehaus.janino.UnitCompiler.compileDeclaredMethods(UnitCompiler.java:1333)
  org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:825)
  org.codehaus.janino.UnitCompiler.compile2(UnitCompiler.java:435)
  org.codehaus.janino.UnitCompiler.access$400(UnitCompiler.java:218)
  org.codehaus.janino.UnitCompiler$2.visitPackageMemberClassDeclaration(UnitCompiler.java:414)
  org.codehaus.janino.UnitCompiler$2.visitPackageMemberClassDeclaration(UnitCompiler.java:409)
  org.codehaus.janino.Java$PackageMemberClassDeclaration.accept(Java.java:1417)
  org.codehaus.janino.UnitCompiler.compile(UnitCompiler.java:409)
  org.codehaus.janino.UnitCompiler.compileUnit(UnitCompiler.java:381)
  org.codehaus.janino.SimpleCompiler.cook(SimpleCompiler.java:237)
  org.codehaus.janino.SimpleCompiler.compileToClassLoader(SimpleCompiler.java:466)
  org.codehaus.janino.ClassBodyEvaluator.compileToClass(ClassBodyEvaluator.java:313)
  org.codehaus.janino.ScriptEvaluator.cook2(ScriptEvaluator.java:725)
  org.codehaus.janino.ScriptEvaluator.cook(ScriptEvaluator.java:714)
  org.codehaus.janino.ScriptEvaluator.cook(ScriptEvaluator.java:661)
  org.codehaus.janino.ScriptEvaluator.cook(ScriptEvaluator.java:629)
  org.codehaus.janino.SimpleCompiler.cook(SimpleCompiler.java:207)
  org.codehaus.commons.compiler.Cookable.cook(Cookable.java:80)
  org.codehaus.commons.compiler.Cookable.cook(Cookable.java:75)

with Janino 3.0.12.

Also, compiling

boolean cond;
Integer result = cond ? null : 1;

generates bytecode that triggers a verification error:

java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    SC.eval0()V @0: iload_0
  Reason:
    Type top (current frame, locals[0]) is not assignable to integer
  Current Frame:
    bci: @0
    flags: { }
    locals: { }
    stack: { }
  Bytecode:
    0x0000000: 1a99 0007 01a7 0004 044c b1

  java.lang.Class.getDeclaredMethods0(Native Method)
  java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
  java.lang.Class.getDeclaredMethods(Class.java:1975)
  org.codehaus.janino.ScriptEvaluator.cook2(ScriptEvaluator.java:746)
  org.codehaus.janino.ScriptEvaluator.cook(ScriptEvaluator.java:714)
  org.codehaus.janino.ScriptEvaluator.cook(ScriptEvaluator.java:661)
  org.codehaus.janino.ScriptEvaluator.cook(ScriptEvaluator.java:629)
  org.codehaus.janino.SimpleCompiler.cook(SimpleCompiler.java:207)
  org.codehaus.commons.compiler.Cookable.cook(Cookable.java:80)
  org.codehaus.commons.compiler.Cookable.cook(Cookable.java:75)

This is a simplified version of a problem that we encountered when generating code in Apache Spark: apache/spark#24636 (comment)

@JoshRosen JoshRosen changed the title 'Incompatible expression types "void" and "int"' "Incompatible expression types" or verification errors when using ternary expressions with null in one branch May 19, 2019
aunkrig added a commit that referenced this issue May 20, 2019
…n using ternary expressions with null in one branch
@aunkrig
Copy link
Member

aunkrig commented May 20, 2019

Thanks for your precise bug description! Actually "b ? 7 : null" is a case that is not defined in JLS7!? Now I implemented it the same way as JAVAC.

Please test!

@JoshRosen
Copy link
Author

Hi @aunkrig,

Thanks for the quick turnaround on this fix. I tried this out with the original Spark code and have run into a new problem:

Caused by: java.lang.VerifyError: Expecting a stackmap frame at branch target 20
Exception Details:
  Location:
    org/apache/spark/sql/catalyst/expressions/GeneratedClass$GeneratedIteratorForCodegenStage1.processNext()V @4: ifne
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: 2ab4 007d 9a00 102a 04b5 007d 2a2a b400
    0x0000010: 0eb8 007f 2ab4 005b 2ab4 0067 949a 0042
    0x0000020: 2ab4 0071 1400 8094 9e00 1614 0080 402a
    0x0000030: 59b4 0071 1400 8065 b500 71a7 0018 2ab4
    0x0000040: 0071 402a 0385 b500 711f 0385 949a 0006
    0x0000050: a701 1b2a 59b4 0067 1f0a 6961 b500 672a
    0x0000060: b400 672a b400 5b65 0a6d 883e 0336 04a7
    0x0000070: 00cd 1504 850a 692a b400 5b61 3705 0336
    0x0000080: 0702 3608 1605 8836 0815 0799 0007 01a7
    0x0000090: 0005 1508 3a09 013a 0a2a b400 8506 32c0
    0x00000a0: 0087 1909 b900 8b02 00c0 008d 3a0a a700
    0x00000b0: 183a 0bbb 0091 592a b400 8505 32c0 0093
    0x00000c0: 190b b700 96bf 190a c600 0703 a700 0404
    0x00000d0: 360c 0236 0d15 0c9a 000a 190a b600 9a36
    0x00000e0: 0d2a b400 2e04 32b6 009f 2ab4 002e 0432
    0x00000f0: b600 a22a b400 2e04 3203 150d b600 a52a
    0x0000100: 2ab4 002e 0432 b600 a9b6 00ad 2ab6 00b1
    0x0000110: 9900 292a 1605 0a61 b500 5b2a b400 8503
    0x0000120: 32c0 00b3 1504 0460 85b6 00b6 2ab4 002a
    0x0000130: 1504 0460 85b6 00bb b184 0401 1504 1da1
    0x0000140: ff33 2a2a b400 67b5 005b 2ab4 0085 0332
    0x0000150: c000 b31d 85b6 00b6 2ab4 002a 1d85 b600
    0x0000160: bb2a b400 1cb6 00be a7fe acb1
  Exception Handler Table:
    bci [153, 174] => handler: 177

You can find the generated Java code in this gist: https://gist.github.com/JoshRosen/416eac9d35bd027475cc787100b0bfe5

Line 100 is where the ternary expression fix was needed: https://gist.github.com/JoshRosen/416eac9d35bd027475cc787100b0bfe5#file-janino_90-java-L100

@aunkrig
Copy link
Member

aunkrig commented May 21, 2019

That's weird... according to the JVMS, the StackMapTable attribute is optional for class files with major version <= 50 (Java 6).
Could you please run java -version and copy the output here?
Also, could you provide the full command line of your JVM please?

@JoshRosen
Copy link
Author

Here's java -version output:

$ java -version
java version "1.8.0_191"
Java(TM) SE Runtime Environment (build 1.8.0_191-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)

Maybe it's a problem with how I did a local mvn install of Janino? That local build was done using the above JDK and not a Java 6 JDK.

Instead, maybe I should set -source and -target explicitly in the Maven compiler plugin?

@aunkrig
Copy link
Member

aunkrig commented May 21, 2019

You may (experimentally) run your JVM with -noverify (or -Xverify:none) and see if it works then...

        // Must not set these to "..._1_7", because then the JVM insists on a StackMapTable (JVMS9 4.7.4), which
        // JANINO currently does not produce:
        //
        //    java.lang.VerifyError: Expecting a stackmap frame at branch target 13
        //
        // ("jre -noverify" removes that requirement, but we cannot force our users to run their JVMs with that
        // option.)
        this.majorVersion  = ClassFile.MAJOR_VERSION_JDK_1_6;
        this.minorVersion  = ClassFile.MINOR_VERSION_JDK_1_6;

@aunkrig
Copy link
Member

aunkrig commented Jun 25, 2019

Ping.

@JoshRosen
Copy link
Author

Spark recently bumped its Janino version to 3.0.13, which contains this fix, but unfortunately it doesn't look like this resolves my issue. I now get a new exception like

[info] ScalaUDFSuite:
[info] - basic *** FAILED *** (1 second, 371 milliseconds)
[info]   Code generation of UDF(1) failed:
[info]   java.lang.VerifyError: Bad type on operand stack
[info]   Exception Details:
[info]     Location:
[info]       org/apache/spark/sql/catalyst/expressions/GeneratedClass$SpecificMutableProjection.apply(Ljava/lang/Object;)Ljava/lang/Object; @6: astore_3
[info]     Reason:
[info]       Type integer (current frame, stack[0]) is not assignable to reference type
[info]     Current Frame:
[info]       bci: @6
[info]       flags: { }
[info]       locals: { 'org/apache/spark/sql/catalyst/expressions/GeneratedClass$SpecificMutableProjection', 'java/lang/Object', 'org/apache/spark/sql/catalyst/InternalRow' }
[info]       stack: { integer }
[info]     Bytecode:
[info]       0x0000000: 2bc0 0018 4d04 4e01 3a04 2ab4 0022 0332
[info]       0x0000010: c000 2404 322a b400 2205 32c0 0026 2db9
[info]       0x0000020: 0028 0200 b900 2802 00c0 001a 3a04 a700
[info]       0x0000030: 183a 05bb 002c 592a b400 2204 32c0 002e
[info]       0x0000040: 1905 b700 32bf 1904 c600 0703 a700 0404
[info]       0x0000050: 3605 0236 0615 059a 000a 1904 b600 3636
[info]       0x0000060: 062a 1505 b500 3a2a 1506 b500 3e2a b400
[info]       0x0000070: 3a9a 0012 2ab4 0012 032a b400 3eb6 0042
[info]       0x0000080: a700 0b2a b400 1203 b600 452a b400 12b0
[info]       0x0000090:
[info]     Exception Handler Table:
[info]       bci [10, 46] => handler: 49

This happens when I change the code generator to

diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/ScalaUDF.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/ScalaUDF.scala
index 3274b66e98..27d08ead7c 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/ScalaUDF.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/ScalaUDF.scala
@@ -1007,10 +1007,8 @@ case class ScalaUDF(
       case ((eval, i), dt) =>
         val argTerm = ctx.freshName("arg")
         val initArg = if (CatalystTypeConverters.isPrimitive(dt)) {
-          val convertedTerm = ctx.freshName("conv")
           s"""
-             |${CodeGenerator.boxedType(dt)} $convertedTerm = ${eval.value};
-             |Object $argTerm = ${eval.isNull} ? null : $convertedTerm;
+             |Object $argTerm = ${eval.isNull} ? null : ${eval.value};
            """.stripMargin
         } else {
           s"Object $argTerm = ${eval.isNull} ? null : $convertersTerm[$i].apply(${eval.value});"

@aunkrig
Copy link
Member

aunkrig commented Dec 30, 2019

Would you please set a system property -DUnitCompiler.disassembleClassFilesToStdout=true and retry? This should print disassemblies of all generated .class files to STDOUT, so we can analyze why this causes a VerifyError.

@HeartSaVioR
Copy link
Contributor

// Class file version = 50.0 (J2SE 6.0)

package org.apache.spark.sql.catalyst.expressions;

class GeneratedClass$SpecificMutableProjection extends org.apache.spark.sql.catalyst.expressions.codegen.BaseMutableProjection {

    // Enclosing/enclosed types:
    //   GeneratedClass { synchronized class GeneratedClass$SpecificMutableProjection }

    Object[]                                 references;

    org.apache.spark.sql.catalyst.InternalRow mutableRow;

    int                                      value_2;

    boolean                                  isNull_2;

    GeneratedClass                           this$0;

    public void initialize(int p0) {
        // Line 19
        return
    }

    public org.apache.spark.sql.catalyst.expressions.codegen.BaseMutableProjection target(org.apache.spark.sql.catalyst.InternalRow p0) {
        // Line 24
        aload_0         [this]
        aload_1         [p0]
        putfield        org.apache.spark.sql.catalyst.InternalRow GeneratedClass$SpecificMutableProjection.mutableRow
        // Line 25
        aload_0         [this]
        // Line 24
        areturn
    }

    public org.apache.spark.sql.catalyst.InternalRow currentValue() {
        // Line 30
        aload_0         [this]
        getfield        org.apache.spark.sql.catalyst.InternalRow GeneratedClass$SpecificMutableProjection.mutableRow
        // Line 29
        areturn
    }

    public Object apply(Object p0) {
        // Line 34
        aload_1         [p0]
        checkcast       org.apache.spark.sql.catalyst.InternalRow
        astore_2        [v2]
        // Line 37
        iconst_1
        astore_3        [v3]
        // Line 40
        aconst_null
        astore          [v4]
        try {
            // Line 42
            aload_0         [this]
            getfield        Object[] GeneratedClass$SpecificMutableProjection.references
            iconst_0
            aaload
            checkcast       scala.Function1[]
            iconst_1
            aaload
            aload_0         [this]
            getfield        Object[] GeneratedClass$SpecificMutableProjection.references
            iconst_2
            aaload
            checkcast       scala.Function1
            aload_3         [v3]
            invokeinterface scala.Function1.apply(Object) => Object
            invokeinterface scala.Function1.apply(Object) => Object
            checkcast       Integer
            astore          [v4]
        } catch (Exception => #49)
        // Line 40
        goto            #70
#49
        // Line 43
        astore          [v5]
        // Line 44
        new             org.apache.spark.SparkException
        dup
        aload_0         [this]
        getfield        Object[] GeneratedClass$SpecificMutableProjection.references
        iconst_1
        aaload
        checkcast       String
        aload           [v5]
        invokespecial   org.apache.spark.SparkException(String, Throwable)
        // Line 43
        athrow
#70
        // Line 48
        aload           [v4]
        ifnull          #79
        iconst_0
        goto            #80
#79
        iconst_1
#80
        istore          [v5]
        // Line 49
        iconst_m1
        istore          [v6]
        // Line 50
        iload           [v5]
        ifne            #97
        // Line 51
        aload           [v4]
        invokevirtual   Integer.intValue() => int
        istore          [v6]
#97
        // Line 53
        aload_0         [this]
        iload           [v5]
        putfield        boolean GeneratedClass$SpecificMutableProjection.isNull_2
        // Line 54
        aload_0         [this]
        iload           [v6]
        putfield        int GeneratedClass$SpecificMutableProjection.value_2
        // Line 58
        aload_0         [this]
        getfield        boolean GeneratedClass$SpecificMutableProjection.isNull_2
        ifne            #131
        // Line 59
        aload_0         [this]
        getfield        org.apache.spark.sql.catalyst.InternalRow GeneratedClass$SpecificMutableProjection.mutableRow
        iconst_0
        aload_0         [this]
        getfield        int GeneratedClass$SpecificMutableProjection.value_2
        invokevirtual   org.apache.spark.sql.catalyst.InternalRow.setInt(int, int)
        // Line 54
        goto            #139
#131
        // Line 61
        aload_0         [this]
        getfield        org.apache.spark.sql.catalyst.InternalRow GeneratedClass$SpecificMutableProjection.mutableRow
        iconst_0
        invokevirtual   org.apache.spark.sql.catalyst.InternalRow.setNullAt(int)
#139
        // Line 64
        aload_0         [this]
        getfield        org.apache.spark.sql.catalyst.InternalRow GeneratedClass$SpecificMutableProjection.mutableRow
        // Line 62
        areturn
    }

    public GeneratedClass$SpecificMutableProjection(GeneratedClass p0, Object[] p1) {
        // Line 12
        aload_0         [this]
        invokespecial   org.apache.spark.sql.catalyst.expressions.codegen.BaseMutableProjection()
        aload_0         [this]
        aload_1         [p0]
        putfield        GeneratedClass GeneratedClass$SpecificMutableProjection.this$0
        // Line 13
        aload_0         [this]
        aload_2         [p1]
        putfield        Object[] GeneratedClass$SpecificMutableProjection.references
        // Line 14
        aload_0         [this]
        new             GenericInternalRow
        dup
        iconst_1
        invokespecial   GenericInternalRow(int)
        putfield        org.apache.spark.sql.catalyst.InternalRow GeneratedClass$SpecificMutableProjection.mutableRow
        // Line 12
        return
    }

    // (Synthetic method)
    public MutableProjection target(org.apache.spark.sql.catalyst.InternalRow p0) {
        aload_0         [this]
        aload_1         [p0]
        invokevirtual   GeneratedClass$SpecificMutableProjection.target(org.apache.spark.sql.catalyst.InternalRow) => org.apache.spark.sql.catalyst.expressions.codegen.BaseMutableProjection
        areturn
    }
}

It's easily reproducible with below test code:

    @Test public void
    testIssue() throws Exception {
        ExpressionEvaluator ee = new ExpressionEvaluator();
        ee.setExpressionType(Object.class);
        ee.cook("false ? null : 1");
    }

which fails on 3.0.15 with same error message, and passes on master branch.

I guess compiler has to deal with auto boxing which was missing on 3.0.15 and somehow addressed recently.

If we don't have some test for this, I feel it would be good to have one to prevent regression. WDYT?

@HeartSaVioR
Copy link
Contributor

@JoshRosen FYI the problem seems to be fixed in master branch of Janino - your patch seems to be simple so just ran with recent master branch of Spark & Janino, and it passed.

I feel Spark community would like to upgrade Janino to the new release anyway due to #113 - once we upgrade the Janino your patch would work. (Though I'd love to see explicit conversion as the boxed type would be completely determined by the type of constant which ends up with not-intended type; casting from right side of ternary might do the same safely.)

dongjoon-hyun pushed a commit to apache/spark that referenced this issue May 29, 2020
### What changes were proposed in this pull request?

This PR proposes to upgrade Janino to 3.1.2 which is released recently.

Major changes were done for refactoring, as well as there're lots of "no commit message". Belows are the pairs of (commit title, commit) which seem to deal with some bugs or specific improvements (not purposed to refactor) after 3.0.15.

* Issue #119: Guarantee executing popOperand() in popUninitializedVariableOperand() via moving popOperand() out of "assert"
* Issue #116: replace operand to final target type if boxing conversion and widening reference conversion happen together
* Merged pull request `#114` "Grow the code for relocatables, and do fixup, and relocate".
  * janino-compiler/janino@367c58e
* issue `#107`: Janino requires "org.codehaus.commons.compiler.io", but commons-compiler does not export this package
  * janino-compiler/janino@f7d9959
* Throw an NYI CompileException when a static interface method is invoked.
  * janino-compiler/janino@efd3884
* Fixed the promotion of the array access index expression (see JLS7 15.13 Array Access Expressions)
  * janino-compiler/janino@32fdb5f
* Issue `#104`: ClassLoaderIClassLoader 's ClassNotFoundException handle mechanism enhancement
  * janino-compiler/janino@6e8a97d

You can see the changelog from the link: http://janino-compiler.github.io/janino/changelog.html

### Why are the changes needed?

We got some report on failure on user's query which Janino throws error on compiling generated code. The issue is here: janino-compiler/janino#113 It contains the information of generated code, symptom (error), and analysis of the bug, so please refer the link for more details.
Janino 3.1.1 contains the PR janino-compiler/janino#114 which would enable Janino to succeed to compile user's query properly. I've also fixed a couple of more bugs as 3.1.1 made Spark UTs fail - hence we need to upgrade to 3.1.2.

Furthermore, from my testing, janino-compiler/janino#90 (which Josh Rosen filed before) seems to be also resolved in 3.1.2 as well.

Looks like Janino is maintained by one person and there's no even version branches and releases/tags so we can't expect Janino maintainer to release a new bugfix version - hence have to try out new minor version.

### Does this PR introduce any user-facing change?

No.

### How was this patch tested?

Existing UTs.

Closes #27860 from HeartSaVioR/SPARK-31101.

Authored-by: Jungtaek Lim (HeartSaVioR) <kabhwan.opensource@gmail.com>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
@aunkrig aunkrig closed this as completed Jun 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants