Browse Source

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 <Alexander@Kriegisch.name>
tags/V1_9_20_1
Alexander Kriegisch 9 months ago
parent
commit
e2a355b379

+ 59
- 0
tests/bugs1921/gh_spring_27761/JpaRepositoryDump.java View File

@@ -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();
}
}

BIN
tests/bugs1921/gh_spring_27761/JpaRepository_bridge_first.jar View File


+ 35
- 0
tests/bugs1921/gh_spring_27761/RepositoryAspect.aj View File

@@ -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;
}
}

+ 12
- 0
tests/src/test/java/org/aspectj/systemtest/ajc1920/Bugs1920Tests.java View 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);
}

+ 11
- 0
tests/src/test/resources/org/aspectj/systemtest/ajc1920/ajc1920.xml View File

@@ -418,4 +418,15 @@
</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>

Loading…
Cancel
Save