Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve ConstructorConstructor #2068

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions gson/src/main/java/com/google/gson/Gson.java
Expand Up @@ -993,6 +993,9 @@ public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J
TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
TypeAdapter<T> typeAdapter = getAdapter(typeToken);
T object = typeAdapter.read(reader);

// Verify that adapter created the correct type
Primitives.wrap(typeToken.getRawType()).cast(object);
return object;
} catch (EOFException e) {
/*
Expand Down
173 changes: 109 additions & 64 deletions gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java
Expand Up @@ -36,15 +36,9 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

/**
Expand Down Expand Up @@ -261,7 +255,6 @@ public T construct() {
* Constructors for common interface types like Map and List and their
* subtypes.
*/
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
final Type type, Class<? super T> rawType) {

Expand All @@ -274,68 +267,120 @@ private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
*/

if (Collection.class.isAssignableFrom(rawType)) {
if (SortedSet.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new TreeSet<>();
}
};
} else if (Set.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedHashSet<>();
}
};
} else if (Queue.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ArrayDeque<>();
}
};
} else {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ArrayList<>();
}
};
}
@SuppressWarnings("unchecked")
ObjectConstructor<T> constructor = (ObjectConstructor<T>) newCollectionConstructor(type, rawType);
return constructor;
}

if (Map.class.isAssignableFrom(rawType)) {
if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ConcurrentSkipListMap<>();
}
};
} else if (ConcurrentMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new ConcurrentHashMap<>();
}
};
} else if (SortedMap.class.isAssignableFrom(rawType)) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new TreeMap<>();
}
};
} else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedHashMap<>();
}
};
} else {
return new ObjectConstructor<T>() {
@Override public T construct() {
return (T) new LinkedTreeMap<>();
}
};
}
@SuppressWarnings("unchecked")
ObjectConstructor<T> constructor = (ObjectConstructor<T>) newMapConstructor(type, rawType);
return constructor;
}

// Unsupported type; try other means of creating constructor
return null;
}

private static ObjectConstructor<? extends Collection<? extends Object>> newCollectionConstructor(
final Type type, Class<?> rawType) {

// First try List implementation
if (rawType.isAssignableFrom(ArrayList.class)) {
return new ObjectConstructor<ArrayList<Object>>() {
@Override public ArrayList<Object> construct() {
return new ArrayList<>();
}
};
}
// Then try Set implementation
else if (rawType.isAssignableFrom(LinkedHashSet.class)) {
return new ObjectConstructor<LinkedHashSet<Object>>() {
@Override public LinkedHashSet<Object> construct() {
return new LinkedHashSet<>();
}
};
}
// Then try SortedSet / NavigableSet implementation
else if (rawType.isAssignableFrom(TreeSet.class)) {
return new ObjectConstructor<TreeSet<Object>>() {
@Override public TreeSet<Object> construct() {
return new TreeSet<>();
}
};
}
// Then try Queue implementation
else if (rawType.isAssignableFrom(ArrayDeque.class)) {
return new ObjectConstructor<ArrayDeque<Object>>() {
@Override public ArrayDeque<Object> construct() {
return new ArrayDeque<>();
}
};
}

// Was unable to create matching Collection constructor
return null;
}

private static boolean hasStringKeyType(Type mapType) {
// If mapType is not parameterized, assume it might have String as key type
if (!(mapType instanceof ParameterizedType)) {
return true;
}

Type[] typeArguments = ((ParameterizedType) mapType).getActualTypeArguments();
if (typeArguments.length == 0) {
return false;
}
// Consider String and supertypes of it
return TypeToken.get(typeArguments[0]).getRawType().isAssignableFrom(String.class);
}

private static ObjectConstructor<? extends Map<? extends Object, Object>> newMapConstructor(final Type type, Class<?> rawType) {
// First try Map implementation
/*
* Legacy special casing for Map<String, ...> to avoid DoS from colliding String hashCode
* values for older JDKs; use own LinkedTreeMap<String, Object> instead
*/
if (rawType.isAssignableFrom(LinkedHashMap.class) && !hasStringKeyType(type)) {
return new ObjectConstructor<LinkedHashMap<Object, Object>>() {
@Override public LinkedHashMap<Object, Object> construct() {
return new LinkedHashMap<>();
}
};
} else if (rawType.isAssignableFrom(LinkedTreeMap.class)) {
return new ObjectConstructor<LinkedTreeMap<String, Object>>() {
@Override public LinkedTreeMap<String, Object> construct() {
return new LinkedTreeMap<>();
}
};
}
// Then try SortedMap / NavigableMap implementation
else if (rawType.isAssignableFrom(TreeMap.class)) {
return new ObjectConstructor<TreeMap<Object, Object>>() {
@Override public TreeMap<Object, Object> construct() {
return new TreeMap<>();
}
};
}
// Then try ConcurrentMap implementation
else if (rawType.isAssignableFrom(ConcurrentHashMap.class)) {
return new ObjectConstructor<ConcurrentHashMap<Object, Object>>() {
@Override public ConcurrentHashMap<Object, Object> construct() {
return new ConcurrentHashMap<>();
}
};
}
// Then try ConcurrentNavigableMap implementation
else if (rawType.isAssignableFrom(ConcurrentSkipListMap.class)) {
return new ObjectConstructor<ConcurrentSkipListMap<Object, Object>>() {
@Override public ConcurrentSkipListMap<Object, Object> construct() {
return new ConcurrentSkipListMap<>();
}
};
}

// Was unable to create matching Map constructor
return null;
}

Expand Down
23 changes: 11 additions & 12 deletions gson/src/test/java/com/google/gson/functional/CollectionTest.java
Expand Up @@ -16,6 +16,16 @@

package com.google.gson.functional;

import static org.junit.Assert.assertArrayEquals;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -30,18 +40,7 @@
import java.util.Set;
import java.util.Stack;
import java.util.Vector;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.reflect.TypeToken;

import junit.framework.TestCase;
import static org.junit.Assert.assertArrayEquals;

/**
* Functional tests for Json serialization and deserialization of collections.
Expand Down Expand Up @@ -324,7 +323,7 @@ public void testFieldIsArrayList() {
HasArrayListField copy = gson.fromJson("{\"longs\":[1,3]}", HasArrayListField.class);
assertEquals(Arrays.asList(1L, 3L), copy.longs);
}

public void testUserCollectionTypeAdapter() {
Type listOfString = new TypeToken<List<String>>() {}.getType();
Object stringListSerializer = new JsonSerializer<List<String>>() {
Expand Down
61 changes: 47 additions & 14 deletions gson/src/test/java/com/google/gson/functional/MapTest.java
Expand Up @@ -16,18 +16,6 @@

package com.google.gson.functional;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
Expand All @@ -41,8 +29,20 @@
import com.google.gson.JsonSyntaxException;
import com.google.gson.common.TestTypes;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.LinkedTreeMap;
import com.google.gson.reflect.TypeToken;

import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import junit.framework.TestCase;

/**
Expand Down Expand Up @@ -195,6 +195,39 @@ public void testMapDeserializationWithUnquotedLongKeys() {
assertEquals("456", map.get(longKey));
}

public void testMapStringKeyDeserialization() {
Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
Map<?, ?> map = gson.fromJson("{\"a\":1}", typeOfMap);

assertTrue("Map<String, ...> should use LinkedTreeMap to protect against DoS in older JDK versions",
map instanceof LinkedTreeMap);

Map<?, ?> expectedMap = Collections.singletonMap("a", 1);
assertEquals(expectedMap, map);
}

public void testMapStringSupertypeKeyDeserialization() {
Type typeOfMap = new TypeToken<Map<Object, Integer>>() {}.getType();
Map<?, ?> map = gson.fromJson("{\"a\":1}", typeOfMap);

assertTrue("Map<Object, ...> should use LinkedTreeMap to protect against DoS in older JDK versions",
map instanceof LinkedTreeMap);

Map<?, ?> expectedMap = Collections.singletonMap("a", 1);
assertEquals(expectedMap, map);
}

public void testMapNonStringKeyDeserialization() {
Type typeOfMap = new TypeToken<Map<Integer, Integer>>() {}.getType();
Map<?, ?> map = gson.fromJson("{\"1\":1}", typeOfMap);

assertFalse("non-Map<String, ...> should not use Gson Map implementation",
map instanceof LinkedTreeMap);

Map<?, ?> expectedMap = Collections.singletonMap(1, 1);
assertEquals(expectedMap, map);
}

public void testHashMapDeserialization() throws Exception {
Type typeOfMap = new TypeToken<HashMap<Integer, String>>() {}.getType();
HashMap<Integer, String> map = gson.fromJson("{\"123\":\"456\"}", typeOfMap);
Expand Down Expand Up @@ -587,7 +620,7 @@ public void testSerializeMapOfMaps() {
gson.toJson(map, type).replace('"', '\''));
}

public void testDeerializeMapOfMaps() {
public void testDeserializeMapOfMaps() {
Type type = new TypeToken<Map<String, Map<String, String>>>() {}.getType();
Map<String, Map<String, String>> map = newMap(
"a", newMap("ka1", "va1", "ka2", "va2"),
Expand Down
Expand Up @@ -111,7 +111,7 @@ public void testSerializeInternalImplementationObject() {
// But deserialization should fail
Class<?> internalClass = Collections.emptyList().getClass();
try {
gson.fromJson("{}", internalClass);
gson.fromJson("[]", internalClass);
fail("Missing exception; test has to be run with `--illegal-access=deny`");
} catch (JsonIOException expected) {
assertTrue(expected.getMessage().startsWith(
Expand Down