Skip to content

Instantly share code, notes, and snippets.

@raphw
Last active September 11, 2017 11:30
Show Gist options
  • Save raphw/8789f5ef08240008efc8 to your computer and use it in GitHub Desktop.
Save raphw/8789f5ef08240008efc8 to your computer and use it in GitHub Desktop.
Constructor interception fix
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;
}
}
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