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 index 2f006517..325274e2 100644 --- a/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java +++ b/gson/src/main/java/com/google/gson/internal/reflect/PreJava9ReflectionAccessor.java @@ -25,12 +25,9 @@ import java.lang.reflect.AccessibleObject; */ final class PreJava9ReflectionAccessor extends ReflectionAccessor { - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void makeAccessible(AccessibleObject ao) { ao.setAccessible(true); } - } 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 index 5bc59bd8..749335b7 100644 --- a/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java +++ b/gson/src/main/java/com/google/gson/internal/reflect/UnsafeReflectionAccessor.java @@ -15,10 +15,11 @@ */ package com.google.gson.internal.reflect; -import sun.misc.Unsafe; - import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import com.google.gson.JsonIOException; /** * An implementation of {@link ReflectionAccessor} based on {@link Unsafe}. @@ -26,29 +27,51 @@ import java.lang.reflect.Field; * 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. */ +@SuppressWarnings({"unchecked", "rawtypes"}) final class UnsafeReflectionAccessor extends ReflectionAccessor { - private final Unsafe theUnsafe = getUnsafeInstance(); + private static Class unsafeClass; + private final Object theUnsafe = getUnsafeInstance(); private final Field overrideField = getOverrideField(); - /** - * {@inheritDoc} - */ + /** {@inheritDoc} */ @Override public void makeAccessible(AccessibleObject ao) { - if (theUnsafe != null && overrideField != null) { - long overrideOffset = theUnsafe.objectFieldOffset(overrideField); - theUnsafe.putBoolean(ao, overrideOffset, true); + boolean success = makeAccessibleWithUnsafe(ao); + if (!success) { + try { + // unsafe couldn't be found, so try using accessible anyway + ao.setAccessible(true); + } catch (SecurityException e) { + throw new JsonIOException("Gson couldn't modify fields for " + ao + + "\nand sun.misc.Unsafe not found.\nEither write a custom type adapter," + + " or make fields accessible, or include sun.misc.Unsafe.", e); + } } } - private static Unsafe getUnsafeInstance() { + // Visible for testing only + boolean makeAccessibleWithUnsafe(AccessibleObject ao) { + if (theUnsafe != null && overrideField != null) { + try { + Method method = unsafeClass.getMethod("objectFieldOffset", Field.class); + long overrideOffset = (Long) method.invoke(theUnsafe, overrideField); // long overrideOffset = theUnsafe.objectFieldOffset(overrideField); + Method putBooleanMethod = unsafeClass.getMethod("putBoolean", Object.class, long.class, boolean.class); + putBooleanMethod.invoke(theUnsafe, ao, overrideOffset, true); // theUnsafe.putBoolean(ao, overrideOffset, true); + return true; + } catch (Exception ignored) { // do nothing + } + } + return false; + } + + private static Object getUnsafeInstance() { try { - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeClass = Class.forName("sun.misc.Unsafe"); + Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); - return (Unsafe) unsafeField.get(null); + return unsafeField.get(null); } catch (Exception e) { - e.printStackTrace(); return null; } } @@ -57,7 +80,6 @@ final class UnsafeReflectionAccessor extends ReflectionAccessor { try { return AccessibleObject.class.getDeclaredField("override"); } catch (NoSuchFieldException e) { - e.printStackTrace(); return null; } } diff --git a/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java b/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java new file mode 100644 index 00000000..d5caaf53 --- /dev/null +++ b/gson/src/test/java/com/google/gson/internal/reflect/UnsafeReflectionAccessorTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 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 static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.Field; + +import org.junit.Test; + +/** + * Unit tests for {@link UnsafeReflectionAccessor} + * + * @author Inderjeet Singh + */ +public class UnsafeReflectionAccessorTest { + + @Test + public void testMakeAccessibleWithUnsafe() throws Exception { + UnsafeReflectionAccessor accessor = new UnsafeReflectionAccessor(); + Field field = ClassWithPrivateFinalFields.class.getDeclaredField("a"); + try { + boolean success = accessor.makeAccessibleWithUnsafe(field); + assertTrue(success); + } catch (Exception e) { + fail("Unsafe didn't work on the JDK"); + } + } + + @SuppressWarnings("unused") + private static final class ClassWithPrivateFinalFields { + private final String a; + public ClassWithPrivateFinalFields(String a) { + this.a = a; + } + } +}