From 86bfb124264fff1c52a4600a5743f4065f08e30e Mon Sep 17 00:00:00 2001 From: George Gastaldi Date: Tue, 30 Apr 2024 19:19:03 -0300 Subject: [PATCH] Avoid classes with incomplete hierarchy in Hibernate Validator --- .../HibernateValidatorProcessor.java | 29 ++++++--- .../validator/test/ClassHierarchyTest.java | 65 +++++++++++++++++++ 2 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java index ac67b9da6484f..347bdce44bb50 100644 --- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java +++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java @@ -562,10 +562,7 @@ public void build( jaxRsMethods, methodsWithInheritedValidation))); - Set> classesToBeValidated = new HashSet<>(); - for (DotName className : classNamesToBeValidated) { - classesToBeValidated.add(recorderContext.classProxy(className.toString())); - } + Set> classesToBeValidated = toClassSet(classNamesToBeValidated); // Prevent the removal of ValueExtractor beans // and collect all classes implementing ValueExtractor (for use in HibernateValidatorRecorder) @@ -574,10 +571,7 @@ public void build( valueExtractorClassNames.add(valueExtractorType.name()); } unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(valueExtractorClassNames)); - Set> valueExtractorClassProxies = new HashSet<>(); - for (DotName className : valueExtractorClassNames) { - valueExtractorClassProxies.add(recorderContext.classProxy(className.toString())); - } + Set> valueExtractorClassProxies = toClassSet(valueExtractorClassNames); beanContainerListener .produce(new BeanContainerListenerBuildItem( @@ -590,6 +584,25 @@ public void build( hibernateValidatorBuildTimeConfig))); } + private static Set> toClassSet(Set dotNames) { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Set> classSet = new HashSet<>(dotNames.size()); + for (DotName item : dotNames) { + String className = item.toString(); + try { + if (QuarkusClassLoader.isClassPresentAtRuntime(className)) { + Class type = classLoader.loadClass(className); + // We call getCanonicalName() to make sure the class hierarchy can be loaded + LOG.debugf("Type %s successfully loaded", type.getCanonicalName()); + classSet.add(type); + } + } catch (NoClassDefFoundError | ClassNotFoundException e) { + LOG.debugf(e, "Unable to load class %s", className); + } + } + return classSet; + } + @BuildStep void indexAdditionalConstrainedClasses(List additionalConstrainedClasses, BuildProducer additionalConstrainedClassesIndex) { diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java new file mode 100644 index 0000000000000..dd0f4f912c10f --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java @@ -0,0 +1,65 @@ +package io.quarkus.hibernate.validator.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.AbstractCollection; + +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.validation.Validator; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; + +public class ClassHierarchyTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .add(new ByteArrayAsset(createClassWithIncompleteHierarchy()), "InnerClass.class") + .addClass(Dto.class)); + + private static byte[] createClassWithIncompleteHierarchy() { + // Create an inner class with an incomplete hierarchy + try (DynamicType.Unloaded superClass = new ByteBuddy() + .subclass(Object.class) + .name("SuperClass") + .make(); + DynamicType.Unloaded outerClass = new ByteBuddy() + .subclass(superClass.getTypeDescription()) + .name("OuterClass") + .make(); + DynamicType.Unloaded innerClass = new ByteBuddy() + .subclass(AbstractCollection.class) + .innerTypeOf(outerClass.getTypeDescription()) + .name("InnerClass") + .make(); + DynamicType.Loaded load = innerClass.load(Thread.currentThread().getContextClassLoader())) { + return load.getBytes(); + } + } + + @Inject + Validator validator; + + @Test + public void doNotFailWhenLoadingIncompleteClassHierarchy() { + assertThat(validator).isNotNull(); + } + + @Valid + public static class Dto { + String name; + + // XMLBean.ErrorLogger is a subclass with an incomplete hierarchy + @Valid + AbstractCollection items; + } +}