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]

Reply via email to