All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jogamp.common.util.UnsafeUtil Maven / Gradle / Ivy

The newest version!
/**
 * 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(); } } }