Skip to content

Commit 80dc64b

Browse files
committed
DATACMNS-1376 - Fix illegal access warning in DefaultMethodInvokingMethodInterceptor on Java 9 and higher.
We now attempt to use private MethodHandles lookup as the first mechanism to resolve a MethodHandle for default interface methods and fall back to reflection-based Lookup construction if private lookup is not available. Reflective availability is checked lazily to prevent illegal access on enum constant construction. This approach prevents an illegal access which was logged by attempting a reflection-based lookup first. We also introduced a FALLBACK mechanism to split encapsulated access from a fallback mechanism.
1 parent 7f51a3e commit 80dc64b

File tree

2 files changed

+132
-36
lines changed

2 files changed

+132
-36
lines changed

src/main/java/org/springframework/data/projection/DefaultMethodInvokingMethodInterceptor.java

Lines changed: 78 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@
2121
import java.lang.invoke.MethodType;
2222
import java.lang.reflect.Constructor;
2323
import java.lang.reflect.Method;
24-
import java.util.Arrays;
24+
import java.lang.reflect.Modifier;
2525
import java.util.Map;
26-
import java.util.Optional;
2726

2827
import org.aopalliance.intercept.MethodInterceptor;
2928
import org.aopalliance.intercept.MethodInvocation;
3029
import org.springframework.aop.ProxyMethodInvocation;
30+
import org.springframework.data.util.Lazy;
3131
import org.springframework.lang.Nullable;
3232
import org.springframework.util.ConcurrentReferenceHashMap;
3333
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
@@ -85,13 +85,56 @@ private MethodHandle getMethodHandle(Method method) throws Exception {
8585
*/
8686
enum MethodHandleLookup {
8787

88+
/**
89+
* Encapsulated {@link MethodHandle} lookup working on Java 9.
90+
*/
91+
ENCAPSULATED {
92+
93+
private final @Nullable Method privateLookupIn = ReflectionUtils.findMethod(MethodHandles.class,
94+
"privateLookupIn", Class.class, Lookup.class);
95+
96+
/*
97+
* (non-Javadoc)
98+
* @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#lookup(java.lang.reflect.Method)
99+
*/
100+
@Override
101+
MethodHandle lookup(Method method) throws ReflectiveOperationException {
102+
103+
if (privateLookupIn == null) {
104+
throw new IllegalStateException("Could not obtain MethodHandles.privateLookupIn!");
105+
}
106+
107+
return doLookup(method, getLookup(method.getDeclaringClass(), privateLookupIn));
108+
}
109+
110+
/*
111+
* (non-Javadoc)
112+
* @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#isAvailable()
113+
*/
114+
@Override
115+
boolean isAvailable() {
116+
return privateLookupIn != null;
117+
}
118+
119+
private Lookup getLookup(Class<?> declaringClass, Method privateLookupIn) {
120+
121+
Lookup lookup = MethodHandles.lookup();
122+
123+
try {
124+
return (Lookup) privateLookupIn.invoke(MethodHandles.class, declaringClass, lookup);
125+
} catch (ReflectiveOperationException e) {
126+
return lookup;
127+
}
128+
}
129+
},
130+
88131
/**
89132
* Open (via reflection construction of {@link MethodHandles.Lookup}) method handle lookup. Works with Java 8 and
90133
* with Java 9 permitting illegal access.
91134
*/
92135
OPEN {
93136

94-
private final Optional<Constructor<Lookup>> constructor = getLookupConstructor();
137+
private final Lazy<Constructor<Lookup>> constructor = Lazy.of(MethodHandleLookup::getLookupConstructor);
95138

96139
/*
97140
* (non-Javadoc)
@@ -100,8 +143,11 @@ enum MethodHandleLookup {
100143
@Override
101144
MethodHandle lookup(Method method) throws ReflectiveOperationException {
102145

103-
Constructor<Lookup> constructor = this.constructor
104-
.orElseThrow(() -> new IllegalStateException("Could not obtain MethodHandles.lookup constructor"));
146+
if (!isAvailable()) {
147+
throw new IllegalStateException("Could not obtain MethodHandles.lookup constructor!");
148+
}
149+
150+
Constructor<Lookup> constructor = this.constructor.get();
105151

106152
return constructor.newInstance(method.getDeclaringClass()).unreflectSpecial(method, method.getDeclaringClass());
107153
}
@@ -112,29 +158,24 @@ MethodHandle lookup(Method method) throws ReflectiveOperationException {
112158
*/
113159
@Override
114160
boolean isAvailable() {
115-
return constructor.isPresent();
161+
return constructor.orElse(null) != null;
116162
}
117163
},
118164

119165
/**
120-
* Encapsulated {@link MethodHandle} lookup working on Java 9.
166+
* Fallback {@link MethodHandle} lookup using {@link MethodHandles#lookup() public lookup}.
167+
*
168+
* @since 2.1
121169
*/
122-
ENCAPSULATED {
123-
124-
private final @Nullable Method privateLookupIn = ReflectionUtils.findMethod(MethodHandles.class,
125-
"privateLookupIn", Class.class, Lookup.class);
170+
FALLBACK {
126171

127172
/*
128173
* (non-Javadoc)
129174
* @see org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup#lookup(java.lang.reflect.Method)
130175
*/
131176
@Override
132177
MethodHandle lookup(Method method) throws ReflectiveOperationException {
133-
134-
MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
135-
136-
return getLookup(method.getDeclaringClass(), privateLookupIn).findSpecial(method.getDeclaringClass(),
137-
method.getName(), methodType, method.getDeclaringClass());
178+
return doLookup(method, MethodHandles.lookup());
138179
}
139180

140181
/*
@@ -145,22 +186,19 @@ MethodHandle lookup(Method method) throws ReflectiveOperationException {
145186
boolean isAvailable() {
146187
return true;
147188
}
189+
};
148190

149-
private Lookup getLookup(Class<?> declaringClass, @Nullable Method privateLookupIn) {
191+
private static MethodHandle doLookup(Method method, Lookup lookup)
192+
throws NoSuchMethodException, IllegalAccessException {
150193

151-
if (privateLookupIn == null) {
152-
return MethodHandles.lookup();
153-
}
154-
155-
Lookup lookup = MethodHandles.lookup();
194+
MethodType methodType = MethodType.methodType(method.getReturnType(), method.getParameterTypes());
156195

157-
try {
158-
return (Lookup) privateLookupIn.invoke(MethodHandles.class, declaringClass, lookup);
159-
} catch (ReflectiveOperationException e) {
160-
return lookup;
161-
}
196+
if (Modifier.isStatic(method.getModifiers())) {
197+
return lookup.findStatic(method.getDeclaringClass(), method.getName(), methodType);
162198
}
163-
};
199+
200+
return lookup.findSpecial(method.getDeclaringClass(), method.getName(), methodType, method.getDeclaringClass());
201+
}
164202

165203
/**
166204
* Lookup a {@link MethodHandle} given {@link Method} to look up.
@@ -184,26 +222,30 @@ private Lookup getLookup(Class<?> declaringClass, @Nullable Method privateLookup
184222
*/
185223
public static MethodHandleLookup getMethodHandleLookup() {
186224

187-
return Arrays.stream(MethodHandleLookup.values()) //
188-
.filter(it -> it.isAvailable()) //
189-
.findFirst() //
190-
.orElseThrow(() -> new IllegalStateException("No MethodHandleLookup available!"));
225+
for (MethodHandleLookup it : MethodHandleLookup.values()) {
226+
227+
if (it.isAvailable()) {
228+
return it;
229+
}
230+
}
231+
232+
throw new IllegalStateException("No MethodHandleLookup available!");
191233
}
192234

193-
private static Optional<Constructor<Lookup>> getLookupConstructor() {
235+
@Nullable
236+
private static Constructor<Lookup> getLookupConstructor() {
194237

195238
try {
196239

197240
Constructor<Lookup> constructor = Lookup.class.getDeclaredConstructor(Class.class);
198241
ReflectionUtils.makeAccessible(constructor);
199242

200-
return Optional.of(constructor);
201-
243+
return constructor;
202244
} catch (Exception ex) {
203245

204246
// this is the signal that we are on Java 9 (encapsulated) and can't use the accessible constructor approach.
205247
if (ex.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
206-
return Optional.empty();
248+
return null;
207249
}
208250

209251
throw new IllegalStateException(ex);
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.projection;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.junit.Assume.*;
20+
21+
import org.junit.Test;
22+
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.MethodHandleLookup;
23+
import org.springframework.data.util.Version;
24+
25+
/**
26+
* Unit tests for {@link DefaultMethodInvokingMethodInterceptor}.
27+
*
28+
* @author Mark Paluch
29+
*/
30+
public class DefaultMethodInvokingMethodInterceptorUnitTests {
31+
32+
@Test // DATACMNS-1376
33+
public void shouldApplyEncapsulatedLookupOnJava9AndHigher() {
34+
35+
Version version = Version.parse(System.getProperty("java.version"));
36+
37+
assumeTrue(version.isGreaterThanOrEqualTo(Version.parse("9.0")));
38+
39+
assertThat(MethodHandleLookup.getMethodHandleLookup()).isEqualTo(MethodHandleLookup.ENCAPSULATED);
40+
assertThat(MethodHandleLookup.ENCAPSULATED.isAvailable()).isTrue();
41+
}
42+
43+
@Test // DATACMNS-1376
44+
public void shouldApplyOpenLookupOnJava8() {
45+
46+
Version version = Version.parse(System.getProperty("java.version"));
47+
48+
assumeTrue(version.isLessThan(Version.parse("1.8.9999")));
49+
50+
assertThat(MethodHandleLookup.getMethodHandleLookup()).isEqualTo(MethodHandleLookup.OPEN);
51+
assertThat(MethodHandleLookup.OPEN.isAvailable()).isTrue();
52+
assertThat(MethodHandleLookup.ENCAPSULATED.isAvailable()).isFalse();
53+
}
54+
}

0 commit comments

Comments
 (0)