google / gson

A Java serialization/deserialization library to convert Java Objects into JSON and back
Apache License 2.0
23.4k stars 4.29k forks source link

java.lang.NoSuchFieldException: UNAVAILABLE #2020

Closed jd565 closed 3 years ago

jd565 commented 3 years ago

Gson version

2.8.9

Java / Android version

Android compileSdk 31, target Java 1.8

Description

After upgrading from gson 2.8.8 to 2.8.9, we started seeing the following exception:

    java.lang.AssertionError: java.lang.NoSuchFieldException: UNAVAILABLE
        at com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter.<init>(TypeAdapters.java:12)
        at com.google.gson.internal.bind.TypeAdapters$30.create(TypeAdapters.java:5)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:15)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.Gson.getAdapter(Gson.java:9)

This happens when gson is creating the EnumTypeAdapter for the following class:

enum class CallingStatus {
    @SerializedName("unavailable")
    UNAVAILABLE,
    @SerializedName("disabled")
    DISABLED,
    @SerializedName("requested")
    REQUESTED,
    @SerializedName("pending")
    PENDING,
    @SerializedName("enabled")
    ENABLED
}

This enum is written in kotlin, and the class is not kept by proguard (The serialized name annotations should be telling gson how to parse this)

If I log out this info on a release build like follows:

CallingStatus::class.java.declaredFields.filter { it.isEnumConstant }.joinToString("\n") {
            "${it.name} - ${it.get(null)} - ${(it.get(null) as CallingStatus).name}"
        }

I get: a - UNAVAILABLE - UNAVAILABLE b - DISABLED - DISABLED c - REQUESTED - REQUESTED d - PENDING - PENDING e - ENABLED - ENABLED

Expected behavior

Gson doesn't crash creating a type adapter

Actual behavior

Gson crashes creating a type adapter

Reproduction steps

  1. Create an enum class in kotlin and do not cover it with proguard
  2. Build the project and try and create the type adapter

Exception stack trace

    java.lang.AssertionError: java.lang.NoSuchFieldException: UNAVAILABLE
        at com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter.<init>(TypeAdapters.java:12)
        at com.google.gson.internal.bind.TypeAdapters$30.create(TypeAdapters.java:5)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:15)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:15)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at com.google.gson.internal.bind.CollectionTypeAdapterFactory.create(CollectionTypeAdapterFactory.java:5)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:15)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:15)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:4)
        at com.google.gson.Gson.getAdapter(Gson.java:9)
        at retrofit2.converter.gson.GsonConverterFactory.responseBodyConverter(GsonConverterFactory.java:1)
        at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:6)
        at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:1)
        at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:2)
        at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:16)
        at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:5)
        at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:4)
        at retrofit2.Retrofit$1.invoke(Retrofit.java:6)
        at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
        at $Proxy2.b(Unknown Source)
Marcono1234 commented 3 years ago

Are you sure the stack trace really comes from a device running Gson 2.8.9? The stack trace looks a lot like Gson 2.8.7 or lower (before #1495 was integrated):

The whole point of #1495 was to fix the situation you are seeing, where enum constant names were obfuscated, but have a @SerializedName annotation and Gson should be able to handle them.

The line numbers in the provided stack trace also look a bit suspicious, maybe ProGuard is messing with the Gson library itself (though that would not directly explain this issue).

jd565 commented 3 years ago

I will try again but the changes I made before were upgrading to 2.8.9 (from 2.8.8), and then we started seeing these crashes, and when I downgraded back to 2.8.8, it started working fine again.

jd565 commented 3 years ago

Having had another look it looks like you are right: