From e00b797936a02fea1c43b04ba23a3400e5b3f639 Mon Sep 17 00:00:00 2001 From: Jason Tedor Date: Mon, 18 Jul 2016 15:29:47 -0400 Subject: [PATCH] Acquire Java version simply Motivation: The Java version is used for platform dependent logic. Yet, the logic for acquiring the Java version requires special permissions (the runtime permission "getClassLoader") that some downstream projects will never grant. As such, these projects are doomed to have Netty act is their Java major version is six. While there are ways to maintain the same logic without requiring these special permissions, the logic is needlessly complicated because it relies on loading classes that exist in version n but not version n - 1. This complexity can be removed. As a bonanza, the dangerous permission is no longer required. Modifications: Rather than attempting to load classes that exist in version n but not in version n - 1, we can just parse the Java specification version. This only requires a begign property (property permission "java.specification.version") and is simple. Result: Acquisition of the Java version is safe and simple. --- .../util/internal/PlatformDependent.java | 80 +++++++++---------- .../util/internal/PlatformDependentTest.java | 37 +++++++++ 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java index fe21f2d0a7..e8c823ea67 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java @@ -38,12 +38,13 @@ import java.net.InetSocketAddress; import java.net.ServerSocket; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Deque; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Queue; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentMap; @@ -1018,51 +1019,48 @@ public final class PlatformDependent { return false; } - @SuppressWarnings("LoopStatementThatDoesntLoop") private static int javaVersion0() { - int javaVersion; + final int majorVersion; - // Not really a loop - for (;;) { - // Android - if (isAndroid()) { - javaVersion = 6; - break; - } - - try { - Method getVersion = java.lang.Runtime.class.getMethod("version"); - Object version = getVersion.invoke(null); - javaVersion = (Integer) version.getClass().getMethod("major").invoke(version); - break; - } catch (Throwable ignored) { - // Ignore - } - - try { - Class.forName("java.time.Clock", false, getClassLoader(Object.class)); - javaVersion = 8; - break; - } catch (Throwable ignored) { - // Ignore - } - - try { - Class.forName("java.util.concurrent.LinkedTransferQueue", false, getClassLoader(BlockingQueue.class)); - javaVersion = 7; - break; - } catch (Throwable ignored) { - // Ignore - } - - javaVersion = 6; - break; + if (isAndroid()) { + majorVersion = 6; + } else { + majorVersion = majorVersionFromJavaSpecificationVersion(); } - if (logger.isDebugEnabled()) { - logger.debug("Java version: {}", javaVersion); + logger.debug("Java version: {}", majorVersion); + + return majorVersion; + } + + static int majorVersionFromJavaSpecificationVersion() { + try { + final String javaSpecVersion = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return System.getProperty("java.specification.version"); + } + }); + return majorVersion(javaSpecVersion); + } catch (SecurityException e) { + logger.debug("security exception while reading java.specification.version", e); + return 6; + } + } + + static int majorVersion(final String javaSpecVersion) { + final String[] components = javaSpecVersion.split("\\."); + final int[] version = new int[components.length]; + for (int i = 0; i < components.length; i++) { + version[i] = Integer.parseInt(components[i]); + } + + if (version[0] == 1) { + assert version[1] >= 6; + return version[1]; + } else { + return version[0]; } - return javaVersion; } private static boolean hasUnsafe0() { diff --git a/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java b/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java index 0c38f85a56..2999b66b04 100644 --- a/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java +++ b/common/src/test/java/io/netty/util/internal/PlatformDependentTest.java @@ -17,6 +17,7 @@ package io.netty.util.internal; import org.junit.Test; +import java.security.Permission; import java.util.Random; import static io.netty.util.internal.PlatformDependent.hashCodeAscii; @@ -129,4 +130,40 @@ public class PlatformDependentTest { hashCodeAscii(string)); } } + + @Test + public void testMajorVersionFromJavaSpecificationVersion() { + final SecurityManager current = System.getSecurityManager(); + + try { + System.setSecurityManager(new SecurityManager() { + @Override + public void checkPropertyAccess(String key) { + if (key.equals("java.specification.version")) { + // deny + throw new SecurityException(key); + } + } + + // so we can restore the security manager + @Override + public void checkPermission(Permission perm) { + } + }); + + assertEquals(6, PlatformDependent.majorVersionFromJavaSpecificationVersion()); + } finally { + System.setSecurityManager(current); + } + } + + @Test + public void testMajorVersion() { + assertEquals(6, PlatformDependent.majorVersion("1.6")); + assertEquals(7, PlatformDependent.majorVersion("1.7")); + assertEquals(8, PlatformDependent.majorVersion("1.8")); + assertEquals(8, PlatformDependent.majorVersion("8")); + assertEquals(9, PlatformDependent.majorVersion("1.9")); // early version of JDK 9 before Project Verona + assertEquals(9, PlatformDependent.majorVersion("9")); + } }