HHH-17418 Change field reader for private final fields

This commit is contained in:
marko-bekhta 2023-11-10 18:39:35 +01:00 committed by Christian Beikov
parent ef4609baad
commit 638e8b857a
2 changed files with 131 additions and 48 deletions

View File

@ -54,55 +54,64 @@ abstract class FieldReaderAppender implements ByteCodeAppender {
TypeDescription dispatcherType = persistentFieldAsDefined.getType().isPrimitive()
? persistentFieldAsDefined.getType().asErasure()
: TypeDescription.OBJECT;
// if ( this.$$_hibernate_getInterceptor() != null )
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
managedCtClass.getInternalName(),
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
false
);
Label skip = new Label();
methodVisitor.visitJumpInsn( Opcodes.IFNULL, skip );
// this (for field write)
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
// this.$$_hibernate_getInterceptor();
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
managedCtClass.getInternalName(),
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
false
);
// .readXXX( self, fieldName, field );
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
methodVisitor.visitLdcInsn( persistentFieldAsDefined.getName() );
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
fieldRead( methodVisitor );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
Type.getInternalName( PersistentAttributeInterceptor.class ),
"read" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ),
Type.getMethodDescriptor(
Type.getType( dispatcherType.getDescriptor() ),
Type.getType( Object.class ),
Type.getType( String.class ),
Type.getType( dispatcherType.getDescriptor() )
),
true
);
// field = (cast) XXX
if ( !dispatcherType.isPrimitive() ) {
methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() );
}
fieldWrite( methodVisitor );
// end if
methodVisitor.visitLabel( skip );
if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) {
methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null );
// From `PersistentAttributeTransformer`:
// Final fields will only be written to from the constructor,
// so there's no point trying to replace final field writes with a method call.
// as a result if a field is final then there will be no write method, and we don't want to have this block:
if ( !persistentField.asDefined().isFinal() ) {
// if ( this.$$_hibernate_getInterceptor() != null )
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
managedCtClass.getInternalName(),
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
false
);
Label skip = new Label();
methodVisitor.visitJumpInsn( Opcodes.IFNULL, skip );
// this (for field write)
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
// this.$$_hibernate_getInterceptor();
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEVIRTUAL,
managedCtClass.getInternalName(),
EnhancerConstants.INTERCEPTOR_GETTER_NAME,
Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ),
false
);
// .readXXX( self, fieldName, field );
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
methodVisitor.visitLdcInsn( persistentFieldAsDefined.getName() );
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
fieldRead( methodVisitor );
methodVisitor.visitMethodInsn(
Opcodes.INVOKEINTERFACE,
Type.getInternalName( PersistentAttributeInterceptor.class ),
"read" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ),
Type.getMethodDescriptor(
Type.getType( dispatcherType.getDescriptor() ),
Type.getType( Object.class ),
Type.getType( String.class ),
Type.getType( dispatcherType.getDescriptor() )
),
true
);
// field = (cast) XXX
if ( !dispatcherType.isPrimitive() ) {
methodVisitor.visitTypeInsn(
Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() );
}
fieldWrite( methodVisitor );
// end if
methodVisitor.visitLabel( skip );
if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) {
methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null );
}
}
// return field
methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 );
fieldRead( methodVisitor );

View File

@ -0,0 +1,74 @@
package org.hibernate.orm.test.bytecode.enhancement.superclass;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import java.time.LocalDateTime;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.hibernate.testing.orm.junit.JiraKey;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
@JiraKey("HHH-17418")
@RunWith(BytecodeEnhancerRunner.class)
public class MappedSuperclassTest extends BaseCoreFunctionalTestCase {
private static final LocalDateTime TEST_DATE_UPDATED_VALUE = LocalDateTime.of( 2023, 11, 10, 0, 0 );
private static final long TEST_ID = 1L;
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { MyEntity.class };
}
@Before
public void prepare() {
doInHibernate( this::sessionFactory, s -> {
MyEntity testEntity = new MyEntity();
testEntity.id = TEST_ID;
s.persist( testEntity );
} );
}
@Test
public void test() {
doInHibernate( this::sessionFactory, s -> {
MyEntity testEntity = s.get( MyEntity.class, TEST_ID );
assertThat( testEntity.value() ).isEqualTo( TEST_DATE_UPDATED_VALUE );
} );
}
@After
public void cleanup() {
doInHibernate( this::sessionFactory, s -> {
MyEntity testEntity = s.get( MyEntity.class, TEST_ID );
s.remove( testEntity );
} );
}
@MappedSuperclass
public static class MappedBase {
// field is private on purpose so that enhancer will not use field access
@Column
private final LocalDateTime updated = TEST_DATE_UPDATED_VALUE;
public LocalDateTime value() {
return updated;
}
}
@Entity
public static class MyEntity extends MappedBase {
@Id
Long id;
}
}