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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions