View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.internal.expectations.mocking;
7   
8   import static java.lang.reflect.Modifier.isAbstract;
9   import static java.lang.reflect.Modifier.isPublic;
10  
11  import static mockit.internal.util.GeneratedClasses.getNameForGeneratedClass;
12  import static mockit.internal.util.Utilities.JAVA8;
13  import static mockit.internal.util.Utilities.getClassType;
14  
15  import edu.umd.cs.findbugs.annotations.NonNull;
16  import edu.umd.cs.findbugs.annotations.Nullable;
17  
18  import java.lang.instrument.ClassDefinition;
19  import java.lang.reflect.ParameterizedType;
20  import java.lang.reflect.Proxy;
21  import java.lang.reflect.Type;
22  import java.lang.reflect.TypeVariable;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import mockit.asm.classes.ClassReader;
29  import mockit.asm.classes.ClassVisitor;
30  import mockit.asm.jvmConstants.ClassVersion;
31  import mockit.internal.ClassFile;
32  import mockit.internal.classGeneration.ImplementationClass;
33  import mockit.internal.expectations.mocking.InstanceFactory.ClassInstanceFactory;
34  import mockit.internal.expectations.mocking.InstanceFactory.InterfaceInstanceFactory;
35  import mockit.internal.reflection.ConstructorReflection;
36  import mockit.internal.reflection.EmptyProxy.Impl;
37  import mockit.internal.state.TestRun;
38  import mockit.internal.util.ClassLoad;
39  import mockit.internal.util.VisitInterruptedException;
40  
41  class BaseTypeRedefinition {
42      private static final ClassDefinition[] CLASS_DEFINITIONS = {};
43  
44      private static final class MockedClass {
45          @Nullable
46          final InstanceFactory instanceFactory;
47          @NonNull
48          final ClassDefinition[] mockedClassDefinitions;
49  
50          MockedClass(@Nullable InstanceFactory instanceFactory, @NonNull ClassDefinition[] classDefinitions) {
51              this.instanceFactory = instanceFactory;
52              mockedClassDefinitions = classDefinitions;
53          }
54  
55          void redefineClasses() {
56              TestRun.mockFixture().redefineClasses(mockedClassDefinitions);
57          }
58      }
59  
60      @NonNull
61      private static final Map<Integer, MockedClass> mockedClasses = new HashMap<>();
62      @NonNull
63      private static final Map<Type, Class<?>> mockImplementations = new HashMap<>();
64  
65      Class<?> targetClass;
66      @Nullable
67      MockedType typeMetadata;
68      @Nullable
69      private InstanceFactory instanceFactory;
70      @Nullable
71      private List<ClassDefinition> mockedClassDefinitions;
72  
73      BaseTypeRedefinition() {
74      }
75  
76      BaseTypeRedefinition(@NonNull MockedType typeMetadata) {
77          targetClass = typeMetadata.getClassType();
78          this.typeMetadata = typeMetadata;
79      }
80  
81      @Nullable
82      final InstanceFactory redefineType(@NonNull Type typeToMock) {
83          if (targetClass == TypeVariable.class || targetClass.isInterface()) {
84              createMockedInterfaceImplementationAndInstanceFactory(typeToMock);
85          } else {
86              if (typeMetadata == null || !typeMetadata.isClassInitializationToBeStubbedOut()) {
87                  TestRun.ensureThatClassIsInitialized(targetClass);
88              }
89              redefineTargetClassAndCreateInstanceFactory(typeToMock);
90          }
91  
92          if (instanceFactory != null) {
93              Class<?> mockedType = getClassType(typeToMock);
94              TestRun.mockFixture().registerInstanceFactoryForMockedType(mockedType, instanceFactory);
95          }
96  
97          return instanceFactory;
98      }
99  
100     private void createMockedInterfaceImplementationAndInstanceFactory(@NonNull Type interfaceToMock) {
101         Class<?> mockedInterface = interfaceToMock(interfaceToMock);
102         Object mockedInstance;
103 
104         if (mockedInterface == null) {
105             mockedInstance = createMockInterfaceImplementationUsingStandardProxy(interfaceToMock);
106         } else {
107             mockedInstance = createMockInterfaceImplementationDirectly(interfaceToMock);
108         }
109 
110         redefinedImplementedInterfacesIfRunningOnJava8(targetClass);
111         instanceFactory = new InterfaceInstanceFactory(mockedInstance);
112     }
113 
114     @Nullable
115     private static Class<?> interfaceToMock(@NonNull Type typeToMock) {
116         while (true) {
117             if (typeToMock instanceof Class<?>) {
118                 Class<?> theInterface = (Class<?>) typeToMock;
119 
120                 if (isPublic(theInterface.getModifiers()) && !theInterface.isAnnotation()) {
121                     return theInterface;
122                 }
123             } else if (typeToMock instanceof ParameterizedType) {
124                 typeToMock = ((ParameterizedType) typeToMock).getRawType();
125                 continue;
126             }
127 
128             return null;
129         }
130     }
131 
132     @NonNull
133     private Object createMockInterfaceImplementationUsingStandardProxy(@NonNull Type typeToMock) {
134         ClassLoader loader = getClass().getClassLoader();
135         Object mockedInstance = Impl.newEmptyProxy(loader, typeToMock);
136         targetClass = mockedInstance.getClass();
137         redefineClass(targetClass);
138         return mockedInstance;
139     }
140 
141     @NonNull
142     private Object createMockInterfaceImplementationDirectly(@NonNull Type interfaceToMock) {
143         Class<?> previousMockImplementationClass = mockImplementations.get(interfaceToMock);
144 
145         if (previousMockImplementationClass == null) {
146             generateNewMockImplementationClassForInterface(interfaceToMock);
147             mockImplementations.put(interfaceToMock, targetClass);
148         } else {
149             targetClass = previousMockImplementationClass;
150         }
151 
152         return ConstructorReflection.newInstanceUsingDefaultConstructor(targetClass);
153     }
154 
155     private void redefineClass(@NonNull Class<?> realClass) {
156         ClassReader classReader = ClassFile.createReaderOrGetFromCache(realClass);
157 
158         if (realClass.isInterface() && classReader.getVersion() < ClassVersion.V8) {
159             return;
160         }
161 
162         ClassLoader loader = realClass.getClassLoader();
163         MockedClassModifier modifier = createClassModifier(loader, classReader);
164         redefineClass(realClass, classReader, modifier);
165     }
166 
167     @NonNull
168     private MockedClassModifier createClassModifier(@Nullable ClassLoader loader, @NonNull ClassReader classReader) {
169         MockedClassModifier modifier = new MockedClassModifier(loader, classReader, typeMetadata);
170         configureClassModifier(modifier);
171         return modifier;
172     }
173 
174     void configureClassModifier(@NonNull MockedClassModifier modifier) {
175     }
176 
177     private void generateNewMockImplementationClassForInterface(@NonNull final Type interfaceToMock) {
178         ImplementationClass<?> implementationGenerator = new ImplementationClass<>(interfaceToMock) {
179             @NonNull
180             @Override
181             protected ClassVisitor createMethodBodyGenerator(@NonNull ClassReader cr) {
182                 return new InterfaceImplementationGenerator(cr, interfaceToMock, generatedClassName);
183             }
184         };
185 
186         targetClass = implementationGenerator.generateClass();
187     }
188 
189     private void redefinedImplementedInterfacesIfRunningOnJava8(@NonNull Class<?> aClass) {
190         if (JAVA8) {
191             redefineImplementedInterfaces(aClass.getInterfaces());
192         }
193     }
194 
195     final boolean redefineMethodsAndConstructorsInTargetType() {
196         return redefineClassAndItsSuperClasses(targetClass);
197     }
198 
199     private boolean redefineClassAndItsSuperClasses(@NonNull Class<?> realClass) {
200         ClassLoader loader = realClass.getClassLoader();
201         ClassReader classReader = ClassFile.createReaderOrGetFromCache(realClass);
202         MockedClassModifier modifier = createClassModifier(loader, classReader);
203 
204         try {
205             redefineClass(realClass, classReader, modifier);
206         } catch (VisitInterruptedException ignore) {
207             // As defined in MockedClassModifier, some critical JRE classes have all methods excluded from mocking by
208             // default. This exception occurs when they are visited.
209             // In this case, we simply stop class redefinition for the rest of the class hierarchy.
210             return false;
211         }
212 
213         redefineElementSubclassesOfEnumTypeIfAny(modifier.enumSubclasses);
214         redefinedImplementedInterfacesIfRunningOnJava8(realClass);
215 
216         Class<?> superClass = realClass.getSuperclass();
217         boolean redefined = true;
218 
219         if (superClass != null && superClass != Object.class && superClass != Proxy.class && superClass != Enum.class) {
220             redefined = redefineClassAndItsSuperClasses(superClass);
221         }
222 
223         return redefined;
224     }
225 
226     private void redefineClass(@NonNull Class<?> realClass, @NonNull ClassReader classReader,
227             @NonNull MockedClassModifier modifier) {
228         classReader.accept(modifier);
229 
230         if (modifier.wasModified()) {
231             byte[] modifiedClass = modifier.toByteArray();
232             applyClassRedefinition(realClass, modifiedClass);
233         }
234     }
235 
236     void applyClassRedefinition(@NonNull Class<?> realClass, @NonNull byte[] modifiedClass) {
237         ClassDefinition classDefinition = new ClassDefinition(realClass, modifiedClass);
238         TestRun.mockFixture().redefineClasses(classDefinition);
239 
240         if (mockedClassDefinitions != null) {
241             mockedClassDefinitions.add(classDefinition);
242         }
243     }
244 
245     private void redefineElementSubclassesOfEnumTypeIfAny(@Nullable List<String> enumSubclasses) {
246         if (enumSubclasses != null) {
247             for (String enumSubclassDesc : enumSubclasses) {
248                 Class<?> enumSubclass = ClassLoad.loadByInternalName(enumSubclassDesc);
249                 redefineClass(enumSubclass);
250             }
251         }
252     }
253 
254     private void redefineImplementedInterfaces(@NonNull Class<?>[] implementedInterfaces) {
255         for (Class<?> implementedInterface : implementedInterfaces) {
256             redefineClass(implementedInterface);
257             redefineImplementedInterfaces(implementedInterface.getInterfaces());
258         }
259     }
260 
261     private void redefineTargetClassAndCreateInstanceFactory(@NonNull Type typeToMock) {
262         Integer mockedClassId = redefineClassesFromCache();
263 
264         if (mockedClassId == null) {
265             return;
266         }
267 
268         boolean redefined = redefineMethodsAndConstructorsInTargetType();
269         instanceFactory = createInstanceFactory(typeToMock);
270 
271         if (redefined) {
272             storeRedefinedClassesInCache(mockedClassId);
273         }
274     }
275 
276     @NonNull
277     final InstanceFactory createInstanceFactory(@NonNull Type typeToMock) {
278         Class<?> classToInstantiate = targetClass;
279 
280         if (isAbstract(classToInstantiate.getModifiers())) {
281             classToInstantiate = generateConcreteSubclassForAbstractType(typeToMock);
282         }
283 
284         return new ClassInstanceFactory(classToInstantiate);
285     }
286 
287     @Nullable
288     private Integer redefineClassesFromCache() {
289         // noinspection ConstantConditions
290         Integer mockedClassId = typeMetadata.hashCode();
291         MockedClass mockedClass = mockedClasses.get(mockedClassId);
292 
293         if (mockedClass != null) {
294             mockedClass.redefineClasses();
295             instanceFactory = mockedClass.instanceFactory;
296             return null;
297         }
298 
299         mockedClassDefinitions = new ArrayList<>();
300         return mockedClassId;
301     }
302 
303     private void storeRedefinedClassesInCache(@NonNull Integer mockedClassId) {
304         assert mockedClassDefinitions != null;
305         ClassDefinition[] classDefs = mockedClassDefinitions.toArray(CLASS_DEFINITIONS);
306         MockedClass mockedClass = new MockedClass(instanceFactory, classDefs);
307 
308         mockedClasses.put(mockedClassId, mockedClass);
309     }
310 
311     @NonNull
312     private Class<?> generateConcreteSubclassForAbstractType(@NonNull final Type typeToMock) {
313         final String subclassName = getNameForConcreteSubclassToCreate();
314 
315         return new ImplementationClass<>(targetClass, subclassName) {
316             @NonNull
317             @Override
318             protected ClassVisitor createMethodBodyGenerator(@NonNull ClassReader cr) {
319                 return new SubclassGenerationModifier(targetClass, typeToMock, cr, subclassName, false);
320             }
321         }.generateClass();
322     }
323 
324     @NonNull
325     private String getNameForConcreteSubclassToCreate() {
326         String mockId = typeMetadata == null ? null : typeMetadata.getName();
327         return getNameForGeneratedClass(targetClass, mockId);
328     }
329 }