1 /*
   2  * Copyright (c) 2001, 2006, 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 sun.reflect;
  27 
  28 import java.lang.reflect.*;
  29 import java.util.Collections;
  30 import java.util.HashMap;
  31 import java.util.Map;
  32 
  33 /** Common utility routines used by both java.lang and
  34     java.lang.reflect */
  35 
  36 public class Reflection {
  37 
  38     /** Used to filter out fields and methods from certain classes from public
  39         view, where they are sensitive or they may contain VM-internal objects.
  40         These Maps are updated very rarely. Rather than synchronize on
  41         each access, we use copy-on-write */
  42     private static volatile Map<Class,String[]> fieldFilterMap;
  43     private static volatile Map<Class,String[]> methodFilterMap;
  44 
  45     static {
  46         Map<Class,String[]> map = new HashMap<Class,String[]>();
  47         map.put(Reflection.class,
  48             new String[] {"fieldFilterMap", "methodFilterMap"});
  49         map.put(System.class, new String[] {"security"});
  50         fieldFilterMap = map;
  51 
  52         methodFilterMap = new HashMap<Class,String[]>();
  53     }
  54 
  55     /** Returns the class of the caller of the method calling this method,
  56         ignoring frames associated with java.lang.reflect.Method.invoke()
  57         and its implementation. */
  58     @CallerSensitive
  59     public static native Class getCallerClass();
  60 
  61     /**
  62      * @deprecated No replacement.  This method will be removed in the next
  63      * JDK 7 update release.
  64      */
  65     @Deprecated
  66     @CallerSensitive
  67     public static Class getCallerClass(int depth) {
  68         if (sun.misc.VM.allowGetCallerClass()) {
  69             return getCallerClass0(depth+1);
  70         }
  71         throw new UnsupportedOperationException("This method is in the sun.* namespace. " +
  72              "Such methods are not a supported, public interface. " +
  73              "As a temporary measure, the 7u40 release notes describe " +
  74              "a mechanism to reenable the historical functionality of " +
  75              "this method.  However, this method will be removed without " +
  76              "further warning in a subsequent 7 update release.  " +
  77              "Update code properly using this method to allow the code " +
  78              "to work on future 7 update releases.");
  79     }
  80 
  81     // If the VM enforces getting caller class with @CallerSensitive,
  82     // this will fail anyway.
  83     @CallerSensitive
  84     private static native Class getCallerClass0(int depth);
  85 
  86     /** Retrieves the access flags written to the class file. For
  87         inner classes these flags may differ from those returned by
  88         Class.getModifiers(), which searches the InnerClasses
  89         attribute to find the source-level access flags. This is used
  90         instead of Class.getModifiers() for run-time access checks due
  91         to compatibility reasons; see 4471811. Only the values of the
  92         low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be
  93         valid. */
  94     private static native int getClassAccessFlags(Class c);
  95 
  96     /** A quick "fast-path" check to try to avoid getCallerClass()
  97         calls. */
  98     public static boolean quickCheckMemberAccess(Class memberClass,
  99                                                  int modifiers)
 100     {
 101         return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers);
 102     }
 103 
 104     public static void ensureMemberAccess(Class currentClass,
 105                                           Class memberClass,
 106                                           Object target,
 107                                           int modifiers)
 108         throws IllegalAccessException
 109     {
 110         if (currentClass == null || memberClass == null) {
 111             throw new InternalError();
 112         }
 113 
 114         if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) {
 115             throw new IllegalAccessException("Class " + currentClass.getName() +
 116                                              " can not access a member of class " +
 117                                              memberClass.getName() +
 118                                              " with modifiers \"" +
 119                                              Modifier.toString(modifiers) +
 120                                              "\"");
 121         }
 122     }
 123 
 124     public static boolean verifyMemberAccess(Class currentClass,
 125                                              // Declaring class of field
 126                                              // or method
 127                                              Class  memberClass,
 128                                              // May be NULL in case of statics
 129                                              Object target,
 130                                              int    modifiers)
 131     {
 132         // Verify that currentClass can access a field, method, or
 133         // constructor of memberClass, where that member's access bits are
 134         // "modifiers".
 135 
 136         boolean gotIsSameClassPackage = false;
 137         boolean isSameClassPackage = false;
 138 
 139         if (currentClass == memberClass) {
 140             // Always succeeds
 141             return true;
 142         }
 143 
 144         if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
 145             isSameClassPackage = isSameClassPackage(currentClass, memberClass);
 146             gotIsSameClassPackage = true;
 147             if (!isSameClassPackage) {
 148                 return false;
 149             }
 150         }
 151 
 152         // At this point we know that currentClass can access memberClass.
 153 
 154         if (Modifier.isPublic(modifiers)) {
 155             return true;
 156         }
 157 
 158         boolean successSoFar = false;
 159 
 160         if (Modifier.isProtected(modifiers)) {
 161             // See if currentClass is a subclass of memberClass
 162             if (isSubclassOf(currentClass, memberClass)) {
 163                 successSoFar = true;
 164             }
 165         }
 166 
 167         if (!successSoFar && !Modifier.isPrivate(modifiers)) {
 168             if (!gotIsSameClassPackage) {
 169                 isSameClassPackage = isSameClassPackage(currentClass,
 170                                                         memberClass);
 171                 gotIsSameClassPackage = true;
 172             }
 173 
 174             if (isSameClassPackage) {
 175                 successSoFar = true;
 176             }
 177         }
 178 
 179         if (!successSoFar) {
 180             return false;
 181         }
 182 
 183         if (Modifier.isProtected(modifiers)) {
 184             // Additional test for protected members: JLS 6.6.2
 185             Class targetClass = (target == null ? memberClass : target.getClass());
 186             if (targetClass != currentClass) {
 187                 if (!gotIsSameClassPackage) {
 188                     isSameClassPackage = isSameClassPackage(currentClass, memberClass);
 189                     gotIsSameClassPackage = true;
 190                 }
 191                 if (!isSameClassPackage) {
 192                     if (!isSubclassOf(targetClass, currentClass)) {
 193                         return false;
 194                     }
 195                 }
 196             }
 197         }
 198 
 199         return true;
 200     }
 201 
 202     private static boolean isSameClassPackage(Class c1, Class c2) {
 203         return isSameClassPackage(c1.getClassLoader(), c1.getName(),
 204                                   c2.getClassLoader(), c2.getName());
 205     }
 206 
 207     /** Returns true if two classes are in the same package; classloader
 208         and classname information is enough to determine a class's package */
 209     private static boolean isSameClassPackage(ClassLoader loader1, String name1,
 210                                               ClassLoader loader2, String name2)
 211     {
 212         if (loader1 != loader2) {
 213             return false;
 214         } else {
 215             int lastDot1 = name1.lastIndexOf('.');
 216             int lastDot2 = name2.lastIndexOf('.');
 217             if ((lastDot1 == -1) || (lastDot2 == -1)) {
 218                 // One of the two doesn't have a package.  Only return true
 219                 // if the other one also doesn't have a package.
 220                 return (lastDot1 == lastDot2);
 221             } else {
 222                 int idx1 = 0;
 223                 int idx2 = 0;
 224 
 225                 // Skip over '['s
 226                 if (name1.charAt(idx1) == '[') {
 227                     do {
 228                         idx1++;
 229                     } while (name1.charAt(idx1) == '[');
 230                     if (name1.charAt(idx1) != 'L') {
 231                         // Something is terribly wrong.  Shouldn't be here.
 232                         throw new InternalError("Illegal class name " + name1);
 233                     }
 234                 }
 235                 if (name2.charAt(idx2) == '[') {
 236                     do {
 237                         idx2++;
 238                     } while (name2.charAt(idx2) == '[');
 239                     if (name2.charAt(idx2) != 'L') {
 240                         // Something is terribly wrong.  Shouldn't be here.
 241                         throw new InternalError("Illegal class name " + name2);
 242                     }
 243                 }
 244 
 245                 // Check that package part is identical
 246                 int length1 = lastDot1 - idx1;
 247                 int length2 = lastDot2 - idx2;
 248 
 249                 if (length1 != length2) {
 250                     return false;
 251                 }
 252                 return name1.regionMatches(false, idx1, name2, idx2, length1);
 253             }
 254         }
 255     }
 256 
 257     static boolean isSubclassOf(Class queryClass,
 258                                 Class ofClass)
 259     {
 260         while (queryClass != null) {
 261             if (queryClass == ofClass) {
 262                 return true;
 263             }
 264             queryClass = queryClass.getSuperclass();
 265         }
 266         return false;
 267     }
 268 
 269     // fieldNames must contain only interned Strings
 270     public static synchronized void registerFieldsToFilter(Class containingClass,
 271                                               String ... fieldNames) {
 272         fieldFilterMap =
 273             registerFilter(fieldFilterMap, containingClass, fieldNames);
 274     }
 275 
 276     // methodNames must contain only interned Strings
 277     public static synchronized void registerMethodsToFilter(Class containingClass,
 278                                               String ... methodNames) {
 279         methodFilterMap =
 280             registerFilter(methodFilterMap, containingClass, methodNames);
 281     }
 282 
 283     private static Map<Class,String[]> registerFilter(Map<Class,String[]> map,
 284             Class containingClass, String ... names) {
 285         if (map.get(containingClass) != null) {
 286             throw new IllegalArgumentException
 287                             ("Filter already registered: " + containingClass);
 288         }
 289         map = new HashMap<Class,String[]>(map);
 290         map.put(containingClass, names);
 291         return map;
 292     }
 293 
 294     public static Field[] filterFields(Class containingClass,
 295                                        Field[] fields) {
 296         if (fieldFilterMap == null) {
 297             // Bootstrapping
 298             return fields;
 299         }
 300         return (Field[])filter(fields, fieldFilterMap.get(containingClass));
 301     }
 302 
 303     public static Method[] filterMethods(Class containingClass, Method[] methods) {
 304         if (methodFilterMap == null) {
 305             // Bootstrapping
 306             return methods;
 307         }
 308         return (Method[])filter(methods, methodFilterMap.get(containingClass));
 309     }
 310 
 311     private static Member[] filter(Member[] members, String[] filteredNames) {
 312         if ((filteredNames == null) || (members.length == 0)) {
 313             return members;
 314         }
 315         int numNewMembers = 0;
 316         for (Member member : members) {
 317             boolean shouldSkip = false;
 318             for (String filteredName : filteredNames) {
 319                 if (member.getName() == filteredName) {
 320                     shouldSkip = true;
 321                     break;
 322                 }
 323             }
 324             if (!shouldSkip) {
 325                 ++numNewMembers;
 326             }
 327         }
 328         Member[] newMembers =
 329             (Member[])Array.newInstance(members[0].getClass(), numNewMembers);
 330         int destIdx = 0;
 331         for (Member member : members) {
 332             boolean shouldSkip = false;
 333             for (String filteredName : filteredNames) {
 334                 if (member.getName() == filteredName) {
 335                     shouldSkip = true;
 336                     break;
 337                 }
 338             }
 339             if (!shouldSkip) {
 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 extension class loader.
 349      */
 350     public static boolean isCallerSensitive(Method m) {
 351         final ClassLoader loader = m.getDeclaringClass().getClassLoader();
 352         if (loader == null || isExtClassLoader(loader))  {
 353             return m.isAnnotationPresent(CallerSensitive.class);
 354         }
 355         return false;
 356     }
 357 
 358     private static boolean isExtClassLoader(ClassLoader loader) {
 359         ClassLoader cl = ClassLoader.getSystemClassLoader();
 360         while (cl != null) {
 361             if (cl.getParent() == null && cl == loader) {
 362                 return true;
 363             }
 364             cl = cl.getParent();
 365         }
 366         return false;
 367     }
 368 }