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