Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.google.gson.internal.bind;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
Expand Down Expand Up @@ -56,22 +57,35 @@ public void write(JsonWriter out, T value) throws IOException {
@SuppressWarnings("ReferenceEquality")
boolean isDifferentType = runtimeType != type;
if (isDifferentType) {
@SuppressWarnings("unchecked")
TypeAdapter<T> runtimeTypeAdapter =
(TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not any
// other wrapping adapters, see
// https://github.com/google/gson/pull/1787#issuecomment-1222175189
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!isReflective(delegate)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
} else {
// Use the type adapter for runtime type
chosen = runtimeTypeAdapter;
TypeAdapter<T> runtimeTypeAdapter = null;
try {
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) context.getAdapter(TypeToken.get(runtimeType));
runtimeTypeAdapter = adapter;
} catch (JsonIOException e) {
// Building an adapter for the runtime type can fail (for example when reflective
// access to a JDK-internal implementation class is denied). Only propagate that
// failure if we would actually have used the runtime-type adapter; otherwise keep
// the non-reflective declared-type adapter. See https://github.com/google/gson/issues/3122
if (isReflective(delegate)) {
throw e;
}
}
if (runtimeTypeAdapter != null) {
// For backward compatibility only check ReflectiveTypeAdapterFactory.Adapter here but not
// any other wrapping adapters, see
// https://github.com/google/gson/pull/1787#issuecomment-1222175189
if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
// The user registered a type adapter for the runtime type, so we will use that
chosen = runtimeTypeAdapter;
} else if (!isReflective(delegate)) {
// The user registered a type adapter for Base class, so we prefer it over the
// reflective type adapter for the runtime type
chosen = delegate;
} else {
// Use the type adapter for runtime type
chosen = runtimeTypeAdapter;
}
}
}
chosen.write(out, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,28 @@
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.JsonPrimitive;
import com.google.gson.JsonSerializer;
import com.google.gson.ReflectionAccessFilter.FilterResult;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Arrays;
import java.util.List;
import org.junit.AssumptionViolatedException;
import org.junit.Test;

public class TypeAdapterRuntimeTypeWrapperTest {
Expand Down Expand Up @@ -69,6 +78,21 @@ public void testJsonSerializer() {
assertThat(json).isEqualTo("{\"b\":\"serializer\"}");
}

@Test
public void testNonReflectiveDelegateWhenRuntimeReflectionIsBlocked() {
Gson gson =
new GsonBuilder()
.addReflectionAccessFilter(
type -> type == Subclass.class ? FilterResult.BLOCK_ALL : FilterResult.INDECISIVE)
.registerTypeAdapter(
Base.class,
(JsonSerializer<Base>) (src, typeOfSrc, context) -> new JsonPrimitive("serializer"))
.create();

assertThrows(JsonIOException.class, () -> gson.getAdapter(Subclass.class));
assertThat(gson.toJson(new Container())).isEqualTo("{\"b\":\"serializer\"}");
}

/**
* When only {@link JsonDeserializer} is registered for Base, then on serialization should prefer
* reflective adapter for Subclass since Base would use reflective adapter as delegate.
Expand Down Expand Up @@ -202,4 +226,62 @@ public void testGsonFutureAdapter() {
String json = new Gson().toJson(b);
assertThat(json).isEqualTo("{\"f\":{\"i\":2}}");
}

/**
* A trivial adapter for {@link WildcardType}. It never uses reflection.
*
* <p>Regression test for https://github.com/google/gson/issues/3122: when serializing a value as
* an array/collection element, {@code TypeAdapterRuntimeTypeWrapper} must not fail while eagerly
* resolving a reflective adapter for an inaccessible runtime implementation class if the
* declared-type adapter would be preferred anyway.
*/
private static final TypeAdapter<WildcardType> WILDCARD_ADAPTER =
new TypeAdapter<WildcardType>() {
@Override
public void write(JsonWriter out, WildcardType value) throws IOException {
out.value(value == null ? null : value.getTypeName());
}

@Override
public WildcardType read(JsonReader in) throws IOException {
throw new UnsupportedOperationException();
}
};

private static WildcardType sampleWildcard() throws Exception {
class Holder {
@SuppressWarnings("unused")
List<? extends Number> field;
}

ParameterizedType listOfWildcard =
(ParameterizedType) Holder.class.getDeclaredField("field").getGenericType();
return (WildcardType) listOfWildcard.getActualTypeArguments()[0];
}
Comment thread
jaideeppyne marked this conversation as resolved.

@Test
public void testNonReflectiveInterfaceAdapter_PrefersDelegateWhenRuntimeReflectiveFails()
throws Exception {
WildcardType wildcard = sampleWildcard();
// Runtime class is typically sun.reflect.generics.reflectiveObjects.WildcardTypeImpl, which is
// not reflectively accessible under the module system.
try {
var unused = new Gson().getAdapter(TypeToken.get(wildcard.getClass()));
// If reflective access happens to succeed on this JDK, the bug cannot be reproduced here.
throw new AssumptionViolatedException("Runtime wildcard type is reflectively accessible");
} catch (JsonIOException expected) {
// Continue with the regression assertions.
}
Comment thread
jaideeppyne marked this conversation as resolved.

Gson gson =
new GsonBuilder().registerTypeAdapter(WildcardType.class, WILDCARD_ADAPTER).create();

String expected = "\"" + wildcard.getTypeName() + "\"";
assertThat(gson.toJson(wildcard, WildcardType.class)).isEqualTo(expected);
assertThat(gson.toJson(new WildcardType[] {wildcard}, WildcardType[].class))
.isEqualTo("[" + expected + "]");
assertThat(
gson.toJson(Arrays.asList(wildcard), new TypeToken<List<WildcardType>>() {}.getType()))
.isEqualTo("[" + expected + "]");
}
}
Loading