diff --git a/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java b/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java index 1fa132c69..1822f8b6e 100644 --- a/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java +++ b/classlib/src/main/java/org/teavm/classlib/impl/lambda/LambdaMetafactorySubstitutor.java @@ -15,8 +15,10 @@ */ package org.teavm.classlib.impl.lambda; +import java.lang.invoke.SerializedLambda; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -58,7 +60,8 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor MethodHandle implMethod = callSite.getBootstrapArguments().get(1).getMethodHandle(); ValueType[] instantiatedMethodType = callSite.getBootstrapArguments().get(2).getMethodType(); - String samName = ((ValueType.Object) callSite.getCalledMethod().getResultType()).getClassName(); + ValueType.Object lambdaInterfaceType = (ValueType.Object) callSite.getCalledMethod().getResultType(); + String samName = lambdaInterfaceType.getClassName(); ClassHierarchy hierarchy = callSite.getAgent().getClassHierarchy(); ClassReader samClass = hierarchy.getClassSource().get(samName); @@ -128,6 +131,19 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor if ((flags & FLAG_SERIALIZABLE) != 0) { implementor.getInterfaces().add("java.io.Serializable"); + String functionInterfaceMethodName = callSite.getCalledMethod().getName(); + addWriteReplaceMethod( + callerPe.getCurrentLocation(), + hierarchy, + implementor, + ValueType.object(callSite.getCaller().getClassName()), + lambdaInterfaceType, + new MethodDescriptor(functionInterfaceMethodName, samMethodType), + implMethod.getKind(), + ValueType.object(implMethod.getClassName()), + new MethodDescriptor(implMethod.getName(), implMethod.signature()), + new MethodDescriptor(functionInterfaceMethodName, instantiatedMethodType) + ); } int bootstrapArgIndex = 4; @@ -375,4 +391,51 @@ public class LambdaMetafactorySubstitutor implements BootstrapMethodSubstitutor implementor.addMethod(bridge); } + + private static void addWriteReplaceMethod( + TextLocation location, + ClassHierarchy classHierarchy, + ClassHolder lambdaClassDefinition, + ValueType.Object capturingClass, + ValueType.Object functionalInterfaceClass, + MethodDescriptor functionalInterfaceMethodDescriptor, + MethodHandleType implMethodKind, + ValueType.Object implClass, + MethodDescriptor implMethodDescriptor, + MethodDescriptor instantiatedMethodDescriptor + ) { + MethodHolder writeReplace = + new MethodHolder("writeReplace", new ValueType.Object(SerializedLambda.class.getName())); + writeReplace.setLevel(AccessLevel.PRIVATE); + writeReplace.getModifiers().add(ElementModifier.FINAL); + ProgramEmitter programEmitter = ProgramEmitter.create(writeReplace, classHierarchy); + programEmitter.setCurrentLocation(location); + Collection fields = lambdaClassDefinition.getFields(); + ValueEmitter capturedParametersArray = programEmitter.constructArray(Object.class, fields.size()); + ValueEmitter lambdaThis = programEmitter.var(0, lambdaClassDefinition); + int index = 0; + for (FieldHolder fieldHolder : fields) { + ValueType fieldType = fieldHolder.getType(); + ValueEmitter fieldValue = lambdaThis.getField(fieldHolder.getName(), fieldType); + if (fieldType instanceof ValueType.Primitive) { + fieldValue = fieldValue.cast(((ValueType.Primitive) fieldType).getBoxedType()); + } + capturedParametersArray.setElement(index++, fieldValue); + } + ValueEmitter newSerializedLambda = programEmitter.construct( + SerializedLambda.class, + programEmitter.constant(capturingClass), + programEmitter.constant(functionalInterfaceClass.getClassName().replace('.', '/')), + programEmitter.constant(functionalInterfaceMethodDescriptor.getName()), + programEmitter.constant(functionalInterfaceMethodDescriptor.signatureToString()), + programEmitter.constant(implMethodKind.getReferenceKind()), + programEmitter.constant(implClass.getClassName().replace('.', '/')), + programEmitter.constant(implMethodDescriptor.getName()), + programEmitter.constant(implMethodDescriptor.signatureToString()), + programEmitter.constant(instantiatedMethodDescriptor.signatureToString()), + capturedParametersArray + ); + newSerializedLambda.returnValue(); + lambdaClassDefinition.addMethod(writeReplace); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 2159c490c..dc0cac49e 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -79,6 +79,12 @@ public class TClass extends TObject implements TAnnotatedElement { return result; } + @Override + public String toString() { + return (isInterface() ? "interface " : (isPrimitive() ? "" : "class ")) + + getName(); + } + public PlatformClass getPlatformClass() { return platformClass; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TMethodHandleInfo.java b/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TMethodHandleInfo.java new file mode 100644 index 000000000..2f364bc3a --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TMethodHandleInfo.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020 adam. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.invoke; + +public +interface TMethodHandleInfo { + static String referenceKindToString(int referenceKind) { + if (!TMethodHandleNatives.refKindIsValid(referenceKind)) { + throw new IllegalArgumentException("invalid reference kind: " + referenceKind); + } + return TMethodHandleNatives.refKindName((byte) referenceKind); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TMethodHandleNatives.java b/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TMethodHandleNatives.java new file mode 100644 index 000000000..0558bf0d8 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TMethodHandleNatives.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020 adam. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.invoke; + +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_LIMIT; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_NONE; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_getField; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_getStatic; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_invokeInterface; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_invokeSpecial; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_invokeStatic; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_invokeVirtual; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_newInvokeSpecial; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_putField; +import static org.teavm.classlib.java.lang.invoke.TMethodHandleNatives.Constants.REF_putStatic; + +class TMethodHandleNatives { + static class Constants { + Constants() { } // static only + + static final byte REF_NONE = 0; + static final byte REF_getField = 1; + static final byte REF_getStatic = 2; + static final byte REF_putField = 3; + static final byte REF_putStatic = 4; + static final byte REF_invokeVirtual = 5; + static final byte REF_invokeStatic = 6; + static final byte REF_invokeSpecial = 7; + static final byte REF_newInvokeSpecial = 8; + static final byte REF_invokeInterface = 9; + static final byte REF_LIMIT = 10; + } + + static boolean refKindIsValid(int refKind) { + return refKind > REF_NONE && refKind < REF_LIMIT; + } + static String refKindName(byte refKind) { + assert refKindIsValid(refKind); + switch (refKind) { + case REF_getField: return "getField"; + case REF_getStatic: return "getStatic"; + case REF_putField: return "putField"; + case REF_putStatic: return "putStatic"; + case REF_invokeVirtual: return "invokeVirtual"; + case REF_invokeStatic: return "invokeStatic"; + case REF_invokeSpecial: return "invokeSpecial"; + case REF_newInvokeSpecial: return "newInvokeSpecial"; + case REF_invokeInterface: return "invokeInterface"; + default: return "REF_???"; + } + } + + private TMethodHandleNatives() { + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TSerializedLambda.java b/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TSerializedLambda.java new file mode 100644 index 000000000..680ff07d7 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/invoke/TSerializedLambda.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020 adam. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.invoke; + +import java.io.Serializable; +import java.lang.invoke.MethodHandleInfo; +import java.util.Objects; + +public final class TSerializedLambda implements Serializable { + private static final long serialVersionUID = 8025925345765570181L; + private final Class capturingClass; + private final String functionalInterfaceClass; + private final String functionalInterfaceMethodName; + private final String functionalInterfaceMethodSignature; + private final String implClass; + private final String implMethodName; + private final String implMethodSignature; + private final int implMethodKind; + private final String instantiatedMethodType; + private final Object[] capturedArgs; + + public TSerializedLambda(Class capturingClass, + String functionalInterfaceClass, + String functionalInterfaceMethodName, + String functionalInterfaceMethodSignature, + int implMethodKind, + String implClass, + String implMethodName, + String implMethodSignature, + String instantiatedMethodType, + Object[] capturedArgs) { + this.capturingClass = capturingClass; + this.functionalInterfaceClass = functionalInterfaceClass; + this.functionalInterfaceMethodName = functionalInterfaceMethodName; + this.functionalInterfaceMethodSignature = functionalInterfaceMethodSignature; + this.implMethodKind = implMethodKind; + this.implClass = implClass; + this.implMethodName = implMethodName; + this.implMethodSignature = implMethodSignature; + this.instantiatedMethodType = instantiatedMethodType; + this.capturedArgs = Objects.requireNonNull(capturedArgs).clone(); + } + + public String getCapturingClass() { + return capturingClass.getName().replace('.', '/'); + } + + public String getFunctionalInterfaceClass() { + return functionalInterfaceClass; + } + + public String getFunctionalInterfaceMethodName() { + return functionalInterfaceMethodName; + } + + public String getFunctionalInterfaceMethodSignature() { + return functionalInterfaceMethodSignature; + } + + public String getImplClass() { + return implClass; + } + + public String getImplMethodName() { + return implMethodName; + } + + public String getImplMethodSignature() { + return implMethodSignature; + } + + public int getImplMethodKind() { + return implMethodKind; + } + + public String getInstantiatedMethodType() { + return instantiatedMethodType; + } + + public int getCapturedArgCount() { + return capturedArgs.length; + } + + public Object getCapturedArg(int i) { + return capturedArgs[i]; + } + + private Object readResolve() throws ReflectiveOperationException { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + String implKind = MethodHandleInfo.referenceKindToString(implMethodKind); + return "SerializedLambda[capturingClass=" + capturingClass + ", functionalInterfaceMethod=" + + functionalInterfaceClass + "." + functionalInterfaceMethodName + ":" + + functionalInterfaceMethodSignature + ", " + + "implementation=" + implKind + " " + implClass + "." + implMethodName + ":" + implMethodSignature + + ", instantiatedMethodType=" + instantiatedMethodType + ", numCaptured=" + capturedArgs.length + "]"; + } +} diff --git a/core/src/main/java/org/teavm/model/MethodHandleType.java b/core/src/main/java/org/teavm/model/MethodHandleType.java index 157649b00..ef1f2511c 100644 --- a/core/src/main/java/org/teavm/model/MethodHandleType.java +++ b/core/src/main/java/org/teavm/model/MethodHandleType.java @@ -16,13 +16,23 @@ package org.teavm.model; public enum MethodHandleType { - GET_FIELD, - GET_STATIC_FIELD, - PUT_FIELD, - PUT_STATIC_FIELD, - INVOKE_VIRTUAL, - INVOKE_STATIC, - INVOKE_SPECIAL, - INVOKE_CONSTRUCTOR, - INVOKE_INTERFACE + GET_FIELD(1), + GET_STATIC_FIELD(2), + PUT_FIELD(3), + PUT_STATIC_FIELD(4), + INVOKE_VIRTUAL(5), + INVOKE_STATIC(6), + INVOKE_SPECIAL(7), + INVOKE_CONSTRUCTOR(8), + INVOKE_INTERFACE(9), + ; + private final int referenceKind; + + MethodHandleType(int referenceKind) { + this.referenceKind = referenceKind; + } + + public int getReferenceKind() { + return referenceKind; + } } diff --git a/core/src/main/java/org/teavm/model/ValueType.java b/core/src/main/java/org/teavm/model/ValueType.java index 253ef4d1e..67d89e18b 100644 --- a/core/src/main/java/org/teavm/model/ValueType.java +++ b/core/src/main/java/org/teavm/model/ValueType.java @@ -73,10 +73,12 @@ public abstract class ValueType implements Serializable { public static class Primitive extends ValueType { private PrimitiveType kind; + private final ValueType.Object boxedType; private int hash; - private Primitive(PrimitiveType kind) { + private Primitive(PrimitiveType kind, ValueType.Object boxedType) { this.kind = kind; + this.boxedType = boxedType; hash = 17988782 ^ (kind.ordinal() * 31); } @@ -84,6 +86,10 @@ public abstract class ValueType implements Serializable { return kind; } + public ValueType.Object getBoxedType() { + return boxedType; + } + @Override public String toString() { switch (kind) { @@ -198,21 +204,25 @@ public abstract class ValueType implements Serializable { public static final Void VOID = new Void(); - public static final Primitive BOOLEAN = new Primitive(PrimitiveType.BOOLEAN); + public static final Primitive BOOLEAN = + new Primitive(PrimitiveType.BOOLEAN, ValueType.object(Boolean.class.getName())); - public static final Primitive BYTE = new Primitive(PrimitiveType.BYTE); + public static final Primitive BYTE = new Primitive(PrimitiveType.BYTE, ValueType.object(Byte.class.getName())); - public static final Primitive SHORT = new Primitive(PrimitiveType.SHORT); + public static final Primitive SHORT = new Primitive(PrimitiveType.SHORT, ValueType.object(Short.class.getName())); - public static final Primitive INTEGER = new Primitive(PrimitiveType.INTEGER); + public static final Primitive INTEGER = + new Primitive(PrimitiveType.INTEGER, ValueType.object(Integer.class.getName())); - public static final Primitive FLOAT = new Primitive(PrimitiveType.FLOAT); + public static final Primitive FLOAT = new Primitive(PrimitiveType.FLOAT, ValueType.object(Float.class.getName())); - public static final Primitive LONG = new Primitive(PrimitiveType.LONG); + public static final Primitive LONG = new Primitive(PrimitiveType.LONG, ValueType.object(Long.class.getName())); - public static final Primitive DOUBLE = new Primitive(PrimitiveType.DOUBLE); + public static final Primitive DOUBLE = + new Primitive(PrimitiveType.DOUBLE, ValueType.object(Double.class.getName())); - public static final Primitive CHARACTER = new Primitive(PrimitiveType.CHARACTER); + public static final Primitive CHARACTER = + new Primitive(PrimitiveType.CHARACTER, ValueType.object(Character.class.getName())); static { primitiveMap.put(boolean.class, BOOLEAN); @@ -226,7 +236,7 @@ public abstract class ValueType implements Serializable { primitiveMap.put(void.class, VOID); } - public static ValueType object(String cls) { + public static ValueType.Object object(String cls) { return new Object(cls); } diff --git a/tests/src/test/java/org/teavm/classlib/java/lang/invoke/SerializedLambdaTest.java b/tests/src/test/java/org/teavm/classlib/java/lang/invoke/SerializedLambdaTest.java new file mode 100644 index 000000000..c33598c1b --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/lang/invoke/SerializedLambdaTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020 adam. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.invoke; + +import static org.junit.Assert.assertEquals; +import java.io.Serializable; +import java.lang.invoke.SerializedLambda; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Function; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class SerializedLambdaTest { + @Test + public void serializableLambdaHasWriteReplaceMethod() throws NoSuchMethodException, InvocationTargetException, + IllegalAccessException { + SerializableFunction lambda = Object::toString; + Method writeReplace = lambda.getClass().getDeclaredMethod("writeReplace"); + writeReplace.setAccessible(true); + SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(lambda); + assertEquals("org/teavm/classlib/java/lang/invoke/SerializedLambdaTest", serializedLambda.getCapturingClass()); + assertEquals(0, serializedLambda.getCapturedArgCount()); + assertEquals("org/teavm/classlib/java/lang/invoke/SerializedLambdaTest$SerializableFunction", + serializedLambda.getFunctionalInterfaceClass()); + assertEquals("apply", serializedLambda.getFunctionalInterfaceMethodName()); + assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", + serializedLambda.getFunctionalInterfaceMethodSignature()); + assertEquals("java/lang/Object", serializedLambda.getImplClass()); + assertEquals(5, serializedLambda.getImplMethodKind()); + assertEquals("toString", serializedLambda.getImplMethodName()); + assertEquals("()Ljava/lang/String;", serializedLambda.getImplMethodSignature()); + assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", serializedLambda.getInstantiatedMethodType()); + assertEquals( + "SerializedLambda[capturingClass=class org.teavm.classlib.java.lang.invoke.SerializedLambdaTest, " + + "functionalInterfaceMethod=org/teavm/classlib/java/lang/invoke" + + "/SerializedLambdaTest$SerializableFunction.apply:(Ljava/lang/Object;)Ljava/lang/Object;, " + + "implementation=invokeVirtual java/lang/Object.toString:()Ljava/lang/String;, " + + "instantiatedMethodType=(Ljava/lang/Object;)Ljava/lang/String;, numCaptured=0]", + serializedLambda.toString()); + } + + @Test + public void serializableLambdaWriteReplaceCapturesArguments() throws NoSuchMethodException, + InvocationTargetException, + IllegalAccessException { + String captureValue = "captured-value"; + SerializableFunction lambda = o -> captureValue; + Method writeReplace = lambda.getClass().getDeclaredMethod("writeReplace"); + writeReplace.setAccessible(true); + SerializedLambda serializedLambda = (SerializedLambda) writeReplace.invoke(lambda); + assertEquals("org/teavm/classlib/java/lang/invoke/SerializedLambdaTest", serializedLambda.getCapturingClass()); + assertEquals(1, serializedLambda.getCapturedArgCount()); + assertEquals(captureValue, serializedLambda.getCapturedArg(0)); + assertEquals("org/teavm/classlib/java/lang/invoke/SerializedLambdaTest$SerializableFunction", + serializedLambda.getFunctionalInterfaceClass()); + assertEquals("apply", serializedLambda.getFunctionalInterfaceMethodName()); + assertEquals("(Ljava/lang/Object;)Ljava/lang/Object;", + serializedLambda.getFunctionalInterfaceMethodSignature()); + assertEquals("org/teavm/classlib/java/lang/invoke/SerializedLambdaTest", serializedLambda.getImplClass()); + assertEquals(6, serializedLambda.getImplMethodKind()); + assertEquals("(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", + serializedLambda.getImplMethodSignature()); + assertEquals("(Ljava/lang/Object;)Ljava/lang/String;", serializedLambda.getInstantiatedMethodType()); + } + + private interface SerializableFunction extends Function, Serializable { + } +} \ No newline at end of file diff --git a/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java b/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java index bade95073..8f4c59886 100644 --- a/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java +++ b/tests/src/test/java/org/teavm/classlib/support/ReflectionSupplierImpl.java @@ -15,6 +15,7 @@ */ package org.teavm.classlib.support; +import java.lang.invoke.SerializedLambda; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -45,6 +46,10 @@ public class ReflectionSupplierImpl implements ReflectionSupplier { for (MethodReader method : cls.getMethods()) { if (method.getAnnotations().get(Reflectable.class.getName()) != null) { methods.add(method.getDescriptor()); + } else if ("writeReplace".equals(method.getName()) + && method.getResultType().isObject(SerializedLambda.class)) { + //Required by org.teavm.classlib.java.lang.invoke.SerializedLambdaTest. + methods.add(method.getDescriptor()); } } return methods;