From febe8f80591f215c54ab11c8357a0ba05e73579f Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 16 Apr 2023 14:40:59 +0200 Subject: [PATCH 1/5] Prevent `TypeToken` from capturing type variables --- Troubleshooting.md | 22 ++++- .../com/google/gson/reflect/TypeToken.java | 66 +++++++++++-- .../google/gson/reflect/TypeTokenTest.java | 97 +++++++++++++++++++ 3 files changed, 177 insertions(+), 8 deletions(-) diff --git a/Troubleshooting.md b/Troubleshooting.md index 57e781cbb9..3954f7e960 100644 --- a/Troubleshooting.md +++ b/Troubleshooting.md @@ -17,7 +17,7 @@ This guide describes how to troubleshoot common issues when using Gson. See the [user guide](UserGuide.md#collections-examples) for more information. - When using `TypeToken` prefer the `Gson.fromJson` overloads with `TypeToken` parameter such as [`fromJson(Reader, TypeToken)`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/Gson.html#fromJson(java.io.Reader,com.google.gson.reflect.TypeToken)). The overloads with `Type` parameter do not provide any type-safety guarantees. -- When using `TypeToken` make sure you don't capture a type variable. For example avoid something like `new TypeToken>()` (where `T` is a type variable). Due to Java type erasure the actual type of `T` is not available at runtime. Refactor your code to pass around `TypeToken` instances or use [`TypeToken.getParameterized(...)`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/reflect/TypeToken.html#getParameterized(java.lang.reflect.Type,java.lang.reflect.Type...)), for example `TypeToken.getParameterized(List.class, elementClass)`. +- When using `TypeToken` make sure you don't capture a type variable. For example avoid something like `new TypeToken>()` (where `T` is a type variable). Due to Java [type erasure](https://dev.java/learn/generics/type-erasure/) the actual type of `T` is not available at runtime. Refactor your code to pass around `TypeToken` instances or use [`TypeToken.getParameterized(...)`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/reflect/TypeToken.html#getParameterized(java.lang.reflect.Type,java.lang.reflect.Type...)), for example `TypeToken.getParameterized(List.class, elementType)` where `elementType` is a type you have to provide separately. ## `InaccessibleObjectException`: 'module ... does not "opens ..." to unnamed module' @@ -327,3 +327,23 @@ Note: For newer Gson versions these rules might be applied automatically; make s For Android you can add this rule to the `proguard-rules.pro` file, see also the [Android documentation](https://developer.android.com/build/shrink-code#keep-code). In case the class name in the exception message is obfuscated, see the Android documentation about [retracing](https://developer.android.com/build/shrink-code#retracing). Note: If the class which you are trying to deserialize is actually abstract, then this exception is probably unrelated to R8 and you will have to implement a custom [`InstanceCreator`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/InstanceCreator.html) or [`TypeAdapter`](https://javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/TypeAdapter.html) which creates an instance of a non-abstract subclass of the class. + +## `IllegalArgumentException`: 'TypeToken type argument must not contain a type variable' + +**Symptom:** An exception with the message 'TypeToken type argument must not contain a type variable' is thrown + +**Reason:** This exception is thrown when you create an anonymous `TypeToken` subclass which captures a type variable, for example `new TypeToken>() {}` (where `T` is a type variable). At compile time such code looks safe and you can use the type `List` without any warnings. However, this code is not actually type safe because at runtime due to [type erasure](https://dev.java/learn/generics/type-erasure/) only the upper bound of the type variable is available. For the previous example that would be `List`. When using such a `TypeToken` with any Gson methods performing deserialization this would lead to confusing and difficult to debug `ClassCastException`s. For serialization it can in some cases also lead to undesired results. + +Note: Earlier version of Gson unfortunately did not prevent capturing type variables, which caused many users to unwittingly write type unsafe code. + +**Solution:** + +- Use [`TypeToken.getParameterized(...)`](https://www.javadoc.io/doc/com.google.code.gson/gson/latest/com.google.gson/com/google/gson/reflect/TypeToken.html#getParameterized(java.lang.reflect.Type,java.lang.reflect.Type...)), for example `TypeToken.getParameterized(List.class, elementType)` where `elementType` is a type you have to provide separately. +- For Kotlin users: Use [`reified` type parameters](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters), that means change `` to ``, if possible. If you have a chain of functions with type parameters you will probably have to make all of them `reified`. +- If you don't actually use Gson's `TypeToken` for any Gson method, use a general purpose 'type token' implementation provided by a different library instead, for example Guava's [`com.google.common.reflect.TypeToken`](https://javadoc.io/doc/com.google.guava/guava/latest/com/google/common/reflect/TypeToken.html). + +For backward compatibility it is possible to restore Gson's old behavior of allowing `TypeToken` to capture type variables by setting the [system property](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setProperty(java.lang.String,java.lang.String)) `gson.allow-capturing-type-variables` to `"true"`. +**Important:** + +- This does not solve any of the type safety problems mentioned above; in the long term you should prefer one of the other solutions listed above. This system property might be removed in future Gson versions. +- You should only ever set the property to `"true"`, but never to any other value or manually clear it. Otherwise this might counteract any libraries you are using which might have deliberately set the system property because they rely on its behavior. 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 4a695666c8..fc5270fb23 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -22,6 +22,7 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -38,11 +39,12 @@ *

* {@code TypeToken> list = new TypeToken>() {};} * - *

Capturing a type variable as type argument of a {@code TypeToken} should - * be avoided. Due to type erasure the runtime type of a type variable is not - * available to Gson and therefore it cannot provide the functionality one - * might expect, which gives a false sense of type-safety at compilation time - * and can lead to an unexpected {@code ClassCastException} at runtime. + *

Capturing a type variable as type argument of an anonymous {@code TypeToken} + * subclass is not allowed, for example {@code TypeToken>}. + * Due to type erasure the runtime type of a type variable is not available + * to Gson and therefore it cannot provide the functionality one might expect. + * This would give a false sense of type-safety at compile time and could + * lead to an unexpected {@code ClassCastException} at runtime. * *

If the type arguments of the parameterized type are only available at * runtime, for example when you want to create a {@code List} based on @@ -64,7 +66,14 @@ public class TypeToken { * *

Clients create an empty anonymous subclass. Doing so embeds the type * parameter in the anonymous class's type hierarchy so we can reconstitute it - * at runtime despite erasure. + * at runtime despite erasure, for example: + *

+ * {@code new TypeToken>() {}} + * + * @throws IllegalArgumentException + * If the anonymous {@code TypeToken} subclass captures a type variable, + * for example {@code TypeToken>}. See the {@code TypeToken} + * class documentation for more details. */ @SuppressWarnings("unchecked") protected TypeToken() { @@ -83,6 +92,11 @@ private TypeToken(Type type) { this.hashCode = this.type.hashCode(); } + private static boolean isCapturingTypeVariablesForbidden() { + String value = System.getProperty("gson.allow-capturing-type-variables"); + return value == null || !value.equals("true"); + } + /** * Verifies that {@code this} is an instance of a direct subclass of TypeToken and * returns the type argument for {@code T} in {@link $Gson$Types#canonicalize @@ -93,7 +107,12 @@ private Type getTypeTokenTypeArgument() { if (superclass instanceof ParameterizedType) { ParameterizedType parameterized = (ParameterizedType) superclass; if (parameterized.getRawType() == TypeToken.class) { - return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); + Type typeArgument = $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); + + if (isCapturingTypeVariablesForbidden()) { + verifyNoTypeVariable(typeArgument); + } + return typeArgument; } } // Check for raw TypeToken as superclass @@ -108,6 +127,39 @@ else if (superclass == TypeToken.class) { throw new IllegalStateException("Must only create direct subclasses of TypeToken"); } + private static void verifyNoTypeVariable(Type type) { + if (type instanceof TypeVariable) { + TypeVariable typeVariable = (TypeVariable) type; + throw new IllegalArgumentException("TypeToken type argument must not contain a type variable; captured type variable " + + typeVariable.getName() + " declared by " + typeVariable.getGenericDeclaration() + + "\nSee " + TroubleshootingGuide.createUrl("typetoken-type-variable")); + } else if (type instanceof GenericArrayType) { + verifyNoTypeVariable(((GenericArrayType) type).getGenericComponentType()); + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type ownerType = parameterizedType.getOwnerType(); + if (ownerType != null) { + verifyNoTypeVariable(ownerType); + } + + for (Type typeArgument : parameterizedType.getActualTypeArguments()) { + verifyNoTypeVariable(typeArgument); + } + } else if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + for (Type bound : wildcardType.getLowerBounds()) { + verifyNoTypeVariable(bound); + } + for (Type bound : wildcardType.getUpperBounds()) { + verifyNoTypeVariable(bound); + } + } else if (type == null) { + // Occurs in Eclipse IDE and certain Java versions (e.g. Java 11.0.18) when capturing type variable + // declared by method of local class, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975 + throw new IllegalArgumentException("TypeToken captured `null` as type argument; probably a compiler / runtime bug"); + } + } + /** * Returns the raw (non-generic) type for this type. */ 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 d38f05b1ec..fcd7a25dee 100644 --- a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java +++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java @@ -20,8 +20,10 @@ import static org.junit.Assert.assertThrows; import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -230,6 +232,101 @@ class SubSubTypeToken2 extends SubTypeToken {} assertThat(e).hasMessageThat().isEqualTo("Must only create direct subclasses of TypeToken"); } + private static void createTypeTokenTypeVariable() { + new TypeToken() {}; + } + + /** + * TypeToken type argument must not contain a type variable because, due to + * type erasure, at runtime only the bound of the type variable is available + * which is likely not what the user wanted. + * + *

Note that type variables are allowed for the {@code TypeToken} factory + * methods calling {@code TypeToken(Type)} because for them the return type is + * {@code TypeToken} which does not give a false sense of type-safety. + */ + @Test + public void testTypeTokenTypeVariable() throws Exception { + // Put the test code inside generic class to be able to access `T` + class Enclosing { + class Inner {} + + void test() { + String expectedMessage = "TypeToken type argument must not contain a type variable;" + + " captured type variable T declared by " + Enclosing.class + + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable"; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new TypeToken() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + + e = assertThrows(IllegalArgumentException.class, () -> new TypeToken>>() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + + e = assertThrows(IllegalArgumentException.class, () -> new TypeToken>>() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + + e = assertThrows(IllegalArgumentException.class, () -> new TypeToken>>() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + + e = assertThrows(IllegalArgumentException.class, () -> new TypeToken[]>() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + + e = assertThrows(IllegalArgumentException.class, () -> new TypeToken.Inner>() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + + String systemProperty = "gson.allow-capturing-type-variables"; + try { + // Any value other than 'true' should be ignored + System.setProperty(systemProperty, "some-value"); + + e = assertThrows(IllegalArgumentException.class, () -> new TypeToken() {}); + assertThat(e).hasMessageThat().isEqualTo(expectedMessage); + } finally { + System.clearProperty(systemProperty); + } + + try { + System.setProperty(systemProperty, "true"); + + TypeToken typeToken = new TypeToken() {}; + assertThat(typeToken.getType()).isEqualTo(Enclosing.class.getTypeParameters()[0]); + } finally { + System.clearProperty(systemProperty); + } + } + + void testMethodTypeVariable() throws Exception { + Method testMethod = Enclosing.class.getDeclaredMethod("testMethodTypeVariable"); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new TypeToken() {}); + assertThat(e).hasMessageThat().isAnyOf("TypeToken type argument must not contain a type variable;" + + " captured type variable M declared by " + testMethod + + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable", + // Note: When running this test in Eclipse IDE or with certain Java versions it seems to capture `null` + // instead of the type variable + "TypeToken captured `null` as type argument; probably a compiler / runtime bug"); + } + } + + new Enclosing<>().test(); + new Enclosing<>().testMethodTypeVariable(); + + Method testMethod = TypeTokenTest.class.getDeclaredMethod("createTypeTokenTypeVariable"); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> createTypeTokenTypeVariable()); + assertThat(e).hasMessageThat().isEqualTo("TypeToken type argument must not contain a type variable;" + + " captured type variable M declared by " + testMethod + + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable"); + + // Using type variable as argument for factory methods should be allowed; this is not a type-safety + // problem because the user would have to perform unsafe casts + TypeVariable typeVar = Enclosing.class.getTypeParameters()[0]; + TypeToken typeToken = TypeToken.get(typeVar); + assertThat(typeToken.getType()).isEqualTo(typeVar); + + TypeToken parameterizedTypeToken = TypeToken.getParameterized(List.class, typeVar); + ParameterizedType parameterizedType = (ParameterizedType) parameterizedTypeToken.getType(); + assertThat(parameterizedType.getRawType()).isEqualTo(List.class); + assertThat(parameterizedType.getActualTypeArguments()).asList().containsExactly(typeVar); + } + @SuppressWarnings("rawtypes") @Test public void testTypeTokenRaw() { From 9bc4255469db21b760ed1c344a7226a2a420fc30 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 24 Jun 2023 01:31:27 +0200 Subject: [PATCH 2/5] Use hyphen for term "type-safe" Not completely sure if that is grammatically correct, but it might make the text a bit easier to understand. --- Troubleshooting.md | 9 ++++----- .../src/main/java/com/google/gson/reflect/TypeToken.java | 2 +- .../test/java/com/google/gson/reflect/TypeTokenTest.java | 2 +- .../java/com/example/ClassWithJsonAdapterAnnotation.java | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Troubleshooting.md b/Troubleshooting.md index 3954f7e960..9ae6180a39 100644 --- a/Troubleshooting.md +++ b/Troubleshooting.md @@ -332,9 +332,9 @@ Note: If the class which you are trying to deserialize is actually abstract, the **Symptom:** An exception with the message 'TypeToken type argument must not contain a type variable' is thrown -**Reason:** This exception is thrown when you create an anonymous `TypeToken` subclass which captures a type variable, for example `new TypeToken>() {}` (where `T` is a type variable). At compile time such code looks safe and you can use the type `List` without any warnings. However, this code is not actually type safe because at runtime due to [type erasure](https://dev.java/learn/generics/type-erasure/) only the upper bound of the type variable is available. For the previous example that would be `List`. When using such a `TypeToken` with any Gson methods performing deserialization this would lead to confusing and difficult to debug `ClassCastException`s. For serialization it can in some cases also lead to undesired results. +**Reason:** This exception is thrown when you create an anonymous `TypeToken` subclass which captures a type variable, for example `new TypeToken>() {}` (where `T` is a type variable). At compile time such code looks safe and you can use the type `List` without any warnings. However, this code is not actually type-safe because at runtime due to [type erasure](https://dev.java/learn/generics/type-erasure/) only the upper bound of the type variable is available. For the previous example that would be `List`. When using such a `TypeToken` with any Gson methods performing deserialization this would lead to confusing and difficult to debug `ClassCastException`s. For serialization it can in some cases also lead to undesired results. -Note: Earlier version of Gson unfortunately did not prevent capturing type variables, which caused many users to unwittingly write type unsafe code. +Note: Earlier version of Gson unfortunately did not prevent capturing type variables, which caused many users to unwittingly write type-unsafe code. **Solution:** @@ -342,8 +342,7 @@ Note: Earlier version of Gson unfortunately did not prevent capturing type varia - For Kotlin users: Use [`reified` type parameters](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters), that means change `` to ``, if possible. If you have a chain of functions with type parameters you will probably have to make all of them `reified`. - If you don't actually use Gson's `TypeToken` for any Gson method, use a general purpose 'type token' implementation provided by a different library instead, for example Guava's [`com.google.common.reflect.TypeToken`](https://javadoc.io/doc/com.google.guava/guava/latest/com/google/common/reflect/TypeToken.html). -For backward compatibility it is possible to restore Gson's old behavior of allowing `TypeToken` to capture type variables by setting the [system property](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setProperty(java.lang.String,java.lang.String)) `gson.allow-capturing-type-variables` to `"true"`. -**Important:** +For backward compatibility it is possible to restore Gson's old behavior of allowing `TypeToken` to capture type variables by setting the [system property](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setProperty(java.lang.String,java.lang.String)) `gson.allow-capturing-type-variables` to `"true"`, **however**: -- This does not solve any of the type safety problems mentioned above; in the long term you should prefer one of the other solutions listed above. This system property might be removed in future Gson versions. +- This does not solve any of the type-safety problems mentioned above; in the long term you should prefer one of the other solutions listed above. This system property might be removed in future Gson versions. - You should only ever set the property to `"true"`, but never to any other value or manually clear it. Otherwise this might counteract any libraries you are using which might have deliberately set the system property because they rely on its behavior. 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 fc5270fb23..0dc30f2016 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -386,7 +386,7 @@ public static TypeToken get(Class type) { * Class valueClass = ...; * TypeToken mapTypeToken = TypeToken.getParameterized(Map.class, keyClass, valueClass); * } - * As seen here the result is a {@code TypeToken}; this method cannot provide any type safety, + * As seen here the result is a {@code TypeToken}; this method cannot provide any type-safety, * and care must be taken to pass in the correct number of type arguments. * * @throws IllegalArgumentException 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 fcd7a25dee..16ad2b9187 100644 --- a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java +++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java @@ -301,7 +301,7 @@ void testMethodTypeVariable() throws Exception { + " captured type variable M declared by " + testMethod + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable", // Note: When running this test in Eclipse IDE or with certain Java versions it seems to capture `null` - // instead of the type variable + // instead of the type variable, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975 "TypeToken captured `null` as type argument; probably a compiler / runtime bug"); } } diff --git a/shrinker-test/src/main/java/com/example/ClassWithJsonAdapterAnnotation.java b/shrinker-test/src/main/java/com/example/ClassWithJsonAdapterAnnotation.java index 238ee1818e..42ff2dc0e6 100644 --- a/shrinker-test/src/main/java/com/example/ClassWithJsonAdapterAnnotation.java +++ b/shrinker-test/src/main/java/com/example/ClassWithJsonAdapterAnnotation.java @@ -77,7 +77,7 @@ public void write(JsonWriter out, DummyClass value) throws IOException { static class Factory implements TypeAdapterFactory { @Override public TypeAdapter create(Gson gson, TypeToken type) { - @SuppressWarnings("unchecked") // the code below is not type safe, but does not matter for this test + @SuppressWarnings("unchecked") // the code below is not type-safe, but does not matter for this test TypeAdapter r = (TypeAdapter) new TypeAdapter() { @Override public DummyClass read(JsonReader in) throws IOException { From d178fe0727580a7d1b43f37341e5df73d9d6d7d9 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 24 Jun 2023 01:35:11 +0200 Subject: [PATCH 3/5] Update Troubleshooting Guide URLs in tests from 'master' to 'main' --- .../test/java/com/google/gson/reflect/TypeTokenTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 16ad2b9187..a54eb3aaba 100644 --- a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java +++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java @@ -254,7 +254,7 @@ class Inner {} void test() { String expectedMessage = "TypeToken type argument must not contain a type variable;" + " captured type variable T declared by " + Enclosing.class - + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable"; + + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#typetoken-type-variable"; IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new TypeToken() {}); assertThat(e).hasMessageThat().isEqualTo(expectedMessage); @@ -299,7 +299,7 @@ void testMethodTypeVariable() throws Exception { IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new TypeToken() {}); assertThat(e).hasMessageThat().isAnyOf("TypeToken type argument must not contain a type variable;" + " captured type variable M declared by " + testMethod - + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable", + + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#typetoken-type-variable", // Note: When running this test in Eclipse IDE or with certain Java versions it seems to capture `null` // instead of the type variable, see https://github.com/eclipse-jdt/eclipse.jdt.core/issues/975 "TypeToken captured `null` as type argument; probably a compiler / runtime bug"); @@ -313,7 +313,7 @@ void testMethodTypeVariable() throws Exception { IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> createTypeTokenTypeVariable()); assertThat(e).hasMessageThat().isEqualTo("TypeToken type argument must not contain a type variable;" + " captured type variable M declared by " + testMethod - + "\nSee https://github.com/google/gson/blob/master/Troubleshooting.md#typetoken-type-variable"); + + "\nSee https://github.com/google/gson/blob/main/Troubleshooting.md#typetoken-type-variable"); // Using type variable as argument for factory methods should be allowed; this is not a type-safety // problem because the user would have to perform unsafe casts From 37fd5d060476a88d9e281dc6760e1bfe6638c4c5 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sat, 8 Jul 2023 17:36:37 +0200 Subject: [PATCH 4/5] Rename system property --- Troubleshooting.md | 2 +- gson/src/main/java/com/google/gson/reflect/TypeToken.java | 2 +- gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Troubleshooting.md b/Troubleshooting.md index 9ae6180a39..7da8933ec5 100644 --- a/Troubleshooting.md +++ b/Troubleshooting.md @@ -342,7 +342,7 @@ Note: Earlier version of Gson unfortunately did not prevent capturing type varia - For Kotlin users: Use [`reified` type parameters](https://kotlinlang.org/docs/inline-functions.html#reified-type-parameters), that means change `` to ``, if possible. If you have a chain of functions with type parameters you will probably have to make all of them `reified`. - If you don't actually use Gson's `TypeToken` for any Gson method, use a general purpose 'type token' implementation provided by a different library instead, for example Guava's [`com.google.common.reflect.TypeToken`](https://javadoc.io/doc/com.google.guava/guava/latest/com/google/common/reflect/TypeToken.html). -For backward compatibility it is possible to restore Gson's old behavior of allowing `TypeToken` to capture type variables by setting the [system property](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setProperty(java.lang.String,java.lang.String)) `gson.allow-capturing-type-variables` to `"true"`, **however**: +For backward compatibility it is possible to restore Gson's old behavior of allowing `TypeToken` to capture type variables by setting the [system property](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/System.html#setProperty(java.lang.String,java.lang.String)) `gson.allowCapturingTypeVariables` to `"true"`, **however**: - This does not solve any of the type-safety problems mentioned above; in the long term you should prefer one of the other solutions listed above. This system property might be removed in future Gson versions. - You should only ever set the property to `"true"`, but never to any other value or manually clear it. Otherwise this might counteract any libraries you are using which might have deliberately set the system property because they rely on its behavior. 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 0dc30f2016..5fdbdc1df3 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -93,7 +93,7 @@ private TypeToken(Type type) { } private static boolean isCapturingTypeVariablesForbidden() { - String value = System.getProperty("gson.allow-capturing-type-variables"); + String value = System.getProperty("gson.allowCapturingTypeVariables"); return value == null || !value.equals("true"); } 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 a54eb3aaba..cdabba0df8 100644 --- a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java +++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java @@ -273,7 +273,7 @@ void test() { e = assertThrows(IllegalArgumentException.class, () -> new TypeToken.Inner>() {}); assertThat(e).hasMessageThat().isEqualTo(expectedMessage); - String systemProperty = "gson.allow-capturing-type-variables"; + String systemProperty = "gson.allowCapturingTypeVariables"; try { // Any value other than 'true' should be ignored System.setProperty(systemProperty, "some-value"); From 23241907531fc639ecbd88b9154d2a1f4cca9364 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 14 Aug 2023 22:05:52 +0200 Subject: [PATCH 5/5] Simplify system property check --- gson/src/main/java/com/google/gson/reflect/TypeToken.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 4d3dc7912b..e91a6d731b 100644 --- a/gson/src/main/java/com/google/gson/reflect/TypeToken.java +++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java @@ -93,8 +93,7 @@ private TypeToken(Type type) { } private static boolean isCapturingTypeVariablesForbidden() { - String value = System.getProperty("gson.allowCapturingTypeVariables"); - return value == null || !value.equals("true"); + return !Objects.equals(System.getProperty("gson.allowCapturingTypeVariables"), "true"); } /**