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