Home | History | Annotate | Download | only in bytebuddy
      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      */
    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