Repository: netty/netty
Issue: Initialization of PlatformDependent0 fails on Java 9
Labels: defect

Initialization of `PlatformDependent0` fails on Java 9 in static initializer when calling `setAccessible(true)`.

```
Field field = Buffer.class.getDeclaredField("address");
field.setAccessible(true);
```

### Expected behavior

Initialization should not fail without `--add-opens=java.base/java.nio=ALL-UNNAMED`

### Actual behavior

Class initialization fails with:

```
java.lang.reflect.InaccessibleObjectException: Unable to make field long java.nio.Buffer.address accessible: module java.base does not "opens java.nio" to unnamed module @598067a5
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:207)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:171)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:165)
	at io.netty.util.internal.PlatformDependent0$1.run(PlatformDependent0.java:72)
	at java.base/java.security.AccessController.doPrivileged(Native Method)
	at io.netty.util.internal.PlatformDependent0.<clinit>(PlatformDependent0.java:67)
	at io.netty.util.internal.PlatformDependent.getSystemClassLoader(PlatformDependent.java:888)
	at io.netty.util.internal.PlatformDependent.isAndroid0(PlatformDependent.java:905)
	at io.netty.util.internal.PlatformDependent.<clinit>(PlatformDependent.java:81)
	at io.netty.buffer.PooledByteBufAllocator.<clinit>(PooledByteBufAllocator.java:87)

```
### Steps to reproduce

Trigger class initialization of `PlatformDependent0` or call to `PlatformDependent.getContextClassLoader()`.

### Minimal yet complete reproducer code (or URL to code)

Variant 1:
```
PlatformDependent.getContextClassLoader()
```

Variant 2:
```
Version nettyBufferVersion = Version.identify().get("netty-buffer");
```
### Netty version

4.1.8

### JVM version (e.g. `java -version`)

Java(TM) SE Runtime Environment (build 9-ea+156)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+156, mixed mode)

### OS version (e.g. `uname -a`)

macOS Sierra (16.4.0 Darwin Kernel Version 16.4.0: Thu Dec 22 22:53:21 PST 2016; root:xnu-3789.41.3~3/RELEASE_X86_64 x86_64)

Issue comments:
@mp911de thanks for reporting... let me fix this
Started working on a fix... should have a PR soon
Fixed by https://github.com/netty/netty/pull/6356

Pull request review comments:
@normanmaurer +1 ... can't hurt

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
index cbfd8ed4253..6abdb0035f8 100644
--- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
+++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
@@ -36,7 +36,7 @@
 final class PlatformDependent0 {
 
     private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent0.class);
-    static final Unsafe UNSAFE;
+    private static final Unsafe UNSAFE;
     private static final long ADDRESS_FIELD_OFFSET;
     private static final long BYTE_ARRAY_BASE_OFFSET;
     private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
@@ -69,7 +69,10 @@ final class PlatformDependent0 {
                 public Object run() {
                     try {
                         final Field field = Buffer.class.getDeclaredField("address");
-                        field.setAccessible(true);
+                        Throwable cause = ReflectionUtil.trySetAccessible(field);
+                        if (cause != null) {
+                            return cause;
+                        }
                         // if direct really is a direct buffer, address will be non-zero
                         if (field.getLong(direct) == 0) {
                             return null;
@@ -102,7 +105,10 @@ public Object run() {
                 public Object run() {
                     try {
                         final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
-                        unsafeField.setAccessible(true);
+                        Throwable cause = ReflectionUtil.trySetAccessible(unsafeField);
+                        if (cause != null) {
+                            return cause;
+                        }
                         // the unsafe instance
                         return unsafeField.get(null);
                     } catch (NoSuchFieldException e) {
@@ -179,7 +185,10 @@ public Object run() {
                                 try {
                                     final Constructor<?> constructor =
                                             direct.getClass().getDeclaredConstructor(long.class, int.class);
-                                    constructor.setAccessible(true);
+                                    Throwable cause = ReflectionUtil.trySetAccessible(constructor);
+                                    if (cause != null) {
+                                        return cause;
+                                    }
                                     return constructor;
                                 } catch (NoSuchMethodException e) {
                                     return e;
@@ -226,10 +235,21 @@ public Object run() {
                         Class<?> bitsClass =
                                 Class.forName("java.nio.Bits", false, PlatformDependent.getSystemClassLoader());
                         Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
-                        unalignedMethod.setAccessible(true);
+                        Throwable cause = ReflectionUtil.trySetAccessible(unalignedMethod);
+                        if (cause != null) {
+                            return cause;
+                        }
                         return unalignedMethod.invoke(null);
-                    } catch (Throwable cause) {
-                        return cause;
+                    } catch (NoSuchMethodException e) {
+                        return e;
+                    } catch (SecurityException e) {
+                        return e;
+                    } catch (IllegalAccessException e) {
+                        return e;
+                    } catch (ClassNotFoundException e) {
+                        return e;
+                    } catch (InvocationTargetException e) {
+                        return e;
                     }
                 }
             });
diff --git a/common/src/main/java/io/netty/util/internal/ReflectionUtil.java b/common/src/main/java/io/netty/util/internal/ReflectionUtil.java
new file mode 100644
index 00000000000..ef39b352150
--- /dev/null
+++ b/common/src/main/java/io/netty/util/internal/ReflectionUtil.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.util.internal;
+
+import java.lang.reflect.AccessibleObject;
+
+public final class ReflectionUtil {
+
+    private ReflectionUtil() { }
+
+    /**
+     * Try to call {@link AccessibleObject#setAccessible(boolean)} but will catch any {@link SecurityException} and
+     * {@link java.lang.reflect.InaccessibleObjectException} and return it.
+     * The caller must check if it returns {@code null} and if not handle the returned exception.
+     */
+    public static Throwable trySetAccessible(AccessibleObject object) {
+        try {
+            object.setAccessible(true);
+            return null;
+        } catch (SecurityException e) {
+            return e;
+        } catch (RuntimeException e) {
+            return handleInaccessibleObjectException(e);
+        }
+    }
+
+    private static RuntimeException handleInaccessibleObjectException(RuntimeException e) {
+        // JDK 9 can throw an inaccessible object exception here; since Netty compiles
+        // against JDK 7 and this exception was only added in JDK 9, we have to weakly
+        // check the type
+        if ("java.lang.reflect.InaccessibleObjectException".equals(e.getClass().getName())) {
+            return e;
+        }
+        throw e;
+    }
+}
diff --git a/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java b/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java
index ac25e425907..76f3fc79d63 100644
--- a/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java
+++ b/transport/src/main/java/io/netty/channel/nio/NioEventLoop.java
@@ -24,6 +24,7 @@
 import io.netty.util.IntSupplier;
 import io.netty.util.concurrent.RejectedExecutionHandler;
 import io.netty.util.internal.PlatformDependent;
+import io.netty.util.internal.ReflectionUtil;
 import io.netty.util.internal.SystemPropertyUtil;
 import io.netty.util.internal.logging.InternalLogger;
 import io.netty.util.internal.logging.InternalLoggerFactory;
@@ -195,8 +196,14 @@ public Object run() {
                     Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                     Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
 
-                    selectedKeysField.setAccessible(true);
-                    publicSelectedKeysField.setAccessible(true);
+                    Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField);
+                    if (cause != null) {
+                        return cause;
+                    }
+                    cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
+                    if (cause != null) {
+                        return cause;
+                    }
 
                     selectedKeysField.set(selector, selectedKeySet);
                     publicSelectedKeysField.set(selector, selectedKeySet);
@@ -205,15 +212,6 @@ public Object run() {
                     return e;
                 } catch (IllegalAccessException e) {
                     return e;
-                } catch (RuntimeException e) {
-                    // JDK 9 can throw an inaccessible object exception here; since Netty compiles
-                    // against JDK 7 and this exception was only added in JDK 9, we have to weakly
-                    // check the type
-                    if ("java.lang.reflect.InaccessibleObjectException".equals(e.getClass().getName())) {
-                        return e;
-                    } else {
-                        throw e;
-                    }
                 }
             }
         });


Test patch: