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 fa16b1e85 fix(Java): prevent StackOverflow in
normalizeIterableTypeArguments for self-referential collections (#3817)
fa16b1e85 is described below
commit fa16b1e8583769e82927b2661f5b407ca5e5696a
Author: Pigsy-Monk <[email protected]>
AuthorDate: Sat Jul 4 13:18:45 2026 +0800
fix(Java): prevent StackOverflow in normalizeIterableTypeArguments for
self-referential collections (#3817)
Add a check to detect when the element type's raw type matches the
container type, which would cause infinite recursion in
normalizeIterableTypeArguments.
This fixes issues with self-referential collections like Box<T>
implements List<Box<?>> where the element type is the same as the
container type.
## 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 or equivalent persisted links of the
final clean AI review results from both fresh reviewers described in
`AI_POLICY.md`, the Fory-guided reviewer and the independent general
reviewer, 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
---------
Co-authored-by: chaokunyang <[email protected]>
---
.../main/java/org/apache/fory/reflect/TypeRef.java | 62 ++++++++++++++++------
.../java/org/apache/fory/reflect/TypeRefTest.java | 49 +++++++++++++++++
2 files changed, 94 insertions(+), 17 deletions(-)
diff --git a/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
b/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
index 407a6ddfa..c7d5ece3b 100644
--- a/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
+++ b/java/fory-core/src/main/java/org/apache/fory/reflect/TypeRef.java
@@ -95,12 +95,24 @@ public class TypeRef<T> {
TypeExtMeta typeExtMeta,
List<TypeRef<?>> typeArguments,
TypeRef<?> componentType) {
+ this(type, typeExtMeta, typeArguments, componentType, true);
+ }
+
+ private TypeRef(
+ Type type,
+ TypeExtMeta typeExtMeta,
+ List<TypeRef<?>> typeArguments,
+ TypeRef<?> componentType,
+ boolean normalizeArgs) {
this.type = type;
this.typeExtMeta = typeExtMeta;
this.typeArguments =
typeArguments == null
? null
- : immutableTypeArguments(normalizeContainerTypeArguments(type,
typeArguments));
+ : immutableTypeArguments(
+ normalizeArgs
+ ? normalizeContainerTypeArguments(type, typeArguments)
+ : typeArguments);
this.componentType = componentType;
this.hasTypeExtMeta = hasNestedTypeExtMeta(typeExtMeta,
this.typeArguments, componentType);
}
@@ -150,9 +162,13 @@ public class TypeRef<T> {
}
private static List<TypeRef<?>> immutableTypeArguments(List<TypeRef<?>>
typeArguments) {
- return typeArguments == null
- ? null
- : Collections.unmodifiableList(new ArrayList<>(typeArguments));
+ if (typeArguments == null) {
+ return null;
+ }
+ if (typeArguments.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableList(new ArrayList<>(typeArguments));
}
private static List<TypeRef<?>> normalizeContainerTypeArguments(
@@ -175,10 +191,10 @@ public class TypeRef<T> {
if (!hasFullExplicitRawArgs(type, rawType, typeArguments)) {
return typeArguments;
}
+ TypeRef<?> elementType = rawIterableElementType(rawType);
return Collections.singletonList(
resolveTypeVariables(
- rawIterableElementType(rawType).getType(),
- explicitTypeVarRefs(rawType, typeArguments)));
+ elementType.getType(), explicitTypeVarRefs(rawType,
typeArguments), rawType));
}
private static List<TypeRef<?>> normalizeMapTypeArguments(
@@ -189,8 +205,8 @@ public class TypeRef<T> {
Tuple2<TypeRef<?>, TypeRef<?>> keyValueType = rawMapKeyValueTypes(rawType);
Map<TypeVariableKey, TypeRef<?>> typeVarRefs =
explicitTypeVarRefs(rawType, typeArguments);
return Arrays.asList(
- resolveTypeVariables(keyValueType.f0.getType(), typeVarRefs),
- resolveTypeVariables(keyValueType.f1.getType(), typeVarRefs));
+ resolveTypeVariables(keyValueType.f0.getType(), typeVarRefs, rawType),
+ resolveTypeVariables(keyValueType.f1.getType(), typeVarRefs, rawType));
}
private static boolean hasFullExplicitRawArgs(
@@ -241,7 +257,7 @@ public class TypeRef<T> {
}
private static TypeRef<?> resolveTypeVariables(
- Type type, Map<TypeVariableKey, TypeRef<?>> typeVarRefs) {
+ Type type, Map<TypeVariableKey, TypeRef<?>> typeVarRefs, Class<?>
containerRawType) {
if (type instanceof TypeVariable) {
TypeRef<?> typeRef = typeVarRefs.get(new
TypeVariableKey((TypeVariable<?>) type));
return typeRef == null ? TypeRef.of(type) : typeRef;
@@ -250,30 +266,42 @@ public class TypeRef<T> {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type ownerType = parameterizedType.getOwnerType();
Type resolvedOwnerType =
- ownerType == null ? null : resolveTypeVariables(ownerType,
typeVarRefs).getType();
+ ownerType == null
+ ? null
+ : resolveTypeVariables(ownerType, typeVarRefs,
containerRawType).getType();
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
List<TypeRef<?>> resolvedArguments = new
ArrayList<>(actualTypeArguments.length);
Type[] resolvedTypes = new Type[actualTypeArguments.length];
for (int i = 0; i < actualTypeArguments.length; i++) {
- TypeRef<?> resolvedType = resolveTypeVariables(actualTypeArguments[i],
typeVarRefs);
+ TypeRef<?> resolvedType =
+ resolveTypeVariables(actualTypeArguments[i], typeVarRefs,
containerRawType);
resolvedArguments.add(resolvedType);
resolvedTypes[i] = resolvedType.getType();
}
- return TypeRef.of(
+ ParameterizedType resolvedType =
new ParameterizedTypeImpl(
- resolvedOwnerType, parameterizedType.getRawType(),
resolvedTypes),
- null,
- resolvedArguments,
- null);
+ resolvedOwnerType, parameterizedType.getRawType(),
resolvedTypes);
+ if (resolvedType.getRawType() == containerRawType) {
+ // Self-referential containers have no finite normalized argument
tree. Keep the resolved
+ // element type, such as Box<?> or Box<String>, without expanding its
collection element
+ // arguments again.
+ return ofResolvedTypeArgs(resolvedType, Collections.emptyList());
+ }
+ return TypeRef.of(resolvedType, null, resolvedArguments, null);
}
if (type instanceof GenericArrayType) {
TypeRef<?> componentType =
- resolveTypeVariables(((GenericArrayType)
type).getGenericComponentType(), typeVarRefs);
+ resolveTypeVariables(
+ ((GenericArrayType) type).getGenericComponentType(),
typeVarRefs, containerRawType);
return TypeRef.of(newArrayType(componentType.getType()), null, null,
componentType);
}
return TypeRef.of(type);
}
+ private static TypeRef<?> ofResolvedTypeArgs(Type type, List<TypeRef<?>>
typeArguments) {
+ return new TypeRef<>(type, null, typeArguments, null, false);
+ }
+
private static boolean isMapLike(Class<?> rawType) {
return Map.class.isAssignableFrom(rawType) || isScalaMap(rawType);
}
diff --git
a/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
b/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
index ef85f4eef..0c64b0a07 100644
--- a/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/reflect/TypeRefTest.java
@@ -25,6 +25,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
+import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -104,6 +105,30 @@ public class TypeRefTest extends ForyTestBase {
static class MultiParamList<A, E> extends ArrayList<E> {}
+ static class Box<T> extends AbstractList<Box<?>> {
+ @Override
+ public Box<?> get(int index) {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+ }
+
+ static class RecursiveBox<T> extends AbstractList<RecursiveBox<T>> {
+ @Override
+ public RecursiveBox<T> get(int index) {
+ return null;
+ }
+
+ @Override
+ public int size() {
+ return 0;
+ }
+ }
+
static class MultiParamMap<A, K, V> extends HashMap<K, V> {}
static class StringKeyMap<V> extends HashMap<String, V> {}
@@ -157,6 +182,30 @@ public class TypeRefTest extends ForyTestBase {
Assert.assertEquals(fixedKeyMapGenericType.getTypeParameter1().getCls(),
List.class);
}
+ @Test
+ public void testSelfElementType() {
+ TypeRef<?> boxType = new TypeRef<Box<String>>() {};
+ Assert.assertEquals(boxType.getTypeArguments().size(), 1);
+ TypeRef<?> boxElementType = boxType.getTypeArguments().get(0);
+ Assert.assertEquals(boxElementType.getRawType(), Box.class);
+ Assert.assertTrue(boxElementType.getType() instanceof ParameterizedType);
+ Type boxArgument = ((ParameterizedType)
boxElementType.getType()).getActualTypeArguments()[0];
+ Assert.assertTrue(boxArgument instanceof WildcardType);
+ Assert.assertEquals(TypeUtils.getElementType(boxType), boxElementType);
+ Assert.assertNotEquals(boxElementType, TypeRef.of(String.class));
+
+ GenericType boxGenericType = GenericType.build(boxType);
+ Assert.assertEquals(boxGenericType.getTypeParametersCount(), 1);
+ Assert.assertEquals(boxGenericType.getTypeParameter0().getTypeRef(),
boxElementType);
+
Assert.assertEquals(boxGenericType.getTypeParameter0().getTypeParametersCount(),
0);
+
+ TypeRef<?> recursiveBoxType = new TypeRef<RecursiveBox<String>>() {};
+ TypeRef<?> recursiveElementType = new TypeRef<RecursiveBox<String>>() {};
+ Assert.assertEquals(recursiveBoxType.getTypeArguments().size(), 1);
+ Assert.assertEquals(recursiveBoxType.getTypeArguments().get(0),
recursiveElementType);
+ Assert.assertEquals(TypeUtils.getElementType(recursiveBoxType),
recursiveElementType);
+ }
+
@Test
public void testCustomContainerArrayNormalization() {
TypeRef<?> elementType =
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]