From 3f67886482fddb106de0d22c5a8a71f64815b903 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Fri, 4 Jun 2021 16:49:35 +0200 Subject: [PATCH] Add GsonBuilder.disableJdkUnsafe() --- gson/src/main/java/com/google/gson/Gson.java | 5 ++- .../java/com/google/gson/GsonBuilder.java | 22 ++++++++++- .../gson/internal/ConstructorConstructor.java | 38 ++++++++++++------- gson/src/main/java/module-info.java | 3 ++ .../java/com/google/gson/GsonBuilderTest.java | 23 +++++++++++ .../test/java/com/google/gson/GsonTest.java | 4 +- 6 files changed, 78 insertions(+), 17 deletions(-) diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index 27f3ee9246..d59bc25710 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -110,6 +110,7 @@ public final class Gson { static final boolean DEFAULT_SERIALIZE_NULLS = false; static final boolean DEFAULT_COMPLEX_MAP_KEYS = false; static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false; + static final boolean DEFAULT_USE_JDK_UNSAFE = true; private static final TypeToken NULL_KEY_SURROGATE = TypeToken.get(Object.class); private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; @@ -187,6 +188,7 @@ public Gson() { Collections.>emptyMap(), DEFAULT_SERIALIZE_NULLS, DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML, DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, + DEFAULT_USE_JDK_UNSAFE, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); @@ -196,6 +198,7 @@ public Gson() { Map> instanceCreators, boolean serializeNulls, boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues, + boolean useJdkUnsafe, LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle, int timeStyle, List builderFactories, List builderHierarchyFactories, @@ -203,7 +206,7 @@ public Gson() { this.excluder = excluder; this.fieldNamingStrategy = fieldNamingStrategy; this.instanceCreators = instanceCreators; - this.constructorConstructor = new ConstructorConstructor(instanceCreators); + this.constructorConstructor = new ConstructorConstructor(instanceCreators, useJdkUnsafe); this.serializeNulls = serializeNulls; this.complexMapKeySerialization = complexMapKeySerialization; this.generateNonExecutableJson = generateNonExecutableGson; diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index b97be452be..3ffe52de16 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -40,6 +40,7 @@ import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT; import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS; import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES; +import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE; /** *

Use this builder to construct a {@link Gson} instance when you need to set configuration @@ -94,6 +95,7 @@ public final class GsonBuilder { private boolean prettyPrinting = DEFAULT_PRETTY_PRINT; private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; private boolean lenient = DEFAULT_LENIENT; + private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE; /** * Creates a GsonBuilder instance that can be used to build Gson with various configuration @@ -577,6 +579,24 @@ public GsonBuilder serializeSpecialFloatingPointValues() { return this; } + /** + * Disables usage of JDK's {@code sun.misc.Unsafe}. + * + *

By default Gson uses {@code Unsafe} to create instances of classes which don't have + * a no-args constructor. However, {@code Unsafe} might not be available for all Java + * runtimes. For example Android does not provide {@code Unsafe}, or only with limited + * functionality.
+ * Therefore, to get reliable behavior regardless of which runtime is used, and to detect + * classes which cannot be deserialized in an early stage of development, this method allows + * disabling usage of {@code Unsafe}. + * + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + public GsonBuilder disableJdkUnsafe() { + this.useJdkUnsafe = false; + 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. @@ -597,7 +617,7 @@ public Gson create() { return new Gson(excluder, fieldNamingPolicy, instanceCreators, serializeNulls, complexMapKeySerialization, generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient, - serializeSpecialFloatingPointValues, longSerializationPolicy, + serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy, datePattern, dateStyle, timeStyle, this.factories, this.hierarchyFactories, factories); } diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java index d5590c6e46..4a134cb75c 100644 --- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java +++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java @@ -48,9 +48,11 @@ */ public final class ConstructorConstructor { private final Map> instanceCreators; + private final boolean useJdkUnsafe; - public ConstructorConstructor(Map> instanceCreators) { + public ConstructorConstructor(Map> instanceCreators, boolean useJdkUnsafe) { this.instanceCreators = instanceCreators; + this.useJdkUnsafe = useJdkUnsafe; } public ObjectConstructor get(TypeToken typeToken) { @@ -236,19 +238,29 @@ private ObjectConstructor newDefaultImplementationConstructor( private ObjectConstructor newUnsafeAllocator( final Type type, final Class rawType) { - return new ObjectConstructor() { - private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); - @SuppressWarnings("unchecked") - @Override public T construct() { - try { - Object newInstance = unsafeAllocator.newInstance(rawType); - return (T) newInstance; - } catch (Exception e) { - throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". " - + "Registering an InstanceCreator with Gson for this type may fix this problem."), e); + if (useJdkUnsafe) { + return new ObjectConstructor() { + private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); + @SuppressWarnings("unchecked") + @Override public T construct() { + try { + Object newInstance = unsafeAllocator.newInstance(rawType); + return (T) newInstance; + } catch (Exception e) { + throw new RuntimeException(("Unable to invoke no-args constructor for " + rawType + ". " + + "Registering an InstanceCreator with Gson for this type may fix this problem."), e); + } } - } - }; + }; + } else { + return new ObjectConstructor() { + @Override public T construct() { + throw new JsonIOException("Unable to create instance of " + rawType + "; usage of JDK Unsafe " + + "is disabled. Register an InstanceCreator or a TypeAdapter for this type or enable " + + "usage of JDK Unsafe."); + } + }; + } } @Override public String toString() { diff --git a/gson/src/main/java/module-info.java b/gson/src/main/java/module-info.java index 161fbdba7f..01af7b7f52 100644 --- a/gson/src/main/java/module-info.java +++ b/gson/src/main/java/module-info.java @@ -9,4 +9,7 @@ exports com.google.gson.stream; requires transitive java.sql; + + // Optional dependency on `jdk.unsupported` for JDK sun.misc.Unsafe + requires static jdk.unsupported; } diff --git a/gson/src/test/java/com/google/gson/GsonBuilderTest.java b/gson/src/test/java/com/google/gson/GsonBuilderTest.java index 73601c0e3c..9cd17d8b0f 100644 --- a/gson/src/test/java/com/google/gson/GsonBuilderTest.java +++ b/gson/src/test/java/com/google/gson/GsonBuilderTest.java @@ -84,4 +84,27 @@ public void testTransientFieldExclusion() { static class HasTransients { transient String a = "a"; } + + public void testDisableJdkUnsafe() { + Gson gson = new GsonBuilder() + .disableJdkUnsafe() + .create(); + try { + gson.fromJson("{}", ClassWithoutNoArgsConstructor.class); + fail("Expected exception"); + } catch (JsonIOException expected) { + assertEquals( + "Unable to create instance of class com.google.gson.GsonBuilderTest$ClassWithoutNoArgsConstructor; " + + "usage of JDK Unsafe is disabled. Register an InstanceCreator or a TypeAdapter for this type or " + + "enable usage of JDK Unsafe.", + expected.getMessage() + ); + } + } + + private static class ClassWithoutNoArgsConstructor { + @SuppressWarnings("unused") + public ClassWithoutNoArgsConstructor(String s) { + } + } } diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index eec2ec91ca..f435dcf533 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -47,7 +47,7 @@ public final class GsonTest extends TestCase { public void testOverridesDefaultExcluder() { Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, - true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, + true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), new ArrayList(), new ArrayList()); @@ -60,7 +60,7 @@ public void testOverridesDefaultExcluder() { public void testClonedTypeAdapterFactoryListsAreIndependent() { Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY, new HashMap>(), true, false, true, false, - true, true, false, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, + true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, new ArrayList(), new ArrayList(), new ArrayList());