From 8445689e4d1159298179580b4e260ed23bb2b9bc Mon Sep 17 00:00:00 2001 From: Andrey Mogilev Date: Thu, 4 Jan 2018 02:08:50 +0700 Subject: [PATCH] Java 9 support: use Unsafe-based reflection in Java 9+ (#1218) * Java 9 support: use Unsafe-based reflection in Java 9+ fixes "illegal reflective access" warnings and exceptions * fix Codacy warnings * improve code quality based on PR review * improve code quality based on PR review * fix Codacy warning * improve code quality based on PR review * inlined createReflectionAccessor method --- .../gson/internal/ConstructorConstructor.java | 4 +- .../bind/ReflectiveTypeAdapterFactory.java | 4 +- .../reflect/PreJava9ReflectionAccessor.java | 36 +++++++++++ .../internal/reflect/ReflectionAccessor.java | 54 ++++++++++++++++ .../reflect/UnsafeReflectionAccessor.java | 64 +++++++++++++++++++ .../com/google/gson/reflect/package-info.java | 2 +- 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java create mode 100644 gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java create mode 100644 gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java 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 6d1e7c96..5fab4601 100644 --- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java +++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java @@ -40,6 +40,7 @@ import java.util.concurrent.ConcurrentSkipListMap; import com.google.gson.InstanceCreator; import com.google.gson.JsonIOException; +import com.google.gson.internal.reflect.ReflectionAccessor; import com.google.gson.reflect.TypeToken; /** @@ -47,6 +48,7 @@ import com.google.gson.reflect.TypeToken; */ public final class ConstructorConstructor { private final Map> instanceCreators; + private final ReflectionAccessor accessor = ReflectionAccessor.getInstance(); public ConstructorConstructor(Map> instanceCreators) { this.instanceCreators = instanceCreators; @@ -98,7 +100,7 @@ public final class ConstructorConstructor { try { final Constructor constructor = rawType.getDeclaredConstructor(); if (!constructor.isAccessible()) { - constructor.setAccessible(true); + accessor.makeAccessible(constructor); } return new ObjectConstructor() { @SuppressWarnings("unchecked") // T is the same raw type as is requested diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java index 42798d05..777e7dee 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -28,6 +28,7 @@ import com.google.gson.internal.ConstructorConstructor; import com.google.gson.internal.Excluder; import com.google.gson.internal.ObjectConstructor; import com.google.gson.internal.Primitives; +import com.google.gson.internal.reflect.ReflectionAccessor; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; @@ -49,6 +50,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { private final FieldNamingStrategy fieldNamingPolicy; private final Excluder excluder; private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; + private final ReflectionAccessor accessor = ReflectionAccessor.getInstance(); public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder, @@ -154,7 +156,7 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { if (!serialize && !deserialize) { continue; } - field.setAccessible(true); + accessor.makeAccessible(field); Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); List fieldNames = getFieldNames(field); BoundField previous = null; diff --git a/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java new file mode 100644 index 00000000..2f006517 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 The Gson authors + * + * 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. + */ +package com.google.gson.internal.reflect; + +import java.lang.reflect.AccessibleObject; + +/** + * A basic implementation of {@link ReflectionAccessor} which is suitable for Java 8 and below. + *

+ * This implementation just calls {@link AccessibleObject#setAccessible(boolean) setAccessible(true)}, which worked + * fine before Java 9. + */ +final class PreJava9ReflectionAccessor extends ReflectionAccessor { + + /** + * {@inheritDoc} + */ + @Override + public void makeAccessible(AccessibleObject ao) { + ao.setAccessible(true); + } + +} diff --git a/gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java new file mode 100644 index 00000000..42230d25 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/reflect/ReflectionAccessor.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 The Gson authors + * + * 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. + */ +package com.google.gson.internal.reflect; + +import com.google.gson.util.VersionUtils; + +import java.lang.reflect.AccessibleObject; + +/** + * Provides a replacement for {@link AccessibleObject#setAccessible(boolean)}, which may be used to + * avoid reflective access issues appeared in Java 9, like {@link java.lang.reflect.InaccessibleObjectException} + * thrown or warnings like + *

+ *   WARNING: An illegal reflective access operation has occurred
+ *   WARNING: Illegal reflective access by ...
+ * 
+ *

+ * Works both for Java 9 and earlier Java versions. + */ +public abstract class ReflectionAccessor { + + // the singleton instance, use getInstance() to obtain + private static final ReflectionAccessor instance = VersionUtils.getMajorJavaVersion() < 9 ? new PreJava9ReflectionAccessor() : new UnsafeReflectionAccessor(); + + /** + * Does the same as {@code ao.setAccessible(true)}, but never throws + * {@link java.lang.reflect.InaccessibleObjectException} + */ + public abstract void makeAccessible(AccessibleObject ao); + + /** + * Obtains a {@link ReflectionAccessor} instance suitable for the current Java version. + *

+ * You may need one a reflective operation in your code throws {@link java.lang.reflect.InaccessibleObjectException}. + * In such a case, use {@link ReflectionAccessor#makeAccessible(AccessibleObject)} on a field, method or constructor + * (instead of basic {@link AccessibleObject#setAccessible(boolean)}). + */ + public static ReflectionAccessor getInstance() { + return instance; + } +} diff --git a/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java b/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java new file mode 100644 index 00000000..5bc59bd8 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2017 The Gson authors + * + * 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. + */ +package com.google.gson.internal.reflect; + +import sun.misc.Unsafe; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; + +/** + * An implementation of {@link ReflectionAccessor} based on {@link Unsafe}. + *

+ * NOTE: This implementation is designed for Java 9. Although it should work with earlier Java releases, it is better to + * use {@link PreJava9ReflectionAccessor} for them. + */ +final class UnsafeReflectionAccessor extends ReflectionAccessor { + + private final Unsafe theUnsafe = getUnsafeInstance(); + private final Field overrideField = getOverrideField(); + + /** + * {@inheritDoc} + */ + @Override + public void makeAccessible(AccessibleObject ao) { + if (theUnsafe != null && overrideField != null) { + long overrideOffset = theUnsafe.objectFieldOffset(overrideField); + theUnsafe.putBoolean(ao, overrideOffset, true); + } + } + + private static Unsafe getUnsafeInstance() { + try { + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + return (Unsafe) unsafeField.get(null); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + private static Field getOverrideField() { + try { + return AccessibleObject.class.getDeclaredField("override"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/gson/src/main/java/com/google/gson/reflect/package-info.java b/gson/src/main/java/com/google/gson/reflect/package-info.java index e666c431..5e43ee9f 100644 --- a/gson/src/main/java/com/google/gson/reflect/package-info.java +++ b/gson/src/main/java/com/google/gson/reflect/package-info.java @@ -1,6 +1,6 @@ /** * This package provides utility classes for finding type information for generic types. - * + * * @author Inderjeet Singh, Joel Leitch */ package com.google.gson.reflect; \ No newline at end of file