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

javac infers type Supplier<?> for lambda that throws

    XMLWordPrintable

Details

    • x86_64
    • 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

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

            Dates

              Created:
              Updated:
              Resolved: