Merge pull request #234 from zcbbpo/FIXED-LAMBDA-MR

[Lambda] Fixed method reference external var
This commit is contained in:
Fan Lin 2021-10-31 14:19:02 +08:00 committed by GitHub
commit 397fc3d2fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 376 additions and 10 deletions

View File

@ -0,0 +1,243 @@
package com.alibaba.demo.lambda;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author jimca
*/
@SuppressWarnings({"WrapperTypeMayBePrimitive", "ResultOfMethodCallIgnored", "MismatchedReadAndWriteOfArray", "unused"})
public class ExternalLambdaDemo {
public void string1() {
String s = "";
consumesFunction2(s::contains);
}
public void string2() {
String s = "";
consumesFunction2(s::charAt);
}
public void string3() {
String s = "";
consumesFunction0(s::notify);
}
public void byte1() {
Byte s = 1;
consumesSupplier(s::floatValue);
}
public void byte2() {
Byte s = 1;
consumesFunction2(s::compareTo);
}
public void byte3() {
Byte s = 1;
consumesFunction0(s::notify);
}
public void char1() {
Character s = 1;
consumesFunction0(s::toString);
}
public void char2() {
Character s = 1;
consumesFunction2(s::compareTo);
}
public void char3() {
Character s = 1;
consumesFunction0(s::notify);
}
public void short1() {
Short s = 1;
consumesFunction0(s::toString);
}
public void short2() {
Short s = 1;
consumesFunction2(s::compareTo);
}
public void short3() {
Short s = 1;
consumesFunction0(s::notify);
}
public void int1() {
Integer s = 1;
consumesFunction0(s::toString);
}
public void int2() {
Integer s = 1;
consumesFunction2(s::compareTo);
}
public void int3() {
Integer s = 1;
consumesFunction0(s::notify);
}
public void long1() {
Long s = 1L;
consumesFunction0(s::toString);
}
public void long2() {
Long s = 1L;
consumesFunction2(s::compareTo);
}
public void long3() {
Long s = 1L;
consumesFunction0(s::notify);
}
public void float1() {
Float s = 1f;
consumesFunction0(s::toString);
}
public void float2() {
Float s = 1f;
consumesFunction2(s::compareTo);
}
public void float3() {
Float s = 1f;
consumesFunction0(s::notify);
}
public void double1() {
Double s = 1d;
consumesFunction0(s::toString);
}
public void double2() {
Double s = 1d;
consumesFunction2(s::compareTo);
}
public void double3() {
Double s = 1d;
consumesFunction0(s::notify);
}
public void bool1() {
Boolean s = true;
consumesFunction0(s::toString);
}
public void bool2() {
Boolean s = true;
consumesFunction2(s::compareTo);
}
public void bool3() {
Boolean s = true;
consumesFunction0(s::notify);
}
public void stringArray1() {
String[] array = new String[]{""};
consumesFunction0(array::toString);
}
public void stringArray2() {
String[] array = new String[]{""};
consumesFunction2(array::equals);
}
public void stringArray3() {
String[] array = new String[]{""};
consumesFunction0(array::notify);
}
public void intArray1() {
int[] array = new int[]{1};
consumesFunction0(array::toString);
}
public void intArray2() {
int[] array = new int[]{1};
consumesFunction2(array::equals);
}
public void intArray3() {
int[] array = new int[]{1};
consumesFunction0(array::notify);
}
public void mul() {
String s = "";
consumesTwoFunction2(s::contains, s::contains);
}
public void externalClass() {
LambdaDemo lambdaDemo = new LambdaDemo();
consumesFunction0(lambdaDemo::methodReference0);
}
public void interClass() {
A a = new A();
consumesFunction2(a::m1);
consumesFunction2(a::m2);
}
public void function3() {
ExternalLambdaDemo externalLambdaDemo = new ExternalLambdaDemo();
consumesFunction3(externalLambdaDemo::f3);
}
public Boolean f3(String s1, Long l) {
return false;
}
private void consumesFunction0(Runnable f) {
f.run();
}
private <T> void consumesFunction1(Consumer<T> f) {
f.accept(null);
}
private <T, R> void consumesFunction2(Function<T, R> f) {
f.apply(null);
}
private <T1, T2, R> void consumesFunction3(BiFunction<T1, T2, R> f) {
f.apply(null, null);
}
private <T> void consumesSupplier(Supplier<T> supplier) {
supplier.get();
}
private <T, R> void consumesTwoFunction2(Function<T, R> f1, Function<T, R> f2) {
f1.apply(null);
f2.apply(null);
}
public static class A {
public String m1(int i) {
return "";
}
public String m2(Integer i) {
return "";
}
}
}

View File

@ -0,0 +1,83 @@
package com.alibaba.demo.lambda;
import com.alibaba.testable.core.annotation.MockDiagnose;
import com.alibaba.testable.core.annotation.MockMethod;
import com.alibaba.testable.core.model.LogLevel;
import org.junit.jupiter.api.Test;
import static com.alibaba.testable.core.matcher.InvokeVerifier.verify;
/**
* @author zcbbpo
*/
public class ExternalLambdaDemoTest {
private final ExternalLambdaDemo lambdaDemo = new ExternalLambdaDemo();
@SuppressWarnings("unused")
@MockDiagnose(LogLevel.VERBOSE)
public static class Mock {
@MockMethod(targetClass = String.class, targetMethod = "contains")
public boolean mockContains(CharSequence s) {
return false;
}
@MockMethod(targetClass = Byte.class, targetMethod = "floatValue")
public float mockFloatValue() {
return 0.1f;
}
@MockMethod(targetClass = Double.class, targetMethod = "compareTo")
public int mockCompareTo(Double anotherDouble) {
return 1;
}
@MockMethod(targetClass = LambdaDemo.class, targetMethod = "methodReference0")
public String mockMethodReference0() {
return "";
}
@MockMethod(targetClass = ExternalLambdaDemo.class, targetMethod = "f3")
public Boolean mockF3(String s1, Long l) {
return true;
}
}
@Test
public void shouldMockString1() {
lambdaDemo.string1();
verify("mockContains").withTimes(1);
}
@Test
public void shouldMockByte1() {
lambdaDemo.byte1();
verify("mockFloatValue").withTimes(1);
}
@Test
public void shouldMockDouble2() {
lambdaDemo.double2();
verify("mockCompareTo").withTimes(1);
}
@Test
public void testMul() {
lambdaDemo.mul();
verify("mockContains").withTimes(2);
}
@Test
public void testExternalClass() {
lambdaDemo.externalClass();
verify("mockMethodReference0").withTimes(1);
}
@Test
public void testFunction3() {
lambdaDemo.function3();
verify("mockF3").withTimes(1);
}
}

View File

@ -12,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
* @author zcbbpo
*/
public class LambdaDemoTest {
private LambdaDemo lambdaDemo = new LambdaDemo();
private final LambdaDemo lambdaDemo = new LambdaDemo();
@SuppressWarnings("unused")
@MockDiagnose(LogLevel.VERBOSE)

View File

@ -369,6 +369,7 @@ public class SourceClassHandler extends BaseClassHandler {
// process for method reference
for (Handle handle : invokeDynamicList) {
// the jdk auto generation method
if (handle.getName().startsWith("lambda$")) {
continue;
}
@ -379,7 +380,15 @@ public class SourceClassHandler extends BaseClassHandler {
// lambda new method reference
continue;
}
boolean isStatic = tag == Opcodes.H_INVOKESTATIC;
// external mean:
// public void foo() {
// String s = "";
// consumes(s::contains);
//}
boolean external = tag == Opcodes.H_INVOKEVIRTUAL;
boolean isStatic = tag == Opcodes.H_INVOKESTATIC || external;
String desc = handle.getDesc();
String parameters = desc.substring(desc.indexOf("(") + 1, desc.lastIndexOf(")"));
@ -391,8 +400,15 @@ public class SourceClassHandler extends BaseClassHandler {
len--;
}
}
len = external ? len + 1 : len;
String[] refineParameterArray = new String[len];
int index = 0;
int index = external ? 1 : 0;
if (external) {
// The type should was reference type
refineParameterArray[0] = "L" + handle.getOwner();
}
for (String s : parameterArray) {
if (!s.isEmpty()) {
refineParameterArray[index] = s;
@ -400,14 +416,16 @@ public class SourceClassHandler extends BaseClassHandler {
}
}
String externalDesc = buildDesc(refineParameterArray, returnType);
String lambdaName = String.format("Lambda$_%s_%d", handle.getName(), atomicInteger.incrementAndGet());
MethodVisitor mv = cn.visitMethod(isStatic ? ACC_PUBLIC + ACC_STATIC : ACC_PUBLIC, lambdaName, desc, null, null);
MethodVisitor mv = cn.visitMethod(isStatic ? ACC_PUBLIC + ACC_STATIC : ACC_PUBLIC, lambdaName, external ? externalDesc : desc, null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
if (!isStatic) {
// add this
mv.visitVarInsn(ALOAD, 0);
}
for (int i = 0; i < refineParameterArray.length; i++) {
@ -415,19 +433,19 @@ public class SourceClassHandler extends BaseClassHandler {
mv.visitVarInsn(getLoadType(arg), isStatic ? i : i + 1);
}
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL/*INVOKESPECIAL*/, handle.getOwner(), handle.getName(), desc, false);
mv.visitMethodInsn(isStatic ? INVOKESTATIC : INVOKEVIRTUAL, handle.getOwner(), handle.getName(), desc, false);
mv.visitInsn(getReturnType(returnType));
Label l1 = new Label();
mv.visitLabel(l1);
String localVarOwner = handle.getOwner();
// static function was not required add this to first parameter
if (isStatic) {
for (int i = 0; i < refineParameterArray.length; i++) {
String localVar = refineParameterArray[i];
if (!isPrimitive(localVar)) {
// primitive type and reference type difference
localVar = localVar.endsWith(";") ? localVar : localVar + ";";
}
@ -435,10 +453,11 @@ public class SourceClassHandler extends BaseClassHandler {
continue;
}
// add local var
mv.visitLocalVariable(String.format("o%d", i), localVar, null, l0, l1, i);
}
} else {
mv.visitLocalVariable("this", "L" + localVarOwner + ";", null, l0, l1, 0);
mv.visitLocalVariable("this", "L" + handle.getOwner() + ";", null, l0, l1, 0);
for (int i = 0; i < refineParameterArray.length; i++) {
String localVar = refineParameterArray[i];
if (!isPrimitive(localVar) && !isPrimitiveArray(localVar)) {
@ -456,15 +475,36 @@ public class SourceClassHandler extends BaseClassHandler {
mv.visitEnd();
try {
// modify handle to the generation method
setFinalValue(handle.getClass().getDeclaredField("name"), handle, lambdaName);
// mark: should merge the below two if.
if (!handle.getOwner().equals(cn.name) && isStatic) {
setFinalValue(handle.getClass().getDeclaredField("owner"), handle, cn.name);
}
if (external) {
setFinalValue(handle.getClass().getDeclaredField("owner"), handle, cn.name);
setFinalValue(handle.getClass().getDeclaredField("descriptor"), handle, externalDesc);
setFinalValue(handle.getClass().getDeclaredField("tag"), handle, H_INVOKESTATIC);
}
} catch (Exception ignore) {
}
}
}
private String buildDesc(String[] refineParameterArray, String returnType) {
StringBuilder sb = new StringBuilder();
sb.append("(");
for (String s : refineParameterArray) {
sb.append(s);
if (!isPrimitive(s)) {
sb.append(";");
}
}
sb.append(")");
sb.append(returnType);
return sb.toString();
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean isPrimitive(String type) {
if (type.endsWith(";")) {