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:
Mike Cumings 2018-09-25 16:09:48 -07:00
parent 3f4ac29f91
commit e2296f42d5
2 changed files with 92 additions and 15 deletions

View File

@ -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) {

View File

@ -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;
}
}