Fix issue with recursive type variable protections to fix #1390
When a type variable is referenced multiple times it needs to resolve to the same value. Previously, the second attempt would abort resolution early in order to protect against infinite recursion.
This commit is contained in:
parent
3f4ac29f91
commit
e2296f42d5
@ -25,7 +25,12 @@ import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.lang.reflect.WildcardType;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
|
||||
import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
|
||||
@ -334,41 +339,50 @@ public final class $Gson$Types {
|
||||
}
|
||||
|
||||
public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
|
||||
return resolve(context, contextRawType, toResolve, new HashSet<TypeVariable>());
|
||||
return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable, Type>());
|
||||
}
|
||||
|
||||
private static Type resolve(Type context, Class<?> contextRawType, Type toResolve,
|
||||
Collection<TypeVariable> visitedTypeVariables) {
|
||||
Map<TypeVariable, Type> visitedTypeVariables) {
|
||||
// this implementation is made a little more complicated in an attempt to avoid object-creation
|
||||
TypeVariable resolving = null;
|
||||
while (true) {
|
||||
if (toResolve instanceof TypeVariable) {
|
||||
TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
|
||||
if (visitedTypeVariables.contains(typeVariable)) {
|
||||
Type previouslyResolved = visitedTypeVariables.get(typeVariable);
|
||||
if (previouslyResolved != null) {
|
||||
// cannot reduce due to infinite recursion
|
||||
return toResolve;
|
||||
} else {
|
||||
visitedTypeVariables.add(typeVariable);
|
||||
return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved;
|
||||
}
|
||||
|
||||
// Insert a placeholder to mark the fact that we are in the process of resolving this type
|
||||
visitedTypeVariables.put(typeVariable, Void.TYPE);
|
||||
if (resolving == null) {
|
||||
resolving = typeVariable;
|
||||
}
|
||||
|
||||
toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
|
||||
if (toResolve == typeVariable) {
|
||||
return toResolve;
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
|
||||
Class<?> original = (Class<?>) toResolve;
|
||||
Type componentType = original.getComponentType();
|
||||
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
||||
return componentType == newComponentType
|
||||
toResolve = componentType == newComponentType
|
||||
? original
|
||||
: arrayOf(newComponentType);
|
||||
break;
|
||||
|
||||
} else if (toResolve instanceof GenericArrayType) {
|
||||
GenericArrayType original = (GenericArrayType) toResolve;
|
||||
Type componentType = original.getGenericComponentType();
|
||||
Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
|
||||
return componentType == newComponentType
|
||||
toResolve = componentType == newComponentType
|
||||
? original
|
||||
: arrayOf(newComponentType);
|
||||
break;
|
||||
|
||||
} else if (toResolve instanceof ParameterizedType) {
|
||||
ParameterizedType original = (ParameterizedType) toResolve;
|
||||
@ -388,9 +402,10 @@ public final class $Gson$Types {
|
||||
}
|
||||
}
|
||||
|
||||
return changed
|
||||
toResolve = changed
|
||||
? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
|
||||
: original;
|
||||
break;
|
||||
|
||||
} else if (toResolve instanceof WildcardType) {
|
||||
WildcardType original = (WildcardType) toResolve;
|
||||
@ -400,20 +415,28 @@ public final class $Gson$Types {
|
||||
if (originalLowerBound.length == 1) {
|
||||
Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
|
||||
if (lowerBound != originalLowerBound[0]) {
|
||||
return supertypeOf(lowerBound);
|
||||
toResolve = supertypeOf(lowerBound);
|
||||
break;
|
||||
}
|
||||
} else if (originalUpperBound.length == 1) {
|
||||
Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
|
||||
if (upperBound != originalUpperBound[0]) {
|
||||
return subtypeOf(upperBound);
|
||||
toResolve = subtypeOf(upperBound);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return original;
|
||||
toResolve = original;
|
||||
break;
|
||||
|
||||
} else {
|
||||
return toResolve;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ensure that any in-process resolution gets updated with the final result
|
||||
if (resolving != null) {
|
||||
visitedTypeVariables.put(resolving, toResolve);
|
||||
}
|
||||
return toResolve;
|
||||
}
|
||||
|
||||
static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
|
||||
|
@ -0,0 +1,54 @@
|
||||
package com.google.gson.functional;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* This test covers the scenario described in #1390 where a type variable needs to be used
|
||||
* by a type definition multiple times. Both type variable references should resolve to the
|
||||
* same underlying concrete type.
|
||||
*/
|
||||
public class ReusedTypeVariablesFullyResolveTest {
|
||||
|
||||
private Gson gson;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
gson = new GsonBuilder().create();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions") // The instances were being unmarshaled as Strings instead of TestEnums
|
||||
@Test
|
||||
public void testGenericsPreservation() {
|
||||
TestEnumSetCollection withSet = gson.fromJson("{\"collection\":[\"ONE\",\"THREE\"]}", TestEnumSetCollection.class);
|
||||
Iterator<TestEnum> iterator = withSet.collection.iterator();
|
||||
assertNotNull(withSet);
|
||||
assertNotNull(withSet.collection);
|
||||
assertEquals(2, withSet.collection.size());
|
||||
TestEnum first = iterator.next();
|
||||
TestEnum second = iterator.next();
|
||||
|
||||
assertTrue(first instanceof TestEnum);
|
||||
assertTrue(second instanceof TestEnum);
|
||||
}
|
||||
|
||||
enum TestEnum { ONE, TWO, THREE }
|
||||
|
||||
private static class TestEnumSetCollection extends SetCollection<TestEnum> {}
|
||||
|
||||
private static class SetCollection<T> extends BaseCollection<T, Set<T>> {}
|
||||
|
||||
private static class BaseCollection<U, C extends Collection<U>>
|
||||
{
|
||||
public C collection;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user