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