From 8ca4b77560553e0e71d9076038e7216445b448cd Mon Sep 17 00:00:00 2001 From: GianMaria Romanato Date: Tue, 15 Jan 2019 17:01:17 +0100 Subject: [PATCH] Fix #47 Originally the static initialization was assuming that the ConstantPool would be accessible via Class.getConstantPool(). However such method only exists in the Oracle JDK and is not provided by the IBM OpenJ9 JDK. But, both JDKs define an interface JavaLangAccess which offers method getConstantPool( Class c ) that returns the constant pool for the given class and the JavaLangAccess can be obtained via factory method SharedSecrets.getJavaLangAccess(). Both SharedSecrets and JavaLangAccess have been relocated from sun.* packages to jdk.internal.* packages in Java 9 or later. Now the code obtains the pool using the right classes from the right packages and the behavior is therefore portable across all JDKs available at the time of writing --- .travis.yml | 17 +++++-- .../net/jodah/typetools/TypeResolver.java | 44 ++++++++++++++----- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58d2c3d..7f3106f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,21 @@ sudo: false language: java -install: mvn install -DskipTests=true -Dgpg.skip=true -jdk: - - oraclejdk8 +jobs: + include: + - stage: adoptopenjdk.net - Eclipse OpenJ9 + env: + - MAVEN_OPTS="--add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-exports java.base/jdk.internal.reflect=ALL-UNNAMED" + script: + - unset -v _JAVA_OPTIONS + - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh + - source install-jdk.sh --url $(curl --silent https://api.adoptopenjdk.net/v2/binary/nightly/openjdk11\?openjdk_impl\=openj9\&os\=linux\&arch\=x64\&release\=latest\&type\=jdk | grep 'binary_link' | grep -Eo '(http|https)://[^"]+' | head -1) + - mvn install -DskipTests=true -Dgpg.skip=true + - stage: jdk.java.net - OpenJDK - GPL + jdk: oraclejdk8 + script: + - mvn install -DskipTests=true -Dgpg.skip=true notifications: email: false diff --git a/src/main/java/net/jodah/typetools/TypeResolver.java b/src/main/java/net/jodah/typetools/TypeResolver.java index 0b9f17d..24458b7 100644 --- a/src/main/java/net/jodah/typetools/TypeResolver.java +++ b/src/main/java/net/jodah/typetools/TypeResolver.java @@ -17,7 +17,6 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; -import java.lang.reflect.AccessibleObject; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -54,6 +53,7 @@ public final class TypeResolver { private static Method GET_CONSTANT_POOL; private static Method GET_CONSTANT_POOL_SIZE; private static Method GET_CONSTANT_POOL_METHOD_AT; + private static Object JAVA_LANG_ACCESS; private static final Map OBJECT_METHODS = new HashMap(); private static final Map, Class> PRIMITIVE_WRAPPERS; private static final Double JAVA_VERSION; @@ -72,29 +72,49 @@ public Unsafe run() throws Exception { } }); - GET_CONSTANT_POOL = Class.class.getDeclaredMethod("getConstantPool"); - String constantPoolName = JAVA_VERSION < 9 ? "sun.reflect.ConstantPool" : "jdk.internal.reflect.ConstantPool"; + String constantPoolName = null; + String sharedSecretName = null; + if (JAVA_VERSION < 9) { + constantPoolName = "sun.reflect.ConstantPool"; + sharedSecretName = "sun.misc.SharedSecrets"; + } else { + constantPoolName = "jdk.internal.reflect.ConstantPool"; + sharedSecretName = "jdk.internal.misc.SharedSecrets"; + } + + Class sharedSecretsClass = Class.forName(sharedSecretName); + Method getJavaLangAccessMethod = sharedSecretsClass.getDeclaredMethod("getJavaLangAccess"); + JAVA_LANG_ACCESS = getJavaLangAccessMethod.invoke(null); + + GET_CONSTANT_POOL = JAVA_LANG_ACCESS.getClass().getDeclaredMethod("getConstantPool", Class.class);; + Class constantPoolClass = Class.forName(constantPoolName); GET_CONSTANT_POOL_SIZE = constantPoolClass.getDeclaredMethod("getSize"); GET_CONSTANT_POOL_METHOD_AT = constantPoolClass.getDeclaredMethod("getMethodAt", int.class); - // setting the methods as accessible - Field overrideField = AccessibleObject.class.getDeclaredField("override"); - long overrideFieldOffset = unsafe.objectFieldOffset(overrideField); - unsafe.putBoolean(GET_CONSTANT_POOL, overrideFieldOffset, true); - unsafe.putBoolean(GET_CONSTANT_POOL_SIZE, overrideFieldOffset, true); - unsafe.putBoolean(GET_CONSTANT_POOL_METHOD_AT, overrideFieldOffset, true); + GET_CONSTANT_POOL.setAccessible(true); + GET_CONSTANT_POOL_SIZE.setAccessible(true); + GET_CONSTANT_POOL_METHOD_AT.setAccessible(true); // additional checks - make sure we get a result when invoking the Class::getConstantPool and // ConstantPool::getSize on a class - Object constantPool = GET_CONSTANT_POOL.invoke(Object.class); + Object constantPool = GET_CONSTANT_POOL.invoke(JAVA_LANG_ACCESS, Object.class); GET_CONSTANT_POOL_SIZE.invoke(constantPool); for (Method method : Object.class.getDeclaredMethods()) OBJECT_METHODS.put(method.getName(), method); RESOLVES_LAMBDAS = true; - } catch (Exception ignore) { + } catch (Exception exception) { + if (JAVA_VERSION == 8) { + throw new IllegalStateException("Java 8 detected but lambda support initialization failed - Unsupported JDK?", exception); + } else if (JAVA_VERSION > 8) { + // TypeResolver can only work if JVM is started with parameters + // --add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-exports java.base/jdk.internal.reflect=ALL-UNNAMED + // which allow unnamed Java modules to access the jdk.internal.misc and jdk.internal.reflect packages, even if they + // are not exported by module java.base + throw new IllegalStateException("Java 9 or higher detected but internal access failed, did you remember to --add-exports?", exception); + } } Map, Class> types = new HashMap, Class>(); @@ -738,7 +758,7 @@ private static boolean isDefaultMethod(Method m) { private static Member getMemberRef(Class type) { Object constantPool; try { - constantPool = GET_CONSTANT_POOL.invoke(type); + constantPool = GET_CONSTANT_POOL.invoke(JAVA_LANG_ACCESS, type); } catch (Exception ignore) { return null; }