diff --git a/demo/java-demo/src/main/java/com/alibaba/demo/lambda/ExternalLambdaDemo.java b/demo/java-demo/src/main/java/com/alibaba/demo/lambda/ExternalLambdaDemo.java new file mode 100644 index 0000000..b9fa289 --- /dev/null +++ b/demo/java-demo/src/main/java/com/alibaba/demo/lambda/ExternalLambdaDemo.java @@ -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 void consumesFunction1(Consumer f) { + f.accept(null); + } + + private void consumesFunction2(Function f) { + f.apply(null); + } + + private void consumesFunction3(BiFunction f) { + f.apply(null, null); + } + + private void consumesSupplier(Supplier supplier) { + supplier.get(); + } + + private void consumesTwoFunction2(Function f1, Function f2) { + f1.apply(null); + f2.apply(null); + } + + public static class A { + public String m1(int i) { + return ""; + } + + public String m2(Integer i) { + return ""; + } + + } +} diff --git a/demo/java-demo/src/test/java/com/alibaba/demo/lambda/ExternalLambdaDemoTest.java b/demo/java-demo/src/test/java/com/alibaba/demo/lambda/ExternalLambdaDemoTest.java new file mode 100644 index 0000000..f45e90a --- /dev/null +++ b/demo/java-demo/src/test/java/com/alibaba/demo/lambda/ExternalLambdaDemoTest.java @@ -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); + } + +} diff --git a/demo/java-demo/src/test/java/com/alibaba/demo/lambda/LambdaDemoTest.java b/demo/java-demo/src/test/java/com/alibaba/demo/lambda/LambdaDemoTest.java index d44fb8b..1889039 100644 --- a/demo/java-demo/src/test/java/com/alibaba/demo/lambda/LambdaDemoTest.java +++ b/demo/java-demo/src/test/java/com/alibaba/demo/lambda/LambdaDemoTest.java @@ -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) diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java index 4f3ba6b..7a3951c 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java @@ -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(";")) {