-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Prevent TypeToken
from capturing type variables
#2376
Prevent TypeToken
from capturing type variables
#2376
Conversation
a8934ed
to
eebd97a
Compare
// 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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is quite an interesting bug, which even occurred even on CI when running with JDK 11, so in the corresponding unit tests it checks for this exception message as well.
See also eclipse-jdt/eclipse.jdt.core#975
@@ -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<List<T>>()` (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<List<T>>()` (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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refers to https://dev.java here and below because that page contains a good explanation, and also this seems to be the official Java website, even though it is probably not that well known yet because it is still quite new.
|
||
- 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 `<T>` to `<reified T>`, 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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not completely sure to what extent the Guava TypeToken
actually wants to support capturing type variables. For example new TypeToken<T>() {}
throws an exception. However creating TypeToken<List<T>>
and similar works. Similarly the wiki says that TypeToken
"supports non-reified types such as T
, List<T>
", so support for type variables seems to be at least to some extent intended.
@eamonnmcmanus, what is your position on this? Your comment #1219 (comment) sounded to me as if you are generally open to adding this restriction eventually, but maybe I misunderstood it. The solution proposed here does not differentiate between serialization and deserialization, it blocks usage of type variables for Depending on that I will try to resolve the merge conflicts in the near future. |
OK, a few things. I commented on #2375 separately and I think we can probably make that work with the small change I suggested. I ran this change against all of Google's internal tests and encountered one failure. The class looks like this in outline: public abstract class AbstractAction<D extends Dto, T extends Service<D>> implements Action {
...
private final Type listType = new TypeToken<List<D>>() {}.getType();
...
return new SafeJsonResponse(GSON, result, listType); So, even though Gson is in the picture, here I naively tried to fix the new exception by writing Just one problem out of tens of thousands of files referencing Gson is a pretty low rate, and needs to be balanced against the many issues you found on StackOverflow. So I think we probably could control this with a system property. For example the class I cited could have a static intializer that sets the property. I think this would be the first time we introduce a system property into the Gson code, so we would need to decide on a naming convention. Unfortunately system property names are all over the place, but using dashes does seem to be somewhat unusual. I've never been very happy with the likes of |
Thanks for taking a look and thanks for the feedback on this!
Might be #1870; for
In this specific case, if you have
Good point! If we use a naming which looks like a package or class name, then we could also use the qualified class name of Your shorter suggestions also sound good, but I think it would be better to lowercase the first |
Not completely sure if that is grammatically correct, but it might make the text a bit easier to understand.
eebd97a
to
9bc4255
Compare
Rebased the changes onto |
Would using |
Yes, I think the convention that that establishes looks good. |
@eamonnmcmanus, do you think this can be merged? In the worst case, if this is really causing more issues than it helps preventing bugs, we could revert it again in a future release. Since this only adds validation but does not introduce any new feature or alters Gson behavior in any other way, reverting the changes should be safe. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. I think we can go ahead with this. Just one small change requested.
* Prevent `TypeToken` from capturing type variables * 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. * Update Troubleshooting Guide URLs in tests from 'master' to 'main' * Rename system property * Simplify system property check
Purpose
Fixes #1219
Description
Throws an exception when the user tries to capture a type variable using
new TypeToken<...>() {}
, see #1219. This is extremely error-prone because at compile time such code looks safe, but at runtime it leads to confusingClassCastException
s due to type erasure; and users keep running into this issue (see #1219 (comment)).The exception message which is thrown refers to a new troubleshooting guide section which describes possible workarounds. Additionally for now an opt-in system property is added which allows capturing type variables again.
Checklist
null
@since $next-version$
(
$next-version$
is a special placeholder which is automatically replaced during release)TestCase
)mvn clean verify javadoc:jar
passes without errors