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.
This commit is contained in:
Jason Tedor 2016-07-18 15:29:47 -04:00 committed by Norman Maurer
parent 3d7ae97359
commit e00b797936
2 changed files with 76 additions and 41 deletions

View File

@ -38,12 +38,13 @@ import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -1018,51 +1019,48 @@ public final class PlatformDependent {
return false; return false;
} }
@SuppressWarnings("LoopStatementThatDoesntLoop")
private static int javaVersion0() { private static int javaVersion0() {
int javaVersion; final int majorVersion;
// Not really a loop
for (;;) {
// Android
if (isAndroid()) { if (isAndroid()) {
javaVersion = 6; majorVersion = 6;
break; } else {
majorVersion = majorVersionFromJavaSpecificationVersion();
} }
logger.debug("Java version: {}", majorVersion);
return majorVersion;
}
static int majorVersionFromJavaSpecificationVersion() {
try { try {
Method getVersion = java.lang.Runtime.class.getMethod("version"); final String javaSpecVersion = AccessController.doPrivileged(new PrivilegedAction<String>() {
Object version = getVersion.invoke(null); @Override
javaVersion = (Integer) version.getClass().getMethod("major").invoke(version); public String run() {
break; return System.getProperty("java.specification.version");
} catch (Throwable ignored) { }
// Ignore });
return majorVersion(javaSpecVersion);
} catch (SecurityException e) {
logger.debug("security exception while reading java.specification.version", e);
return 6;
}
} }
try { static int majorVersion(final String javaSpecVersion) {
Class.forName("java.time.Clock", false, getClassLoader(Object.class)); final String[] components = javaSpecVersion.split("\\.");
javaVersion = 8; final int[] version = new int[components.length];
break; for (int i = 0; i < components.length; i++) {
} catch (Throwable ignored) { version[i] = Integer.parseInt(components[i]);
// Ignore
} }
try { if (version[0] == 1) {
Class.forName("java.util.concurrent.LinkedTransferQueue", false, getClassLoader(BlockingQueue.class)); assert version[1] >= 6;
javaVersion = 7; return version[1];
break; } else {
} catch (Throwable ignored) { return version[0];
// Ignore
} }
javaVersion = 6;
break;
}
if (logger.isDebugEnabled()) {
logger.debug("Java version: {}", javaVersion);
}
return javaVersion;
} }
private static boolean hasUnsafe0() { private static boolean hasUnsafe0() {

View File

@ -17,6 +17,7 @@ package io.netty.util.internal;
import org.junit.Test; import org.junit.Test;
import java.security.Permission;
import java.util.Random; import java.util.Random;
import static io.netty.util.internal.PlatformDependent.hashCodeAscii; import static io.netty.util.internal.PlatformDependent.hashCodeAscii;
@ -129,4 +130,40 @@ public class PlatformDependentTest {
hashCodeAscii(string)); 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"));
}
} }