Skip to content

Commit 03a4bee

Browse files
committed
HHH-16572 Incorrect enhancement for PROPERTY attributes with mismatched field and method names
Signed-off-by: Scott Marlow <[email protected]>
1 parent 6584eb3 commit 03a4bee

File tree

1 file changed

+98
-1
lines changed
  • hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy

1 file changed

+98
-1
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@
1111
import java.util.ArrayList;
1212
import java.util.Collection;
1313
import java.util.Collections;
14+
import java.util.HashSet;
1415
import java.util.List;
1516
import java.util.Map;
1617
import java.util.Objects;
1718
import java.util.Optional;
19+
import java.util.Set;
1820
import java.util.function.Supplier;
1921

22+
import net.bytebuddy.description.type.TypeList;
23+
import net.bytebuddy.dynamic.scaffold.MethodGraph;
2024
import org.hibernate.Version;
2125
import org.hibernate.bytecode.enhance.VersionMismatchException;
2226
import org.hibernate.bytecode.enhance.internal.tracker.CompositeOwnerTracker;
@@ -172,6 +176,12 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
172176
}
173177

174178
if ( enhancementContext.isEntityClass( managedCtClass ) ) {
179+
180+
// Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
181+
if ( !allowedEnhancementCheck( managedCtClass ) ) {
182+
return null;
183+
}
184+
175185
log.debugf( "Enhancing [%s] as Entity", managedCtClass.getName() );
176186
DynamicType.Builder<?> builder = builderSupplier.get();
177187
builder = builder.implement( ManagedEntity.class )
@@ -331,6 +341,12 @@ private DynamicType.Builder<?> doEnhance(Supplier<DynamicType.Builder<?>> builde
331341
return createTransformer( managedCtClass ).applyTo( builder );
332342
}
333343
else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
344+
345+
// Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
346+
if ( !allowedEnhancementCheck( managedCtClass ) ) {
347+
return null;
348+
}
349+
334350
log.debugf( "Enhancing [%s] as Composite", managedCtClass.getName() );
335351

336352
DynamicType.Builder<?> builder = builderSupplier.get();
@@ -364,6 +380,12 @@ else if ( enhancementContext.isCompositeClass( managedCtClass ) ) {
364380
return createTransformer( managedCtClass ).applyTo( builder );
365381
}
366382
else if ( enhancementContext.isMappedSuperclassClass( managedCtClass ) ) {
383+
384+
// Check for HHH-16572 (PROPERTY attributes with mismatched field and method names)
385+
if ( !allowedEnhancementCheck( managedCtClass ) ) {
386+
return null;
387+
}
388+
367389
log.debugf( "Enhancing [%s] as MappedSuperclass", managedCtClass.getName() );
368390

369391
DynamicType.Builder<?> builder = builderSupplier.get();
@@ -380,6 +402,81 @@ else if ( enhancementContext.doExtendedEnhancement( managedCtClass ) ) {
380402
}
381403
}
382404

405+
// See HHH-16572
406+
// return true if enhancement is supported
407+
private boolean allowedEnhancementCheck(TypeDescription managedCtClass) {
408+
// For process access rules, See https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#default-access-type
409+
// and https://jakarta.ee/specifications/persistence/3.2/jakarta-persistence-spec-3.2#a122
410+
//
411+
// This check will determine if entity field names do not match Property accessor method name
412+
// For example:
413+
// @Entity
414+
// class Book {
415+
// Integer id;
416+
// String smtg;
417+
//
418+
// @Id Integer getId() { return id; }
419+
// String getSomething() { return smtg; }
420+
// }
421+
//
422+
// Check name of the getter/setter method with Peristence annotation and getter/setter method name that doesn't refer to an entity field
423+
// and will return false. If the property accessor method(s) are named to match the field name(s), return true.
424+
boolean propertyHasAnnotation = false;
425+
MethodGraph.Linked methodGraph = MethodGraph.Compiler.Default.forJavaHierarchy().compile((TypeDefinition)managedCtClass);
426+
for (MethodGraph.Node node : methodGraph.listNodes()) {
427+
MethodDescription methodDescription = node.getRepresentative();
428+
if (methodDescription.getDeclaringType().represents(Object.class)) { // skip class java.lang.Object methods
429+
continue;
430+
}
431+
432+
String methodName = methodDescription.getActualName();
433+
if (methodName.equals("") ||
434+
(!methodName.startsWith("get") && !methodName.startsWith("set") && !methodName.startsWith("is"))) {
435+
continue;
436+
}
437+
String methodFieldName;
438+
if (methodName.startsWith("is")) { // skip past "is"
439+
methodFieldName = methodName.substring(2);
440+
} else if (methodName.startsWith("get") || methodName.startsWith("set")){ // skip past "get" or "set"
441+
methodFieldName = methodName.substring(3);
442+
} else {
443+
// not a property accessor method so ignore it
444+
continue;
445+
}
446+
boolean propertyNameMatchesFieldName=false;
447+
// convert field letter to lower case
448+
methodFieldName = methodFieldName.substring(0, 1).toLowerCase() + methodFieldName.substring(1);
449+
TypeList typeList = methodDescription.getDeclaredAnnotations().asTypeList();
450+
if ( typeList.stream().anyMatch(typeDefinitions ->
451+
(typeDefinitions.getName().contains("jakarta.persistence.Id") ||
452+
typeDefinitions.getName().contains("jakarta.persistence.Inheritance") ||
453+
typeDefinitions.getName().contains("jakarta.persistence.ManyToOne") ||
454+
typeDefinitions.getName().contains("jakarta.persistence.ManyToMany") ||
455+
typeDefinitions.getName().contains("jakarta.persistence.OneToMany") ||
456+
typeDefinitions.getName().contains("jakarta.persistence.OneToOne")))) {
457+
propertyHasAnnotation = true;
458+
}
459+
for (FieldDescription ctField : methodDescription.getDeclaringType().getDeclaredFields()) {
460+
if (!Modifier.isStatic(ctField.getModifiers())) {
461+
AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription(enhancementContext, ctField);
462+
boolean containsPropertyAccessorMethods = false;
463+
if (enhancementContext.isPersistentField(annotatedField)) {
464+
if (methodFieldName.equals(ctField.getActualName())) {
465+
propertyNameMatchesFieldName = true;
466+
break;
467+
}
468+
}
469+
}
470+
}
471+
if ( propertyHasAnnotation && !propertyNameMatchesFieldName ) {
472+
log.debugf("Skipping enhancement of [%s]: due to class [%s] not having a property accessor method name matching field name [%s]",
473+
managedCtClass, methodDescription.getDeclaringType().getActualName(), methodFieldName);
474+
return false;
475+
}
476+
}
477+
return true;
478+
}
479+
383480
private static void verifyVersions(TypeDescription managedCtClass, ByteBuddyEnhancementContext enhancementContext) {
384481
final AnnotationDescription.Loadable<EnhancementInfo> existingInfo = managedCtClass
385482
.getDeclaredAnnotations()
@@ -495,7 +592,7 @@ private Collection<AnnotatedFieldDescription> collectInheritCollectionFields(Typ
495592
AnnotatedFieldDescription annotatedField = new AnnotatedFieldDescription( enhancementContext, ctField );
496593
if ( enhancementContext.isPersistentField( annotatedField ) && enhancementContext.isMappedCollection( annotatedField ) ) {
497594
if ( ctField.getType().asErasure().isAssignableTo( Collection.class ) || ctField.getType().asErasure().isAssignableTo( Map.class ) ) {
498-
collectionList.add( annotatedField );
595+
collectionList.add( annotatedField );
499596
}
500597
}
501598
}

0 commit comments

Comments
 (0)