Last active
September 11, 2017 11:30
-
-
Save raphw/8789f5ef08240008efc8 to your computer and use it in GitHub Desktop.
Constructor interception fix
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.mockito.internal.creation.bytebuddy; | |
import java.util.Random; | |
import net.bytebuddy.implementation.Implementation; | |
import net.bytebuddy.implementation.bind.annotation.FieldProxy; | |
import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock; | |
import org.mockito.internal.creation.util.SearchingClassLoader; | |
import net.bytebuddy.ByteBuddy; | |
import net.bytebuddy.ClassFileVersion; | |
import net.bytebuddy.description.modifier.FieldManifestation; | |
import net.bytebuddy.description.modifier.Ownership; | |
import net.bytebuddy.description.modifier.Visibility; | |
import net.bytebuddy.dynamic.DynamicType; | |
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; | |
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; | |
import net.bytebuddy.implementation.FieldAccessor; | |
import net.bytebuddy.implementation.MethodDelegation; | |
import net.bytebuddy.implementation.attribute.MethodAttributeAppender; | |
import net.bytebuddy.implementation.attribute.TypeAttributeAppender; | |
import net.bytebuddy.matcher.ElementMatchers; | |
class MockBytecodeGenerator { | |
private final ByteBuddy byteBuddy; | |
private final Implementation delegation; | |
private final Random random; | |
public MockBytecodeGenerator() { | |
byteBuddy = new ByteBuddy(ClassFileVersion.JAVA_V5) | |
// .withIgnoredMethods(isBridge()) | |
.withDefaultMethodAttributeAppender(MethodAttributeAppender.ForInstrumentedMethod.INSTANCE) | |
.withAttribute(TypeAttributeAppender.ForSuperType.INSTANCE); | |
delegation = MethodDelegation.to(MockMethodInterceptor.Dispatcher.class) | |
.appendParameterBinder(FieldProxy.Binder.install(MockMethodInterceptor.FieldGetter.class, MockMethodInterceptor.FieldSetter.class)); | |
random = new Random(); | |
} | |
public <T> Class<? extends T> generateMockClass(MockFeatures<T> features) { | |
DynamicType.Builder<T> builder = byteBuddy.subclass(features.mockedType, ConstructorStrategy.Default.IMITATE_SUPER_TYPE) | |
.name(nameFor(features.mockedType)) | |
.implement(features.interfaces.toArray(new Class<?>[features.interfaces.size()])) | |
.method(ElementMatchers.any()).intercept(delegation) | |
.defineField("mockitoInterceptor", MockMethodInterceptor.class, Visibility.PRIVATE) | |
.implement(MockMethodInterceptor.MockAccess.class).intercept(FieldAccessor.ofBeanProperty()) | |
.method(ElementMatchers.isHashCode()).intercept(MethodDelegation.to(MockMethodInterceptor.ForHashCode.class)) | |
.method(ElementMatchers.isEquals()).intercept(MethodDelegation.to(MockMethodInterceptor.ForEquals.class)) | |
.defineField("serialVersionUID", long.class, Ownership.STATIC, Visibility.PRIVATE, FieldManifestation.FINAL).value(42L); | |
if (features.crossClassLoaderSerializable) { | |
builder = builder.implement(CrossClassLoaderSerializableMock.class) | |
.intercept(MethodDelegation.to(MockMethodInterceptor.ForWriteReplace.class)); | |
} | |
Class<?>[] allMockedTypes = new Class<?>[features.interfaces.size() + 1]; | |
allMockedTypes[0] = features.mockedType; | |
// System.arraycopy(interfaces.toArray(), 0, allMockedTypes, 1, interfaces.size()); | |
int index = 1; | |
for (Class<?> type : features.interfaces) { | |
allMockedTypes[index++] = type; | |
} | |
return builder.make() | |
.load(SearchingClassLoader.combineLoadersOf(allMockedTypes), ClassLoadingStrategy.Default.INJECTION) | |
.getLoaded(); | |
} | |
// TODO inspect naming strategy (for OSGI, signed package, java.* (and bootstrap classes), etc...) | |
private String nameFor(Class<?> type) { | |
String typeName = type.getName(); | |
if (isComingFromJDK(type) | |
|| isComingFromSignedJar(type) | |
|| isComingFromSealedPackage(type)) { | |
typeName = "codegen." + typeName; | |
} | |
return String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt())); | |
} | |
private boolean isComingFromJDK(Class<?> type) { | |
// Comes from the manifest entry : | |
// Implementation-Title: Java Runtime Environment | |
// This entry is not necessarily present in every jar of the JDK | |
return type.getPackage() != null && "Java Runtime Environment".equalsIgnoreCase(type.getPackage().getImplementationTitle()) | |
|| type.getName().startsWith("java.") | |
|| type.getName().startsWith("javax."); | |
} | |
private boolean isComingFromSealedPackage(Class<?> type) { | |
return type.getPackage() != null && type.getPackage().isSealed(); | |
} | |
private boolean isComingFromSignedJar(Class<?> type) { | |
return type.getSigners() != null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.mockito.internal.creation.bytebuddy; | |
import net.bytebuddy.implementation.bind.annotation.*; | |
import org.mockito.internal.InternalMockHandler; | |
import org.mockito.internal.creation.DelegatingMethod; | |
import org.mockito.internal.invocation.MockitoMethod; | |
import org.mockito.internal.invocation.SerializableMethod; | |
import org.mockito.internal.progress.SequenceNumber; | |
import org.mockito.invocation.MockHandler; | |
import org.mockito.mock.MockCreationSettings; | |
import java.io.ObjectStreamException; | |
import java.io.Serializable; | |
import java.lang.reflect.Method; | |
import java.util.concurrent.Callable; | |
public class MockMethodInterceptor implements Serializable { | |
private static final long serialVersionUID = 7152947254057253027L; | |
private final InternalMockHandler handler; | |
private final MockCreationSettings mockCreationSettings; | |
private final ByteBuddyCrossClassLoaderSerializationSupport serializationSupport; | |
public MockMethodInterceptor(InternalMockHandler handler, MockCreationSettings mockCreationSettings) { | |
this.handler = handler; | |
this.mockCreationSettings = mockCreationSettings; | |
serializationSupport = new ByteBuddyCrossClassLoaderSerializationSupport(); | |
} | |
public interface FieldGetter<T> { | |
T getValue(); | |
} | |
public interface FieldSetter<T> { | |
void setValue(T value); | |
} | |
public static class Dispatcher { | |
@RuntimeType | |
@BindingPriority(BindingPriority.DEFAULT * 2) | |
public static Object interceptSuperCallable(@This Object mock, | |
@FieldProxy("mockitoInterceptor") FieldGetter<MockMethodInterceptor> fieldGetter, | |
@Origin Method invokedMethod, | |
@AllArguments Object[] arguments, | |
@SuperCall(serializableProxy = true) Callable<?> superCall) throws Throwable { | |
MockMethodInterceptor interceptor = fieldGetter.getValue(); | |
if (interceptor == null) { | |
return null; | |
} | |
return interceptor.doIntercept( | |
mock, | |
invokedMethod, | |
arguments, | |
new InterceptedInvocation.SuperMethod.FromCallable(superCall) | |
); | |
} | |
@RuntimeType | |
public static Object interceptAbstract(@This Object mock, | |
@FieldProxy("mockitoInterceptor") FieldGetter<MockMethodInterceptor> fieldGetter, | |
@Origin(cache = true) Method invokedMethod, | |
@AllArguments Object[] arguments) throws Throwable {MockMethodInterceptor interceptor = fieldGetter.getValue(); | |
if (interceptor == null) { | |
return null; | |
} | |
return interceptor.doIntercept( | |
mock, | |
invokedMethod, | |
arguments, | |
InterceptedInvocation.SuperMethod.IsIllegal.INSTANCE | |
); | |
} | |
} | |
private Object doIntercept(Object mock, | |
Method invokedMethod, | |
Object[] arguments, | |
InterceptedInvocation.SuperMethod superMethod) throws Throwable { | |
return handler.handle(new InterceptedInvocation( | |
mock, | |
createMockitoMethod(invokedMethod), | |
arguments, | |
superMethod, | |
SequenceNumber.next() | |
)); | |
} | |
private MockitoMethod createMockitoMethod(Method method) { | |
if (mockCreationSettings.isSerializable()) { | |
return new SerializableMethod(method); | |
} else { | |
return new DelegatingMethod(method); | |
} | |
} | |
public MockHandler getMockHandler() { | |
return handler; | |
} | |
public ByteBuddyCrossClassLoaderSerializationSupport getSerializationSupport() { | |
return serializationSupport; | |
} | |
public static class ForHashCode { | |
public static int doIdentityHashCode(@This Object thiz) { | |
return System.identityHashCode(thiz); | |
} | |
} | |
public static class ForEquals { | |
public static boolean doIdentityEquals(@This Object thiz, @Argument(0) Object other) { | |
return thiz == other; | |
} | |
} | |
public static class ForWriteReplace { | |
public static Object doWriteReplace(@This MockAccess thiz) throws ObjectStreamException { | |
return thiz.getMockitoInterceptor().getSerializationSupport().writeReplace(thiz); | |
} | |
} | |
public static interface MockAccess { | |
MockMethodInterceptor getMockitoInterceptor(); | |
void setMockitoInterceptor(MockMethodInterceptor mockMethodInterceptor); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment