1 /*
2 * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package java.lang.invoke;
26
27 import sun.invoke.util.Wrapper;
28
29 import java.lang.reflect.Modifier;
30
31 import static java.lang.invoke.MethodHandleInfo.*;
32 import static sun.invoke.util.Wrapper.forPrimitiveType;
33 import static sun.invoke.util.Wrapper.forWrapperType;
34 import static sun.invoke.util.Wrapper.isWrapperType;
35
36 /**
37 * Abstract implementation of a lambda metafactory which provides parameter
38 * unrolling and input validation.
39 *
40 * @see LambdaMetafactory
41 */
42 /* package */ abstract class AbstractValidatingLambdaMetafactory {
43
44 /*
45 * For context, the comments for the following fields are marked in quotes
46 * with their values, given this program:
47 * interface II<T> { Object foo(T x); }
48 * interface JJ<R extends Number> extends II<R> { }
49 * class CC { String impl(int i) { return "impl:"+i; }}
50 * class X {
51 * public static void main(String[] args) {
52 * JJ<Integer> iii = (new CC())::impl;
53 * System.out.printf(">>> %s\n", iii.foo(44));
54 * }}
55 */
56 final MethodHandles.Lookup caller; // The caller's lookup context
57 final Class<?> targetClass; // The class calling the meta-factory via invokedynamic "class X"
58 final MethodType factoryType; // The type of the invoked method "(CC)II"
59 final Class<?> interfaceClass; // The type of the returned instance "interface JJ"
60 final String interfaceMethodName; // Name of the method to implement "foo"
61 final MethodType interfaceMethodType; // Type of the method to implement "(Object)Object"
62 final MethodHandle implementation; // Raw method handle for the implementation method
63 final MethodType implMethodType; // Type of the implementation MethodHandle "(CC,int)String"
64 final MethodHandleInfo implInfo; // Info about the implementation method handle "MethodHandleInfo[5 CC.impl(int)String]"
65 final int implKind; // Invocation kind for implementation "5"=invokevirtual
66 final boolean implIsInstanceMethod; // Is the implementation an instance method "true"
67 final Class<?> implClass; // Class for referencing the implementation method "class CC"
68 final MethodType dynamicMethodType; // Dynamically checked method type "(Integer)Object"
69 final boolean isSerializable; // Should the returned instance be serializable
70 final Class<?>[] altInterfaces; // Additional interfaces to be implemented
71 final MethodType[] altMethods; // Signatures of additional methods to bridge
72
73
74 /**
75 * Meta-factory constructor.
76 *
77 * @param caller Stacked automatically by VM; represents a lookup context
78 * with the accessibility privileges of the caller.
79 * @param factoryType Stacked automatically by VM; the signature of the
80 * invoked method, which includes the expected static
81 * type of the returned lambda object, and the static
82 * types of the captured arguments for the lambda. In
83 * the event that the implementation method is an
84 * instance method, the first argument in the invocation
85 * signature will correspond to the receiver.
86 * @param interfaceMethodName Name of the method in the functional interface to
87 * which the lambda or method reference is being
88 * converted, represented as a String.
89 * @param interfaceMethodType Type of the method in the functional interface to
90 * which the lambda or method reference is being
91 * converted, represented as a MethodType.
92 * @param implementation The implementation method which should be called
93 * (with suitable adaptation of argument types, return
94 * types, and adjustment for captured arguments) when
95 * methods of the resulting functional interface instance
96 * are invoked.
97 * @param dynamicMethodType The signature of the primary functional
98 * interface method after type variables are
99 * substituted with their instantiation from
100 * the capture site
101 * @param isSerializable Should the lambda be made serializable? If set,
102 * either the target type or one of the additional SAM
103 * types must extend {@code Serializable}.
104 * @param altInterfaces Additional interfaces which the lambda object
105 * should implement.
106 * @param altMethods Method types for additional signatures to be
107 * implemented by invoking the implementation method
108 * @throws LambdaConversionException If any of the meta-factory protocol
109 * invariants are violated
110 */
111 AbstractValidatingLambdaMetafactory(MethodHandles.Lookup caller,
112 MethodType factoryType,
113 String interfaceMethodName,
114 MethodType interfaceMethodType,
115 MethodHandle implementation,
116 MethodType dynamicMethodType,
117 boolean isSerializable,
118 Class<?>[] altInterfaces,
119 MethodType[] altMethods)
120 throws LambdaConversionException {
121 if (!caller.hasFullPrivilegeAccess()) {
122 throw new LambdaConversionException(String.format(
123 "Invalid caller: %s",
124 caller.lookupClass().getName()));
125 }
126 this.caller = caller;
127 this.targetClass = caller.lookupClass();
128 this.factoryType = factoryType;
129
130 this.interfaceClass = factoryType.returnType();
131
132 this.interfaceMethodName = interfaceMethodName;
133 this.interfaceMethodType = interfaceMethodType;
134
135 this.implementation = implementation;
136 this.implMethodType = implementation.type();
137 try {
138 this.implInfo = caller.revealDirect(implementation);
139 } catch (IllegalArgumentException e) {
140 throw new LambdaConversionException(implementation + " is not direct or cannot be cracked");
141 }
142 switch (implInfo.getReferenceKind()) {
143 case REF_invokeVirtual:
144 case REF_invokeInterface:
145 this.implClass = implMethodType.parameterType(0);
146 // reference kind reported by implInfo may not match implMethodType's first param
147 // Example: implMethodType is (Cloneable)String, implInfo is for Object.toString
148 this.implKind = implClass.isInterface() ? REF_invokeInterface : REF_invokeVirtual;
149 this.implIsInstanceMethod = true;
150 break;
151 case REF_invokeSpecial:
152 // JDK-8172817: should use referenced class here, but we don't know what it was
153 this.implClass = implInfo.getDeclaringClass();
154 this.implIsInstanceMethod = true;
155
156 // Classes compiled prior to dynamic nestmate support invoke a private instance
157 // method with REF_invokeSpecial. Newer classes use REF_invokeVirtual or
158 // REF_invokeInterface, and we can use that instruction in the lambda class.
159 if (targetClass == implClass && Modifier.isPrivate(implInfo.getModifiers())) {
160 this.implKind = implClass.isInterface() ? REF_invokeInterface : REF_invokeVirtual;
161 } else {
162 this.implKind = REF_invokeSpecial;
163 }
164 break;
165 case REF_invokeStatic:
166 case REF_newInvokeSpecial:
167 // JDK-8172817: should use referenced class here for invokestatic, but we don't know what it was
168 this.implClass = implInfo.getDeclaringClass();
169 this.implKind = implInfo.getReferenceKind();
170 this.implIsInstanceMethod = false;
171 break;
172 default:
173 throw new LambdaConversionException(String.format("Unsupported MethodHandle kind: %s", implInfo));
174 }
175
176 this.dynamicMethodType = dynamicMethodType;
177 this.isSerializable = isSerializable;
178 this.altInterfaces = altInterfaces;
179 this.altMethods = altMethods;
180
181 if (interfaceMethodName.isEmpty() ||
182 interfaceMethodName.indexOf('.') >= 0 ||
183 interfaceMethodName.indexOf(';') >= 0 ||
184 interfaceMethodName.indexOf('[') >= 0 ||
185 interfaceMethodName.indexOf('/') >= 0 ||
186 interfaceMethodName.indexOf('<') >= 0 ||
187 interfaceMethodName.indexOf('>') >= 0) {
188 throw new LambdaConversionException(String.format(
189 "Method name '%s' is not legal",
190 interfaceMethodName));
191 }
192
193 if (!interfaceClass.isInterface()) {
194 throw new LambdaConversionException(String.format(
195 "%s is not an interface",
196 interfaceClass.getName()));
197 }
198
199 for (Class<?> c : altInterfaces) {
200 if (!c.isInterface()) {
201 throw new LambdaConversionException(String.format(
202 "%s is not an interface",
203 c.getName()));
204 }
205 }
206 }
207
208 /**
209 * Build the CallSite.
210 *
211 * @return a CallSite, which, when invoked, will return an instance of the
212 * functional interface
213 * @throws LambdaConversionException
214 */
215 abstract CallSite buildCallSite()
216 throws LambdaConversionException;
217
218 /**
219 * Check the meta-factory arguments for errors
220 * @throws LambdaConversionException if there are improper conversions
221 */
222 void validateMetafactoryArgs() throws LambdaConversionException {
223 // Check arity: captured + SAM == impl
224 final int implArity = implMethodType.parameterCount();
225 final int capturedArity = factoryType.parameterCount();
226 final int samArity = interfaceMethodType.parameterCount();
227 final int dynamicArity = dynamicMethodType.parameterCount();
228 if (implArity != capturedArity + samArity) {
229 throw new LambdaConversionException(
230 String.format("Incorrect number of parameters for %s method %s; %d captured parameters, %d functional interface method parameters, %d implementation parameters",
231 implIsInstanceMethod ? "instance" : "static", implInfo,
232 capturedArity, samArity, implArity));
233 }
234 if (dynamicArity != samArity) {
235 throw new LambdaConversionException(
236 String.format("Incorrect number of parameters for %s method %s; %d dynamic parameters, %d functional interface method parameters",
237 implIsInstanceMethod ? "instance" : "static", implInfo,
238 dynamicArity, samArity));
239 }
240 for (MethodType bridgeMT : altMethods) {
241 if (bridgeMT.parameterCount() != samArity) {
242 throw new LambdaConversionException(
243 String.format("Incorrect number of parameters for bridge signature %s; incompatible with %s",
244 bridgeMT, interfaceMethodType));
245 }
246 }
247
248 // If instance: first captured arg (receiver) must be subtype of class where impl method is defined
249 final int capturedStart; // index of first non-receiver capture parameter in implMethodType
250 final int samStart; // index of first non-receiver sam parameter in implMethodType
251 if (implIsInstanceMethod) {
252 final Class<?> receiverClass;
253
254 // implementation is an instance method, adjust for receiver in captured variables / SAM arguments
255 if (capturedArity == 0) {
256 // receiver is function parameter
257 capturedStart = 0;
258 samStart = 1;
259 receiverClass = dynamicMethodType.parameterType(0);
260 } else {
261 // receiver is a captured variable
262 capturedStart = 1;
263 samStart = capturedArity;
264 receiverClass = factoryType.parameterType(0);
265 }
266
267 // check receiver type
268 if (!implClass.isAssignableFrom(receiverClass)) {
269 throw new LambdaConversionException(
270 String.format("Invalid receiver type %s; not a subtype of implementation type %s",
271 receiverClass, implClass));
272 }
273 } else {
274 // no receiver
275 capturedStart = 0;
276 samStart = capturedArity;
277 }
278
279 // Check for exact match on non-receiver captured arguments
280 for (int i=capturedStart; i<capturedArity; i++) {
281 Class<?> implParamType = implMethodType.parameterType(i);
282 Class<?> capturedParamType = factoryType.parameterType(i);
283 if (!capturedParamType.equals(implParamType)) {
284 throw new LambdaConversionException(
285 String.format("Type mismatch in captured lambda parameter %d: expecting %s, found %s",
286 i, capturedParamType, implParamType));
287 }
288 }
289 // Check for adaptation match on non-receiver SAM arguments
290 for (int i=samStart; i<implArity; i++) {
291 Class<?> implParamType = implMethodType.parameterType(i);
292 Class<?> dynamicParamType = dynamicMethodType.parameterType(i - capturedArity);
293 if (!isAdaptableTo(dynamicParamType, implParamType, true)) {
294 throw new LambdaConversionException(
295 String.format("Type mismatch for lambda argument %d: %s is not convertible to %s",
296 i, dynamicParamType, implParamType));
297 }
298 }
299
300 // Adaptation match: return type
301 Class<?> expectedType = dynamicMethodType.returnType();
302 Class<?> actualReturnType = implMethodType.returnType();
303 if (!isAdaptableToAsReturn(actualReturnType, expectedType)) {
304 throw new LambdaConversionException(
305 String.format("Type mismatch for lambda return: %s is not convertible to %s",
306 actualReturnType, expectedType));
307 }
308
309 // Check descriptors of generated methods
310 checkDescriptor(interfaceMethodType);
311 for (MethodType bridgeMT : altMethods) {
312 checkDescriptor(bridgeMT);
313 }
314 }
315
316 /** Validate that the given descriptor's types are compatible with {@code dynamicMethodType} **/
317 private void checkDescriptor(MethodType descriptor) throws LambdaConversionException {
318 for (int i = 0; i < dynamicMethodType.parameterCount(); i++) {
319 Class<?> dynamicParamType = dynamicMethodType.parameterType(i);
320 Class<?> descriptorParamType = descriptor.parameterType(i);
321 if (!descriptorParamType.isAssignableFrom(dynamicParamType)) {
322 String msg = String.format("Type mismatch for dynamic parameter %d: %s is not a subtype of %s",
323 i, dynamicParamType, descriptorParamType);
324 throw new LambdaConversionException(msg);
325 }
326 }
327
328 Class<?> dynamicReturnType = dynamicMethodType.returnType();
329 Class<?> descriptorReturnType = descriptor.returnType();
330 if (!isAdaptableToAsReturnStrict(dynamicReturnType, descriptorReturnType)) {
331 String msg = String.format("Type mismatch for lambda expected return: %s is not convertible to %s",
332 dynamicReturnType, descriptorReturnType);
333 throw new LambdaConversionException(msg);
334 }
335 }
336
337 /**
338 * Check type adaptability for parameter types.
339 * @param fromType Type to convert from
340 * @param toType Type to convert to
341 * @param strict If true, do strict checks, else allow that fromType may be parameterized
342 * @return True if 'fromType' can be passed to an argument of 'toType'
343 */
344 private boolean isAdaptableTo(Class<?> fromType, Class<?> toType, boolean strict) {
345 if (fromType.equals(toType)) {
346 return true;
347 }
348 if (fromType.isPrimitive()) {
349 Wrapper wfrom = forPrimitiveType(fromType);
350 if (toType.isPrimitive()) {
351 // both are primitive: widening
352 Wrapper wto = forPrimitiveType(toType);
353 return wto.isConvertibleFrom(wfrom);
354 } else {
355 // from primitive to reference: boxing
356 return toType.isAssignableFrom(wfrom.wrapperType());
357 }
358 } else {
359 if (toType.isPrimitive()) {
360 // from reference to primitive: unboxing
361 Wrapper wfrom;
362 if (isWrapperType(fromType) && (wfrom = forWrapperType(fromType)).primitiveType().isPrimitive()) {
363 // fromType is a primitive wrapper; unbox+widen
364 Wrapper wto = forPrimitiveType(toType);
365 return wto.isConvertibleFrom(wfrom);
366 } else {
367 // must be convertible to primitive
368 return !strict;
369 }
370 } else {
371 // both are reference types: fromType should be a superclass of toType.
372 return !strict || toType.isAssignableFrom(fromType);
373 }
374 }
375 }
376
377 /**
378 * Check type adaptability for return types --
379 * special handling of void type) and parameterized fromType
380 * @return True if 'fromType' can be converted to 'toType'
381 */
382 private boolean isAdaptableToAsReturn(Class<?> fromType, Class<?> toType) {
383 return toType.equals(void.class)
384 || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false);
385 }
386 private boolean isAdaptableToAsReturnStrict(Class<?> fromType, Class<?> toType) {
387 if (fromType.equals(void.class) || toType.equals(void.class)) return fromType.equals(toType);
388 else return isAdaptableTo(fromType, toType, true);
389 }
390
391
392 /*********** Logging support -- for debugging only, uncomment as needed
393 static final Executor logPool = Executors.newSingleThreadExecutor();
394 protected static void log(final String s) {
395 MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() {
396 @Override
397 public void run() {
398 System.out.println(s);
399 }
400 });
401 }
402
403 protected static void log(final String s, final Throwable e) {
404 MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() {
405 @Override
406 public void run() {
407 System.out.println(s);
408 e.printStackTrace(System.out);
409 }
410 });
411 }
412 ***********************/
413
414 }