diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java index 9891154af8..9f34a0d467 100644 --- a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java +++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java @@ -16,6 +16,9 @@ package com.google.gson.internal; +import static com.google.gson.internal.$Gson$Preconditions.checkArgument; +import static com.google.gson.internal.$Gson$Preconditions.checkNotNull; + import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; @@ -32,9 +35,6 @@ 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; - /** * Static methods for working with types. * @@ -486,6 +486,7 @@ private static final class ParameterizedTypeImpl implements ParameterizedType, S private final Type[] typeArguments; public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { + checkNotNull(rawType); // require an owner type if the raw type needs it if (rawType instanceof Class) { Class rawTypeAsClass = (Class) rawType; @@ -552,6 +553,7 @@ private static final class GenericArrayTypeImpl implements GenericArrayType, Ser private final Type componentType; public GenericArrayTypeImpl(Type componentType) { + checkNotNull(componentType); this.componentType = canonicalize(componentType); } diff --git a/gson/src/main/java/com/google/gson/reflect/TypeToken.java b/gson/src/main/java/com/google/gson/reflect/TypeToken.java index b12d201f7b..0124757d3a 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -16,8 +16,8 @@ package com.google.gson.reflect; -import com.google.gson.internal.$Gson$Types; import com.google.gson.internal.$Gson$Preconditions; +import com.google.gson.internal.$Gson$Types; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -319,8 +319,46 @@ public static TypeToken get(Class type) { /** * Gets type literal for the parameterized type represented by applying {@code typeArguments} to * {@code rawType}. + * + * @throws IllegalArgumentException + * If {@code rawType} is not of type {@code Class}, or if the type arguments are invalid for + * the raw type */ public static TypeToken getParameterized(Type rawType, Type... typeArguments) { + $Gson$Preconditions.checkNotNull(rawType); + $Gson$Preconditions.checkNotNull(typeArguments); + + // Perform basic validation here because this is the only public API where users + // can create malformed parameterized types + if (!(rawType instanceof Class)) { + // See also https://bugs.openjdk.org/browse/JDK-8250659 + throw new IllegalArgumentException("rawType must be of type Class, but was " + rawType); + } + Class rawClass = (Class) rawType; + TypeVariable[] typeVariables = rawClass.getTypeParameters(); + + int expectedArgsCount = typeVariables.length; + int actualArgsCount = typeArguments.length; + if (actualArgsCount != expectedArgsCount) { + throw new IllegalArgumentException(rawClass.getName() + " requires " + expectedArgsCount + + " type arguments, but got " + actualArgsCount); + } + + for (int i = 0; i < expectedArgsCount; i++) { + Type typeArgument = typeArguments[i]; + Class rawTypeArgument = $Gson$Types.getRawType(typeArgument); + TypeVariable typeVariable = typeVariables[i]; + + for (Type bound : typeVariable.getBounds()) { + Class rawBound = $Gson$Types.getRawType(bound); + + if (!rawBound.isAssignableFrom(rawTypeArgument)) { + throw new IllegalArgumentException("Type argument " + typeArgument + " does not satisfy bounds " + + "for type variable " + typeVariable + " declared by " + rawType); + } + } + } + return new TypeToken<>($Gson$Types.newParameterizedTypeWithOwner(null, rawType, typeArguments)); } diff --git a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java index 1cdd7361af..55c2e82357 100644 --- a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java +++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java @@ -16,6 +16,7 @@ package com.google.gson.reflect; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -91,6 +92,12 @@ public void testArrayFactory() { TypeToken expectedListOfStringArray = new TypeToken[]>() {}; Type listOfString = new TypeToken>() {}.getType(); assertEquals(expectedListOfStringArray, TypeToken.getArray(listOfString)); + + try { + TypeToken.getArray(null); + fail(); + } catch (NullPointerException e) { + } } public void testParameterizedFactory() { @@ -104,6 +111,97 @@ public void testParameterizedFactory() { Type listOfString = TypeToken.getParameterized(List.class, String.class).getType(); Type listOfListOfString = TypeToken.getParameterized(List.class, listOfString).getType(); assertEquals(expectedListOfListOfListOfString, TypeToken.getParameterized(List.class, listOfListOfString)); + + TypeToken expectedWithExactArg = new TypeToken>() {}; + assertEquals(expectedWithExactArg, TypeToken.getParameterized(GenericWithBound.class, Number.class)); + + TypeToken expectedWithSubclassArg = new TypeToken>() {}; + assertEquals(expectedWithSubclassArg, TypeToken.getParameterized(GenericWithBound.class, Integer.class)); + + TypeToken expectedSatisfyingTwoBounds = new TypeToken>() {}; + assertEquals(expectedSatisfyingTwoBounds, TypeToken.getParameterized(GenericWithMultiBound.class, ClassSatisfyingBounds.class)); + } + + public void testParameterizedFactory_Invalid() { + try { + TypeToken.getParameterized(null, new Type[0]); + fail(); + } catch (NullPointerException e) { + } + + GenericArrayType arrayType = (GenericArrayType) TypeToken.getArray(String.class).getType(); + try { + TypeToken.getParameterized(arrayType, new Type[0]); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("rawType must be of type Class, but was java.lang.String[]", e.getMessage()); + } + + try { + TypeToken.getParameterized(String.class, String.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("java.lang.String requires 0 type arguments, but got 1", e.getMessage()); + } + + try { + TypeToken.getParameterized(List.class, new Type[0]); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("java.util.List requires 1 type arguments, but got 0", e.getMessage()); + } + + try { + TypeToken.getParameterized(List.class, String.class, String.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("java.util.List requires 1 type arguments, but got 2", e.getMessage()); + } + + try { + TypeToken.getParameterized(GenericWithBound.class, String.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Type argument class java.lang.String does not satisfy bounds " + + "for type variable T declared by " + GenericWithBound.class, + e.getMessage()); + } + + try { + TypeToken.getParameterized(GenericWithBound.class, Object.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Type argument class java.lang.Object does not satisfy bounds " + + "for type variable T declared by " + GenericWithBound.class, + e.getMessage()); + } + + try { + TypeToken.getParameterized(GenericWithMultiBound.class, Number.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Type argument class java.lang.Number does not satisfy bounds " + + "for type variable T declared by " + GenericWithMultiBound.class, + e.getMessage()); + } + + try { + TypeToken.getParameterized(GenericWithMultiBound.class, CharSequence.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Type argument interface java.lang.CharSequence does not satisfy bounds " + + "for type variable T declared by " + GenericWithMultiBound.class, + e.getMessage()); + } + + try { + TypeToken.getParameterized(GenericWithMultiBound.class, Object.class); + fail(); + } catch (IllegalArgumentException e) { + assertEquals("Type argument class java.lang.Object does not satisfy bounds " + + "for type variable T declared by " + GenericWithMultiBound.class, + e.getMessage()); + } } private static class CustomTypeToken extends TypeToken { @@ -158,3 +256,13 @@ public void testTypeTokenRaw() { } } } + +// Have to declare these classes here as top-level classes because otherwise tests for +// TypeToken.getParameterized fail due to owner type mismatch +class GenericWithBound { +} +class GenericWithMultiBound { +} +@SuppressWarnings("serial") +abstract class ClassSatisfyingBounds extends Number implements CharSequence { +}