Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for reflection access filter #1905

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -20,6 +20,7 @@
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;
Expand All @@ -30,6 +31,7 @@
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;
Expand All @@ -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));
Expand Down
12 changes: 8 additions & 4 deletions gson/src/main/java/com/google/gson/Gson.java
Expand Up @@ -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
Expand Down Expand Up @@ -199,7 +200,8 @@ public 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,
Expand All @@ -211,11 +213,12 @@ public 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;
Expand All @@ -232,6 +235,7 @@ public Gson() {
this.builderHierarchyFactories = builderHierarchyFactories;
this.objectToNumberStrategy = objectToNumberStrategy;
this.numberToNumberStrategy = numberToNumberStrategy;
this.reflectionFilters = reflectionFilters;

List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();

Expand Down Expand Up @@ -296,7 +300,7 @@ public 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);
}
Expand Down
32 changes: 29 additions & 3 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Expand Up @@ -22,6 +22,7 @@
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -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>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're on Java 7 now, right? So we should be able to use new LinkedList<>(), and similarly in other places in the PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I completely forgot that Java 7 added that feature already. Would you mind if I create a separate PR which replaces all redundant type arguments with the diamond operator, and include these changes there?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fair.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Have created #2104 for that.


/**
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
Expand Down Expand Up @@ -137,6 +139,7 @@ public GsonBuilder() {
this.useJdkUnsafe = gson.useJdkUnsafe;
this.objectToNumberStrategy = gson.objectToNumberStrategy;
this.numberToNumberStrategy = gson.numberToNumberStrategy;
this.reflectionFilters.addAll(gson.reflectionFilters);
}

/**
Expand Down Expand Up @@ -632,6 +635,28 @@ public GsonBuilder disableJdkUnsafe() {
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 least recently
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
* 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.
Expand All @@ -649,12 +674,13 @@ public Gson create() {

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,
Expand Down
194 changes: 194 additions & 0 deletions gson/src/main/java/com/google/gson/ReflectionAccessFilter.java
@@ -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 &sect;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,
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
/**
* 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).
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
*
* <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.
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
*
* <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.
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
*
* <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.
Marcono1234 marked this conversation as resolved.
Show resolved Hide resolved
*
* <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);
}