From 6f715dce6df0699b111afbde271be3b2504d9c17 Mon Sep 17 00:00:00 2001 From: Marco Belladelli Date: Mon, 7 Oct 2024 18:01:28 +0200 Subject: [PATCH] Generate Hibernate ORM InstantiationOptimizers to avoid reflection --- .../orm/deployment/HibernateOrmProcessor.java | 43 +++++++++++++++++++ ...arkusRuntimeBytecodeProviderInitiator.java | 1 + .../RuntimeBytecodeProvider.java | 23 ++++++++++ 3 files changed, 67 insertions(+) diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java index 2aa61f22adc2f..2936d33384295 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java @@ -3,10 +3,12 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; import static io.quarkus.hibernate.orm.deployment.HibernateConfigUtil.firstPresent; +import static io.quarkus.hibernate.orm.runtime.service.bytecodeprovider.QuarkusRuntimeBytecodeProviderInitiator.INSTANTIATOR_SUFFIX; import static org.hibernate.cfg.AvailableSettings.JAKARTA_SHARED_CACHE_MODE; import static org.hibernate.cfg.AvailableSettings.USE_DIRECT_REFERENCE_CACHE_ENTRIES; import static org.hibernate.cfg.AvailableSettings.USE_QUERY_CACHE; import static org.hibernate.cfg.AvailableSettings.USE_SECOND_LEVEL_CACHE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import java.io.IOException; import java.net.URL; @@ -44,6 +46,7 @@ import org.hibernate.boot.archive.scan.spi.ClassDescriptor; import org.hibernate.boot.archive.scan.spi.PackageDescriptor; import org.hibernate.boot.beanvalidation.BeanValidationIntegrator; +import org.hibernate.bytecode.spi.ReflectionOptimizer; import org.hibernate.cfg.AvailableSettings; import org.hibernate.id.SequenceMismatchStrategy; import org.hibernate.integrator.spi.Integrator; @@ -80,6 +83,7 @@ import io.quarkus.datasource.common.runtime.DatabaseKind; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -108,6 +112,10 @@ import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.IoUtil; import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import io.quarkus.hibernate.orm.PersistenceUnit; import io.quarkus.hibernate.orm.deployment.HibernateOrmConfigPersistenceUnit.IdentifierQuotingStrategy; import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; @@ -612,6 +620,41 @@ public HibernateModelClassCandidatesForFieldAccessBuildItem candidatesForFieldAc return new HibernateModelClassCandidatesForFieldAccessBuildItem(jpaModel.getManagedClassNames()); } + @BuildStep + void instantiationOptimizers(JpaModelBuildItem jpaModel, CombinedIndexBuildItem index, + BuildProducer generatedClasses, + List additionalJpaModelBuildItems, + List persistenceUnitDescriptorBuildItems) { + GeneratedClassGizmoAdaptor classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true); + // Generate InstantiationOptimizers for all managed classes to avoid reflection + Set managedClassAndPackageNames = new HashSet<>(jpaModel.getEntityClassNames()); + for (PersistenceUnitDescriptorBuildItem pud : persistenceUnitDescriptorBuildItems) { + // Note: getManagedClassNames() can also return *package* names + // See the source code of Hibernate ORM for proof: + // org.hibernate.boot.archive.scan.internal.ScanResultCollector.isListedOrDetectable + // is used for packages too, and it relies (indirectly) on getManagedClassNames(). + managedClassAndPackageNames.addAll(pud.getManagedClassNames()); + } + for (AdditionalJpaModelBuildItem additionalJpaModelBuildItem : additionalJpaModelBuildItems) { + managedClassAndPackageNames.add(additionalJpaModelBuildItem.getClassName()); + } + for (String i : managedClassAndPackageNames) { + ClassInfo classInfo = index.getIndex().getClassByName(i); + if (classInfo != null && !classInfo.isAbstract() && classInfo.hasNoArgsConstructor()) { + String className = classInfo.name() + INSTANTIATOR_SUFFIX; + try (ClassCreator classCreator = ClassCreator.builder().classOutput(classOutput) + .interfaces(ReflectionOptimizer.InstantiationOptimizer.class).className(className).build()) { + MethodCreator method = classCreator + .getMethodCreator("newInstance", Object.class) + .setModifiers(ACC_PUBLIC); + ResultHandle constructorHandle = method + .newInstance(MethodDescriptor.ofConstructor(classInfo.name().toString())); + method.returnValue(constructorHandle); + } + } + } + } + @BuildStep @Record(RUNTIME_INIT) public void build(HibernateOrmRecorder recorder, HibernateOrmConfig hibernateOrmConfig, diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java index 0b164364d7e41..bc72be9ceb2a6 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/QuarkusRuntimeBytecodeProviderInitiator.java @@ -9,6 +9,7 @@ import io.quarkus.hibernate.orm.runtime.customized.QuarkusRuntimeProxyFactoryFactory; public final class QuarkusRuntimeBytecodeProviderInitiator implements StandardServiceInitiator { + public static final String INSTANTIATOR_SUFFIX = "$QuarkusInstantiationOptimizer"; private final QuarkusRuntimeProxyFactoryFactory statefulProxyFactory; diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java index cde9b0b109318..34083834a0366 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/bytecodeprovider/RuntimeBytecodeProvider.java @@ -1,5 +1,7 @@ package io.quarkus.hibernate.orm.runtime.service.bytecodeprovider; +import static io.quarkus.hibernate.orm.runtime.service.bytecodeprovider.QuarkusRuntimeBytecodeProviderInitiator.INSTANTIATOR_SUFFIX; + import java.util.Map; import org.hibernate.bytecode.enhance.spi.EnhancementContext; @@ -35,6 +37,27 @@ public ReflectionOptimizer getReflectionOptimizer( @Override public ReflectionOptimizer getReflectionOptimizer(Class clazz, Map propertyAccessMap) { + try { + Class instantiatorClass = Class.forName(clazz.getName() + INSTANTIATOR_SUFFIX, true, + Thread.currentThread().getContextClassLoader()); + ReflectionOptimizer.InstantiationOptimizer optimizer = (ReflectionOptimizer.InstantiationOptimizer) instantiatorClass + .getDeclaredConstructor().newInstance(); + return new ReflectionOptimizer() { + @Override + public InstantiationOptimizer getInstantiationOptimizer() { + return optimizer; + } + + @Override + public AccessOptimizer getAccessOptimizer() { + return null; + } + }; + } catch (Exception e) { + // Not all mapped classes will have access-optimizers, integrators might contribute additional mappings + // to Hibernate that do not get picked up by the Jandex index at build time and won't have an + // InstantiationOptimizer to load - just ignore the exception in those cases + } return null; }