This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new c2b4316a4 fix(java): guard replace-resolve class reads (#3706)
c2b4316a4 is described below
commit c2b4316a49643392e76a30eebbccb409a06b15a3
Author: Shawn Yang <[email protected]>
AuthorDate: Tue May 26 19:39:40 2026 +0800
fix(java): guard replace-resolve class reads (#3706)
## Why?
## What does this PR do?
## Related issues
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review`
summary and screenshot evidence of the final clean AI review results
from both fresh reviewers on the current PR diff or current HEAD after
the latest code changes.
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
.github/workflows/ci.yml | 6 +-
.../java/org/apache/fory/context/ReadContext.java | 2 +
.../fory/io/ClassLoaderObjectInputStream.java | 35 +++++++-
.../main/java/org/apache/fory/meta/FieldTypes.java | 5 ++
.../org/apache/fory/meta/NativeTypeDefDecoder.java | 11 ++-
.../java/org/apache/fory/meta/TypeDefDecoder.java | 8 +-
.../org/apache/fory/resolver/ClassResolver.java | 89 ++++++++++++++++++--
.../org/apache/fory/resolver/TypeResolver.java | 5 ++
.../fory/serializer/ExceptionSerializers.java | 8 ++
.../org/apache/fory/serializer/JavaSerializer.java | 3 +-
.../fory/serializer/ObjectStreamSerializer.java | 3 +-
.../serializer/SerializedLambdaSerializer.java | 47 +++++++++--
.../org/apache/fory/serializer/Serializers.java | 2 +
.../apache/fory/serializer/StringSerializer.java | 36 ++++++--
.../fory/serializer/collection/MapSerializers.java | 96 +++++++++++++---------
.../apache/fory/meta/NativeTypeDefEncoderTest.java | 26 ++++++
.../apache/fory/resolver/ClassResolverTest.java | 14 ++++
.../apache/fory/resolver/MetaShareContextTest.java | 22 +++++
.../fory/serializer/ExceptionSerializersTest.java | 17 ++++
.../apache/fory/serializer/JavaSerializerTest.java | 24 ++++++
.../fory/serializer/JdkProxySerializerTest.java | 56 ++++++++++++-
.../fory/serializer/LambdaSerializerTest.java | 30 +++++++
.../serializer/ObjectStreamSerializerTest.java | 49 +++++++++++
.../serializer/ReplaceResolveSerializerTest.java | 52 ++++++++++++
.../apache/fory/serializer/SerializersTest.java | 33 ++++++++
.../fory/serializer/StringSerializerTest.java | 13 +++
.../serializer/collection/MapSerializersTest.java | 27 ++++++
27 files changed, 646 insertions(+), 73 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8658e0b0a..1f7ce2daa 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -593,7 +593,7 @@ jobs:
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- - uses: sbt/setup-sbt@1cad58d595b729a71ca2254cdf5b43dd6f42d4bb # v1.1.18
+ - uses: sbt/setup-sbt@2e222825582620cc38d2a54e674f3c01b7c14f5d # v1.1.24
- name: Install fory java
run: cd java && mvn -T10 --no-transfer-progress clean install
-DskipTests -Dmaven.javadoc.skip=true -Dmaven.source.skip=true && cd -
- name: Test
@@ -622,7 +622,7 @@ jobs:
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- - uses: sbt/setup-sbt@1cad58d595b729a71ca2254cdf5b43dd6f42d4bb # v1.1.18
+ - uses: sbt/setup-sbt@2e222825582620cc38d2a54e674f3c01b7c14f5d # v1.1.24
- name: Run Scala Xlang Test
env:
FORY_SCALA_JAVA_CI: "1"
@@ -655,7 +655,7 @@ jobs:
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- - uses: sbt/setup-sbt@1cad58d595b729a71ca2254cdf5b43dd6f42d4bb # v1.1.18
+ - uses: sbt/setup-sbt@2e222825582620cc38d2a54e674f3c01b7c14f5d # v1.1.24
- name: Install Fory Java
run: |
cd java
diff --git
a/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java
b/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java
index 0b03800dd..6dca2e503 100644
--- a/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java
+++ b/java/fory-core/src/main/java/org/apache/fory/context/ReadContext.java
@@ -470,6 +470,8 @@ public final class ReadContext {
if (size < 0) {
throw new IllegalArgumentException("Buffer object size must be
non-negative: " + size);
}
+ // This returns a zero-copy slice. Allocation limits belong to
serializers which allocate
+ // objects from the slice, not to the buffer-object transport itself.
buffer.checkReadableBytes(size);
int readerIndex = buffer.readerIndex();
MemoryBuffer slice = buffer.slice(readerIndex, size);
diff --git
a/java/fory-core/src/main/java/org/apache/fory/io/ClassLoaderObjectInputStream.java
b/java/fory-core/src/main/java/org/apache/fory/io/ClassLoaderObjectInputStream.java
index 38c70634f..9e9f7fab5 100644
---
a/java/fory-core/src/main/java/org/apache/fory/io/ClassLoaderObjectInputStream.java
+++
b/java/fory-core/src/main/java/org/apache/fory/io/ClassLoaderObjectInputStream.java
@@ -19,10 +19,12 @@ package org.apache.fory.io;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.io.StreamCorruptedException;
import java.lang.reflect.Proxy;
+import org.apache.fory.resolver.TypeResolver;
// Derived from
//
https://github.com/apache/commons-io/blob/5168fa5e9de9dd2ff6ace3f34226397a4faebc14/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java.
@@ -38,6 +40,8 @@ public class ClassLoaderObjectInputStream extends
ObjectInputStream {
/** The class loader to use. */
private final ClassLoader classLoader;
+ private final TypeResolver typeResolver;
+
/**
* Constructs a new ClassLoaderObjectInputStream.
*
@@ -50,6 +54,14 @@ public class ClassLoaderObjectInputStream extends
ObjectInputStream {
throws IOException, StreamCorruptedException {
super(inputStream);
this.classLoader = classLoader;
+ typeResolver = null;
+ }
+
+ public ClassLoaderObjectInputStream(TypeResolver typeResolver, InputStream
inputStream)
+ throws IOException, StreamCorruptedException {
+ super(inputStream);
+ this.typeResolver = typeResolver;
+ classLoader = typeResolver.getClassLoader();
}
/**
@@ -67,10 +79,13 @@ public class ClassLoaderObjectInputStream extends
ObjectInputStream {
Class<?> clazz = Class.forName(objectStreamClass.getName(), false,
classLoader);
if (clazz != null) {
// the classloader knows of the class
+ checkClass(clazz);
return clazz;
} else {
// classloader knows not of class, let the super classloader do it
- return super.resolveClass(objectStreamClass);
+ Class<?> superClass = super.resolveClass(objectStreamClass);
+ checkClass(superClass);
+ return superClass;
}
}
@@ -91,11 +106,27 @@ public class ClassLoaderObjectInputStream extends
ObjectInputStream {
Class<?>[] interfaceClasses = new Class[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
interfaceClasses[i] = Class.forName(interfaces[i], false, classLoader);
+ checkClass(interfaceClasses[i]);
}
try {
return Proxy.getProxyClass(classLoader, interfaceClasses);
} catch (IllegalArgumentException e) {
- return super.resolveProxyClass(interfaces);
+ Class<?> proxyClass = super.resolveProxyClass(interfaces);
+ checkClass(proxyClass);
+ return proxyClass;
+ }
+ }
+
+ private void checkClass(Class<?> cls) throws InvalidClassException {
+ if (typeResolver == null) {
+ return;
+ }
+ try {
+ typeResolver.checkClassForDeserialization(cls);
+ } catch (RuntimeException e) {
+ InvalidClassException exception = new
InvalidClassException(cls.getName(), e.getMessage());
+ exception.initCause(e);
+ throw exception;
}
}
}
diff --git a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
index c4d177991..074fcc6e3 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/FieldTypes.java
@@ -46,6 +46,7 @@ import org.apache.fory.collection.UInt16List;
import org.apache.fory.collection.UInt32List;
import org.apache.fory.collection.UInt64List;
import org.apache.fory.collection.UInt8List;
+import org.apache.fory.exception.DeserializationException;
import org.apache.fory.logging.Logger;
import org.apache.fory.logging.LoggerFactory;
import org.apache.fory.memory.MemoryBuffer;
@@ -67,6 +68,7 @@ import org.apache.fory.util.Preconditions;
public class FieldTypes {
private static final Logger LOG = LoggerFactory.getLogger(FieldTypes.class);
+ private static final int MAX_ARRAY_DIMS = 255;
/** Returns true if can use current field type. */
static boolean useFieldType(Class<?> parsedType, Descriptor descriptor) {
@@ -525,6 +527,9 @@ public class FieldTypes {
return new CollectionFieldType(-1, nullable, trackingRef, read(buffer,
resolver));
} else if (kind == 3) {
int dims = buffer.readVarUInt32Small7();
+ if (dims <= 0 || dims > MAX_ARRAY_DIMS) {
+ throw new DeserializationException("Invalid array dimensions in
TypeDef: " + dims);
+ }
return new ArrayFieldType(-1, nullable, trackingRef, read(buffer,
resolver), dims);
} else if (kind == 4) {
return new EnumFieldType(nullable, -1, -1);
diff --git
a/java/fory-core/src/main/java/org/apache/fory/meta/NativeTypeDefDecoder.java
b/java/fory-core/src/main/java/org/apache/fory/meta/NativeTypeDefDecoder.java
index a4d125601..7115414ab 100644
---
a/java/fory-core/src/main/java/org/apache/fory/meta/NativeTypeDefDecoder.java
+++
b/java/fory-core/src/main/java/org/apache/fory/meta/NativeTypeDefDecoder.java
@@ -83,7 +83,11 @@ class NativeTypeDefDecoder {
int rootTypeId = nativeTypeId(bodyHeader >>> 4);
int numClasses = bodyHeader & NUM_CLASS_THRESHOLD;
if (numClasses == NUM_CLASS_THRESHOLD) {
- numClasses += typeDefBuf.readVarUInt32Small7();
+ int extraClasses = typeDefBuf.readVarUInt32Small7();
+ if (extraClasses < 0 || extraClasses > Integer.MAX_VALUE -
NUM_CLASS_THRESHOLD - 1) {
+ throw new DeserializationException("Invalid TypeDef class count");
+ }
+ numClasses += extraClasses;
}
numClasses += 1;
String className;
@@ -95,6 +99,9 @@ class NativeTypeDefDecoder {
// | num fields + register flag | header + package name | header + class
name
// | header + type id + field name | next field info | ... |
int currentClassHeader = typeDefBuf.readVarUInt32Small7();
+ if (currentClassHeader < 0) {
+ throw new DeserializationException("Invalid TypeDef field count");
+ }
boolean isRegistered = (currentClassHeader & 0b1) != 0;
int numFields = currentClassHeader >>> 1;
Class<?> currentClass = null;
@@ -269,7 +276,7 @@ class NativeTypeDefDecoder {
private static List<FieldInfo> readFieldsInfo(
MemoryBuffer buffer, ClassResolver resolver, String className, int
numFields) {
- List<FieldInfo> fieldInfos = new ArrayList<>(numFields);
+ List<FieldInfo> fieldInfos = new ArrayList<>();
for (int i = 0; i < numFields; i++) {
int header = buffer.readByte() & 0xff;
// `3 bits size + 2 bits field name encoding + nullability flag + ref
tracking flag`
diff --git
a/java/fory-core/src/main/java/org/apache/fory/meta/TypeDefDecoder.java
b/java/fory-core/src/main/java/org/apache/fory/meta/TypeDefDecoder.java
index b184656c4..83ca2287b 100644
--- a/java/fory-core/src/main/java/org/apache/fory/meta/TypeDefDecoder.java
+++ b/java/fory-core/src/main/java/org/apache/fory/meta/TypeDefDecoder.java
@@ -76,7 +76,11 @@ class TypeDefDecoder {
}
numFields = header & SMALL_NUM_FIELDS_THRESHOLD;
if (numFields == SMALL_NUM_FIELDS_THRESHOLD) {
- numFields += buffer.readVarUInt32Small7();
+ int extraFields = buffer.readVarUInt32Small7();
+ if (extraFields < 0 || extraFields > Integer.MAX_VALUE -
SMALL_NUM_FIELDS_THRESHOLD) {
+ throw new DeserializationException("Invalid TypeDef field count");
+ }
+ numFields += extraFields;
}
if (named) {
String namespace = readPkgName(buffer);
@@ -201,7 +205,7 @@ class TypeDefDecoder {
// | header + type info + field name | ... | header + type info + field name
|
private static List<FieldInfo> readFieldsInfo(
MemoryBuffer buffer, XtypeResolver resolver, String className, int
numFields) {
- List<FieldInfo> fieldInfos = new ArrayList<>(numFields);
+ List<FieldInfo> fieldInfos = new ArrayList<>();
for (int i = 0; i < numFields; i++) {
// header: 2 bits field name encoding + 4 bits size + nullability flag +
ref tracking flag
byte header = buffer.readByte();
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index 49c4a6143..3a94bc805 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -31,6 +31,7 @@ import java.io.Externalizable;
import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
+import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
@@ -57,6 +58,9 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -1908,6 +1912,7 @@ public class ClassResolver extends TypeResolver {
if (config.requireClassRegistration()) {
return Functions.isLambda(cls)
|| ReflectionUtils.isJdkProxy(cls)
+ || isDefaultSafeClassToken(cls)
|| extRegistry.registeredClassIdMap.get(cls) != null
|| shimDispatcher.contains(cls);
} else {
@@ -1915,6 +1920,31 @@ public class ClassResolver extends TypeResolver {
}
}
+ private static boolean isDefaultSafeClassToken(Class<?> cls) {
+ return cls == Serializable.class || cls == Externalizable.class ||
isDefaultSafeInterface(cls);
+ }
+
+ private static boolean isDefaultSafeInterface(Class<?> cls) {
+ return cls.isInterface()
+ && (cls == Collection.class
+ || cls == List.class
+ || cls == Set.class
+ || cls == Map.class
+ || cls == SortedMap.class
+ || cls == SortedSet.class
+ || cls.getName().startsWith("java.util.function.")
+ || !hasDefaultMethods(cls));
+ }
+
+ private static boolean hasDefaultMethods(Class<?> cls) {
+ for (Method method : cls.getMethods()) {
+ if (method.isDefault()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Write class info to <code>buffer</code>. TODO(chaokunyang): The method
should try to write
* aligned data to reduce cpu instruction overhead. `writeTypeInfo` is the
last step before
@@ -2025,11 +2055,22 @@ public class ClassResolver extends TypeResolver {
}
/**
- * Read serialized java classname. Note that the object of the class can be
non-serializable. For
- * serializable object, {@link #readTypeInfo(ReadContext)} or {@link
#readTypeInfo(ReadContext,
- * TypeInfoHolder)} should be invoked.
+ * Read a serialized Java class token.
+ *
+ * <p>For named-class tokens, this method enforces deserialization class
policy before returning
+ * the class, including the disallowed list and registration or TypeChecker
checks. For registered
+ * type-id tokens, it returns the registered class whose admission was
already checked during
+ * registration.
+ *
+ * <p>Note that the object of the class can be non-serializable. For
serializable object, {@link
+ * #readTypeInfo(ReadContext)} or {@link #readTypeInfo(ReadContext,
TypeInfoHolder)} should be
+ * invoked.
*/
public Class<?> readClassInternal(ReadContext readContext) {
+ return readClassInternal(readContext, true);
+ }
+
+ private Class<?> readClassInternal(ReadContext readContext, boolean
checkNamedClass) {
MemoryBuffer buffer = readContext.getBuffer();
int header = buffer.readVarUInt32Small14();
if ((header & 0b1) != 0) {
@@ -2038,7 +2079,7 @@ public class ClassResolver extends TypeResolver {
MetaStringReader metaStringReader = readContext.getMetaStringReader();
EncodedMetaString packageBytes =
metaStringReader.readMetaStringWithFlag(buffer, header);
EncodedMetaString simpleClassNameBytes =
metaStringReader.readMetaString(buffer);
- return loadBytesToTypeInfo(packageBytes, simpleClassNameBytes).type;
+ return loadBytesToTypeInfo(packageBytes, simpleClassNameBytes,
checkNamedClass).type;
}
int typeId = header >>> 1;
switch (typeId) {
@@ -2055,6 +2096,11 @@ public class ClassResolver extends TypeResolver {
}
}
+ @Internal
+ public Class<?> readClassInternalUnchecked(ReadContext readContext) {
+ return readClassInternal(readContext, false);
+ }
+
private TypeInfo getTypeInfoByTypeIdForReadClassInternal(int typeId, int
userTypeId) {
TypeInfo typeInfo;
if (userTypeId != INVALID_USER_TYPE_ID) {
@@ -2069,10 +2115,16 @@ public class ClassResolver extends TypeResolver {
@Override
protected TypeInfo loadBytesToTypeInfo(
EncodedMetaString packageBytes, EncodedMetaString simpleClassNameBytes) {
+ return loadBytesToTypeInfo(packageBytes, simpleClassNameBytes, true);
+ }
+
+ private TypeInfo loadBytesToTypeInfo(
+ EncodedMetaString packageBytes, EncodedMetaString simpleClassNameBytes,
boolean checkClass) {
TypeNameBytes typeNameBytes = new TypeNameBytes(packageBytes,
simpleClassNameBytes);
TypeInfo typeInfo = compositeNameBytes2TypeInfo.get(typeNameBytes);
if (typeInfo == null) {
- typeInfo = populateBytesToTypeInfo(typeNameBytes, packageBytes,
simpleClassNameBytes);
+ typeInfo =
+ populateBytesToTypeInfo(typeNameBytes, packageBytes,
simpleClassNameBytes, checkClass);
}
// Note: Don't create serializer here - this method is used by both
readTypeInfo
// (which needs serializer) and readClassInternal (which doesn't need
serializer).
@@ -2102,28 +2154,47 @@ public class ClassResolver extends TypeResolver {
private TypeInfo populateBytesToTypeInfo(
TypeNameBytes typeNameBytes,
EncodedMetaString packageBytes,
- EncodedMetaString simpleClassNameBytes) {
+ EncodedMetaString simpleClassNameBytes,
+ boolean checkClass) {
String packageName = packageBytes.decode(PACKAGE_DECODER);
String className = simpleClassNameBytes.decode(TYPE_NAME_DECODER);
ClassSpec classSpec = Encoders.decodePkgAndClass(packageName, className);
Class<?> cls = loadClass(classSpec.entireClassName, classSpec.isEnum,
classSpec.dimension);
+ boolean unknownClass =
UnknownClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls));
+ if (checkClass && !unknownClass) {
+ checkClassForDeserialization(cls);
+ }
int typeId = buildUnregisteredTypeId(cls, null);
TypeInfo typeInfo =
new TypeInfo(cls, packageBytes, simpleClassNameBytes, null, typeId,
INVALID_USER_TYPE_ID);
- if
(UnknownClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls))) {
+ if (unknownClass) {
typeInfo.serializer =
UnknownClassSerializers.getSerializer(this,
classSpec.entireClassName, cls);
- } else {
+ } else if (checkClass) {
// don't create serializer here, if the class is an interface,
// there won't be serializer since interface has no instance.
if (!classInfoMap.containsKey(cls)) {
classInfoMap.put(cls, typeInfo);
}
}
- compositeNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
+ if (checkClass) {
+ compositeNameBytes2TypeInfo.put(typeNameBytes, typeInfo);
+ }
return typeInfo;
}
+ @Internal
+ @Override
+ public void checkClassForDeserialization(Class<?> cls) {
+ if
(UnknownClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls))) {
+ return;
+ }
+ DisallowedList.checkNotInDisallowedList(cls.getName());
+ if (!isSecure(cls)) {
+ throw new InsecureException(generateSecurityMsg(cls));
+ }
+ }
+
public Class<?> loadClassForMeta(String className, boolean isEnum, int
arrayDims) {
String pkg = ReflectionUtils.getPackage(className);
String typeName = ReflectionUtils.getClassNameWithoutPackage(className);
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index f249884d0..d60a72ce3 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -176,6 +176,9 @@ public abstract class TypeResolver {
return extRegistry.classLoader;
}
+ @Internal
+ public void checkClassForDeserialization(Class<?> cls) {}
+
public final SharedRegistry getSharedRegistry() {
return sharedRegistry;
}
@@ -1011,6 +1014,8 @@ public abstract class TypeResolver {
return typeInfo;
}
Class<?> cls = loadClass(typeDef.getClassSpec());
+ // A wire TypeDef may create a compatible serializer; admit the class
before caching it by id.
+ checkClassForDeserialization(cls);
if (!typeDef.isStructSchemaKind()
&&
!UnknownClass.class.isAssignableFrom(TypeUtils.getComponentIfArray(cls))) {
typeInfo = getTypeInfo(cls);
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
index dc22b47ed..b0f5f99e0 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/ExceptionSerializers.java
@@ -485,6 +485,14 @@ public final class ExceptionSerializers {
private static List<Throwable> readSuppressedExceptions(ReadContext
readContext) {
MemoryBuffer buffer = readContext.getBuffer();
int numSuppressedExceptions = buffer.readVarUInt32();
+ int maxCollectionSize = readContext.getConfig().maxCollectionSize();
+ if (numSuppressedExceptions < 0 || numSuppressedExceptions >
maxCollectionSize) {
+ throw new ForyException(
+ "Throwable suppressed exception count "
+ + numSuppressedExceptions
+ + " exceeds max collection size "
+ + maxCollectionSize);
+ }
List<Throwable> suppressedExceptions = new
ArrayList<>(numSuppressedExceptions);
for (int i = 0; i < numSuppressedExceptions; i++) {
suppressedExceptions.add((Throwable) readContext.readRef());
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/JavaSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/JavaSerializer.java
index eb0497dc2..397c3ac15 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/JavaSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/JavaSerializer.java
@@ -101,8 +101,7 @@ public class JavaSerializer extends
AbstractObjectSerializer {
ObjectInputStream objectInputStream =
(ObjectInputStream) readContext.getContextObject(objectInput);
if (objectInputStream == null) {
- objectInputStream =
- new ClassLoaderObjectInputStream(typeResolver.getClassLoader(),
objectInput);
+ objectInputStream = new ClassLoaderObjectInputStream(typeResolver,
objectInput);
readContext.putContextObject(objectInput, objectInputStream);
}
return objectInputStream.readObject();
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
index fabaa64af..2e7a8df80 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/ObjectStreamSerializer.java
@@ -291,7 +291,7 @@ public class ObjectStreamSerializer extends
AbstractObjectSerializer {
ClassResolver classResolver = (ClassResolver) typeResolver;
TreeMap<Integer, ObjectInputValidation> callbacks = new
TreeMap<>(Collections.reverseOrder());
for (int i = 0; i < numClasses; i++) {
- Class<?> currentClass = classResolver.readClassInternal(readContext);
+ Class<?> currentClass =
classResolver.readClassInternalUnchecked(readContext);
// Find the matching local slot for sender's class
SlotInfo matchedSlot = null;
@@ -324,6 +324,7 @@ public class ObjectStreamSerializer extends
AbstractObjectSerializer {
if (matchedSlot == null) {
// Sender has a layer that receiver doesn't have - read TypeDef and
skip the data
+ classResolver.checkClassForDeserialization(currentClass);
skipUnknownLayerData(readContext, currentClass);
continue;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/SerializedLambdaSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/SerializedLambdaSerializer.java
index e359979a0..0962e0710 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/SerializedLambdaSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/SerializedLambdaSerializer.java
@@ -25,6 +25,7 @@ import java.lang.reflect.Method;
import org.apache.fory.context.CopyContext;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
+import org.apache.fory.exception.DeserializationException;
import org.apache.fory.exception.ForyException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.platform.AndroidSupport;
@@ -42,6 +43,7 @@ public class SerializedLambdaSerializer extends Serializer {
static final Class<SerializedLambda> SERIALIZED_LAMBDA =
SerializedLambda.class;
private static final MethodHandle READ_RESOLVE_HANDLE;
private final TypeResolver typeResolver;
+ private final int maxCollectionSize;
static {
if (AndroidSupport.IS_ANDROID) {
@@ -62,6 +64,7 @@ public class SerializedLambdaSerializer extends Serializer {
public SerializedLambdaSerializer(TypeResolver typeResolver, Class<?> cls) {
super(typeResolver.getConfig(), cls);
this.typeResolver = typeResolver;
+ maxCollectionSize = typeResolver.getConfig().maxCollectionSize();
Preconditions.checkArgument(cls == SERIALIZED_LAMBDA);
}
@@ -105,7 +108,8 @@ public class SerializedLambdaSerializer extends Serializer {
serializedLambda.getImplMethodName(),
serializedLambda.getImplMethodSignature(),
serializedLambda.getInstantiatedMethodType(),
- capturedArgs);
+ capturedArgs,
+ false);
}
@Override
@@ -127,6 +131,9 @@ public class SerializedLambdaSerializer extends Serializer {
int implMethodKind = buffer.readVarInt32();
String instantiatedMethodType = readContext.readStringRef();
int capturedArgCount = buffer.readVarUInt32Small7();
+ if (capturedArgCount < 0 || capturedArgCount > maxCollectionSize) {
+ throwInvalidCapturedArgCount(capturedArgCount);
+ }
Object[] capturedArgs = new Object[capturedArgCount];
for (int i = 0; i < capturedArgCount; i++) {
capturedArgs[i] = readContext.readRef();
@@ -141,7 +148,20 @@ public class SerializedLambdaSerializer extends Serializer
{
implMethodName,
implMethodSignature,
instantiatedMethodType,
- capturedArgs);
+ capturedArgs,
+ true);
+ }
+
+ private void throwInvalidCapturedArgCount(int capturedArgCount) {
+ if (capturedArgCount < 0) {
+ throw new DeserializationException(
+ "SerializedLambda captured arg count must be non-negative: " +
capturedArgCount);
+ }
+ throw new DeserializationException(
+ "SerializedLambda captured arg count "
+ + capturedArgCount
+ + " exceeds max collection size "
+ + maxCollectionSize);
}
static Object readResolve(Object replacement) {
@@ -170,9 +190,10 @@ public class SerializedLambdaSerializer extends Serializer
{
String implMethodName,
String implMethodSignature,
String instantiatedMethodType,
- Object[] capturedArgs) {
+ Object[] capturedArgs,
+ boolean checkCapturingClass) {
return new SerializedLambda(
- loadCapturingClass(capturingClass),
+ loadCapturingClass(capturingClass, checkCapturingClass),
functionalInterfaceClass,
functionalInterfaceMethodName,
functionalInterfaceMethodSignature,
@@ -184,17 +205,27 @@ public class SerializedLambdaSerializer extends
Serializer {
capturedArgs);
}
- private Class<?> loadCapturingClass(String className) {
+ private Class<?> loadCapturingClass(String className, boolean checkClass) {
String binaryClassName = className.replace('/', '.');
try {
- return Class.forName(binaryClassName, false,
typeResolver.getClassLoader());
+ return loadCapturingClass(binaryClassName,
typeResolver.getClassLoader(), checkClass);
} catch (ClassNotFoundException e) {
try {
- return Class.forName(
- binaryClassName, false,
Thread.currentThread().getContextClassLoader());
+ return loadCapturingClass(
+ binaryClassName, Thread.currentThread().getContextClassLoader(),
checkClass);
} catch (ClassNotFoundException ex) {
throw new RuntimeException("Can't load capturing class " +
binaryClassName, ex);
}
}
}
+
+ private Class<?> loadCapturingClass(String className, ClassLoader
classLoader, boolean checkClass)
+ throws ClassNotFoundException {
+ Class<?> cls = Class.forName(className, false, classLoader);
+ if (checkClass) {
+ // JDK SerializedLambda readResolve invokes restoration code on the
capturing class.
+ typeResolver.checkClassForDeserialization(cls);
+ }
+ return cls;
+ }
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
index 8da69a667..4bff2392f 100644
--- a/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
+++ b/java/fory-core/src/main/java/org/apache/fory/serializer/Serializers.java
@@ -699,6 +699,8 @@ public class Serializers {
@Override
public Class read(ReadContext readContext) {
+ // A wire-provided Class value can later drive reflection, proxy
creation, or serializer
+ // selection, so class literals must stay under the same
registration/TypeChecker boundary.
return ((ClassResolver)
readContext.getTypeResolver()).readClassInternal(readContext);
}
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java
index bacdc6b29..939ee9aad 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/StringSerializer.java
@@ -40,6 +40,7 @@ import org.apache.fory.codegen.Expression.StaticInvoke;
import org.apache.fory.config.Config;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
+import org.apache.fory.exception.DeserializationException;
import org.apache.fory.memory.LittleEndian;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.NativeByteOrder;
@@ -139,6 +140,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
private final boolean compressString;
private final boolean writeNumUtf16BytesForUtf8Encoding;
private final boolean xlang;
+ private final long maxBinarySize;
// set default length to 0, since char array and bytes array won't be used
at the same time.
private static final byte[] EMPTY_BYTES_STUB = new byte[0];
@@ -157,6 +159,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
Preconditions.checkArgument(compressString, "compress string muse be
enabled for xlang mode");
}
writeNumUtf16BytesForUtf8Encoding =
config.writeNumUtf16BytesForUtf8Encoding();
+ maxBinarySize = config.maxBinarySize();
}
@Override
@@ -221,7 +224,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
public String readBytesString(MemoryBuffer buffer) {
long header = buffer.readVarUint36Small();
byte coder = (byte) (header & 0b11);
- int numBytes = (int) (header >>> 2);
+ int numBytes = readStringSize(header);
byte[] bytes;
if (!NativeByteOrder.IS_LITTLE_ENDIAN && coder == UTF16) {
bytes = readBytesUTF16BE(buffer, numBytes);
@@ -239,7 +242,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
public String readCharsString(MemoryBuffer buffer) {
long header = buffer.readVarUint36Small();
byte coder = (byte) (header & 0b11);
- int numBytes = (int) (header >>> 2);
+ int numBytes = readStringSize(header);
char[] chars;
if (coder == LATIN1) {
chars = readCharsLatin1(buffer, numBytes);
@@ -255,7 +258,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
public String readCompressedBytesString(MemoryBuffer buffer) {
long header = buffer.readVarUint36Small();
byte coder = (byte) (header & 0b11);
- int numBytes = (int) (header >>> 2);
+ int numBytes = readStringSize(header);
if (coder == UTF8) {
byte[] data;
if (writeNumUtf16BytesForUtf8Encoding) {
@@ -345,7 +348,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
public String readCompressedCharsString(MemoryBuffer buffer) {
long header = buffer.readVarUint36Small();
byte coder = (byte) (header & 0b11);
- int numBytes = (int) (header >>> 2);
+ int numBytes = readStringSize(header);
char[] chars;
if (coder == LATIN1) {
chars = readCharsLatin1(buffer, numBytes);
@@ -436,13 +439,14 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
private String readStringSlow(MemoryBuffer buffer) {
long header = buffer.readVarUint36Small();
byte coder = (byte) (header & 0b11);
- int numBytes = (int) (header >>> 2);
+ int numBytes = readStringSize(header);
if (coder == LATIN1) {
return new String(readBytesUnCompressedUTF16(buffer, numBytes),
StandardCharsets.ISO_8859_1);
} else if (coder == UTF16) {
return new String(readCharsUTF16(buffer, numBytes));
} else if (coder == UTF8) {
int utf8Bytes = writeNumUtf16BytesForUtf8Encoding ? buffer.readInt32() :
numBytes;
+ checkStringSize(utf8Bytes);
return new String(buffer.readBytes(utf8Bytes), StandardCharsets.UTF_8);
} else {
throw new RuntimeException("Unknown coder type " + coder);
@@ -628,6 +632,7 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
private byte[] readBytesUTF8PerfOptimized(MemoryBuffer buffer, int numBytes)
{
int udf8Bytes = buffer.readInt32();
+ checkStringSize(udf8Bytes);
byte[] bytes = new byte[numBytes];
// noinspection Duplicates
buffer.checkReadableBytes(udf8Bytes);
@@ -690,8 +695,10 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
}
public String readCharsUTF8PerfOptimized(MemoryBuffer buffer, int numBytes) {
+ checkStringSize(numBytes);
int udf16Chars = numBytes >> 1;
int udf8Bytes = buffer.readInt32();
+ checkStringSize(udf8Bytes);
char[] chars = new char[udf16Chars];
// noinspection Duplicates
buffer.checkReadableBytes(udf8Bytes);
@@ -710,6 +717,25 @@ public final class StringSerializer extends
ImmutableSerializer<String> {
return newCharsStringZeroCopy(chars);
}
+ private int readStringSize(long header) {
+ long size = header >>> 2;
+ if (size > maxBinarySize) {
+ throwStringSizeOutOfBounds(size);
+ }
+ return (int) size;
+ }
+
+ private void checkStringSize(int size) {
+ if (size < 0 || size > maxBinarySize) {
+ throwStringSizeOutOfBounds(size);
+ }
+ }
+
+ private void throwStringSizeOutOfBounds(long size) {
+ throw new DeserializationException(
+ "String payload size " + size + " is outside allowed range [0, " +
maxBinarySize + "]");
+ }
+
public void writeCharsLatin1(MemoryBuffer buffer, char[] chars, int
numBytes) {
int writerIndex = buffer.writerIndex();
long header = ((long) numBytes << 2) | LATIN1;
diff --git
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java
index 953314a03..43a464b12 100644
---
a/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java
+++
b/java/fory-core/src/main/java/org/apache/fory/serializer/collection/MapSerializers.java
@@ -19,7 +19,15 @@
package org.apache.fory.serializer.collection;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
import java.lang.invoke.MethodHandle;
import java.util.Collection;
import java.util.Collections;
@@ -29,6 +37,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
@@ -39,17 +48,15 @@ import org.apache.fory.context.CopyContext;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
import org.apache.fory.memory.MemoryBuffer;
-import org.apache.fory.platform.AndroidSupport;
-import org.apache.fory.platform.UnsafeOps;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.resolver.ClassResolver;
import org.apache.fory.resolver.TypeInfo;
import org.apache.fory.resolver.TypeResolver;
import org.apache.fory.serializer.ExternalizableSerializer;
-import org.apache.fory.serializer.JavaSerializer;
import org.apache.fory.serializer.ReplaceResolveSerializer;
import org.apache.fory.serializer.Serializer;
import org.apache.fory.serializer.Serializers;
+import org.apache.fory.util.ExceptionUtils;
import org.apache.fory.util.Preconditions;
/**
@@ -333,24 +340,35 @@ public class MapSerializers {
}
public static class EnumMapSerializer extends MapSerializer<EnumMap> {
- private static final byte NORMAL_ENUM_MAP = 0;
- private static final byte JAVA_SERIALIZED_EMPTY_ENUM_MAP = 1;
-
- private static final class KeyTypeFieldOffset {
- // Make offset compatible with graalvm native image.
- private static final long VALUE;
+ private static final class CapturingObjectInputStream extends
ObjectInputStream {
+ private final ClassLoader fallbackLoader;
+ private Class<?> enumClass;
+
+ private CapturingObjectInputStream(InputStream in, ClassLoader
fallbackLoader)
+ throws IOException {
+ super(in);
+ this.fallbackLoader = fallbackLoader;
+ }
- static {
+ @Override
+ protected Class<?> resolveClass(ObjectStreamClass desc)
+ throws IOException, ClassNotFoundException {
+ Class<?> cls;
try {
- VALUE =
UnsafeOps.objectFieldOffset(EnumMap.class.getDeclaredField("keyType"));
- } catch (final Exception e) {
- throw new RuntimeException(e);
+ cls = super.resolveClass(desc);
+ } catch (ClassNotFoundException e) {
+ if (fallbackLoader == null) {
+ throw e;
+ }
+ cls = Class.forName(desc.getName(), false, fallbackLoader);
}
+ if (enumClass == null && cls != Enum.class &&
Enum.class.isAssignableFrom(cls)) {
+ enumClass = cls;
+ }
+ return cls;
}
}
- private JavaSerializer javaSerializer;
-
public EnumMapSerializer(TypeResolver typeResolver) {
// getMapKeyValueType(EnumMap.class) will be `K, V` without Enum as key
bound.
// so no need to infer key generics in init.
@@ -360,12 +378,6 @@ public class MapSerializers {
@Override
public Map onMapWrite(WriteContext writeContext, EnumMap value) {
MemoryBuffer buffer = writeContext.getBuffer();
- if (AndroidSupport.IS_ANDROID && value.isEmpty()) {
- buffer.writeByte(JAVA_SERIALIZED_EMPTY_ENUM_MAP);
- getJavaSerializer().write(writeContext, value);
- return value;
- }
- buffer.writeByte(NORMAL_ENUM_MAP);
buffer.writeVarUInt32Small7(value.size());
Class keyType = getKeyType(value);
((ClassResolver) typeResolver).writeClassAndUpdateCache(writeContext,
keyType);
@@ -375,16 +387,6 @@ public class MapSerializers {
@Override
public EnumMap newMap(ReadContext readContext) {
MemoryBuffer buffer = readContext.getBuffer();
- byte payloadMode = buffer.readByte();
- if (payloadMode == JAVA_SERIALIZED_EMPTY_ENUM_MAP) {
- EnumMap map = (EnumMap) getJavaSerializer().read(readContext);
- setNumElements(0);
- readContext.reference(map);
- return map;
- }
- if (payloadMode != NORMAL_ENUM_MAP) {
- throw new IllegalArgumentException("Unknown EnumMap payload mode: " +
payloadMode);
- }
setNumElements(readMapSize(buffer));
Class<?> keyType = typeResolver.readTypeInfo(readContext).getType();
EnumMap map = new EnumMap(keyType);
@@ -397,20 +399,38 @@ public class MapSerializers {
return new EnumMap(originMap);
}
- private static Class<?> getKeyType(EnumMap value) {
+ private Class<?> getKeyType(EnumMap value) {
+ Objects.requireNonNull(value, "value");
if (!value.isEmpty()) {
Enum key = (Enum) value.keySet().iterator().next();
return key.getDeclaringClass();
}
- return (Class<?>) UnsafeOps.getObject(value, KeyTypeFieldOffset.VALUE);
+ try {
+ return keyTypeBySerialization(value, typeResolver.getClassLoader());
+ } catch (IOException | ClassNotFoundException e) {
+ throw ExceptionUtils.throwException(e);
+ }
}
- private JavaSerializer getJavaSerializer() {
- JavaSerializer javaSerializer = this.javaSerializer;
- if (javaSerializer == null) {
- javaSerializer = this.javaSerializer = new
JavaSerializer(typeResolver, EnumMap.class);
+ private static Class<?> keyTypeBySerialization(EnumMap value, ClassLoader
fallbackLoader)
+ throws IOException, ClassNotFoundException {
+ // This JDK stream is local-only key-type discovery for an already-owned
EnumMap; remote Fory
+ // payloads must keep using the normal class metadata path in newMap.
+ EnumMap copy = value.clone();
+ copy.clear();
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream(128);
+ try (ObjectOutputStream out = new ObjectOutputStream(bytes)) {
+ out.writeObject(copy);
+ }
+ try (CapturingObjectInputStream in =
+ new CapturingObjectInputStream(
+ new ByteArrayInputStream(bytes.toByteArray()), fallbackLoader)) {
+ in.readObject();
+ if (in.enumClass == null) {
+ throw new InvalidObjectException("Cannot determine EnumMap key
type");
+ }
+ return in.enumClass;
}
- return javaSerializer;
}
}
diff --git
a/java/fory-core/src/test/java/org/apache/fory/meta/NativeTypeDefEncoderTest.java
b/java/fory-core/src/test/java/org/apache/fory/meta/NativeTypeDefEncoderTest.java
index 036a9c6da..4a0e39c37 100644
---
a/java/fory-core/src/test/java/org/apache/fory/meta/NativeTypeDefEncoderTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/meta/NativeTypeDefEncoderTest.java
@@ -72,6 +72,32 @@ public class NativeTypeDefEncoderTest {
}
}
+ @Test
+ public void testTypeDefCountIgnoresLimit() {
+ Fory writer = Fory.builder().withXlang(false).withMetaShare(true).build();
+ Fory reader =
+
Fory.builder().withXlang(false).withMetaShare(true).withMaxCollectionSize(1).build();
+ TypeDef typeDef =
+ TypeDef.buildTypeDef(writer.getTypeResolver(),
TypeDefTest.TestFieldsOrderClass1.class);
+
+ TypeDef decoded =
+ TypeDef.readTypeDef(
+ reader.getTypeResolver(),
MemoryBuffer.fromByteArray(typeDef.getEncoded()));
+ Assert.assertEquals(decoded, typeDef);
+ }
+
+ @Test
+ public void testTypeDefArrayDimensionLimit() {
+ Fory fory = Fory.builder().withXlang(false).build();
+ MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(16);
+ buffer.writeByte(3 << 2);
+ buffer.writeVarUInt32Small7(256);
+
+ Assert.assertThrows(
+ DeserializationException.class,
+ () -> FieldTypes.FieldType.read(buffer, fory.getTypeResolver()));
+ }
+
@Data
public static class Foo1 {
private int f1;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
index 2f723a8b7..7c8a4b6e8 100644
---
a/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/resolver/ClassResolverTest.java
@@ -53,6 +53,7 @@ import org.apache.fory.builder.Generated;
import org.apache.fory.config.ForyBuilder;
import org.apache.fory.context.ReadContext;
import org.apache.fory.context.WriteContext;
+import org.apache.fory.exception.InsecureException;
import org.apache.fory.logging.Logger;
import org.apache.fory.logging.LoggerFactory;
import org.apache.fory.memory.MemoryBuffer;
@@ -449,6 +450,19 @@ public class ClassResolverTest extends ForyTestBase {
Arrays.asList(Interface1.class, Interface1.class, Interface2.class,
Interface2.class));
}
+ @Test
+ public void testClassLiteralRegistration() {
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ byte[] serialized = writer.serialize(Foo.class);
+
+ Fory reader = Fory.builder().withXlang(false).build();
+ Assert.assertThrows(InsecureException.class, () ->
reader.deserialize(serialized));
+
+ Fory registeredReader = Fory.builder().withXlang(false).build();
+ registeredReader.register(Foo.class);
+ Assert.assertSame(registeredReader.deserialize(serialized), Foo.class);
+ }
+
@Test
public void testWriteClassName() {
{
diff --git
a/java/fory-core/src/test/java/org/apache/fory/resolver/MetaShareContextTest.java
b/java/fory-core/src/test/java/org/apache/fory/resolver/MetaShareContextTest.java
index 7eec8145c..0280473b1 100644
---
a/java/fory-core/src/test/java/org/apache/fory/resolver/MetaShareContextTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/resolver/MetaShareContextTest.java
@@ -28,6 +28,7 @@ import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
import org.apache.fory.context.MetaReadContext;
import org.apache.fory.context.MetaWriteContext;
+import org.apache.fory.exception.InsecureException;
import org.apache.fory.meta.TypeDef;
import org.apache.fory.test.bean.BeanA;
import org.apache.fory.test.bean.BeanB;
@@ -40,6 +41,12 @@ public class MetaShareContextTest extends ForyTestBase {
int cents();
}
+ public static class MetaSharedPayload {
+ public int value;
+
+ public MetaSharedPayload() {}
+ }
+
@Test
public void testShareClassName() {
Fory fory =
@@ -89,6 +96,21 @@ public class MetaShareContextTest extends ForyTestBase {
Assert.assertNull(typeInfo.getSerializer());
}
+ @Test
+ public void testMetaTypeDefAdmission() {
+ Fory writer =
+ Fory.builder()
+ .withXlang(false)
+ .withMetaShare(true)
+ .withCompatible(true)
+ .requireClassRegistration(false)
+ .build();
+ Fory reader =
Fory.builder().withXlang(false).withMetaShare(true).withCompatible(true).build();
+ TypeDef typeDef = TypeDef.buildTypeDef(writer.getTypeResolver(),
MetaSharedPayload.class);
+ Assert.assertThrows(
+ InsecureException.class, () ->
reader.getTypeResolver().buildMetaSharedTypeInfo(typeDef));
+ }
+
private void checkMetaShare(Fory fory, Object o) {
MetaWriteContext metaWriteContext = new MetaWriteContext();
MetaReadContext metaReadContext = new MetaReadContext();
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
index 2b0505e07..97899c4e4 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/ExceptionSerializersTest.java
@@ -60,6 +60,23 @@ public class ExceptionSerializersTest extends ForyTestBase {
Assert.assertEquals(copy.getSuppressed()[1].getMessage(), "suppressed-2");
}
+ @Test
+ public void testSuppressedCountLimit() {
+ Fory writer = Fory.builder().withXlang(false).build();
+ Fory reader =
Fory.builder().withXlang(false).withMaxCollectionSize(1).build();
+ RuntimeException value = new RuntimeException("outer");
+ RuntimeException suppressed1 = new RuntimeException("suppressed-1");
+ RuntimeException suppressed2 = new RuntimeException("suppressed-2");
+ value.setStackTrace(new StackTraceElement[0]);
+ suppressed1.setStackTrace(new StackTraceElement[0]);
+ suppressed2.setStackTrace(new StackTraceElement[0]);
+ value.addSuppressed(suppressed1);
+ value.addSuppressed(suppressed2);
+ byte[] bytes = writer.serialize(value);
+
+ Assert.assertThrows(ForyException.class, () -> reader.deserialize(bytes));
+ }
+
@Test(dataProvider = "javaFory")
public void testStackTraceElementRoundTrip(Fory fory) {
StackTraceElement value = new Exception().getStackTrace()[0];
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/JavaSerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/JavaSerializerTest.java
index f69260eeb..9afcd8b1c 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/JavaSerializerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/JavaSerializerTest.java
@@ -21,6 +21,7 @@ package org.apache.fory.serializer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InvalidClassException;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamConstants;
import java.io.Serializable;
@@ -31,6 +32,7 @@ import lombok.Data;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
import org.apache.fory.memory.BigEndian;
+import org.apache.fory.memory.MemoryBuffer;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -52,6 +54,16 @@ public class JavaSerializerTest extends ForyTestBase {
}
}
+ public static class JavaBox implements Serializable {
+ Object value;
+
+ JavaBox(Object value) {
+ this.value = value;
+ }
+ }
+
+ public static class NestedValue implements Serializable {}
+
@Test
public void testWriteObject() {
Fory fory =
@@ -85,4 +97,16 @@ public class JavaSerializerTest extends ForyTestBase {
fory.registerSerializer(URL.class, JavaSerializer.class);
copyCheck(fory, url);
}
+
+ @Test
+ public void testJdkStreamChecksNestedClass() {
+ Fory fory = Fory.builder().withXlang(false).build();
+ Serializer serializer = new JavaSerializer(fory.getTypeResolver(),
JavaBox.class);
+ fory.registerSerializer(JavaBox.class, serializer);
+ MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(128);
+ writeSerializer(fory, serializer, buffer, new JavaBox(new NestedValue()));
+
+ Assert.assertThrows(
+ InvalidClassException.class, () -> readSerializer(fory, serializer,
buffer));
+ }
}
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/JdkProxySerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/JdkProxySerializerTest.java
index 01f664a73..b3ebe2724 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/JdkProxySerializerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/JdkProxySerializerTest.java
@@ -88,6 +88,54 @@ public class JdkProxySerializerTest extends ForyTestBase {
assertThrows(InsecureException.class, () -> reader.deserialize(bytes));
}
+ @Test
+ public void testJdkProxyStrictInterfaces() {
+ Fory fory =
Fory.builder().withXlang(false).requireClassRegistration(true).build();
+ fory.register(TestInvocationHandler.class);
+ Function function =
+ (Function)
+ Proxy.newProxyInstance(
+ fory.getClassLoader(),
+ new Class[] {Function.class, Serializable.class},
+ new TestInvocationHandler());
+
+ Function deserializedFunction = (Function)
fory.deserialize(fory.serialize(function));
+ assertEquals(deserializedFunction.apply(null), 1);
+ }
+
+ @Test
+ public void testJdkProxyStrictNoDefaultInterface() {
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ TestInterface function =
+ (TestInterface)
+ Proxy.newProxyInstance(
+ writer.getClassLoader(),
+ new Class[] {TestInterface.class},
+ new TestInvocationHandler());
+ byte[] bytes = writer.serialize(function);
+
+ Fory reader =
Fory.builder().withXlang(false).requireClassRegistration(true).build();
+ reader.register(TestInvocationHandler.class);
+ TestInterface deserializedFunction = (TestInterface)
reader.deserialize(bytes);
+ assertEquals(deserializedFunction.test(), 1);
+ }
+
+ @Test
+ public void testJdkProxyStrictRejectsDefaultInterface() {
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ TestDefaultInterface function =
+ (TestDefaultInterface)
+ Proxy.newProxyInstance(
+ writer.getClassLoader(),
+ new Class[] {TestDefaultInterface.class},
+ new TestInvocationHandler());
+ byte[] bytes = writer.serialize(function);
+
+ Fory reader =
Fory.builder().withXlang(false).requireClassRegistration(true).build();
+ reader.register(TestInvocationHandler.class);
+ assertThrows(InsecureException.class, () -> reader.deserialize(bytes));
+ }
+
@Test(dataProvider = "foryCopyConfig")
public void testJdkProxy(Fory fory) {
Function function =
@@ -206,7 +254,13 @@ public class JdkProxySerializerTest extends ForyTestBase {
}
interface TestInterface {
- void test();
+ int test();
+ }
+
+ interface TestDefaultInterface {
+ default int test() {
+ return 1;
+ }
}
static class ProxyFactory {
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/LambdaSerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/LambdaSerializerTest.java
index 099540b6f..a508d0843 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/LambdaSerializerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/LambdaSerializerTest.java
@@ -33,6 +33,9 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
+import org.apache.fory.exception.DeserializationException;
+import org.apache.fory.exception.InsecureException;
+import org.testng.Assert;
import org.testng.annotations.Test;
@SuppressWarnings("unchecked")
@@ -105,6 +108,33 @@ public class LambdaSerializerTest extends ForyTestBase {
assertEquals(newFunc.apply(10), Integer.valueOf(17));
}
+ @Test
+ public void testSerializedLambdaAdmission() throws Exception {
+ int delta = 7;
+ Function<Integer, Integer> function =
+ (Serializable & Function<Integer, Integer>) (x) -> x + delta;
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ Fory reader = Fory.builder().withXlang(false).build();
+ byte[] bytes = writer.serialize(extractSerializedLambda(function));
+ Assert.assertThrows(InsecureException.class, () ->
reader.deserialize(bytes));
+ }
+
+ @Test
+ public void testSerializedLambdaArgLimit() throws Exception {
+ int delta = 7;
+ Function<Integer, Integer> function =
+ (Serializable & Function<Integer, Integer>) (x) -> x + delta;
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ Fory reader =
+ Fory.builder()
+ .withXlang(false)
+ .requireClassRegistration(false)
+ .withMaxCollectionSize(0)
+ .build();
+ byte[] bytes = writer.serialize(extractSerializedLambda(function));
+ Assert.assertThrows(DeserializationException.class, () ->
reader.deserialize(bytes));
+ }
+
@Test(dataProvider = "foryCopyConfig")
public void testSerializedLambdaCopy(Fory fory) throws Exception {
int delta = 7;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/ObjectStreamSerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/ObjectStreamSerializerTest.java
index d623d609c..66a93e71c 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/ObjectStreamSerializerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/ObjectStreamSerializerTest.java
@@ -48,6 +48,7 @@ import org.apache.fory.ForyTestBase;
import org.apache.fory.config.ForyBuilder;
import org.apache.fory.context.MetaReadContext;
import org.apache.fory.context.MetaWriteContext;
+import org.apache.fory.exception.InsecureException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.resolver.SharedRegistry;
import org.apache.fory.serializer.collection.CollectionSerializers;
@@ -1029,6 +1030,54 @@ public class ObjectStreamSerializerTest extends
ForyTestBase {
assertEquals(result.childValue, 42);
}
+ @Test
+ public void testObjectStreamExpectedParentLayer() {
+ Fory writerFory =
+
Fory.builder().withXlang(false).withRefTracking(true).withCodegen(false).build();
+ writerFory.register(HierarchyChildDefault.class);
+ writerFory.registerSerializer(
+ HierarchyChildDefault.class,
+ new ObjectStreamSerializer(writerFory.getTypeResolver(),
HierarchyChildDefault.class));
+
+ Fory readerFory =
+
Fory.builder().withXlang(false).withRefTracking(true).withCodegen(false).build();
+ readerFory.register(HierarchyChildDefault.class);
+ readerFory.registerSerializer(
+ HierarchyChildDefault.class,
+ new ObjectStreamSerializer(readerFory.getTypeResolver(),
HierarchyChildDefault.class));
+
+ HierarchyChildDefault obj = new HierarchyChildDefault("parent", "child",
42);
+ HierarchyChildDefault result =
+ (HierarchyChildDefault)
readerFory.deserialize(writerFory.serialize(obj));
+ assertEquals(result.parentData, "parent");
+ assertEquals(result.childData, "child");
+ assertEquals(result.childValue, 42);
+ }
+
+ @Test
+ public void testObjectStreamRejectsParentRoot() {
+ Fory writerFory =
+ Fory.builder()
+ .withXlang(false)
+ .requireClassRegistration(false)
+ .withRefTracking(true)
+ .withCodegen(false)
+ .build();
+ writerFory.registerSerializer(
+ HierarchyParentPutFields.class,
+ new ObjectStreamSerializer(writerFory.getTypeResolver(),
HierarchyParentPutFields.class));
+
+ Fory readerFory =
+
Fory.builder().withXlang(false).withRefTracking(true).withCodegen(false).build();
+ readerFory.register(HierarchyChildDefault.class);
+ readerFory.registerSerializer(
+ HierarchyChildDefault.class,
+ new ObjectStreamSerializer(readerFory.getTypeResolver(),
HierarchyChildDefault.class));
+
+ byte[] bytes = writerFory.serialize(new
HierarchyParentPutFields("parent"));
+ Assert.assertThrows(InsecureException.class, () ->
readerFory.deserialize(bytes));
+ }
+
// ==================== Cross-Fory Instance Schema Tests ====================
@Test(dataProvider = "compatibleModeProvider")
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/ReplaceResolveSerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/ReplaceResolveSerializerTest.java
index 027f00617..143a3e983 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/ReplaceResolveSerializerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/ReplaceResolveSerializerTest.java
@@ -20,6 +20,7 @@
package org.apache.fory.serializer;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotSame;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertThrows;
@@ -40,6 +41,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
+import org.apache.fory.exception.InsecureException;
import org.apache.fory.util.Preconditions;
import org.testng.annotations.Test;
@@ -617,6 +619,56 @@ public class ReplaceResolveSerializerTest extends
ForyTestBase {
assertEquals(o.f1, 10);
}
+ static class ReplaceProtectedExternalizable implements Externalizable {
+ static boolean readExternalCalled;
+ private int f1;
+
+ public ReplaceProtectedExternalizable() {}
+
+ ReplaceProtectedExternalizable(int f1) {
+ this.f1 = f1;
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(f1);
+ }
+
+ @Override
+ public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
+ readExternalCalled = true;
+ f1 = in.readInt();
+ }
+
+ private Object writeReplace() {
+ return this;
+ }
+ }
+
+ @Test
+ public void testRejectExternalizableReplace() {
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ byte[] bytes = writer.serialize(new ReplaceProtectedExternalizable(10));
+ ReplaceProtectedExternalizable.readExternalCalled = false;
+
+ Fory reader = Fory.builder().withXlang(false).build();
+ assertThrows(InsecureException.class, () -> reader.deserialize(bytes));
+ assertFalse(ReplaceProtectedExternalizable.readExternalCalled);
+ }
+
+ @Test
+ public void testRegisteredExternalizableReplace() {
+ Fory writer =
Fory.builder().withXlang(false).requireClassRegistration(false).build();
+ byte[] bytes = writer.serialize(new ReplaceProtectedExternalizable(10));
+ ReplaceProtectedExternalizable.readExternalCalled = false;
+
+ Fory reader = Fory.builder().withXlang(false).build();
+ reader.register(ReplaceProtectedExternalizable.class);
+ ReplaceProtectedExternalizable o = (ReplaceProtectedExternalizable)
reader.deserialize(bytes);
+ assertTrue(ReplaceProtectedExternalizable.readExternalCalled);
+ assertEquals(o.f1, 10);
+ }
+
static class ReplaceSelfExternalizable implements Externalizable {
private transient int f1;
private transient boolean newInstance;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/SerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/SerializersTest.java
index f25ed88c6..89bb3e390 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/SerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/SerializersTest.java
@@ -24,6 +24,8 @@ import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertThrows;
import static org.testng.Assert.assertTrue;
+import java.io.Externalizable;
+import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
@@ -31,18 +33,25 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Currency;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
import java.util.regex.Pattern;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
import org.apache.fory.config.ForyBuilder;
import org.apache.fory.exception.DeserializationException;
+import org.apache.fory.exception.InsecureException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
import org.testng.Assert;
@@ -277,6 +286,14 @@ public class SerializersTest extends ForyTestBase {
private static class TestClassSerialization {}
+ private interface TestClassTokenInterface {
+ void test();
+ }
+
+ private interface TestDefaultClassTokenInterface {
+ default void test() {}
+ }
+
private static class TestReplaceClassSerialization {
private Object writeReplace() {
return 1;
@@ -300,6 +317,22 @@ public class SerializersTest extends ForyTestBase {
serDe(fory, new TestReplaceClassSerialization());
}
+ @Test
+ public void testDefaultSafeClassTokens() {
+ Fory fory =
Fory.builder().withXlang(false).requireClassRegistration(true).build();
+ assertSame(serDe(fory, Serializable.class), Serializable.class);
+ assertSame(serDe(fory, Externalizable.class), Externalizable.class);
+ assertSame(serDe(fory, Function.class), Function.class);
+ assertSame(serDe(fory, Collection.class), Collection.class);
+ assertSame(serDe(fory, List.class), List.class);
+ assertSame(serDe(fory, Set.class), Set.class);
+ assertSame(serDe(fory, Map.class), Map.class);
+ assertSame(serDe(fory, SortedMap.class), SortedMap.class);
+ assertSame(serDe(fory, SortedSet.class), SortedSet.class);
+ assertSame(serDe(fory, TestClassTokenInterface.class),
TestClassTokenInterface.class);
+ assertThrows(InsecureException.class, () -> serDe(fory,
TestDefaultClassTokenInterface.class));
+ }
+
@Test
public void testEmptyObject() {
Fory fory =
Fory.builder().withXlang(false).requireClassRegistration(true).build();
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java
index 95c41e671..d45f3271a 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/StringSerializerTest.java
@@ -33,6 +33,7 @@ import lombok.Data;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
import org.apache.fory.collection.Tuple2;
+import org.apache.fory.exception.DeserializationException;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.memory.MemoryUtils;
import org.apache.fory.platform.JdkVersion;
@@ -168,6 +169,18 @@ public class StringSerializerTest extends ForyTestBase {
}
}
+ @Test
+ public void testStringSizeLimit() {
+ Fory writer = Fory.builder().withXlang(false).build();
+ Fory reader = Fory.builder().withXlang(false).withMaxBinarySize(2).build();
+ MemoryBuffer buffer = MemoryUtils.buffer(32);
+ new StringSerializer(writer.getConfig()).writeString(buffer, "abcd");
+
+ Assert.assertThrows(
+ DeserializationException.class,
+ () -> new StringSerializer(reader.getConfig()).readString(buffer));
+ }
+
@Data
public static class Simple {
private String str;
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
index 2db9d5c84..25c3b6186 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/collection/MapSerializersTest.java
@@ -716,6 +716,33 @@ public class MapSerializersTest extends ForyTestBase {
MapSerializers.EnumMapSerializer.class);
}
+ @Test
+ public void testEmptyEnumMap() {
+ Fory fory = getJavaFory();
+ EnumMap<TestEnum, Object> enumMap = new EnumMap<>(TestEnum.class);
+ Serializer<EnumMap> serializer = fory.getSerializer(EnumMap.class);
+ MemoryBuffer buffer = MemoryUtils.buffer(64);
+ writeSerializer(fory, serializer, buffer, enumMap);
+ Assert.assertEquals(buffer.getByte(0), (byte) 0);
+ Assert.assertEquals(buffer.readerIndex(), 0);
+ EnumMap<TestEnum, Object> restored = readSerializer(fory, serializer,
buffer);
+ Assert.assertEquals(restored, enumMap);
+ restored.put(TestEnum.A, "value");
+ Assert.assertEquals(restored.get(TestEnum.A), "value");
+ }
+
+ @Test
+ public void testEnumMapHasNoPayloadMode() {
+ Fory fory = getJavaFory();
+ Serializer<EnumMap> serializer = fory.getSerializer(EnumMap.class);
+ EnumMap<TestEnum, Object> enumMap = new EnumMap<>(TestEnum.class);
+ enumMap.put(TestEnum.A, "value");
+ MemoryBuffer buffer = MemoryUtils.buffer(64);
+ writeSerializer(fory, serializer, buffer, enumMap);
+ Assert.assertEquals(buffer.getByte(0), (byte) 1);
+ Assert.assertEquals(readSerializer(fory, serializer, buffer), enumMap);
+ }
+
@Test(dataProvider = "foryCopyConfig")
public void testEnumMap(Fory fory) {
EnumMap<TestEnum, Object> enumMap = new EnumMap<>(TestEnum.class);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]