SubclassBytecodeGenerator.java revision 08bd32ce48b12ae751dd5c4829ff09a6fb9894f0
1/*
2 * Copyright (c) 2016 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5package org.mockito.internal.creation.bytebuddy;
6
7import net.bytebuddy.ByteBuddy;
8import net.bytebuddy.description.method.MethodDescription;
9import net.bytebuddy.description.modifier.SynchronizationState;
10import net.bytebuddy.description.modifier.Visibility;
11import net.bytebuddy.dynamic.DynamicType;
12import net.bytebuddy.dynamic.loading.MultipleParentClassLoader;
13import net.bytebuddy.dynamic.scaffold.TypeValidation;
14import net.bytebuddy.implementation.FieldAccessor;
15import net.bytebuddy.implementation.Implementation;
16import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
17import net.bytebuddy.matcher.ElementMatcher;
18import org.mockito.exceptions.base.MockitoException;
19import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock;
20import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.DispatcherDefaultingToRealMethod;
21import org.mockito.mock.SerializableMode;
22
23import java.io.IOException;
24import java.io.ObjectInputStream;
25import java.lang.annotation.Annotation;
26import java.lang.reflect.Modifier;
27import java.lang.reflect.Type;
28import java.util.ArrayList;
29import java.util.Random;
30
31import static java.lang.Thread.currentThread;
32import static net.bytebuddy.description.modifier.Visibility.PRIVATE;
33import static net.bytebuddy.dynamic.Transformer.ForMethod.withModifiers;
34import static net.bytebuddy.implementation.MethodDelegation.to;
35import static net.bytebuddy.implementation.attribute.MethodAttributeAppender.ForInstrumentedMethod.INCLUDING_RECEIVER;
36import static net.bytebuddy.matcher.ElementMatchers.*;
37import static org.mockito.internal.util.StringUtil.join;
38
39class 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