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
This commit is contained in:
parent
b640797de1
commit
f0c76cacc3
@ -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();
|
||||||
|
}
|
@ -18,26 +18,10 @@ package io.netty.util.internal;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class ThrowableUtil {
|
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() { }
|
private ThrowableUtil() { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,20 +55,15 @@ public final class ThrowableUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean haveSuppressed() {
|
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) {
|
public static void addSuppressed(Throwable target, Throwable suppressed) {
|
||||||
if (!haveSuppressed()) {
|
if (!haveSuppressed()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
target.addSuppressed(suppressed);
|
||||||
addSupressedMethod.invoke(target, suppressed);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addSuppressedAndClear(Throwable target, List<Throwable> suppressed) {
|
public static void addSuppressedAndClear(Throwable target, List<Throwable> suppressed) {
|
||||||
|
@ -17,7 +17,6 @@ package io.netty.util.internal;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -26,8 +25,6 @@ import static org.junit.Assert.fail;
|
|||||||
|
|
||||||
public class NativeLibraryLoaderTest {
|
public class NativeLibraryLoaderTest {
|
||||||
|
|
||||||
private static final Method getSupressedMethod = getGetSuppressed();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFileNotFound() {
|
public void testFileNotFound() {
|
||||||
try {
|
try {
|
||||||
@ -35,7 +32,7 @@ public class NativeLibraryLoaderTest {
|
|||||||
fail();
|
fail();
|
||||||
} catch (UnsatisfiedLinkError error) {
|
} catch (UnsatisfiedLinkError error) {
|
||||||
assertTrue(error.getCause() instanceof FileNotFoundException);
|
assertTrue(error.getCause() instanceof FileNotFoundException);
|
||||||
if (getSupressedMethod != null) {
|
if (PlatformDependent.javaVersion() >= 7) {
|
||||||
verifySuppressedException(error, UnsatisfiedLinkError.class);
|
verifySuppressedException(error, UnsatisfiedLinkError.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,34 +45,24 @@ public class NativeLibraryLoaderTest {
|
|||||||
fail();
|
fail();
|
||||||
} catch (UnsatisfiedLinkError error) {
|
} catch (UnsatisfiedLinkError error) {
|
||||||
assertTrue(error.getCause() instanceof FileNotFoundException);
|
assertTrue(error.getCause() instanceof FileNotFoundException);
|
||||||
if (getSupressedMethod != null) {
|
if (PlatformDependent.javaVersion() >= 7) {
|
||||||
verifySuppressedException(error, ClassNotFoundException.class);
|
verifySuppressedException(error, ClassNotFoundException.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressJava6Requirement(reason = "uses Java 7+ Throwable#getSuppressed but is guarded by version checks")
|
||||||
private static void verifySuppressedException(UnsatisfiedLinkError error,
|
private static void verifySuppressedException(UnsatisfiedLinkError error,
|
||||||
Class<?> expectedSuppressedExceptionClass) {
|
Class<?> expectedSuppressedExceptionClass) {
|
||||||
try {
|
try {
|
||||||
Throwable[] suppressed = (Throwable[]) getSupressedMethod.invoke(error.getCause());
|
Throwable[] suppressed = error.getCause().getSuppressed();
|
||||||
assertTrue(suppressed.length == 1);
|
assertTrue(suppressed.length == 1);
|
||||||
assertTrue(suppressed[0] instanceof UnsatisfiedLinkError);
|
assertTrue(suppressed[0] instanceof UnsatisfiedLinkError);
|
||||||
suppressed = (Throwable[]) getSupressedMethod.invoke(suppressed[0]);
|
suppressed = (suppressed[0]).getSuppressed();
|
||||||
assertTrue(suppressed.length == 1);
|
assertTrue(suppressed.length == 1);
|
||||||
assertTrue(expectedSuppressedExceptionClass.isInstance(suppressed[0]));
|
assertTrue(expectedSuppressedExceptionClass.isInstance(suppressed[0]));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
3
pom.xml
3
pom.xml
@ -721,6 +721,9 @@
|
|||||||
<ignore>java.nio.file.Path</ignore>
|
<ignore>java.nio.file.Path</ignore>
|
||||||
<ignore>java.io.File</ignore>
|
<ignore>java.io.File</ignore>
|
||||||
</ignores>
|
</ignores>
|
||||||
|
<annotations>
|
||||||
|
<annotation>io.netty.util.internal.SuppressJava6Requirement</annotation>
|
||||||
|
</annotations>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
Loading…
Reference in New Issue
Block a user