Repository: netty/netty
Issue: NativeLibraryLoader should check the result of ClassLoader#getResource method
Labels: improvement

### Expected behavior

`NativeLibraryLoader` uses `ClassLoader#getResource` method that can return nulls when the resource cannot be found. The returned `url` variable should be checked for nullity and fail in a more usable manner than a `NullPointerException` 

### Actual behavior

`NativeLibraryLoader` fails with a `NullPointerException` (wrapped in `UnsatisfiedLinkError`) which does not give much information about the real cause of the failure.

```
java.lang.UnsatisfiedLinkError: could not load a native library: netty_transport_native_kqueue
	at io.netty.util.internal.NativeLibraryLoader.load(NativeLibraryLoader.java:178)
	at io.netty.channel.kqueue.Native.loadNativeLibrary(Native.java:114)
	at io.netty.channel.kqueue.Native.<clinit>(Native.java:54)
	at io.netty.channel.kqueue.KQueue.<clinit>(KQueue.java:33)
	at io.vertx.core.spi.transport.KQeueTransport.isAvailable(KQeueTransport.java:39)
	at io.vertx.core.spi.transport.NioTransport.nativeTransport(NioTransport.java:47)
	at io.vertx.core.spi.Transport.<clinit>(Transport.java:34)
	at io.vertx.core.impl.VertxImpl.<init>(VertxImpl.java:153)
	at io.vertx.core.impl.VertxImpl.<init>(VertxImpl.java:143)
	at io.vertx.core.impl.VertxFactoryImpl.vertx(VertxFactoryImpl.java:42)
	at io.vertx.core.Vertx.vertx(Vertx.java:91)
	at io.vertx.test.core.VertxTestBase.setUp(VertxTestBase.java:69)
	at io.vertx.test.core.HttpTestBase.setUp(HttpTestBase.java:46)
	at io.vertx.test.core.AsyncTestBase.before(AsyncTestBase.java:76)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
	at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:55)
	at org.junit.rules.RunRules.evaluate(RunRules.java:20)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:367)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:274)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:238)
	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:161)
	at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:290)
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:242)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:121)
Caused by: java.lang.NullPointerException
	at io.netty.util.internal.NativeLibraryLoader.load(NativeLibraryLoader.java:147)
```

### Steps to reproduce

Use the KQueue dependency without a classifier on OSX.

### Netty version

4.1.15.Final

### JVM version (e.g. `java -version`)

java version "1.8.0_144"

### OS version (e.g. `uname -a`)

Darwin julien 15.6.0 Darwin Kernel Version 15.6.0: Fri Feb 17 10:21:18 PST 2017; root:xnu-3248.60.11.4.1~1/RELEASE_X86_64 x86_64


Issue comments:
@vietj please check https://github.com/netty/netty/pull/7228
Fixed by https://github.com/netty/netty/pull/7228
awesome!

Pull request review comments:

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
index dc3e359a78b..a1ea75fc2f1 100644
--- a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
+++ b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
@@ -21,6 +21,7 @@
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -125,22 +126,28 @@ public static void load(String originalName, ClassLoader loader) {
         String libname = System.mapLibraryName(name);
         String path = NATIVE_RESOURCE_HOME + libname;
 
-        URL url = loader.getResource(path);
-        if (url == null && PlatformDependent.isOsx()) {
-            if (path.endsWith(".jnilib")) {
-                url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib");
-            } else {
-                url = loader.getResource(NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib");
-            }
-        }
-
-        int index = libname.lastIndexOf('.');
-        String prefix = libname.substring(0, index);
-        String suffix = libname.substring(index, libname.length());
         InputStream in = null;
         OutputStream out = null;
         File tmpFile = null;
+        URL url = loader.getResource(path);
         try {
+            if (url == null) {
+                if (PlatformDependent.isOsx()) {
+                    String fileName = path.endsWith(".jnilib") ? NATIVE_RESOURCE_HOME + "lib" + name + ".dynlib" :
+                            NATIVE_RESOURCE_HOME + "lib" + name + ".jnilib";
+                    url = loader.getResource(fileName);
+                    if (url == null) {
+                        throw new FileNotFoundException(fileName);
+                    }
+                } else {
+                    throw new FileNotFoundException(path);
+                }
+            }
+
+            int index = libname.lastIndexOf('.');
+            String prefix = libname.substring(0, index);
+            String suffix = libname.substring(index, libname.length());
+
             tmpFile = File.createTempFile(prefix, suffix, WORKDIR);
             in = url.openStream();
             out = new FileOutputStream(tmpFile);


Test patch:
diff --git a/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java b/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java
new file mode 100644
index 00000000000..151e3fc5bc6
--- /dev/null
+++ b/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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 org.junit.Test;
+
+import java.io.FileNotFoundException;
+import java.util.UUID;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class NativeLibraryLoaderTest {
+
+    @Test
+    public void testFileNotFound() {
+        try {
+            NativeLibraryLoader.load(UUID.randomUUID().toString(), NativeLibraryLoaderTest.class.getClassLoader());
+            fail();
+        } catch (UnsatisfiedLinkError error) {
+            assertTrue(error.getCause() instanceof FileNotFoundException);
+        }
+    }
+}