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