Skip to content

Commit

Permalink
Merge pull request #2006 from mockito/introduce-animal-sniffer
Browse files Browse the repository at this point in the history
Introduce animal sniffer
  • Loading branch information
raphw authored Aug 18, 2020
2 parents 70633c4 + ec659f4 commit f41ec63
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 40 deletions.
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ plugins {
id 'eclipse'
id 'com.github.ben-manes.versions' version '0.28.0'
id 'biz.aQute.bnd.builder' version '5.1.0'
id 'ru.vyarus.animalsniffer' version '1.5.1'
}

description = 'Mockito mock objects library core API and implementation'
Expand Down Expand Up @@ -98,6 +99,14 @@ dependencies {
testRuntime configurations.compileOnly

testUtil sourceSets.test.output

signature 'org.codehaus.mojo.signature:java18:1.0@signature'
signature 'net.sf.androidscents.signature:android-api-level-24:7.0_r2@signature'
}

animalsniffer {
sourceSets = [sourceSets.main]
annotation = 'org.mockito.internal.SuppressSignatureCheck'
}

wrapper {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/mockito/internal/MockedStaticImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,6 @@ private void assertNotClosed() {

@Override
public String toString() {
return "static mock for " + control.getType().getTypeName();
return "static mock for " + control.getType().getName();
}
}
11 changes: 11 additions & 0 deletions src/main/java/org/mockito/internal/SuppressSignatureCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2020 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal;

import java.lang.annotation.*;

@Retention(RetentionPolicy.CLASS)
@Documented
public @interface SuppressSignatureCheck {}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.mockito.exceptions.base.MockitoException;
import org.mockito.exceptions.base.MockitoInitializationException;
import org.mockito.exceptions.misusing.MockitoConfigurationException;
import org.mockito.internal.SuppressSignatureCheck;
import org.mockito.internal.configuration.plugins.Plugins;
import org.mockito.internal.util.Platform;
import org.mockito.internal.util.concurrent.DetachedThreadLocal;
Expand Down Expand Up @@ -99,6 +100,7 @@
* support this feature.
*/
@Incubating
@SuppressSignatureCheck
public class InlineByteBuddyMockMaker
implements ClassCreatingMockMaker, InlineMockMaker, Instantiator {

Expand Down Expand Up @@ -506,7 +508,7 @@ public <T> StaticMockControl<T> createStaticMock(
|| ClassLoader.class.isAssignableFrom(type)) {
throw new MockitoException(
"It is not possible to mock static methods of "
+ type.getTypeName()
+ type.getName()
+ " to avoid interfering with class loading what leads to infinite loops");
}

Expand Down Expand Up @@ -534,7 +536,7 @@ public <T> ConstructionMockControl<T> createConstructionMock(
} else if (type.isPrimitive() || Modifier.isAbstract(type.getModifiers())) {
throw new MockitoException(
"It is not possible to construct primitive types or abstract types: "
+ type.getTypeName());
+ type.getName());
}

bytecodeGenerator.mockClassConstruction(type);
Expand All @@ -555,7 +557,7 @@ public <T> ConstructionMockControl<T> createConstructionMock(
public <T> T newInstance(Class<T> cls) throws InstantiationException {
Constructor<?>[] constructors = cls.getDeclaredConstructors();
if (constructors.length == 0) {
throw new InstantiationException(cls.getTypeName() + " does not define a constructor");
throw new InstantiationException(cls.getName() + " does not define a constructor");
}
Constructor<?> selected = constructors[0];
for (Constructor<?> constructor : constructors) {
Expand All @@ -579,7 +581,7 @@ public <T> T newInstance(Class<T> cls) throws InstantiationException {
mockitoConstruction.set(false);
}
} catch (Exception e) {
throw new InstantiationException("Could not instantiate " + cls.getTypeName(), e);
throw new InstantiationException("Could not instantiate " + cls.getName(), e);
}
}

Expand Down Expand Up @@ -754,14 +756,14 @@ private static class InlineConstructionMockContext implements MockedConstruction
private static final Map<String, Class<?>> PRIMITIVES = new HashMap<>();

static {
PRIMITIVES.put(boolean.class.getTypeName(), boolean.class);
PRIMITIVES.put(byte.class.getTypeName(), byte.class);
PRIMITIVES.put(short.class.getTypeName(), short.class);
PRIMITIVES.put(char.class.getTypeName(), char.class);
PRIMITIVES.put(int.class.getTypeName(), int.class);
PRIMITIVES.put(long.class.getTypeName(), long.class);
PRIMITIVES.put(float.class.getTypeName(), float.class);
PRIMITIVES.put(double.class.getTypeName(), double.class);
PRIMITIVES.put(boolean.class.getName(), boolean.class);
PRIMITIVES.put(byte.class.getName(), byte.class);
PRIMITIVES.put(short.class.getName(), short.class);
PRIMITIVES.put(char.class.getName(), char.class);
PRIMITIVES.put(int.class.getName(), int.class);
PRIMITIVES.put(long.class.getName(), long.class);
PRIMITIVES.put(float.class.getName(), float.class);
PRIMITIVES.put(double.class.getName(), double.class);
}

private int count;
Expand Down Expand Up @@ -810,7 +812,7 @@ public Constructor<?> constructor() {
join(
"Could not resolve constructor of type",
"",
type.getTypeName(),
type.getName(),
"",
"with arguments of types",
Arrays.toString(parameterTypes)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import net.bytebuddy.utility.OpenedClassReader;
import net.bytebuddy.utility.RandomString;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.SuppressSignatureCheck;
import org.mockito.internal.creation.bytebuddy.inject.MockMethodDispatcher;
import org.mockito.internal.util.concurrent.DetachedThreadLocal;
import org.mockito.internal.util.concurrent.WeakConcurrentMap;
Expand All @@ -48,6 +49,7 @@
import static net.bytebuddy.matcher.ElementMatchers.*;
import static org.mockito.internal.util.StringUtil.*;

@SuppressSignatureCheck
public class InlineBytecodeGenerator implements BytecodeGenerator, ClassFileTransformer {

private static final String PRELOAD = "org.mockito.inline.preload";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodCall;
import org.mockito.exceptions.base.MockitoInitializationException;
import org.mockito.internal.SuppressSignatureCheck;
import org.mockito.plugins.MemberAccessor;

import java.lang.instrument.Instrumentation;
Expand All @@ -21,6 +22,7 @@
import static net.bytebuddy.matcher.ElementMatchers.named;
import static org.mockito.internal.util.StringUtil.join;

@SuppressSignatureCheck
class InstrumentationMemberAccessor implements MemberAccessor {

private static final Map<Class<?>, Class<?>> WRAPPERS = new HashMap<>();
Expand Down Expand Up @@ -67,6 +69,13 @@ class InstrumentationMemberAccessor implements MemberAccessor {
"setAccessible", boolean.class))
.onArgument(0)
.withArgument(1))
.method(named("invokeWithArguments"))
.intercept(
MethodCall.invoke(
MethodHandle.class.getMethod(
"invokeWithArguments", Object[].class))
.onArgument(0)
.withArgument(1))
.make()
.load(
InstrumentationMemberAccessor.class.getClassLoader(),
Expand Down Expand Up @@ -144,17 +153,20 @@ public Object newInstance(Constructor<?> constructor, Object... arguments)
}
assureArguments(constructor, null, null, arguments, constructor.getParameterTypes());
try {
Object module = getModule.bindTo(constructor.getDeclaringClass()).invokeWithArguments();
Object module =
DISPATCHER.invokeWithArguments(
getModule.bindTo(constructor.getDeclaringClass()));
String packageName = constructor.getDeclaringClass().getPackage().getName();
assureOpen(module, packageName);
MethodHandle handle =
((MethodHandles.Lookup)
privateLookupIn.invokeExact(
DISPATCHER.invokeWithArguments(
privateLookupIn,
constructor.getDeclaringClass(),
DISPATCHER.getLookup()))
.unreflectConstructor(constructor);
try {
return handle.invokeWithArguments(arguments);
return DISPATCHER.invokeWithArguments(handle, arguments);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
Expand All @@ -180,19 +192,22 @@ public Object invoke(Method method, Object target, Object... arguments)
arguments,
method.getParameterTypes());
try {
Object module = getModule.bindTo(method.getDeclaringClass()).invokeWithArguments();
Object module =
DISPATCHER.invokeWithArguments(getModule.bindTo(method.getDeclaringClass()));
String packageName = method.getDeclaringClass().getPackage().getName();
assureOpen(module, packageName);
MethodHandle handle =
((MethodHandles.Lookup)
privateLookupIn.invokeExact(
method.getDeclaringClass(), DISPATCHER.getLookup()))
DISPATCHER.invokeWithArguments(
privateLookupIn,
method.getDeclaringClass(),
DISPATCHER.getLookup()))
.unreflect(method);
if (!Modifier.isStatic(method.getModifiers())) {
handle = handle.bindTo(target);
}
try {
return handle.invokeWithArguments(arguments);
return DISPATCHER.invokeWithArguments(handle, arguments);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
Expand All @@ -219,18 +234,21 @@ public Object get(Field field, Object target) {
new Object[0],
new Class<?>[0]);
try {
Object module = getModule.bindTo(field.getDeclaringClass()).invokeWithArguments();
Object module =
DISPATCHER.invokeWithArguments(getModule.bindTo(field.getDeclaringClass()));
String packageName = field.getDeclaringClass().getPackage().getName();
assureOpen(module, packageName);
MethodHandle handle =
((MethodHandles.Lookup)
privateLookupIn.invokeExact(
field.getDeclaringClass(), DISPATCHER.getLookup()))
DISPATCHER.invokeWithArguments(
privateLookupIn,
field.getDeclaringClass(),
DISPATCHER.getLookup()))
.unreflectGetter(field);
if (!Modifier.isStatic(field.getModifiers())) {
handle = handle.bindTo(target);
}
return handle.invokeWithArguments();
return DISPATCHER.invokeWithArguments(handle);
} catch (Throwable t) {
throw new IllegalStateException("Could not read " + field + " on " + target, t);
}
Expand All @@ -246,7 +264,8 @@ public void set(Field field, Object target, Object value) throws IllegalAccessEx
new Class<?>[] {field.getType()});
boolean illegalAccess = false;
try {
Object module = getModule.bindTo(field.getDeclaringClass()).invokeWithArguments();
Object module =
DISPATCHER.invokeWithArguments(getModule.bindTo(field.getDeclaringClass()));
String packageName = field.getDeclaringClass().getPackage().getName();
assureOpen(module, packageName);
// Method handles do not allow setting final fields where setAccessible(true)
Expand All @@ -268,13 +287,15 @@ public void set(Field field, Object target, Object value) throws IllegalAccessEx
try {
MethodHandle handle =
((MethodHandles.Lookup)
privateLookupIn.invokeExact(
field.getDeclaringClass(), DISPATCHER.getLookup()))
DISPATCHER.invokeWithArguments(
privateLookupIn,
field.getDeclaringClass(),
DISPATCHER.getLookup()))
.unreflectSetter(field);
if (!Modifier.isStatic(field.getModifiers())) {
handle = handle.bindTo(target);
}
handle.invokeWithArguments(value);
DISPATCHER.invokeWithArguments(handle, value);
} finally {
if (isFinal) {
DISPATCHER.setAccessible(field, false);
Expand All @@ -290,17 +311,18 @@ public void set(Field field, Object target, Object value) throws IllegalAccessEx
}

private void assureOpen(Object module, String packageName) throws Throwable {
if (!(Boolean) isOpen.invokeWithArguments(module, packageName, DISPATCHER.getModule())) {
redefineModule
.bindTo(INSTRUMENTATION)
.invokeWithArguments(
module,
Collections.emptySet(),
Collections.emptyMap(),
Collections.singletonMap(
packageName, Collections.singleton(DISPATCHER.getModule())),
Collections.emptySet(),
Collections.emptyMap());
if (!(Boolean)
DISPATCHER.invokeWithArguments(
isOpen, module, packageName, DISPATCHER.getModule())) {
DISPATCHER.invokeWithArguments(
redefineModule.bindTo(INSTRUMENTATION),
module,
Collections.emptySet(),
Collections.emptyMap(),
Collections.singletonMap(
packageName, Collections.singleton(DISPATCHER.getModule())),
Collections.emptySet(),
Collections.emptyMap());
}
}

Expand Down Expand Up @@ -359,5 +381,10 @@ public interface Dispatcher {
Object getModule();

void setAccessible(AccessibleObject target, boolean value);

// Used to avoid invoke/invokeExact being exposed to Android where this class should
// never be loaded. Since the invocation happens from the generated code, the Android
// build pipeline does not fail.
Object invokeWithArguments(MethodHandle handle, Object... arguments) throws Throwable;
}
}

0 comments on commit f41ec63

Please sign in to comment.