Add write replace method to lambdas that implement java.io.Serializable (#453)

Add write replace method to lambdas that implement java.io.Serializable

* Peripheral java library classes added as part of the toString method
of SerializedLambda to align functionality with the standard library.
* Test added that checks the SerializedLambda can be returned and that
it is populated with the correct values including it's toString method.
This commit is contained in:
adamjryan 2020-01-15 08:11:48 +00:00 committed by Alexey Andreev
parent 9b6f5e7895
commit 34aed76714
9 changed files with 406 additions and 20 deletions

View File

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

View File

@ -79,6 +79,12 @@ public class TClass<T> extends TObject implements TAnnotatedElement {
return result;
}
@Override
public String toString() {
return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
+ getName();
}
public PlatformClass getPlatformClass() {
return platformClass;
}

View File

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

View File

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

View File

@ -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 + "]";
}
}

View File

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

View File

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

View File

@ -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<Object, String> 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<Object, String> 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<T, R> extends Function<T, R>, Serializable {
}
}

View File

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