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         return getCallerClass0(depth);
  69     }
  70 
  71     // If the VM enforces getting caller class with @CallerSensitive,
  72     // this will fail anyway.
  73     @CallerSensitive
  74     private static native Class getCallerClass0(int depth);
  75 
  76     /** Retrieves the access flags written to the class file. For
  77         inner classes these flags may differ from those returned by
  78         Class.getModifiers(), which searches the InnerClasses
  79         attribute to find the source-level access flags. This is used
  80         instead of Class.getModifiers() for run-time access checks due
  81         to compatibility reasons; see 4471811. Only the values of the
  82         low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be
  83         valid. */
  84     private static native int getClassAccessFlags(Class c);
  85 
  86     /** A quick "fast-path" check to try to avoid getCallerClass()
  87         calls. */
  88     public static boolean quickCheckMemberAccess(Class memberClass,
  89                                                  int modifiers)
  90     {
  91         return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers);
  92     }
  93 
  94     public static void ensureMemberAccess(Class currentClass,
  95                                           Class memberClass,
  96                                           Object target,
  97                                           int modifiers)
  98         throws IllegalAccessException
  99     {
 100         if (currentClass == null || memberClass == null) {
 101             throw new InternalError();
 102         }
 103 
 104         if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) {
 105             throw new IllegalAccessException("Class " + currentClass.getName() +
 106                                              " can not access a member of class " +
 107                                              memberClass.getName() +
 108                                              " with modifiers \"" +
 109                                              Modifier.toString(modifiers) +
 110                                              "\"");
 111         }
 112     }
 113 
 114     public static boolean verifyMemberAccess(Class currentClass,
 115                                              // Declaring class of field
 116                                              // or method
 117                                              Class  memberClass,
 118                                              // May be NULL in case of statics
 119                                              Object target,
 120                                              int    modifiers)
 121     {
 122         // Verify that currentClass can access a field, method, or
 123         // constructor of memberClass, where that member's access bits are
 124         // "modifiers".
 125 
 126         boolean gotIsSameClassPackage = false;
 127         boolean isSameClassPackage = false;
 128 
 129         if (currentClass == memberClass) {
 130             // Always succeeds
 131             return true;
 132         }
 133 
 134         if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
 135             isSameClassPackage = isSameClassPackage(currentClass, memberClass);
 136             gotIsSameClassPackage = true;
 137             if (!isSameClassPackage) {
 138                 return false;
 139             }
 140         }
 141 
 142         // At this point we know that currentClass can access memberClass.
 143 
 144         if (Modifier.isPublic(modifiers)) {
 145             return true;
 146         }
 147 
 148         boolean successSoFar = false;
 149 
 150         if (Modifier.isProtected(modifiers)) {
 151             // See if currentClass is a subclass of memberClass
 152             if (isSubclassOf(currentClass, memberClass)) {
 153                 successSoFar = true;
 154             }
 155         }
 156 
 157         if (!successSoFar && !Modifier.isPrivate(modifiers)) {
 158             if (!gotIsSameClassPackage) {
 159                 isSameClassPackage = isSameClassPackage(currentClass,
 160                                                         memberClass);
 161                 gotIsSameClassPackage = true;
 162             }
 163 
 164             if (isSameClassPackage) {
 165                 successSoFar = true;
 166             }
 167         }
 168 
 169         if (!successSoFar) {
 170             return false;
 171         }
 172 
 173         if (Modifier.isProtected(modifiers)) {
 174             // Additional test for protected members: JLS 6.6.2
 175             Class targetClass = (target == null ? memberClass : target.getClass());
 176             if (targetClass != currentClass) {
 177                 if (!gotIsSameClassPackage) {
 178                     isSameClassPackage = isSameClassPackage(currentClass, memberClass);
 179                     gotIsSameClassPackage = true;
 180                 }
 181                 if (!isSameClassPackage) {
 182                     if (!isSubclassOf(targetClass, currentClass)) {
 183                         return false;
 184                     }
 185                 }
 186             }
 187         }
 188 
 189         return true;
 190     }
 191 
 192     private static boolean isSameClassPackage(Class c1, Class c2) {
 193         return isSameClassPackage(c1.getClassLoader(), c1.getName(),
 194                                   c2.getClassLoader(), c2.getName());
 195     }
 196 
 197     /** Returns true if two classes are in the same package; classloader
 198         and classname information is enough to determine a class's package */
 199     private static boolean isSameClassPackage(ClassLoader loader1, String name1,
 200                                               ClassLoader loader2, String name2)
 201     {
 202         if (loader1 != loader2) {
 203             return false;
 204         } else {
 205             int lastDot1 = name1.lastIndexOf('.');
 206             int lastDot2 = name2.lastIndexOf('.');
 207             if ((lastDot1 == -1) || (lastDot2 == -1)) {
 208                 // One of the two doesn't have a package.  Only return true
 209                 // if the other one also doesn't have a package.
 210                 return (lastDot1 == lastDot2);
 211             } else {
 212                 int idx1 = 0;
 213                 int idx2 = 0;
 214 
 215                 // Skip over '['s
 216                 if (name1.charAt(idx1) == '[') {
 217                     do {
 218                         idx1++;
 219                     } while (name1.charAt(idx1) == '[');
 220                     if (name1.charAt(idx1) != 'L') {
 221                         // Something is terribly wrong.  Shouldn't be here.
 222                         throw new InternalError("Illegal class name " + name1);
 223                     }
 224                 }
 225                 if (name2.charAt(idx2) == '[') {
 226                     do {
 227                         idx2++;
 228                     } while (name2.charAt(idx2) == '[');
 229                     if (name2.charAt(idx2) != 'L') {
 230                         // Something is terribly wrong.  Shouldn't be here.
 231                         throw new InternalError("Illegal class name " + name2);
 232                     }
 233                 }
 234 
 235                 // Check that package part is identical
 236                 int length1 = lastDot1 - idx1;
 237                 int length2 = lastDot2 - idx2;
 238 
 239                 if (length1 != length2) {
 240                     return false;
 241                 }
 242                 return name1.regionMatches(false, idx1, name2, idx2, length1);
 243             }
 244         }
 245     }
 246 
 247     static boolean isSubclassOf(Class queryClass,
 248                                 Class ofClass)
 249     {
 250         while (queryClass != null) {
 251             if (queryClass == ofClass) {
 252                 return true;
 253             }
 254             queryClass = queryClass.getSuperclass();
 255         }
 256         return false;
 257     }
 258 
 259     // fieldNames must contain only interned Strings
 260     public static synchronized void registerFieldsToFilter(Class containingClass,
 261                                               String ... fieldNames) {
 262         fieldFilterMap =
 263             registerFilter(fieldFilterMap, containingClass, fieldNames);
 264     }
 265 
 266     // methodNames must contain only interned Strings
 267     public static synchronized void registerMethodsToFilter(Class containingClass,
 268                                               String ... methodNames) {
 269         methodFilterMap =
 270             registerFilter(methodFilterMap, containingClass, methodNames);
 271     }
 272 
 273     private static Map<Class,String[]> registerFilter(Map<Class,String[]> map,
 274             Class containingClass, String ... names) {
 275         if (map.get(containingClass) != null) {
 276             throw new IllegalArgumentException
 277                             ("Filter already registered: " + containingClass);
 278         }
 279         map = new HashMap<Class,String[]>(map);
 280         map.put(containingClass, names);
 281         return map;
 282     }
 283 
 284     public static Field[] filterFields(Class containingClass,
 285                                        Field[] fields) {
 286         if (fieldFilterMap == null) {
 287             // Bootstrapping
 288             return fields;
 289         }
 290         return (Field[])filter(fields, fieldFilterMap.get(containingClass));
 291     }
 292 
 293     public static Method[] filterMethods(Class containingClass, Method[] methods) {
 294         if (methodFilterMap == null) {
 295             // Bootstrapping
 296             return methods;
 297         }
 298         return (Method[])filter(methods, methodFilterMap.get(containingClass));
 299     }
 300 
 301     private static Member[] filter(Member[] members, String[] filteredNames) {
 302         if ((filteredNames == null) || (members.length == 0)) {
 303             return members;
 304         }
 305         int numNewMembers = 0;
 306         for (Member member : members) {
 307             boolean shouldSkip = false;
 308             for (String filteredName : filteredNames) {
 309                 if (member.getName() == filteredName) {
 310                     shouldSkip = true;
 311                     break;
 312                 }
 313             }
 314             if (!shouldSkip) {
 315                 ++numNewMembers;
 316             }
 317         }
 318         Member[] newMembers =
 319             (Member[])Array.newInstance(members[0].getClass(), numNewMembers);
 320         int destIdx = 0;
 321         for (Member member : members) {
 322             boolean shouldSkip = false;
 323             for (String filteredName : filteredNames) {
 324                 if (member.getName() == filteredName) {
 325                     shouldSkip = true;
 326                     break;
 327                 }
 328             }
 329             if (!shouldSkip) {
 330                 newMembers[destIdx++] = member;
 331             }
 332         }
 333         return newMembers;
 334     }
 335 
 336     /**
 337      * Tests if the given method is caller-sensitive and the declaring class
 338      * is defined by either the bootstrap class loader or extension class loader.
 339      */
 340     public static boolean isCallerSensitive(Method m) {
 341         final ClassLoader loader = m.getDeclaringClass().getClassLoader();
 342         if (loader == null || isExtClassLoader(loader))  {
 343             return m.isAnnotationPresent(CallerSensitive.class);
 344         }
 345         return false;
 346     }
 347 
 348     private static boolean isExtClassLoader(ClassLoader loader) {
 349         ClassLoader cl = ClassLoader.getSystemClassLoader();
 350         while (cl != null) {
 351             if (cl.getParent() == null && cl == loader) {
 352                 return true;
 353             }
 354             cl = cl.getParent();
 355         }
 356         return false;
 357     }
 358 }