Skip to content

Unknown enum values in repeated fields cause deserialization crash #3508

@F0x1d

Description

@F0x1d

Problem Summary

When deserializing JSON containing a repeated enum field (List<SomeEnum>) with unknown enum values, Wire's GSON integration crashes with IllegalArgumentException: field_name.contains(null) because:

  1. EnumJsonFormatter.fromString() returns null for unknown enum values
  2. ListJsonAdapter adds these null values to the result list
  3. Wire's immutableCopyOf() rejects lists containing null

Affected Code Paths

1. EnumJsonFormatter.kt (wire-runtime)

override fun fromString(value: String): E? {
    return stringToValue[value]
      // If the constant is unknown to our runtime, we return a `Unrecognized` instance if it has
      // been generated.
      ?: unrecognizedClassConstructor?.newInstance(value.toInt())
}

When there's no Unrecognized class (standard enum generation), this returns null for unknown values.

2. GsonJsonIntegration.kt (wire-gson-support)

private class ListJsonAdapter<T>(
    private val single: TypeAdapter<T>,
) : TypeAdapter<List<T?>>() {
    override fun read(reader: JsonReader): List<T?> {
        val result = mutableListOf<T?>()
        reader.beginArray()
        while (reader.hasNext()) {
            result.add(single.read(reader))  // Adds null from EnumJsonFormatter
        }
        reader.endArray()
        return result  // List contains nulls!
    }
}

3. Internal.kt (wire-runtime)

fun <T> immutableCopyOf(name: String, list: List<T>): List<T> {
    if (list.contains(null)) {
        throw IllegalArgumentException("$name.contains(null)")  // CRASH!
    }
    // ...
}

Reproduction

Given this proto:

enum HotelPageBlock {
    UNKNOWN = 0;
    MAP_BLOCK = 1;
    BENEFITS_BLOCK = 2;
}

message RubiricHotelPageBlockOrder {
    repeated HotelPageBlock hotel_page_blocks = 1;
}

And this JSON with an unknown enum value (e.g., server added NEW_BLOCK = 17):

{
    "hotelPageBlocks": ["MAP_BLOCK", "NEW_BLOCK", "BENEFITS_BLOCK"]
}

Deserialization crashes:

java.lang.IllegalArgumentException: hotel_page_blocks.contains(null)
    at com.squareup.wire.internal.Internal__InternalKt.immutableCopyOf(Internal.kt:72)
    at proto.hotels.v1.RubiricHotelPageBlockOrder.<init>(RubiricHotelPageBlockOrder.kt:48)

Expected Behavior

Wire should handle unknown enum values in repeated fields gracefully, either by:

  1. Filtering out unknown values (like proto binary decoding does - stores in unknownFields)
  2. Using the default enum value (first constant, typically UNKNOWN = 0)
  3. Providing a configuration option to choose the behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions