Repository: netty/netty
Issue: `ShadingIT` fails on macOS and Linux
Labels: ci

### Expected behavior

The following commands should succeed:

```
$ ./mvnw install -DskipTests
$ cd testsuite-shading
$ ../mvnw clean integration-test failsafe:verify
```

### Actual behavior

The above command fails with the following error:

```
[ERROR] Tests run: 2, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.19 s <<< FAILURE! - in io.netty.testsuite.shading.ShadingIT
[ERROR] testShadingNativeTransport  Time elapsed: 0.374 s  <<< ERROR!
java.lang.UnsatisfiedLinkError: failed to load the required native library
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:54)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
Caused by: java.lang.NoClassDefFoundError: shaded2/io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods (wrong name: shaded2//io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods)
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:52)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
```

<details>
<summary>The complete test output:</summary>

```
22:01:56.060 [main] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
22:01:56.062 [main] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
22:01:56.062 [main] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 11
22:01:56.063 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
22:01:56.063 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
22:01:56.063 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available
22:01:56.063 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
22:01:56.063 [main] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable: Reflective setAccessible(true) disabled
22:01:56.064 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true
22:01:56.064 [main] DEBUG io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: class io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @75e32c4c
22:01:56.065 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, int): unavailable
22:01:56.065 [main] DEBUG io.netty.util.internal.PlatformDependent - sun.misc.Unsafe: available
22:01:56.071 [main] DEBUG io.netty.util.internal.PlatformDependent - maxDirectMemory: 8589934592 bytes (maybe)
22:01:56.071 [main] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.tmpdir: /var/folders/90/dr7vn6w14vzdt65wm4tfswwc0000gp/T (java.io.tmpdir)
22:01:56.071 [main] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model)
22:01:56.072 [main] DEBUG io.netty.util.internal.PlatformDependent - Platform: MacOS
22:01:56.072 [main] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: -1 bytes
22:01:56.072 [main] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1
22:01:56.073 [main] DEBUG io.netty.util.internal.CleanerJava9 - java.nio.ByteBuffer.cleaner(): available
22:01:56.073 [main] DEBUG io.netty.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false
22:01:56.076 [main] DEBUG shaded2.io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
22:01:56.090 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false
22:01:56.090 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - Java version: 11
22:01:56.090 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
22:01:56.090 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
22:01:56.091 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available
22:01:56.091 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available
22:01:56.091 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable: Reflective setAccessible(true) disabled
22:01:56.091 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: class shaded2.io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @75e32c4c
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, int): unavailable
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - sun.misc.Unsafe: available
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - maxDirectMemory: 8589934592 bytes (maybe)
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - -Dio.netty.tmpdir: /var/folders/90/dr7vn6w14vzdt65wm4tfswwc0000gp/T (java.io.tmpdir)
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model)
22:01:56.092 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - Platform: MacOS
22:01:56.093 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: -1 bytes
22:01:56.093 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1
22:01:56.093 [main] DEBUG shaded2.io.netty.util.internal.CleanerJava9 - java.nio.ByteBuffer.cleaner(): available
22:01:56.093 [main] DEBUG shaded2.io.netty.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false
22:01:56.095 [main] DEBUG shaded2.io.netty.util.internal.NativeLibraryLoader - -Dio.netty.native.workdir: /var/folders/90/dr7vn6w14vzdt65wm4tfswwc0000gp/T (io.netty.tmpdir)
22:01:56.095 [main] DEBUG shaded2.io.netty.util.internal.NativeLibraryLoader - -Dio.netty.native.deleteLibAfterLoading: true
22:01:56.095 [main] DEBUG shaded2.io.netty.util.internal.NativeLibraryLoader - -Dio.netty.native.tryPatchShadedId: true
22:01:56.095 [main] DEBUG shaded2.io.netty.util.internal.NativeLibraryLoader - -Dio.netty.native.detectNativeLibraryDuplicates: true
22:01:56.110 [main] DEBUG shaded2.io.netty.util.internal.NativeLibraryLoader - Execution of 'install_name_tool -id Zu9wWlVpYUcosy50wS6A1VvqOI5ddgPAT8eIVm /var/folders/90/dr7vn6w14vzdt65wm4tfswwc0000gp/T/libshaded2_netty_transport_native_kqueue_aarch_641290578488165430543.dylib' succeed: 0
22:01:56.149 [main] DEBUG shaded2.io.netty.util.internal.NativeLibraryLoader - Execution of 'codesign -s - /var/folders/90/dr7vn6w14vzdt65wm4tfswwc0000gp/T/libshaded2_netty_transport_native_kqueue_aarch_641290578488165430543.dylib' succeed: 0
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.392 s <<< FAILURE! - in io.netty.testsuite.shading.ShadingIT
[ERROR] testShadingNativeTransport  Time elapsed: 0.385 s  <<< ERROR!
java.lang.UnsatisfiedLinkError: failed to load the required native library
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:54)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
Caused by: java.lang.NoClassDefFoundError: shaded2/io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods (wrong name: shaded2//io/netty/channel/kqueue/KQueueStaticallyReferencedJniMethods)
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:52)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
```

</details>

I see the similar failure for epoll on Linux ARM64 as well:

```
...
Caused by: java.lang.NoClassDefFoundError: shaded2/io/netty/channel/epoll/NativeStaticallyReferencedJniMethods (wrong name: shaded2//io/netty/channel/epoll/NativeStaticallyReferencedJniMethods)
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:52)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
```

What's the current state of `testsuite-shading`? It is running as a part of the CI process?

### Netty version

Branch `4.1` f901ea2e712d9d16e199aaeb7b622e36944eb8dd

### JVM version (e.g. `java -version`)

macOS:

```
❯ java -version
openjdk version "11.0.14" 2022-01-18 LTS
OpenJDK Runtime Environment Zulu11.54+23-CA (build 11.0.14+9-LTS)
OpenJDK 64-Bit Server VM Zulu11.54+23-CA (build 11.0.14+9-LTS, mixed mode)
```

Linux:

```
❯ java -version
openjdk version "11.0.8" 2020-07-14 LTS
OpenJDK Runtime Environment Zulu11.41+75-CA (build 11.0.8+10-LTS)
OpenJDK 64-Bit Server VM Zulu11.41+75-CA (build 11.0.8+10-LTS, mixed mode)
```

### OS version (e.g. `uname -a`)

macOS:

```
❯ uname -a
Darwin ... 21.4.0 Darwin Kernel Version 21.4.0: Mon Feb 21 20:35:58 PST 2022; root:xnu-8020.101.4~2/RELEASE_ARM64_T6000 arm64
```

Linux:

```
❯ uname -a
Linux ... 5.15.0-27-generic #28-Ubuntu SMP Thu Apr 14 12:56:31 UTC 2022 aarch64 aarch64 aarch64 GNU/Linux
```

Issue comments:
The test passes if I comment out `ShadingIT.testShadingNativeTransport()`, but why? Why does `testShadingTcnative()` pass? I can see the shaded native libraries are all there for my architecture (osx_aarch_64):

```
❯ find target/classes-shaded/ | grep META-INF/native/
target/classes-shaded/META-INF/native/libnetty_tcnative_linux_x86_64.so
target/classes-shaded/META-INF/native/netty_tcnative_windows_x86_64.dll
target/classes-shaded/META-INF/native/libshaded_netty_tcnative_osx_aarch_64.jnilib
target/classes-shaded/META-INF/native/libnetty_tcnative_linux_aarch_64.so
target/classes-shaded/META-INF/native/libshaded2_netty_transport_native_kqueue_aarch_64.jnilib
target/classes-shaded/META-INF/native/libshaded2_netty_tcnative_osx_aarch_64.jnilib
target/classes-shaded/META-INF/native/libshaded_netty_transport_native_kqueue_aarch_64.jnilib
target/classes-shaded/META-INF/native/libnetty_tcnative_osx_x86_64.jnilib
```
~One major difference I found is that `lib{shaded,shaded2}_netty_transport_native_kqueue_aarch_64.jnilib` doesn't have `_osx_` before `aarch_64`. Could this be why?~ Looks like `...kqueue.Native.loadNativeLibrary()` doesn't add OS to the library name, so this is fine.
I ran the above build commands on my Ubuntu ARM64 VM and saw the same failure:
```
[ERROR] Tests run: 2, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.417 s <<< FAILURE! - in io.netty.testsuite.shading.ShadingIT
[ERROR] testShadingNativeTransport  Time elapsed: 0.171 s  <<< ERROR!
java.lang.UnsatisfiedLinkError: failed to load the required native library
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:54)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
Caused by: java.lang.NoClassDefFoundError: shaded2/io/netty/channel/epoll/NativeStaticallyReferencedJniMethods (wrong name: shaded2//io/netty/channel/epoll/NativeStaticallyReferencedJniMethods)
	at io.netty.testsuite.shading.ShadingIT.testShading0(ShadingIT.java:52)
	at io.netty.testsuite.shading.ShadingIT.testShadingNativeTransport(ShadingIT.java:37)
```
Let me change the subject of this issue because it seems to affect both macOS and Linux.
Will check 
Thanks for looking, @normanmaurer. It looks like there's a bug in `parsePackagePrefix()` - it appends an extra `/` at the end of the package prefix. Let me dig a little bit more and get back.
OK. I have a fix. Let me clean it up and send out a PR tomorrow. I'll also implement the rule documented [here](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names), so it handles a peculiar package name correctly, such as one with `_` or a non-ASCII character.

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 795ac28006a..826cc38d9b9 100644
--- a/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
+++ b/common/src/main/java/io/netty/util/internal/NativeLibraryLoader.java
@@ -117,11 +117,25 @@ public static void loadFirstAvailable(ClassLoader loader, String... names) {
     }
 
     /**
-     * The shading prefix added to this class's full name.
+     * Calculates the mangled shading prefix added to this class's full name.
+     *
+     * <p>This method mangles the package name as follows, so we can unmangle it back later:
+     * <ul>
+     *   <li>{@code _} to {@code _1}</li>
+     *   <li>{@code .} to {@code _}</li>
+     * </ul>
+     *
+     * <p>Note that we don't mangle non-ASCII characters here because it's extremely unlikely to have
+     * a non-ASCII character in a package name. For more information, see:
+     * <ul>
+     *   <li><a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html">JNI
+     *       specification</a></li>
+     *   <li>{@code parsePackagePrefix()} in {@code netty_jni_util.c}.</li>
+     * </ul>
      *
      * @throws UnsatisfiedLinkError if the shader used something other than a prefix
      */
-    private static String calculatePackagePrefix() {
+    private static String calculateMangledPackagePrefix() {
         String maybeShaded = NativeLibraryLoader.class.getName();
         // Use ! instead of . to avoid shading utilities from modifying the string
         String expected = "io!netty!util!internal!NativeLibraryLoader".replace('!', '.');
@@ -130,16 +144,17 @@ private static String calculatePackagePrefix() {
                     "Could not find prefix added to %s to get %s. When shading, only adding a "
                     + "package prefix is supported", expected, maybeShaded));
         }
-        return maybeShaded.substring(0, maybeShaded.length() - expected.length());
+        return maybeShaded.substring(0, maybeShaded.length() - expected.length())
+                          .replace("_", "_1")
+                          .replace('.', '_');
     }
 
     /**
      * Load the given library with the specified {@link ClassLoader}
      */
     public static void load(String originalName, ClassLoader loader) {
-        // Adjust expected name to support shading of native libraries.
-        String packagePrefix = calculatePackagePrefix().replace('.', '_');
-        String name = packagePrefix + originalName;
+        String mangledPackagePrefix = calculateMangledPackagePrefix();
+        String name = mangledPackagePrefix + originalName;
         List<Throwable> suppressed = new ArrayList<Throwable>();
         try {
             // first try to load from java.library.path
@@ -189,7 +204,7 @@ public static void load(String originalName, ClassLoader loader) {
             }
             out.flush();
 
-            if (shouldShadedLibraryIdBePatched(packagePrefix)) {
+            if (shouldShadedLibraryIdBePatched(mangledPackagePrefix)) {
                 // Let's try to patch the id and re-sign it. This is a best-effort and might fail if a
                 // SecurityManager is setup or the right executables are not installed :/
                 tryPatchShadedLibraryIdAndSign(tmpFile, originalName);
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index bf759fd3aff..ed134f51759 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -29,7 +29,11 @@ services:
 
   build:
     <<: *common
-    command: /bin/bash -cl "./mvnw -B -ntp clean install -Dio.netty.testsuite.badHost=netty.io -Dtcnative.classifier=linux-x86_64-fedora"
+    command: '/bin/bash -cl "
+      ./mvnw -B -ntp clean install -Dio.netty.testsuite.badHost=netty.io -Dtcnative.classifier=linux-x86_64-fedora && 
+      cd testsuite-shading &&
+      ../mvnw -B -ntp integration-test failsafe:verify -Dtcnative.classifier=linux-x86_64-fedora
+    "'
 
   deploy:
     <<: *common
diff --git a/pom.xml b/pom.xml
index 6453ed0545b..a27d57fbbcd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -621,7 +621,7 @@
       <dependency>
         <groupId>io.netty</groupId>
         <artifactId>netty-jni-util</artifactId>
-        <version>0.0.5.Final</version>
+        <version>0.0.6.Final</version>
         <classifier>sources</classifier>
         <optional>true</optional>
       </dependency>


Test patch:
diff --git a/testsuite-shading/pom.xml b/testsuite-shading/pom.xml
index e3582786560..65a650263c7 100644
--- a/testsuite-shading/pom.xml
+++ b/testsuite-shading/pom.xml
@@ -34,7 +34,9 @@
     <classesShadedDir>${project.build.directory}/classes-shaded</classesShadedDir>
     <classesShadedNativeDir>${classesShadedDir}/META-INF/native</classesShadedNativeDir>
     <shadingPrefix>shaded</shadingPrefix>
-    <shadingPrefix2>shaded2</shadingPrefix2>
+    <shadingPrefix2>shaded_2</shadingPrefix2>
+    <mangledShadingPrefix>shaded</mangledShadingPrefix>
+    <mangledShadingPrefix2>shaded_12</mangledShadingPrefix2>
     <skipShadingTestsuite>true</skipShadingTestsuite>
     <shadedPackagePrefix>io.netty.</shadedPackagePrefix>
     <japicmp.skip>true</japicmp.skip>
@@ -126,12 +128,12 @@
                     <include name="shaded2.jar" />
                   </fileset>
                 </unzip>
-                <copy file="${classesShadedNativeDir}/lib${nativeTransportLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix}_${nativeTransportLib}" />
-                <copy file="${classesShadedNativeDir}/lib${nativeTransportLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix2}_${nativeTransportLib}" />
+                <copy file="${classesShadedNativeDir}/lib${nativeTransportLib}" tofile="${classesShadedNativeDir}/lib${mangledShadingPrefix}_${nativeTransportLib}" />
+                <copy file="${classesShadedNativeDir}/lib${nativeTransportLib}" tofile="${classesShadedNativeDir}/lib${mangledShadingPrefix2}_${nativeTransportLib}" />
                 <delete file="${classesShadedNativeDir}/lib${nativeTransportLib}" />
 
-                <copy file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix}_${nativeTcnativeLib}" />
-                <copy file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" tofile="${classesShadedNativeDir}/lib${shadingPrefix2}_${nativeTcnativeLib}" />
+                <copy file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" tofile="${classesShadedNativeDir}/lib${mangledShadingPrefix}_${nativeTcnativeLib}" />
+                <copy file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" tofile="${classesShadedNativeDir}/lib${mangledShadingPrefix2}_${nativeTcnativeLib}" />
                 <delete file="${classesShadedNativeDir}/lib${nativeTcnativeLib}" />
 
                 <delete file="${project.build.directory}/shaded1.jar" />