]> source.dussan.org Git - aspectj.git/commitdiff
Regression test for Spring issue 27761
authorAlexander Kriegisch <Alexander@Kriegisch.name>
Mon, 21 Aug 2023 10:18:27 +0000 (17:18 +0700)
committerAlexander Kriegisch <Alexander@Kriegisch.name>
Mon, 21 Aug 2023 10:58:47 +0000 (17:58 +0700)
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 <Alexander@Kriegisch.name>
tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java [new file with mode: 0644]
tests/bugs1921/gh_spring_27761/JpaRepository_bridge_first.jar [new file with mode: 0644]
tests/bugs1921/gh_spring_27761/RepositoryAspect.aj [new file with mode: 0644]
tests/src/test/java/org/aspectj/systemtest/ajc1920/Bugs1920Tests.java
tests/src/test/resources/org/aspectj/systemtest/ajc1920/ajc1920.xml

diff --git a/tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java b/tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java
new file mode 100644 (file)
index 0000000..a007abe
--- /dev/null
@@ -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<T> extends CrudRepository<T> {
+      @Override
+      <S extends T> List<S> saveAll(Iterable<S> 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", "<T:Ljava/lang/Object;>Ljava/lang/Object;LCrudRepository<TT;>;", "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;", "<S:TT;>(Ljava/lang/Iterable<TS;>;)Ljava/util/List<TS;>;", 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 (file)
index 0000000..47aa5c0
Binary files /dev/null and b/tests/bugs1921/gh_spring_27761/JpaRepository_bridge_first.jar differ
diff --git a/tests/bugs1921/gh_spring_27761/RepositoryAspect.aj b/tests/bugs1921/gh_spring_27761/RepositoryAspect.aj
new file mode 100644 (file)
index 0000000..b938d75
--- /dev/null
@@ -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<String>().saveAll(Arrays.asList("One", "Two", "Three"));
+  }
+}
+
+interface CrudRepository<T> {
+  <S extends T> Iterable<S> saveAll(Iterable<S> entities);
+}
+
+/*
+interface JpaRepository<T> extends CrudRepository<T> {
+  @Override
+  <S extends T> List<S> saveAll(Iterable<S> entities);
+}
+*/
+
+class RepositoryImpl<S> implements JpaRepository<String> {
+  @Override
+  public <S extends String> List<S> saveAll(Iterable<S> entities) {
+    List<S> entityList = new ArrayList<>();
+    entities.iterator().forEachRemaining(entityList::add);
+    System.out.println("Saving " + entityList);
+    return entityList;
+  }
+}
index 574f1b7aac4784028b103d8d506d58dfef3d8ebe..e1f3fa1ca3f880a55574200d01130c502f32fe66 100644 (file)
@@ -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.
+   * <p>
+   * See <a href="https://github.com/spring-projects/spring-framework/issues/27761">Spring GitHub issue 27761</a>.
+   * <p>
+   * This test uses an ASM-modified class file reproducing the problem seen in Spring in plain AspectJ. Before the
+   * bugfix, it fails with <b>"advice defined in RepositoryAspect has not been applied [Xlint:adviceDidNotMatch]".</b>
+   */
+  public void test_Spring_GitHub_27761() {
+    runTest("do not match bridge methods");
+  }
+
   public static Test suite() {
     return XMLBasedAjcTestCase.loadSuite(Bugs1920Tests.class);
   }
index 402f50a2fe906b4981ec89a035057401139106a6..db47e073d88792b0383e0e091ea28c81b78b5178 100644 (file)
                </run>
        </ajc-test>
 
+       <!-- https://github.com/spring-projects/spring-framework/issues/27761 -->
+       <ajc-test dir="bugs1921/gh_spring_27761" vm="8" title="do not match bridge methods">
+               <compile files="RepositoryAspect.aj" inpath="JpaRepository_bridge_first.jar" options="-8"/>
+               <run class="RepositoryAspect">
+                       <stdout>
+                               <line text="execution(List RepositoryImpl.saveAll(Iterable))"/>
+                               <line text="Saving [One, Two, Three]"/>
+                       </stdout>
+               </run>
+       </ajc-test>
+
 </suite>