1 /* 2 * Copyright (c) 2016 Mockito contributors 3 * This program is made available under the terms of the MIT License. 4 */ 5 package org.mockito.internal.creation.bytebuddy; 6 7 import net.bytebuddy.ByteBuddy; 8 import net.bytebuddy.description.method.MethodDescription; 9 import net.bytebuddy.description.modifier.SynchronizationState; 10 import net.bytebuddy.description.modifier.Visibility; 11 import net.bytebuddy.dynamic.DynamicType; 12 import net.bytebuddy.dynamic.loading.MultipleParentClassLoader; 13 import net.bytebuddy.dynamic.scaffold.TypeValidation; 14 import net.bytebuddy.implementation.FieldAccessor; 15 import net.bytebuddy.implementation.Implementation; 16 import net.bytebuddy.implementation.attribute.MethodAttributeAppender; 17 import net.bytebuddy.matcher.ElementMatcher; 18 import org.mockito.exceptions.base.MockitoException; 19 import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock; 20 import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod; 21 import org.mockito.mock.SerializableMode; 22 23 import java.io.IOException; 24 import java.io.ObjectInputStream; 25 import java.lang.annotation.Annotation; 26 import java.lang.reflect.Modifier; 27 import java.lang.reflect.Type; 28 import java.util.ArrayList; 29 import java.util.Random; 30 31 import static java.lang.Thread.currentThread; 32 import static net.bytebuddy.description.modifier.Visibility.PRIVATE; 33 import static net.bytebuddy.dynamic.Transformer.ForMethod.withModifiers; 34 import static net.bytebuddy.implementation.MethodDelegation.to; 35 import static net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER; 36 import static net.bytebuddy.matcher.ElementMatchers.*; 37 import static org.mockito.internal.util.StringUtil.join; 38 39 class SubclassBytecodeGenerator implements BytecodeGenerator { 40 41 private final SubclassLoader loader; 42 43 private final ByteBuddy byteBuddy; 44 private final Random random; 45 46 private final Implementation readReplace; 47 private final ElementMatcher<? super MethodDescription> matcher; 48 49 public SubclassBytecodeGenerator() { 50 this(new SubclassInjectionLoader()); 51 } 52 53 public SubclassBytecodeGenerator(SubclassLoader loader) { 54 this(loader, null, any()); 55 } 56 57 public SubclassBytecodeGenerator(Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) { 58 this(new SubclassInjectionLoader(), readReplace, matcher); 59 } 60 61 protected SubclassBytecodeGenerator(SubclassLoader loader, Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) { 62 this.loader = loader; 63 this.readReplace = readReplace; 64 this.matcher = matcher; 65 byteBuddy = new ByteBuddy().with(TypeValidation.DISABLED); 66 random = new Random(); 67 } 68 69 @Override 70 public <T> Class<? extends T> mockClass(MockFeatures<T> features) { 71 DynamicType.Builder<T> builder = 72 byteBuddy.subclass(features.mockedType) 73 .name(nameFor(features.mockedType)) 74 .ignoreAlso(isGroovyMethod()) 75 .annotateType(features.stripAnnotations 76 ? new Annotation[0] 77 : features.mockedType.getAnnotations()) 78 .implement(new ArrayList<Type>(features.interfaces)) 79 .method(matcher) 80 .intercept(to(DispatcherDefaultingToRealMethod.class)) 81 .transform(withModifiers(SynchronizationState.PLAIN)) 82 .attribute(features.stripAnnotations 83 ? MethodAttributeAppender.NoOp.INSTANCE 84 : INCLUDING_RECEIVER) 85 .method(isHashCode()) 86 .intercept(to(MockMethodInterceptor.ForHashCode.class)) 87 .method(isEquals()) 88 .intercept(to(MockMethodInterceptor.ForEquals.class)) 89 .serialVersionUid(42L) 90 .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) 91 .implement(MockAccess.class) 92 .intercept(FieldAccessor.ofBeanProperty()); 93 if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { 94 builder = builder.implement(CrossClassLoaderSerializableMock.class) 95 .intercept(to(MockMethodInterceptor.ForWriteReplace.class)); 96 } 97 if (readReplace != null) { 98 builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) 99 .withParameters(ObjectInputStream.class) 100 .throwing(ClassNotFoundException.class, IOException.class) 101 .intercept(readReplace); 102 } 103 ClassLoader classLoader = new MultipleParentClassLoader.Builder() 104 .append(features.mockedType) 105 .append(features.interfaces) 106 .append(currentThread().getContextClassLoader()) 107 .append(MockAccess.class, DispatcherDefaultingToRealMethod.class) 108 .append(MockMethodInterceptor.class, 109 MockMethodInterceptor.ForHashCode.class, 110 MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader()); 111 if (classLoader != features.mockedType.getClassLoader()) { 112 assertVisibility(features.mockedType); 113 for (Class<?> iFace : features.interfaces) { 114 assertVisibility(iFace); 115 } 116 builder = builder.ignoreAlso(isPackagePrivate() 117 .or(returns(isPackagePrivate())) 118 .or(hasParameters(whereAny(hasType(isPackagePrivate()))))); 119 } 120 return builder.make() 121 .load(classLoader, loader.getStrategy(features.mockedType)) 122 .getLoaded(); 123 } 124 125 private static ElementMatcher<MethodDescription> isGroovyMethod() { 126 return isDeclaredBy(named("groovy.lang.GroovyObjectSupport")); 127 } 128 129 // TODO inspect naming strategy (for OSGI, signed package, java.* (and bootstrap classes), etc...) 130 private String nameFor(Class<?> type) { 131 String typeName = type.getName(); 132 if (isComingFromJDK(type) 133 || isComingFromSignedJar(type) 134 || isComingFromSealedPackage(type)) { 135 typeName = "codegen." + typeName; 136 } 137 return String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt())); 138 } 139 140 private boolean isComingFromJDK(Class<?> type) { 141 // Comes from the manifest entry : 142 // Implementation-Title: Java Runtime Environment 143 // This entry is not necessarily present in every jar of the JDK 144 return type.getPackage() != null && "Java Runtime Environment".equalsIgnoreCase(type.getPackage().getImplementationTitle()) 145 || type.getName().startsWith("java.") 146 || type.getName().startsWith("javax."); 147 } 148 149 private boolean isComingFromSealedPackage(Class<?> type) { 150 return type.getPackage() != null && type.getPackage().isSealed(); 151 } 152 153 private boolean isComingFromSignedJar(Class<?> type) { 154 return type.getSigners() != null; 155 } 156 157 private static void assertVisibility(Class<?> type) { 158 if (!Modifier.isPublic(type.getModifiers())) { 159 throw new MockitoException(join("Cannot create mock for " + type, 160 "", 161 "The type is not public and its mock class is loaded by a different class loader.", 162 "This can have multiple reasons:", 163 " - You are mocking a class with additional interfaces of another class loader", 164 " - Mockito is loaded by a different class loader than the mocked type (e.g. with OSGi)", 165 " - The thread's context class loader is different than the mock's class loader")); 166 } 167 } 168 } 169