Skip to content
Permalink
Browse files

Java 9 support: use Unsafe-based reflection in Java 9+

fixes "illegal reflective access" warnings and exceptions
  • Loading branch information...
amogilev committed Nov 21, 2017
1 parent 5676078 commit 471352264e04e6fb97a4c2b2fca80e3915138ba0
@@ -17,6 +17,7 @@

import com.gilecode.yagson.ReadContext;
import com.gilecode.yagson.WriteContext;
import com.gilecode.yagson.reflection.ReflectionAccessUtils;
import com.gilecode.yagson.refs.PathElementProducer;
import com.gilecode.yagson.types.*;
import com.google.gson.Gson;
@@ -35,7 +36,6 @@
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.*;
import java.util.regex.Pattern;

import static com.gilecode.yagson.refs.References.REF_FIELD_PREFIX;

@@ -108,7 +108,7 @@
// skip duplicate names, get/set only 'latest' versions (closest to the actual class)
continue;
}
f.setAccessible(true);
ReflectionAccessUtils.getReflectionAccessor().makeAccessible(f);

Object defaultValue;
try {
@@ -17,6 +17,8 @@

import com.gilecode.yagson.ReadContext;
import com.gilecode.yagson.WriteContext;
import com.gilecode.yagson.reflection.ReflectionAccessUtils;
import com.gilecode.yagson.reflection.ReflectionAccessor;
import com.gilecode.yagson.types.NonSerializableLambdaException;
import com.gilecode.yagson.types.TypeUtils;
import com.google.gson.Gson;
@@ -41,6 +43,7 @@
private static final String SERIALIZED_LAMBDA_CLASS_NAME = "java.lang.invoke.SerializedLambda";
private final Class<?> serializedLambdaClass;
private final boolean enabled;
private final ReflectionAccessor accessor = ReflectionAccessUtils.getReflectionAccessor();

private class AdaptersHolder {
final TypeAdapter serializedLambdaAdapter;
@@ -123,7 +126,7 @@ public void write(JsonWriter out, T value, WriteContext ctx) throws IOException
Object serializedLambda;
try {
Method writeReplaceMethod = value.getClass().getDeclaredMethod("writeReplace");
writeReplaceMethod.setAccessible(true);
accessor.makeAccessible(writeReplaceMethod);
serializedLambda = writeReplaceMethod.invoke(value);
} catch (Exception e) {
throw new IllegalStateException("Failed to obtain SerializedLambda using writeReplace()");
@@ -149,7 +152,7 @@ public T read(JsonReader in, ReadContext ctx) throws IOException {
SerializedLambdaAdapter() {
try {
readResolveMethod = serializedLambdaClass.getDeclaredMethod("readResolve");
readResolveMethod.setAccessible(true);
accessor.makeAccessible(readResolveMethod);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Failed to obtain SerializedLambda::readResolve method");
}
@@ -16,6 +16,8 @@
package com.gilecode.yagson.adapters;

import com.gilecode.yagson.WriteContext;
import com.gilecode.yagson.reflection.ReflectionAccessUtils;
import com.gilecode.yagson.reflection.ReflectionAccessor;
import com.gilecode.yagson.refs.PlaceholderUse;
import com.gilecode.yagson.refs.ReferencePlaceholder;
import com.google.gson.*;
@@ -71,17 +73,19 @@ public ThreadTypesAdapterFactory(JsonAdapterAnnotationTypeAdapterFactory jsonAda
this.jsonAdapterFactory = jsonAdapterFactory;
this.constructorConstructor = constructorConstructor;
try {
ReflectionAccessor accessor = ReflectionAccessUtils.getReflectionAccessor();

threadLocalGetMapMethod = ThreadLocal.class.getDeclaredMethod("getMap", Thread.class);
threadLocalGetMapMethod.setAccessible(true);
accessor.makeAccessible(threadLocalGetMapMethod);

Class<?> threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Class<?> threadLocalMapEntryClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap$Entry");

threadLocalMapGetEntryMethod = threadLocalMapClass.getDeclaredMethod("getEntry", ThreadLocal.class);
threadLocalMapGetEntryMethod.setAccessible(true);
accessor.makeAccessible(threadLocalMapGetEntryMethod);

threadLocalEntryValueField = threadLocalMapEntryClass.getDeclaredField("value");
threadLocalEntryValueField.setAccessible(true);
accessor.makeAccessible(threadLocalEntryValueField);

} catch (Exception e) {
throw new IllegalStateException("Failed to initialize ThreadTypesAdapterFactory");
@@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 Andrey Mogilev
*
* 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.gilecode.yagson.reflection;

import com.gilecode.yagson.reflection.impl.PreJava9ReflectionAccessor;
import com.gilecode.yagson.reflection.impl.UnsafeReflectionAccessor;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;

/**
* Provides {@link ReflectionAccessor} instance which may be used to avoid reflective access issues
* appeared in Java 9, like {@link java.lang.reflect.InaccessibleObjectException} thrown or
* warnings like
* <pre>
* WARNING: An illegal reflective access operation has occurred
* WARNING: Illegal reflective access by ...
* </pre>
* <p/>
* Works both for Java 9 and earlier Java versions.
*
* @author Andrey Mogilev
*/
public class ReflectionAccessUtils {

/**
* Obtains a {@link ReflectionAccessor} instance suitable for the current Java version.
* <p>
* 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 getReflectionAccessor() {
return ReflectionAccessorHolder.instance;
}

// singleton holder
private static class ReflectionAccessorHolder {
static final ReflectionAccessor instance = createReflectionAccessor();
}

static int getMajorJavaVersion() {
String[] parts = System.getProperty("java.version").split("[._]");
int firstVer = Integer.parseInt(parts[0]);
if (firstVer == 1 && parts.length > 1) {
return Integer.parseInt(parts[1]);
} else {
return firstVer;
}
}

static ReflectionAccessor createReflectionAccessor() {
if (getMajorJavaVersion() < 9) {
return new PreJava9ReflectionAccessor();
} else {
return new UnsafeReflectionAccessor();
}
}

}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2017 Andrey Mogilev
*
* 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.gilecode.yagson.reflection;

import java.lang.reflect.AccessibleObject;

/**
* Provides a replacement for {@link AccessibleObject#setAccessible(boolean)}, useful when that basic operation is
* prohibited, e.g. throws {@link java.lang.reflect.InaccessibleObjectException} in Java 9.
*
* @author Andrey Mogilev
*/
public interface ReflectionAccessor {

/**
* Does the same as {@code ao.setAccessible(true)}, but never throws
* {@link java.lang.reflect.InaccessibleObjectException}
*/
void makeAccessible(AccessibleObject ao);

}
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2017 Andrey Mogilev
*
* 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.gilecode.yagson.reflection.impl;

import com.gilecode.yagson.reflection.ReflectionAccessor;

import java.lang.reflect.AccessibleObject;

/**
* A basic implementation of {@link ReflectionAccessor} which is suitable for Java 8 and below.
* <p>
* This implementation just calls {@link AccessibleObject#setAccessible(boolean) setAccessible(true)}, which worked
* fine before Java 9.
*
* @author Andrey Mogilev
*/
public class PreJava9ReflectionAccessor implements ReflectionAccessor {

/**
* {@inheritDoc}
*/
public void makeAccessible(AccessibleObject ao) {
ao.setAccessible(true);
}
}
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 Andrey Mogilev
*
* 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.gilecode.yagson.reflection.impl;

import com.gilecode.yagson.reflection.ReflectionAccessor;
import sun.misc.Unsafe;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;

/**
* An implementation of {@link ReflectionAccessor} based on {@link Unsafe}.
* <p>
* 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.
*
* @author Andrey Mogilev
*/
public class UnsafeReflectionAccessor implements ReflectionAccessor {

private static final Unsafe theUnsafe = getUnsafeInstance();
private static final Field overrideField = getOverrideField();

/**
* {@inheritDoc}
*/
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;
}
}
}
@@ -15,6 +15,9 @@
*/
package com.gilecode.yagson.types;

import com.gilecode.yagson.reflection.ReflectionAccessUtils;
import com.gilecode.yagson.reflection.ReflectionAccessor;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;
@@ -30,13 +33,15 @@
*/
public class SetFromMapPostReadProcessor implements PostReadProcessor {

private static final ReflectionAccessor accessor = ReflectionAccessUtils.getReflectionAccessor();

@SuppressWarnings("unchecked")
public void apply(Object instance) {
Class c = instance.getClass();
try {
// sets internal fields as if were deserialized from Java ObjectStream
Method mReadObject = c.getDeclaredMethod("readObject", ObjectInputStream.class);
mReadObject.setAccessible(true);
accessor.makeAccessible(mReadObject);
mReadObject.invoke(instance, new VoidObjectInputStream());

} catch (Exception e) {
@@ -17,6 +17,8 @@

import com.gilecode.yagson.ReadContext;
import com.gilecode.yagson.WriteContext;
import com.gilecode.yagson.reflection.ReflectionAccessUtils;
import com.gilecode.yagson.reflection.ReflectionAccessor;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
@@ -53,6 +55,8 @@
primitiveWrappers.put(double.class.getName(), Double.class);
}

private static final ReflectionAccessor accessor = ReflectionAccessUtils.getReflectionAccessor();

public static boolean typesDiffer(Type type, Class<?> actualClass) {
if (type instanceof GenericArrayType && actualClass.isArray()) {
return typesDiffer(((GenericArrayType)type).getGenericComponentType(), actualClass.getComponentType());
@@ -395,7 +399,7 @@ public static void writeTypeWrapperAndValue(JsonWriter out, Object value, TypeAd
static Field getDeclaredField(Class declaringClass, String fieldName) {
try {
Field f = declaringClass.getDeclaredField(fieldName);
f.setAccessible(true);
accessor.makeAccessible(f);
return f;
} catch (NoSuchFieldException e) {
throw new IllegalStateException("Field '" + fieldName + "' is not found in " + declaringClass, e);
@@ -410,7 +414,7 @@ public static Field findField(Class c, String fieldName) {
for (Field f : c.getDeclaredFields()) {
if (fieldName.equals(f.getName())) {
// found
f.setAccessible(true);
accessor.makeAccessible(f);
return f;
}
}
@@ -505,7 +509,7 @@ static Field findOneFieldByType(Class<?> c, Class<?> fieldClassToFind) {
List<Field> found = findFields(c, true, 1, fieldClassesToFind, null);
if (found.size() > 0) {
Field foundField = found.get(0);
foundField.setAccessible(true);
accessor.makeAccessible(foundField);
return foundField;
}

@@ -768,7 +772,7 @@ private static void copyFields(Object to, Object from, Class<?> declaringClass,
for (String fname : fieldNames) {
try {
Field f = declaringClass.getDeclaredField(fname);
f.setAccessible(true);
accessor.makeAccessible(f);
f.set(to, f.get(from));
} catch (Exception e) {
throw new IllegalStateException("Failed to initialize field " + fname + " of " + declaringClass);

0 comments on commit 4713522

Please sign in to comment.
You can’t perform that action at this time.