/* * Copyright (c) 2001, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /*IKVM*/ /* Modified for IKVM by Jeroen Frijters * * May 27, 2007 Added support for @ikvm.lang.Internal access modifier * */ /*IKVM*/ package sun.reflect; import java.lang.reflect.*; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** Common utility routines used by both java.lang and java.lang.reflect */ public class Reflection { /** Used to filter out fields and methods from certain classes from public view, where they are sensitive or they may contain VM-internal objects. These Maps are updated very rarely. Rather than synchronize on each access, we use copy-on-write */ private static volatile Map fieldFilterMap; private static volatile Map methodFilterMap; static { Map map = new HashMap(); map.put(Reflection.class, new String[] {"fieldFilterMap", "methodFilterMap"}); map.put(System.class, new String[] {"security"}); fieldFilterMap = map; methodFilterMap = new HashMap(); // [IKVM] to avoid initialization order issues, we actually add // Unsafe.getUnsafe() here, instead of in Unsafe's class initializer methodFilterMap.put(sun.misc.Unsafe.class, new String[] {"getUnsafe"}); } /** Returns the class of the caller of the method calling this method, ignoring frames associated with java.lang.reflect.Method.invoke() and its implementation. */ @CallerSensitive public static Class getCallerClass() { return getCallerClass(2); } /** Returns the class of the method realFramesToSkip frames up the stack (zero-based), ignoring frames associated with java.lang.reflect.Method.invoke() and its implementation. The first frame is that associated with this method, so getCallerClass(0) returns the Class object for sun.reflect.Reflection. Frames associated with java.lang.reflect.Method.invoke() and its implementation are completely ignored and do not count toward the number of "real" frames skipped. */ @CallerSensitive public static native Class getCallerClass(int realFramesToSkip); /** Retrieves the access flags written to the class file. For inner classes these flags may differ from those returned by Class.getModifiers(), which searches the InnerClasses attribute to find the source-level access flags. This is used instead of Class.getModifiers() for run-time access checks due to compatibility reasons; see 4471811. Only the values of the low 13 bits (i.e., a mask of 0x1FFF) are guaranteed to be valid. */ private static native int getClassAccessFlags(Class c); /** A quick "fast-path" check to try to avoid getCallerClass() calls. */ public static boolean quickCheckMemberAccess(Class memberClass, int modifiers) { return Modifier.isPublic(getClassAccessFlags(memberClass) & modifiers); } public static void ensureMemberAccess(Class currentClass, Class memberClass, Object target, int modifiers) throws IllegalAccessException { if (currentClass == null || memberClass == null) { throw new InternalError(); } if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) { throw new IllegalAccessException("Class " + currentClass.getName() + " can not access a member of class " + memberClass.getName() + " with modifiers \"" + Modifier.toString(modifiers) + "\""); } } /*IKVM*/ private static native boolean checkInternalAccess(Class currentClass, Class memberClass); public static boolean verifyMemberAccess(Class currentClass, // Declaring class of field // or method Class memberClass, // May be NULL in case of statics Object target, int modifiers) { // Verify that currentClass can access a field, method, or // constructor of memberClass, where that member's access bits are // "modifiers". boolean gotIsSameClassPackage = false; boolean isSameClassPackage = false; if (currentClass == memberClass) { // Always succeeds return true; } if (!Modifier.isPublic(getClassAccessFlags(memberClass))) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true; /*IKVM*/ if (!isSameClassPackage && !checkInternalAccess(currentClass, memberClass)) { return false; } } // At this point we know that currentClass can access memberClass. if (Modifier.isPublic(modifiers)) { return true; } /*IKVM*/ // Is the member @ikvm.lang.Internal accessible? if ((modifiers & 0x40000000) != 0) { return currentClass.getClassLoader() == memberClass.getClassLoader(); } boolean successSoFar = false; if (Modifier.isProtected(modifiers)) { // See if currentClass is a subclass of memberClass if (isSubclassOf(currentClass, memberClass)) { successSoFar = true; } } if (!successSoFar && !Modifier.isPrivate(modifiers)) { if (!gotIsSameClassPackage) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true; } if (isSameClassPackage) { successSoFar = true; } } if (!successSoFar) { return false; } if (Modifier.isProtected(modifiers)) { // Additional test for protected members: JLS 6.6.2 Class targetClass = (target == null ? memberClass : target.getClass()); if (targetClass != currentClass) { if (!gotIsSameClassPackage) { isSameClassPackage = isSameClassPackage(currentClass, memberClass); gotIsSameClassPackage = true; } if (!isSameClassPackage) { if (!isSubclassOf(targetClass, currentClass)) { return false; } } } } return true; } private static boolean isSameClassPackage(Class c1, Class c2) { return isSameClassPackage(c1.getClassLoader(), c1.getName(), c2.getClassLoader(), c2.getName()); } /** Returns true if two classes are in the same package; classloader and classname information is enough to determine a class's package */ private static boolean isSameClassPackage(ClassLoader loader1, String name1, ClassLoader loader2, String name2) { if (loader1 != loader2) { return false; } else { int lastDot1 = name1.lastIndexOf('.'); int lastDot2 = name2.lastIndexOf('.'); if ((lastDot1 == -1) || (lastDot2 == -1)) { // One of the two doesn't have a package. Only return true // if the other one also doesn't have a package. return (lastDot1 == lastDot2); } else { int idx1 = 0; int idx2 = 0; // Skip over '['s if (name1.charAt(idx1) == '[') { do { idx1++; } while (name1.charAt(idx1) == '['); if (name1.charAt(idx1) != 'L') { // Something is terribly wrong. Shouldn't be here. throw new InternalError("Illegal class name " + name1); } } if (name2.charAt(idx2) == '[') { do { idx2++; } while (name2.charAt(idx2) == '['); if (name2.charAt(idx2) != 'L') { // Something is terribly wrong. Shouldn't be here. throw new InternalError("Illegal class name " + name2); } } // Check that package part is identical int length1 = lastDot1 - idx1; int length2 = lastDot2 - idx2; if (length1 != length2) { return false; } return name1.regionMatches(false, idx1, name2, idx2, length1); } } } static boolean isSubclassOf(Class queryClass, Class ofClass) { while (queryClass != null) { if (queryClass == ofClass) { return true; } queryClass = queryClass.getSuperclass(); } return false; } // fieldNames must contain only interned Strings public static synchronized void registerFieldsToFilter(Class containingClass, String ... fieldNames) { fieldFilterMap = registerFilter(fieldFilterMap, containingClass, fieldNames); } // methodNames must contain only interned Strings public static synchronized void registerMethodsToFilter(Class containingClass, String ... methodNames) { methodFilterMap = registerFilter(methodFilterMap, containingClass, methodNames); } private static Map registerFilter(Map map, Class containingClass, String ... names) { if (map.get(containingClass) != null) { throw new IllegalArgumentException ("Filter already registered: " + containingClass); } map = new HashMap(map); map.put(containingClass, names); return map; } public static Field[] filterFields(Class containingClass, Field[] fields) { if (fieldFilterMap == null) { // Bootstrapping return fields; } return (Field[])filter(fields, fieldFilterMap.get(containingClass)); } public static Method[] filterMethods(Class containingClass, Method[] methods) { if (methodFilterMap == null) { // Bootstrapping return methods; } return (Method[])filter(methods, methodFilterMap.get(containingClass)); } private static Member[] filter(Member[] members, String[] filteredNames) { if ((filteredNames == null) || (members.length == 0)) { return members; } int numNewMembers = 0; for (Member member : members) { boolean shouldSkip = false; for (String filteredName : filteredNames) { if (member.getName() == filteredName) { shouldSkip = true; break; } } if (!shouldSkip) { ++numNewMembers; } } Member[] newMembers = (Member[])Array.newInstance(members[0].getClass(), numNewMembers); int destIdx = 0; for (Member member : members) { boolean shouldSkip = false; for (String filteredName : filteredNames) { if (member.getName() == filteredName) { shouldSkip = true; break; } } if (!shouldSkip) { newMembers[destIdx++] = member; } } return newMembers; } }