ClassCastExceptions with JDK9 javac
See original GitHub issueJDK 9 fixes a javac bug (JDK-8058199) that was causing checkcast intructions to be skipped. Previously javac used the parameter types of a method symbol’s erased type as targets when translating the arguments. In JDK 9, javac has been fixed to use the inferred types as targets. The fix causes additional checkcasts to be generated if the inferred types do not have the same erasure.
The fix breaks Mockito answer strategies that pick types based on the erased method signature’s return type, and causes tests to fail with ClassCastExceptions when compiled with the JDK 9 javac.
Example 1
class Super<T> {
T g() {
return null;
}
}
class Sub extends Super<Boolean> {}
@Mock Sub s;
when(s.g()).thenReturn(false);
compiled with javac 8
INVOKEVIRTUAL Sub.g ()Ljava/lang/Object;
INVOKESTATIC org/mockito/Mockito.when (Ljava/lang/Object;)Lorg/mockito/stubbing/OngoingStubbing;
compiled with javac 9
INVOKEVIRTUAL Sub.g ()Ljava/lang/Object;
CHECKCAST java/lang/Boolean
INVOKESTATIC org/mockito/Mockito.when (Ljava/lang/Object;)Lorg/mockito/stubbing/OngoingStubbing;
The erased return type of Super.g
is Object
, but the expected return type of Sub.g
is Boolean
. If the answer strategy returns Object
the checkcast fails.
Example 2
class Foo {
<T> T getFirst(Iterable<T> xs) { return xs.iterator().next(); }
}
@Mock Foo f;
Iterable<Boolean> it = Arrays.asList(false);
when(f.getFirst(it)).thenReturn(false)
compiled with javac 8
INVOKEVIRTUAL Foo.getFirst (Ljava/lang/Iterable;)Ljava/lang/Object;
INVOKESTATIC org/mockito/Mockito.when (Ljava/lang/Object;)Lorg/mockito/stubbing/OngoingStubbing;
compiled with javac 9
INVOKEVIRTUAL Foo.getFirst (Ljava/lang/Iterable;)Ljava/lang/Object;
CHECKCAST java/lang/Boolean
INVOKESTATIC org/mockito/Mockito.when (Ljava/lang/Object;)Lorg/mockito/stubbing/OngoingStubbing;
The erased return type of Foo.getFirst
is Object
, but the inferred return type of getFirst(Iterable<Boolean>)
is Boolean
. If the answer strategy returns Object
the checkcast fails.
The first example could be fixed by using GenericMetadataSupport
in all of the answer implementations instead of invocation.getMethod().getReturnType()
.
It gets more difficult if the mock’s type is an instantiation of a generic type (e.g. @Mock Foo<Bar> x;
), since the field’s type arguments get dropped. I think fixing that would require adding support for mocking types, not just classes.
For the second example, returning the right answer requires considering the generic signature of the invoked method, and performing type inference using the argument types. Unfortunately the runtime type of the argument is going to be a raw Iterable
and the inference depends on knowing it’s Iterable<Boolean>
, so I’m not sure what to do there.
Issue Analytics
- State:
- Created 8 years ago
- Reactions:2
- Comments:23 (22 by maintainers)
Top GitHub Comments
Only when returning automatic mocks from mocks with generic return types, maybe we should add a warning for this to make the error more intuitive.
There’s an Error Prone check that detects this problem at compile-time: http://errorprone.info/bugpattern/MockitoCast. It can also be run as a refactoring to add work-arounds to affected code.