diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BridgeMembersClassInfo.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BridgeMembersClassInfo.java new file mode 100644 index 000000000000..41ddcce7e227 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/BridgeMembersClassInfo.java @@ -0,0 +1,65 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import java.lang.reflect.Member; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class BridgeMembersClassInfo { + private final Class clazz; + private final List propertyNames = new ArrayList<>(); + private final List getters = new ArrayList<>(); + private final List setters = new ArrayList<>(); + + public BridgeMembersClassInfo(Class clazz) { + this.clazz = Objects.requireNonNull(clazz); + } + + public Class getClazz() { + return clazz; + } + + public Iterable gettersIterable() { + return getters; + } + + public Iterable settersIterable() { + return setters; + } + + public boolean containsGetter(Member getter) { + return getters.contains( getter ); + } + + public boolean containsSetter(Member setter) { + return setters.contains( setter ); + } + + public void addGetter(Member getter) { + getters.add( getter ); + } + + public void addSetter(Member setter) { + setters.add( setter ); + } + + public void addProperty(String propertyName) { + propertyNames.add( propertyName ); + } + + public boolean propertyNamesIsEmpty() { + return propertyNames.isEmpty(); + } + + public String encodeName() { + return NameEncodeHelper.encodeName( + propertyNames.toArray( new String[0] ), + getters.toArray( new Member[0] ), + setters.toArray( new Member[0] ) + ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java index a36d22cba8e6..b4827d3c5ca4 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ByteBuddyEnhancementContext.java @@ -31,12 +31,14 @@ class ByteBuddyEnhancementContext { private static final ElementMatcher.Junction IS_GETTER = isGetter(); private final EnhancementContext enhancementContext; + private final EnhancerImplConstants constants; private final ConcurrentHashMap> getterByTypeMap = new ConcurrentHashMap<>(); private final ConcurrentHashMap locksMap = new ConcurrentHashMap<>(); - ByteBuddyEnhancementContext(final EnhancementContext enhancementContext) { + ByteBuddyEnhancementContext(final EnhancementContext enhancementContext, EnhancerImplConstants enhancerConstants) { this.enhancementContext = Objects.requireNonNull( enhancementContext ); + this.constants = enhancerConstants; } public boolean isEntityClass(TypeDescription classDescriptor) { @@ -122,7 +124,8 @@ else if ( isMappedSuperclassClass( managedCtClass ) ) { final EnhancerImpl.AnnotatedFieldDescription[] enhancedFields = PersistentAttributeTransformer.collectPersistentFields( managedCtClass, this, - typePool + typePool, + constants ) .getEnhancedFields(); for ( EnhancerImpl.AnnotatedFieldDescription enhancedField : enhancedFields ) { diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 133b5b624aea..14255d008a20 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -34,8 +34,6 @@ import org.hibernate.AssertionFailure; import org.hibernate.Version; import org.hibernate.bytecode.enhance.VersionMismatchException; -import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; -import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.EnhancementException; import org.hibernate.bytecode.enhance.spi.EnhancementInfo; @@ -43,17 +41,10 @@ import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.hibernate.bytecode.enhance.spi.UnloadedField; import org.hibernate.bytecode.enhance.spi.UnsupportedEnhancementStrategy; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.engine.spi.CompositeOwner; -import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; import org.hibernate.engine.spi.Managed; -import org.hibernate.engine.spi.ManagedComposite; -import org.hibernate.engine.spi.ManagedEntity; -import org.hibernate.engine.spi.ManagedMappedSuperclass; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; -import org.hibernate.engine.spi.SelfDirtinessTracker; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -69,7 +60,6 @@ import java.util.Set; import java.util.function.Supplier; -import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; import static net.bytebuddy.matcher.ElementMatchers.isGetter; import static net.bytebuddy.matcher.ElementMatchers.isSetter; import static net.bytebuddy.matcher.ElementMatchers.isStatic; @@ -85,7 +75,7 @@ public class EnhancerImpl implements Enhancer { private final ByteBuddyState byteBuddyState; private final EnhancerClassLocator typePool; private final EnhancerImplConstants constants; - private final List infoAnnotationList; + private final AnnotationList.ForLoadedAnnotations infoAnnotationList; /** * Constructs the Enhancer, using the given context. @@ -106,12 +96,12 @@ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddy * @param classLocator */ public EnhancerImpl(final EnhancementContext enhancementContext, final ByteBuddyState byteBuddyState, final EnhancerClassLocator classLocator) { - this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext ); + this.enhancementContext = new ByteBuddyEnhancementContext( enhancementContext, byteBuddyState.getEnhancerConstants() ); this.byteBuddyState = Objects.requireNonNull( byteBuddyState ); this.typePool = Objects.requireNonNull( classLocator ); this.constants = byteBuddyState.getEnhancerConstants(); - this.infoAnnotationList = List.of( createInfoAnnotation( enhancementContext ) ); + this.infoAnnotationList = new AnnotationList.ForLoadedAnnotations( List.of( createInfoAnnotation( enhancementContext ) ) ); } @@ -135,7 +125,7 @@ public byte[] enhance(String className, byte[] originalBytes) throws Enhancement final TypeDescription typeDescription = typePool.describe( safeClassName ).resolve(); return byteBuddyState.rewrite( typePool, safeClassName, byteBuddy -> doEnhance( - () -> byteBuddy.ignore( isDefaultFinalizer() ) + () -> byteBuddy.ignore( constants.defaultFinalizer() ) .redefine( typeDescription, typePool.asClassFileLocator() ) .annotateType( infoAnnotationList ), typeDescription @@ -206,8 +196,9 @@ private DynamicType.Builder doEnhance(Supplier> builde log.tracef( "Enhancing [%s] as Entity", managedCtClass.getName() ); DynamicType.Builder builder = builderSupplier.get(); - builder = builder.implement( ManagedEntity.class ) - .defineMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME, constants.TypeObject, constants.methodModifierPUBLIC ) + builder = builder + .implement( constants.INTERFACES_for_ManagedEntity ) + .defineMethod( EnhancerConstants.ENTITY_INSTANCE_GETTER_NAME, constants.TypeObject, constants.modifierPUBLIC ) .intercept( FixedValue.self() ); builder = addFieldWithGetterAndSetter( @@ -261,44 +252,44 @@ private DynamicType.Builder doEnhance(Supplier> builde List collectionFields = collectCollectionFields( managedCtClass ); if ( collectionFields.isEmpty() ) { - builder = builder.implement( SelfDirtinessTracker.class ) - .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, constants.fieldModifierPRIVATE_TRANSIENT ) + builder = builder.implement( constants.INTERFACES_for_SelfDirtinessTracker ) + .defineField( EnhancerConstants.TRACKER_FIELD_NAME, constants.DirtyTrackerTypeDescription, constants.modifierPRIVATE_TRANSIENT ) .annotateField( constants.TRANSIENT_ANNOTATION ) - .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( String.class ) + .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( constants.TypeString ) .intercept( constants.implementationTrackChange ) - .defineMethod( EnhancerConstants.TRACKER_GET_NAME, constants.Type_Array_String, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_GET_NAME, constants.Type_Array_String, constants.modifierPUBLIC ) .intercept( constants.implementationGetDirtyAttributesWithoutCollections ) - .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, constants.TypeBooleanPrimitive, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, constants.TypeBooleanPrimitive, constants.modifierPUBLIC ) .intercept( constants.implementationAreFieldsDirtyWithoutCollections ) - .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, constants.TypeVoid, constants.modifierPUBLIC ) .intercept( constants.implementationClearDirtyAttributesWithoutCollections ) - .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( constants.TypeBooleanPrimitive ) + .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( constants.TypeBooleanPrimitive ) .intercept( constants.implementationSuspendDirtyTracking ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, constants.TypeCollectionTracker, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, constants.TypeCollectionTracker, constants.modifierPUBLIC ) .intercept( constants.implementationGetCollectionTrackerWithoutCollections ); } else { //TODO es.enableInterfaceExtendedSelfDirtinessTracker ? Careful with consequences.. - builder = builder.implement( ExtendedSelfDirtinessTracker.class ) - .defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, constants.fieldModifierPRIVATE_TRANSIENT ) + builder = builder.implement( constants.INTERFACES_for_ExtendedSelfDirtinessTracker ) + .defineField( EnhancerConstants.TRACKER_FIELD_NAME, constants.DirtyTrackerTypeDescription, constants.modifierPRIVATE_TRANSIENT ) .annotateField( constants.TRANSIENT_ANNOTATION ) - .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, constants.TypeCollectionTracker, constants.fieldModifierPRIVATE_TRANSIENT ) + .defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, constants.TypeCollectionTracker, constants.modifierPRIVATE_TRANSIENT ) .annotateField( constants.TRANSIENT_ANNOTATION ) - .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( String.class ) + .defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( constants.TypeString ) .intercept( constants.implementationTrackChange ) - .defineMethod( EnhancerConstants.TRACKER_GET_NAME, constants.Type_Array_String, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_GET_NAME, constants.Type_Array_String, constants.modifierPUBLIC ) .intercept( constants.implementationGetDirtyAttributes ) - .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, constants.TypeBooleanPrimitive, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, constants.TypeBooleanPrimitive, constants.modifierPUBLIC ) .intercept( constants.implementationAreFieldsDirty ) - .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, constants.TypeVoid, constants.modifierPUBLIC ) .intercept( constants.implementationClearDirtyAttributes ) - .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( constants.TypeBooleanPrimitive ) + .defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( constants.TypeBooleanPrimitive ) .intercept( constants.implementationSuspendDirtyTracking ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, constants.TypeCollectionTracker, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, constants.TypeCollectionTracker, constants.modifierPUBLIC ) .intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) ); Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE; @@ -360,17 +351,17 @@ private DynamicType.Builder doEnhance(Supplier> builde clearDirtyNames = constants.adviceInitializeLazyAttributeLoadingInterceptor.wrap( clearDirtyNames ); } - builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, constants.TypeBooleanPrimitive, constants.methodModifierPUBLIC ) + builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, constants.TypeBooleanPrimitive, constants.modifierPUBLIC ) .intercept( isDirty ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( DirtyTracker.class ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( constants.DirtyTrackerTypeDescription ) .intercept( getDirtyNames ) - .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, constants.TypeVoid, constants.modifierPUBLIC ) .intercept( Advice.withCustomMapping() .to( CodeTemplates.ClearDirtyCollectionNames.class, constants.adviceLocator ) .wrap( StubMethod.INSTANCE ) ) - .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( LazyAttributeLoadingInterceptor.class ) + .defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( constants.TypeLazyAttributeLoadingInterceptor ) .intercept( clearDirtyNames ); } } @@ -386,30 +377,30 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) { log.tracef( "Enhancing [%s] as Composite", managedCtClass.getName() ); DynamicType.Builder builder = builderSupplier.get(); - builder = builder.implement( ManagedComposite.class ); + builder = builder.implement( constants.INTERFACES_for_ManagedComposite ); builder = addInterceptorHandling( builder, managedCtClass ); if ( enhancementContext.doDirtyCheckingInline() ) { - builder = builder.implement( CompositeTracker.class ) + builder = builder.implement( constants.INTERFACES_for_CompositeTracker ) .defineField( EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, - CompositeOwnerTracker.class, - constants.fieldModifierPRIVATE_TRANSIENT + constants.TypeCompositeOwnerTracker, + constants.modifierPRIVATE_TRANSIENT ) .annotateField( constants.TRANSIENT_ANNOTATION ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, constants.TypeVoid, - constants.methodModifierPUBLIC + constants.modifierPUBLIC ) .withParameters( String.class, CompositeOwner.class ) .intercept( constants.implementationSetOwner ) .defineMethod( EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER, constants.TypeVoid, - constants.methodModifierPUBLIC + constants.modifierPUBLIC ) - .withParameters( String.class ) + .withParameter( constants.TypeString ) .intercept( constants.implementationClearOwner ); } @@ -424,7 +415,7 @@ else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) { log.tracef( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() ); DynamicType.Builder builder = builderSupplier.get(); - builder = builder.implement( ManagedMappedSuperclass.class ); + builder = builder.implement( constants.INTERFACES_for_ManagedMappedSuperclass ); return createTransformer( managedCtClass ).applyTo( builder ); } else if ( enhancementContext.doExtendedEnhancement() ) { @@ -659,7 +650,7 @@ private static boolean checkUnsupportedAttributeNaming(TypeDescription managedCt } private PersistentAttributeTransformer createTransformer(TypeDescription typeDescription) { - return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool ); + return PersistentAttributeTransformer.collectPersistentFields( typeDescription, enhancementContext, typePool, constants ); } // See HHH-10977 HHH-11284 HHH-11404 --- check for declaration of Managed interface on the class, not inherited @@ -677,7 +668,7 @@ private DynamicType.Builder addInterceptorHandling(DynamicType.Builder bui if ( enhancementContext.hasLazyLoadableAttributes( managedCtClass ) ) { log.tracef( "Weaving in PersistentAttributeInterceptable implementation on [%s]", managedCtClass.getName() ); - builder = builder.implement( PersistentAttributeInterceptable.class ); + builder = builder.implement( constants.INTERFACES_for_PersistentAttributeInterceptable ); builder = addFieldWithGetterAndSetter( builder, @@ -698,12 +689,12 @@ private DynamicType.Builder addFieldWithGetterAndSetter( String getterName, String setterName) { return builder - .defineField( fieldName, type, constants.fieldModifierPRIVATE_TRANSIENT ) + .defineField( fieldName, type, constants.modifierPRIVATE_TRANSIENT ) .annotateField( constants.TRANSIENT_ANNOTATION ) - .defineMethod( getterName, type, constants.methodModifierPUBLIC ) + .defineMethod( getterName, type, constants.modifierPUBLIC ) .intercept( FieldAccessor.ofField( fieldName ) ) - .defineMethod( setterName, constants.TypeVoid, constants.methodModifierPUBLIC ) - .withParameters( type ) + .defineMethod( setterName, constants.TypeVoid, constants.modifierPUBLIC ) + .withParameter( type ) .intercept( FieldAccessor.ofField( fieldName ) ); } @@ -714,7 +705,7 @@ private DynamicType.Builder addSetPersistenceInfoMethod( TypeDefinition intType) { return builder // returns previous entity entry - .defineMethod( EnhancerConstants.PERSISTENCE_INFO_SETTER_NAME, entityEntryType, constants.methodModifierPUBLIC ) + .defineMethod( EnhancerConstants.PERSISTENCE_INFO_SETTER_NAME, entityEntryType, constants.modifierPUBLIC ) // previous, next, instance-id .withParameters( entityEntryType, managedEntityType, managedEntityType, intType ) .intercept( constants.implementationSetPersistenceInfo ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java index c3fd43b6e0c3..7151dc438957 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImplConstants.java @@ -6,7 +6,9 @@ import jakarta.persistence.Transient; import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.NamedElement; import net.bytebuddy.description.annotation.AnnotationDescription; +import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.FieldPersistence; import net.bytebuddy.description.modifier.ModifierContributor; import net.bytebuddy.description.modifier.Visibility; @@ -16,13 +18,33 @@ import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.StubMethod; +import java.io.Serializable; import java.util.Collection; import java.util.List; +import net.bytebuddy.jar.asm.Type; +import net.bytebuddy.matcher.ElementMatcher; +import net.bytebuddy.matcher.ElementMatchers; +import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker; +import org.hibernate.bytecode.enhance.internal.tracker.DirtyTracker; import org.hibernate.bytecode.enhance.spi.CollectionTracker; +import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; +import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; +import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; +import org.hibernate.bytecode.spi.ReflectionOptimizer; +import org.hibernate.engine.spi.CompositeOwner; +import org.hibernate.engine.spi.CompositeTracker; import org.hibernate.engine.spi.EntityEntry; +import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker; +import org.hibernate.engine.spi.ManagedComposite; import org.hibernate.engine.spi.ManagedEntity; +import org.hibernate.engine.spi.ManagedMappedSuperclass; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; import org.hibernate.engine.spi.PersistentAttributeInterceptor; +import org.hibernate.engine.spi.SelfDirtinessTracker; +import org.hibernate.proxy.ProxyConfiguration; + +import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer; /** * Extracts constants used by EnhancerImpl. @@ -51,8 +73,9 @@ public final class EnhancerImplConstants { final Implementation implementationSetPersistenceInfo; //Frequently used Modifiers: - final int methodModifierPUBLIC = ModifierContributor.Resolver.of( List.of( Visibility.PUBLIC ) ).resolve(); - final int fieldModifierPRIVATE_TRANSIENT = ModifierContributor.Resolver.of( List.of( FieldPersistence.TRANSIENT, Visibility.PRIVATE ) ).resolve(); + final int modifierPUBLIC = ModifierContributor.Resolver.of( List.of( Visibility.PUBLIC ) ).resolve(); + public final int modifierPRIVATE = ModifierContributor.Resolver.of( List.of( Visibility.PRIVATE ) ).resolve(); + final int modifierPRIVATE_TRANSIENT = ModifierContributor.Resolver.of( List.of( FieldPersistence.TRANSIENT, Visibility.PRIVATE ) ).resolve(); //Frequently used annotations, declared as collections as otherwise they get wrapped into them over and over again: final Collection TRANSIENT_ANNOTATION = List.of( @@ -62,12 +85,94 @@ public final class EnhancerImplConstants { final TypeDefinition TypeVoid = TypeDescription.ForLoadedType.of( void.class ); final TypeDefinition TypeBooleanPrimitive = TypeDescription.ForLoadedType.of( boolean.class ); final TypeDefinition TypeIntegerPrimitive = TypeDescription.ForLoadedType.of( int.class ); + public final TypeDefinition TypeProxyConfiguration_Interceptor = TypeDescription.ForLoadedType.of( ProxyConfiguration.Interceptor.class ); final TypeDefinition TypeManagedEntity = TypeDescription.ForLoadedType.of( ManagedEntity.class ); final TypeDefinition TypeEntityEntry = TypeDescription.ForLoadedType.of( EntityEntry.class ); final TypeDefinition TypePersistentAttributeInterceptor = TypeDescription.ForLoadedType.of( PersistentAttributeInterceptor.class ); - final TypeDefinition TypeObject = TypeDescription.ForLoadedType.of( Object.class ); + public final TypeDefinition TypeObject = TypeDescription.ForLoadedType.of( Object.class ); + final TypeDefinition TypeString = TypeDescription.ForLoadedType.of( String.class ); final TypeDefinition Type_Array_String = TypeDescription.ForLoadedType.of( String[].class ); final TypeDefinition TypeCollectionTracker = TypeDescription.ForLoadedType.of( CollectionTracker.class ); + final TypeDefinition Type_Array_Object = TypeDescription.ForLoadedType.of( Object[].class ); + final TypeDefinition TypeLazyAttributeLoadingInterceptor = TypeDescription.ForLoadedType.of( + LazyAttributeLoadingInterceptor.class ); + final TypeDefinition TypeCompositeOwnerTracker = TypeDescription.ForLoadedType.of( CompositeOwnerTracker.class ); + public final TypeDefinition TypeInstantiationOptimizer = TypeDescription.ForLoadedType.of( + ReflectionOptimizer.InstantiationOptimizer.class ); + + //Careful with the following types being used: the ByteBuddy receiver often supports overloading, and using + //one of the other specific types in the declaration might lead to unexpectedly invoking a different method. + final List INTERFACES_for_PersistentAttributeInterceptor = List.of( TypePersistentAttributeInterceptor ); + public final Collection INTERFACES_for_AccessOptimizer = List.of( + TypeDescription.ForLoadedType.of( ReflectionOptimizer.AccessOptimizer.class ) + ); + public final Collection INTERFACES_for_SelfDirtinessTracker = List.of( + TypeDescription.ForLoadedType.of( SelfDirtinessTracker.class ) + ); + public final Collection INTERFACES_for_ExtendedSelfDirtinessTracker = List.of( + TypeDescription.ForLoadedType.of( ExtendedSelfDirtinessTracker.class ) + ); + public final Collection INTERFACES_for_ManagedComposite = List.of( + TypeDescription.ForLoadedType.of( ManagedComposite.class ) + ); + public final Collection INTERFACES_for_ManagedMappedSuperclass = List.of( + TypeDescription.ForLoadedType.of( ManagedMappedSuperclass.class ) + ); + public final Collection INTERFACES_for_CompositeOwner = List.of( + TypeDescription.ForLoadedType.of( CompositeOwner.class ) + ); + public final Collection INTERFACES_for_CompositeTracker = List.of( + TypeDescription.ForLoadedType.of( CompositeTracker.class ) + ); + public final Collection INTERFACES_for_PersistentAttributeInterceptable = List.of( + TypeDescription.ForLoadedType.of( PersistentAttributeInterceptable.class ) + ); + public final Collection INTERFACES_for_ProxyConfiguration = List.of( + TypeDescription.ForLoadedType.of( ProxyConfiguration.class ) + ); + public final Collection INTERFACES_for_ManagedEntity = List.of( TypeManagedEntity ); + + //Frequently used ElementMatchers: + final ElementMatcher.Junction DEFAULT_FINALIZER = isDefaultFinalizer(); + public final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( "newInstance" ); + public final ElementMatcher.Junction getPropertyValuesMethodName = ElementMatchers.named( "getPropertyValues" ); + public final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( "setPropertyValues" ); + public final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( "getPropertyNames" ); + + //Frequently used types for field definitions: + final TypeDescription.Generic DirtyTrackerTypeDescription = TypeDefinition.Sort.describe( DirtyTracker.class ); + + //Internal names: + final String internalName_CompositeTracker = Type.getInternalName( CompositeTracker.class ); + final String internalName_LazyPropertyInitializer = Type.getInternalName( LazyPropertyInitializer.class ); + final String internalName_Object = Type.getInternalName( Object.class ); + final String internalName_String = Type.getInternalName( String.class ); + final String internalName_BytecodeLazyAttributeInterceptor = Type.getInternalName( BytecodeLazyAttributeInterceptor.class ); + final String internalName_LazyAttributeLoadingInterceptor = Type.getInternalName( + LazyAttributeLoadingInterceptor.class ); + final String internalName_PersistentAttributeInterceptor = Type.getInternalName( + PersistentAttributeInterceptor.class ); + + //Method Descriptors: + final String methodDescriptor_SetOwner = Type.getMethodDescriptor( + Type.getType( void.class ), + Type.getType( String.class ), + Type.getType( CompositeOwner.class ) + ); + final String methodDescriptor_getInterceptor = Type.getMethodDescriptor( + Type.getType( PersistentAttributeInterceptor.class ) + ); + final String methodDescriptor_attributeInitialized = Type.getMethodDescriptor( + Type.getType( void.class ), + Type.getType( String.class ) + ); + final String methodDescriptor_isAttributeLoaded = Type.getMethodDescriptor( + Type.getType( boolean.class ), + Type.getType( String.class ) + ); + + //Others : + final String Serializable_TYPE_DESCRIPTOR = Type.getDescriptor( Serializable.class ); public EnhancerImplConstants() { this.adviceLocator = ClassFileLocator.ForClassLoader.of( CodeTemplates.class.getClassLoader() ); @@ -110,4 +215,8 @@ public EnhancerImplConstants() { .wrap( StubMethod.INSTANCE ); } + public ElementMatcher defaultFinalizer() { + return DEFAULT_FINALIZER; + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java index fbc7c6926c22..c35273d50e08 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java @@ -8,7 +8,6 @@ import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.field.FieldDescription; @@ -29,18 +28,21 @@ abstract class FieldReaderAppender implements ByteCodeAppender { protected final FieldDescription.InDefinedShape persistentFieldAsDefined; - private FieldReaderAppender(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { + protected final EnhancerImplConstants constants; + + private FieldReaderAppender(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField, EnhancerImplConstants constants) { this.managedCtClass = managedCtClass; this.persistentField = persistentField; this.persistentFieldAsDefined = persistentField.asDefined(); + this.constants = constants; } - static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField, EnhancerImplConstants constants) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { - return new MethodDispatching( managedCtClass, persistentField ); + return new MethodDispatching( managedCtClass, persistentField, constants ); } else { - return new FieldWriting( managedCtClass, persistentField ); + return new FieldWriting( managedCtClass, persistentField, constants ); } } @@ -63,7 +65,7 @@ public Size apply( Opcodes.INVOKEVIRTUAL, managedCtClass.getInternalName(), EnhancerConstants.INTERCEPTOR_GETTER_NAME, - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), + constants.methodDescriptor_getInterceptor, false ); Label skip = new Label(); @@ -76,7 +78,7 @@ public Size apply( Opcodes.INVOKEVIRTUAL, managedCtClass.getInternalName(), EnhancerConstants.INTERCEPTOR_GETTER_NAME, - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), + constants.methodDescriptor_getInterceptor, false ); // .readXXX( self, fieldName, field ); @@ -87,7 +89,7 @@ public Size apply( fieldRead( methodVisitor ); methodVisitor.visitMethodInsn( Opcodes.INVOKEINTERFACE, - Type.getInternalName( PersistentAttributeInterceptor.class ), + constants.internalName_PersistentAttributeInterceptor, "read" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ), Type.getMethodDescriptor( Type.getType( dispatcherType.getDescriptor() ), @@ -127,8 +129,8 @@ public Size apply( private static class FieldWriting extends FieldReaderAppender { - private FieldWriting(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { - super( managedCtClass, persistentField ); + private FieldWriting(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField, EnhancerImplConstants constants) { + super( managedCtClass, persistentField, constants ); } @Override @@ -154,8 +156,8 @@ protected void fieldWrite(MethodVisitor methodVisitor) { private static class MethodDispatching extends FieldReaderAppender { - private MethodDispatching(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { - super( managedCtClass, persistentField ); + private MethodDispatching(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField, EnhancerImplConstants constants) { + super( managedCtClass, persistentField, constants ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java index ec955c7519b0..aeadb9da147b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldWriterAppender.java @@ -6,7 +6,6 @@ import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.field.FieldDescription; @@ -25,17 +24,20 @@ abstract class FieldWriterAppender implements ByteCodeAppender { protected final FieldDescription.InDefinedShape persistentFieldAsDefined; - private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined) { + protected final EnhancerImplConstants constants; + + private FieldWriterAppender(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined, EnhancerImplConstants constants) { this.managedCtClass = managedCtClass; this.persistentFieldAsDefined = persistentFieldAsDefined; + this.constants = constants; } - static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField) { + static ByteCodeAppender of(TypeDescription managedCtClass, AnnotatedFieldDescription persistentField, EnhancerImplConstants constants) { if ( !persistentField.isVisibleTo( managedCtClass ) ) { - return new MethodDispatching( managedCtClass, persistentField.asDefined() ); + return new MethodDispatching( managedCtClass, persistentField.asDefined(), constants ); } else { - return new FieldWriting( managedCtClass, persistentField.asDefined() ); + return new FieldWriting( managedCtClass, persistentField.asDefined(), constants ); } } @@ -53,7 +55,7 @@ public Size apply( Opcodes.INVOKEVIRTUAL, managedCtClass.getInternalName(), EnhancerConstants.INTERCEPTOR_GETTER_NAME, - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), + constants.methodDescriptor_getInterceptor, false ); Label noInterceptor = new Label(); @@ -66,7 +68,7 @@ public Size apply( Opcodes.INVOKEVIRTUAL, managedCtClass.getInternalName(), EnhancerConstants.INTERCEPTOR_GETTER_NAME, - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), + constants.methodDescriptor_getInterceptor, false ); // .writeXXX( self, fieldName, field, arg1 ); @@ -77,7 +79,7 @@ public Size apply( methodVisitor.visitVarInsn( Type.getType( dispatcherType.getDescriptor() ).getOpcode( Opcodes.ILOAD ), 1 ); methodVisitor.visitMethodInsn( Opcodes.INVOKEINTERFACE, - Type.getInternalName( PersistentAttributeInterceptor.class ), + constants.internalName_PersistentAttributeInterceptor, "write" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ), Type.getMethodDescriptor( Type.getType( dispatcherType.getDescriptor() ), @@ -119,8 +121,8 @@ public Size apply( private static class FieldWriting extends FieldWriterAppender { - private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined) { - super( managedCtClass, persistentFieldAsDefined ); + private FieldWriting(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined, EnhancerImplConstants constants) { + super( managedCtClass, persistentFieldAsDefined, constants ); } @Override @@ -146,8 +148,8 @@ protected void fieldWrite(MethodVisitor methodVisitor) { private static class MethodDispatching extends FieldWriterAppender { - private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined) { - super( managedCtClass, persistentFieldAsDefined ); + private MethodDispatching(TypeDescription managedCtClass, FieldDescription.InDefinedShape persistentFieldAsDefined, EnhancerImplConstants constants) { + super( managedCtClass, persistentFieldAsDefined, constants ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ForeignPackageMember.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ForeignPackageMember.java new file mode 100644 index 000000000000..50bf4ecc55e5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/ForeignPackageMember.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import java.lang.reflect.Member; + +public class ForeignPackageMember implements Member { + + private final Class foreignPackageAccessor; + private final Member member; + + public ForeignPackageMember(Class foreignPackageAccessor, Member member) { + this.foreignPackageAccessor = foreignPackageAccessor; + this.member = member; + } + + public Class getForeignPackageAccessor() { + return foreignPackageAccessor; + } + + public Member getMember() { + return member; + } + + @Override + public Class getDeclaringClass() { + return member.getDeclaringClass(); + } + + @Override + public String getName() { + return member.getName(); + } + + @Override + public int getModifiers() { + return member.getModifiers(); + } + + @Override + public boolean isSynthetic() { + return member.isSynthetic(); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetFieldOnArgument.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetFieldOnArgument.java new file mode 100644 index 000000000000..5aa5f5810fc1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetFieldOnArgument.java @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +public class GetFieldOnArgument implements ByteCodeAppender { + + private final Member getterMember; + + public GetFieldOnArgument(Member getterMember) { + this.getterMember = getterMember; + } + + @Override + public Size apply( + MethodVisitor methodVisitor, + Implementation.Context implementationContext, + MethodDescription instrumentedMethod) { + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + final Class type; + if ( getterMember instanceof Method getter ) { + type = getter.getReturnType(); + methodVisitor.visitMethodInsn( + getter.getDeclaringClass().isInterface() ? + Opcodes.INVOKEINTERFACE : + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( getter.getDeclaringClass() ), + getter.getName(), + Type.getMethodDescriptor( getter ), + getter.getDeclaringClass().isInterface() + ); + } + else { + final Field getter = (Field) getterMember; + type = getter.getType(); + methodVisitor.visitFieldInsn( + Opcodes.GETFIELD, + Type.getInternalName( getter.getDeclaringClass() ), + getter.getName(), + Type.getDescriptor( type ) + ); + } + methodVisitor.visitInsn( getReturnOpCode( type ) ); + return new Size( 2, instrumentedMethod.getStackSize() ); + } + + private int getReturnOpCode(Class type) { + if ( type.isPrimitive() ) { + switch ( type.getTypeName() ) { + case "long": + return Opcodes.LRETURN; + case "float": + return Opcodes.FRETURN; + case "double": + return Opcodes.DRETURN; + } + return Opcodes.IRETURN; + } + return Opcodes.ARETURN; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetPropertyNames.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetPropertyNames.java new file mode 100644 index 000000000000..7eb01745b594 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetPropertyNames.java @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; + +public class GetPropertyNames implements ByteCodeAppender { + + private final String[] propertyNames; + private final EnhancerImplConstants constants; + + public GetPropertyNames(String[] propertyNames, EnhancerImplConstants constants) { + this.propertyNames = propertyNames; + this.constants = constants; + } + + @Override + public Size apply( + MethodVisitor methodVisitor, + Implementation.Context implementationContext, + MethodDescription instrumentedMethod) { + methodVisitor.visitLdcInsn( propertyNames.length ); + methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, constants.internalName_String ); + for ( int i = 0; i < propertyNames.length; i++ ) { + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitLdcInsn( i ); + methodVisitor.visitLdcInsn( propertyNames[i] ); + methodVisitor.visitInsn( Opcodes.AASTORE ); + } + methodVisitor.visitInsn( Opcodes.ARETURN ); + return new Size( 4, instrumentedMethod.getStackSize() + 1 ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetPropertyValues.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetPropertyValues.java new file mode 100644 index 000000000000..403b42e168cf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/GetPropertyValues.java @@ -0,0 +1,258 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate; +import net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; +import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Helper class to generate bytecode for getting property values, used by BytecodeProviderImpl. + * Moved into package org.hibernate.bytecode.enhance.internal.bytebuddy to benefit from the constants. + */ +public class GetPropertyValues implements ByteCodeAppender { + + private final String[] propertyNames; + private final Member[] getters; + private final boolean persistentAttributeInterceptable; + private final String internalClazzName; + private final EnhancerImplConstants constants; + + + public GetPropertyValues(Class clazz, String[] propertyNames, Member[] getters, EnhancerImplConstants constants) { + this.propertyNames = propertyNames; + this.getters = getters; + this.persistentAttributeInterceptable = PersistentAttributeInterceptable.class.isAssignableFrom( clazz ); + this.internalClazzName = Type.getInternalName( clazz ); + this.constants = constants; + } + + @Override + public Size apply( + MethodVisitor methodVisitor, + Implementation.Context implementationContext, + MethodDescription instrumentedMethod) { + if ( persistentAttributeInterceptable ) { + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, internalClazzName ); + + // Extract the interceptor + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + internalClazzName, + "$$_hibernate_getInterceptor", + constants.methodDescriptor_getInterceptor, + false + ); + // Duplicate the interceptor on the stack and check if it implements LazyAttributeLoadingInterceptor + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitTypeInsn( + Opcodes.INSTANCEOF, + constants.internalName_LazyAttributeLoadingInterceptor + ); + + // Jump to the false label if the instanceof check fails + final Label instanceofFalseLabel = new Label(); + methodVisitor.visitJumpInsn( Opcodes.IFEQ, instanceofFalseLabel ); + + // Cast to the subtype, so we can mark the property as initialized + methodVisitor.visitTypeInsn( + Opcodes.CHECKCAST, + constants.internalName_LazyAttributeLoadingInterceptor + ); + // Store the LazyAttributeLoadingInterceptor at index 2 + methodVisitor.visitVarInsn( Opcodes.ASTORE, 2 ); + + // Skip the cleanup + final Label instanceofEndLabel = new Label(); + methodVisitor.visitJumpInsn( Opcodes.GOTO, instanceofEndLabel ); + + // Here is the cleanup section for the false branch + methodVisitor.visitLabel( instanceofFalseLabel ); + // We still have the duplicated interceptor on the stack + implementationContext.getFrameGeneration().full( + methodVisitor, + constants.INTERFACES_for_PersistentAttributeInterceptor, + Arrays.asList( + implementationContext.getInstrumentedType(), + constants.TypeObject + ) + ); + // Pop that duplicated interceptor from the stack + methodVisitor.visitInsn( Opcodes.POP ); + methodVisitor.visitInsn( Opcodes.ACONST_NULL ); + methodVisitor.visitVarInsn( Opcodes.ASTORE, 2 ); + + methodVisitor.visitLabel( instanceofEndLabel ); + implementationContext.getFrameGeneration().full( + methodVisitor, + Collections.emptyList(), + Arrays.asList( + implementationContext.getInstrumentedType(), + constants.TypeObject, + constants.TypeLazyAttributeLoadingInterceptor + ) + ); + } + methodVisitor.visitLdcInsn( getters.length ); + methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, constants.internalName_Object ); + for ( int index = 0; index < getters.length; index++ ) { + final Member getterMember = getters[index]; + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitLdcInsn( index ); + + final Label arrayStoreLabel = new Label(); + if ( getterMember == BytecodeProviderImpl.EMBEDDED_MEMBER ) { + // The embedded property access returns the owner + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + } + else { + if ( persistentAttributeInterceptable ) { + final Label extractValueLabel = new Label(); + + // Load the LazyAttributeLoadingInterceptor + methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); + // If that is null, then assume attributes are loaded and jump to extraction + methodVisitor.visitJumpInsn( Opcodes.IFNULL, extractValueLabel ); + // Load the LazyAttributeLoadingInterceptor + methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); + // Load the current property name + methodVisitor.visitLdcInsn( propertyNames[index] ); + // Invoke isAttributeLoaded on the interceptor + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + constants.internalName_LazyAttributeLoadingInterceptor, + "isAttributeLoaded", + constants.methodDescriptor_isAttributeLoaded, + false + ); + // If the attribute is loaded, jump to extraction + methodVisitor.visitJumpInsn( Opcodes.IFNE, extractValueLabel ); + + // Push LazyPropertyInitializer.UNFETCHED_PROPERTY on the stack + methodVisitor.visitFieldInsn( + Opcodes.GETSTATIC, + constants.internalName_LazyPropertyInitializer, + "UNFETCHED_PROPERTY", + constants.Serializable_TYPE_DESCRIPTOR + ); + // Jump to the label where we handle storing the unfetched property + methodVisitor.visitJumpInsn( Opcodes.GOTO, arrayStoreLabel ); + + // This is the end of the lazy check i.e. the start of extraction + methodVisitor.visitLabel( extractValueLabel ); + implementationContext.getFrameGeneration().full( + methodVisitor, + Arrays.asList( + constants.Type_Array_Object, + constants.Type_Array_Object, + constants.TypeIntegerPrimitive + ), + Arrays.asList( + implementationContext.getInstrumentedType(), + constants.TypeObject, + constants.TypeLazyAttributeLoadingInterceptor + ) + ); + } + + // Load the entity to extract the property + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, internalClazzName ); + + final Class type; + if ( getterMember instanceof Method getter ) { + type = getter.getReturnType(); + methodVisitor.visitMethodInsn( + getter.getDeclaringClass().isInterface() ? + Opcodes.INVOKEINTERFACE : + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( getter.getDeclaringClass() ), + getter.getName(), + Type.getMethodDescriptor( getter ), + getter.getDeclaringClass().isInterface() + ); + } + else if ( getterMember instanceof Field getter ) { + type = getter.getType(); + methodVisitor.visitFieldInsn( + Opcodes.GETFIELD, + Type.getInternalName( getter.getDeclaringClass() ), + getter.getName(), + Type.getDescriptor( type ) + ); + } + else { + assert getterMember instanceof ForeignPackageMember; + final ForeignPackageMember foreignPackageMember = (ForeignPackageMember) getterMember; + final Member underlyingMember = foreignPackageMember.getMember(); + if ( underlyingMember instanceof Method getter ) { + type = getter.getReturnType(); + } + else { + final Field getter = (Field) underlyingMember; + type = getter.getType(); + } + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName( foreignPackageMember.getForeignPackageAccessor() ), + "get_" + getterMember.getName(), + Type.getMethodDescriptor( + Type.getType( type ), + Type.getType( underlyingMember.getDeclaringClass() ) + ), + false + ); + } + if ( type.isPrimitive() ) { + PrimitiveBoxingDelegate.forPrimitive( new TypeDescription.ForLoadedType( type ) ) + .assignBoxedTo( + TypeDescription.Generic.OBJECT, + ReferenceTypeAwareAssigner.INSTANCE, + Assigner.Typing.STATIC + ) + .apply( methodVisitor, implementationContext ); + } + } + if ( persistentAttributeInterceptable ) { + methodVisitor.visitLabel( arrayStoreLabel ); + implementationContext.getFrameGeneration().full( + methodVisitor, + Arrays.asList( + constants.Type_Array_Object, + constants.Type_Array_Object, + constants.TypeIntegerPrimitive, + constants.TypeObject + ), + List.of( + implementationContext.getInstrumentedType(), + constants.TypeObject, + constants.TypeLazyAttributeLoadingInterceptor + ) + ); + } + methodVisitor.visitInsn( Opcodes.AASTORE ); + } + methodVisitor.visitInsn( Opcodes.ARETURN ); + return new Size( 6, instrumentedMethod.getStackSize() + 1 ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/NameEncodeHelper.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/NameEncodeHelper.java new file mode 100644 index 000000000000..be9773afc730 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/NameEncodeHelper.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +import static org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl.EMBEDDED_MEMBER; + +public class NameEncodeHelper { + + public static String encodeName(String[] propertyNames, Member[] getters, Member[] setters) { + final StringBuilder sb = new StringBuilder(); + for ( int i = 0; i < propertyNames.length; i++ ) { + final String propertyName = propertyNames[i]; + final Member getter = getters[i]; + final Member setter = setters[i]; + // Encode the two member types as 4 bit integer encoded as hex character + sb.append( Integer.toHexString( getKind( getter ) << 2 | getKind( setter ) ) ); + sb.append( propertyName ); + } + return sb.toString(); + } + + private static int getKind(Member member) { + // Encode the member type as 2 bit integer + if ( member == EMBEDDED_MEMBER ) { + return 0; + } + else if ( member instanceof Field ) { + return 1; + } + else if ( member instanceof Method ) { + return 2; + } + else if ( member instanceof ForeignPackageMember ) { + return 3; + } + else { + throw new IllegalArgumentException( "Unknown member type: " + member ); + } + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java index 4b068cb74276..7ec0b142d897 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/PersistentAttributeTransformer.java @@ -17,7 +17,6 @@ import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl.AnnotatedFieldDescription; import org.hibernate.bytecode.enhance.spi.EnhancerConstants; -import org.hibernate.engine.spi.CompositeOwner; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; @@ -27,7 +26,6 @@ import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.modifier.ModifierContributor; -import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; @@ -89,15 +87,19 @@ public boolean isDefault() { private final AnnotatedFieldDescription[] enhancedFields; + private final EnhancerImplConstants constants; + private PersistentAttributeTransformer( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, TypePool classPool, - AnnotatedFieldDescription[] enhancedFields) { + AnnotatedFieldDescription[] enhancedFields, + EnhancerImplConstants constants) { this.managedCtClass = managedCtClass; this.enhancementContext = enhancementContext; this.classPool = classPool; this.enhancedFields = enhancedFields; + this.constants = constants; } public AnnotatedFieldDescription[] getEnhancedFields() { @@ -107,7 +109,8 @@ public AnnotatedFieldDescription[] getEnhancedFields() { public static PersistentAttributeTransformer collectPersistentFields( TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext, - TypePool classPool) { + TypePool classPool, + EnhancerImplConstants constants) { List persistentFieldList = new ArrayList<>(); // HHH-10646 Add fields inherited from @MappedSuperclass // HHH-10981 There is no need to do it for @MappedSuperclass @@ -134,7 +137,7 @@ public static PersistentAttributeTransformer collectPersistentFields( Arrays.toString( orderedFields ) ); } - return new PersistentAttributeTransformer( managedCtClass, enhancementContext, classPool, orderedFields ); + return new PersistentAttributeTransformer( managedCtClass, enhancementContext, classPool, orderedFields, constants ); } private static Collection collectInheritPersistentFields( @@ -262,7 +265,7 @@ DynamicType.Builder applyTo(DynamicType.Builder builder) { .defineMethod( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + enhancedField.getName(), enhancedField.asDefined().getType().asErasure(), - Visibility.PUBLIC + constants.modifierPUBLIC ) .intercept( fieldReader( enhancedField ) ); // Final fields will only be written to from the constructor, @@ -271,10 +274,10 @@ DynamicType.Builder applyTo(DynamicType.Builder builder) { builder = builder .defineMethod( EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + enhancedField.getName(), - TypeDescription.VOID, - Visibility.PUBLIC + constants.TypeVoid, + constants.modifierPUBLIC ) - .withParameters( enhancedField.asDefined().getType().asErasure() ) + .withParameter( enhancedField.asDefined().getType().asErasure() ) .intercept( fieldWriter( enhancedField ) ); } @@ -287,11 +290,11 @@ DynamicType.Builder applyTo(DynamicType.Builder builder) { } if ( compositeOwner ) { - builder = builder.implement( CompositeOwner.class ); + builder = builder.implement( constants.INTERFACES_for_CompositeOwner ); if ( enhancementContext.isCompositeClass( managedCtClass ) ) { - builder = builder.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC ) - .withParameters( String.class ) + builder = builder.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, constants.modifierPUBLIC ) + .withParameter( constants.TypeString ) .intercept( Advice.to( CodeTemplates.CompositeOwnerDirtyCheckingHandler.class ).wrap( StubMethod.INSTANCE ) ); } } @@ -316,7 +319,7 @@ private Implementation fieldReader(AnnotatedFieldDescription enhancedField) { } } else { - return new Implementation.Simple( FieldReaderAppender.of( managedCtClass, enhancedField ) ); + return new Implementation.Simple( FieldReaderAppender.of( managedCtClass, enhancedField, constants ) ); } } @@ -342,7 +345,7 @@ private Implementation fieldWriterImplementation(AnnotatedFieldDescription enhan } } else { - return new Implementation.Simple( FieldWriterAppender.of( managedCtClass, enhancedField ) ); + return new Implementation.Simple( FieldWriterAppender.of( managedCtClass, enhancedField, constants ) ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/SetFieldOnArgument.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/SetFieldOnArgument.java new file mode 100644 index 000000000000..1f5249e063ab --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/SetFieldOnArgument.java @@ -0,0 +1,97 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +public class SetFieldOnArgument implements ByteCodeAppender { + + private final Member setterMember; + + public SetFieldOnArgument(Member setterMember) { + this.setterMember = setterMember; + } + + @Override + public Size apply( + MethodVisitor methodVisitor, + Implementation.Context implementationContext, + MethodDescription instrumentedMethod) { + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + final Class type; + if ( setterMember instanceof Method setter ) { + type = setter.getParameterTypes()[0]; + methodVisitor.visitVarInsn( getLoadOpCode( type ), 1 ); + methodVisitor.visitMethodInsn( + setter.getDeclaringClass().isInterface() ? + Opcodes.INVOKEINTERFACE : + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( setter.getDeclaringClass() ), + setter.getName(), + Type.getMethodDescriptor( + Type.getType( void.class ), + Type.getType( type ) + ), + setter.getDeclaringClass().isInterface() + ); + if ( setter.getReturnType() != void.class ) { + // Setters could return something which we have to ignore + switch ( setter.getReturnType().getTypeName() ) { + case "long": + case "double": + methodVisitor.visitInsn( Opcodes.POP2 ); + break; + default: + methodVisitor.visitInsn( Opcodes.POP ); + break; + } + } + } + else { + final Field setter = (Field) setterMember; + type = setter.getType(); + methodVisitor.visitVarInsn( getLoadOpCode( type ), 1 ); + methodVisitor.visitFieldInsn( + Opcodes.PUTFIELD, + Type.getInternalName( setter.getDeclaringClass() ), + setter.getName(), + Type.getDescriptor( type ) + ); + } + methodVisitor.visitInsn( Opcodes.RETURN ); + return new Size( + is64BitType( type ) ? 3 : 2, + instrumentedMethod.getStackSize() + ); + } + + private int getLoadOpCode(Class type) { + if ( type.isPrimitive() ) { + switch ( type.getTypeName() ) { + case "long": + return Opcodes.LLOAD; + case "float": + return Opcodes.FLOAD; + case "double": + return Opcodes.DLOAD; + } + return Opcodes.ILOAD; + } + return Opcodes.ALOAD; + } + + private boolean is64BitType(Class type) { + return type == long.class || type == double.class; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/SetPropertyValues.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/SetPropertyValues.java new file mode 100644 index 000000000000..5941e6f25d66 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/SetPropertyValues.java @@ -0,0 +1,365 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.bytecode.enhance.internal.bytebuddy; + +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.implementation.Implementation; +import net.bytebuddy.implementation.bytecode.ByteCodeAppender; +import net.bytebuddy.implementation.bytecode.assign.Assigner; +import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate; +import net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner; +import net.bytebuddy.jar.asm.Label; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; +import org.hibernate.bytecode.internal.bytebuddy.BytecodeProviderImpl; +import org.hibernate.engine.spi.CompositeOwner; +import org.hibernate.engine.spi.CompositeTracker; +import org.hibernate.engine.spi.Managed; +import org.hibernate.engine.spi.PersistentAttributeInterceptable; + +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.List; + +public class SetPropertyValues implements ByteCodeAppender { + + private final Class clazz; + private final String[] propertyNames; + private final Member[] setters; + private final boolean enhanced; + private final String internalClazzName; + private final EnhancerImplConstants constants; + + public SetPropertyValues(Class clazz, String[] propertyNames, Member[] setters, EnhancerImplConstants constants) { + this.clazz = clazz; + this.propertyNames = propertyNames; + this.setters = setters; + this.enhanced = Managed.class.isAssignableFrom( clazz ); + this.internalClazzName = Type.getInternalName( clazz ); + this.constants = constants; + } + + @Override + public Size apply( + MethodVisitor methodVisitor, + Implementation.Context implementationContext, + MethodDescription instrumentedMethod) { + final boolean persistentAttributeInterceptable = PersistentAttributeInterceptable.class.isAssignableFrom( + clazz ); + final boolean compositeOwner = CompositeOwner.class.isAssignableFrom( clazz ); + Label currentLabel = null; + Label nextLabel = new Label(); + for ( int index = 0; index < setters.length; index++ ) { + final Member setterMember = setters[index]; + if ( setterMember == BytecodeProviderImpl.EMBEDDED_MEMBER ) { + // The embedded property access does a no-op + continue; + } + if ( currentLabel != null ) { + methodVisitor.visitLabel( currentLabel ); + implementationContext.getFrameGeneration().same( + methodVisitor, + instrumentedMethod.getParameters().asTypeList() + ); + } + // Push entity on stack + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, internalClazzName ); + // Push values array on stack + methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); + methodVisitor.visitLdcInsn( index ); + // Load value for property from array + methodVisitor.visitInsn( Opcodes.AALOAD ); + if ( enhanced ) { + // Duplicate the property value + methodVisitor.visitInsn( Opcodes.DUP ); + // Push LazyPropertyInitializer.UNFETCHED_PROPERTY on the stack + methodVisitor.visitFieldInsn( + Opcodes.GETSTATIC, + constants.internalName_LazyPropertyInitializer, + "UNFETCHED_PROPERTY", + constants.Serializable_TYPE_DESCRIPTOR + ); + Label setterLabel = new Label(); + // Compare property value against LazyPropertyInitializer.UNFETCHED_PROPERTY + // and jump to the setter label if that is unequal + methodVisitor.visitJumpInsn( Opcodes.IF_ACMPNE, setterLabel ); + + // When we get here, we need to clean up the stack before proceeding with the next property + // Pop the property value + methodVisitor.visitInsn( Opcodes.POP ); + // Pop the entity + methodVisitor.visitInsn( Opcodes.POP ); + methodVisitor.visitJumpInsn( Opcodes.GOTO, nextLabel ); + + // This label is jumped to when property value != LazyPropertyInitializer.UNFETCHED_PROPERTY + // At which point we have the entity and the value on the stack + methodVisitor.visitLabel( setterLabel ); + implementationContext.getFrameGeneration().full( + methodVisitor, + List.of( + TypeDescription.ForLoadedType.of( clazz ), + constants.TypeObject + ), + List.of( + implementationContext.getInstrumentedType(), + constants.TypeObject, + constants.Type_Array_Object + ) + ); + } + final Class type; + if ( setterMember instanceof Method setter ) { + type = setter.getParameterTypes()[0]; + } + else if ( setterMember instanceof Field field ) { + type = field.getType(); + } + else { + final ForeignPackageMember foreignPackageMember = (ForeignPackageMember) setterMember; + final Member underlyingMember = foreignPackageMember.getMember(); + if ( underlyingMember instanceof Method setter ) { + type = setter.getParameterTypes()[0]; + } + else { + final Field field = (Field) underlyingMember; + type = field.getType(); + } + } + if ( type.isPrimitive() ) { + PrimitiveUnboxingDelegate.forReferenceType( constants.TypeObject ) + .assignUnboxedTo( + new TypeDescription.Generic.OfNonGenericType.ForLoadedType( type ), + ReferenceTypeAwareAssigner.INSTANCE, + Assigner.Typing.DYNAMIC + ) + .apply( methodVisitor, implementationContext ); + } + else { + methodVisitor.visitTypeInsn( + Opcodes.CHECKCAST, + Type.getInternalName( type ) + ); + } + if ( setterMember instanceof Method setter ) { + methodVisitor.visitMethodInsn( + setter.getDeclaringClass().isInterface() ? + Opcodes.INVOKEINTERFACE : + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( setter.getDeclaringClass() ), + setter.getName(), + Type.getMethodDescriptor( setter ), + setter.getDeclaringClass().isInterface() + ); + if ( setter.getReturnType() != void.class ) { + // Setters could return something which we have to ignore + switch ( setter.getReturnType().getTypeName() ) { + case "long": + case "double": + methodVisitor.visitInsn( Opcodes.POP2 ); + break; + default: + methodVisitor.visitInsn( Opcodes.POP ); + break; + } + } + } + else if ( setterMember instanceof Field field ) { + methodVisitor.visitFieldInsn( + Opcodes.PUTFIELD, + Type.getInternalName( field.getDeclaringClass() ), + field.getName(), + Type.getDescriptor( type ) + ); + } + else { + final ForeignPackageMember foreignPackageMember = (ForeignPackageMember) setterMember; + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName( foreignPackageMember.getForeignPackageAccessor() ), + "set_" + setterMember.getName(), + Type.getMethodDescriptor( + Type.getType( void.class ), + Type.getType( foreignPackageMember.getMember().getDeclaringClass() ), + Type.getType( type ) + ), + false + ); + } + if ( enhanced ) { + final boolean compositeTracker = CompositeTracker.class.isAssignableFrom( type ); + boolean alreadyHasFrame = false; + // The composite owner check and setting only makes sense if + // * the value type is a composite tracker + // * a value subtype can be a composite tracker + // + // Final classes that don't already implement the interface never need to be checked. + // This helps a bit with common final types which otherwise would have to be checked a lot. + if ( compositeOwner && (compositeTracker || !Modifier.isFinal( type.getModifiers() )) ) { + // Push values array on stack + methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); + methodVisitor.visitLdcInsn( index ); + // Load value for property from array + methodVisitor.visitInsn( Opcodes.AALOAD ); + + // Check if value implements composite tracker + methodVisitor.visitInsn( Opcodes.DUP ); + final Label compositeTrackerFalseLabel = new Label(); + final String compositeTrackerType; + final boolean isInterface; + if ( compositeTracker ) { + // If the known type already implements that interface, we use that type, + // so we just do a null check + compositeTrackerType = Type.getInternalName( type ); + isInterface = false; + methodVisitor.visitJumpInsn( Opcodes.IFNULL, compositeTrackerFalseLabel ); + } + else { + // If we don't know for sure, we do an instanceof check + methodVisitor.visitTypeInsn( + Opcodes.INSTANCEOF, + compositeTrackerType = constants.internalName_CompositeTracker + ); + isInterface = true; + methodVisitor.visitJumpInsn( Opcodes.IFEQ, compositeTrackerFalseLabel ); + } + + // Load the tracker on which we will call $$_hibernate_setOwner + methodVisitor.visitTypeInsn( + Opcodes.CHECKCAST, + compositeTrackerType + ); + methodVisitor.visitLdcInsn( propertyNames[index] ); + // Load the owner and cast it to the owner class, as we know it implements CompositeOwner + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, internalClazzName ); + // Invoke the method to set the owner + methodVisitor.visitMethodInsn( + isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, + compositeTrackerType, + "$$_hibernate_setOwner", + constants.methodDescriptor_SetOwner, + isInterface + ); + + // Skip the cleanup + final Label compositeTrackerEndLabel = new Label(); + methodVisitor.visitJumpInsn( Opcodes.GOTO, compositeTrackerEndLabel ); + + // Here is the cleanup section for the false branch + methodVisitor.visitLabel( compositeTrackerFalseLabel ); + // We still have the duplicated value on the stack + implementationContext.getFrameGeneration().full( + methodVisitor, + List.of( + constants.TypeObject + ), + List.of( + implementationContext.getInstrumentedType(), + constants.TypeObject, + constants.Type_Array_Object + ) + ); + // Pop that duplicated property value from the stack + methodVisitor.visitInsn( Opcodes.POP ); + + // Clean stack after the if block + methodVisitor.visitLabel( compositeTrackerEndLabel ); + implementationContext.getFrameGeneration() + .same( methodVisitor, instrumentedMethod.getParameters().asTypeList() ); + alreadyHasFrame = true; + } + if ( persistentAttributeInterceptable ) { + // Load the owner + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, internalClazzName ); + // Extract the interceptor + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + internalClazzName, + "$$_hibernate_getInterceptor", + constants.methodDescriptor_getInterceptor, + false + ); + // Duplicate the interceptor on the stack and check if it implements BytecodeLazyAttributeInterceptor + methodVisitor.visitInsn( Opcodes.DUP ); + methodVisitor.visitTypeInsn( + Opcodes.INSTANCEOF, + constants.internalName_BytecodeLazyAttributeInterceptor + ); + + // Jump to the false label if the instanceof check fails + final Label instanceofFalseLabel = new Label(); + methodVisitor.visitJumpInsn( Opcodes.IFEQ, instanceofFalseLabel ); + + // Cast to the subtype, so we can mark the property as initialized + methodVisitor.visitTypeInsn( + Opcodes.CHECKCAST, + constants.internalName_BytecodeLazyAttributeInterceptor + ); + // Load the property name + methodVisitor.visitLdcInsn( propertyNames[index] ); + // Invoke the method to mark the property as initialized + methodVisitor.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + constants.internalName_BytecodeLazyAttributeInterceptor, + "attributeInitialized", + constants.methodDescriptor_attributeInitialized, + true + ); + + // Skip the cleanup + final Label instanceofEndLabel = new Label(); + methodVisitor.visitJumpInsn( Opcodes.GOTO, instanceofEndLabel ); + + // Here is the cleanup section for the false branch + methodVisitor.visitLabel( instanceofFalseLabel ); + // We still have the duplicated interceptor on the stack + implementationContext.getFrameGeneration().full( + methodVisitor, + constants.INTERFACES_for_PersistentAttributeInterceptor, + List.of( + implementationContext.getInstrumentedType(), + constants.TypeObject, + constants.Type_Array_Object + ) + ); + // Pop that duplicated interceptor from the stack + methodVisitor.visitInsn( Opcodes.POP ); + + // Clean stack after the if block + methodVisitor.visitLabel( instanceofEndLabel ); + implementationContext.getFrameGeneration() + .same( methodVisitor, instrumentedMethod.getParameters().asTypeList() ); + alreadyHasFrame = true; + } + + if ( alreadyHasFrame ) { + // Usually, the currentLabel is visited as well generating a frame, + // but if a frame was already generated, only visit the label here, + // otherwise two frames for the same bytecode index are generated, + // which is wrong and will produce an error when the JDK ClassFile API is used + methodVisitor.visitLabel( nextLabel ); + currentLabel = null; + } + else { + currentLabel = nextLabel; + } + nextLabel = new Label(); + } + } + if ( currentLabel != null ) { + methodVisitor.visitLabel( currentLabel ); + implementationContext.getFrameGeneration() + .same( methodVisitor, instrumentedMethod.getParameters().asTypeList() ); + } + methodVisitor.visitInsn( Opcodes.RETURN ); + return new Size( 4, instrumentedMethod.getStackSize() ); + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 10c114fdf09f..1c86fd450e63 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -6,14 +6,15 @@ import java.lang.reflect.Constructor; +import net.bytebuddy.description.modifier.Visibility; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImplConstants; import org.hibernate.bytecode.spi.BasicProxyFactory; import org.hibernate.engine.spi.PrimeAmongSecondarySupertypes; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.proxy.ProxyConfiguration; -import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy; public class BasicProxyFactoryImpl implements BasicProxyFactory { @@ -36,6 +37,7 @@ public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, final Class superClassOrMainInterface = superClass != null ? superClass : interfaceClass; final ByteBuddyState.ProxyDefinitionHelpers helpers = byteBuddyState.getProxyDefinitionHelpers(); + final EnhancerImplConstants constants = byteBuddyState.getEnhancerConstants(); final String proxyClassName = superClassOrMainInterface.getName() + "$" + PROXY_NAMING_SUFFIX; this.proxyClass = byteBuddyState.loadBasicProxy( superClassOrMainInterface, proxyClassName, (byteBuddy, namingStrategy) -> @@ -46,7 +48,7 @@ public BasicProxyFactoryImpl(final Class superClass, final Class interfaceClass, .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) .method( byteBuddyState.getProxyDefinitionHelpers().getVirtualNotFinalizerFilter() ) .intercept( byteBuddyState.getProxyDefinitionHelpers().getDelegateToInterceptorDispatcherMethodDelegation() ) - .implement( ProxyConfiguration.class ) + .implement( constants.INTERFACES_for_ProxyConfiguration ) .intercept( byteBuddyState.getProxyDefinitionHelpers().getInterceptorFieldAccessor() ) ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index e3b108e557f8..e266e393053b 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -66,8 +66,6 @@ public final class ByteBuddyState { private final ProxyDefinitionHelpers proxyDefinitionHelpers = new ProxyDefinitionHelpers(); - private final ClassRewriter classRewriter; - private final EnhancerImplConstants enhancerConstants = new EnhancerImplConstants(); /** @@ -87,7 +85,6 @@ public ByteBuddyState() { this.byteBuddy = new ByteBuddy( classFileVersion ).with( TypeValidation.DISABLED ); this.proxyCache = new TypeCache( TypeCache.Sort.WEAK ); this.basicProxyCache = new TypeCache( TypeCache.Sort.WEAK ); - this.classRewriter = new StandardClassRewriter(); } /** @@ -355,25 +352,6 @@ public DynamicType.Builder appendIgnoreAlsoAtEnd(DynamicType.Builder build } } - private interface ClassRewriter { - DynamicType.Builder installReflectionMethodVisitors(DynamicType.Builder builder); - - void registerAuthorizedClass(Unloaded unloadedClass); - } - - private static class StandardClassRewriter implements ClassRewriter { - @Override - public DynamicType.Builder installReflectionMethodVisitors(DynamicType.Builder builder) { - // do nothing - return builder; - } - - @Override - public void registerAuthorizedClass(Unloaded unloadedClass) { - // do nothing - } - } - private static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) { try { return ClassLoadingStrategy.UsingLookup.of( MethodHandles.privateLookupIn( originalClass, LOOKUP ) ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index 25cba0cef8c1..b3e6cc0a2915 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -4,7 +4,6 @@ */ package org.hibernate.bytecode.internal.bytebuddy; -import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Member; @@ -12,28 +11,27 @@ import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.BridgeMembersClassInfo; import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerClassLocator; import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImplConstants; +import org.hibernate.bytecode.enhance.internal.bytebuddy.ForeignPackageMember; +import org.hibernate.bytecode.enhance.internal.bytebuddy.GetFieldOnArgument; +import org.hibernate.bytecode.enhance.internal.bytebuddy.GetPropertyNames; +import org.hibernate.bytecode.enhance.internal.bytebuddy.GetPropertyValues; +import org.hibernate.bytecode.enhance.internal.bytebuddy.NameEncodeHelper; +import org.hibernate.bytecode.enhance.internal.bytebuddy.SetFieldOnArgument; +import org.hibernate.bytecode.enhance.internal.bytebuddy.SetPropertyValues; import org.hibernate.bytecode.enhance.spi.EnhancementContext; import org.hibernate.bytecode.enhance.spi.Enhancer; -import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer; -import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor; -import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor; import org.hibernate.bytecode.spi.BytecodeProvider; import org.hibernate.bytecode.spi.ProxyFactoryFactory; import org.hibernate.bytecode.spi.ReflectionOptimizer; -import org.hibernate.engine.spi.CompositeOwner; -import org.hibernate.engine.spi.CompositeTracker; -import org.hibernate.engine.spi.Managed; -import org.hibernate.engine.spi.PersistentAttributeInterceptable; -import org.hibernate.engine.spi.PersistentAttributeInterceptor; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; @@ -49,40 +47,21 @@ import net.bytebuddy.ClassFileVersion; import net.bytebuddy.NamingStrategy; -import net.bytebuddy.description.NamedElement; -import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.MethodCall; -import net.bytebuddy.implementation.bytecode.ByteCodeAppender; -import net.bytebuddy.implementation.bytecode.assign.Assigner; -import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveBoxingDelegate; -import net.bytebuddy.implementation.bytecode.assign.primitive.PrimitiveUnboxingDelegate; -import net.bytebuddy.implementation.bytecode.assign.reference.ReferenceTypeAwareAssigner; -import net.bytebuddy.jar.asm.Label; -import net.bytebuddy.jar.asm.MethodVisitor; import net.bytebuddy.jar.asm.Opcodes; -import net.bytebuddy.jar.asm.Type; -import net.bytebuddy.matcher.ElementMatcher; -import net.bytebuddy.matcher.ElementMatchers; import org.checkerframework.checker.nullness.qual.Nullable; public class BytecodeProviderImpl implements BytecodeProvider { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( BytecodeProviderImpl.class ); - private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "HibernateInstantiator"; + private static final String INSTANTIATOR_PROXY_NAMING_SUFFIX = "$HibernateInstantiator"; private static final String OPTIMIZER_PROXY_NAMING_SUFFIX = "HibernateAccessOptimizer"; - private static final String OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX = "HibernateAccessOptimizerBridge"; - private static final ElementMatcher.Junction newInstanceMethodName = ElementMatchers.named( - "newInstance" ); - private static final ElementMatcher.Junction getPropertyValuesMethodName = ElementMatchers.named( - "getPropertyValues" ); - private static final ElementMatcher.Junction setPropertyValuesMethodName = ElementMatchers.named( - "setPropertyValues" ); - private static final ElementMatcher.Junction getPropertyNamesMethodName = ElementMatchers.named( - "getPropertyNames" ); - private static final Member EMBEDDED_MEMBER = new Member() { + private static final String OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX = "$HibernateAccessOptimizerBridge"; + + public static final Member EMBEDDED_MEMBER = new Member() { @Override public Class getDeclaringClass() { return null; @@ -105,15 +84,16 @@ public boolean isSynthetic() { }; private final ByteBuddyState byteBuddyState; + private final EnhancerImplConstants constants; private final ByteBuddyProxyHelper byteBuddyProxyHelper; /** * Constructs a ByteBuddy BytecodeProvider instance which attempts to auto-detect the target JVM version - * from the currently running one, with a fallback on Java 11. + * from the currently running one, with a fallback on Java 17. */ public BytecodeProviderImpl() { - this( ClassFileVersion.ofThisVm( ClassFileVersion.JAVA_V11 ) ); + this( ClassFileVersion.ofThisVm( ClassFileVersion.JAVA_V17 ) ); } /** @@ -123,6 +103,7 @@ public BytecodeProviderImpl() { public BytecodeProviderImpl(ClassFileVersion targetCompatibleJVM) { this.byteBuddyState = new ByteBuddyState( targetCompatibleJVM ); this.byteBuddyProxyHelper = new ByteBuddyProxyHelper( byteBuddyState ); + this.constants = byteBuddyState.getEnhancerConstants(); } @Override @@ -147,11 +128,11 @@ public ReflectionOptimizer getReflectionOptimizer( fastClass = null; } else { - final String className = clazz.getName() + "$" + INSTANTIATOR_PROXY_NAMING_SUFFIX; + final String className = clazz.getName() + INSTANTIATOR_PROXY_NAMING_SUFFIX; fastClass = byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy .with( namingStrategy ) - .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) - .method( newInstanceMethodName ) + .subclass( constants.TypeInstantiationOptimizer ) + .method( constants.newInstanceMethodName ) .intercept( MethodCall.construct( constructor ) ) ); } @@ -173,16 +154,16 @@ public ReflectionOptimizer getReflectionOptimizer( final Class bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy .with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX, - new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) + new NamingStrategy.Suffixing.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) - .subclass( Object.class ) - .implement( ReflectionOptimizer.AccessOptimizer.class ) - .method( getPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getterNames, getters ) ) ) - .method( setPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new SetPropertyValues( clazz, getterNames, setters ) ) ) - .method( getPropertyNamesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyNames( getterNames ) ) ) + .subclass( constants.TypeObject ) + .implement( constants.INTERFACES_for_AccessOptimizer ) + .method( constants.getPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyValues( clazz, getterNames, getters, constants ) ) ) + .method( constants.setPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new SetPropertyValues( clazz, getterNames, setters, constants ) ) ) + .method( constants.getPropertyNamesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyNames( getterNames, constants ) ) ) ); try { @@ -209,11 +190,11 @@ public ReflectionOptimizer getReflectionOptimizer( fastClass = null; } else { - final String className = clazz.getName() + "$" + INSTANTIATOR_PROXY_NAMING_SUFFIX; + final String className = clazz.getName() + INSTANTIATOR_PROXY_NAMING_SUFFIX; fastClass = byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy .with( namingStrategy ) - .subclass( ReflectionOptimizer.InstantiationOptimizer.class ) - .method( newInstanceMethodName ) + .subclass( constants.TypeInstantiationOptimizer ) + .method( constants.newInstanceMethodName ) .intercept( MethodCall.construct( constructor ) ) ); } @@ -235,36 +216,36 @@ public ReflectionOptimizer getReflectionOptimizer( final String[] propertyNames = propertyAccessMap.keySet().toArray( new String[0] ); final Class superClass = determineAccessOptimizerSuperClass( clazz, propertyNames, getters, setters ); - final String className = clazz.getName() + "$" + OPTIMIZER_PROXY_NAMING_SUFFIX + encodeName( propertyNames, getters, setters ); + final String className = clazz.getName() + "$" + OPTIMIZER_PROXY_NAMING_SUFFIX + NameEncodeHelper.encodeName( propertyNames, getters, setters ); final Class bulkAccessor; if ( className.getBytes( StandardCharsets.UTF_8 ).length >= 0x10000 ) { // The JVM has a 64K byte limit on class name length, so fallback to random name if encoding exceeds that bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy .with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX, - new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) + new NamingStrategy.Suffixing.BaseNameResolver.ForFixedValue( clazz.getName() ) ) ) .subclass( superClass ) - .implement( ReflectionOptimizer.AccessOptimizer.class ) - .method( getPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters ) ) ) - .method( setPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) ) - .method( getPropertyNamesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyNames( propertyNames ) ) ) + .implement( constants.INTERFACES_for_AccessOptimizer ) + .method( constants.getPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters, constants ) ) ) + .method( constants.setPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters, constants ) ) ) + .method( constants.getPropertyNamesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyNames( propertyNames, constants ) ) ) ); } else { bulkAccessor = byteBuddyState.load( clazz, className, (byteBuddy, namingStrategy) -> byteBuddy .with( namingStrategy ) .subclass( superClass ) - .implement( ReflectionOptimizer.AccessOptimizer.class ) - .method( getPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters ) ) ) - .method( setPropertyValuesMethodName ) - .intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters ) ) ) - .method( getPropertyNamesMethodName ) - .intercept( new Implementation.Simple( new GetPropertyNames( propertyNames ) ) ) + .implement( constants.INTERFACES_for_AccessOptimizer ) + .method( constants.getPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyValues( clazz, propertyNames, getters, constants ) ) ) + .method( constants.setPropertyValuesMethodName ) + .intercept( new Implementation.Simple( new SetPropertyValues( clazz, propertyNames, setters, constants ) ) ) + .method( constants.getPropertyNamesMethodName ) + .intercept( new Implementation.Simple( new GetPropertyNames( propertyNames, constants ) ) ) ); } @@ -279,17 +260,6 @@ public ReflectionOptimizer getReflectionOptimizer( } } - private static class BridgeMembersClassInfo { - final Class clazz; - final List propertyNames = new ArrayList<>(); - final List getters = new ArrayList<>(); - final List setters = new ArrayList<>(); - - public BridgeMembersClassInfo(Class clazz) { - this.clazz = clazz; - } - } - private Class determineAccessOptimizerSuperClass(Class clazz, String[] propertyNames, Member[] getters, Member[] setters) { if ( clazz.isInterface() ) { return Object.class; @@ -305,13 +275,13 @@ private Class determineAccessOptimizerSuperClass(Class clazz, String[] pro final BridgeMembersClassInfo bridgeMembersClassInfo = bridgeMembersClassInfos.get( i ); final Class newSuperClass = superClass; - final String className = bridgeMembersClassInfo.clazz.getName() + "$" + OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX + encodeName( bridgeMembersClassInfo.propertyNames, bridgeMembersClassInfo.getters, bridgeMembersClassInfo.setters ); + final String className = bridgeMembersClassInfo.getClazz().getName() + OPTIMIZER_PROXY_BRIDGE_NAMING_SUFFIX + bridgeMembersClassInfo.encodeName(); superClass = byteBuddyState.load( - bridgeMembersClassInfo.clazz, + bridgeMembersClassInfo.getClazz(), className, (byteBuddy, namingStrategy) -> { DynamicType.Builder builder = byteBuddy.with( namingStrategy ).subclass( newSuperClass ); - for ( Member getter : bridgeMembersClassInfo.getters ) { + for ( Member getter : bridgeMembersClassInfo.gettersIterable() ) { if ( !Modifier.isPublic( getter.getModifiers() ) ) { final Class getterType; if ( getter instanceof Field field ) { @@ -331,7 +301,7 @@ else if ( getter instanceof Method method ) { ), Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC ) - .withParameter( bridgeMembersClassInfo.clazz ) + .withParameter( bridgeMembersClassInfo.getClazz() ) .intercept( new Implementation.Simple( new GetFieldOnArgument( @@ -341,7 +311,7 @@ else if ( getter instanceof Method method ) { ); } } - for ( Member setter : bridgeMembersClassInfo.setters ) { + for ( Member setter : bridgeMembersClassInfo.settersIterable() ) { if ( !Modifier.isPublic( setter.getModifiers() ) ) { final Class setterType; if ( setter instanceof Field field ) { @@ -359,7 +329,7 @@ else if ( setter instanceof Method method ) { TypeDescription.Generic.VOID, Opcodes.ACC_PROTECTED | Opcodes.ACC_STATIC ) - .withParameter( bridgeMembersClassInfo.clazz ) + .withParameter( bridgeMembersClassInfo.getClazz() ) .withParameter( setterType ) .intercept( new Implementation.Simple( @@ -378,10 +348,10 @@ else if ( setter instanceof Method method ) { for ( int j = 0; j < getters.length; j++ ) { final Member getter = getters[j]; final Member setter = setters[j]; - if ( bridgeMembersClassInfo.getters.contains( getter ) && !Modifier.isPublic( getter.getModifiers() ) ) { + if ( bridgeMembersClassInfo.containsGetter( getter ) && !Modifier.isPublic( getter.getModifiers() ) ) { getters[j] = new ForeignPackageMember( superClass, getter ); } - if ( bridgeMembersClassInfo.setters.contains( setter ) && !Modifier.isPublic( setter.getModifiers() ) ) { + if ( bridgeMembersClassInfo.containsSetter( setter ) && !Modifier.isPublic( setter.getModifiers() ) ) { setters[j] = new ForeignPackageMember( superClass, setter ); } } @@ -390,219 +360,6 @@ else if ( setter instanceof Method method ) { return superClass; } - private static String encodeName(String[] propertyNames, Member[] getters, Member[] setters) { - return encodeName( Arrays.asList( propertyNames ), Arrays.asList( getters ), Arrays.asList( setters ) ); - } - - private static String encodeName(List propertyNames, List getters, List setters) { - final StringBuilder sb = new StringBuilder(); - for ( int i = 0; i < propertyNames.size(); i++ ) { - final String propertyName = propertyNames.get( i ); - final Member getter = getters.get( i ); - final Member setter = setters.get( i ); - // Encode the two member types as 4 bit integer encoded as hex character - sb.append( Integer.toHexString( getKind( getter ) << 2 | getKind( setter ) ) ); - sb.append( propertyName ); - } - return sb.toString(); - } - - private static int getKind(Member member) { - // Encode the member type as 2 bit integer - if ( member == EMBEDDED_MEMBER ) { - return 0; - } - else if ( member instanceof Field ) { - return 1; - } - else if ( member instanceof Method ) { - return 2; - } - else if ( member instanceof ForeignPackageMember ) { - return 3; - } - else { - throw new IllegalArgumentException( "Unknown member type: " + member ); - } - } - - private static class ForeignPackageMember implements Member { - - private final Class foreignPackageAccessor; - private final Member member; - - public ForeignPackageMember(Class foreignPackageAccessor, Member member) { - this.foreignPackageAccessor = foreignPackageAccessor; - this.member = member; - } - - public Class getForeignPackageAccessor() { - return foreignPackageAccessor; - } - - public Member getMember() { - return member; - } - - @Override - public Class getDeclaringClass() { - return member.getDeclaringClass(); - } - - @Override - public String getName() { - return member.getName(); - } - - @Override - public int getModifiers() { - return member.getModifiers(); - } - - @Override - public boolean isSynthetic() { - return member.isSynthetic(); - } - } - - private static class GetFieldOnArgument implements ByteCodeAppender { - - private final Member getterMember; - - public GetFieldOnArgument(Member getterMember) { - this.getterMember = getterMember; - } - - @Override - public Size apply( - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - MethodDescription instrumentedMethod) { - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - final Class type; - if ( getterMember instanceof Method getter ) { - type = getter.getReturnType(); - methodVisitor.visitMethodInsn( - getter.getDeclaringClass().isInterface() ? - Opcodes.INVOKEINTERFACE : - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( getter.getDeclaringClass() ), - getter.getName(), - Type.getMethodDescriptor( getter ), - getter.getDeclaringClass().isInterface() - ); - } - else { - final Field getter = (Field) getterMember; - type = getter.getType(); - methodVisitor.visitFieldInsn( - Opcodes.GETFIELD, - Type.getInternalName( getter.getDeclaringClass() ), - getter.getName(), - Type.getDescriptor( type ) - ); - } - methodVisitor.visitInsn( getReturnOpCode( type ) ); - return new Size( 2, instrumentedMethod.getStackSize() ); - } - - private int getReturnOpCode(Class type) { - if ( type.isPrimitive() ) { - switch ( type.getTypeName() ) { - case "long": - return Opcodes.LRETURN; - case "float": - return Opcodes.FRETURN; - case "double": - return Opcodes.DRETURN; - } - return Opcodes.IRETURN; - } - return Opcodes.ARETURN; - } - } - - private static class SetFieldOnArgument implements ByteCodeAppender { - - private final Member setterMember; - - public SetFieldOnArgument(Member setterMember) { - this.setterMember = setterMember; - } - - @Override - public Size apply( - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - MethodDescription instrumentedMethod) { - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - final Class type; - if ( setterMember instanceof Method setter ) { - type = setter.getParameterTypes()[0]; - methodVisitor.visitVarInsn( getLoadOpCode( type ), 1 ); - methodVisitor.visitMethodInsn( - setter.getDeclaringClass().isInterface() ? - Opcodes.INVOKEINTERFACE : - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( setter.getDeclaringClass() ), - setter.getName(), - Type.getMethodDescriptor( - Type.getType( void.class ), - Type.getType( type ) - ), - setter.getDeclaringClass().isInterface() - ); - if ( setter.getReturnType() != void.class ) { - // Setters could return something which we have to ignore - switch ( setter.getReturnType().getTypeName() ) { - case "long": - case "double": - methodVisitor.visitInsn( Opcodes.POP2 ); - break; - default: - methodVisitor.visitInsn( Opcodes.POP ); - break; - } - } - } - else { - final Field setter = (Field) setterMember; - type = setter.getType(); - methodVisitor.visitVarInsn( getLoadOpCode( type ), 1 ); - methodVisitor.visitFieldInsn( - Opcodes.PUTFIELD, - Type.getInternalName( setter.getDeclaringClass() ), - setter.getName(), - Type.getDescriptor( type ) - ); - } - methodVisitor.visitInsn( Opcodes.RETURN ); - return new Size( - is64BitType( type ) ? 3 : 2, - instrumentedMethod.getStackSize() - ); - } - - private int getLoadOpCode(Class type) { - if ( type.isPrimitive() ) { - switch ( type.getTypeName() ) { - case "long": - return Opcodes.LLOAD; - case "float": - return Opcodes.FLOAD; - case "double": - return Opcodes.DLOAD; - } - return Opcodes.ILOAD; - } - return Opcodes.ALOAD; - } - - private boolean is64BitType(Class type) { - return type == long.class || type == double.class; - } - } - private List createBridgeMembersClassInfos( Class clazz, Member[] getters, @@ -617,12 +374,12 @@ private List createBridgeMembersClassInfos( final Member setter = setters[i]; if ( getter.getDeclaringClass() == c && !Modifier.isPublic( getter.getModifiers() ) || setter.getDeclaringClass() == c && !Modifier.isPublic( setter.getModifiers() ) ) { - bridgeMemberClassInfo.getters.add( getter ); - bridgeMemberClassInfo.setters.add( setter ); - bridgeMemberClassInfo.propertyNames.add( propertyNames[i] ); + bridgeMemberClassInfo.addGetter( getter ); + bridgeMemberClassInfo.addSetter( setter ); + bridgeMemberClassInfo.addProperty( propertyNames[i] ); } } - if ( !bridgeMemberClassInfo.propertyNames.isEmpty() ) { + if ( !bridgeMemberClassInfo.propertyNamesIsEmpty() ) { bridgeMembersClassInfos.add( bridgeMemberClassInfo ); } c = c.getSuperclass(); @@ -634,568 +391,6 @@ public ByteBuddyProxyHelper getByteBuddyProxyHelper() { return byteBuddyProxyHelper; } - private static class GetPropertyValues implements ByteCodeAppender { - - private final Class clazz; - private final String[] propertyNames; - private final Member[] getters; - private final boolean persistentAttributeInterceptable; - - public GetPropertyValues(Class clazz, String[] propertyNames, Member[] getters) { - this.clazz = clazz; - this.propertyNames = propertyNames; - this.getters = getters; - this.persistentAttributeInterceptable = PersistentAttributeInterceptable.class.isAssignableFrom( clazz ); - } - - @Override - public Size apply( - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - MethodDescription instrumentedMethod) { - if ( persistentAttributeInterceptable ) { - methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) ); - - // Extract the interceptor - methodVisitor.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( clazz ), - "$$_hibernate_getInterceptor", - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), - false - ); - // Duplicate the interceptor on the stack and check if it implements LazyAttributeLoadingInterceptor - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitTypeInsn( - Opcodes.INSTANCEOF, - Type.getInternalName( LazyAttributeLoadingInterceptor.class ) - ); - - // Jump to the false label if the instanceof check fails - final Label instanceofFalseLabel = new Label(); - methodVisitor.visitJumpInsn( Opcodes.IFEQ, instanceofFalseLabel ); - - // Cast to the subtype, so we can mark the property as initialized - methodVisitor.visitTypeInsn( - Opcodes.CHECKCAST, - Type.getInternalName( LazyAttributeLoadingInterceptor.class ) - ); - // Store the LazyAttributeLoadingInterceptor at index 2 - methodVisitor.visitVarInsn( Opcodes.ASTORE, 2 ); - - // Skip the cleanup - final Label instanceofEndLabel = new Label(); - methodVisitor.visitJumpInsn( Opcodes.GOTO, instanceofEndLabel ); - - // Here is the cleanup section for the false branch - methodVisitor.visitLabel( instanceofFalseLabel ); - // We still have the duplicated interceptor on the stack - implementationContext.getFrameGeneration().full( - methodVisitor, - Arrays.asList( - TypeDescription.ForLoadedType.of( PersistentAttributeInterceptor.class ) - ), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ) - ) - ); - // Pop that duplicated interceptor from the stack - methodVisitor.visitInsn( Opcodes.POP ); - methodVisitor.visitInsn( Opcodes.ACONST_NULL ); - methodVisitor.visitVarInsn( Opcodes.ASTORE, 2 ); - - methodVisitor.visitLabel( instanceofEndLabel ); - implementationContext.getFrameGeneration().full( - methodVisitor, - Collections.emptyList(), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ), - TypeDescription.ForLoadedType.of( LazyAttributeLoadingInterceptor.class ) - ) - ); - } - methodVisitor.visitLdcInsn( getters.length ); - methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( Object.class ) ); - for ( int index = 0; index < getters.length; index++ ) { - final Member getterMember = getters[index]; - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitLdcInsn( index ); - - final Label arrayStoreLabel = new Label(); - if ( getterMember == EMBEDDED_MEMBER ) { - // The embedded property access returns the owner - methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - } - else { - if ( persistentAttributeInterceptable ) { - final Label extractValueLabel = new Label(); - - // Load the LazyAttributeLoadingInterceptor - methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); - // If that is null, then assume attributes are loaded and jump to extraction - methodVisitor.visitJumpInsn( Opcodes.IFNULL, extractValueLabel ); - // Load the LazyAttributeLoadingInterceptor - methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); - // Load the current property name - methodVisitor.visitLdcInsn( propertyNames[index] ); - // Invoke isAttributeLoaded on the interceptor - methodVisitor.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( LazyAttributeLoadingInterceptor.class ), - "isAttributeLoaded", - Type.getMethodDescriptor( Type.getType( boolean.class ), Type.getType( String.class ) ), - false - ); - // If the attribute is loaded, jump to extraction - methodVisitor.visitJumpInsn( Opcodes.IFNE, extractValueLabel ); - - // Push LazyPropertyInitializer.UNFETCHED_PROPERTY on the stack - methodVisitor.visitFieldInsn( - Opcodes.GETSTATIC, - Type.getInternalName( LazyPropertyInitializer.class ), - "UNFETCHED_PROPERTY", - Type.getDescriptor( Serializable.class ) - ); - // Jump to the label where we handle storing the unfetched property - methodVisitor.visitJumpInsn( Opcodes.GOTO, arrayStoreLabel ); - - // This is the end of the lazy check i.e. the start of extraction - methodVisitor.visitLabel( extractValueLabel ); - implementationContext.getFrameGeneration().full( - methodVisitor, - Arrays.asList( - TypeDescription.ForLoadedType.of( Object[].class ), - TypeDescription.ForLoadedType.of( Object[].class ), - TypeDescription.ForLoadedType.of( int.class ) - ), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ), - TypeDescription.ForLoadedType.of( LazyAttributeLoadingInterceptor.class ) - ) - ); - } - - // Load the entity to extract the property - methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) ); - - final Class type; - if ( getterMember instanceof Method getter ) { - type = getter.getReturnType(); - methodVisitor.visitMethodInsn( - getter.getDeclaringClass().isInterface() ? - Opcodes.INVOKEINTERFACE : - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( getter.getDeclaringClass() ), - getter.getName(), - Type.getMethodDescriptor( getter ), - getter.getDeclaringClass().isInterface() - ); - } - else if ( getterMember instanceof Field getter ) { - type = getter.getType(); - methodVisitor.visitFieldInsn( - Opcodes.GETFIELD, - Type.getInternalName( getter.getDeclaringClass() ), - getter.getName(), - Type.getDescriptor( type ) - ); - } - else { - assert getterMember instanceof ForeignPackageMember; - final ForeignPackageMember foreignPackageMember = (ForeignPackageMember) getterMember; - final Member underlyingMember = foreignPackageMember.getMember(); - if ( underlyingMember instanceof Method getter ) { - type = getter.getReturnType(); - } - else { - final Field getter = (Field) underlyingMember; - type = getter.getType(); - } - methodVisitor.visitMethodInsn( - Opcodes.INVOKESTATIC, - Type.getInternalName( foreignPackageMember.getForeignPackageAccessor() ), - "get_" + getterMember.getName(), - Type.getMethodDescriptor( - Type.getType( type ), - Type.getType( underlyingMember.getDeclaringClass() ) - ), - false - ); - } - if ( type.isPrimitive() ) { - PrimitiveBoxingDelegate.forPrimitive( new TypeDescription.ForLoadedType( type ) ) - .assignBoxedTo( - TypeDescription.Generic.OBJECT, - ReferenceTypeAwareAssigner.INSTANCE, - Assigner.Typing.STATIC - ) - .apply( methodVisitor, implementationContext ); - } - } - if ( persistentAttributeInterceptable ) { - methodVisitor.visitLabel( arrayStoreLabel ); - implementationContext.getFrameGeneration().full( - methodVisitor, - Arrays.asList( - TypeDescription.ForLoadedType.of( Object[].class ), - TypeDescription.ForLoadedType.of( Object[].class ), - TypeDescription.ForLoadedType.of( int.class ), - TypeDescription.ForLoadedType.of( Object.class ) - ), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ), - TypeDescription.ForLoadedType.of( LazyAttributeLoadingInterceptor.class ) - ) - ); - } - methodVisitor.visitInsn( Opcodes.AASTORE ); - } - methodVisitor.visitInsn( Opcodes.ARETURN ); - return new Size( 6, instrumentedMethod.getStackSize() + 1 ); - } - } - - private static class SetPropertyValues implements ByteCodeAppender { - - private final Class clazz; - private final String[] propertyNames; - private final Member[] setters; - private final boolean enhanced; - - public SetPropertyValues(Class clazz, String[] propertyNames, Member[] setters) { - this.clazz = clazz; - this.propertyNames = propertyNames; - this.setters = setters; - this.enhanced = Managed.class.isAssignableFrom( clazz ); - } - - @Override - public Size apply( - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - MethodDescription instrumentedMethod) { - final boolean persistentAttributeInterceptable = PersistentAttributeInterceptable.class.isAssignableFrom( clazz ); - final boolean compositeOwner = CompositeOwner.class.isAssignableFrom( clazz ); - Label currentLabel = null; - Label nextLabel = new Label(); - for ( int index = 0; index < setters.length; index++ ) { - final Member setterMember = setters[index]; - if ( setterMember == EMBEDDED_MEMBER ) { - // The embedded property access does a no-op - continue; - } - if ( currentLabel != null ) { - methodVisitor.visitLabel( currentLabel ); - implementationContext.getFrameGeneration().same( - methodVisitor, - instrumentedMethod.getParameters().asTypeList() - ); - } - // Push entity on stack - methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) ); - // Push values array on stack - methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); - methodVisitor.visitLdcInsn( index ); - // Load value for property from array - methodVisitor.visitInsn( Opcodes.AALOAD ); - if ( enhanced ) { - // Duplicate the property value - methodVisitor.visitInsn( Opcodes.DUP ); - // Push LazyPropertyInitializer.UNFETCHED_PROPERTY on the stack - methodVisitor.visitFieldInsn( - Opcodes.GETSTATIC, - Type.getInternalName( LazyPropertyInitializer.class ), - "UNFETCHED_PROPERTY", - Type.getDescriptor( Serializable.class ) - ); - Label setterLabel = new Label(); - // Compare property value against LazyPropertyInitializer.UNFETCHED_PROPERTY - // and jump to the setter label if that is unequal - methodVisitor.visitJumpInsn( Opcodes.IF_ACMPNE, setterLabel ); - - // When we get here, we need to clean up the stack before proceeding with the next property - // Pop the property value - methodVisitor.visitInsn( Opcodes.POP ); - // Pop the entity - methodVisitor.visitInsn( Opcodes.POP ); - methodVisitor.visitJumpInsn( Opcodes.GOTO, nextLabel ); - - // This label is jumped to when property value != LazyPropertyInitializer.UNFETCHED_PROPERTY - // At which point we have the entity and the value on the stack - methodVisitor.visitLabel( setterLabel ); - implementationContext.getFrameGeneration().full( - methodVisitor, - Arrays.asList( - TypeDescription.ForLoadedType.of( clazz ), - TypeDescription.ForLoadedType.of( Object.class ) - ), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ), - TypeDescription.ForLoadedType.of( Object[].class ) - ) - ); - } - final Class type; - if ( setterMember instanceof Method setter ) { - type = setter.getParameterTypes()[0]; - } - else if ( setterMember instanceof Field field ) { - type = field.getType(); - } - else { - final ForeignPackageMember foreignPackageMember = (ForeignPackageMember) setterMember; - final Member underlyingMember = foreignPackageMember.getMember(); - if ( underlyingMember instanceof Method setter ) { - type = setter.getParameterTypes()[0]; - } - else { - final Field field = (Field) underlyingMember; - type = field.getType(); - } - } - if ( type.isPrimitive() ) { - PrimitiveUnboxingDelegate.forReferenceType( TypeDescription.Generic.OBJECT ) - .assignUnboxedTo( - new TypeDescription.Generic.OfNonGenericType.ForLoadedType( type ), - ReferenceTypeAwareAssigner.INSTANCE, - Assigner.Typing.DYNAMIC - ) - .apply( methodVisitor, implementationContext ); - } - else { - methodVisitor.visitTypeInsn( - Opcodes.CHECKCAST, - Type.getInternalName( type ) - ); - } - if ( setterMember instanceof Method setter ) { - methodVisitor.visitMethodInsn( - setter.getDeclaringClass().isInterface() ? - Opcodes.INVOKEINTERFACE : - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( setter.getDeclaringClass() ), - setter.getName(), - Type.getMethodDescriptor( setter ), - setter.getDeclaringClass().isInterface() - ); - if ( setter.getReturnType() != void.class ) { - // Setters could return something which we have to ignore - switch ( setter.getReturnType().getTypeName() ) { - case "long": - case "double": - methodVisitor.visitInsn( Opcodes.POP2 ); - break; - default: - methodVisitor.visitInsn( Opcodes.POP ); - break; - } - } - } - else if ( setterMember instanceof Field field ) { - methodVisitor.visitFieldInsn( - Opcodes.PUTFIELD, - Type.getInternalName( field.getDeclaringClass() ), - field.getName(), - Type.getDescriptor( type ) - ); - } - else { - final ForeignPackageMember foreignPackageMember = (ForeignPackageMember) setterMember; - methodVisitor.visitMethodInsn( - Opcodes.INVOKESTATIC, - Type.getInternalName( foreignPackageMember.getForeignPackageAccessor() ), - "set_" + setterMember.getName(), - Type.getMethodDescriptor( - Type.getType( void.class ), - Type.getType( foreignPackageMember.getMember().getDeclaringClass() ), - Type.getType( type ) - ), - false - ); - } - if ( enhanced ) { - final boolean compositeTracker = CompositeTracker.class.isAssignableFrom( type ); - boolean alreadyHasFrame = false; - // The composite owner check and setting only makes sense if - // * the value type is a composite tracker - // * a value subtype can be a composite tracker - // - // Final classes that don't already implement the interface never need to be checked. - // This helps a bit with common final types which otherwise would have to be checked a lot. - if ( compositeOwner && ( compositeTracker || !Modifier.isFinal( type.getModifiers() ) ) ) { - // Push values array on stack - methodVisitor.visitVarInsn( Opcodes.ALOAD, 2 ); - methodVisitor.visitLdcInsn( index ); - // Load value for property from array - methodVisitor.visitInsn( Opcodes.AALOAD ); - - // Check if value implements composite tracker - methodVisitor.visitInsn( Opcodes.DUP ); - final Label compositeTrackerFalseLabel = new Label(); - final String compositeTrackerType; - final boolean isInterface; - if ( compositeTracker ) { - // If the known type already implements that interface, we use that type, - // so we just do a null check - compositeTrackerType = Type.getInternalName( type ); - isInterface = false; - methodVisitor.visitJumpInsn( Opcodes.IFNULL, compositeTrackerFalseLabel ); - } - else { - // If we don't know for sure, we do an instanceof check - methodVisitor.visitTypeInsn( - Opcodes.INSTANCEOF, - compositeTrackerType = Type.getInternalName( CompositeTracker.class ) - ); - isInterface = true; - methodVisitor.visitJumpInsn( Opcodes.IFEQ, compositeTrackerFalseLabel ); - } - - // Load the tracker on which we will call $$_hibernate_setOwner - methodVisitor.visitTypeInsn( - Opcodes.CHECKCAST, - compositeTrackerType - ); - methodVisitor.visitLdcInsn( propertyNames[index] ); - // Load the owner and cast it to the owner class, as we know it implements CompositeOwner - methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) ); - // Invoke the method to set the owner - methodVisitor.visitMethodInsn( - isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, - compositeTrackerType, - "$$_hibernate_setOwner", - Type.getMethodDescriptor( - Type.getType( void.class ), - Type.getType( String.class ), - Type.getType( CompositeOwner.class ) - ), - isInterface - ); - - // Skip the cleanup - final Label compositeTrackerEndLabel = new Label(); - methodVisitor.visitJumpInsn( Opcodes.GOTO, compositeTrackerEndLabel ); - - // Here is the cleanup section for the false branch - methodVisitor.visitLabel( compositeTrackerFalseLabel ); - // We still have the duplicated value on the stack - implementationContext.getFrameGeneration().full( - methodVisitor, - Arrays.asList( - TypeDescription.ForLoadedType.of( Object.class ) - ), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ), - TypeDescription.ForLoadedType.of( Object[].class ) - ) - ); - // Pop that duplicated property value from the stack - methodVisitor.visitInsn( Opcodes.POP ); - - // Clean stack after the if block - methodVisitor.visitLabel( compositeTrackerEndLabel ); - implementationContext.getFrameGeneration().same(methodVisitor, instrumentedMethod.getParameters().asTypeList()); - alreadyHasFrame = true; - } - if ( persistentAttributeInterceptable ) { - // Load the owner - methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, Type.getInternalName( clazz ) ); - // Extract the interceptor - methodVisitor.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - Type.getInternalName( clazz ), - "$$_hibernate_getInterceptor", - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), - false - ); - // Duplicate the interceptor on the stack and check if it implements BytecodeLazyAttributeInterceptor - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitTypeInsn( - Opcodes.INSTANCEOF, - Type.getInternalName( BytecodeLazyAttributeInterceptor.class ) - ); - - // Jump to the false label if the instanceof check fails - final Label instanceofFalseLabel = new Label(); - methodVisitor.visitJumpInsn( Opcodes.IFEQ, instanceofFalseLabel ); - - // Cast to the subtype, so we can mark the property as initialized - methodVisitor.visitTypeInsn( - Opcodes.CHECKCAST, - Type.getInternalName( BytecodeLazyAttributeInterceptor.class ) - ); - // Load the property name - methodVisitor.visitLdcInsn( propertyNames[index] ); - // Invoke the method to mark the property as initialized - methodVisitor.visitMethodInsn( - Opcodes.INVOKEINTERFACE, - Type.getInternalName( BytecodeLazyAttributeInterceptor.class ), - "attributeInitialized", - Type.getMethodDescriptor( Type.getType( void.class ), Type.getType( String.class ) ), - true - ); - - // Skip the cleanup - final Label instanceofEndLabel = new Label(); - methodVisitor.visitJumpInsn( Opcodes.GOTO, instanceofEndLabel ); - - // Here is the cleanup section for the false branch - methodVisitor.visitLabel( instanceofFalseLabel ); - // We still have the duplicated interceptor on the stack - implementationContext.getFrameGeneration().full( - methodVisitor, - Arrays.asList( - TypeDescription.ForLoadedType.of( PersistentAttributeInterceptor.class ) - ), - Arrays.asList( - implementationContext.getInstrumentedType(), - TypeDescription.ForLoadedType.of( Object.class ), - TypeDescription.ForLoadedType.of( Object[].class ) - ) - ); - // Pop that duplicated interceptor from the stack - methodVisitor.visitInsn( Opcodes.POP ); - - // Clean stack after the if block - methodVisitor.visitLabel( instanceofEndLabel ); - implementationContext.getFrameGeneration().same(methodVisitor, instrumentedMethod.getParameters().asTypeList()); - alreadyHasFrame = true; - } - - if ( alreadyHasFrame ) { - // Usually, the currentLabel is visited as well generating a frame, - // but if a frame was already generated, only visit the label here, - // otherwise two frames for the same bytecode index are generated, - // which is wrong and will produce an error when the JDK ClassFile API is used - methodVisitor.visitLabel( nextLabel ); - currentLabel = null; - } - else { - currentLabel = nextLabel; - } - nextLabel = new Label(); - } - } - if ( currentLabel != null ) { - methodVisitor.visitLabel( currentLabel ); - implementationContext.getFrameGeneration().same(methodVisitor, instrumentedMethod.getParameters().asTypeList()); - } - methodVisitor.visitInsn( Opcodes.RETURN ); - return new Size( 4, instrumentedMethod.getStackSize() ); - } - } - private static void findAccessors( Class clazz, String[] getterNames, @@ -1209,7 +404,6 @@ private static void findAccessors( } final Class[] getParam = new Class[0]; - final Class[] setParam = new Class[1]; for ( int i = 0; i < length; i++ ) { if ( getterNames[i] != null ) { final Method getter = findAccessor( clazz, getterNames[i], getParam ); @@ -1353,32 +547,6 @@ private static Constructor findConstructor(Class clazz) { } } - public static class GetPropertyNames implements ByteCodeAppender { - - private final String[] propertyNames; - - private GetPropertyNames(String[] propertyNames) { - this.propertyNames = propertyNames; - } - - @Override - public Size apply( - MethodVisitor methodVisitor, - Implementation.Context implementationContext, - MethodDescription instrumentedMethod) { - methodVisitor.visitLdcInsn( propertyNames.length ); - methodVisitor.visitTypeInsn( Opcodes.ANEWARRAY, Type.getInternalName( String.class ) ); - for ( int i = 0; i < propertyNames.length; i++ ) { - methodVisitor.visitInsn( Opcodes.DUP ); - methodVisitor.visitLdcInsn( i ); - methodVisitor.visitLdcInsn( propertyNames[i] ); - methodVisitor.visitInsn( Opcodes.AASTORE ); - } - methodVisitor.visitInsn( Opcodes.ARETURN ); - return new Size( 4, instrumentedMethod.getStackSize() + 1 ); - } - } - @Override public @Nullable Enhancer getEnhancer(EnhancementContext enhancementContext) { return new EnhancerImpl( enhancementContext, byteBuddyState ); diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java index 8d3767b2c1f4..a9374ff230c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -7,14 +7,12 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.Locale; -import java.util.Set; import java.util.function.BiFunction; import java.util.function.Function; import org.hibernate.HibernateException; +import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImplConstants; import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState; import org.hibernate.engine.spi.PrimeAmongSecondarySupertypes; import org.hibernate.internal.util.ReflectHelper; @@ -23,7 +21,6 @@ import net.bytebuddy.ByteBuddy; import net.bytebuddy.NamingStrategy; -import net.bytebuddy.description.modifier.Visibility; import net.bytebuddy.description.type.TypeDefinition; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.description.type.TypeList; @@ -38,21 +35,17 @@ public class ByteBuddyProxyHelper implements Serializable { private static final TypeDescription OBJECT = TypeDescription.ForLoadedType.of(Object.class); private final ByteBuddyState byteBuddyState; + private final EnhancerImplConstants constants; public ByteBuddyProxyHelper(ByteBuddyState byteBuddyState) { this.byteBuddyState = byteBuddyState; + this.constants = byteBuddyState.getEnhancerConstants(); } @SuppressWarnings("rawtypes") public Class buildProxy( final Class persistentClass, final Class[] interfaces) { - Set> key = new HashSet<>(); - if ( interfaces.length == 1 ) { - key.add( persistentClass ); - } - Collections.addAll( key, interfaces ); - final String proxyClassName = persistentClass.getTypeName() + "$" + PROXY_NAMING_SUFFIX; return byteBuddyState.loadProxy( persistentClass, proxyClassName, proxyBuilder( TypeDescription.ForLoadedType.of( persistentClass ), new TypeList.Generic.ForLoadedTypes( interfaces ) ) ); @@ -96,8 +89,8 @@ private BiFunction> proxyBuild .intercept( helpers.getDelegateToInterceptorDispatcherMethodDelegation() ) .method( helpers.getProxyNonInterceptedMethodFilter() ) .intercept( SuperMethodCall.INSTANCE ) - .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE ) - .implement( ProxyConfiguration.class ) + .defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, constants.modifierPRIVATE ) + .implement( constants.INTERFACES_for_ProxyConfiguration ) .intercept( helpers.getInterceptorFieldAccessor() ) ); }