1 /*
2  * Copyright (c) 2007 Mockito contributors
3  * This program is made available under the terms of the MIT License.
4  */
5 
6 package org.mockito.internal.creation;
7 
8 import org.mockito.Incubating;
9 import org.mockito.exceptions.base.MockitoSerializationIssue;
10 import org.mockito.internal.creation.jmock.ClassImposterizer;
11 import org.mockito.internal.util.MockUtil;
12 import org.mockito.internal.util.reflection.FieldSetter;
13 import org.mockito.mock.MockCreationSettings;
14 import org.mockito.mock.MockName;
15 
16 import java.io.*;
17 import java.lang.reflect.Field;
18 import java.lang.reflect.Method;
19 import java.util.Set;
20 import java.util.concurrent.locks.Lock;
21 import java.util.concurrent.locks.ReentrantLock;
22 
23 import static org.mockito.internal.util.StringJoiner.join;
24 
25 /**
26  * This is responsible for serializing a mock, it is enabled if the mock is implementing
27  * {@link Serializable}.
28  *
29  * <p>
30  *     The way it works is to enable serialization via the {@link #enableSerializationAcrossJVM(MockCreationSettings)},
31  *     if the mock settings is set to be serializable it will add the {@link org.mockito.internal.creation.AcrossJVMSerializationFeature.AcrossJVMMockitoMockSerializable} interface.
32  *     This interface defines a the {@link org.mockito.internal.creation.AcrossJVMSerializationFeature.AcrossJVMMockitoMockSerializable#writeReplace()}
33  *     whose signature match the one that is looked by the standard Java serialization.
34  * </p>
35  *
36  * <p>
37  *     Then in the {@link MethodInterceptorFilter} of mockito, if the <code>writeReplace</code> method is called,
38  *     it will use the custom implementation of this class {@link #writeReplace(Object)}. This method has a specific
39  *     knowledge on how to serialize a mockito mock that is based on CGLIB.
40  * </p>
41  *
42  * <p><strong>Only one instance per mock! See {@link MethodInterceptorFilter}</strong></p>
43  *
44  * TODO use a proper way to add the interface
45  * TODO offer a way to disable completely this behavior, or maybe enable this behavior only with a specific setting
46  * TODO check the class is mockable in the deserialization side
47  *
48  * @see CglibMockMaker
49  * @see MethodInterceptorFilter
50  * @author Brice Dutheil
51  * @since 1.9.6
52  */
53 @Incubating
54 public class AcrossJVMSerializationFeature implements Serializable {
55     private static final long serialVersionUID = 7411152578314420778L;
56     private static final String MOCKITO_PROXY_MARKER = "MockitoProxyMarker";
57     private boolean instanceLocalCurrentlySerializingFlag = false;
58     private Lock mutex = new ReentrantLock();
59 
isWriteReplace(Method method)60     public boolean isWriteReplace(Method method) {
61         return  method.getReturnType() == Object.class
62                 && method.getParameterTypes().length == 0
63                 && method.getName().equals("writeReplace");
64     }
65 
66 
67     /**
68      * Custom implementation of the <code>writeReplace</code> method for serialization.
69      *
70      * Here's how it's working and why :
71      * <ol>
72      *     <li>
73      *         <p>When first entering in this method, it's because some is serializing the mock, with some code like :
74      * <pre class="code"><code class="java">
75      *     objectOutputStream.writeObject(mock);
76      * </code></pre>
77      *         So, {@link ObjectOutputStream} will track the <code>writeReplace</code> method in the instance and
78      *         execute it, which is wanted to replace the mock by another type that will encapsulate the actual mock.
79      *         At this point, the code will return an {@link AcrossJVMMockSerializationProxy}.</p>
80      *     </li>
81      *     <li>
82      *         <p>Now, in the constructor {@link AcrossJVMMockSerializationProxy#AcrossJVMMockSerializationProxy(Object)}
83      *         the mock is being serialized in a custom way (using {@link MockitoMockObjectOutputStream}) to a
84      *         byte array. So basically it means the code is performing double nested serialization of the passed
85      *         <code>mockitoMock</code>.</p>
86      *
87      *         <p>However the <code>ObjectOutputStream</code> will still detect the custom
88      *         <code>writeReplace</code> and execute it.
89      *         <em>(For that matter disabling replacement via {@link ObjectOutputStream#enableReplaceObject(boolean)}
90      *         doesn't disable the <code>writeReplace</code> call, but just just toggle replacement in the
91      *         written stream, <strong><code>writeReplace</code> is always called by
92      *         <code>ObjectOutputStream</code></strong>.)</em></p>
93      *
94      *         <p>In order to avoid this recursion, obviously leading to a {@link StackOverflowError}, this method is using
95      *         a flag that marks the mock as already being replaced, and then shouldn't replace itself again.
96      *         <strong>This flag is local to this class</strong>, which means the flag of this class unfortunately needs
97      *         to be protected against concurrent access, hence the reentrant lock.</p>
98      *     </li>
99      * </ol>
100      *
101      *
102      * @param mockitoMock The Mockito mock to be serialized.
103      * @return A wrapper ({@link AcrossJVMMockSerializationProxy}) to be serialized by the calling ObjectOutputStream.
104      * @throws ObjectStreamException
105      */
writeReplace(Object mockitoMock)106     public Object writeReplace(Object mockitoMock) throws ObjectStreamException {
107         try {
108             // reentrant lock for critical section. could it be improved ?
109             mutex.lock();
110             // mark started flag // per thread, not per instance
111             // temporary loosy hack to avoid stackoverflow
112             if(mockIsCurrentlyBeingReplaced()) {
113                 return mockitoMock;
114             }
115             mockReplacementStarted();
116 
117             return new AcrossJVMMockSerializationProxy(mockitoMock);
118         } catch (IOException ioe) {
119             MockUtil mockUtil = new MockUtil();
120             MockName mockName = mockUtil.getMockName(mockitoMock);
121             String mockedType = mockUtil.getMockSettings(mockitoMock).getTypeToMock().getCanonicalName();
122             throw new MockitoSerializationIssue(join(
123                     "The mock '" + mockName + "' of type '" + mockedType + "'",
124                     "The Java Standard Serialization reported an '" + ioe.getClass().getSimpleName() + "' saying :",
125                     "  " + ioe.getMessage()
126             ), ioe);
127         } finally {
128             // unmark
129             mockReplacementCompleted();
130             mutex.unlock();
131         }
132     }
133 
134 
mockReplacementCompleted()135     private void mockReplacementCompleted() {
136         instanceLocalCurrentlySerializingFlag = false;
137     }
138 
139 
mockReplacementStarted()140     private void mockReplacementStarted() {
141         instanceLocalCurrentlySerializingFlag = true;
142     }
143 
144 
mockIsCurrentlyBeingReplaced()145     private boolean mockIsCurrentlyBeingReplaced() {
146         return instanceLocalCurrentlySerializingFlag;
147     }
148 
149 
150     /**
151      * Enable serialization serialization that will work across classloaders / and JVM.
152      *
153      * <p>Only enable if settings says the mock should be serializable. In this case add the
154      * {@link AcrossJVMMockitoMockSerializable} to the extra interface list.</p>
155      *
156      * @param settings Mock creation settings.
157      * @param <T> Type param to not be bothered by the generics
158      */
enableSerializationAcrossJVM(MockCreationSettings<T> settings)159     public <T> void enableSerializationAcrossJVM(MockCreationSettings<T> settings) {
160         if (settings.isSerializable()) {
161             // havin faith that this set is modifiable
162             // TODO use a proper way to add the interface
163             settings.getExtraInterfaces().add(AcrossJVMMockitoMockSerializable.class);
164         }
165     }
166 
167 
168     /**
169      * This is the serialization proxy that will encapsulate the real mock data as a byte array.
170      *
171      * <p>When called in the constructor it will serialize the mock in a byte array using a
172      * custom {@link MockitoMockObjectOutputStream} that will annotate the mock class in the stream.
173      * other information are used in this class in order to facilitate deserialization.
174      * </p>
175      *
176      * <p>Deserialization of the mock will be performed by the {@link #readResolve()} method via
177      * the custom {@link MockitoMockObjectInputStream} that will be in charge of creating the mock class.</p>
178      */
179     public static class AcrossJVMMockSerializationProxy implements Serializable {
180 
181 
182         private static final long serialVersionUID = -7600267929109286514L;
183         private byte[] serializedMock;
184         private Class typeToMock;
185         private Set<Class> extraInterfaces;
186         /**
187          * Creates the wrapper that be used in the serialization stream.
188          *
189          * <p>Immediately serializes the Mockito mock using specifically crafted {@link MockitoMockObjectOutputStream},
190          * in a byte array.</p>
191          *
192          * @param mockitoMock The Mockito mock to serialize.
193          * @throws IOException
194          */
AcrossJVMMockSerializationProxy(Object mockitoMock)195         public AcrossJVMMockSerializationProxy(Object mockitoMock) throws IOException {
196             ByteArrayOutputStream out = new ByteArrayOutputStream();
197             ObjectOutputStream objectOutputStream = new MockitoMockObjectOutputStream(out);
198 
199             objectOutputStream.writeObject(mockitoMock);
200 
201             objectOutputStream.close();
202             out.close();
203 
204             MockCreationSettings mockSettings = new MockUtil().getMockSettings(mockitoMock);
205             this.serializedMock = out.toByteArray();
206             this.typeToMock = mockSettings.getTypeToMock();
207             this.extraInterfaces = mockSettings.getExtraInterfaces();
208         }
209 
210         /**
211          * Resolves the proxy to a new deserialized instance of the Mockito mock.
212          *
213          * <p>Uses the custom crafted {@link MockitoMockObjectInputStream} to deserialize the mock.</p>
214          *
215          * @return A deserialized instance of the Mockito mock.
216          * @throws ObjectStreamException
217          */
readResolve()218         private Object readResolve() throws ObjectStreamException {
219             try {
220                 ByteArrayInputStream bis = new ByteArrayInputStream(serializedMock);
221                 ObjectInputStream objectInputStream = new MockitoMockObjectInputStream(bis, typeToMock, extraInterfaces);
222 
223                 Object deserializedMock = objectInputStream.readObject();
224 
225                 bis.close();
226                 objectInputStream.close();
227 
228                 return deserializedMock;
229             } catch (IOException ioe) {
230                 throw new MockitoSerializationIssue(join(
231                         "Mockito mock cannot be deserialized to a mock of '" + typeToMock.getCanonicalName() + "'. The error was :",
232                         "  " + ioe.getMessage(),
233                         "If you are unsure what is the reason of this exception, feel free to contact us on the mailing list."
234                 ), ioe);
235             } catch (ClassNotFoundException cce) {
236                 throw new MockitoSerializationIssue(join(
237                         "A class couldn't be found while deserializing a Mockito mock, you should check your classpath. The error was :",
238                         "  " + cce.getMessage(),
239                         "If you are still unsure what is the reason of this exception, feel free to contact us on the mailing list."
240                 ), cce);
241             }
242         }
243     }
244 
245 
246     /**
247      * Special Mockito aware <code>ObjectInputStream</code> that will resolve the Mockito proxy class.
248      *
249      * <p>
250      *     This specificaly crafted ObjectInoutStream has the most important role to resolve the Mockito generated
251      *     class. It is doing so via the {@link #resolveClass(java.io.ObjectStreamClass)} which looks in the stream
252      *     for a Mockito marker. If this marker is found it will try to resolve the mockito class otherwise it
253      *     delegates class resolution to the default super behavior.
254      *     The mirror method used for serializing the mock is {@link MockitoMockObjectOutputStream#annotateClass(Class)}.
255      * </p>
256      *
257      * <p>
258      *     When this marker is found, {@link ClassImposterizer} methods are being used to create the mock class.
259      *     <em>Note that behind the <code>ClassImposterizer</code> there is CGLIB and the
260      *     {@link org.mockito.internal.creation.jmock.SearchingClassLoader} that will look if this enhanced class has
261      *     already been created in an accessible classloader ; so basically this code trusts the ClassImposterizer
262      *     code.</em>
263      * </p>
264      */
265     public static class MockitoMockObjectInputStream extends ObjectInputStream {
266         private Class typeToMock;
267         private Set<Class> extraInterfaces;
268 
MockitoMockObjectInputStream(InputStream in, Class typeToMock, Set<Class> extraInterfaces)269         public MockitoMockObjectInputStream(InputStream in, Class typeToMock, Set<Class> extraInterfaces) throws IOException {
270             super(in) ;
271             this.typeToMock = typeToMock;
272             this.extraInterfaces = extraInterfaces;
273             enableResolveObject(true); // ensure resolving is enabled
274         }
275 
276         /**
277          * Resolve the Mockito proxy class if it is marked as such.
278          *
279          * <p>Uses the fields {@link #typeToMock} and {@link #extraInterfaces} to
280          * create the Mockito proxy class as the <code>ObjectStreamClass</code>
281          * doesn't carry useful information for this purpose.</p>
282          *
283          * @param desc Description of the class in the stream, not used.
284          * @return The class that will be used to deserialize the instance mock.
285          * @throws IOException
286          * @throws ClassNotFoundException
287          */
288         @Override
resolveClass(ObjectStreamClass desc)289         protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
290             if (notMarkedAsAMockitoMock(readObject())) {
291                 return super.resolveClass(desc);
292             }
293 
294             // TODO check the class is mockable in the deserialization side
295             // ClassImposterizer.INSTANCE.canImposterise(typeToMock);
296 
297             // create the Mockito mock class before it can even be deserialized
298             ClassImposterizer.INSTANCE.setConstructorsAccessible(typeToMock, true);
299             Class<?> proxyClass = ClassImposterizer.INSTANCE.createProxyClass(
300                     typeToMock,
301                     extraInterfaces.toArray(new Class[extraInterfaces.size()])
302             );
303 
304             hackClassNameToMatchNewlyCreatedClass(desc, proxyClass);
305 
306             return proxyClass;
307 
308         }
309 
310         /**
311          * Hack the <code>name</code> field of the given <code>ObjectStreamClass</code> with
312          * the <code>newProxyClass</code>.
313          *
314          * The parent ObjectInputStream will check the name of the class in the stream matches the name of the one
315          * that is created in this method.
316          *
317          * The CGLIB classes uses a hash of the classloader and/or maybe some other data that allow them to be
318          * relatively unique in a JVM.
319          *
320          * When names differ, which happens when the mock is deserialized in another ClassLoader, a
321          * <code>java.io.InvalidObjectException</code> is thrown, so this part of the code is hacking through
322          * the given <code>ObjectStreamClass</code> to change the name with the newly created class.
323          *
324          * @param descInstance The <code>ObjectStreamClass</code> that will be hacked.
325          * @param proxyClass The proxy class whose name will be applied.
326          * @throws InvalidObjectException
327          */
hackClassNameToMatchNewlyCreatedClass(ObjectStreamClass descInstance, Class<?> proxyClass)328         private void hackClassNameToMatchNewlyCreatedClass(ObjectStreamClass descInstance, Class<?> proxyClass) throws ObjectStreamException {
329             try {
330               Field classNameField = descInstance.getClass().getDeclaredField("name");
331               new FieldSetter(descInstance, classNameField).set(proxyClass.getCanonicalName());
332             } catch (NoSuchFieldException nsfe) {
333                 // TODO use our own mockito mock serialization exception
334                 throw new MockitoSerializationIssue(join(
335                         "Wow, the class 'ObjectStreamClass' in the JDK don't have the field 'name',",
336                         "this is definitely a bug in our code as it means the JDK team changed a few internal things.",
337                         "",
338                         "Please report an issue with the JDK used, a code sample and a link to download the JDK would be welcome."
339                 ), nsfe);
340             }
341         }
342 
343         /**
344          * Read the stream class annotation and identify it as a Mockito mock or not.
345          *
346          * @param marker The marker to identify.
347          * @return <code>true</code> if not marked as a Mockito, <code>false</code> if the class annotation marks a Mockito mock.
348          * @throws IOException
349          * @throws ClassNotFoundException
350          */
notMarkedAsAMockitoMock(Object marker)351         private boolean notMarkedAsAMockitoMock(Object marker) throws IOException, ClassNotFoundException {
352             return !MOCKITO_PROXY_MARKER.equals(marker);
353         }
354     }
355 
356 
357     /**
358      * Special Mockito aware <code>ObjectOutputStream</code>.
359      *
360      * <p>
361      *     This output stream has the role of marking in the stream the Mockito class. This
362      *     marking process is necessary to identify the proxy class that will need to be recreated.
363      *
364      *     The mirror method used for deserializing the mock is
365      *     {@link MockitoMockObjectInputStream#resolveClass(ObjectStreamClass)}.
366      * </p>
367      *
368      */
369     private static class MockitoMockObjectOutputStream extends ObjectOutputStream {
370         private static final String NOTHING = "";
371         private MockUtil mockUtil = new MockUtil();
372 
MockitoMockObjectOutputStream(ByteArrayOutputStream out)373         public MockitoMockObjectOutputStream(ByteArrayOutputStream out) throws IOException {
374             super(out);
375         }
376 
377         /**
378          * Annotates (marks) the class if this class is a Mockito mock.
379          *
380          * @param cl The class to annotate.
381          * @throws IOException
382          */
383         @Override
annotateClass(Class<?> cl)384         protected void annotateClass(Class<?> cl) throws IOException {
385             writeObject(mockitoProxyClassMarker(cl));
386             // might be also useful later, for embedding classloader info ...maybe ...maybe not
387         }
388 
389         /**
390          * Returns the Mockito marker if this class is a Mockito mock.
391          *
392          * @param cl The class to mark.
393          * @return The marker if this is a Mockito proxy class, otherwise returns a void marker.
394          */
mockitoProxyClassMarker(Class<?> cl)395         private String mockitoProxyClassMarker(Class<?> cl) {
396             if (mockUtil.isMock(cl)) {
397                 return MOCKITO_PROXY_MARKER;
398             } else {
399                 return NOTHING;
400             }
401         }
402     }
403 
404 
405     /**
406      * Simple interface that hold a correct <code>writeReplace</code> signature that can be seen by an
407      * <code>ObjectOutputStream</code>.
408      *
409      * It will be applied before the creation of the mock when the mock setting says it should serializable.
410      *
411      * @see #enableSerializationAcrossJVM(org.mockito.mock.MockCreationSettings)
412      */
413     public interface AcrossJVMMockitoMockSerializable {
writeReplace()414         public Object writeReplace() throws java.io.ObjectStreamException;
415     }
416 }
417