Skip to content

Commit 03f7003

Browse files
committed
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.
1 parent 3d7ae97 commit 03f7003

File tree

2 files changed

+72
-37
lines changed

2 files changed

+72
-37
lines changed

common/src/main/java/io/netty/util/internal/PlatformDependent.java

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@
3838
import java.net.ServerSocket;
3939
import java.nio.ByteBuffer;
4040
import java.nio.ByteOrder;
41+
import java.security.AccessController;
42+
import java.security.PrivilegedAction;
4143
import java.util.Deque;
4244
import java.util.List;
4345
import java.util.Locale;
4446
import java.util.Map;
4547
import java.util.Queue;
46-
import java.util.concurrent.BlockingQueue;
4748
import java.util.concurrent.ConcurrentHashMap;
4849
import java.util.concurrent.ConcurrentLinkedDeque;
4950
import java.util.concurrent.ConcurrentMap;
@@ -1018,51 +1019,48 @@ private static boolean isRoot0() {
10181019
return false;
10191020
}
10201021

1021-
@SuppressWarnings("LoopStatementThatDoesntLoop")
10221022
private static int javaVersion0() {
1023-
int javaVersion;
1023+
final int majorVersion;
10241024

1025-
// Not really a loop
1026-
for (;;) {
1027-
// Android
1028-
if (isAndroid()) {
1029-
javaVersion = 6;
1030-
break;
1031-
}
1025+
if (isAndroid()) {
1026+
majorVersion = 6;
1027+
} else {
1028+
majorVersion = majorVersionFromJavaSpecificationVersion();
1029+
}
10321030

1033-
try {
1034-
Method getVersion = java.lang.Runtime.class.getMethod("version");
1035-
Object version = getVersion.invoke(null);
1036-
javaVersion = (Integer) version.getClass().getMethod("major").invoke(version);
1037-
break;
1038-
} catch (Throwable ignored) {
1039-
// Ignore
1040-
}
1031+
logger.debug("Java version: {}", majorVersion);
10411032

1042-
try {
1043-
Class.forName("java.time.Clock", false, getClassLoader(Object.class));
1044-
javaVersion = 8;
1045-
break;
1046-
} catch (Throwable ignored) {
1047-
// Ignore
1048-
}
1033+
return majorVersion;
1034+
}
10491035

1050-
try {
1051-
Class.forName("java.util.concurrent.LinkedTransferQueue", false, getClassLoader(BlockingQueue.class));
1052-
javaVersion = 7;
1053-
break;
1054-
} catch (Throwable ignored) {
1055-
// Ignore
1056-
}
1036+
static int majorVersionFromJavaSpecificationVersion() {
1037+
try {
1038+
final String javaSpecVersion = AccessController.doPrivileged(new PrivilegedAction<String>() {
1039+
@Override
1040+
public String run() {
1041+
return System.getProperty("java.specification.version");
1042+
}
1043+
});
1044+
return majorVersion(javaSpecVersion);
1045+
} catch (SecurityException e) {
1046+
logger.debug("security exception while reading java.specification.version", e);
1047+
return 6;
1048+
}
1049+
}
10571050

1058-
javaVersion = 6;
1059-
break;
1051+
static int majorVersion(final String javaSpecVersion) {
1052+
final String[] components = javaSpecVersion.split("\\.");
1053+
final int[] version = new int[components.length];
1054+
for (int i = 0; i < components.length; i++) {
1055+
version[i] = Integer.parseInt(components[i]);
10601056
}
10611057

1062-
if (logger.isDebugEnabled()) {
1063-
logger.debug("Java version: {}", javaVersion);
1058+
if (version[0] == 1) {
1059+
assert version[1] >= 6;
1060+
return version[1];
1061+
} else {
1062+
return version[0];
10641063
}
1065-
return javaVersion;
10661064
}
10671065

10681066
private static boolean hasUnsafe0() {

common/src/test/java/io/netty/util/internal/PlatformDependentTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import org.junit.Test;
1919

20+
import java.security.Permission;
2021
import java.util.Random;
2122

2223
import static io.netty.util.internal.PlatformDependent.hashCodeAscii;
@@ -129,4 +130,40 @@ public void testHashCodeAscii() {
129130
hashCodeAscii(string));
130131
}
131132
}
133+
134+
@Test
135+
public void testMajorVersionFromJavaSpecificationVersion() {
136+
final SecurityManager current = System.getSecurityManager();
137+
138+
try {
139+
System.setSecurityManager(new SecurityManager() {
140+
@Override
141+
public void checkPropertyAccess(String key) {
142+
if (key.equals("java.specification.version")) {
143+
// deny
144+
throw new SecurityException(key);
145+
}
146+
}
147+
148+
// so we can restore the security manager
149+
@Override
150+
public void checkPermission(Permission perm) {
151+
}
152+
});
153+
154+
assertEquals(6, PlatformDependent.majorVersionFromJavaSpecificationVersion());
155+
} finally {
156+
System.setSecurityManager(current);
157+
}
158+
}
159+
160+
@Test
161+
public void testMajorVersion() {
162+
assertEquals(6, PlatformDependent.majorVersion("1.6"));
163+
assertEquals(7, PlatformDependent.majorVersion("1.7"));
164+
assertEquals(8, PlatformDependent.majorVersion("1.8"));
165+
assertEquals(8, PlatformDependent.majorVersion("8"));
166+
assertEquals(9, PlatformDependent.majorVersion("1.9")); // early version of JDK 9 before Project Verona
167+
assertEquals(9, PlatformDependent.majorVersion("9"));
168+
}
132169
}

0 commit comments

Comments
 (0)