Skip to content

Commit 615c883

Browse files
authored
Add GsonBuilder.disableJdkUnsafe() (#1904)
* Add GsonBuilder.disableJdkUnsafe() * Address review feedback
1 parent 9793828 commit 615c883

File tree

7 files changed

+94
-25
lines changed

7 files changed

+94
-25
lines changed

extras/src/main/java/com/google/gson/graph/GraphAdapterBuilder.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,12 @@ public final class GraphAdapterBuilder {
4747

4848
public GraphAdapterBuilder() {
4949
this.instanceCreators = new HashMap<Type, InstanceCreator<?>>();
50-
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
50+
this.constructorConstructor = new ConstructorConstructor(instanceCreators, true);
5151
}
5252
public GraphAdapterBuilder addType(Type type) {
5353
final ObjectConstructor<?> objectConstructor = constructorConstructor.get(TypeToken.get(type));
5454
InstanceCreator<Object> instanceCreator = new InstanceCreator<Object>() {
55+
@Override
5556
public Object createInstance(Type type) {
5657
return objectConstructor.construct();
5758
}
@@ -83,6 +84,7 @@ static class Factory implements TypeAdapterFactory, InstanceCreator {
8384
this.instanceCreators = instanceCreators;
8485
}
8586

87+
@Override
8688
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
8789
if (!instanceCreators.containsKey(type.getType())) {
8890
return null;
@@ -212,6 +214,7 @@ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
212214
* that is only when we've called back into Gson to deserialize a tree.
213215
*/
214216
@SuppressWarnings("unchecked")
217+
@Override
215218
public Object createInstance(Type type) {
216219
Graph graph = graphThreadLocal.get();
217220
if (graph == null || graph.nextCreate == null) {

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

+7-2
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";
@@ -141,6 +142,7 @@ public final class Gson {
141142
final boolean prettyPrinting;
142143
final boolean lenient;
143144
final boolean serializeSpecialFloatingPointValues;
145+
final boolean useJdkUnsafe;
144146
final String datePattern;
145147
final int dateStyle;
146148
final int timeStyle;
@@ -189,6 +191,7 @@ public Gson() {
189191
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
190192
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
191193
DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
194+
DEFAULT_USE_JDK_UNSAFE,
192195
LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT,
193196
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
194197
Collections.<TypeAdapterFactory>emptyList(), ToNumberPolicy.DOUBLE, ToNumberPolicy.LAZILY_PARSED_NUMBER);
@@ -198,22 +201,24 @@ public Gson() {
198201
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
199202
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
200203
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
204+
boolean useJdkUnsafe,
201205
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
202206
int timeStyle, List<TypeAdapterFactory> builderFactories,
203207
List<TypeAdapterFactory> builderHierarchyFactories,
204208
List<TypeAdapterFactory> factoriesToBeAdded,
205-
ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) {
209+
ToNumberStrategy objectToNumberStrategy, ToNumberStrategy numberToNumberStrategy) {
206210
this.excluder = excluder;
207211
this.fieldNamingStrategy = fieldNamingStrategy;
208212
this.instanceCreators = instanceCreators;
209-
this.constructorConstructor = new ConstructorConstructor(instanceCreators);
213+
this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe);
210214
this.serializeNulls = serializeNulls;
211215
this.complexMapKeySerialization = complexMapKeySerialization;
212216
this.generateNonExecutableJson = generateNonExecutableGson;
213217
this.htmlSafe = htmlSafe;
214218
this.prettyPrinting = prettyPrinting;
215219
this.lenient = lenient;
216220
this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
221+
this.useJdkUnsafe = useJdkUnsafe;
217222
this.longSerializationPolicy = longSerializationPolicy;
218223
this.datePattern = datePattern;
219224
this.dateStyle = dateStyle;

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
4242
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
4343
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
44+
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
4445

4546
/**
4647
* <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
private ToNumberStrategy objectToNumberStrategy = ToNumberPolicy.DOUBLE;
98100
private ToNumberStrategy numberToNumberStrategy = ToNumberPolicy.LAZILY_PARSED_NUMBER;
99101

@@ -129,6 +131,7 @@ public GsonBuilder() {
129131
this.timeStyle = gson.timeStyle;
130132
this.factories.addAll(gson.builderFactories);
131133
this.hierarchyFactories.addAll(gson.builderHierarchyFactories);
134+
this.useJdkUnsafe = gson.useJdkUnsafe;
132135
this.objectToNumberStrategy = gson.objectToNumberStrategy;
133136
this.numberToNumberStrategy = gson.numberToNumberStrategy;
134137
}
@@ -606,6 +609,26 @@ public GsonBuilder serializeSpecialFloatingPointValues() {
606609
return this;
607610
}
608611

612+
/**
613+
* Disables usage of JDK's {@code sun.misc.Unsafe}.
614+
*
615+
* <p>By default Gson uses {@code Unsafe} to create instances of classes which don't have
616+
* a no-args constructor. However, {@code Unsafe} might not be available for all Java
617+
* runtimes. For example Android does not provide {@code Unsafe}, or only with limited
618+
* functionality. Additionally {@code Unsafe} creates instances without executing any
619+
* constructor or initializer block, or performing initialization of field values. This can
620+
* lead to surprising and difficult to debug errors.
621+
* Therefore, to get reliable behavior regardless of which runtime is used, and to detect
622+
* classes which cannot be deserialized in an early stage of development, this method allows
623+
* disabling usage of {@code Unsafe}.
624+
*
625+
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
626+
*/
627+
public GsonBuilder disableJdkUnsafe() {
628+
this.useJdkUnsafe = false;
629+
return this;
630+
}
631+
609632
/**
610633
* Creates a {@link Gson} instance based on the current configuration. This method is free of
611634
* side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
@@ -626,7 +649,7 @@ public Gson create() {
626649
return new Gson(excluder, fieldNamingPolicy, instanceCreators,
627650
serializeNulls, complexMapKeySerialization,
628651
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
629-
serializeSpecialFloatingPointValues, longSerializationPolicy,
652+
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
630653
datePattern, dateStyle, timeStyle,
631654
this.factories, this.hierarchyFactories, factories, objectToNumberStrategy, numberToNumberStrategy);
632655
}

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

+32-18
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) {
@@ -92,7 +94,7 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
9294
}
9395

9496
// finally try unsafe
95-
return newUnsafeAllocator(type, rawType);
97+
return newUnsafeAllocator(rawType);
9698
}
9799

98100
private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
@@ -125,10 +127,11 @@ public T construct() {
125127
}
126128

127129
return new ObjectConstructor<T>() {
128-
@SuppressWarnings("unchecked") // T is the same raw type as is requested
129130
@Override public T construct() {
130131
try {
131-
return (T) constructor.newInstance();
132+
@SuppressWarnings("unchecked") // T is the same raw type as is requested
133+
T newInstance = (T) constructor.newInstance();
134+
return newInstance;
132135
} catch (InstantiationException e) {
133136
// TODO: JsonParseException ?
134137
throw new RuntimeException("Failed to invoke " + constructor + " with no args", e);
@@ -233,21 +236,32 @@ private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
233236
return null;
234237
}
235238

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

253267
@Override public String toString() {

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ public <T> T newInstance(Class<T> c) throws Exception {
101101
return new UnsafeAllocator() {
102102
@Override
103103
public <T> T newInstance(Class<T> c) {
104-
throw new UnsupportedOperationException("Cannot allocate " + c);
104+
throw new UnsupportedOperationException("Cannot allocate " + c + ". Usage of JDK sun.misc.Unsafe is enabled, "
105+
+ "but it could not be used. Make sure your runtime is configured correctly.");
105106
}
106107
};
107108
}

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. Registering an InstanceCreator or a TypeAdapter for this type, "
99+
+ "adding a no-args constructor, or enabling usage of JDK Unsafe may fix this problem.",
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
@@ -53,7 +53,7 @@ public final class GsonTest extends TestCase {
5353
public void testOverridesDefaultExcluder() {
5454
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
5555
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
56-
true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
56+
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
5757
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
5858
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
5959
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY);
@@ -67,7 +67,7 @@ public void testOverridesDefaultExcluder() {
6767
public void testClonedTypeAdapterFactoryListsAreIndependent() {
6868
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
6969
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
70-
true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
70+
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
7171
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
7272
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
7373
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY);

0 commit comments

Comments
 (0)