Skip to content

Commit 6b23112

Browse files
committed
[LANG-1685] reflectionToString falls back to toString if reflective access is not permitted.
1 parent 8b6fdad commit 6b23112

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,12 @@ public String toString() {
849849
validate();
850850

851851
Class<?> clazz = getObject().getClass();
852+
853+
if (!reflectiveAccess.isAllowed(clazz)) {
854+
appendToString(getStyle().getContentStart() + getObject().toString() + getStyle().getContentEnd());
855+
return super.toString();
856+
}
857+
852858
appendFieldsIn(clazz);
853859
while (clazz.getSuperclass() != null && clazz != getUpToClass()) {
854860
clazz = clazz.getSuperclass();
@@ -857,6 +863,8 @@ public String toString() {
857863
return super.toString();
858864
}
859865

866+
ReflectiveAccessUtil reflectiveAccess = new ReflectiveAccessUtil();
867+
860868
/**
861869
* Validates that include and exclude names do not intersect.
862870
*/
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.lang3.builder;
18+
19+
import java.lang.reflect.InvocationTargetException;
20+
import java.lang.reflect.Method;
21+
22+
/**
23+
* Utility class to determine whether reflective access to a target module is permitted from the current module.
24+
* This class assumes all reflective accesses are allowed in JVM where JPMS is not supported (i.e. JDK8 or earlier).
25+
*/
26+
class ReflectiveAccessUtil {
27+
28+
private static Method initializeGetModuleMethod() {
29+
try {
30+
return Class.class.getMethod("getModule");
31+
} catch (NoSuchMethodException e) {
32+
return null;
33+
}
34+
}
35+
36+
private static Method initializeIsOpenMethod() {
37+
if (getModule == null) {
38+
return null;
39+
}
40+
try {
41+
final Object module = getModule.invoke(ReflectiveAccessUtil.class);
42+
final Class<?> moduleClass = module.getClass();
43+
return moduleClass.getMethod("isOpen", String.class, moduleClass);
44+
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
45+
return null;
46+
}
47+
}
48+
49+
private static final Method getModule = initializeGetModuleMethod();
50+
private static final Method isOpen = initializeIsOpenMethod();
51+
52+
/**
53+
* Determines whether {@code targetClass} is accessible via reflection from the module that defines this class.
54+
* On JVMs that do not support the Java Platform Module System, the method always returns {@code true}.
55+
* Otherwise, it checks whether the module that declares {@code targetClass} is open to the module containing
56+
* this class at runtime.
57+
*
58+
* @param targetClass the {@link Class} object to inspect.
59+
* @return {@code true} if {@code targetClass} can be reflected on from this module,
60+
* {@code false} otherwise.
61+
*/
62+
boolean isAllowed(Class<?> targetClass) {
63+
if (!isJpmsSupported()) {
64+
return true;
65+
}
66+
if (targetClass.isArray()) {
67+
return true;
68+
}
69+
try {
70+
final Object targetModule = getModule.invoke(targetClass);
71+
final Object selfModule = getModule.invoke(getClass());
72+
return (Boolean) isOpen.invoke(targetModule, targetClass.getPackage().getName(), selfModule);
73+
} catch (IllegalAccessException | InvocationTargetException e) {
74+
return false;
75+
}
76+
}
77+
78+
/**
79+
* Returns {@code }true} if the current JVM supports JPMS.
80+
*/
81+
boolean isJpmsSupported() {
82+
return getModule != null && isOpen != null;
83+
}
84+
}

src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.apache.commons.lang3.builder;
1818

1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
2021

2122
import org.apache.commons.lang3.AbstractLangTest;
2223
import org.junit.jupiter.api.Test;
@@ -31,4 +32,48 @@ void testConstructorWithNullObject() {
3132
assertEquals("<null>", new ReflectionToStringBuilder(null, ToStringStyle.DEFAULT_STYLE, new StringBuffer()).toString());
3233
}
3334

35+
@Test
36+
void testFallsBackToCallingToStringMethodIfReflectiveAccessNotAllowed() {
37+
final String data = "string-value";
38+
final ReflectionToStringBuilder builder = new ReflectionToStringBuilder(data, ToStringStyle.DEFAULT_STYLE, new StringBuffer());
39+
builder.reflectiveAccess = new ReflectiveAccessUtil() {
40+
@Override
41+
boolean isAllowed(Class<?> targetClass) {
42+
return false;
43+
}
44+
};
45+
assertEquals(String.format("java.lang.String@%s[%s]", Integer.toHexString(System.identityHashCode(data)), data), builder.toString());
46+
}
47+
48+
@Test
49+
void testUsesReflectionToGetFieldsIfReflectiveAccessAllowed() {
50+
final String data = "string-value";
51+
final ReflectionToStringBuilder builder = new ReflectionToStringBuilder(data, ToStringStyle.DEFAULT_STYLE, new StringBuffer());
52+
builder.reflectiveAccess = new ReflectiveAccessUtil() {
53+
@Override
54+
boolean isAllowed(Class<?> targetClass) {
55+
return true;
56+
}
57+
};
58+
final String result = builder.toString();
59+
assertTrue(result.startsWith(String.format("java.lang.String@%s[", Integer.toHexString(System.identityHashCode(data)))));
60+
assertTrue(result.contains(String.format("hash=%s", data.hashCode())));
61+
assertTrue(result.endsWith("]"));
62+
}
63+
64+
@Test
65+
void testUsesReflectionToGetFieldsIfJpmsNotSupported() {
66+
final String data = "string-value";
67+
final ReflectionToStringBuilder builder = new ReflectionToStringBuilder(data, ToStringStyle.DEFAULT_STYLE, new StringBuffer());
68+
builder.reflectiveAccess = new ReflectiveAccessUtil() {
69+
@Override
70+
boolean isJpmsSupported() {
71+
return false;
72+
}
73+
};
74+
final String result = builder.toString();
75+
assertTrue(result.startsWith(String.format("java.lang.String@%s[", Integer.toHexString(System.identityHashCode(data)))));
76+
assertTrue(result.contains(String.format("hash=%s", data.hashCode())));
77+
assertTrue(result.endsWith("]"));
78+
}
3479
}

0 commit comments

Comments
 (0)