From f0c76cacc3f6eb3a7df3fdc06059d5ddaac3c442 Mon Sep 17 00:00:00 2001 From: jaymode Date: Tue, 23 Jan 2018 14:28:25 -0700 Subject: [PATCH] Replace reflective access of Throwable#addSuppressed with version guarded access Motivation: In environments with a security manager, the reflective access to get the reference to Throwable#addSuppressed can cause issues that result in Netty failing to load. The main motivation in this pull request is to remove the use of reflection to prevent issues in these environments. Modifications: ThrowableUtil no longer uses Class#getDeclaredMembers to get the Method that references Throwable#addSuppressed and instead guards the call to Throwable#addSuppressed with a Java version check. Additionally, a annotation was added that suppresses the animal sniffer java16 signature check on the given method. The benefit of the annotation is that it limits the exclusion of Throwable to just the ThrowableUtil class and has string text indicating the reason for suppressing the java16 signature check. Result: Netty no longer requires the use of Class#getDeclaredMethod for ThrowableUtil and will work in environments restricted by a security manager without needing to grant reflection permissions. Fixes #7614 --- .../internal/SuppressJava6Requirement.java | 32 +++++++++++++++++++ .../io/netty/util/internal/ThrowableUtil.java | 27 ++-------------- .../internal/NativeLibraryLoaderTest.java | 23 +++---------- pom.xml | 3 ++ 4 files changed, 43 insertions(+), 42 deletions(-) create mode 100644 common/src/main/java/io/netty/util/internal/SuppressJava6Requirement.java diff --git a/common/src/main/java/io/netty/util/internal/SuppressJava6Requirement.java b/common/src/main/java/io/netty/util/internal/SuppressJava6Requirement.java new file mode 100644 index 0000000000..557d0095a6 --- /dev/null +++ b/common/src/main/java/io/netty/util/internal/SuppressJava6Requirement.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to suppress the Java 6 source code requirement checks for a method. + */ +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD }) +public @interface SuppressJava6Requirement { + + String reason(); +} diff --git a/common/src/main/java/io/netty/util/internal/ThrowableUtil.java b/common/src/main/java/io/netty/util/internal/ThrowableUtil.java index 4c1cf8fdd5..de336b7e3c 100644 --- a/common/src/main/java/io/netty/util/internal/ThrowableUtil.java +++ b/common/src/main/java/io/netty/util/internal/ThrowableUtil.java @@ -18,26 +18,10 @@ package io.netty.util.internal; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; public final class ThrowableUtil { - private static final Method addSupressedMethod = getAddSuppressed(); - - private static Method getAddSuppressed() { - if (PlatformDependent.javaVersion() < 7) { - return null; - } - try { - // addSuppressed is final, so we only need to look it up on Throwable. - return Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - private ThrowableUtil() { } /** @@ -71,20 +55,15 @@ public final class ThrowableUtil { } public static boolean haveSuppressed() { - return addSupressedMethod != null; + return PlatformDependent.javaVersion() >= 7; } + @SuppressJava6Requirement(reason = "Throwable addSuppressed is only available for >= 7. Has check for < 7.") public static void addSuppressed(Throwable target, Throwable suppressed) { if (!haveSuppressed()) { return; } - try { - addSupressedMethod.invoke(target, suppressed); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); - } + target.addSuppressed(suppressed); } public static void addSuppressedAndClear(Throwable target, List suppressed) { diff --git a/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java b/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java index 0a40fb85b5..de73b9ecf5 100644 --- a/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java +++ b/common/src/test/java/io/netty/util/internal/NativeLibraryLoaderTest.java @@ -17,7 +17,6 @@ package io.netty.util.internal; import org.junit.Test; -import java.lang.reflect.Method; import java.io.FileNotFoundException; import java.util.UUID; @@ -26,8 +25,6 @@ import static org.junit.Assert.fail; public class NativeLibraryLoaderTest { - private static final Method getSupressedMethod = getGetSuppressed(); - @Test public void testFileNotFound() { try { @@ -35,7 +32,7 @@ public class NativeLibraryLoaderTest { fail(); } catch (UnsatisfiedLinkError error) { assertTrue(error.getCause() instanceof FileNotFoundException); - if (getSupressedMethod != null) { + if (PlatformDependent.javaVersion() >= 7) { verifySuppressedException(error, UnsatisfiedLinkError.class); } } @@ -48,34 +45,24 @@ public class NativeLibraryLoaderTest { fail(); } catch (UnsatisfiedLinkError error) { assertTrue(error.getCause() instanceof FileNotFoundException); - if (getSupressedMethod != null) { + if (PlatformDependent.javaVersion() >= 7) { verifySuppressedException(error, ClassNotFoundException.class); } } } + @SuppressJava6Requirement(reason = "uses Java 7+ Throwable#getSuppressed but is guarded by version checks") private static void verifySuppressedException(UnsatisfiedLinkError error, Class expectedSuppressedExceptionClass) { try { - Throwable[] suppressed = (Throwable[]) getSupressedMethod.invoke(error.getCause()); + Throwable[] suppressed = error.getCause().getSuppressed(); assertTrue(suppressed.length == 1); assertTrue(suppressed[0] instanceof UnsatisfiedLinkError); - suppressed = (Throwable[]) getSupressedMethod.invoke(suppressed[0]); + suppressed = (suppressed[0]).getSuppressed(); assertTrue(suppressed.length == 1); assertTrue(expectedSuppressedExceptionClass.isInstance(suppressed[0])); } catch (Exception e) { throw new RuntimeException(e); } } - - private static Method getGetSuppressed() { - if (PlatformDependent.javaVersion() < 7) { - return null; - } - try { - return Throwable.class.getDeclaredMethod("getSuppressed"); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - } } diff --git a/pom.xml b/pom.xml index 9d217cce3c..a4936d8610 100644 --- a/pom.xml +++ b/pom.xml @@ -721,6 +721,9 @@ java.nio.file.Path java.io.File + + io.netty.util.internal.SuppressJava6Requirement +