Validate TypeToken.getParameterized arguments (#2166)

This commit is contained in:
Marcono1234 2022-08-05 15:59:38 +02:00 committed by GitHub
parent 0b6a7bf7d9
commit 98f2bbf4c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 152 additions and 4 deletions

View File

@ -16,6 +16,9 @@
package com.google.gson.internal; 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.io.Serializable;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericArrayType;
@ -32,9 +35,6 @@ import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Properties; 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. * Static methods for working with types.
* *
@ -486,6 +486,7 @@ public final class $Gson$Types {
private final Type[] typeArguments; private final Type[] typeArguments;
public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
checkNotNull(rawType);
// require an owner type if the raw type needs it // require an owner type if the raw type needs it
if (rawType instanceof Class<?>) { if (rawType instanceof Class<?>) {
Class<?> rawTypeAsClass = (Class<?>) rawType; Class<?> rawTypeAsClass = (Class<?>) rawType;
@ -552,6 +553,7 @@ public final class $Gson$Types {
private final Type componentType; private final Type componentType;
public GenericArrayTypeImpl(Type componentType) { public GenericArrayTypeImpl(Type componentType) {
checkNotNull(componentType);
this.componentType = canonicalize(componentType); this.componentType = canonicalize(componentType);
} }

View File

@ -16,8 +16,8 @@
package com.google.gson.reflect; 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$Preconditions;
import com.google.gson.internal.$Gson$Types;
import java.lang.reflect.GenericArrayType; import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
@ -319,8 +319,46 @@ public class TypeToken<T> {
/** /**
* Gets type literal for the parameterized type represented by applying {@code typeArguments} to * Gets type literal for the parameterized type represented by applying {@code typeArguments} to
* {@code rawType}. * {@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) { 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)); return new TypeToken<>($Gson$Types.newParameterizedTypeWithOwner(null, rawType, typeArguments));
} }

View File

@ -16,6 +16,7 @@
package com.google.gson.reflect; package com.google.gson.reflect;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -91,6 +92,12 @@ public final class TypeTokenTest extends TestCase {
TypeToken<?> expectedListOfStringArray = new TypeToken<List<String>[]>() {}; TypeToken<?> expectedListOfStringArray = new TypeToken<List<String>[]>() {};
Type listOfString = new TypeToken<List<String>>() {}.getType(); Type listOfString = new TypeToken<List<String>>() {}.getType();
assertEquals(expectedListOfStringArray, TypeToken.getArray(listOfString)); assertEquals(expectedListOfStringArray, TypeToken.getArray(listOfString));
try {
TypeToken.getArray(null);
fail();
} catch (NullPointerException e) {
}
} }
public void testParameterizedFactory() { public void testParameterizedFactory() {
@ -104,6 +111,97 @@ public final class TypeTokenTest extends TestCase {
Type listOfString = TypeToken.getParameterized(List.class, String.class).getType(); Type listOfString = TypeToken.getParameterized(List.class, String.class).getType();
Type listOfListOfString = TypeToken.getParameterized(List.class, listOfString).getType(); Type listOfListOfString = TypeToken.getParameterized(List.class, listOfString).getType();
assertEquals(expectedListOfListOfListOfString, TypeToken.getParameterized(List.class, listOfListOfString)); assertEquals(expectedListOfListOfListOfString, TypeToken.getParameterized(List.class, listOfListOfString));
TypeToken<?> expectedWithExactArg = new TypeToken<GenericWithBound<Number>>() {};
assertEquals(expectedWithExactArg, TypeToken.getParameterized(GenericWithBound.class, Number.class));
TypeToken<?> expectedWithSubclassArg = new TypeToken<GenericWithBound<Integer>>() {};
assertEquals(expectedWithSubclassArg, TypeToken.getParameterized(GenericWithBound.class, Integer.class));
TypeToken<?> expectedSatisfyingTwoBounds = new TypeToken<GenericWithMultiBound<ClassSatisfyingBounds>>() {};
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<String> { private static class CustomTypeToken extends TypeToken<String> {
@ -158,3 +256,13 @@ public final class TypeTokenTest extends TestCase {
} }
} }
} }
// Have to declare these classes here as top-level classes because otherwise tests for
// TypeToken.getParameterized fail due to owner type mismatch
class GenericWithBound<T extends Number> {
}
class GenericWithMultiBound<T extends Number & CharSequence> {
}
@SuppressWarnings("serial")
abstract class ClassSatisfyingBounds extends Number implements CharSequence {
}