From 89e5529d0a04f86b8da3d4adfdec41199d8faf7b Mon Sep 17 00:00:00 2001 From: Petr Portnov | PROgrm_JARvis Date: Tue, 14 May 2024 20:15:17 +0300 Subject: [PATCH] fix: add more probing methods for `map0` implementation (#81) * fix: add more probing methods for `map0` implementation * fix: correctly name `map` method * fix: support newer `unmap0` implementations * fix(GH-81): don't reference `f.getChannel()` when not needed * fix(GH-81): specify `null` as receiver of `static` methods --- src/one/nio/mem/MappedFile.java | 128 ++++++++++++++++++++++++---- src/one/nio/util/JavaInternals.java | 14 +++ 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/src/one/nio/mem/MappedFile.java b/src/one/nio/mem/MappedFile.java index 3c9d22b..6f2557a 100755 --- a/src/one/nio/mem/MappedFile.java +++ b/src/one/nio/mem/MappedFile.java @@ -16,6 +16,7 @@ package one.nio.mem; +import java.lang.reflect.Constructor; import one.nio.os.Mem; import one.nio.serial.DataStream; import one.nio.util.JavaInternals; @@ -160,22 +161,72 @@ public static long map(RandomAccessFile f, int mode, long start, long size) thro } try { - Class cls = Class.forName("sun.nio.ch.FileChannelImpl"); - Method map0 = JavaInternals.getMethod(cls, "map0", int.class, long.class, long.class); - if (map0 != null) { - return (long) map0.invoke(f.getChannel(), mode, start, size); + Class fileChannelImplClass = JavaInternals.getClass("sun.nio.ch.FileChannelImpl"); + if (fileChannelImplClass != null) { + Method map0 = JavaInternals.getMethod( + fileChannelImplClass, "map0", int.class, long.class, long.class + ); + if (map0 != null) { + return (long) map0.invoke(f.getChannel(), mode, start, size); + } + // Newer JDK has an extra 'sync' argument + map0 = JavaInternals.getMethod( + fileChannelImplClass, "map0", int.class, long.class, long.class, boolean.class + ); + if (map0 != null) { + return (long) map0.invoke(f.getChannel(), mode, start, size, false); + } + // Since 19 JDK has an extra 'file descriptor' argument + map0 = JavaInternals.getMethod( + fileChannelImplClass, "map0", FileDescriptor.class, int.class, long.class, long.class, boolean.class + ); + if (map0 != null) { + return (long) map0.invoke(f.getChannel(), f.getFD(), mode, start, size, false); + } } - // Newer JDK has an extra 'sync' argument - map0 = JavaInternals.getMethod(cls, "map0", int.class, long.class, long.class, boolean.class); - if (map0 != null) { - return (long) map0.invoke(f.getChannel(), mode, start, size, false); + // JDKs since 20 have the `map0` method moved into the new `FileDispatcherImpl` class as `map` + // In reality, the `static native long map0` method is still available via `UnixFileDispatcherImpl` + // so it is also probed as an alternative (we don't know which one will get removed earlier /shrug) + // See https://github.com/openjdk/jdk/commit/48cc15602b62e81bb179ca9570a1e7d8bbf4d6df + Class fileDispatcherImplClass = JavaInternals.getClass("sun.nio.ch.FileDispatcherImpl"); + if (fileDispatcherImplClass != null) { + Method map0 = JavaInternals.getMethodRecursively(fileDispatcherImplClass, + "map", FileDescriptor.class, int.class, long.class, long.class, boolean.class + ); + if (map0 != null) { + Constructor fileDispatcherImplConstructor = JavaInternals.getConstructor(fileDispatcherImplClass); + if (fileDispatcherImplConstructor != null) { + Object fileDispatcherImpl = fileDispatcherImplConstructor.newInstance(); + return (long) map0.invoke( + fileDispatcherImpl, + f.getFD(), mode, start, size, false + ); + } + } } - // Since 19 JDK has an extra 'file descriptor' argument - map0 = JavaInternals.getMethod( - cls, "map0", FileDescriptor.class, int.class, long.class, long.class, boolean.class); - if (map0 != null) { - return (long) map0.invoke(f.getFD(), f.getChannel(), mode, start, size, false); + Class unixFileDispatcherImplClass = JavaInternals.getClass("sun.nio.ch.UnixFileDispatcherImpl"); + if (unixFileDispatcherImplClass != null) { + Method map0 = JavaInternals.getMethod( + unixFileDispatcherImplClass, "map0", FileDescriptor.class, int.class, long.class, long.class, boolean.class + ); + if (map0 != null) { + return (long) map0.invoke(null, f.getFD(), mode, start, size, false); + } + // we also probe the non-static `map` method on this class... + map0 = JavaInternals.getMethodRecursively( + unixFileDispatcherImplClass, "map", FileDescriptor.class, int.class, long.class, long.class, boolean.class + ); + if (map0 != null) { + Constructor unixFileDispatcherImplConstructor = JavaInternals.getConstructor(unixFileDispatcherImplClass); + if (unixFileDispatcherImplConstructor != null) { + Object unixFileDispatcherImplObject = unixFileDispatcherImplConstructor.newInstance(); + return (long) map0.invoke(unixFileDispatcherImplObject, + f.getFD(), mode, start, size, false + ); + } + } } + throw new IllegalStateException("map0 method not found"); } catch (InvocationTargetException e) { Throwable target = e.getTargetException(); @@ -193,12 +244,53 @@ public static void unmap(long start, long size) { } try { - Class cls = Class.forName("sun.nio.ch.FileChannelImpl"); - Method unmap0 = JavaInternals.getMethod(cls, "unmap0", long.class, long.class); - if (unmap0 == null) { - throw new IllegalStateException("unmap0 method not found"); + Class fileChannelImplClass = JavaInternals.getClass("sun.nio.ch.FileChannelImpl"); + if (fileChannelImplClass != null) { + Method unmap0 = JavaInternals.getMethod(fileChannelImplClass, "unmap0", long.class, long.class); + if (unmap0 != null) { + unmap0.invoke(null, start, size); + return; + } + } + + // JDKs since 20 have the `unmap0` method moved into the new `FileDispatcherImpl` class as `unmap` + // In reality, the `static native long unmap0` method is still available via `UnixFileDispatcherImpl` + // so it is also probed as an alternative (we don't know which one will get removed earlier /shrug) + // See https://github.com/openjdk/jdk/commit/48cc15602b62e81bb179ca9570a1e7d8bbf4d6df + Class fileDispatcherImplClass = JavaInternals.getClass("sun.nio.ch.FileDispatcherImpl"); + if (fileDispatcherImplClass != null) { + Method unmap0 = JavaInternals.getMethodRecursively(fileDispatcherImplClass, "unmap", long.class, long.class); + if (unmap0 != null) { + Constructor fileDispatcherImplConstructor = JavaInternals.getConstructor(fileDispatcherImplClass); + if (fileDispatcherImplConstructor != null) { + Object fileDispatcherImpl = fileDispatcherImplConstructor.newInstance(); + unmap0.invoke(fileDispatcherImpl, start, size); + return; + } + } } - unmap0.invoke(null, start, size); + Class unixFileDispatcherImplClass = JavaInternals.getClass("sun.nio.ch.UnixFileDispatcherImpl"); + if (unixFileDispatcherImplClass != null) { + Method unmap0 = JavaInternals.getMethod( + unixFileDispatcherImplClass, "map0", FileDescriptor.class, int.class, long.class, long.class, boolean.class + ); + if (unmap0 != null) { + unmap0.invoke(null, start, size); + return; + } + // we also probe the non-static `map` method on this class... + unmap0 = JavaInternals.getMethodRecursively(unixFileDispatcherImplClass, "unmap", long.class, long.class); + if (unmap0 != null) { + Constructor unixFileDispatcherImplConstructor = JavaInternals.getConstructor(unixFileDispatcherImplClass); + if (unixFileDispatcherImplConstructor != null) { + Object unixFileDispatcherImplObject = unixFileDispatcherImplConstructor.newInstance(); + unmap0.invoke(unixFileDispatcherImplObject, start, size); + return; + } + } + } + + throw new IllegalStateException("unmap0 method not found"); } catch (ReflectiveOperationException e) { throw new IllegalStateException(e); } diff --git a/src/one/nio/util/JavaInternals.java b/src/one/nio/util/JavaInternals.java index 0df018d..c56ed00 100755 --- a/src/one/nio/util/JavaInternals.java +++ b/src/one/nio/util/JavaInternals.java @@ -63,6 +63,14 @@ private static long getAccessibleOffset() { return 0; } + public static Class getClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + return null; + } + } + public static Field getField(Class cls, String name) { Field f = findField(cls, name); if (f != null) setAccessible(f); @@ -101,6 +109,12 @@ public static Method getMethod(Class cls, String name, Class... params) { return m; } + public static Method getMethodRecursively(Class cls, String name, Class... params) { + Method m = findMethodRecursively(cls, name, params); + if (m != null) setAccessible(m); + return m; + } + public static Method findMethod(Class cls, String name, Class... params) { try { return cls.getDeclaredMethod(name, params);