Skip to content

Commit

Permalink
Replace reflective access of Throwable#addSuppressed with version gua…
Browse files Browse the repository at this point in the history
…rded 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 netty#7614
  • Loading branch information
jaymode authored and normanmaurer committed Jan 25, 2018
1 parent b640797 commit f0c76ca
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -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();
}
27 changes: 3 additions & 24 deletions common/src/main/java/io/netty/util/internal/ThrowableUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,10 @@
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() { }

/**
Expand Down Expand Up @@ -71,20 +55,15 @@ public static String stackTraceToString(Throwable cause) {
}

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<Throwable> suppressed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import org.junit.Test;

import java.lang.reflect.Method;
import java.io.FileNotFoundException;
import java.util.UUID;

Expand All @@ -26,16 +25,14 @@

public class NativeLibraryLoaderTest {

private static final Method getSupressedMethod = getGetSuppressed();

@Test
public void testFileNotFound() {
try {
NativeLibraryLoader.load(UUID.randomUUID().toString(), NativeLibraryLoaderTest.class.getClassLoader());
fail();
} catch (UnsatisfiedLinkError error) {
assertTrue(error.getCause() instanceof FileNotFoundException);
if (getSupressedMethod != null) {
if (PlatformDependent.javaVersion() >= 7) {
verifySuppressedException(error, UnsatisfiedLinkError.class);
}
}
Expand All @@ -48,34 +45,24 @@ public void testFileNotFoundWithNullClassLoader() {
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);
}
}
}
3 changes: 3 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,9 @@
<ignore>java.nio.file.Path</ignore>
<ignore>java.io.File</ignore>
</ignores>
<annotations>
<annotation>io.netty.util.internal.SuppressJava6Requirement</annotation>
</annotations>
</configuration>
<executions>
<execution>
Expand Down

0 comments on commit f0c76ca

Please sign in to comment.