From f19cb1bf9fd81ce309d4ae9e8f1cb94eec632e9a Mon Sep 17 00:00:00 2001 From: Jake Wang Date: Sat, 26 Sep 2026 19:31:20 -0400 Subject: [PATCH 1/4] Preserve checked I/O failures when reading through tree type adapters Signed-off-by: Jake Wang --- .../gson/internal/bind/TreeTypeAdapter.java | 11 ++++- .../gson/functional/TreeTypeAdaptersTest.java | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java index 62ea14d383..2c662cb73f 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TreeTypeAdapter.java @@ -20,6 +20,7 @@ import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; @@ -88,7 +89,15 @@ public T read(JsonReader in) throws IOException { if (deserializer == null) { return delegate().read(in); } - JsonElement value = Streams.parse(in); + JsonElement value; + try { + value = Streams.parse(in); + } catch (JsonIOException e) { + if (e.getCause() instanceof IOException) { + throw (IOException) e.getCause(); + } + throw e; + } if (nullSafe && value.isJsonNull()) { return null; } diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java index 2d9efca61a..c6131a2f00 100644 --- a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java @@ -17,19 +17,25 @@ package com.google.gson.functional; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; +import com.google.gson.JsonIOException; import com.google.gson.JsonParseException; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.net.SocketTimeoutException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -79,6 +85,46 @@ public void testDeserializeId() { assertThat(target.getId().getValue()).isEqualTo("1"); } + @Test + public void testReadPropagatesIOException() { + for (String prefix : Arrays.asList("", "{\"value\":")) { + IOException failure = new SocketTimeoutException("response timed out"); + Reader reader = + new StringReader(prefix) { + @Override + public int read(char[] buffer, int offset, int length) throws IOException { + int count = super.read(buffer, offset, length); + if (count == -1) { + throw failure; + } + return count; + } + }; + + IOException actual = + assertThrows(IOException.class, () -> gson.getAdapter(Id.class).fromJson(reader)); + assertThat(actual).isSameInstanceAs(failure); + } + } + + @Test + public void testReadPreservesDeserializerException() { + JsonIOException failure = new JsonIOException(new IOException("custom deserializer")); + Gson customGson = + new GsonBuilder() + .registerTypeAdapter( + Id.class, + (JsonDeserializer>) + (json, type, context) -> { + throw failure; + }) + .create(); + + JsonIOException actual = + assertThrows(JsonIOException.class, () -> customGson.getAdapter(Id.class).fromJson("1")); + assertThat(actual).isSameInstanceAs(failure); + } + @SuppressWarnings("UnusedTypeParameter") private static final class Id { final String value; From 7d94c4bc9d765b69549c4f18bf9c7319b94e6261 Mon Sep 17 00:00:00 2001 From: Jake Wang Date: Sat, 26 Sep 2026 19:34:44 -0400 Subject: [PATCH 2/4] Keep adapter lookup outside exception assertions Signed-off-by: Jake Wang --- .../com/google/gson/functional/TreeTypeAdaptersTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java index c6131a2f00..4b200a47ae 100644 --- a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java @@ -29,6 +29,7 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; import java.io.IOException; import java.io.Reader; @@ -101,8 +102,8 @@ public int read(char[] buffer, int offset, int length) throws IOException { } }; - IOException actual = - assertThrows(IOException.class, () -> gson.getAdapter(Id.class).fromJson(reader)); + TypeAdapter adapter = gson.getAdapter(Id.class); + IOException actual = assertThrows(IOException.class, () -> adapter.fromJson(reader)); assertThat(actual).isSameInstanceAs(failure); } } @@ -120,8 +121,8 @@ public void testReadPreservesDeserializerException() { }) .create(); - JsonIOException actual = - assertThrows(JsonIOException.class, () -> customGson.getAdapter(Id.class).fromJson("1")); + TypeAdapter adapter = customGson.getAdapter(Id.class); + JsonIOException actual = assertThrows(JsonIOException.class, () -> adapter.fromJson("1")); assertThat(actual).isSameInstanceAs(failure); } From ad0db4480d48915f926fd379b6be46ae8273b28c Mon Sep 17 00:00:00 2001 From: Jake Wang Date: Tue, 29 Sep 2026 16:03:31 -0400 Subject: [PATCH 3/4] Test EOF syntax errors in tree type adapters --- .../google/gson/functional/TreeTypeAdaptersTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java index 4b200a47ae..0d4b966db5 100644 --- a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java @@ -29,8 +29,10 @@ import com.google.gson.JsonPrimitive; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; +import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; +import java.io.EOFException; import java.io.IOException; import java.io.Reader; import java.io.StringReader; @@ -108,6 +110,14 @@ public int read(char[] buffer, int offset, int length) throws IOException { } } + @Test + public void testReadPreservesEOFExceptionAsJsonSyntaxException() { + TypeAdapter adapter = gson.getAdapter(Id.class); + JsonSyntaxException actual = + assertThrows(JsonSyntaxException.class, () -> adapter.fromJson(new StringReader("["))); + assertThat(actual).hasCauseThat().isInstanceOf(EOFException.class); + } + @Test public void testReadPreservesDeserializerException() { JsonIOException failure = new JsonIOException(new IOException("custom deserializer")); From add7f8cffed473729588325eae3451659448e535 Mon Sep 17 00:00:00 2001 From: Jake Wang Date: Tue, 29 Sep 2026 16:05:34 -0400 Subject: [PATCH 4/4] Keep reader construction outside exception assertion --- .../java/com/google/gson/functional/TreeTypeAdaptersTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java index 0d4b966db5..2d74a8571b 100644 --- a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java @@ -113,8 +113,9 @@ public int read(char[] buffer, int offset, int length) throws IOException { @Test public void testReadPreservesEOFExceptionAsJsonSyntaxException() { TypeAdapter adapter = gson.getAdapter(Id.class); + Reader reader = new StringReader("["); JsonSyntaxException actual = - assertThrows(JsonSyntaxException.class, () -> adapter.fromJson(new StringReader("["))); + assertThrows(JsonSyntaxException.class, () -> adapter.fromJson(reader)); assertThat(actual).hasCauseThat().isInstanceOf(EOFException.class); }