1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5
6package org.mockito.internal.creation.bytebuddy;
7
8import org.mockito.Incubating;
9import org.mockito.exceptions.base.MockitoSerializationIssue;
10import org.mockito.internal.configuration.plugins.Plugins;
11import org.mockito.internal.creation.settings.CreationSettings;
12import org.mockito.internal.util.MockUtil;
13import org.mockito.mock.MockCreationSettings;
14import org.mockito.mock.MockName;
15import org.mockito.mock.SerializableMode;
16
17import java.io.*;
18import java.lang.reflect.Field;
19import java.util.Set;
20import java.util.concurrent.locks.Lock;
21import java.util.concurrent.locks.ReentrantLock;
22
23import static org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.ForWriteReplace;
24import static org.mockito.internal.util.StringUtil.join;
25import 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
55class 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     */
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
132    private void mockReplacementCompleted() {
133        instanceLocalCurrentlySerializingFlag = false;
134    }
135
136
137    private void mockReplacementStarted() {
138        instanceLocalCurrentlySerializingFlag = true;
139    }
140
141
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         */
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         */
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
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
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         */
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         */
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
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
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         */
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 {
386        Object writeReplace();
387    }
388}
389