From 3e37279db66d6139bcb2851143326dd23dc6afde Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Wed, 1 Oct 2025 19:14:14 +0200 Subject: [PATCH 1/2] Make CGLIB Enhancer compatible with Kotlin 2.2.20+ This commit refines Enhancer#emitMethods to support the changes introduced by https://youtrack.jetbrains.com/issue/KT-76667. See gh-35487 --- .../springframework/cglib/proxy/Enhancer.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java index fc655f244ad..fcd0353caff 100644 --- a/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java +++ b/spring-core/src/main/java/org/springframework/cglib/proxy/Enhancer.java @@ -1278,29 +1278,32 @@ public class Enhancer extends AbstractClassGenerator { Signature bridgeTarget = (Signature) bridgeToTarget.get(method.getSignature()); if (bridgeTarget != null) { // checkcast each argument against the target's argument types - for (int i = 0; i < bridgeTarget.getArgumentTypes().length; i++) { + Type[] argTypes = method.getSignature().getArgumentTypes(); + Type[] targetTypes = bridgeTarget.getArgumentTypes(); + for (int i = 0; i < targetTypes.length; i++) { e.load_arg(i); - Type target = bridgeTarget.getArgumentTypes()[i]; - if (!target.equals(method.getSignature().getArgumentTypes()[i])) { + Type argType = argTypes[i]; + Type target = targetTypes[i]; + if (!target.equals(argType)) { + if (!TypeUtils.isPrimitive(target)) { + e.box(argType); + } e.checkcast(target); } } e.invoke_virtual_this(bridgeTarget); + // Not necessary to cast if the target & bridge have the same return type. Type retType = method.getSignature().getReturnType(); - // Not necessary to cast if the target & bridge have - // the same return type. - // (This conveniently includes void and primitive types, - // which would fail if casted. It's not possible to - // covariant from boxed to unbox (or vice versa), so no having - // to box/unbox for bridges). - // TODO: It also isn't necessary to checkcast if the return is - // assignable from the target. (This would happen if a subclass - // used covariant returns to narrow the return type within a bridge - // method.) - if (!retType.equals(bridgeTarget.getReturnType())) { - e.checkcast(retType); + Type target = bridgeTarget.getReturnType(); + if (!target.equals(retType)) { + if (!TypeUtils.isPrimitive(target)) { + e.unbox(retType); + } + else { + e.checkcast(retType); + } } } else { From cb849a7071d39e6e474bfd977db71b445ae702c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Wed, 1 Oct 2025 19:21:40 +0200 Subject: [PATCH 2/2] Add a test for CGLIB Enhancer Kotlin refinements This commit adds a reproducer for the change of behavior introduced via https://youtrack.jetbrains.com/issue/KT-76667. The test is only broken with Kotlin 2.2.20+ without the related fix (see previous commit). Closes gh-35487 --- .../aop/framework/CglibAopProxyKotlinTests.kt | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spring-aop/src/test/kotlin/org/springframework/aop/framework/CglibAopProxyKotlinTests.kt b/spring-aop/src/test/kotlin/org/springframework/aop/framework/CglibAopProxyKotlinTests.kt index 10e632abd39..0489d68bb0c 100644 --- a/spring-aop/src/test/kotlin/org/springframework/aop/framework/CglibAopProxyKotlinTests.kt +++ b/spring-aop/src/test/kotlin/org/springframework/aop/framework/CglibAopProxyKotlinTests.kt @@ -19,6 +19,7 @@ package org.springframework.aop.framework import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.Test +import java.time.LocalDateTime /** * Tests for Kotlin support in [CglibAopProxy]. @@ -48,6 +49,13 @@ class CglibAopProxyKotlinTests { assertThatThrownBy { proxy.checkedException() }.isInstanceOf(CheckedException::class.java) } + @Test // gh-35487 + fun jvmDefault() { + val proxyFactory = ProxyFactory() + proxyFactory.setTarget(AddressRepo()) + proxyFactory.proxy + } + open class MyKotlinBean { @@ -63,4 +71,24 @@ class CglibAopProxyKotlinTests { } class CheckedException() : Exception() + + open class AddressRepo(): CrudRepo + + interface CrudRepo { + fun save(e: E): E { + return e + } + fun delete(id: ID): Long { + return 0L + } + } + + data class Address( + val id: Int = 0, + val street: String, + val version: Int = 0, + val createdAt: LocalDateTime? = null, + val updatedAt: LocalDateTime? = null, + ) + }