diff --git a/CHANGES.md b/CHANGES.md index ef944fcbb..601752c86 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,8 @@ Next Release (5.16.0) Features -------- -* [#1626](https://github.com/java-native-access/jna/pull/1626): Add caching of field list and field validation in `Structure` along with more efficient reentrant read-write locking instead of synchronized() blocks - [@BrettWooldridge](https://github.com/brettwooldridge) +* [#1626](https://github.com/java-native-access/jna/pull/1626): Add caching of field list and field validation in `Structure` along with more efficient reentrant read-write locking instead of synchronized() blocks - [@BrettWooldridge](https://github.com/brettwooldridge). +* [#1636](https://github.com/java-native-access/jna/issues/1636): Drop hard dependency on java.lang.SecurityManager/java.security.AccessController - [@matthiasblaesing](https://github.com/matthiasblaesing). Bug Fixes --------- diff --git a/src/com/sun/jna/Native.java b/src/com/sun/jna/Native.java index 5226b36dd..945ef3f72 100644 --- a/src/com/sun/jna/Native.java +++ b/src/com/sun/jna/Native.java @@ -23,6 +23,8 @@ */ package com.sun.jna; +import com.sun.jna.Callback.UncaughtExceptionHandler; +import com.sun.jna.Structure.FFIType; import java.awt.Component; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; @@ -33,6 +35,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; @@ -50,7 +56,6 @@ import java.nio.charset.Charset; import java.nio.charset.IllegalCharsetNameException; import java.nio.charset.UnsupportedCharsetException; -import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; @@ -60,11 +65,9 @@ import java.util.Map; import java.util.StringTokenizer; import java.util.WeakHashMap; - -import com.sun.jna.Callback.UncaughtExceptionHandler; -import com.sun.jna.Structure.FFIType; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Stream; /** Provides generation of invocation plumbing for a defined native * library interface. Also provides various utilities for native operations. @@ -181,6 +184,16 @@ public void uncaughtException(Callback c, Throwable e) { private static final int TYPE_BOOL = 4; private static final int TYPE_LONG_DOUBLE = 5; + private static final MethodHandle stackWalkerGetInstance; + private static final Enum stackWalkerRetainClassReference; + private static final MethodHandle stackWalkerWalk; + private static final Object stackWalkerFilter; + + private static final MethodHandle securityManagerExposerConstructor; + private static final MethodHandle securityManagerGetClassContext; + + private static final MethodHandle accessControllerDoPrivileged; + static final int MAX_ALIGNMENT; static final int MAX_PADDING; @@ -255,6 +268,70 @@ static boolean isCompatibleVersion(String expectedVersion, String nativeVersion) || (Platform.isAndroid() && !Platform.isIntel()) ? 8 : LONG_SIZE; MAX_PADDING = (Platform.isMac() && Platform.isPPC()) ? 8 : MAX_ALIGNMENT; + + Enum stackWalkerRetainClassReferenceBuilder; + MethodHandle stackWalkerGetInstanceBuilder; + MethodHandle stackWalkerWalkBuilder; + Object stackWalkerFilterBuilder; + try { + Lookup lookup = MethodHandles.lookup(); + Class stackWalkerClass = Class.forName("java.lang.StackWalker"); + Class stackWalkerOptionClass = (Class) Class.forName("java.lang.StackWalker$Option"); + stackWalkerRetainClassReferenceBuilder = Enum.valueOf(stackWalkerOptionClass, "RETAIN_CLASS_REFERENCE"); + stackWalkerGetInstanceBuilder = lookup.findStatic(stackWalkerClass, "getInstance", MethodType.methodType(stackWalkerClass, stackWalkerOptionClass)); + stackWalkerWalkBuilder = lookup.findVirtual(stackWalkerClass, "walk", MethodType.methodType(Object.class, java.util.function.Function.class)); + Class stackframe = Class.forName("java.lang.StackWalker$StackFrame"); + MethodHandle stackFrameGetDeclaringClass = lookup.findVirtual(stackframe, "getDeclaringClass", MethodType.methodType(Class.class)); + stackWalkerFilterBuilder = new java.util.function.Function, Class>() { + @Override + public Class apply(Stream t) { + Object stackFrame = t.skip(2).findFirst().get(); + try { + return (Class) stackFrameGetDeclaringClass.invoke(stackFrame); + } catch (Throwable ex) { + return null; + } + } + }; + + } catch (Throwable ex) { + LOG.log(Level.FINE, "Failed to initialize stack accessor method StackWalker", ex); + stackWalkerRetainClassReferenceBuilder = null; + stackWalkerGetInstanceBuilder = null; + stackWalkerWalkBuilder = null; + stackWalkerFilterBuilder = null; + } + stackWalkerRetainClassReference = stackWalkerRetainClassReferenceBuilder; + stackWalkerGetInstance = stackWalkerGetInstanceBuilder; + stackWalkerWalk = stackWalkerWalkBuilder; + stackWalkerFilter = stackWalkerFilterBuilder; + + MethodHandle securityManagerExposerConstructorBuilder; + MethodHandle securityManagerGetClassContextBuilder; + try { + Lookup lookup = MethodHandles.lookup(); + Class securityManagerExposerClass = Class.forName("com.sun.jna.SecurityManagerExposer"); + securityManagerExposerConstructorBuilder = lookup.findConstructor(securityManagerExposerClass, MethodType.methodType(void.class)); + securityManagerGetClassContextBuilder = lookup.findVirtual(securityManagerExposerClass, "getClassContext", MethodType.methodType(Class[].class)); + } catch (Throwable ex) { + LOG.log(Level.FINE, "Failed to initialize stack accessor method SecurityManager", ex); + securityManagerExposerConstructorBuilder = null; + securityManagerGetClassContextBuilder = null; + } + securityManagerExposerConstructor = securityManagerExposerConstructorBuilder; + securityManagerGetClassContext = securityManagerGetClassContextBuilder; + + MethodHandle accessControllerDoPrivilegedBuilder = null; + try { + Lookup lookup = MethodHandles.lookup(); + Class accessControllerClass = Class.forName("java.security.AccessController"); + accessControllerDoPrivilegedBuilder = lookup.findStatic(accessControllerClass, "doPrivileged", MethodType.methodType(Object.class, PrivilegedAction.class)); + } catch (Throwable ex) { + LOG.log(Level.FINE, "Failed to initialize AccessController#doPrivileged", ex); + accessControllerDoPrivilegedBuilder = null; + } + accessControllerDoPrivileged = accessControllerDoPrivilegedBuilder; + System.setProperty("jna.loaded", "true"); } @@ -1275,7 +1352,7 @@ public static String getWebStartLibraryPath(final String libName) { try { final ClassLoader cl = Native.class.getClassLoader(); - Method m = AccessController.doPrivileged(new PrivilegedAction() { + Method m = (Method) accessControllerDoPrivileged.invoke(new PrivilegedAction() { @Override public Method run() { try { @@ -1294,7 +1371,7 @@ public Method run() { } return null; } - catch (Exception e) { + catch (Throwable e) { return null; } } @@ -1523,19 +1600,35 @@ static Class findDirectMappedClass(Class cls) { was made. */ static Class getCallingClass() { - Class[] context = new SecurityManager() { - @Override - public Class[] getClassContext() { - return super.getClassContext(); + if (stackWalkerGetInstance != null) { + try { + Object walker = stackWalkerGetInstance.invoke(stackWalkerRetainClassReference); + Class caller = (Class) stackWalkerWalk.invoke(walker, stackWalkerFilter); + return caller; + } catch (Throwable ex) { + LOG.log(Level.WARNING, "Failed to invoke StackWalker#getInstance or StackWalker#walk", ex); } - }.getClassContext(); - if (context == null) { - throw new IllegalStateException("The SecurityManager implementation on this platform is broken; you must explicitly provide the class to register"); } - if (context.length < 4) { - throw new IllegalStateException("This method must be called from the static initializer of a class"); + + if (securityManagerExposerConstructor != null) { + Class[] context = null; + try { + Object securityManagerExposer = securityManagerExposerConstructor.invoke(); + context = (Class[]) securityManagerGetClassContext.invoke(securityManagerExposer); + } catch (Throwable ex) { + LOG.log(Level.WARNING, "Failed to invoke SecurityManagerExposer# or SecurityManagerExposer#getClassContext", ex); + } + + if (context == null) { + throw new IllegalStateException("The SecurityManager implementation on this platform is broken; you must explicitly provide the class to register"); + } + if (context.length < 4) { + throw new IllegalStateException("This method must be called from the static initializer of a class"); + } + return context[3]; } - return context[3]; + + throw new IllegalStateException("Neither the StackWalker, nor the SecurityManager based getCallingClass implementation are useable; you must explicitly provide the class to register"); } /** diff --git a/src/com/sun/jna/SecurityManagerExposer.java b/src/com/sun/jna/SecurityManagerExposer.java new file mode 100644 index 000000000..029246d14 --- /dev/null +++ b/src/com/sun/jna/SecurityManagerExposer.java @@ -0,0 +1,38 @@ +/* Copyright (c) 2024 Matthias Bläsing, All Rights Reserved + * + * The contents of this file is dual-licensed under 2 + * alternative Open Source/Free licenses: LGPL 2.1 or later and + * Apache License 2.0. (starting with JNA version 4.0.0). + * + * You can freely decide which license you want to apply to + * the project. + * + * You may obtain a copy of the LGPL License at: + * + * http://www.gnu.org/licenses/licenses.html + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "LGPL2.1". + * + * You may obtain a copy of the Apache License at: + * + * http://www.apache.org/licenses/ + * + * A copy is also included in the downloadable source code package + * containing JNA, in file "AL2.0". + */ +package com.sun.jna; + +/** + * Helper class to make {@link SecurityManager#getClassContext()} accessible + * from {@link Native}. It is used as a fallback, if {@link StackWalker} is not + * availble. + */ +class SecurityManagerExposer extends SecurityManager { + + @Override + protected Class[] getClassContext() { + return super.getClassContext(); + } + +}