1 /*
  2  * Copyright (c) 2001, 2025, 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 java.lang.reflect.*;
 29 import java.util.HashMap;
 30 import java.util.Map;
 31 import java.util.Objects;
 32 import java.util.Set;
 33 
 34 import jdk.internal.access.JavaLangAccess;
 35 import jdk.internal.access.SharedSecrets;
 36 import jdk.internal.misc.VM;
 37 import jdk.internal.vm.annotation.ForceInline;
 38 import jdk.internal.vm.annotation.IntrinsicCandidate;
 39 
 40 /** Common utility routines used by both java.lang and
 41     java.lang.reflect */
 42 
 43 public class Reflection {
 44 
 45     /** Used to filter out fields and methods from certain classes from public
 46         view, where they are sensitive or they may contain VM-internal objects.
 47         These Maps are updated very rarely. Rather than synchronize on
 48         each access, we use copy-on-write */
 49     private static volatile Map<Class<?>, Set<String>> fieldFilterMap;
 50     private static volatile Map<Class<?>, Set<String>> methodFilterMap;
 51     private static final String WILDCARD = "*";
 52     public static final Set<String> ALL_MEMBERS = Set.of(WILDCARD);
 53 
 54     static {
 55         fieldFilterMap = Map.of(
 56             Reflection.class, ALL_MEMBERS,
 57             AccessibleObject.class, ALL_MEMBERS,
 58             Class.class, Set.of("classLoader", "classData", "modifiers", "protectionDomain", "primitive"),
 59             ClassLoader.class, ALL_MEMBERS,
 60             Constructor.class, ALL_MEMBERS,
 61             Field.class, ALL_MEMBERS,
 62             Method.class, ALL_MEMBERS,
 63             Module.class, ALL_MEMBERS
 64         );
 65         methodFilterMap = Map.of();
 66     }
 67 
 68     /** Returns the class of the caller of the method calling this method,
 69         ignoring frames associated with java.lang.reflect.Method.invoke()
 70         and its implementation. */
 71     @CallerSensitive
 72     @IntrinsicCandidate
 73     public static native Class<?> getCallerClass();
 74 
 75     /** Retrieves the access flags written to the class file. For
 76         inner classes these flags may differ from those returned by
 77         Class.getModifiers(), which searches the InnerClasses
 78         attribute to find the source-level access flags. This is used
 79         instead of Class.getModifiers() for run-time access checks due
 80         to compatibility reasons; see 4471811. Only the values of the
 81         low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be
 82         valid. */
 83     public static int getClassAccessFlags(Class<?> c) {
 84         return SharedSecrets.getJavaLangAccess().getClassFileAccessFlags(c);
 85     }
 86 
 87 
 88     /**
 89      * Ensures that access to a member is granted and throws
 90      * IllegalAccessException if not.
 91      *
 92      * @param currentClass the class performing the access
 93      * @param memberClass the declaring class of the member being accessed
 94      * @param targetClass the class of target object if accessing instance
 95      *                    field or method;
 96      *                    or the declaring class if accessing constructor;
 97      *                    or null if accessing static field or method
 98      * @param modifiers the member's access modifiers
 99      * @throws IllegalAccessException if access to member is denied
100      */
101     public static void ensureMemberAccess(Class<?> currentClass,
102                                           Class<?> memberClass,
103                                           Class<?> targetClass,
104                                           int modifiers)
105         throws IllegalAccessException
106     {
107         if (!verifyMemberAccess(currentClass, memberClass, targetClass, modifiers)) {
108             throw newIllegalAccessException(currentClass, memberClass, targetClass, modifiers);
109         }
110     }
111 
112     @ForceInline
113     public static void ensureNativeAccess(Class<?> currentClass, Class<?> owner, String methodName, boolean jni) {
114         // if there is no caller class, act as if the call came from unnamed module of system class loader
115         Module module = currentClass != null ?
116                 currentClass.getModule() :
117                 ClassLoader.getSystemClassLoader().getUnnamedModule();
118         class Holder {
119             static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
120         }
121         if (module != null) {
122             // not in init phase
123             Holder.JLA.ensureNativeAccess(module, owner, methodName, currentClass, jni);
124         }
125     }
126 
127     /**
128      * Verify access to a member and return {@code true} if it is granted.
129      *
130      * @param currentClass the class performing the access
131      * @param memberClass the declaring class of the member being accessed
132      * @param targetClass the class of target object if accessing instance
133      *                    field or method;
134      *                    or the declaring class if accessing constructor;
135      *                    or null if accessing static field or method
136      * @param modifiers the member's access modifiers
137      * @return {@code true} if access to member is granted
138      */
139     public static boolean verifyMemberAccess(Class<?> currentClass,
140                                              Class<?> memberClass,
141                                              Class<?> targetClass,
142                                              int modifiers)
143     {
144         Objects.requireNonNull(currentClass);
145         Objects.requireNonNull(memberClass);
146 
147         if (currentClass == memberClass) {
148             // Always succeeds
149             return true;
150         }
151 
152         if (!verifyModuleAccess(currentClass.getModule(), memberClass)) {
153             return false;
154         }
155 
156         boolean gotIsSameClassPackage = false;
157         boolean isSameClassPackage = false;
158 
159         if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
160             isSameClassPackage = isSameClassPackage(currentClass, memberClass);
161             gotIsSameClassPackage = true;
162             if (!isSameClassPackage) {
163                 return false;
164             }
165         }
166 
167         // At this point we know that currentClass can access memberClass.
168 
169         if (Modifier.isPublic(modifiers)) {
170             return true;
171         }
172 
173         // Check for nestmate access if member is private
174         if (Modifier.isPrivate(modifiers)) {
175             // Note: targetClass may be outside the nest, but that is okay
176             //       as long as memberClass is in the nest.
177             if (areNestMates(currentClass, memberClass)) {
178                 return true;
179             }
180         }
181 
182         boolean successSoFar = false;
183 
184         if (Modifier.isProtected(modifiers)) {
185             // See if currentClass is a subclass of memberClass
186             if (isSubclassOf(currentClass, memberClass)) {
187                 successSoFar = true;
188             }
189         }
190 
191         if (!successSoFar && !Modifier.isPrivate(modifiers)) {
192             if (!gotIsSameClassPackage) {
193                 isSameClassPackage = isSameClassPackage(currentClass,
194                                                         memberClass);
195                 gotIsSameClassPackage = true;
196             }
197 
198             if (isSameClassPackage) {
199                 successSoFar = true;
200             }
201         }
202 
203         if (!successSoFar) {
204             return false;
205         }
206 
207         // Additional test for protected instance members
208         // and protected constructors: JLS 6.6.2
209         if (targetClass != null && Modifier.isProtected(modifiers) &&
210             targetClass != currentClass)
211         {
212             if (!gotIsSameClassPackage) {
213                 isSameClassPackage = isSameClassPackage(currentClass, memberClass);
214                 gotIsSameClassPackage = true;
215             }
216             if (!isSameClassPackage) {
217                 if (!isSubclassOf(targetClass, currentClass)) {
218                     return false;
219                 }
220             }
221         }
222 
223         return true;
224     }
225 
226     /*
227      * Verify if a member is public and memberClass is a public type
228      * in a package that is unconditionally exported and
229      * return {@code true} if it is granted.
230      *
231      * @param memberClass the declaring class of the member being accessed
232      * @param modifiers the member's access modifiers
233      * @return {@code true} if the member is public and in a publicly accessible type
234      */
235     public static boolean verifyPublicMemberAccess(Class<?> memberClass, int modifiers) {
236         Module m = memberClass.getModule();
237         return Modifier.isPublic(modifiers)
238             && m.isExported(memberClass.getPackageName())
239             && Modifier.isPublic(Reflection.getClassAccessFlags(memberClass));
240     }
241 
242     /**
243      * Returns {@code true} if memberClass's module exports memberClass's
244      * package to currentModule.
245      */
246     public static boolean verifyModuleAccess(Module currentModule, Class<?> memberClass) {
247         Module memberModule = memberClass.getModule();
248         if (currentModule == memberModule) {
249             // same module (named or unnamed) or both null if called
250             // before module system is initialized, which means we are
251             // dealing with java.base only.
252             return true;
253         } else {
254             String pkg = memberClass.getPackageName();
255             return memberModule.isExported(pkg, currentModule);
256         }
257     }
258 
259     /**
260      * Returns true if two classes in the same package.
261      */
262     private static boolean isSameClassPackage(Class<?> c1, Class<?> c2) {
263         if (c1.getClassLoader() != c2.getClassLoader())
264             return false;
265         return Objects.equals(c1.getPackageName(), c2.getPackageName());
266     }
267 
268     static boolean isSubclassOf(Class<?> queryClass,
269                                 Class<?> ofClass)
270     {
271         while (queryClass != null) {
272             if (queryClass == ofClass) {
273                 return true;
274             }
275             queryClass = queryClass.getSuperclass();
276         }
277         return false;
278     }
279 
280     // fieldNames must contain only interned Strings
281     public static synchronized void registerFieldsToFilter(Class<?> containingClass,
282                                                            Set<String> fieldNames) {
283         fieldFilterMap =
284             registerFilter(fieldFilterMap, containingClass, fieldNames);
285     }
286 
287     // methodNames must contain only interned Strings
288     public static synchronized void registerMethodsToFilter(Class<?> containingClass,
289                                                             Set<String> methodNames) {
290         methodFilterMap =
291             registerFilter(methodFilterMap, containingClass, methodNames);
292     }
293 
294     private static Map<Class<?>, Set<String>> registerFilter(Map<Class<?>, Set<String>> map,
295                                                              Class<?> containingClass,
296                                                              Set<String> names) {
297         if (map.get(containingClass) != null) {
298             throw new IllegalArgumentException
299                             ("Filter already registered: " + containingClass);
300         }
301         map = new HashMap<>(map);
302         map.put(containingClass, Set.copyOf(names));
303         return map;
304     }
305 
306     public static Field[] filterFields(Class<?> containingClass, Field[] fields) {
307         if (fieldFilterMap == null) {
308             // Bootstrapping
309             return fields;
310         }
311         return (Field[])filter(fields, fieldFilterMap.get(containingClass));
312     }
313 
314     public static Method[] filterMethods(Class<?> containingClass, Method[] methods) {
315         if (methodFilterMap == null) {
316             // Bootstrapping
317             return methods;
318         }
319         return (Method[])filter(methods, methodFilterMap.get(containingClass));
320     }
321 
322     private static Member[] filter(Member[] members, Set<String> filteredNames) {
323         if ((filteredNames == null) || (members.length == 0)) {
324             return members;
325         }
326         Class<?> memberType = members[0].getClass();
327         if (filteredNames.contains(WILDCARD)) {
328             return (Member[]) Array.newInstance(memberType, 0);
329         }
330         int numNewMembers = 0;
331         for (Member member : members) {
332             if (!filteredNames.contains(member.getName())) {
333                 ++numNewMembers;
334             }
335         }
336         Member[] newMembers = (Member[])Array.newInstance(memberType, numNewMembers);
337         int destIdx = 0;
338         for (Member member : members) {
339             if (!filteredNames.contains(member.getName())) {
340                 newMembers[destIdx++] = member;
341             }
342         }
343         return newMembers;
344     }
345 
346     /**
347      * Tests if the given method is caller-sensitive and the declaring class
348      * is defined by either the bootstrap class loader or platform class loader.
349      */
350     public static boolean isCallerSensitive(Method m) {
351         final ClassLoader loader = m.getDeclaringClass().getClassLoader();
352         if (VM.isSystemDomainLoader(loader)) {
353             return m.isAnnotationPresent(CallerSensitive.class);
354         }
355         return false;
356     }
357 
358     /*
359      * Tests if the given Field is a trusted final field and it cannot be
360      * modified reflectively regardless of the value of its accessible flag.
361      */
362     public static boolean isTrustedFinalField(Field field) {
363         return SharedSecrets.getJavaLangReflectAccess().isTrustedFinalField(field);
364     }
365 
366     /**
367      * Returns an IllegalAccessException with an exception message based on
368      * the access that is denied.
369      */
370     public static IllegalAccessException newIllegalAccessException(Class<?> currentClass,
371                                                                    Class<?> memberClass,
372                                                                    Class<?> targetClass,
373                                                                    int modifiers)
374     {
375         if (currentClass == null)
376             return newIllegalAccessException(memberClass, modifiers);
377 
378         String currentSuffix = "";
379         String memberSuffix = "";
380         Module m1 = currentClass.getModule();
381         if (m1.isNamed())
382             currentSuffix = " (in " + m1 + ")";
383         Module m2 = memberClass.getModule();
384         if (m2.isNamed())
385             memberSuffix = " (in " + m2 + ")";
386 
387         String memberPackageName = memberClass.getPackageName();
388 
389         String msg = currentClass + currentSuffix + " cannot access ";
390         if (m2.isExported(memberPackageName, m1)) {
391             // module access okay so include the modifiers in the message
392             msg += "a member of " + memberClass + memberSuffix + msgSuffix(modifiers);
393         } else {
394             // module access failed
395             msg += memberClass + memberSuffix+ " because "
396                    + m2 + " does not export " + memberPackageName;
397             if (m2.isNamed()) msg += " to " + m1;
398         }
399 
400         return new IllegalAccessException(msg);
401     }
402 
403     /**
404      * Returns an IllegalAccessException with an exception message where
405      * there is no caller frame.
406      */
407     private static IllegalAccessException newIllegalAccessException(Class<?> memberClass,
408                                                                     int modifiers)
409     {
410         String memberSuffix = "";
411         Module m2 = memberClass.getModule();
412         if (m2.isNamed())
413             memberSuffix = " (in " + m2 + ")";
414 
415         String memberPackageName = memberClass.getPackageName();
416 
417         String msg = "JNI attached native thread (null caller frame) cannot access ";
418         if (m2.isExported(memberPackageName)) {
419             // module access okay so include the modifiers in the message
420             msg += "a member of " + memberClass + memberSuffix + msgSuffix(modifiers);
421         } else {
422             // module access failed
423             msg += memberClass + memberSuffix+ " because "
424                 + m2 + " does not export " + memberPackageName;
425         }
426 
427         return new IllegalAccessException(msg);
428     }
429 
430     private static String msgSuffix(int modifiers) {
431         boolean packageAccess =
432             ((Modifier.PRIVATE |
433               Modifier.PROTECTED |
434               Modifier.PUBLIC) & modifiers) == 0;
435         return packageAccess ?
436             " with package access" :
437             " with modifiers \"" + Modifier.toString(modifiers) + "\"";
438     }
439 
440     /**
441      * Returns true if {@code currentClass} and {@code memberClass}
442      * are nestmates - that is, if they have the same nesthost as
443      * determined by the VM.
444      */
445     public static native boolean areNestMates(Class<?> currentClass,
446                                               Class<?> memberClass);
447 }