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