You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Eric Milles (Jira)" <ji...@apache.org> on 2025/10/12 14:15:00 UTC

[jira] [Assigned] (GROOVY-11782) Inconsistent unboxing of null, and resulting exceptions

     [ https://issues.apache.org/jira/browse/GROOVY-11782?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Eric Milles reassigned GROOVY-11782:
------------------------------------

    Assignee: Eric Milles

> Inconsistent unboxing of null, and resulting exceptions
> -------------------------------------------------------
>
>                 Key: GROOVY-11782
>                 URL: https://issues.apache.org/jira/browse/GROOVY-11782
>             Project: Groovy
>          Issue Type: Bug
>         Environment: JDK21
> Groovy 4.0.26
>            Reporter: Val E
>            Assignee: Eric Milles
>            Priority: Minor
>
> So I ran into a weird optimization bug, where an expected exception gets thrown once for an incorrect invocation, will then produce 0 for the exact same incorrect invocations, if its followed by a 'correct' invocation. 
>  
> Attached is a super basic pure java class, with a very basic arithmetic method
> {code:java}
> package test;
> public class JavaTarget {
>     public static double primitiveMethod(double value) {
>         System.out.println("\tJavaTarget.primitiveMethod v="+value);
>         return value - 3.0;
>     }
> }{code}
> The innards of the class are not important, but do note that is take a primitive double param and returns a primitive double after subtracting 3 from the param
> Now when groovy tries to call this class, from a closure, with a Double param, we generally expect type coercion, boxing and unboxng, to kick in and everything should still work, and it does under normal circumstances. 
> However the bug occurs when you call with a Double v= null param, in which case type coercion fails, because we cant unbox a null to a double, as expected. If you then call it with a good Double, ex Double v=2.0; all subsequent calls with Double v=null will be coerced to 0 and no more exception will be thrown.
> Test groovy script to illustrate; again vary basic loop 3 times, call java target with null and with a 2.0
> {code:java}
> Closure javaTargetCaller = {Double v ->
>     println "\tcall JavaTarget.primitiveMethod($v)"
>     JavaTarget.primitiveMethod(v)
> }
> for(int i = 0; i<3; i++){
>     try{
>         println "CALL[$i] | with null"
>         Object a = javaTargetCaller(null)
>         println "\tresult with v=null | *NOT-OK* | expecting exception, got "+a+" --"+a.getClass()
>     }catch(Exception x){
>         println "\tresult with v=null| *OK* |expecting exception, got exception --\n"+x
>     }
>     println "CALL[$i] | with v=-2"
>     Object b = javaTargetCaller(-2)
>     println "\tresult with v=-2: expecting -5.0 : got "+b
>     assert b==-5
> }{code}
> You will note the first iteration the call will produce an exception as expected, however the next call with a param of -2, will cache something in the dynamic resolution, and all subsequent calls produce a number, 
> {panel:title=Output}
> CALL[0] | with null
>   call JavaTarget.primitiveMethod(null)
>   result with v=null| *OK* |expecting exception, got exception –
>   groovy.lang.MissingMethodException: No signature of method: static test.JavaTarget.primitiveMethod() is applicable for argument types: (null) values: [null]
> Possible solutions: primitiveMethod(double)
> CALL[0] | with v=-2
>   call JavaTarget.primitiveMethod(-2.0)
>   JavaTarget.primitiveMethod v=-2.0
>   result with v=-2: expecting -5.0 : got -5.0
> CALL[1] | with null
>   call JavaTarget.primitiveMethod(null)
>   JavaTarget.primitiveMethod v=0.0
>   result with v=null | *NOT-OK* | expecting exception, got -3.0 --class java.lang.Double
> CALL[1] | with v=-2
>   call JavaTarget.primitiveMethod(-2.0)
>   JavaTarget.primitiveMethod v=-2.0
>   result with v=-2: expecting -5.0 : got -5.0
> CALL[2] | with null
>   call JavaTarget.primitiveMethod(null)
>   JavaTarget.primitiveMethod v=0.0
>   result with v=null | *NOT-OK* | expecting exception, got -3.0 --class java.lang.Double
> CALL[2] | with v=-2
>   call JavaTarget.primitiveMethod(-2.0)
>   JavaTarget.primitiveMethod v=-2.0
>   result with v=-2: expecting -5.0 : got -5.0
> {panel}
> -note that the very first call indeed failed to unbox the null and threw an exception, however immediately after the second correct call, the nulls start getting unboxed to 0 and no more exception get thrown.
> -it gets more inconsistent, if you change the order and call with a -2 first, then the nulls start getting unboxed to 0 immediately, and no exception is thrown ever
> Almost like some site call somewhere learns how to unbox a Double null, but it depends on the order in which it will be called.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)