diff options
author | Sven Gothel <[email protected]> | 2019-09-04 04:08:06 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2019-09-04 04:08:06 +0200 |
commit | 07c1885e9a3d1f3a3853414648c06fb3864bc69f (patch) | |
tree | 05d3dc6707694aef3056db666aec7488835a6ae5 | |
parent | 252ab21e6b6958cb54992d68535cdb7563b70f40 (diff) |
UnsafeUtil centralizes the workarounds (hack) of certain Java>=9
modularization encapsulation pitfalls,
where no exports have been defined. The last resort.
1) Buffers utilizes UnsafeUtil for Java>=9 invokeCleaner.
2) To gain access for certain methods + fields w/o 'illegal access warnings',
we have to temporarily disable the IllegalAccessLogger.
Hence we provide a method 'T doWithoutIllegalAccessLogger(..<T> action)'
for our essential module access under Java >= 9.
-rw-r--r-- | src/java/com/jogamp/common/nio/Buffers.java | 40 | ||||
-rw-r--r-- | src/java/com/jogamp/common/util/UnsafeUtil.java | 222 |
2 files changed, 234 insertions, 28 deletions
diff --git a/src/java/com/jogamp/common/nio/Buffers.java b/src/java/com/jogamp/common/nio/Buffers.java index 5125ee8..875163e 100644 --- a/src/java/com/jogamp/common/nio/Buffers.java +++ b/src/java/com/jogamp/common/nio/Buffers.java @@ -53,7 +53,9 @@ import java.nio.ShortBuffer; import java.security.AccessController; import java.security.PrivilegedAction; +import com.jogamp.common.ExceptionUtils; import com.jogamp.common.util.ReflectionUtil; +import com.jogamp.common.util.UnsafeUtil; import com.jogamp.common.util.ValueConv; import jogamp.common.Debug; @@ -1167,31 +1169,20 @@ public class Buffers { * Access to NIO {@link sun.misc.Cleaner}, allowing caller to deterministically clean a given {@link sun.nio.ch.DirectBuffer}. */ public static class Cleaner { - private static final Object theUnsafe; private static final Method mbbCleaner; private static final Method cClean; - private static final boolean hasCleaner; /** OK to be lazy on thread synchronization, just for early out **/ private static volatile boolean cleanerError; static { - final Object[] _theUnsafe = { null }; final Method[] _mbbCleaner = { null }; final Method[] _cClean = { null }; + final boolean hasCleaner; if( AccessController.doPrivileged(new PrivilegedAction<Boolean>() { @Override public Boolean run() { try { - if(PlatformPropsImpl.JAVA_9) { - // Using: sun.misc.Unsafe { public void invokeCleaner(java.nio.ByteBuffer directBuffer); } - _mbbCleaner[0] = null; - final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe"); - { - final Field f = unsafeClass.getDeclaredField("theUnsafe"); - f.setAccessible(true); - _theUnsafe[0] = f.get(null); - } - _cClean[0] = unsafeClass.getMethod("invokeCleaner", java.nio.ByteBuffer.class); - _cClean[0].setAccessible(true); + if( PlatformPropsImpl.JAVA_9 ) { + return UnsafeUtil.hasInvokeCleaner(); } else { _mbbCleaner[0] = ReflectionUtil.getMethod("sun.nio.ch.DirectBuffer", "cleaner", null, Buffers.class.getClassLoader()); _mbbCleaner[0].setAccessible(true); @@ -1202,21 +1193,18 @@ public class Buffers { // Java <= 8: sun.misc.Cleaner OK _cClean[0] = cleanerType.getMethod("clean"); _cClean[0].setAccessible(true); + return Boolean.TRUE; } - return Boolean.TRUE; } catch(final Throwable t) { if( DEBUG ) { - System.err.println("Caught "+t.getMessage()); - t.printStackTrace(); + ExceptionUtils.dumpThrowable("Buffers", t); } return Boolean.FALSE; } } } ).booleanValue() ) { - theUnsafe = _theUnsafe[0]; mbbCleaner = _mbbCleaner[0]; cClean = _cClean[0]; - hasCleaner = PlatformPropsImpl.JAVA_9 ? null != theUnsafe && null != cClean : null != mbbCleaner && null != cClean; + hasCleaner = PlatformPropsImpl.JAVA_9 || ( null != mbbCleaner && null != cClean ); } else { - theUnsafe = null; mbbCleaner = null; cClean = null; hasCleaner = false; @@ -1227,24 +1215,21 @@ public class Buffers { if( null != mbbCleaner ) { System.err.print(", using Cleaner class: "+mbbCleaner.getReturnType().getName()); } - if( null != theUnsafe ) { - System.err.print(", using sun.misc.Unsafe::invokeCleaner(..);"); - } System.err.println(); } } /** * If {@code b} is an direct NIO buffer, i.e {@link sun.nio.ch.DirectBuffer}, - * calls it's {@link sun.misc.Cleaner} instance {@code clean()} method. + * calls it's {@link sun.misc.Cleaner} instance {@code clean()} method once. * @return {@code true} if successful, otherwise {@code false}. */ public static boolean clean(final ByteBuffer bb) { - if( !hasCleaner || cleanerError || !bb.isDirect() ) { + if( cleanerError || !bb.isDirect() ) { return false; } try { if( PlatformPropsImpl.JAVA_9 ) { - cClean.invoke(theUnsafe, bb); + UnsafeUtil.invokeCleaner(bb); } else { cClean.invoke(mbbCleaner.invoke(bb)); } @@ -1252,8 +1237,7 @@ public class Buffers { } catch(final Throwable t) { cleanerError = true; if( DEBUG ) { - System.err.println("Caught "+t.getMessage()); - t.printStackTrace(); + ExceptionUtils.dumpThrowable("Buffers", t); } return false; } diff --git a/src/java/com/jogamp/common/util/UnsafeUtil.java b/src/java/com/jogamp/common/util/UnsafeUtil.java new file mode 100644 index 0000000..6fde3fa --- /dev/null +++ b/src/java/com/jogamp/common/util/UnsafeUtil.java @@ -0,0 +1,222 @@ +/** + * Copyright 2019 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.common.util; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import com.jogamp.common.ExceptionUtils; + +import jogamp.common.Debug; +import jogamp.common.os.PlatformPropsImpl; + +/** + * Utility methods allowing easy access to certain {@link sun.misc.Unsafe} functionality. + */ +public class UnsafeUtil { + + static final boolean DEBUG; + static { + DEBUG = Debug.debug("UnsafeUtil"); + } + + protected UnsafeUtil() {} + + private static final Object theUnsafe; + private static final Method unsafeCleanBB; + private static volatile boolean hasUnsafeCleanBBError; /** OK to be lazy on thread synchronization, just for early out **/ + + private static final Method staticFieldOffset; + private static final Method getObjectVolatile; + private static final Method putObjectVolatile; + private static volatile boolean hasGetPutObjectVolatile; + + private static final Class<?> illegalAccessLoggerClass; + private static final Long illegalAccessLoggerOffset; + private static final Object illegalAccessLoggerSync = new Object(); + private static volatile boolean hasIllegalAccessError; + + static { + final Object[] _theUnsafe = { null }; + final Method[] _cleanBB = { null }; + final Method[] _staticFieldOffset = { null }; + final Method[] _objectVolatile = { null, null }; // unsafeGetObjectVolatile, unsafePutObjectVolatile + final Class<?>[] _illegalAccessLoggerClass = { null }; + final Long[] _loggerOffset = { null }; + + AccessController.doPrivileged(new PrivilegedAction<Object>() { + @Override + public Object run() { + Class<?> unsafeClass = null; + try { + // Using: sun.misc.Unsafe { public void invokeCleaner(java.nio.ByteBuffer directBuffer); } + unsafeClass = Class.forName("sun.misc.Unsafe"); + { + final Field f = unsafeClass.getDeclaredField("theUnsafe"); + f.setAccessible(true); + _theUnsafe[0] = f.get(null); + } + _cleanBB[0] = unsafeClass.getMethod("invokeCleaner", java.nio.ByteBuffer.class); + _cleanBB[0].setAccessible(true); + } catch(final Throwable t) { + if( DEBUG ) { + ExceptionUtils.dumpThrowable("UnsafeUtil", t); + } + } + if( null != _theUnsafe[0] && PlatformPropsImpl.JAVA_9 ) { + try { + _staticFieldOffset[0] = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class); + _objectVolatile[0] = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class); + _objectVolatile[1] = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class); + + if( PlatformPropsImpl.JAVA_9 ) { + _illegalAccessLoggerClass[0] = Class.forName("jdk.internal.module.IllegalAccessLogger"); + final Field loggerField = _illegalAccessLoggerClass[0].getDeclaredField("logger"); + _loggerOffset[0] = (Long) _staticFieldOffset[0].invoke(_theUnsafe[0], loggerField); + } + } catch(final Throwable t) { + if( DEBUG ) { + ExceptionUtils.dumpThrowable("UnsafeUtil", t); + } + } + } + return null; + } } ); + theUnsafe = _theUnsafe[0]; + unsafeCleanBB = _cleanBB[0]; + hasUnsafeCleanBBError = null == theUnsafe || null == unsafeCleanBB; + if( DEBUG ) { + System.err.println("UnsafeUtil.init: hasTheUnsafe: "+(null!=theUnsafe)+", hasInvokeCleaner: "+!hasUnsafeCleanBBError); + } + + staticFieldOffset = _staticFieldOffset[0]; + getObjectVolatile = _objectVolatile[0]; + putObjectVolatile = _objectVolatile[1]; + hasGetPutObjectVolatile = null != staticFieldOffset && null != getObjectVolatile && null != putObjectVolatile; + illegalAccessLoggerClass = _illegalAccessLoggerClass[0]; + illegalAccessLoggerOffset = _loggerOffset[0]; + hasIllegalAccessError = !hasGetPutObjectVolatile || null == illegalAccessLoggerClass || null == illegalAccessLoggerOffset; + if( DEBUG ) { + System.err.println("UnsafeUtil.init: hasUnsafeGetPutObjectVolatile: "+hasGetPutObjectVolatile+", hasUnsafeIllegalAccessLogger: "+!hasIllegalAccessError); + } + } + + /** + * Returns {@code true} if {@code sun.misc.Unsafe.invokeCleaner(java.nio.ByteBuffer)} + * is available and has not caused an exception. + * @see #invokeCleaner(ByteBuffer) + */ + public static boolean hasInvokeCleaner() { return !hasUnsafeCleanBBError; } + + /** + * Access to {@code sun.misc.Unsafe.invokeCleaner(java.nio.ByteBuffer)}. + * <p> + * If {@code b} is an direct NIO buffer, i.e {@link sun.nio.ch.DirectBuffer}, + * calls it's {@link sun.misc.Cleaner} instance {@code clean()} method once. + * </p> + * @return {@code true} if successful, otherwise {@code false}. + * @see #hasInvokeCleaner() + */ + public static boolean invokeCleaner(final ByteBuffer bb) { + if( hasUnsafeCleanBBError || !bb.isDirect() ) { + return false; + } + try { + unsafeCleanBB.invoke(theUnsafe, bb); + return true; + } catch(final Throwable t) { + hasUnsafeCleanBBError = true; + if( DEBUG ) { + ExceptionUtils.dumpThrowable("UnsafeUtil", t); + } + return false; + } + } + + /** + * Returns {@code true} if access to {@code jdk.internal.module.IllegalAcessLogger}'s {@code logger} field + * is available and has not caused an exception. + * @see #doWithoutIllegalAccessLogger(PrivilegedAction) + */ + public static boolean hasIllegalAccessLoggerAccess() { return !hasIllegalAccessError; } + + /** + * Issue the given user {@code action} while {@code jdk.internal.module.IllegalAcessLogger}'s {@code logger} has been temporarily disabled. + * <p> + * The caller shall place this call into their own {@link AccessController#doPrivileged(PrivilegedAction)} block. + * </p> + * <p> + * In case the runtime is not {@link PlatformPropsImpl#JAVA_9} or the logger is not accessible or disabling caused an exception, + * the user {@code action} is just executed w/o temporary logger modifications. + * </p> + * @param action the user action task + * @throws RuntimeException is thrown for a caught {@link Throwable} while executing the user {@code action} + * @see #hasIllegalAccessLoggerAccess() + */ + public static <T> T doWithoutIllegalAccessLogger(final PrivilegedAction<T> action) throws RuntimeException { + if( !hasIllegalAccessError ) { + synchronized(illegalAccessLoggerSync) { + final Object newLogger = null; + Object oldLogger = null; + try { + oldLogger = getObjectVolatile.invoke(theUnsafe, illegalAccessLoggerClass, illegalAccessLoggerOffset); + putObjectVolatile.invoke(theUnsafe, illegalAccessLoggerClass, illegalAccessLoggerOffset, newLogger); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // unaccessible .. + hasIllegalAccessError = true; + if( DEBUG ) { + ExceptionUtils.dumpThrowable("UnsafeUtil", e); + } + return action.run(); + } + try { + return action.run(); + } catch (final Throwable t) { + if( DEBUG ) { + t.printStackTrace(); + } + throw new RuntimeException(t); + } finally { + try { + putObjectVolatile.invoke(theUnsafe, illegalAccessLoggerClass, illegalAccessLoggerOffset, oldLogger); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + // should not happen, worked above @ logger setup + hasIllegalAccessError = true; + throw new InternalError(e); + } + } + } + } else { + return action.run(); + } + } +} |