From 91429f6e2e36e8e89a270cd08fe9566516458d05 Mon Sep 17 00:00:00 2001 From: Jan Ouwens Date: Fri, 2 Dec 2022 15:50:46 +0100 Subject: [PATCH] Ensure classloader compatibility when generating dynamic subclass --- CHANGELOG.md | 1 + .../internal/reflection/Instantiator.java | 10 +++++++--- .../internal/reflection/Util.java | 18 ++++++++++++++++++ .../internal/reflection/UtilTest.java | 14 ++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a858558..288c38663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix `ClassFormatError` when attempting to create a dynamic subclass for a class that has no package. ([Issue 638](https://github.com/jqno/equalsverifier/issues/638)) +- Fix `LinkageError` when running Quarkus dev mode and testing an abstract class. ([Issue 550](https://github.com/jqno/equalsverifier/issues/550)) ## [3.12] - 2022-11-30 diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java index 674e821b5..09d88a56e 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java @@ -93,12 +93,16 @@ public static synchronized Class giveDynamicSubclass( "$" + nameSuffix; - Class existsAlready = (Class) classForName(name); + Class context = isSystemClass ? Instantiator.class : superclass; + ClassLoader classLoader = context.getClassLoader(); + + // `mvn quarkus:dev` does strange classloader stuff. We need to make sure that we + // check existence with the correct classloader. I don't know how to unit test this. + Class existsAlready = (Class) classForName(classLoader, name); if (existsAlready != null) { return existsAlready; } - Class context = isSystemClass ? Instantiator.class : superclass; ClassLoadingStrategy cs = getClassLoadingStrategy(context); DynamicType.Builder builder = new ByteBuddy() .with(TypeValidation.DISABLED) @@ -107,7 +111,7 @@ public static synchronized Class giveDynamicSubclass( builder = modify.apply(builder); - return (Class) builder.make().load(context.getClassLoader(), cs).getLoaded(); + return (Class) builder.make().load(classLoader, cs).getLoaded(); } private static String getPackageName(Class type) { diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java index 07b4b6569..5720a2b03 100644 --- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java +++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Util.java @@ -27,6 +27,24 @@ public static Class classForName(String className) { } } + /** + * Helper method to resolve a Class of a given name through a given ClassLoader. + * + * @param classLoader The class loader to resolve the class against. + * @param className The fully qualified name of the class to resolve. + * @param The type of the class to resolve. + * @return The corresponding class if it exists, null otherwise. + */ + @SuppressWarnings("unchecked") + public static Class classForName(ClassLoader classLoader, String className) { + try { + return (Class) classLoader.loadClass(className); + } catch (ClassNotFoundException | VerifyError e) { + // Catching VerifyError fixes issue #147. I don't know how to unit test it. + return null; + } + } + /** * Helper method to create an array of Classes. * diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java index 574a9027d..79f0eacfa 100644 --- a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java +++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/internal/reflection/UtilTest.java @@ -29,6 +29,20 @@ public void forNameReturnsNull_whenTypeDoesntExist() { assertNull(actual); } + @Test + public void forNameWithClassLoaderReturnsClass_whenTypeExists() { + ClassLoader cl = getClass().getClassLoader(); + Class actual = Util.classForName(cl, "java.util.GregorianCalendar"); + assertEquals(actual, GregorianCalendar.class); + } + + @Test + public void forNameWithClassLoaderReturnsNull_whenTypeDoesntExist() { + ClassLoader cl = getClass().getClassLoader(); + Class actual = Util.classForName(cl, "this.type.does.not.exist"); + assertNull(actual); + } + @Test public void classesReturnsItsArguments() { Class[] expected = new Class[] { String.class, Object.class };