diff --git a/agent/src/main/java/reactor/blockhound/BlockHound.java b/agent/src/main/java/reactor/blockhound/BlockHound.java index 6c3ba5d..60be4eb 100644 --- a/agent/src/main/java/reactor/blockhound/BlockHound.java +++ b/agent/src/main/java/reactor/blockhound/BlockHound.java @@ -134,6 +134,8 @@ public TypePool typePool(ClassFileLocator classFileLocator, ClassLoader classLoa public static class Builder { private final Map>> blockingMethods = new HashMap>>() {{ + int jdkMajorVersion = InstrumentationUtils.getJdkMajorVersion(); + put("java/lang/Object", new HashMap>() {{ put("wait", singleton("(J)V")); }}); @@ -194,10 +196,7 @@ public static class Builder { put("writeBytes", singleton("([BIIZ)V")); }}); - try { - // Check if Java 9+ - Class.forName("java.lang.StackWalker"); - + if (jdkMajorVersion >= 9) { put("jdk/internal/misc/Unsafe", new HashMap>() {{ put("park", singleton("(ZJ)V")); }}); @@ -205,7 +204,7 @@ public static class Builder { put("forkAndExec", singleton("(I[B[B[BI[BI[B[IZ)I")); }}); } - catch (ClassNotFoundException __) { + else { put("sun/misc/Unsafe", new HashMap>() {{ put("park", singleton("(ZJ)V")); }}); @@ -214,19 +213,27 @@ public static class Builder { }}); } - try { - // Check if Java 19+ - Class.forName("java.lang.WrongThreadException"); - + if (jdkMajorVersion < 19) { + // for jdk version < 19, the native method for Thread.sleep is "sleep" + put("java/lang/Thread", new HashMap>() {{ + put("sleep", singleton("(J)V")); + put("yield", singleton("()V")); + put("onSpinWait", singleton("()V")); + }}); + } + else if (jdkMajorVersion >= 19 && jdkMajorVersion <= 21) { + // for jdk version in the range [19, 21], the native method for Thread.sleep is "sleep0" put("java/lang/Thread", new HashMap>() {{ put("sleep0", singleton("(J)V")); put("yield0", singleton("()V")); put("onSpinWait", singleton("()V")); }}); - } catch (ClassNotFoundException __) { + } + else { + // for jdk version >= 22, the native method for Thread.sleep is "sleepNanos0" put("java/lang/Thread", new HashMap>() {{ - put("sleep", singleton("(J)V")); - put("yield", singleton("()V")); + put("sleepNanos0", singleton("(J)V")); + put("yield0", singleton("()V")); put("onSpinWait", singleton("()V")); }}); } diff --git a/agent/src/main/java/reactor/blockhound/InstrumentationUtils.java b/agent/src/main/java/reactor/blockhound/InstrumentationUtils.java index fe803f1..c88c2b4 100644 --- a/agent/src/main/java/reactor/blockhound/InstrumentationUtils.java +++ b/agent/src/main/java/reactor/blockhound/InstrumentationUtils.java @@ -26,6 +26,8 @@ import java.io.IOException; import java.io.InputStream; import java.lang.instrument.Instrumentation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -72,4 +74,70 @@ public void visit(int version, int access, String name, String signature, String super.visit(version, access | Opcodes.ACC_PUBLIC, name, signature, superName, interfaces); } } + + /** + * Helper method to detect the current JDK major version. + * For security reasons, we don't rely on "java.version" system property, but on Runtime.version() method, which is + * available from JDK9 + + * And starting from JDK10+, we rely on Runtime.version().feature() method. + * + * @return the current jdk major version (8, 9, 10, ... 22) + */ + static int getJdkMajorVersion() { + Object version = getRuntimeVersion(); + + if (version == null) { + return 8; // Runtime.version() not available, JDK 8 + } + + return getRuntimeVersionFeature(version); + } + + /** + * Detects the Runtime.version() object, or null if JDK version is < JDK 9 + * + * @return the detected JDK version object or null if not available + */ + private static Object getRuntimeVersion() { + Runtime runtime = Runtime.getRuntime(); + try { + Method versionMethod = runtime.getClass().getMethod("version"); + return versionMethod.invoke(null); + } + + catch (NoSuchMethodException e) { + // Method Runtime.version() not found -> return null, meaning JDK 8 + return null; // JDK 8 + } + + catch (IllegalAccessException | InvocationTargetException e) { + // if the Runtime.version() method exists, we should be able to invoke it, consider this is an error state + throw new IllegalStateException("Could not invoke Runtime.version() method", e); + } + } + + /** + * Extracts the major version from the JDK version object. + * + * @param version the JDK version object + * @return the major version (9, 10, ...) + */ + private static int getRuntimeVersionFeature(Object version) { + try { + Method featureMethod = version.getClass().getMethod("feature"); + Object feature = featureMethod.invoke(version); + return (int) feature; + } + + catch (NoSuchMethodException e) { + // Version.feature() method not found -> JDK 9 (because feature method is only available starting from JDK10 +) + return 9; + } + + catch (IllegalAccessException | InvocationTargetException e) { + // if the Runtime.version().feature() method exists, we should be able to invoke it, consider this is an error state + throw new IllegalStateException("Could not invoke Runtime.version().feature() method", e); + } + } + }