From e2a355b37935faf9a8de1ce7ca1f134b5a9df891 Mon Sep 17 00:00:00 2001 From: Alexander Kriegisch Date: Mon, 21 Aug 2023 17:18:27 +0700 Subject: [PATCH] Regression test for Spring issue 27761 Relates to spring-projects/spring-framework#27761. The test needs an ASM-generated class file with reordered methods in order to reproduce the issue in plain AspectJ. The test fails now, but should pass after the fix. Signed-off-by: Alexander Kriegisch --- .../gh_spring_27761/JpaRepositoryDump.java | 59 ++++++++++++++++++ .../JpaRepository_bridge_first.jar | Bin 0 -> 456 bytes .../gh_spring_27761/RepositoryAspect.aj | 35 +++++++++++ .../systemtest/ajc1920/Bugs1920Tests.java | 12 ++++ .../aspectj/systemtest/ajc1920/ajc1920.xml | 11 ++++ 5 files changed, 117 insertions(+) create mode 100644 tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java create mode 100644 tests/bugs1921/gh_spring_27761/JpaRepository_bridge_first.jar create mode 100644 tests/bugs1921/gh_spring_27761/RepositoryAspect.aj diff --git a/tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java b/tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java new file mode 100644 index 000000000..a007abe96 --- /dev/null +++ b/tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java @@ -0,0 +1,59 @@ +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.FileOutputStream; +import java.io.IOException; + +class JpaRepositoryDump implements Opcodes { + public static void main(String[] args) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream("JpaRepository.class")) { + outputStream.write(dump()); + } + } + + public static byte[] dump() { + /* + Write out a class corresponding to this source code: + ------------------------------------------------------------ + interface JpaRepository extends CrudRepository { + @Override + List saveAll(Iterable entities); + } + ------------------------------------------------------------ + The only difference to the original class created by Javac or Ajc is that the bridge method is written to the class + file first, then the overriding method with the return type narrowed from Iterable to List. This has the effect of + org.aspectj.weaver.ResolvedType.getMethodsIncludingIntertypeDeclarations also finding the bridge method first, + which helps to reproduce https://github.com/spring-projects/spring-framework/issues/27761 in a regression test. + + The resulting class file can be found in .../gh_spring_27761/JpaRepository_bridge_first.jar and is used during test + "do not match bridge methods". + */ + + ClassWriter classWriter = new ClassWriter(0); + classWriter.visit(V1_8, ACC_ABSTRACT | ACC_INTERFACE, "JpaRepository", "Ljava/lang/Object;LCrudRepository;", "java/lang/Object", new String[]{"CrudRepository"}); + classWriter.visitSource("RepositoryAspect.aj", null); + + MethodVisitor methodVisitor; + methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC, "saveAll", "(Ljava/lang/Iterable;)Ljava/lang/Iterable;", null, null); + methodVisitor.visitCode(); + Label label0 = new Label(); + methodVisitor.visitLabel(label0); + methodVisitor.visitLineNumber(1, label0); + methodVisitor.visitVarInsn(ALOAD, 0); + methodVisitor.visitVarInsn(ALOAD, 1); + methodVisitor.visitTypeInsn(CHECKCAST, "java/lang/Iterable"); + methodVisitor.visitMethodInsn(INVOKEINTERFACE, "JpaRepository", "saveAll", "(Ljava/lang/Iterable;)Ljava/util/List;", true); + methodVisitor.visitInsn(ARETURN); + methodVisitor.visitMaxs(2, 2); + methodVisitor.visitEnd(); + + methodVisitor = classWriter.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "saveAll", "(Ljava/lang/Iterable;)Ljava/util/List;", "(Ljava/lang/Iterable;)Ljava/util/List;", null); + methodVisitor.visitEnd(); + + classWriter.visitEnd(); + + return classWriter.toByteArray(); + } +} diff --git a/tests/bugs1921/gh_spring_27761/JpaRepository_bridge_first.jar b/tests/bugs1921/gh_spring_27761/JpaRepository_bridge_first.jar new file mode 100644 index 0000000000000000000000000000000000000000..47aa5c05332b6bec84f71c4bf136687a8b34e3f1 GIT binary patch literal 456 zcmWIWW@Zs#U|`^2(997HKVAC#hYTYF!y6zL28wzWBnG7x%WM^%$)D|)eGd|` z@o6dD*s5}SvDs?Jbc@?xJyjPU`LZY|sBOab1EO!f@8f>Q+;pdg?C zCb0%C7gUmwL4x5zL37;C%QqR_OD=FST)D}(wXqt^5AbGX1DU`GgsDK<7Z_Iz3;>i2 Bw(kG{ literal 0 HcmV?d00001 diff --git a/tests/bugs1921/gh_spring_27761/RepositoryAspect.aj b/tests/bugs1921/gh_spring_27761/RepositoryAspect.aj new file mode 100644 index 000000000..b938d754c --- /dev/null +++ b/tests/bugs1921/gh_spring_27761/RepositoryAspect.aj @@ -0,0 +1,35 @@ +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public aspect RepositoryAspect { + Object around(): execution(* JpaRepository.save*(..)) { + System.out.println(thisJoinPoint); + return proceed(); + } + + public static void main(String[] args) { + new RepositoryImpl().saveAll(Arrays.asList("One", "Two", "Three")); + } +} + +interface CrudRepository { + Iterable saveAll(Iterable entities); +} + +/* +interface JpaRepository extends CrudRepository { + @Override + List saveAll(Iterable entities); +} +*/ + +class RepositoryImpl implements JpaRepository { + @Override + public List saveAll(Iterable entities) { + List entityList = new ArrayList<>(); + entities.iterator().forEachRemaining(entityList::add); + System.out.println("Saving " + entityList); + return entityList; + } +} diff --git a/tests/src/test/java/org/aspectj/systemtest/ajc1920/Bugs1920Tests.java b/tests/src/test/java/org/aspectj/systemtest/ajc1920/Bugs1920Tests.java index 574f1b7aa..e1f3fa1ca 100644 --- a/tests/src/test/java/org/aspectj/systemtest/ajc1920/Bugs1920Tests.java +++ b/tests/src/test/java/org/aspectj/systemtest/ajc1920/Bugs1920Tests.java @@ -67,6 +67,18 @@ public class Bugs1920Tests extends XMLBasedAjcTestCase { runTest("correctly handle overloaded private methods in aspects"); } + /** + * If one generic method overrides another one with a narrower return type, avoid matching bridge methods. + *

+ * See Spring GitHub issue 27761. + *

+ * This test uses an ASM-modified class file reproducing the problem seen in Spring in plain AspectJ. Before the + * bugfix, it fails with "advice defined in RepositoryAspect has not been applied [Xlint:adviceDidNotMatch]". + */ + public void test_Spring_GitHub_27761() { + runTest("do not match bridge methods"); + } + public static Test suite() { return XMLBasedAjcTestCase.loadSuite(Bugs1920Tests.class); } diff --git a/tests/src/test/resources/org/aspectj/systemtest/ajc1920/ajc1920.xml b/tests/src/test/resources/org/aspectj/systemtest/ajc1920/ajc1920.xml index 402f50a2f..db47e073d 100644 --- a/tests/src/test/resources/org/aspectj/systemtest/ajc1920/ajc1920.xml +++ b/tests/src/test/resources/org/aspectj/systemtest/ajc1920/ajc1920.xml @@ -418,4 +418,15 @@ + + + + + + + + + + + -- 2.39.5