1 /*
  2  * Copyright (c) 2021, 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 
 26 package jdk.internal.reflect;
 27 
 28 import jdk.internal.access.JavaLangInvokeAccess;
 29 import jdk.internal.access.SharedSecrets;
 30 import jdk.internal.misc.VM;
 31 import jdk.internal.vm.annotation.ForceInline;
 32 import jdk.internal.vm.annotation.Hidden;
 33 
 34 import java.lang.invoke.MethodHandle;
 35 import java.lang.invoke.MethodHandles;
 36 import java.lang.invoke.WrongMethodTypeException;
 37 import java.lang.reflect.InvocationTargetException;
 38 import java.lang.reflect.Method;
 39 import java.lang.reflect.Modifier;
 40 
 41 import static java.lang.invoke.MethodType.genericMethodType;
 42 import static jdk.internal.reflect.MethodHandleAccessorFactory.SPECIALIZED_PARAM_COUNT;
 43 import static jdk.internal.reflect.MethodHandleAccessorFactory.LazyStaticHolder.JLIA;
 44 
 45 class DirectMethodHandleAccessor extends MethodAccessorImpl {
 46     /**
 47      * Creates a MethodAccessorImpl for a non-native method.
 48      */
 49     static MethodAccessorImpl methodAccessor(Method method, MethodHandle target) {
 50         assert !Modifier.isNative(method.getModifiers());
 51 
 52         return new DirectMethodHandleAccessor(method, target, false);
 53     }
 54 
 55     /**
 56      * Creates MethodAccessorImpl for the adapter method for a caller-sensitive method.
 57      * The given target method handle is the adapter method with the additional caller class
 58      * parameter.
 59      */
 60     static MethodAccessorImpl callerSensitiveAdapter(Method original, MethodHandle target) {
 61         assert Reflection.isCallerSensitive(original);
 62 
 63         // for CSM adapter method with the additional caller class parameter
 64         // creates the adaptive method accessor only.
 65         return new DirectMethodHandleAccessor(original, target, true);
 66     }
 67 
 68     /**
 69      * Creates MethodAccessorImpl that invokes the given method via VM native reflection
 70      * support.  This is used for native methods.  It can be used for java methods
 71      * during early VM startup.
 72      */
 73     static MethodAccessorImpl nativeAccessor(Method method, boolean callerSensitive) {
 74         return callerSensitive ? new NativeAccessor(method, findCSMethodAdapter(method))
 75                                : new NativeAccessor(method);
 76     }
 77 
 78     private static final int PARAM_COUNT_MASK = 0x00FF;
 79     private static final int HAS_CALLER_PARAM_BIT = 0x0100;
 80     private static final int IS_STATIC_BIT = 0x0200;
 81     private static final int NONZERO_BIT = 0x8000_0000;
 82 
 83     private final Class<?> declaringClass;
 84     private final int paramCount;
 85     private final int flags;
 86     private final MethodHandle target;
 87 
 88     DirectMethodHandleAccessor(Method method, MethodHandle target, boolean hasCallerParameter) {
 89         this.declaringClass = method.getDeclaringClass();
 90         this.paramCount = method.getParameterCount();
 91         this.flags = (hasCallerParameter ? HAS_CALLER_PARAM_BIT : 0) |
 92                      (Modifier.isStatic(method.getModifiers()) ? IS_STATIC_BIT : 0);
 93         this.target = target;
 94     }
 95 
 96     @Override
 97     @ForceInline
 98     public Object invoke(Object obj, Object[] args) throws InvocationTargetException {
 99         if (!isStatic()) {
100             checkReceiver(obj);
101         }
102         checkArgumentCount(paramCount, args);
103         try {
104             return invokeImpl(obj, args);
105         } catch (ClassCastException | WrongMethodTypeException e) {
106             if (isIllegalArgument(e)) {
107                 // No cause in IAE to be consistent with the old behavior
108                 throw new IllegalArgumentException("argument type mismatch");
109             } else {
110                 throw new InvocationTargetException(e);
111             }
112         } catch (NullPointerException e) {
113             if (isIllegalArgument(e)) {
114                 throw new IllegalArgumentException(e);
115             } else {
116                 throw new InvocationTargetException(e);
117             }
118         } catch (Throwable e) {
119             throw new InvocationTargetException(e);
120         }
121     }
122 
123     @Override
124     @ForceInline
125     public Object invoke(Object obj, Object[] args, Class<?> caller) throws InvocationTargetException {
126         if (!isStatic()) {
127             checkReceiver(obj);
128         }
129         checkArgumentCount(paramCount, args);
130         try {
131             return invokeImpl(obj, args, caller);
132         } catch (ClassCastException | WrongMethodTypeException e) {
133             if (isIllegalArgument(e)) {
134                 // No cause in IAE to be consistent with the old behavior
135                 throw new IllegalArgumentException("argument type mismatch");
136             } else {
137                 throw new InvocationTargetException(e);
138             }
139         } catch (NullPointerException e) {
140             if (isIllegalArgument(e)) {
141                 throw new IllegalArgumentException(e);
142             } else {
143                 throw new InvocationTargetException(e);
144             }
145         } catch (Throwable e) {
146             throw new InvocationTargetException(e);
147         }
148     }
149 
150     @Hidden
151     @ForceInline
152     private Object invokeImpl(Object obj, Object[] args) throws Throwable {
153         return switch (paramCount) {
154             case 0 -> target.invokeExact(obj);
155             case 1 -> target.invokeExact(obj, args[0]);
156             case 2 -> target.invokeExact(obj, args[0], args[1]);
157             case 3 -> target.invokeExact(obj, args[0], args[1], args[2]);
158             default -> target.invokeExact(obj, args);
159         };
160     }
161 
162     @Hidden
163     @ForceInline
164     private Object invokeImpl(Object obj, Object[] args, Class<?> caller) throws Throwable {
165         if (hasCallerParameter()) {
166             // caller-sensitive method is invoked through method with caller parameter
167             return switch (paramCount) {
168                 case 0 -> target.invokeExact(obj, caller);
169                 case 1 -> target.invokeExact(obj, args[0], caller);
170                 case 2 -> target.invokeExact(obj, args[0], args[1], caller);
171                 case 3 -> target.invokeExact(obj, args[0], args[1], args[2], caller);
172                 default -> target.invokeExact(obj, args, caller);
173             };
174         } else {
175             // caller-sensitive method is invoked through a per-caller invoker while
176             // the target MH is always spreading the args
177             var invoker = JLIA.reflectiveInvoker(caller);
178             try {
179                 // invoke the target method handle via an invoker
180                 return invoker.invokeExact(target, obj, args);
181             } catch (IllegalArgumentException e) {
182                 throw new InvocationTargetException(e);
183             }
184         }
185     }
186 
187     private boolean isStatic() {
188         return (flags & IS_STATIC_BIT) == IS_STATIC_BIT;
189     }
190 
191     private boolean hasCallerParameter() {
192         return (flags & HAS_CALLER_PARAM_BIT) == HAS_CALLER_PARAM_BIT;
193     }
194 
195     private boolean isIllegalArgument(RuntimeException ex) {
196         return AccessorUtils.isIllegalArgument(DirectMethodHandleAccessor.class, ex);
197     }
198 
199     private void checkReceiver(Object o) {
200         // NOTE: will throw NullPointerException, as specified, if o is null
201         if (!declaringClass.isAssignableFrom(o.getClass())) {
202             throw new IllegalArgumentException("object is not an instance of declaring class");
203         }
204     }
205 
206     /**
207      * Invoke the method via native VM reflection
208      */
209     static class NativeAccessor extends MethodAccessorImpl {
210         private final Method method;
211         private final Method csmAdapter;
212         private final boolean callerSensitive;
213         NativeAccessor(Method method) {
214             assert !Reflection.isCallerSensitive(method);
215             this.method = method;
216             this.csmAdapter = null;
217             this.callerSensitive = false;
218         }
219 
220         NativeAccessor(Method method, Method csmAdapter) {
221             assert Reflection.isCallerSensitive(method);
222             this.method = method;
223             this.csmAdapter = csmAdapter;
224             this.callerSensitive = true;
225         }
226 
227         @Override
228         public Object invoke(Object obj, Object[] args) throws InvocationTargetException {
229             assert csmAdapter == null;
230             return invoke0(method, obj, args);
231         }
232 
233         @Override
234         public Object invoke(Object obj, Object[] args, Class<?> caller) throws InvocationTargetException {
235             assert callerSensitive;
236 
237             if (csmAdapter != null) {
238                 Object[] newArgs = new Object[csmAdapter.getParameterCount()];
239                 newArgs[0] = caller;
240                 if (args != null) {
241                     System.arraycopy(args, 0, newArgs, 1, args.length);
242                 }
243                 return invoke0(csmAdapter, obj, newArgs);
244             } else {
245                 assert VM.isJavaLangInvokeInited();
246                 try {
247                     return ReflectiveInvoker.invoke(methodAccessorInvoker(), caller, obj, args);
248                 } catch (InvocationTargetException|RuntimeException|Error e) {
249                     throw e;
250                 } catch (Throwable e) {
251                     throw new InternalError(e);
252                 }
253             }
254         }
255 
256         public Object invokeViaReflectiveInvoker(Object obj, Object[] args) throws InvocationTargetException {
257             return invoke0(method, obj, args);
258         }
259 
260         /*
261          * A method handle to invoke Reflective::Invoker
262          */
263         private MethodHandle maInvoker;
264         private MethodHandle methodAccessorInvoker() {
265             MethodHandle invoker = maInvoker;
266             if (invoker == null) {
267                 maInvoker = invoker = ReflectiveInvoker.bindTo(this);
268             }
269             return invoker;
270         }
271 
272         private static native Object invoke0(Method m, Object obj, Object[] args);
273 
274         static class ReflectiveInvoker {
275             /**
276              * Return a method handle for NativeAccessor::invoke bound to the given accessor object
277              */
278             static MethodHandle bindTo(NativeAccessor accessor) {
279                 return NATIVE_ACCESSOR_INVOKE.bindTo(accessor);
280             }
281 
282             /*
283              * When Method::invoke on a caller-sensitive method is to be invoked
284              * and no adapter method with an additional caller class argument is defined,
285              * the caller-sensitive method must be invoked via an invoker injected
286              * which has the following signature:
287              *     reflect_invoke_V(MethodHandle mh, Object target, Object[] args)
288              *
289              * The stack frames calling the method `csm` through reflection will
290              * look like this:
291              *     obj.csm(args)
292              *     NativeAccessor::invoke(obj, args)
293              *     InjectedInvoker::reflect_invoke_V(vamh, obj, args);
294              *     method::invoke(obj, args)
295              *     p.Foo::m
296              *
297              * An injected invoker class is a hidden class which has the same
298              * defining class loader, runtime package, and protection domain
299              * as the given caller class.
300              *
301              * The caller-sensitive method will call Reflection::getCallerClass
302              * to get the caller class.
303              */
304             static Object invoke(MethodHandle target, Class<?> caller, Object obj, Object[] args)
305                     throws InvocationTargetException
306             {
307                 var reflectInvoker = JLIA.reflectiveInvoker(caller);
308                 try {
309                     return reflectInvoker.invokeExact(target, obj, args);
310                 } catch (InvocationTargetException | RuntimeException | Error e) {
311                     throw e;
312                 } catch (Throwable e) {
313                     throw new InternalError(e);
314                 }
315             }
316 
317             static final JavaLangInvokeAccess JLIA;
318             static final MethodHandle NATIVE_ACCESSOR_INVOKE;
319             static {
320                 try {
321                     JLIA = SharedSecrets.getJavaLangInvokeAccess();
322                     NATIVE_ACCESSOR_INVOKE = MethodHandles.lookup().findVirtual(NativeAccessor.class, "invoke",
323                             genericMethodType(1, true));
324                 } catch (NoSuchMethodException|IllegalAccessException e) {
325                     throw new InternalError(e);
326                 }
327             }
328         }
329     }
330 
331     private static void checkArgumentCount(int paramCount, Object[] args) {
332         // only check argument count for specialized forms
333         if (paramCount > SPECIALIZED_PARAM_COUNT) return;
334 
335         int argc = args != null ? args.length : 0;
336         if (argc != paramCount) {
337             throw new IllegalArgumentException("wrong number of arguments: " + argc + " expected: " + paramCount);
338         }
339     }
340 
341     /**
342      * Returns an adapter for caller-sensitive method if present.
343      * Otherwise, null.
344      *
345      * A trusted method can define an adapter method for a caller-sensitive method `foo`
346      * with an additional caller class argument that will be invoked reflectively.
347      */
348     private static Method findCSMethodAdapter(Method method) {
349         if (!Reflection.isCallerSensitive(method)) return null;
350 
351         int paramCount = method.getParameterCount();
352         Class<?>[] ptypes = new Class<?>[paramCount+1];
353         ptypes[paramCount] = Class.class;
354         System.arraycopy(method.getParameterTypes(), 0, ptypes, 0, paramCount);
355         try {
356             return method.getDeclaringClass().getDeclaredMethod(method.getName(), ptypes);
357         } catch (NoSuchMethodException ex) {
358             return null;
359         }
360     }
361 }