Add support for reflection access filter (#1905)
* Add support for reflection access filter * Improve documentation * Fix compilation errors * Relax handling for BLOCK_ALL when invoking default constructor * Improve handling for inherited fields * Fix accessible test failing for static fields * Simplify ReflectiveTypeAdapterFactory field writing * Fix GsonBuilder changes affecting created Gson instances * Improve documentation * Improve handling for IllegalAccessException For Java < 9, AccessibleObject.canAccess is not available and therefore checks might pass even if object is not accessible, causing IllegalAccessException later. * Fix incorrect GsonBuilder.addReflectionAccessFilter documentation
This commit is contained in:
parent
f79ea208b1
commit
e82637c485
|
@ -20,6 +20,7 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.ReflectionAccessFilter;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.internal.ConstructorConstructor;
|
||||
|
@ -30,6 +31,7 @@ import com.google.gson.stream.JsonToken;
|
|||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
|
@ -47,7 +49,7 @@ public final class GraphAdapterBuilder {
|
|||
|
||||
public GraphAdapterBuilder() {
|
||||
this.instanceCreators = new HashMap<Type, InstanceCreator<?>>();
|
||||
this.constructorConstructor = new ConstructorConstructor(instanceCreators, true);
|
||||
this.constructorConstructor = new ConstructorConstructor(instanceCreators, true, Collections.<ReflectionAccessFilter>emptyList());
|
||||
}
|
||||
public GraphAdapterBuilder addType(Type type) {
|
||||
final ObjectConstructor<?> objectConstructor = constructorConstructor.get(TypeToken.get(type));
|
||||
|
|
|
@ -156,6 +156,7 @@ public final class Gson {
|
|||
final List<TypeAdapterFactory> builderHierarchyFactories;
|
||||
final ToNumberStrategy objectToNumberStrategy;
|
||||
final ToNumberStrategy numberToNumberStrategy;
|
||||
final List<ReflectionAccessFilter> reflectionFilters;
|
||||
|
||||
/**
|
||||
* Constructs a Gson object with default configuration. The default configuration has the
|
||||
|
@ -199,7 +200,8 @@ public final class Gson {
|
|||
DEFAULT_USE_JDK_UNSAFE,
|
||||
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
|
||||
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
|
||||
Collections.<TypeAdapterFactory>emptyList(), DEFAULT_OBJECT_TO_NUMBER_STRATEGY, DEFAULT_NUMBER_TO_NUMBER_STRATEGY);
|
||||
Collections.<TypeAdapterFactory>emptyList(), DEFAULT_OBJECT_TO_NUMBER_STRATEGY, DEFAULT_NUMBER_TO_NUMBER_STRATEGY,
|
||||
Collections.<ReflectionAccessFilter>emptyList());
|
||||
}
|
||||
|
||||
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
|
||||
|
@ -211,11 +213,12 @@ public final class Gson {
|
|||
int timeStyle, List<TypeAdapterFactory> builderFactories,
|
||||
List<TypeAdapterFactory> builderHierarchyFactories,
|
||||
List<TypeAdapterFactory> factoriesToBeAdded,
|
||||
ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) {
|
||||
ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy,
|
||||
List<ReflectionAccessFilter> reflectionFilters) {
|
||||
this.excluder = excluder;
|
||||
this.fieldNamingStrategy = fieldNamingStrategy;
|
||||
this.instanceCreators = instanceCreators;
|
||||
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe);
|
||||
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe, reflectionFilters);
|
||||
this.serializeNulls = serializeNulls;
|
||||
this.complexMapKeySerialization = complexMapKeySerialization;
|
||||
this.generateNonExecutableJson = generateNonExecutableGson;
|
||||
|
@ -232,6 +235,7 @@ public final class Gson {
|
|||
this.builderHierarchyFactories = builderHierarchyFactories;
|
||||
this.objectToNumberStrategy = objectToNumberStrategy;
|
||||
this.numberToNumberStrategy = numberToNumberStrategy;
|
||||
this.reflectionFilters = reflectionFilters;
|
||||
|
||||
List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
|
||||
|
||||
|
@ -296,7 +300,7 @@ public final class Gson {
|
|||
factories.add(jsonAdapterFactory);
|
||||
factories.add(TypeAdapters.ENUM_FACTORY);
|
||||
factories.add(new ReflectiveTypeAdapterFactory(
|
||||
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory));
|
||||
constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory, reflectionFilters));
|
||||
|
||||
this.factories = Collections.unmodifiableList(factories);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -101,6 +102,7 @@ public final class GsonBuilder {
|
|||
private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
|
||||
private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
|
||||
private ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
|
||||
private final LinkedList<ReflectionAccessFilter> reflectionFilters = new LinkedList<ReflectionAccessFilter>();
|
||||
|
||||
/**
|
||||
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
|
||||
|
@ -137,6 +139,7 @@ public final class GsonBuilder {
|
|||
this.useJdkUnsafe = gson.useJdkUnsafe;
|
||||
this.objectToNumberStrategy = gson.objectToNumberStrategy;
|
||||
this.numberToNumberStrategy = gson.numberToNumberStrategy;
|
||||
this.reflectionFilters.addAll(gson.reflectionFilters);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -632,6 +635,28 @@ public final class GsonBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a reflection access filter. A reflection access filter prevents Gson from using
|
||||
* reflection for the serialization and deserialization of certain classes. The logic in
|
||||
* the filter specifies which classes those are.
|
||||
*
|
||||
* <p>Filters will be invoked in reverse registration order, that is, the most recently
|
||||
* added filter will be invoked first.
|
||||
*
|
||||
* <p>By default Gson has no filters configured and will try to use reflection for
|
||||
* all classes for which no {@link TypeAdapter} has been registered, and for which no
|
||||
* built-in Gson {@code TypeAdapter} exists.
|
||||
*
|
||||
* @param filter filter to add
|
||||
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
|
||||
*/
|
||||
public GsonBuilder addReflectionAccessFilter(ReflectionAccessFilter filter) {
|
||||
if (filter == null) throw new NullPointerException();
|
||||
|
||||
reflectionFilters.addFirst(filter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Gson} instance based on the current configuration. This method is free of
|
||||
* side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
|
||||
|
@ -649,12 +674,13 @@ public final class GsonBuilder {
|
|||
|
||||
addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
|
||||
|
||||
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
|
||||
return new Gson(excluder, fieldNamingPolicy, new HashMap<Type, InstanceCreator<?>>(instanceCreators),
|
||||
serializeNulls, complexMapKeySerialization,
|
||||
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
|
||||
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
|
||||
datePattern, dateStyle, timeStyle,
|
||||
this.factories, this.hierarchyFactories, factories, objectToNumberStrategy, numberToNumberStrategy);
|
||||
datePattern, dateStyle, timeStyle, new ArrayList<TypeAdapterFactory>(this.factories),
|
||||
new ArrayList<TypeAdapterFactory>(this.hierarchyFactories), factories,
|
||||
objectToNumberStrategy, numberToNumberStrategy, new ArrayList<ReflectionAccessFilter>(reflectionFilters));
|
||||
}
|
||||
|
||||
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
package com.google.gson;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
|
||||
import com.google.gson.internal.ReflectionAccessFilterHelper;
|
||||
|
||||
/**
|
||||
* Filter for determining whether reflection based serialization and
|
||||
* deserialization is allowed for a class.
|
||||
*
|
||||
* <p>A filter can be useful in multiple scenarios, for example when
|
||||
* upgrading to newer Java versions which use the Java Platform Module
|
||||
* System (JPMS). A filter then allows to {@linkplain FilterResult#BLOCK_INACCESSIBLE
|
||||
* prevent making inaccessible members accessible}, even if the used
|
||||
* Java version might still allow illegal access (but logs a warning),
|
||||
* or if {@code java} command line arguments are used to open the inaccessible
|
||||
* packages to other parts of the application. This interface defines some
|
||||
* convenience filters for this task, such as {@link #BLOCK_INACCESSIBLE_JAVA}.
|
||||
*
|
||||
* <p>A filter can also be useful to prevent mixing model classes of a
|
||||
* project with other non-model classes; the filter could
|
||||
* {@linkplain FilterResult#BLOCK_ALL block all reflective access} to
|
||||
* non-model classes.
|
||||
*
|
||||
* <p>A reflection access filter is similar to an {@link ExclusionStrategy}
|
||||
* with the major difference that a filter will cause an exception to be
|
||||
* thrown when access is disallowed while an exclusion strategy just skips
|
||||
* fields and classes.
|
||||
*
|
||||
* @see GsonBuilder#addReflectionAccessFilter(ReflectionAccessFilter)
|
||||
*/
|
||||
public interface ReflectionAccessFilter {
|
||||
/**
|
||||
* Result of a filter check.
|
||||
*/
|
||||
enum FilterResult {
|
||||
/**
|
||||
* Reflection access for the class is allowed.
|
||||
*
|
||||
* <p>Note that this does not affect the Java access checks in any way,
|
||||
* it only permits Gson to try using reflection for a class. The Java
|
||||
* runtime might still deny such access.
|
||||
*/
|
||||
ALLOW,
|
||||
/**
|
||||
* The filter is indecisive whether reflection access should be allowed.
|
||||
* The next registered filter will be consulted to get the result. If
|
||||
* there is no next filter, this result acts like {@link #ALLOW}.
|
||||
*/
|
||||
INDECISIVE,
|
||||
/**
|
||||
* Blocks reflection access if a member of the class is not accessible
|
||||
* by default and would have to be made accessible. This is unaffected
|
||||
* by any {@code java} command line arguments being used to make packages
|
||||
* accessible, or by module declaration directives which <i>open</i> the
|
||||
* complete module or certain packages for reflection and will consider
|
||||
* such packages inaccessible.
|
||||
*
|
||||
* <p>Note that this <b>only works for Java 9 and higher</b>, for older
|
||||
* Java versions its functionality will be limited and it might behave like
|
||||
* {@link #ALLOW}. Access checks are only performed as defined by the Java
|
||||
* Language Specification (<a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.6">JLS 11 §6.6</a>),
|
||||
* restrictions imposed by a {@link SecurityManager} are not considered.
|
||||
*
|
||||
* <p>This result type is mainly intended to help enforce the access checks of
|
||||
* the Java Platform Module System. It allows detecting illegal access, even if
|
||||
* the used Java version would only log a warning, or is configured to open
|
||||
* packages for reflection using command line arguments.
|
||||
*
|
||||
* @see AccessibleObject#canAccess(Object)
|
||||
*/
|
||||
BLOCK_INACCESSIBLE,
|
||||
/**
|
||||
* Blocks all reflection access for the class. Other means for serializing
|
||||
* and deserializing the class, such as a {@link TypeAdapter}, have to
|
||||
* be used.
|
||||
*/
|
||||
BLOCK_ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks all reflection access to members of standard Java classes which are
|
||||
* not accessible by default. However, reflection access is still allowed for
|
||||
* classes for which all fields are accessible and which have an accessible
|
||||
* no-args constructor (or for which an {@link InstanceCreator} has been registered).
|
||||
*
|
||||
* <p>If this filter encounters a class other than a standard Java class it
|
||||
* returns {@link FilterResult#INDECISIVE}.
|
||||
*
|
||||
* <p>This filter is mainly intended to help enforcing the access checks of
|
||||
* Java Platform Module System. It allows detecting illegal access, even if
|
||||
* the used Java version would only log a warning, or is configured to open
|
||||
* packages for reflection. However, this filter <b>only works for Java 9 and
|
||||
* higher</b>, when using an older Java version its functionality will be
|
||||
* limited.
|
||||
*
|
||||
* <p>Note that this filter might not cover all standard Java classes. Currently
|
||||
* only classes in a {@code java.*} or {@code javax.*} package are considered. The
|
||||
* set of detected classes might be expanded in the future without prior notice.
|
||||
*
|
||||
* @see FilterResult#BLOCK_INACCESSIBLE
|
||||
*/
|
||||
ReflectionAccessFilter BLOCK_INACCESSIBLE_JAVA = new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return ReflectionAccessFilterHelper.isJavaType(rawClass)
|
||||
? FilterResult.BLOCK_INACCESSIBLE
|
||||
: FilterResult.INDECISIVE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Blocks all reflection access to members of standard Java classes.
|
||||
*
|
||||
* <p>If this filter encounters a class other than a standard Java class it
|
||||
* returns {@link FilterResult#INDECISIVE}.
|
||||
*
|
||||
* <p>This filter is mainly intended to prevent depending on implementation
|
||||
* details of the Java platform and to help applications prepare for upgrading
|
||||
* to the Java Platform Module System.
|
||||
*
|
||||
* <p>Note that this filter might not cover all standard Java classes. Currently
|
||||
* only classes in a {@code java.*} or {@code javax.*} package are considered. The
|
||||
* set of detected classes might be expanded in the future without prior notice.
|
||||
*
|
||||
* @see #BLOCK_INACCESSIBLE_JAVA
|
||||
* @see FilterResult#BLOCK_ALL
|
||||
*/
|
||||
ReflectionAccessFilter BLOCK_ALL_JAVA = new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return ReflectionAccessFilterHelper.isJavaType(rawClass)
|
||||
? FilterResult.BLOCK_ALL
|
||||
: FilterResult.INDECISIVE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Blocks all reflection access to members of standard Android classes.
|
||||
*
|
||||
* <p>If this filter encounters a class other than a standard Android class it
|
||||
* returns {@link FilterResult#INDECISIVE}.
|
||||
*
|
||||
* <p>This filter is mainly intended to prevent depending on implementation
|
||||
* details of the Android platform.
|
||||
*
|
||||
* <p>Note that this filter might not cover all standard Android classes. Currently
|
||||
* only classes in an {@code android.*} or {@code androidx.*} package, and standard
|
||||
* Java classes in a {@code java.*} or {@code javax.*} package are considered. The
|
||||
* set of detected classes might be expanded in the future without prior notice.
|
||||
*
|
||||
* @see FilterResult#BLOCK_ALL
|
||||
*/
|
||||
ReflectionAccessFilter BLOCK_ALL_ANDROID = new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return ReflectionAccessFilterHelper.isAndroidType(rawClass)
|
||||
? FilterResult.BLOCK_ALL
|
||||
: FilterResult.INDECISIVE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Blocks all reflection access to members of classes belonging to programming
|
||||
* language platforms, such as Java, Android, Kotlin or Scala.
|
||||
*
|
||||
* <p>If this filter encounters a class other than a standard platform class it
|
||||
* returns {@link FilterResult#INDECISIVE}.
|
||||
*
|
||||
* <p>This filter is mainly intended to prevent depending on implementation
|
||||
* details of the platform classes.
|
||||
*
|
||||
* <p>Note that this filter might not cover all platform classes. Currently it
|
||||
* combines the filters {@link #BLOCK_ALL_JAVA} and {@link #BLOCK_ALL_ANDROID},
|
||||
* and checks for other language-specific platform classes like {@code kotlin.*}.
|
||||
* The set of detected classes might be expanded in the future without prior notice.
|
||||
*
|
||||
* @see FilterResult#BLOCK_ALL
|
||||
*/
|
||||
ReflectionAccessFilter BLOCK_ALL_PLATFORM = new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return ReflectionAccessFilterHelper.isAnyPlatformType(rawClass)
|
||||
? FilterResult.BLOCK_ALL
|
||||
: FilterResult.INDECISIVE;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if reflection access should be allowed for a class.
|
||||
*
|
||||
* @param rawClass
|
||||
* Class to check
|
||||
* @return
|
||||
* Result indicating whether reflection access is allowed
|
||||
*/
|
||||
FilterResult check(Class<?> rawClass);
|
||||
}
|
|
@ -16,6 +16,12 @@
|
|||
|
||||
package com.google.gson.internal;
|
||||
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.ReflectionAccessFilter;
|
||||
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
||||
import com.google.gson.internal.reflect.ReflectionHelper;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
@ -28,6 +34,7 @@ import java.util.EnumMap;
|
|||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
@ -40,21 +47,18 @@ import java.util.concurrent.ConcurrentMap;
|
|||
import java.util.concurrent.ConcurrentNavigableMap;
|
||||
import java.util.concurrent.ConcurrentSkipListMap;
|
||||
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.internal.reflect.ReflectionHelper;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
/**
|
||||
* Returns a function that can construct an instance of a requested type.
|
||||
*/
|
||||
public final class ConstructorConstructor {
|
||||
private final Map<Type, InstanceCreator<?>> instanceCreators;
|
||||
private final boolean useJdkUnsafe;
|
||||
private final List<ReflectionAccessFilter> reflectionFilters;
|
||||
|
||||
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators, boolean useJdkUnsafe) {
|
||||
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators, boolean useJdkUnsafe, List<ReflectionAccessFilter> reflectionFilters) {
|
||||
this.instanceCreators = instanceCreators;
|
||||
this.useJdkUnsafe = useJdkUnsafe;
|
||||
this.reflectionFilters = reflectionFilters;
|
||||
}
|
||||
|
||||
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
|
||||
|
@ -85,7 +89,16 @@ public final class ConstructorConstructor {
|
|||
};
|
||||
}
|
||||
|
||||
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
|
||||
// First consider special constructors before checking for no-args constructors
|
||||
// below to avoid matching internal no-args constructors which might be added in
|
||||
// future JDK versions
|
||||
ObjectConstructor<T> specialConstructor = newSpecialCollectionConstructor(type, rawType);
|
||||
if (specialConstructor != null) {
|
||||
return specialConstructor;
|
||||
}
|
||||
|
||||
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, rawType);
|
||||
ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType, filterResult);
|
||||
if (defaultConstructor != null) {
|
||||
return defaultConstructor;
|
||||
}
|
||||
|
@ -95,11 +108,81 @@ public final class ConstructorConstructor {
|
|||
return defaultImplementation;
|
||||
}
|
||||
|
||||
// finally try unsafe
|
||||
return newUnsafeAllocator(rawType);
|
||||
// Check whether type is instantiable; otherwise ReflectionAccessFilter recommendation
|
||||
// of adjusting filter suggested below is irrelevant since it would not solve the problem
|
||||
final String exceptionMessage = UnsafeAllocator.checkInstantiable(rawType);
|
||||
if (exceptionMessage != null) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
throw new JsonIOException(exceptionMessage);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Consider usage of Unsafe as reflection, so don't use if BLOCK_ALL
|
||||
// Additionally, since it is not calling any constructor at all, don't use if BLOCK_INACCESSIBLE
|
||||
if (filterResult == FilterResult.ALLOW) {
|
||||
// finally try unsafe
|
||||
return newUnsafeAllocator(rawType);
|
||||
} else {
|
||||
final String message = "Unable to create instance of " + rawType + "; ReflectionAccessFilter "
|
||||
+ "does not permit using reflection or Unsafe. Register an InstanceCreator or a TypeAdapter "
|
||||
+ "for this type or adjust the access filter to allow using reflection.";
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
throw new JsonIOException(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
|
||||
/**
|
||||
* Creates constructors for special JDK collection types which do not have a public no-args constructor.
|
||||
*/
|
||||
private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(final Type type, Class<? super T> rawType) {
|
||||
if (EnumSet.class.isAssignableFrom(rawType)) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
if (elementType instanceof Class) {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
T set = (T) EnumSet.noneOf((Class)elementType);
|
||||
return set;
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
|
||||
}
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// Only support creation of EnumMap, but not of custom subtypes; for them type parameters
|
||||
// and constructor parameter might have completely different meaning
|
||||
else if (rawType == EnumMap.class) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
if (elementType instanceof Class) {
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
T map = (T) new EnumMap((Class) elementType);
|
||||
return map;
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumMap type: " + type.toString());
|
||||
}
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumMap type: " + type.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType, FilterResult filterResult) {
|
||||
// Cannot invoke constructor of abstract class
|
||||
if (Modifier.isAbstract(rawType.getModifiers())) {
|
||||
return null;
|
||||
|
@ -112,27 +195,47 @@ public final class ConstructorConstructor {
|
|||
return null;
|
||||
}
|
||||
|
||||
final String exceptionMessage = ReflectionHelper.tryMakeAccessible(constructor);
|
||||
if (exceptionMessage != null) {
|
||||
/*
|
||||
* Create ObjectConstructor which throws exception.
|
||||
* This keeps backward compatibility (compared to returning `null` which
|
||||
* would then choose another way of creating object).
|
||||
* And it supports types which are only serialized but not deserialized
|
||||
* (compared to directly throwing exception here), e.g. when runtime type
|
||||
* of object is inaccessible, but compile-time type is accessible.
|
||||
*/
|
||||
boolean canAccess = filterResult == FilterResult.ALLOW || (ReflectionAccessFilterHelper.canAccess(constructor, null)
|
||||
// Be a bit more lenient here for BLOCK_ALL; if constructor is accessible and public then allow calling it
|
||||
&& (filterResult != FilterResult.BLOCK_ALL || Modifier.isPublic(constructor.getModifiers())));
|
||||
|
||||
if (!canAccess) {
|
||||
final String message = "Unable to invoke no-args constructor of " + rawType + "; "
|
||||
+ "constructor is not accessible and ReflectionAccessFilter does not permit making "
|
||||
+ "it accessible. Register an InstanceCreator or a TypeAdapter for this type, change "
|
||||
+ "the visibility of the constructor or adjust the access filter.";
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override
|
||||
public T construct() {
|
||||
// New exception is created every time to avoid keeping reference
|
||||
// to exception with potentially long stack trace, causing a
|
||||
// memory leak
|
||||
throw new JsonIOException(exceptionMessage);
|
||||
@Override public T construct() {
|
||||
throw new JsonIOException(message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Only try to make accessible if allowed; in all other cases checks above should
|
||||
// have verified that constructor is accessible
|
||||
if (filterResult == FilterResult.ALLOW) {
|
||||
final String exceptionMessage = ReflectionHelper.tryMakeAccessible(constructor);
|
||||
if (exceptionMessage != null) {
|
||||
/*
|
||||
* Create ObjectConstructor which throws exception.
|
||||
* This keeps backward compatibility (compared to returning `null` which
|
||||
* would then choose another way of creating object).
|
||||
* And it supports types which are only serialized but not deserialized
|
||||
* (compared to directly throwing exception here), e.g. when runtime type
|
||||
* of object is inaccessible, but compile-time type is accessible.
|
||||
*/
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override
|
||||
public T construct() {
|
||||
// New exception is created every time to avoid keeping reference
|
||||
// to exception with potentially long stack trace, causing a
|
||||
// memory leak
|
||||
throw new JsonIOException(exceptionMessage);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
try {
|
||||
|
@ -148,7 +251,7 @@ public final class ConstructorConstructor {
|
|||
throw new RuntimeException("Failed to invoke " + constructor + " with no args",
|
||||
e.getTargetException());
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -159,8 +262,17 @@ public final class ConstructorConstructor {
|
|||
* subtypes.
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
|
||||
private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
|
||||
private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
|
||||
final Type type, Class<? super T> rawType) {
|
||||
|
||||
/*
|
||||
* IMPORTANT: Must only create instances for classes with public no-args constructor.
|
||||
* For classes with special constructors / factory methods (e.g. EnumSet)
|
||||
* `newSpecialCollectionConstructor` defined above must be used, to avoid no-args
|
||||
* constructor check (which is called before this method) detecting internal no-args
|
||||
* constructors which might be added in a future JDK version
|
||||
*/
|
||||
|
||||
if (Collection.class.isAssignableFrom(rawType)) {
|
||||
if (SortedSet.class.isAssignableFrom(rawType)) {
|
||||
return new ObjectConstructor<T>() {
|
||||
|
@ -168,22 +280,6 @@ public final class ConstructorConstructor {
|
|||
return (T) new TreeSet<Object>();
|
||||
}
|
||||
};
|
||||
} else if (EnumSet.class.isAssignableFrom(rawType)) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override public T construct() {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
if (elementType instanceof Class) {
|
||||
return (T) EnumSet.noneOf((Class)elementType);
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
|
||||
}
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (Set.class.isAssignableFrom(rawType)) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
|
@ -206,26 +302,7 @@ public final class ConstructorConstructor {
|
|||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(rawType)) {
|
||||
// Only support creation of EnumMap, but not of custom subtypes; for them type parameters
|
||||
// and constructor parameter might have completely different meaning
|
||||
if (rawType == EnumMap.class) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
if (type instanceof ParameterizedType) {
|
||||
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
|
||||
if (elementType instanceof Class) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
T map = (T) new EnumMap((Class) elementType);
|
||||
return map;
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumMap type: " + type.toString());
|
||||
}
|
||||
} else {
|
||||
throw new JsonIOException("Invalid EnumMap type: " + type.toString());
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
|
||||
if (ConcurrentNavigableMap.class.isAssignableFrom(rawType)) {
|
||||
return new ObjectConstructor<T>() {
|
||||
@Override public T construct() {
|
||||
return (T) new ConcurrentSkipListMap<Object, Object>();
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package com.google.gson.internal;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.ReflectionAccessFilter;
|
||||
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
||||
|
||||
/**
|
||||
* Internal helper class for {@link ReflectionAccessFilter}.
|
||||
*/
|
||||
public class ReflectionAccessFilterHelper {
|
||||
private ReflectionAccessFilterHelper() { }
|
||||
|
||||
// Platform type detection is based on Moshi's Util.isPlatformType(Class)
|
||||
// See https://github.com/square/moshi/blob/3c108919ee1cce88a433ffda04eeeddc0341eae7/moshi/src/main/java/com/squareup/moshi/internal/Util.java#L141
|
||||
|
||||
public static boolean isJavaType(Class<?> c) {
|
||||
return isJavaType(c.getName());
|
||||
}
|
||||
|
||||
private static boolean isJavaType(String className) {
|
||||
return className.startsWith("java.") || className.startsWith("javax.");
|
||||
}
|
||||
|
||||
public static boolean isAndroidType(Class<?> c) {
|
||||
return isAndroidType(c.getName());
|
||||
}
|
||||
|
||||
private static boolean isAndroidType(String className) {
|
||||
return className.startsWith("android.")
|
||||
|| className.startsWith("androidx.")
|
||||
|| isJavaType(className);
|
||||
}
|
||||
|
||||
public static boolean isAnyPlatformType(Class<?> c) {
|
||||
String className = c.getName();
|
||||
return isAndroidType(className) // Covers Android and Java
|
||||
|| className.startsWith("kotlin.")
|
||||
|| className.startsWith("kotlinx.")
|
||||
|| className.startsWith("scala.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the result of applying all filters until the first one returns a result
|
||||
* other than {@link FilterResult#INDECISIVE}, or {@link FilterResult#ALLOW} if
|
||||
* the list of filters is empty or all returned {@code INDECISIVE}.
|
||||
*/
|
||||
public static FilterResult getFilterResult(List<ReflectionAccessFilter> reflectionFilters, Class<?> c) {
|
||||
for (ReflectionAccessFilter filter : reflectionFilters) {
|
||||
FilterResult result = filter.check(c);
|
||||
if (result != FilterResult.INDECISIVE) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return FilterResult.ALLOW;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link AccessibleObject#canAccess(Object)} (Java >= 9)
|
||||
*/
|
||||
public static boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
||||
return AccessChecker.INSTANCE.canAccess(accessibleObject, object);
|
||||
}
|
||||
|
||||
private static abstract class AccessChecker {
|
||||
public static final AccessChecker INSTANCE;
|
||||
static {
|
||||
AccessChecker accessChecker = null;
|
||||
// TODO: Ideally should use Multi-Release JAR for this version specific code
|
||||
if (JavaVersion.isJava9OrLater()) {
|
||||
try {
|
||||
final Method canAccessMethod = AccessibleObject.class.getDeclaredMethod("canAccess", Object.class);
|
||||
accessChecker = new AccessChecker() {
|
||||
@Override public boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
||||
try {
|
||||
return (Boolean) canAccessMethod.invoke(accessibleObject, object);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed invoking canAccess", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (accessChecker == null) {
|
||||
accessChecker = new AccessChecker() {
|
||||
@Override public boolean canAccess(AccessibleObject accessibleObject, Object object) {
|
||||
// Cannot determine whether object can be accessed, so assume it can be accessed
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
INSTANCE = accessChecker;
|
||||
}
|
||||
|
||||
public abstract boolean canAccess(AccessibleObject accessibleObject, Object object);
|
||||
}
|
||||
}
|
|
@ -31,6 +31,37 @@ import java.lang.reflect.Modifier;
|
|||
public abstract class UnsafeAllocator {
|
||||
public abstract <T> T newInstance(Class<T> c) throws Exception;
|
||||
|
||||
/**
|
||||
* Check if the class can be instantiated by Unsafe allocator. If the instance has interface or abstract modifiers
|
||||
* return an exception message.
|
||||
* @param c instance of the class to be checked
|
||||
* @return if instantiable {@code null}, else a non-{@code null} exception message
|
||||
*/
|
||||
static String checkInstantiable(Class<?> c) {
|
||||
int modifiers = c.getModifiers();
|
||||
if (Modifier.isInterface(modifiers)) {
|
||||
return "Interfaces can't be instantiated! Register an InstanceCreator "
|
||||
+ "or a TypeAdapter for this type. Interface name: " + c.getName();
|
||||
}
|
||||
if (Modifier.isAbstract(modifiers)) {
|
||||
return "Abstract classes can't be instantiated! Register an InstanceCreator "
|
||||
+ "or a TypeAdapter for this type. Class name: " + c.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the class is instantiable. This check should have already occurred
|
||||
* in {@link ConstructorConstructor}; this check here acts as safeguard since trying
|
||||
* to use Unsafe for non-instantiable classes might crash the JVM on some devices.
|
||||
*/
|
||||
private static void assertInstantiable(Class<?> c) {
|
||||
String exceptionMessage = checkInstantiable(c);
|
||||
if (exceptionMessage != null) {
|
||||
throw new AssertionError("UnsafeAllocator is used for non-instantiable type: " + exceptionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public static UnsafeAllocator create() {
|
||||
// try JVM
|
||||
// public class Unsafe {
|
||||
|
@ -106,19 +137,4 @@ public abstract class UnsafeAllocator {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the class can be instantiated by unsafe allocator. If the instance has interface or abstract modifiers
|
||||
* throw an {@link java.lang.UnsupportedOperationException}
|
||||
* @param c instance of the class to be checked
|
||||
*/
|
||||
static void assertInstantiable(Class<?> c) {
|
||||
int modifiers = c.getModifiers();
|
||||
if (Modifier.isInterface(modifiers)) {
|
||||
throw new UnsupportedOperationException("Interface can't be instantiated! Interface name: " + c.getName());
|
||||
}
|
||||
if (Modifier.isAbstract(modifiers)) {
|
||||
throw new UnsupportedOperationException("Abstract class can't be instantiated! Class name: " + c.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,10 @@ package com.google.gson.internal.bind;
|
|||
|
||||
import com.google.gson.FieldNamingStrategy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.ReflectionAccessFilter;
|
||||
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
|
@ -28,6 +31,7 @@ import com.google.gson.internal.ConstructorConstructor;
|
|||
import com.google.gson.internal.Excluder;
|
||||
import com.google.gson.internal.ObjectConstructor;
|
||||
import com.google.gson.internal.Primitives;
|
||||
import com.google.gson.internal.ReflectionAccessFilterHelper;
|
||||
import com.google.gson.internal.reflect.ReflectionHelper;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
|
@ -35,6 +39,7 @@ import com.google.gson.stream.JsonToken;
|
|||
import com.google.gson.stream.JsonWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -50,14 +55,17 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
private final FieldNamingStrategy fieldNamingPolicy;
|
||||
private final Excluder excluder;
|
||||
private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory;
|
||||
private final List<ReflectionAccessFilter> reflectionFilters;
|
||||
|
||||
public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
|
||||
FieldNamingStrategy fieldNamingPolicy, Excluder excluder,
|
||||
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) {
|
||||
JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory,
|
||||
List<ReflectionAccessFilter> reflectionFilters) {
|
||||
this.constructorConstructor = constructorConstructor;
|
||||
this.fieldNamingPolicy = fieldNamingPolicy;
|
||||
this.excluder = excluder;
|
||||
this.jsonAdapterFactory = jsonAdapterFactory;
|
||||
this.reflectionFilters = reflectionFilters;
|
||||
}
|
||||
|
||||
public boolean excludeField(Field f, boolean serialize) {
|
||||
|
@ -97,13 +105,30 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
return null; // it's a primitive!
|
||||
}
|
||||
|
||||
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
||||
if (filterResult == FilterResult.BLOCK_ALL) {
|
||||
throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for "
|
||||
+ raw + ". Register a TypeAdapter for this type or adjust the access filter.");
|
||||
}
|
||||
boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
||||
|
||||
ObjectConstructor<T> constructor = constructorConstructor.get(type);
|
||||
return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
|
||||
return new Adapter<T>(constructor, getBoundFields(gson, type, raw, blockInaccessible));
|
||||
}
|
||||
|
||||
private static void checkAccessible(Object object, Field field) {
|
||||
if (!ReflectionAccessFilterHelper.canAccess(field, Modifier.isStatic(field.getModifiers()) ? null : object)) {
|
||||
throw new JsonIOException("Field '" + field.getDeclaringClass().getName() + "#"
|
||||
+ field.getName() + "' is not accessible and ReflectionAccessFilter does not "
|
||||
+ "permit making it accessible. Register a TypeAdapter for the declaring type "
|
||||
+ "or adjust the access filter.");
|
||||
}
|
||||
}
|
||||
|
||||
private ReflectiveTypeAdapterFactory.BoundField createBoundField(
|
||||
final Gson context, final Field field, final String name,
|
||||
final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
|
||||
final TypeToken<?> fieldType, boolean serialize, boolean deserialize,
|
||||
final boolean blockInaccessible) {
|
||||
final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
|
||||
// special casing primitives here saves ~5% on Android...
|
||||
JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
|
||||
|
@ -120,7 +145,17 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
@SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
|
||||
@Override void write(JsonWriter writer, Object value)
|
||||
throws IOException, IllegalAccessException {
|
||||
if (!serialized) return;
|
||||
if (blockInaccessible) {
|
||||
checkAccessible(value, field);
|
||||
}
|
||||
|
||||
Object fieldValue = field.get(value);
|
||||
if (fieldValue == value) {
|
||||
// avoid direct recursion
|
||||
return;
|
||||
}
|
||||
writer.name(name);
|
||||
TypeAdapter t = jsonAdapterPresent ? typeAdapter
|
||||
: new TypeAdapterRuntimeTypeWrapper(context, typeAdapter, fieldType.getType());
|
||||
t.write(writer, fieldValue);
|
||||
|
@ -129,33 +164,48 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
throws IOException, IllegalAccessException {
|
||||
Object fieldValue = typeAdapter.read(reader);
|
||||
if (fieldValue != null || !isPrimitive) {
|
||||
if (blockInaccessible) {
|
||||
checkAccessible(value, field);
|
||||
}
|
||||
field.set(value, fieldValue);
|
||||
}
|
||||
}
|
||||
@Override public boolean writeField(Object value) throws IOException, IllegalAccessException {
|
||||
if (!serialized) return false;
|
||||
Object fieldValue = field.get(value);
|
||||
return fieldValue != value; // avoid recursion for example for Throwable.cause
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
|
||||
private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw, boolean blockInaccessible) {
|
||||
Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
|
||||
if (raw.isInterface()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Type declaredType = type.getType();
|
||||
Class<?> originalRaw = raw;
|
||||
while (raw != Object.class) {
|
||||
Field[] fields = raw.getDeclaredFields();
|
||||
|
||||
// For inherited fields, check if access to their declaring class is allowed
|
||||
if (raw != originalRaw && fields.length > 0) {
|
||||
FilterResult filterResult = ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);
|
||||
if (filterResult == FilterResult.BLOCK_ALL) {
|
||||
throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for "
|
||||
+ raw + " (supertype of " + originalRaw + "). Register a TypeAdapter for this type "
|
||||
+ "or adjust the access filter.");
|
||||
}
|
||||
blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;
|
||||
}
|
||||
|
||||
for (Field field : fields) {
|
||||
boolean serialize = excludeField(field, true);
|
||||
boolean deserialize = excludeField(field, false);
|
||||
if (!serialize && !deserialize) {
|
||||
continue;
|
||||
}
|
||||
ReflectionHelper.makeAccessible(field);
|
||||
|
||||
// If blockInaccessible, skip and perform access check later
|
||||
if (!blockInaccessible) {
|
||||
ReflectionHelper.makeAccessible(field);
|
||||
}
|
||||
Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
|
||||
List<String> fieldNames = getFieldNames(field);
|
||||
BoundField previous = null;
|
||||
|
@ -163,7 +213,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
String name = fieldNames.get(i);
|
||||
if (i != 0) serialize = false; // only serialize the default name
|
||||
BoundField boundField = createBoundField(context, field, name,
|
||||
TypeToken.get(fieldType), serialize, deserialize);
|
||||
TypeToken.get(fieldType), serialize, deserialize, blockInaccessible);
|
||||
BoundField replaced = result.put(name, boundField);
|
||||
if (previous == null) previous = replaced;
|
||||
}
|
||||
|
@ -188,7 +238,6 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
this.serialized = serialized;
|
||||
this.deserialized = deserialized;
|
||||
}
|
||||
abstract boolean writeField(Object value) throws IOException, IllegalAccessException;
|
||||
abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
|
||||
abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
|
||||
}
|
||||
|
@ -224,7 +273,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
} catch (IllegalStateException e) {
|
||||
throw new JsonSyntaxException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
||||
}
|
||||
in.endObject();
|
||||
return instance;
|
||||
|
@ -239,13 +288,10 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
|
|||
out.beginObject();
|
||||
try {
|
||||
for (BoundField boundField : boundFields.values()) {
|
||||
if (boundField.writeField(value)) {
|
||||
out.name(boundField.name);
|
||||
boundField.write(out, value);
|
||||
}
|
||||
boundField.write(out, value);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);
|
||||
}
|
||||
out.endObject();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.google.gson.internal.reflect;
|
||||
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.internal.GsonBuildConfig;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
|
@ -63,4 +64,11 @@ public class ReflectionHelper {
|
|||
+ exception.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static RuntimeException createExceptionForUnexpectedIllegalAccess(IllegalAccessException exception) {
|
||||
throw new RuntimeException("Unexpected IllegalAccessException occurred (Gson " + GsonBuildConfig.VERSION + "). "
|
||||
+ "Certain ReflectionAccessFilter features require Java >= 9 to work correctly. If you are not using "
|
||||
+ "ReflectionAccessFilter, report this to the Gson maintainers.",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.lang.reflect.Field;
|
|||
import java.lang.reflect.Type;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
|
@ -56,7 +57,8 @@ public final class GsonTest extends TestCase {
|
|||
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY);
|
||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
|
||||
Collections.<ReflectionAccessFilter>emptyList());
|
||||
|
||||
assertEquals(CUSTOM_EXCLUDER, gson.excluder);
|
||||
assertEquals(CUSTOM_FIELD_NAMING_STRATEGY, gson.fieldNamingStrategy());
|
||||
|
@ -70,7 +72,8 @@ public final class GsonTest extends TestCase {
|
|||
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
|
||||
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
|
||||
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
|
||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY);
|
||||
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
|
||||
Collections.<ReflectionAccessFilter>emptyList());
|
||||
|
||||
Gson clone = original.newBuilder()
|
||||
.registerTypeAdapter(Object.class, new TestTypeAdapter())
|
||||
|
|
|
@ -0,0 +1,426 @@
|
|||
package com.google.gson.functional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import com.google.gson.ReflectionAccessFilter;
|
||||
import com.google.gson.ReflectionAccessFilter.FilterResult;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.internal.ConstructorConstructor;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import java.awt.Point;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ReflectionAccessFilterTest {
|
||||
// Reader has protected `lock` field which cannot be accessed
|
||||
private static class ClassExtendingJdkClass extends Reader {
|
||||
@Override
|
||||
public int read(char[] cbuf, int off, int len) throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockInaccessibleJava() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(ReflectionAccessFilter.BLOCK_INACCESSIBLE_JAVA)
|
||||
.create();
|
||||
|
||||
// Serialization should fail for classes with non-public fields
|
||||
try {
|
||||
gson.toJson(new File("a"));
|
||||
fail("Expected exception; test needs to be run with Java >= 9");
|
||||
} catch (JsonIOException expected) {
|
||||
// Note: This test is rather brittle and depends on the JDK implementation
|
||||
assertEquals(
|
||||
"Field 'java.io.File#path' is not accessible and ReflectionAccessFilter does not permit "
|
||||
+ "making it accessible. Register a TypeAdapter for the declaring type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
// But serialization should succeed for classes with only public fields
|
||||
String json = gson.toJson(new Point(1, 2));
|
||||
assertEquals("{\"x\":1,\"y\":2}", json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockInaccessibleJavaExtendingJdkClass() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(ReflectionAccessFilter.BLOCK_INACCESSIBLE_JAVA)
|
||||
.create();
|
||||
|
||||
try {
|
||||
gson.toJson(new ClassExtendingJdkClass());
|
||||
fail("Expected exception; test needs to be run with Java >= 9");
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"Field 'java.io.Reader#lock' is not accessible and ReflectionAccessFilter does not permit "
|
||||
+ "making it accessible. Register a TypeAdapter for the declaring type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockAllJava() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(ReflectionAccessFilter.BLOCK_ALL_JAVA)
|
||||
.create();
|
||||
|
||||
// Serialization should fail for any Java class
|
||||
try {
|
||||
gson.toJson(Thread.currentThread());
|
||||
fail();
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"ReflectionAccessFilter does not permit using reflection for class java.lang.Thread. "
|
||||
+ "Register a TypeAdapter for this type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockAllJavaExtendingJdkClass() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(ReflectionAccessFilter.BLOCK_ALL_JAVA)
|
||||
.create();
|
||||
|
||||
try {
|
||||
gson.toJson(new ClassExtendingJdkClass());
|
||||
fail();
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"ReflectionAccessFilter does not permit using reflection for class java.io.Reader "
|
||||
+ "(supertype of class com.google.gson.functional.ReflectionAccessFilterTest$ClassExtendingJdkClass). "
|
||||
+ "Register a TypeAdapter for this type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassWithStaticField {
|
||||
@SuppressWarnings("unused")
|
||||
private static int i = 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockInaccessibleStaticField() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_INACCESSIBLE;
|
||||
}
|
||||
})
|
||||
// Include static fields
|
||||
.excludeFieldsWithModifiers(0)
|
||||
.create();
|
||||
|
||||
try {
|
||||
gson.toJson(new ClassWithStaticField());
|
||||
fail("Expected exception; test needs to be run with Java >= 9");
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithStaticField#i' "
|
||||
+ "is not accessible and ReflectionAccessFilter does not permit making it accessible. "
|
||||
+ "Register a TypeAdapter for the declaring type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class SuperTestClass {
|
||||
}
|
||||
private static class SubTestClass extends SuperTestClass {
|
||||
@SuppressWarnings("unused")
|
||||
public int i = 1;
|
||||
}
|
||||
private static class OtherClass {
|
||||
@SuppressWarnings("unused")
|
||||
public int i = 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelegation() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
// INDECISIVE in last filter should act like ALLOW
|
||||
return SuperTestClass.class.isAssignableFrom(rawClass) ? FilterResult.BLOCK_ALL : FilterResult.INDECISIVE;
|
||||
}
|
||||
})
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
// INDECISIVE should delegate to previous filter
|
||||
return rawClass == SubTestClass.class ? FilterResult.ALLOW : FilterResult.INDECISIVE;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
// Filter disallows SuperTestClass
|
||||
try {
|
||||
gson.toJson(new SuperTestClass());
|
||||
fail();
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"ReflectionAccessFilter does not permit using reflection for class "
|
||||
+ "com.google.gson.functional.ReflectionAccessFilterTest$SuperTestClass. "
|
||||
+ "Register a TypeAdapter for this type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
// But registration order is reversed, so filter for SubTestClass allows reflection
|
||||
String json = gson.toJson(new SubTestClass());
|
||||
assertEquals("{\"i\":1}", json);
|
||||
|
||||
// And unrelated class should not be affected
|
||||
json = gson.toJson(new OtherClass());
|
||||
assertEquals("{\"i\":2}", json);
|
||||
}
|
||||
|
||||
private static class ClassWithPrivateField {
|
||||
@SuppressWarnings("unused")
|
||||
private int i = 1;
|
||||
}
|
||||
private static class ExtendingClassWithPrivateField extends ClassWithPrivateField {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllowForSupertype() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_INACCESSIBLE;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
// First make sure test is implemented correctly and access is blocked
|
||||
try {
|
||||
gson.toJson(new ExtendingClassWithPrivateField());
|
||||
fail("Expected exception; test needs to be run with Java >= 9");
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateField#i' "
|
||||
+ "is not accessible and ReflectionAccessFilter does not permit making it accessible. "
|
||||
+ "Register a TypeAdapter for the declaring type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
gson = gson.newBuilder()
|
||||
// Allow reflective access for supertype
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return rawClass == ClassWithPrivateField.class ? FilterResult.ALLOW : FilterResult.INDECISIVE;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
// Inherited (inaccessible) private field should have been made accessible
|
||||
String json = gson.toJson(new ExtendingClassWithPrivateField());
|
||||
assertEquals("{\"i\":1}", json);
|
||||
}
|
||||
|
||||
private static class ClassWithPrivateNoArgsConstructor {
|
||||
private ClassWithPrivateNoArgsConstructor() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInaccessibleNoArgsConstructor() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_INACCESSIBLE;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
try {
|
||||
gson.fromJson("{}", ClassWithPrivateNoArgsConstructor.class);
|
||||
fail("Expected exception; test needs to be run with Java >= 9");
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"Unable to invoke no-args constructor of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateNoArgsConstructor; "
|
||||
+ "constructor is not accessible and ReflectionAccessFilter does not permit making it accessible. Register an "
|
||||
+ "InstanceCreator or a TypeAdapter for this type, change the visibility of the constructor or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassWithoutNoArgsConstructor {
|
||||
public String s;
|
||||
|
||||
public ClassWithoutNoArgsConstructor(String s) {
|
||||
this.s = s;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassWithoutNoArgsConstructor() {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
// Even BLOCK_INACCESSIBLE should prevent usage of Unsafe for object creation
|
||||
return FilterResult.BLOCK_INACCESSIBLE;
|
||||
}
|
||||
});
|
||||
Gson gson = gsonBuilder.create();
|
||||
|
||||
try {
|
||||
gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
|
||||
fail();
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"Unable to create instance of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithoutNoArgsConstructor; "
|
||||
+ "ReflectionAccessFilter does not permit using reflection or Unsafe. Register an InstanceCreator "
|
||||
+ "or a TypeAdapter for this type or adjust the access filter to allow using reflection.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
|
||||
// But should not fail when custom TypeAdapter is specified
|
||||
gson = gson.newBuilder()
|
||||
.registerTypeAdapter(ClassWithoutNoArgsConstructor.class, new TypeAdapter<ClassWithoutNoArgsConstructor>() {
|
||||
@Override public ClassWithoutNoArgsConstructor read(JsonReader in) throws IOException {
|
||||
in.skipValue();
|
||||
return new ClassWithoutNoArgsConstructor("TypeAdapter");
|
||||
}
|
||||
@Override public void write(JsonWriter out, ClassWithoutNoArgsConstructor value) throws IOException {
|
||||
throw new AssertionError("Not needed for test");
|
||||
};
|
||||
})
|
||||
.create();
|
||||
ClassWithoutNoArgsConstructor deserialized = gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
|
||||
assertEquals("TypeAdapter", deserialized.s);
|
||||
|
||||
// But should not fail when custom InstanceCreator is specified
|
||||
gson = gsonBuilder
|
||||
.registerTypeAdapter(ClassWithoutNoArgsConstructor.class, new InstanceCreator<ClassWithoutNoArgsConstructor>() {
|
||||
@Override public ClassWithoutNoArgsConstructor createInstance(Type type) {
|
||||
return new ClassWithoutNoArgsConstructor("InstanceCreator");
|
||||
}
|
||||
})
|
||||
.create();
|
||||
deserialized = gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
|
||||
assertEquals("InstanceCreator", deserialized.s);
|
||||
}
|
||||
|
||||
/**
|
||||
* When using {@link FilterResult#BLOCK_ALL}, registering only a {@link JsonSerializer}
|
||||
* but not performing any deserialization should not throw any exception.
|
||||
*/
|
||||
@Test
|
||||
public void testBlockAllPartial() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_ALL;
|
||||
}
|
||||
})
|
||||
.registerTypeAdapter(OtherClass.class, new JsonSerializer<OtherClass>() {
|
||||
@Override public JsonElement serialize(OtherClass src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
return new JsonPrimitive(123);
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
String json = gson.toJson(new OtherClass());
|
||||
assertEquals("123", json);
|
||||
|
||||
// But deserialization should fail
|
||||
try {
|
||||
gson.fromJson("{}", OtherClass.class);
|
||||
fail();
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"ReflectionAccessFilter does not permit using reflection for class com.google.gson.functional.ReflectionAccessFilterTest$OtherClass. "
|
||||
+ "Register a TypeAdapter for this type or adjust the access filter.",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not fail when deserializing collection interface
|
||||
* (Even though this goes through {@link ConstructorConstructor} as well)
|
||||
*/
|
||||
@Test
|
||||
public void testBlockAllCollectionInterface() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_ALL;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
List<?> deserialized = gson.fromJson("[1.0]", List.class);
|
||||
assertEquals(1.0, deserialized.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Should not fail when deserializing specific collection implementation
|
||||
* (Even though this goes through {@link ConstructorConstructor} as well)
|
||||
*/
|
||||
@Test
|
||||
public void testBlockAllCollectionImplementation() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_ALL;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
List<?> deserialized = gson.fromJson("[1.0]", LinkedList.class);
|
||||
assertEquals(1.0, deserialized.get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* When trying to deserialize interface an exception for that should
|
||||
* be thrown, even if {@link FilterResult#BLOCK_INACCESSIBLE} is used
|
||||
*/
|
||||
@Test
|
||||
public void testBlockInaccessibleInterface() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.addReflectionAccessFilter(new ReflectionAccessFilter() {
|
||||
@Override public FilterResult check(Class<?> rawClass) {
|
||||
return FilterResult.BLOCK_INACCESSIBLE;
|
||||
}
|
||||
})
|
||||
.create();
|
||||
|
||||
try {
|
||||
gson.fromJson("{}", Runnable.class);
|
||||
fail();
|
||||
} catch (JsonIOException expected) {
|
||||
assertEquals(
|
||||
"Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for "
|
||||
+ "this type. Interface name: java.lang.Runnable",
|
||||
expected.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,17 +3,18 @@ package com.google.gson.internal;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.ReflectionAccessFilter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
public class ConstructorConstructorTest {
|
||||
private static final Map<Type, InstanceCreator<?>> NO_INSTANCE_CREATORS = Collections.emptyMap();
|
||||
private ConstructorConstructor constructorConstructor = new ConstructorConstructor(
|
||||
Collections.<Type, InstanceCreator<?>>emptyMap(), true,
|
||||
Collections.<ReflectionAccessFilter>emptyList()
|
||||
);
|
||||
|
||||
private abstract static class AbstractClass {
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -27,15 +28,14 @@ public class ConstructorConstructorTest {
|
|||
*/
|
||||
@Test
|
||||
public void testGet_AbstractClassNoArgConstructor() {
|
||||
ConstructorConstructor constructorFactory = new ConstructorConstructor(NO_INSTANCE_CREATORS, true);
|
||||
ObjectConstructor<AbstractClass> constructor = constructorFactory.get(TypeToken.get(AbstractClass.class));
|
||||
ObjectConstructor<AbstractClass> constructor = constructorConstructor.get(TypeToken.get(AbstractClass.class));
|
||||
try {
|
||||
constructor.construct();
|
||||
fail("Expected exception");
|
||||
} catch (RuntimeException exception) {
|
||||
assertEquals(
|
||||
"Unable to create instance of class com.google.gson.internal.ConstructorConstructorTest$AbstractClass. "
|
||||
+ "Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args constructor may fix this problem.",
|
||||
"Abstract classes can't be instantiated! Register an InstanceCreator or a TypeAdapter for this "
|
||||
+ "type. Class name: com.google.gson.internal.ConstructorConstructorTest$AbstractClass",
|
||||
exception.getMessage()
|
||||
);
|
||||
}
|
||||
|
@ -43,15 +43,14 @@ public class ConstructorConstructorTest {
|
|||
|
||||
@Test
|
||||
public void testGet_Interface() {
|
||||
ConstructorConstructor constructorFactory = new ConstructorConstructor(NO_INSTANCE_CREATORS, true);
|
||||
ObjectConstructor<Interface> constructor = constructorFactory.get(TypeToken.get(Interface.class));
|
||||
ObjectConstructor<Interface> constructor = constructorConstructor.get(TypeToken.get(Interface.class));
|
||||
try {
|
||||
constructor.construct();
|
||||
fail("Expected exception");
|
||||
} catch (RuntimeException exception) {
|
||||
assertEquals(
|
||||
"Unable to create instance of interface com.google.gson.internal.ConstructorConstructorTest$Interface. "
|
||||
+ "Registering an InstanceCreator or a TypeAdapter for this type, or adding a no-args constructor may fix this problem.",
|
||||
"Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for "
|
||||
+ "this type. Interface name: com.google.gson.internal.ConstructorConstructorTest$Interface",
|
||||
exception.getMessage()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -33,42 +33,39 @@ public final class UnsafeAllocatorInstantiationTest extends TestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Ensure that the {@link java.lang.UnsupportedOperationException} is thrown when trying
|
||||
* Ensure that an {@link AssertionError} is thrown when trying
|
||||
* to instantiate an interface
|
||||
*/
|
||||
public void testInterfaceInstantiation() {
|
||||
public void testInterfaceInstantiation() throws Exception {
|
||||
UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
|
||||
try {
|
||||
unsafeAllocator.newInstance(Interface.class);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertEquals(e.getClass(), UnsupportedOperationException.class);
|
||||
} catch (AssertionError e) {
|
||||
assertTrue(e.getMessage().startsWith("UnsafeAllocator is used for non-instantiable type"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the {@link java.lang.UnsupportedOperationException} is thrown when trying
|
||||
* Ensure that an {@link AssertionError} is thrown when trying
|
||||
* to instantiate an abstract class
|
||||
*/
|
||||
public void testAbstractClassInstantiation() {
|
||||
public void testAbstractClassInstantiation() throws Exception {
|
||||
UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
|
||||
try {
|
||||
unsafeAllocator.newInstance(AbstractClass.class);
|
||||
fail();
|
||||
} catch (Exception e) {
|
||||
assertEquals(e.getClass(), UnsupportedOperationException.class);
|
||||
} catch (AssertionError e) {
|
||||
assertTrue(e.getMessage().startsWith("UnsafeAllocator is used for non-instantiable type"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that no exception is thrown when trying to instantiate a concrete class
|
||||
*/
|
||||
public void testConcreteClassInstantiation() {
|
||||
public void testConcreteClassInstantiation() throws Exception {
|
||||
UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
|
||||
try {
|
||||
unsafeAllocator.newInstance(ConcreteClass.class);
|
||||
} catch (Exception e) {
|
||||
fail();
|
||||
}
|
||||
ConcreteClass instance = unsafeAllocator.newInstance(ConcreteClass.class);
|
||||
assertNotNull(instance);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue