Skip to content

Commit 3f67886

Browse files
committed
Add GsonBuilder.disableJdkUnsafe()
1 parent 6819419 commit 3f67886

File tree

6 files changed

+78
-17
lines changed

6 files changed

+78
-17
lines changed

gson/src/main/java/com/google/gson/Gson.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ public final class Gson {
110110
static final boolean DEFAULT_SERIALIZE_NULLS = false;
111111
static final boolean DEFAULT_COMPLEX_MAP_KEYS = false;
112112
static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false;
113+
static final boolean DEFAULT_USE_JDK_UNSAFE = true;
113114

114115
private static final TypeToken<?> NULL_KEY_SURROGATE = TypeToken.get(Object.class);
115116
private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
@@ -187,6 +188,7 @@ public Gson() {
187188
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
188189
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
189190
DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
191+
DEFAULT_USE_JDK_UNSAFE,
190192
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT,
191193
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
192194
Collections.<TypeAdapterFactory>emptyList());
@@ -196,14 +198,15 @@ public Gson() {
196198
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
197199
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
198200
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
201+
boolean useJdkUnsafe,
199202
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
200203
int timeStyle, List<TypeAdapterFactory> builderFactories,
201204
List<TypeAdapterFactory> builderHierarchyFactories,
202205
List<TypeAdapterFactory> factoriesToBeAdded) {
203206
this.excluder = excluder;
204207
this.fieldNamingStrategy = fieldNamingStrategy;
205208
this.instanceCreators = instanceCreators;
206-
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
209+
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe);
207210
this.serializeNulls = serializeNulls;
208211
this.complexMapKeySerialization = complexMapKeySerialization;
209212
this.generateNonExecutableJson = generateNonExecutableGson;

gson/src/main/java/com/google/gson/GsonBuilder.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
4141
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
4242
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
43+
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
4344

4445
/**
4546
* <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
@@ -94,6 +95,7 @@ public final class GsonBuilder {
9495
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
9596
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
9697
private boolean lenient = DEFAULT_LENIENT;
98+
private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
9799

98100
/**
99101
* Creates a GsonBuilder instance that can be used to build Gson with various configuration
@@ -577,6 +579,24 @@ public GsonBuilder serializeSpecialFloatingPointValues() {
577579
return this;
578580
}
579581

582+
/**
583+
* Disables usage of JDK's {@code sun.misc.Unsafe}.
584+
*
585+
* <p>By default Gson uses {@code Unsafe} to create instances of classes which don't have
586+
* a no-args constructor. However, {@code Unsafe} might not be available for all Java
587+
* runtimes. For example Android does not provide {@code Unsafe}, or only with limited
588+
* functionality.<br>
589+
* Therefore, to get reliable behavior regardless of which runtime is used, and to detect
590+
* classes which cannot be deserialized in an early stage of development, this method allows
591+
* disabling usage of {@code Unsafe}.
592+
*
593+
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
594+
*/
595+
public GsonBuilder disableJdkUnsafe() {
596+
this.useJdkUnsafe = false;
597+
return this;
598+
}
599+
580600
/**
581601
* Creates a {@link Gson} instance based on the current configuration. This method is free of
582602
* side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
@@ -597,7 +617,7 @@ public Gson create() {
597617
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
598618
serializeNulls, complexMapKeySerialization,
599619
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
600-
serializeSpecialFloatingPointValues, longSerializationPolicy,
620+
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
601621
datePattern, dateStyle, timeStyle,
602622
this.factories, this.hierarchyFactories, factories);
603623
}

gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java

+25-13
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,11 @@
4848
*/
4949
public final class ConstructorConstructor {
5050
private final Map<Type, InstanceCreator<?>> instanceCreators;
51+
private final boolean useJdkUnsafe;
5152

52-
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators) {
53+
public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators, boolean useJdkUnsafe) {
5354
this.instanceCreators = instanceCreators;
55+
this.useJdkUnsafe = useJdkUnsafe;
5456
}
5557

5658
public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
@@ -236,19 +238,29 @@ private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
236238

237239
private <T> ObjectConstructor<T> newUnsafeAllocator(
238240
final Type type, final Class<? super T> rawType) {
239-
return new ObjectConstructor<T>() {
240-
private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
241-
@SuppressWarnings("unchecked")
242-
@Override public T construct() {
243-
try {
244-
Object newInstance = unsafeAllocator.newInstance(rawType);
245-
return (T) newInstance;
246-
} catch (Exception e) {
247-
throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". "
248-
+ "Registering an InstanceCreator with Gson for this type may fix this problem."), e);
241+
if (useJdkUnsafe) {
242+
return new ObjectConstructor<T>() {
243+
private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
244+
@SuppressWarnings("unchecked")
245+
@Override public T construct() {
246+
try {
247+
Object newInstance = unsafeAllocator.newInstance(rawType);
248+
return (T) newInstance;
249+
} catch (Exception e) {
250+
throw new RuntimeException(("Unable to invoke no-args constructor for " + rawType + ". "
251+
+ "Registering an InstanceCreator with Gson for this type may fix this problem."), e);
252+
}
249253
}
250-
}
251-
};
254+
};
255+
} else {
256+
return new ObjectConstructor<T>() {
257+
@Override public T construct() {
258+
throw new JsonIOException("Unable to create instance of " + rawType + "; usage of JDK Unsafe "
259+
+ "is disabled. Register an InstanceCreator or a TypeAdapter for this type or enable "
260+
+ "usage of JDK Unsafe.");
261+
}
262+
};
263+
}
252264
}
253265

254266
@Override public String toString() {

gson/src/main/java/module-info.java

+3
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@
99
exports com.google.gson.stream;
1010

1111
requires transitive java.sql;
12+
13+
// Optional dependency on `jdk.unsupported` for JDK sun.misc.Unsafe
14+
requires static jdk.unsupported;
1215
}

gson/src/test/java/com/google/gson/GsonBuilderTest.java

+23
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,27 @@ public void testTransientFieldExclusion() {
8484
static class HasTransients {
8585
transient String a = "a";
8686
}
87+
88+
public void testDisableJdkUnsafe() {
89+
Gson gson = new GsonBuilder()
90+
.disableJdkUnsafe()
91+
.create();
92+
try {
93+
gson.fromJson("{}", ClassWithoutNoArgsConstructor.class);
94+
fail("Expected exception");
95+
} catch (JsonIOException expected) {
96+
assertEquals(
97+
"Unable to create instance of class com.google.gson.GsonBuilderTest$ClassWithoutNoArgsConstructor; "
98+
+ "usage of JDK Unsafe is disabled. Register an InstanceCreator or a TypeAdapter for this type or "
99+
+ "enable usage of JDK Unsafe.",
100+
expected.getMessage()
101+
);
102+
}
103+
}
104+
105+
private static class ClassWithoutNoArgsConstructor {
106+
@SuppressWarnings("unused")
107+
public ClassWithoutNoArgsConstructor(String s) {
108+
}
109+
}
87110
}

gson/src/test/java/com/google/gson/GsonTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public final class GsonTest extends TestCase {
4747
public void testOverridesDefaultExcluder() {
4848
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
4949
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
50-
true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
50+
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
5151
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
5252
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>());
5353

@@ -60,7 +60,7 @@ public void testOverridesDefaultExcluder() {
6060
public void testClonedTypeAdapterFactoryListsAreIndependent() {
6161
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
6262
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
63-
true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
63+
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
6464
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
6565
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>());
6666

0 commit comments

Comments
 (0)