2023-01-31 23:05:12 +01:00
/ *
* Copyright ( C ) 2022 Google Inc .
*
* Licensed under the Apache License , Version 2 . 0 ( the " License " ) ;
* you may not use this file except in compliance with the License .
* You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
* /
2022-04-17 18:05:18 +02:00
package com.google.gson.functional ;
2023-01-31 20:20:54 +01:00
import static com.google.common.truth.Truth.assertThat ;
2022-04-17 18:05:18 +02:00
import static org.junit.Assert.fail ;
2023-02-06 15:13:28 +01:00
import static org.junit.Assume.assumeNotNull ;
2022-04-17 18:05:18 +02:00
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.io.File ;
import java.io.IOException ;
import java.io.Reader ;
2022-09-27 21:32:40 +02:00
import java.lang.reflect.Constructor ;
2022-04-17 18:05:18 +02:00
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
2022-09-27 21:32:40 +02:00
public void testBlockInaccessibleJava ( ) throws ReflectiveOperationException {
2022-04-17 18:05:18 +02:00
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
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( )
. isEqualTo ( " Field 'java.io.File#path' is not accessible and ReflectionAccessFilter does not permit "
2022-10-22 18:01:56 +02:00
+ " making it accessible. Register a TypeAdapter for the declaring type, adjust the access "
2023-01-31 20:20:54 +01:00
+ " filter or increase the visibility of the element and its declaring type. " ) ;
2022-04-17 18:05:18 +02:00
}
2022-09-27 21:32:40 +02:00
// But serialization should succeed for classes with only public fields.
// Not many JDK classes have mutable public fields, thank goodness, but java.awt.Point does.
Class < ? > pointClass = null ;
try {
pointClass = Class . forName ( " java.awt.Point " ) ;
} catch ( ClassNotFoundException e ) {
}
assumeNotNull ( pointClass ) ;
Constructor < ? > pointConstructor = pointClass . getConstructor ( int . class , int . class ) ;
Object point = pointConstructor . newInstance ( 1 , 2 ) ;
String json = gson . toJson ( point ) ;
2023-01-31 20:20:54 +01:00
assertThat ( json ) . isEqualTo ( " { \" x \" :1, \" y \" :2} " ) ;
2022-04-17 18:05:18 +02:00
}
@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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( )
. isEqualTo ( " Field 'java.io.Reader#lock' is not accessible and ReflectionAccessFilter does not permit "
+ " making it accessible. Register a TypeAdapter for the declaring type, adjust the access "
+ " filter or increase the visibility of the element and its declaring type. " ) ;
2022-04-17 18:05:18 +02:00
}
}
@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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( )
. isEqualTo ( " ReflectionAccessFilter does not permit using reflection for class java.lang.Thread. "
+ " Register a TypeAdapter for this type or adjust the access filter. " ) ;
2022-04-17 18:05:18 +02:00
}
}
@Test
public void testBlockAllJavaExtendingJdkClass ( ) {
Gson gson = new GsonBuilder ( )
. addReflectionAccessFilter ( ReflectionAccessFilter . BLOCK_ALL_JAVA )
. create ( ) ;
try {
gson . toJson ( new ClassExtendingJdkClass ( ) ) ;
fail ( ) ;
} catch ( JsonIOException expected ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( )
. isEqualTo ( " 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. " ) ;
2022-04-17 18:05:18 +02:00
}
}
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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( )
. isEqualTo ( " 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, adjust the access filter or increase "
+ " the visibility of the element and its declaring type. " ) ;
2022-04-17 18:05:18 +02:00
}
}
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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( ) . isEqualTo ( " ReflectionAccessFilter does not permit using reflection for class "
2022-10-22 18:01:56 +02:00
+ " com.google.gson.functional.ReflectionAccessFilterTest$SuperTestClass. "
2023-01-31 20:20:54 +01:00
+ " Register a TypeAdapter for this type or adjust the access filter. " ) ;
2022-04-17 18:05:18 +02:00
}
// But registration order is reversed, so filter for SubTestClass allows reflection
String json = gson . toJson ( new SubTestClass ( ) ) ;
2023-01-31 20:20:54 +01:00
assertThat ( json ) . isEqualTo ( " { \" i \" :1} " ) ;
2022-04-17 18:05:18 +02:00
// And unrelated class should not be affected
json = gson . toJson ( new OtherClass ( ) ) ;
2023-01-31 20:20:54 +01:00
assertThat ( json ) . isEqualTo ( " { \" i \" :2} " ) ;
2022-04-17 18:05:18 +02:00
}
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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( ) . isEqualTo ( " Field 'com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateField#i' "
2022-10-22 18:01:56 +02:00
+ " is not accessible and ReflectionAccessFilter does not permit making it accessible. "
+ " Register a TypeAdapter for the declaring type, adjust the access filter or increase "
2023-01-31 20:20:54 +01:00
+ " the visibility of the element and its declaring type. " ) ;
2022-04-17 18:05:18 +02:00
}
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 ( ) ) ;
2023-01-31 20:20:54 +01:00
assertThat ( json ) . isEqualTo ( " { \" i \" :1} " ) ;
2022-04-17 18:05:18 +02:00
}
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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( ) . isEqualTo ( " Unable to invoke no-args constructor of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithPrivateNoArgsConstructor; "
2022-10-22 18:01:56 +02:00
+ " constructor is not accessible and ReflectionAccessFilter does not permit making it accessible. Register an "
2023-01-31 20:20:54 +01:00
+ " InstanceCreator or a TypeAdapter for this type, change the visibility of the constructor or adjust the access filter. " ) ;
2022-04-17 18:05:18 +02:00
}
}
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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( ) . isEqualTo ( " Unable to create instance of class com.google.gson.functional.ReflectionAccessFilterTest$ClassWithoutNoArgsConstructor; "
2022-10-22 18:01:56 +02:00
+ " ReflectionAccessFilter does not permit using reflection or Unsafe. Register an InstanceCreator "
2023-01-31 20:20:54 +01:00
+ " or a TypeAdapter for this type or adjust the access filter to allow using reflection. " ) ;
2022-04-17 18:05:18 +02:00
}
// 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 " ) ;
}
2023-01-31 20:20:54 +01:00
@Override public void write ( JsonWriter out , ClassWithoutNoArgsConstructor value ) {
2022-04-17 18:05:18 +02:00
throw new AssertionError ( " Not needed for test " ) ;
2022-10-22 18:01:56 +02:00
}
2022-04-17 18:05:18 +02:00
} )
. create ( ) ;
ClassWithoutNoArgsConstructor deserialized = gson . fromJson ( " {} " , ClassWithoutNoArgsConstructor . class ) ;
2023-01-31 20:20:54 +01:00
assertThat ( deserialized . s ) . isEqualTo ( " TypeAdapter " ) ;
2022-04-17 18:05:18 +02:00
// 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 ) ;
2023-01-31 20:20:54 +01:00
assertThat ( deserialized . s ) . isEqualTo ( " InstanceCreator " ) ;
2022-04-17 18:05:18 +02:00
}
/ * *
* 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 ( ) ) ;
2023-01-31 20:20:54 +01:00
assertThat ( json ) . isEqualTo ( " 123 " ) ;
2022-04-17 18:05:18 +02:00
// But deserialization should fail
try {
gson . fromJson ( " {} " , OtherClass . class ) ;
fail ( ) ;
} catch ( JsonIOException expected ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( ) . isEqualTo ( " 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. " ) ;
2022-04-17 18:05:18 +02:00
}
}
/ * *
* 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 ) ;
2023-01-31 20:20:54 +01:00
assertThat ( deserialized . get ( 0 ) ) . isEqualTo ( 1 . 0 ) ;
2022-04-17 18:05:18 +02:00
}
/ * *
* 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 ) ;
2023-01-31 20:20:54 +01:00
assertThat ( deserialized . get ( 0 ) ) . isEqualTo ( 1 . 0 ) ;
2022-04-17 18:05:18 +02:00
}
/ * *
* 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 ) {
2023-01-31 20:20:54 +01:00
assertThat ( expected ) . hasMessageThat ( ) . isEqualTo ( " Interfaces can't be instantiated! Register an InstanceCreator or a TypeAdapter for "
+ " this type. Interface name: java.lang.Runnable " ) ;
2022-04-17 18:05:18 +02:00
}
}
}