• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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