Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8208482

javac infers type Supplier<?> for lambda that throws

    Details

    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      windows_10

      Description

      ADDITIONAL SYSTEM INFORMATION :
      Tested on 64-bit Windows 10 build 17713 with jdk-11-ea+24, jdk-10.0.2, and jdk1.8u181.

      A DESCRIPTION OF THE PROBLEM :
      If there are two possible targets for a lambda (say, an overloaded method), one which is Runnable, and one which is a Supplier<?>, and the lambda does not return anything, but does return abnormally i.e. contains an unconditional throw statement, the compiler infers that the lambda is a Supplier even though a lambda which returns void can never be a Supplier.

      The code won't actually produce a ClassCastException since calling get() on the Supplier will always throw, and any attempts I have made to alter the code to avoid throwing have caused it to switch back to compiling it to Runnable. However, I worry that I have not been clever enough, and there may be a way to get it to cause a ClassCastException at runtime after all.

      Originally found by StackOverflow user "Gili" in this question: https://stackoverflow.com/questions/51577332/why-does-a-lambda-change-overloads-when-it-throws-a-runtime-exception#51577332

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Compile the attached test case, then examine the bytecode with javap -c Bug

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Both the call with the lambda that prints and the call with the lambda that throws should be Runnable.
      ACTUAL -
      The first call is with a Runnable, but the second call is with a Supplier<Integer>, even though the lambda cannot possibly be a Supplier<Integer>. The bytecode for the main method produced by javac from jdk-11 is as follows:

        public static void main(java.lang.String[]);
          Code:
             0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
             5: invokestatic #3 // Method method:(Ljava/lang/Runnable;)V
             8: invokedynamic #4, 0 // InvokeDynamic #1:get:()Ljava/util/function/Supplier;
            13: invokestatic #5 // Method method:(Ljava/util/function/Supplier;)V
            16: return

      The first call is with Runnable, as expected. The second call, with Supplier, is erroneous.

      ---------- BEGIN SOURCE ----------
      import java.util.function.Supplier;

      public class Bug {
          public static void method(Runnable runnable) { }

          public static void method(Supplier<Integer> supplier) { }

          public static void main(String[] args) {
              method(() -> System.out.println());
              method(() -> { throw new RuntimeException(); });
          }
      }

      ---------- END SOURCE ----------

      FREQUENCY : always


        Attachments

          Activity

            People

            • Assignee:
              fmatte Fairoz Matte
              Reporter:
              webbuggrp Webbug Group
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: