Skip to content

Commit

Permalink
feat(java): support jdk proxy serialization for graalvm (#1379)
Browse files Browse the repository at this point in the history
support jdk proxy serialization for graalvm, see more in #1364 

Closes #1378
  • Loading branch information
chaokunyang authored Feb 20, 2024
1 parent dbbd096 commit 18a8c7a
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 62 deletions.
2 changes: 2 additions & 0 deletions integration_tests/graalvm_tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@
<buildArgs>
<!-- for fast build -->
<!-- <buildArg>-Ob</buildArg> -->
<buildArg>-H:+UnlockExperimentalVMOptions</buildArg>
<buildArg>-H:DynamicProxyConfigurationFiles=src/main/resources/META-INF/native-image/proxy-config.json</buildArg>
</buildArgs>
</configuration>
</plugin>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static void main(String[] args) throws Throwable {
RecordExample.main(args);
RecordExample2.main(args);
ThreadSafeExample.main(args);
ProxyExample.main(args);
Benchmark.main(args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.fury.graalvm;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.function.Function;
import org.apache.fury.Fury;
import org.apache.fury.util.Preconditions;

@SuppressWarnings({"rawtypes", "unchecked"})
public class ProxyExample {
private static class TestInvocationHandler implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return 1;
}
}

static Fury fury;

static {
fury = Fury.builder().requireClassRegistration(true).build();
// register and generate serializer code.
fury.register(TestInvocationHandler.class, true);
}

public static void main(String[] args) {
Function function =
(Function)
Proxy.newProxyInstance(
fury.getClassLoader(), new Class[] {Function.class}, new TestInvocationHandler());
Function deserializedFunction = (Function) fury.deserialize(fury.serialize(function));
Preconditions.checkArgument(deserializedFunction.apply(null).equals(1));
System.out.println("Proxy tests pass");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ Args = -H:+ReportExceptionStackTraces \
org.apache.fury.graalvm.record.RecordExample,\
org.apache.fury.graalvm.record.RecordExample2,\
org.apache.fury.graalvm.ThreadSafeExample,\
org.apache.fury.graalvm.ProxyExample,\
org.apache.fury.graalvm.Benchmark
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"interfaces": [
"java.util.function.Function"
]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ private void addDefaultSerializers() {
// primitive types will be boxed.
addDefaultSerializer(String.class, new StringSerializer(fury));
PrimitiveSerializers.registerDefaultSerializers(fury);
ArraySerializers.registerDefaultSerializers(fury);
Serializers.registerDefaultSerializers(fury);
ArraySerializers.registerDefaultSerializers(fury);
TimeSerializers.registerDefaultSerializers(fury);
OptionalSerializers.registerDefaultSerializers(fury);
CollectionSerializers.registerDefaultSerializers(fury);
Expand Down Expand Up @@ -1820,6 +1820,9 @@ private Class<? extends Serializer> getSerializerClassFromGraalvmRegistry(Class<
}
}
if (GraalvmSupport.isGraalRuntime()) {
if (Functions.isLambda(cls) || ReflectionUtils.isJdkProxy(cls)) {
return null;
}
throw new RuntimeException(String.format("Class %s is not registered", cls));
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ public String[] xread(MemoryBuffer buffer) {

public static void registerDefaultSerializers(Fury fury) {
fury.registerSerializer(Object[].class, new ObjectArraySerializer<>(fury, Object[].class));
fury.registerSerializer(Class[].class, new ObjectArraySerializer<>(fury, Class[].class));
fury.registerSerializer(byte[].class, new ByteArraySerializer(fury));
fury.registerSerializer(char[].class, new CharArraySerializer(fury));
fury.registerSerializer(short[].class, new ShortArraySerializer(fury));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@

package org.apache.fury.serializer;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.apache.fury.Fury;
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.resolver.RefResolver;
import org.apache.fury.util.GraalvmSupport;
import org.apache.fury.util.Platform;
import org.apache.fury.util.Preconditions;
import org.apache.fury.util.ReflectionUtils;
Expand All @@ -34,21 +34,12 @@
public class JdkProxySerializer extends Serializer {

// Make offset compatible with graalvm native image.
private static final Field FIELD;
private static final long PROXY_HANDLER_FIELD_OFFSET;

static {
if (GraalvmSupport.isGraalBuildtime()) {
try {
// Make offset compatible with graalvm native image.
PROXY_HANDLER_FIELD_OFFSET = Platform.objectFieldOffset(Proxy.class.getDeclaredField("h"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
} else {
// not all JVM implementations use 'h' as internal InvocationHandler name
PROXY_HANDLER_FIELD_OFFSET =
ReflectionUtils.getFieldOffset(Proxy.class, InvocationHandler.class);
}
FIELD = ReflectionUtils.getField(Proxy.class, InvocationHandler.class);
PROXY_HANDLER_FIELD_OFFSET = Platform.objectFieldOffset(FIELD);
}

private static final InvocationHandler STUB_HANDLER =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.net.URI;
import java.nio.charset.Charset;
import java.util.Currency;
import java.util.IdentityHashMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
Expand All @@ -47,7 +46,6 @@
import org.apache.fury.memory.MemoryBuffer;
import org.apache.fury.resolver.ClassResolver;
import org.apache.fury.type.Type;
import org.apache.fury.type.TypeUtils;
import org.apache.fury.util.GraalvmSupport;
import org.apache.fury.util.Platform;
import org.apache.fury.util.Preconditions;
Expand Down Expand Up @@ -532,20 +530,8 @@ public UUID read(MemoryBuffer buffer) {
}

public static final class ClassSerializer extends Serializer<Class> {
private static final byte USE_CLASS_ID = 0;
private static final byte USE_CLASSNAME = 1;
private static final byte PRIMITIVE_FLAG = 2;
private final IdentityHashMap<Class<?>, Byte> primitivesMap = new IdentityHashMap<>();
private final Class<?>[] id2PrimitiveClasses = new Class[9];

public ClassSerializer(Fury fury) {
super(fury, Class.class);
byte count = 0;
for (Class<?> primitiveType : TypeUtils.getSortedPrimitiveClasses()) {
primitivesMap.put(primitiveType, count);
id2PrimitiveClasses[count] = primitiveType;
count++;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,44 @@ public static Field getField(Class<?> cls, String fieldName) {
return field;
}

/**
* Gets the only field in the class matching the required type.
*
* @param clazz the class in which the field should be declared
* @param fieldType the required type of the field
* @return the field with specified type
* @throws IllegalStateException if there are multiple fields of the required type
* @throws IllegalArgumentException if there are no fields of the required type
*/
public static Field getField(Class<?> clazz, Class<?> fieldType) {
Field f = null;
Class<?> cls = clazz;
while (cls != null) {
for (Field fi : cls.getDeclaredFields()) {
if (fieldType.equals(fi.getType())) {
if (f != null) {
throw new IllegalStateException(
"Found multiple field s matching "
+ fieldType
+ " in "
+ cls
+ ": "
+ f
+ " and "
+ fi);
}
f = fi;
}
}
cls = cls.getSuperclass();
}
if (f == null) {
throw new IllegalArgumentException(
"Found no field matching " + fieldType.getName() + " in " + clazz + "!");
}
return f;
}

public static Field getFieldNullable(Class<?> cls, String fieldName) {
Class<?> clazz = cls;
do {
Expand Down Expand Up @@ -348,40 +386,6 @@ public static long getFieldOffset(Class<?> cls, String fieldName) {
return getFieldOffset(field);
}

/**
* Gets the offset of the only field in the class matching the required type.
*
* @param clazz the class in which the field should be declared
* @param fieldType the required type of the field
* @return offset of the field
* @throws IllegalStateException if there are multiple fields of the required type
* @throws IllegalArgumentException if there are no fields of the required type
*/
public static long getFieldOffset(Class<?> clazz, Class<?> fieldType) {
Field f = null;
for (Field fi : clazz.getDeclaredFields()) {
if (fieldType.equals(fi.getType())) {
if (f != null) {
throw new IllegalStateException(
"Found multiple field s matching "
+ fieldType
+ " in "
+ clazz
+ ": "
+ f
+ " and "
+ fi);
}
f = fi;
}
}
if (f == null) {
throw new IllegalArgumentException(
"Found no field matching " + fieldType.getName() + " in " + clazz + "!");
}
return Platform.objectFieldOffset(f);
}

public static long getFieldOffsetChecked(Class<?> cls, String fieldName) {
long offset = getFieldOffset(cls, fieldName);
Preconditions.checkArgument(offset != -1);
Expand Down

0 comments on commit 18a8c7a

Please sign in to comment.