/** * 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() { @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)}. *

* 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. *

* @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. *

* The caller shall place this call into their own {@link AccessController#doPrivileged(PrivilegedAction)} block. *

*

* 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. *

* @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 doWithoutIllegalAccessLogger(final PrivilegedAction 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(); } } }