diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 0b15d0ec00..8348a23506 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -4,7 +4,7 @@ ext { def versions = [:] -versions.bytebuddy = '1.12.8' +versions.bytebuddy = '1.12.9' versions.junitJupiter = '5.8.2' versions.errorprone = '2.10.0' diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java index 580f22b309..fc92e49acf 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/MockMethodAdvice.java @@ -33,6 +33,7 @@ import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.method.MethodList; import net.bytebuddy.description.method.ParameterDescription; +import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.scaffold.MethodGraph; import net.bytebuddy.implementation.Implementation; @@ -188,7 +189,9 @@ public boolean isOverridden(Object instance, Method origin) { SoftReference reference = graphs.get(instance.getClass()); MethodGraph methodGraph = reference == null ? null : reference.get(); if (methodGraph == null) { - methodGraph = compiler.compile(new TypeDescription.ForLoadedType(instance.getClass())); + methodGraph = + compiler.compile( + (TypeDefinition) TypeDescription.ForLoadedType.of(instance.getClass())); graphs.put(instance.getClass(), new SoftReference<>(methodGraph)); } MethodGraph.Node node = diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/ModuleHandler.java b/src/main/java/org/mockito/internal/creation/bytebuddy/ModuleHandler.java index 38716d0776..e476929132 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/ModuleHandler.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ModuleHandler.java @@ -9,7 +9,6 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Random; import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.modifier.Ownership; @@ -18,6 +17,8 @@ import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.MethodCall; import net.bytebuddy.implementation.StubMethod; +import net.bytebuddy.utility.RandomString; +import org.mockito.Mockito; import org.mockito.codegen.InjectionBase; import org.mockito.exceptions.base.MockitoException; @@ -35,9 +36,9 @@ abstract class ModuleHandler { abstract void adjustModuleGraph(Class source, Class target, boolean export, boolean read); - static ModuleHandler make(ByteBuddy byteBuddy, SubclassLoader loader, Random random) { + static ModuleHandler make(ByteBuddy byteBuddy, SubclassLoader loader) { try { - return new ModuleSystemFound(byteBuddy, loader, random); + return new ModuleSystemFound(byteBuddy, loader); } catch (Exception ignored) { return new NoModuleSystemFound(); } @@ -47,7 +48,6 @@ private static class ModuleSystemFound extends ModuleHandler { private final ByteBuddy byteBuddy; private final SubclassLoader loader; - private final Random random; private final int injectonBaseSuffix; @@ -61,12 +61,10 @@ private static class ModuleSystemFound extends ModuleHandler { addOpens, forName; - private ModuleSystemFound(ByteBuddy byteBuddy, SubclassLoader loader, Random random) - throws Exception { + private ModuleSystemFound(ByteBuddy byteBuddy, SubclassLoader loader) throws Exception { this.byteBuddy = byteBuddy; this.loader = loader; - this.random = random; - injectonBaseSuffix = Math.abs(random.nextInt()); + injectonBaseSuffix = Math.abs(Mockito.class.hashCode()); Class moduleType = Class.forName("java.lang.Module"); getModule = Class.class.getMethod("getModule"); isOpen = moduleType.getMethod("isOpen", String.class, moduleType); @@ -207,9 +205,12 @@ void adjustModuleGraph(Class source, Class target, boolean export, boolean ConstructorStrategy.Default.NO_CONSTRUCTORS) .name( String.format( - "%s$%d", + "%s$%s%s", "org.mockito.codegen.MockitoTypeCarrier", - Math.abs(random.nextInt()))) + RandomString.hashOf( + source.getName().hashCode()), + RandomString.hashOf( + target.getName().hashCode()))) .defineField( "mockitoType", Class.class, @@ -262,10 +263,11 @@ void adjustModuleGraph(Class source, Class target, boolean export, boolean .subclass(Object.class) .name( String.format( - "%s$%s$%d", + "%s$%s$%s%s", source.getName(), "MockitoModuleProbe", - Math.abs(random.nextInt()))) + RandomString.hashOf(source.getName().hashCode()), + RandomString.hashOf(target.getName().hashCode()))) .invokable(isTypeInitializer()) .intercept(implementation) .make() diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java index f67fdfc5a6..6f62777de9 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java @@ -24,15 +24,13 @@ import java.io.IOException; import java.io.ObjectInputStream; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.Random; +import java.util.*; + import net.bytebuddy.ByteBuddy; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.SynchronizationState; @@ -44,6 +42,9 @@ import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.attribute.MethodAttributeAppender; import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.utility.CompoundList; +import net.bytebuddy.utility.GraalImageCode; +import net.bytebuddy.utility.RandomString; import org.mockito.codegen.InjectionBase; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock; @@ -57,7 +58,6 @@ class SubclassBytecodeGenerator implements BytecodeGenerator { private final SubclassLoader loader; private final ModuleHandler handler; private final ByteBuddy byteBuddy; - private final Random random; private final Implementation readReplace; private final ElementMatcher matcher; @@ -87,8 +87,7 @@ protected SubclassBytecodeGenerator( this.readReplace = readReplace; this.matcher = matcher; byteBuddy = new ByteBuddy().with(TypeValidation.DISABLED); - random = new Random(); - handler = ModuleHandler.make(byteBuddy, loader, random); + handler = ModuleHandler.make(byteBuddy, loader); } private static boolean needsSamePackageClassLoader(MockFeatures features) { @@ -172,7 +171,8 @@ public Class mockClass(MockFeatures features) { && features.serializableMode != SerializableMode.ACROSS_CLASSLOADERS && !isComingFromJDK(features.mockedType) && (loader.isDisrespectingOpenness() - || handler.isOpened(features.mockedType, MockAccess.class)); + || handler.isOpened(features.mockedType, MockAccess.class)) + && !GraalImageCode.getCurrent().isDefined(); String typeName; if (localMock || (loader instanceof MultipleParentClassLoader @@ -185,7 +185,13 @@ public Class mockClass(MockFeatures features) { + features.mockedType.getSimpleName(); } String name = - String.format("%s$%s$%d", typeName, "MockitoMock", Math.abs(random.nextInt())); + String.format( + "%s$%s$%s", + typeName, + "MockitoMock", + GraalImageCode.getCurrent().isDefined() + ? suffix(features) + : RandomString.make()); if (localMock) { handler.adjustModuleGraph(features.mockedType, MockAccess.class, false, true); @@ -229,7 +235,13 @@ public Class mockClass(MockFeatures features) { features.stripAnnotations ? new Annotation[0] : features.mockedType.getAnnotations()) - .implement(new ArrayList(features.interfaces)) + .implement( + GraalImageCode.getCurrent().isDefined() + && !features.interfaces.contains(Serializable.class) + ? CompoundList.of( + new ArrayList(features.interfaces), + Serializable.class) + : new ArrayList(features.interfaces)) .method(matcher) .intercept(dispatcher) .transform(withModifiers(SynchronizationState.PLAIN)) @@ -271,6 +283,20 @@ public Class mockClass(MockFeatures features) { .getLoaded(); } + private static CharSequence suffix(MockFeatures features) { + // Constructs a deterministic suffix for this mock to assure that mocks always carry the + // same name. + StringBuilder sb = new StringBuilder(); + Set names = new TreeSet<>(); + names.add(features.mockedType.getName()); + for (Class type : features.interfaces) { + names.add(type.getName()); + } + return sb.append(RandomString.hashOf(names.hashCode())) + .append(RandomString.hashOf(features.serializableMode.name().hashCode())) + .append(features.stripAnnotations ? "S" : "N"); + } + @Override public void mockClassStatic(Class type) { throw new MockitoException("The subclass byte code generator cannot create static mocks"); diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java index c70de01355..b5013891d7 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassInjectionLoader.java @@ -11,6 +11,7 @@ import net.bytebuddy.dynamic.loading.ClassInjector; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.utility.GraalImageCode; import org.mockito.codegen.InjectionBase; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.util.Platform; @@ -27,9 +28,14 @@ class SubclassInjectionLoader implements SubclassLoader { private final SubclassLoader loader; SubclassInjectionLoader() { - if (!Boolean.getBoolean("org.mockito.internal.noUnsafeInjection") + if (!Boolean.parseBoolean( + System.getProperty( + "org.mockito.internal.noUnsafeInjection", + Boolean.toString(GraalImageCode.getCurrent().isDefined()))) && ClassInjector.UsingReflection.isAvailable()) { this.loader = new WithReflection(); + } else if (GraalImageCode.getCurrent().isDefined()) { + this.loader = new WithIsolatedLoader(); } else if (ClassInjector.UsingLookup.isAvailable()) { this.loader = tryLookup(); } else { @@ -70,6 +76,20 @@ public ClassLoadingStrategy resolveStrategy( } } + private static class WithIsolatedLoader implements SubclassLoader { + + @Override + public boolean isDisrespectingOpenness() { + return false; + } + + @Override + public ClassLoadingStrategy resolveStrategy( + Class mockedType, ClassLoader classLoader, boolean localMock) { + return ClassLoadingStrategy.Default.WRAPPER; + } + } + private static class WithLookup implements SubclassLoader { private final Object lookup; diff --git a/src/main/java/org/mockito/internal/util/Platform.java b/src/main/java/org/mockito/internal/util/Platform.java index 946e4ff3be..fde59fbbe9 100644 --- a/src/main/java/org/mockito/internal/util/Platform.java +++ b/src/main/java/org/mockito/internal/util/Platform.java @@ -69,7 +69,11 @@ public static String describe() { } public static boolean isJava8BelowUpdate45() { - return isJava8BelowUpdate45(JVM_VERSION); + if (JVM_VERSION == null) { + return false; + } else { + return isJava8BelowUpdate45(JVM_VERSION); + } } static boolean isJava8BelowUpdate45(String jvmVersion) { diff --git a/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java b/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java index 2363fe6f35..1edc533b15 100644 --- a/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java +++ b/src/test/java/org/mockitointegration/NoByteCodeDependenciesTest.java @@ -15,8 +15,6 @@ public class NoByteCodeDependenciesTest { - private ClassLoader contextClassLoader; - @Test public void pure_mockito_should_not_depend_bytecode_libraries() throws Exception { @@ -35,6 +33,8 @@ public void pure_mockito_should_not_depend_bytecode_libraries() throws Exception "org.mockito.internal.creation.instance.DefaultInstantiatorProvider"); pureMockitoAPIClasses.remove( "org.mockito.internal.creation.instance.ObjenesisInstantiator"); + pureMockitoAPIClasses.remove( + "org.mockito.internal.creation.instance.UnsafeInstantiatorStrategy"); // Remove classes that trigger plugin-loading, since bytebuddy plugins are the default. pureMockitoAPIClasses.remove("org.mockito.internal.debugging.LocationImpl");