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.state;
7   
8   import static java.lang.reflect.Modifier.isAbstract;
9   
10  import static mockit.internal.util.GeneratedClasses.getMockedClass;
11  import static mockit.internal.util.GeneratedClasses.getMockedClassOrInterfaceType;
12  import static mockit.internal.util.GeneratedClasses.isGeneratedImplementationClass;
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.InvocationTargetException;
20  import java.lang.reflect.Method;
21  import java.lang.reflect.Type;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.IdentityHashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  import java.util.Set;
32  import java.util.concurrent.ConcurrentHashMap;
33  
34  import mockit.internal.ClassFile;
35  import mockit.internal.ClassIdentification;
36  import mockit.internal.capturing.CaptureTransformer;
37  import mockit.internal.expectations.mocking.CaptureOfNewInstances;
38  import mockit.internal.expectations.mocking.InstanceFactory;
39  import mockit.internal.startup.Startup;
40  import mockit.internal.util.ClassLoad;
41  
42  /**
43   * Holds data about redefined/transformed classes, with methods to add/remove and query such data.
44   */
45  public final class MockFixture {
46      /**
47       * Similar to {@link #redefinedClasses}, but for classes modified by a <code>ClassFileTransformer</code> such as the
48       * <code>CaptureTransformer</code>, and containing the pre-transform bytecode instead of the modified one.
49       *
50       * @see #addTransformedClass(ClassIdentification, byte[])
51       * @see #getTransformedClasses()
52       * @see #restoreTransformedClasses(Set)
53       */
54      @NonNull
55      private final Map<ClassIdentification, byte[]> transformedClasses;
56  
57      /**
58       * Real classes currently redefined in the running JVM and their current (modified) bytecodes.
59       * <p>
60       * The keys in the map allow each redefined real class to be later restored to a previous definition.
61       * <p>
62       * The modified bytecode arrays in the map allow a new redefinition to be made on top of the current redefinition
63       * (in the case of the Faking API), or to restore the class to a previous definition (provided the map is copied
64       * between redefinitions of the same class).
65       *
66       * @see #addRedefinedClass(ClassDefinition)
67       * @see #getRedefinedClasses()
68       * @see #getRedefinedClassfile(Class)
69       * @see #containsRedefinedClass(Class)
70       * @see #restoreRedefinedClasses(Map)
71       */
72      @NonNull
73      private final Map<Class<?>, byte[]> redefinedClasses;
74  
75      /**
76       * Subset of all currently redefined classes which contain one or more native methods.
77       * <p/>
78       * This is needed because in order to restore such methods it is necessary (for some classes) to re-register them
79       * with the JVM.
80       *
81       * @see #reregisterNativeMethodsForRestoredClass(Class)
82       */
83      @NonNull
84      private final Set<String> redefinedClassesWithNativeMethods;
85  
86      /**
87       * Maps redefined real classes to the internal name of the corresponding fake classes, when it's the case.
88       * <p>
89       * This allows any global state associated to a fake class to be discarded when the corresponding real class is
90       * later restored to its original definition.
91       *
92       * @see #addRedefinedClass(String, ClassDefinition)
93       */
94      @NonNull
95      private final Map<Class<?>, String> realClassesToFakeClasses;
96  
97      /**
98       * A list of classes that are currently mocked. Said classes are also added to {@link #mockedTypesAndInstances}.
99       *
100      * @see #registerMockedClass(Class)
101      * @see #getMockedClasses()
102      * @see #isStillMocked(Object, String)
103      * @see #isInstanceOfMockedClass(Object)
104      * @see #removeMockedClasses(List)
105      */
106     @NonNull
107     private final List<Class<?>> mockedClasses;
108 
109     /**
110      * A map of mocked types to their corresponding {@linkplain InstanceFactory mocked instance factories}.
111      *
112      * @see #registerInstanceFactoryForMockedType(Class, InstanceFactory)
113      * @see #findInstanceFactory(Type)
114      * @see #isStillMocked(Object, String)
115      * @see #removeMockedClasses(List)
116      */
117     @NonNull
118     private final Map<Type, InstanceFactory> mockedTypesAndInstances;
119 
120     /**
121      * A list of "capturing" class file transformers, used by both the mocking and faking APIs.
122      *
123      * @see #addCaptureTransformer(CaptureTransformer)
124      * @see #areCapturedClasses(Class, Class)
125      * @see #isCaptured(Object)
126      * @see #getCaptureTransformerCount()
127      * @see #removeCaptureTransformers(int)
128      */
129     @NonNull
130     private final List<CaptureTransformer<?>> captureTransformers;
131 
132     MockFixture() {
133         transformedClasses = new HashMap<>(2);
134         redefinedClasses = new ConcurrentHashMap<>(8);
135         redefinedClassesWithNativeMethods = new HashSet<>();
136         realClassesToFakeClasses = new IdentityHashMap<>(8);
137         mockedClasses = new ArrayList<>();
138         mockedTypesAndInstances = new IdentityHashMap<>();
139         captureTransformers = new ArrayList<>();
140     }
141 
142     // Methods to add/remove transformed/redefined classes /////////////////////////////////////////////////////////////
143 
144     public void addTransformedClass(@NonNull ClassIdentification classId, @NonNull byte[] pretransformClassfile) {
145         transformedClasses.put(classId, pretransformClassfile);
146     }
147 
148     // Methods used by both the Mocking and Faking APIs.
149 
150     public void addRedefinedClass(@NonNull ClassDefinition newClassDefinition) {
151         redefinedClasses.put(newClassDefinition.getDefinitionClass(), newClassDefinition.getDefinitionClassFile());
152     }
153 
154     public void registerMockedClass(@NonNull Class<?> mockedType) {
155         if (!mockedClasses.contains(mockedType)) {
156             mockedType = getMockedClassOrInterfaceType(mockedType);
157             mockedClasses.add(mockedType);
158         }
159     }
160 
161     // Methods used by the Mocking API.
162 
163     public void redefineClasses(@NonNull ClassDefinition... definitions) {
164         Startup.redefineMethods(definitions);
165 
166         for (ClassDefinition def : definitions) {
167             addRedefinedClass(def);
168         }
169     }
170 
171     public void redefineMethods(@NonNull Map<Class<?>, byte[]> modifiedClassfiles) {
172         ClassDefinition[] classDefs = new ClassDefinition[modifiedClassfiles.size()];
173         int i = 0;
174 
175         for (Entry<Class<?>, byte[]> classAndBytecode : modifiedClassfiles.entrySet()) {
176             Class<?> modifiedClass = classAndBytecode.getKey();
177             byte[] modifiedClassfile = classAndBytecode.getValue();
178 
179             ClassDefinition classDef = new ClassDefinition(modifiedClass, modifiedClassfile);
180             classDefs[i] = classDef;
181             i++;
182 
183             addRedefinedClass(classDef);
184         }
185 
186         Startup.redefineMethods(classDefs);
187     }
188 
189     public boolean isStillMocked(@Nullable Object instance, @NonNull String classDesc) {
190         Class<?> targetClass;
191 
192         if (instance == null) {
193             targetClass = ClassLoad.loadByInternalName(classDesc);
194             return isClassAssignableTo(targetClass);
195         }
196 
197         targetClass = instance.getClass();
198         return mockedTypesAndInstances.containsKey(targetClass) || isInstanceOfMockedClass(instance);
199     }
200 
201     private boolean isClassAssignableTo(@NonNull Class<?> toClass) {
202         for (Class<?> mockedClass : mockedClasses) {
203             if (toClass == mockedClass || toClass.isAssignableFrom(mockedClass)) {
204                 return true;
205             }
206         }
207 
208         return false;
209     }
210 
211     public boolean isInstanceOfMockedClass(@NonNull Object mockedInstance) {
212         Class<?> mockedClass = getMockedClassOrInterfaceType(mockedInstance.getClass());
213         Class<?> mockedSuperclass = mockedClass.getSuperclass();
214 
215         if (mockedSuperclass != null && mockedSuperclass.isEnum()) {
216             return mockedClasses.contains(mockedSuperclass);
217         }
218 
219         return mockedClasses.contains(mockedClass) || isCaptured(mockedClass);
220     }
221 
222     public void registerInstanceFactoryForMockedType(@NonNull Class<?> mockedType,
223             @NonNull InstanceFactory mockedInstanceFactory) {
224         registerMockedClass(mockedType);
225         mockedTypesAndInstances.put(mockedType, mockedInstanceFactory);
226     }
227 
228     @Nullable
229     public InstanceFactory findInstanceFactory(@NonNull Type mockedType) {
230         InstanceFactory instanceFactory = mockedTypesAndInstances.get(mockedType);
231 
232         if (instanceFactory != null) {
233             return instanceFactory;
234         }
235 
236         Class<?> mockedClass = getClassType(mockedType);
237         // noinspection ReuseOfLocalVariable
238         instanceFactory = mockedTypesAndInstances.get(mockedClass);
239 
240         if (instanceFactory != null) {
241             return instanceFactory;
242         }
243 
244         boolean abstractType = mockedClass.isInterface() || isAbstract(mockedClass.getModifiers());
245 
246         for (Entry<Type, InstanceFactory> entry : mockedTypesAndInstances.entrySet()) {
247             Type registeredMockedType = entry.getKey();
248             Class<?> registeredMockedClass = getClassType(registeredMockedType);
249 
250             if (abstractType) {
251                 registeredMockedClass = getMockedClassOrInterfaceType(registeredMockedClass);
252             }
253 
254             if (mockedClass.isAssignableFrom(registeredMockedClass)) {
255                 instanceFactory = entry.getValue();
256                 break;
257             }
258         }
259 
260         return instanceFactory;
261     }
262 
263     // Methods used by the Faking API.
264 
265     public void addRedefinedClass(@NonNull String fakeClassInternalName, @NonNull ClassDefinition classDef) {
266         @NonNull
267         Class<?> redefinedClass = classDef.getDefinitionClass();
268         String previousNames = realClassesToFakeClasses.put(redefinedClass, fakeClassInternalName);
269 
270         if (previousNames != null) {
271             realClassesToFakeClasses.put(redefinedClass, previousNames + ' ' + fakeClassInternalName);
272         }
273 
274         addRedefinedClass(classDef);
275     }
276 
277     // Methods used by test save-points ////////////////////////////////////////////////////////////////////////////////
278 
279     void restoreTransformedClasses(@NonNull Set<ClassIdentification> previousTransformedClasses) {
280         if (!transformedClasses.isEmpty()) {
281             Set<ClassIdentification> classesToRestore;
282 
283             if (previousTransformedClasses.isEmpty()) {
284                 classesToRestore = transformedClasses.keySet();
285             } else {
286                 classesToRestore = getTransformedClasses();
287                 classesToRestore.removeAll(previousTransformedClasses);
288             }
289 
290             if (!classesToRestore.isEmpty()) {
291                 restoreAndRemoveTransformedClasses(classesToRestore);
292             }
293         }
294     }
295 
296     @NonNull
297     Set<ClassIdentification> getTransformedClasses() {
298         return transformedClasses.isEmpty() ? Collections.emptySet() : new HashSet<>(transformedClasses.keySet());
299     }
300 
301     @NonNull
302     Map<Class<?>, byte[]> getRedefinedClasses() {
303         return redefinedClasses.isEmpty() ? Collections.emptyMap() : new HashMap<>(redefinedClasses);
304     }
305 
306     private void restoreAndRemoveTransformedClasses(@NonNull Set<ClassIdentification> classesToRestore) {
307         for (ClassIdentification transformedClassId : classesToRestore) {
308             byte[] definitionToRestore = transformedClasses.get(transformedClassId);
309             Startup.redefineMethods(transformedClassId, definitionToRestore);
310         }
311 
312         transformedClasses.keySet().removeAll(classesToRestore);
313     }
314 
315     void restoreRedefinedClasses(@NonNull Map<?, byte[]> previousDefinitions) {
316         if (redefinedClasses.isEmpty()) {
317             return;
318         }
319 
320         Iterator<Entry<Class<?>, byte[]>> itr = redefinedClasses.entrySet().iterator();
321 
322         while (itr.hasNext()) {
323             Entry<Class<?>, byte[]> entry = itr.next();
324             Class<?> redefinedClass = entry.getKey();
325             byte[] currentDefinition = entry.getValue();
326             byte[] previousDefinition = previousDefinitions.get(redefinedClass);
327 
328             if (previousDefinition == null) {
329                 restoreDefinition(redefinedClass);
330                 itr.remove();
331             } else if (currentDefinition != previousDefinition) {
332                 Startup.redefineMethods(redefinedClass, previousDefinition);
333                 entry.setValue(previousDefinition);
334             }
335         }
336     }
337 
338     private void restoreDefinition(@NonNull Class<?> redefinedClass) {
339         if (!isGeneratedImplementationClass(redefinedClass)) {
340             byte[] previousDefinition = ClassFile.getClassFile(redefinedClass);
341             Startup.redefineMethods(redefinedClass, previousDefinition);
342         }
343         if (redefinedClassesWithNativeMethods.contains(redefinedClass.getName())) {
344             reregisterNativeMethodsForRestoredClass(redefinedClass);
345         }
346 
347         removeMockedClass(redefinedClass);
348         discardStateForCorrespondingFakeClassIfAny(redefinedClass);
349     }
350 
351     private void removeMockedClass(@NonNull Class<?> mockedClass) {
352         mockedTypesAndInstances.remove(mockedClass);
353         mockedClasses.remove(mockedClass);
354     }
355 
356     private void discardStateForCorrespondingFakeClassIfAny(@NonNull Class<?> redefinedClass) {
357         String mockClassesInternalNames = realClassesToFakeClasses.remove(redefinedClass);
358         TestRun.getFakeStates().removeClassState(redefinedClass, mockClassesInternalNames);
359     }
360 
361     void removeMockedClasses(@NonNull List<Class<?>> previousMockedClasses) {
362         int currentMockedClassCount = mockedClasses.size();
363 
364         if (currentMockedClassCount > 0) {
365             int previousMockedClassCount = previousMockedClasses.size();
366 
367             if (previousMockedClassCount == 0) {
368                 mockedClasses.clear();
369                 mockedTypesAndInstances.clear();
370             } else if (previousMockedClassCount < currentMockedClassCount) {
371                 mockedClasses.retainAll(previousMockedClasses);
372                 mockedTypesAndInstances.keySet().retainAll(previousMockedClasses);
373             }
374         }
375     }
376 
377     // Getter methods for the maps and collections of transformed/redefined/mocked classes /////////////////////////////
378 
379     @Nullable
380     public byte[] getRedefinedClassfile(@NonNull Class<?> redefinedClass) {
381         return redefinedClasses.get(redefinedClass);
382     }
383 
384     public boolean containsRedefinedClass(@NonNull Class<?> redefinedClass) {
385         return redefinedClasses.containsKey(redefinedClass);
386     }
387 
388     @NonNull
389     public List<Class<?>> getMockedClasses() {
390         return mockedClasses.isEmpty() ? Collections.emptyList() : new ArrayList<>(mockedClasses);
391     }
392 
393     // Methods dealing with capture transformers ///////////////////////////////////////////////////////////////////////
394 
395     public void addCaptureTransformer(@NonNull CaptureTransformer<?> transformer) {
396         captureTransformers.add(transformer);
397     }
398 
399     // The following methods are used by test save-points to discard currently active capture transformers.
400 
401     int getCaptureTransformerCount() {
402         return captureTransformers.size();
403     }
404 
405     void removeCaptureTransformers(int previousTransformerCount) {
406         int currentTransformerCount = captureTransformers.size();
407 
408         for (int i = currentTransformerCount - 1; i >= previousTransformerCount; i--) {
409             CaptureTransformer<?> transformer = captureTransformers.get(i);
410             transformer.deactivate();
411             Startup.instrumentation().removeTransformer(transformer);
412             captureTransformers.remove(i);
413         }
414     }
415 
416     // The following methods are only used by the Mocking API.
417 
418     public boolean isCaptured(@NonNull Object mockedInstance) {
419         if (!captureTransformers.isEmpty()) {
420             Class<?> mockedClass = getMockedClass(mockedInstance);
421             return isCaptured(mockedClass);
422         }
423 
424         return false;
425     }
426 
427     private boolean isCaptured(@NonNull Class<?> mockedClass) {
428         for (CaptureTransformer<?> captureTransformer : captureTransformers) {
429             CaptureOfNewInstances capture = captureTransformer.getCaptureOfImplementationsIfApplicable(mockedClass);
430 
431             if (capture != null) {
432                 return true;
433             }
434         }
435 
436         return false;
437     }
438 
439     public boolean areCapturedClasses(@NonNull Class<?> mockedClass1, @NonNull Class<?> mockedClass2) {
440         for (CaptureTransformer<?> captureTransformer : captureTransformers) {
441             if (captureTransformer.areCapturedClasses(mockedClass1, mockedClass2)) {
442                 return true;
443             }
444         }
445 
446         return false;
447     }
448 
449     private static void reregisterNativeMethodsForRestoredClass(@NonNull Class<?> realClass) {
450         Method registerNatives = null;
451 
452         try {
453             registerNatives = realClass.getDeclaredMethod("registerNatives");
454         } catch (NoSuchMethodException ignore) {
455             try {
456                 registerNatives = realClass.getDeclaredMethod("initIDs");
457             } catch (NoSuchMethodException ignored) {
458             } // OK
459         }
460 
461         if (registerNatives != null) {
462             try {
463                 registerNatives.setAccessible(true);
464                 registerNatives.invoke(null);
465             } catch (IllegalAccessException | InvocationTargetException ignore) {
466             } // won't happen
467         }
468 
469         // OK, although another solution will be required for this particular class if it requires
470         // natives to be explicitly registered again (not all do, such as java.lang.Float).
471     }
472 
473     public void addRedefinedClassWithNativeMethods(@NonNull String redefinedClassInternalName) {
474         redefinedClassesWithNativeMethods.add(redefinedClassInternalName.replace('/', '.'));
475     }
476 }