2121
2222import java .io .IOException ;
2323import java .io .ObjectInputStream ;
24+ import java .io .Serializable ;
2425import java .lang .annotation .Annotation ;
2526import java .lang .reflect .Method ;
2627import java .lang .reflect .Modifier ;
2728import java .lang .reflect .Type ;
28- import java .util .ArrayList ;
29- import java .util .Iterator ;
30- import java .util .Random ;
29+ import java .util .*;
30+
3131import net .bytebuddy .ByteBuddy ;
3232import net .bytebuddy .description .method .MethodDescription ;
3333import net .bytebuddy .description .modifier .SynchronizationState ;
3939import net .bytebuddy .implementation .Implementation ;
4040import net .bytebuddy .implementation .attribute .MethodAttributeAppender ;
4141import net .bytebuddy .matcher .ElementMatcher ;
42+ import net .bytebuddy .utility .GraalImageCode ;
43+ import net .bytebuddy .utility .RandomString ;
4244import org .mockito .codegen .InjectionBase ;
4345import org .mockito .exceptions .base .MockitoException ;
4446import org .mockito .internal .creation .bytebuddy .ByteBuddyCrossClassLoaderSerializationSupport .CrossClassLoaderSerializableMock ;
@@ -52,7 +54,6 @@ class SubclassBytecodeGenerator implements BytecodeGenerator {
5254 private final SubclassLoader loader ;
5355 private final ModuleHandler handler ;
5456 private final ByteBuddy byteBuddy ;
55- private final Random random ;
5657 private final Implementation readReplace ;
5758 private final ElementMatcher <? super MethodDescription > matcher ;
5859
@@ -82,8 +83,7 @@ protected SubclassBytecodeGenerator(
8283 this .readReplace = readReplace ;
8384 this .matcher = matcher ;
8485 byteBuddy = new ByteBuddy ().with (TypeValidation .DISABLED );
85- random = new Random ();
86- handler = ModuleHandler .make (byteBuddy , loader , random );
86+ handler = ModuleHandler .make (byteBuddy , loader );
8787 }
8888
8989 private static boolean needsSamePackageClassLoader (MockFeatures <?> features ) {
@@ -167,7 +167,8 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
167167 && features .serializableMode != SerializableMode .ACROSS_CLASSLOADERS
168168 && !isComingFromJDK (features .mockedType )
169169 && (loader .isDisrespectingOpenness ()
170- || handler .isOpened (features .mockedType , MockAccess .class ));
170+ || handler .isOpened (features .mockedType , MockAccess .class ))
171+ && !GraalImageCode .getCurrent ().isDefined ();
171172 String typeName ;
172173 if (localMock
173174 || (loader instanceof MultipleParentClassLoader
@@ -180,7 +181,13 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
180181 + features .mockedType .getSimpleName ();
181182 }
182183 String name =
183- String .format ("%s$%s$%d" , typeName , "MockitoMock" , Math .abs (random .nextInt ()));
184+ String .format (
185+ "%s$%s$%s" ,
186+ typeName ,
187+ "MockitoMock" ,
188+ GraalImageCode .getCurrent ().isDefined ()
189+ ? suffix (features )
190+ : RandomString .make ());
184191
185192 if (localMock ) {
186193 handler .adjustModuleGraph (features .mockedType , MockAccess .class , false , true );
@@ -214,17 +221,36 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
214221 }
215222 }
216223 }
217-
224+ // Graal requires that the byte code of classes is identical what requires that interfaces
225+ // are always
226+ // defined in the exact same order. Therefore, we add an interface to the interface set if
227+ // not mocking
228+ // a class when Graal is active.
229+ @ SuppressWarnings ("unchecked" )
230+ Class <T > target =
231+ GraalImageCode .getCurrent ().isDefined () && features .mockedType .isInterface ()
232+ ? (Class <T >) Object .class
233+ : features .mockedType ;
218234 DynamicType .Builder <T > builder =
219235 byteBuddy
220- .subclass (features . mockedType )
236+ .subclass (target )
221237 .name (name )
222238 .ignoreAlso (BytecodeGenerator .isGroovyMethod (false ))
223239 .annotateType (
224- features .stripAnnotations
240+ features .stripAnnotations || features . mockedType . isInterface ()
225241 ? new Annotation [0 ]
226242 : features .mockedType .getAnnotations ())
227- .implement (new ArrayList <Type >(features .interfaces ))
243+ .implement (
244+ new ArrayList <>(
245+ GraalImageCode .getCurrent ().isDefined ()
246+ ? sortedSerializable (
247+ features .interfaces ,
248+ GraalImageCode .getCurrent ().isDefined ()
249+ && features .mockedType
250+ .isInterface ()
251+ ? features .mockedType
252+ : void .class )
253+ : features .interfaces ))
228254 .method (matcher )
229255 .intercept (dispatcher )
230256 .transform (withModifiers (SynchronizationState .PLAIN ))
@@ -266,6 +292,31 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
266292 .getLoaded ();
267293 }
268294
295+ private static CharSequence suffix (MockFeatures <?> features ) {
296+ // Constructs a deterministic suffix for this mock to assure that mocks always carry the
297+ // same name.
298+ StringBuilder sb = new StringBuilder ();
299+ Set <String > names = new TreeSet <>();
300+ names .add (features .mockedType .getName ());
301+ for (Class <?> type : features .interfaces ) {
302+ names .add (type .getName ());
303+ }
304+ return sb .append (RandomString .hashOf (names .hashCode ()))
305+ .append (RandomString .hashOf (features .serializableMode .name ().hashCode ()))
306+ .append (features .stripAnnotations ? "S" : "N" );
307+ }
308+
309+ private static Collection <? extends Type > sortedSerializable (
310+ Collection <Class <?>> interfaces , Class <?> mockedType ) {
311+ SortedSet <Class <?>> types = new TreeSet <>(Comparator .comparing (Class ::getName ));
312+ types .addAll (interfaces );
313+ if (mockedType != void .class ) {
314+ types .add (mockedType );
315+ }
316+ types .add (Serializable .class );
317+ return types ;
318+ }
319+
269320 @ Override
270321 public void mockClassStatic (Class <?> type ) {
271322 throw new MockitoException ("The subclass byte code generator cannot create static mocks" );
0 commit comments