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 static final String CODEGEN_PACKAGE = "org.mockito.codegen."; 42 43 private final SubclassLoader loader; 44 45 private final ByteBuddy byteBuddy; 46 private final Random random; 47 48 private final Implementation readReplace; 49 private final ElementMatcher<? super MethodDescription> matcher; 50 51 private final Implementation dispatcher = to(DispatcherDefaultingToRealMethod.class); 52 private final Implementation hashCode = to(MockMethodInterceptor.ForHashCode.class); 53 private final Implementation equals = to(MockMethodInterceptor.ForEquals.class); 54 private final Implementation writeReplace = to(MockMethodInterceptor.ForWriteReplace.class); 55 SubclassBytecodeGenerator()56 public SubclassBytecodeGenerator() { 57 this(new SubclassInjectionLoader()); 58 } 59 SubclassBytecodeGenerator(SubclassLoader loader)60 public SubclassBytecodeGenerator(SubclassLoader loader) { 61 this(loader, null, any()); 62 } 63 SubclassBytecodeGenerator(Implementation readReplace, ElementMatcher<? super MethodDescription> matcher)64 public SubclassBytecodeGenerator(Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) { 65 this(new SubclassInjectionLoader(), readReplace, matcher); 66 } 67 SubclassBytecodeGenerator(SubclassLoader loader, Implementation readReplace, ElementMatcher<? super MethodDescription> matcher)68 protected SubclassBytecodeGenerator(SubclassLoader loader, Implementation readReplace, ElementMatcher<? super MethodDescription> matcher) { 69 this.loader = loader; 70 this.readReplace = readReplace; 71 this.matcher = matcher; 72 byteBuddy = new ByteBuddy().with(TypeValidation.DISABLED); 73 random = new Random(); 74 } 75 76 @Override mockClass(MockFeatures<T> features)77 public <T> Class<? extends T> mockClass(MockFeatures<T> features) { 78 String name = nameFor(features.mockedType); 79 DynamicType.Builder<T> builder = 80 byteBuddy.subclass(features.mockedType) 81 .name(name) 82 .ignoreAlso(isGroovyMethod()) 83 .annotateType(features.stripAnnotations 84 ? new Annotation[0] 85 : features.mockedType.getAnnotations()) 86 .implement(new ArrayList<Type>(features.interfaces)) 87 .method(matcher) 88 .intercept(dispatcher) 89 .transform(withModifiers(SynchronizationState.PLAIN)) 90 .attribute(features.stripAnnotations 91 ? MethodAttributeAppender.NoOp.INSTANCE 92 : INCLUDING_RECEIVER) 93 .method(isHashCode()) 94 .intercept(hashCode) 95 .method(isEquals()) 96 .intercept(equals) 97 .serialVersionUid(42L) 98 .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) 99 .implement(MockAccess.class) 100 .intercept(FieldAccessor.ofBeanProperty()); 101 if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { 102 builder = builder.implement(CrossClassLoaderSerializableMock.class) 103 .intercept(writeReplace); 104 } 105 if (readReplace != null) { 106 builder = builder.defineMethod("readObject", void.class, Visibility.PRIVATE) 107 .withParameters(ObjectInputStream.class) 108 .throwing(ClassNotFoundException.class, IOException.class) 109 .intercept(readReplace); 110 } 111 ClassLoader classLoader = new MultipleParentClassLoader.Builder() 112 .append(features.mockedType) 113 .append(features.interfaces) 114 .append(currentThread().getContextClassLoader()) 115 .append(MockAccess.class, DispatcherDefaultingToRealMethod.class) 116 .append(MockMethodInterceptor.class, 117 MockMethodInterceptor.ForHashCode.class, 118 MockMethodInterceptor.ForEquals.class).build(MockMethodInterceptor.class.getClassLoader()); 119 if (classLoader != features.mockedType.getClassLoader()) { 120 assertVisibility(features.mockedType); 121 for (Class<?> iFace : features.interfaces) { 122 assertVisibility(iFace); 123 } 124 builder = builder.ignoreAlso(isPackagePrivate() 125 .or(returns(isPackagePrivate())) 126 .or(hasParameters(whereAny(hasType(isPackagePrivate()))))); 127 } 128 return builder.make() 129 .load(classLoader, loader.resolveStrategy(features.mockedType, classLoader, name.startsWith(CODEGEN_PACKAGE))) 130 .getLoaded(); 131 } 132 isGroovyMethod()133 private static ElementMatcher<MethodDescription> isGroovyMethod() { 134 return isDeclaredBy(named("groovy.lang.GroovyObjectSupport")); 135 } 136 137 // TODO inspect naming strategy (for OSGI, signed package, java.* (and bootstrap classes), etc...) nameFor(Class<?> type)138 private String nameFor(Class<?> type) { 139 String typeName = type.getName(); 140 if (isComingFromJDK(type) 141 || isComingFromSignedJar(type) 142 || isComingFromSealedPackage(type)) { 143 typeName = CODEGEN_PACKAGE + type.getSimpleName(); 144 } 145 return String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt())); 146 } 147 isComingFromJDK(Class<?> type)148 private boolean isComingFromJDK(Class<?> type) { 149 // Comes from the manifest entry : 150 // Implementation-Title: Java Runtime Environment 151 // This entry is not necessarily present in every jar of the JDK 152 return type.getPackage() != null && "Java Runtime Environment".equalsIgnoreCase(type.getPackage().getImplementationTitle()) 153 || type.getName().startsWith("java.") 154 || type.getName().startsWith("javax."); 155 } 156 isComingFromSealedPackage(Class<?> type)157 private boolean isComingFromSealedPackage(Class<?> type) { 158 return type.getPackage() != null && type.getPackage().isSealed(); 159 } 160 isComingFromSignedJar(Class<?> type)161 private boolean isComingFromSignedJar(Class<?> type) { 162 return type.getSigners() != null; 163 } 164 assertVisibility(Class<?> type)165 private static void assertVisibility(Class<?> type) { 166 if (!Modifier.isPublic(type.getModifiers())) { 167 throw new MockitoException(join("Cannot create mock for " + type, 168 "", 169 "The type is not public and its mock class is loaded by a different class loader.", 170 "This can have multiple reasons:", 171 " - You are mocking a class with additional interfaces of another class loader", 172 " - Mockito is loaded by a different class loader than the mocked type (e.g. with OSGi)", 173 " - The thread's context class loader is different than the mock's class loader")); 174 } 175 } 176 } 177