diff --git a/src/com/esotericsoftware/reflectasm/AccessClassLoader.java b/src/com/esotericsoftware/reflectasm/AccessClassLoader.java index 59c4a8f..b6270c9 100644 --- a/src/com/esotericsoftware/reflectasm/AccessClassLoader.java +++ b/src/com/esotericsoftware/reflectasm/AccessClassLoader.java @@ -17,6 +17,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.security.ProtectionDomain; +import java.util.HashSet; import java.util.WeakHashMap; class AccessClassLoader extends ClassLoader { @@ -29,60 +30,34 @@ class AccessClassLoader extends ClassLoader { // Fast-path for classes loaded in the same ClassLoader as this class. static private final ClassLoader selfContextParentClassLoader = getParentClassLoader(AccessClassLoader.class); static private volatile AccessClassLoader selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader); - + static private volatile Method defineClassMethod; - static AccessClassLoader get (Class type) { - ClassLoader parent = getParentClassLoader(type); - // 1. fast-path: - if (selfContextParentClassLoader.equals(parent)) { - if (selfContextAccessClassLoader == null) { - synchronized (accessClassLoaders) { // DCL with volatile semantics - if (selfContextAccessClassLoader == null) - selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader); - } - } - return selfContextAccessClassLoader; - } - // 2. normal search: - synchronized (accessClassLoaders) { - WeakReference ref = accessClassLoaders.get(parent); - if (ref != null) { - AccessClassLoader accessClassLoader = ref.get(); - if (accessClassLoader != null) - return accessClassLoader; - else - accessClassLoaders.remove(parent); // the value has been GC-reclaimed, but still not the key (defensive sanity) - } - AccessClassLoader accessClassLoader = new AccessClassLoader(parent); - accessClassLoaders.put(parent, new WeakReference(accessClassLoader)); - return accessClassLoader; - } + private final HashSet localClassNames = new HashSet(); + + private AccessClassLoader (ClassLoader parent) { + super(parent); } - public static void remove (ClassLoader parent) { - // 1. fast-path: - if (selfContextParentClassLoader.equals(parent)) { - selfContextAccessClassLoader = null; - } else { - // 2. normal search: - synchronized (accessClassLoaders) { - accessClassLoaders.remove(parent); + /** Returns null if the access class has not yet been defined. */ + Class loadAccessClass (String name) { + // No need to check the parent class loader if the access class hasn't been defined yet. + if (localClassNames.contains(name)) { + try { + return loadClass(name, false); + } catch (ClassNotFoundException ex) { + throw new RuntimeException(ex); // Should not happen, since we know the class has been defined. } } + return null; } - public static int activeAccessClassLoaders () { - int sz = accessClassLoaders.size(); - if (selfContextAccessClassLoader != null) sz++; - return sz; + Class defineAccessClass (String name, byte[] bytes) throws ClassFormatError { + localClassNames.add(name); + return defineClass(name, bytes); } - private AccessClassLoader (ClassLoader parent) { - super(parent); - } - - protected java.lang.Class loadClass (String name, boolean resolve) throws ClassNotFoundException { + protected Class loadClass (String name, boolean resolve) throws ClassNotFoundException { // These classes come from the classloader that loaded AccessClassLoader. if (name.equals(FieldAccess.class.getName())) return FieldAccess.class; if (name.equals(MethodAccess.class.getName())) return MethodAccess.class; @@ -95,52 +70,95 @@ protected java.lang.Class loadClass (String name, boolean resolve) throws Cla Class defineClass (String name, byte[] bytes) throws ClassFormatError { try { // Attempt to load the access class in the same loader, which makes protected and default access members accessible. - return (Class)getDefineClassMethod().invoke(getParent(), new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length), - getClass().getProtectionDomain()}); + return (Class)getDefineClassMethod().invoke(getParent(), + new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length), getClass().getProtectionDomain()}); } catch (Exception ignored) { // continue with the definition in the current loader (won't have access to protected and package-protected members) } return defineClass(name, bytes, 0, bytes.length, getClass().getProtectionDomain()); } - - // As per JLS, section 5.3, - // "The runtime package of a class or interface is determined by the package name and defining class loader of the class or interface." - static boolean areInSameRuntimeClassLoader(Class type1, Class type2) { - if (type1.getPackage()!=type2.getPackage()) { + // As per JLS, section 5.3, + // "The runtime package of a class or interface is determined by the package name and defining class loader of the class or + // interface." + static boolean areInSameRuntimeClassLoader (Class type1, Class type2) { + if (type1.getPackage() != type2.getPackage()) { return false; } ClassLoader loader1 = type1.getClassLoader(); ClassLoader loader2 = type2.getClassLoader(); ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); - if (loader1==null) { - return (loader2==null || loader2==systemClassLoader); - } - if (loader2==null) { - return loader1==systemClassLoader; + if (loader1 == null) { + return (loader2 == null || loader2 == systemClassLoader); } - return loader1==loader2; + if (loader2 == null) return loader1 == systemClassLoader; + return loader1 == loader2; } - private static ClassLoader getParentClassLoader (Class type) { + static private ClassLoader getParentClassLoader (Class type) { ClassLoader parent = type.getClassLoader(); if (parent == null) parent = ClassLoader.getSystemClassLoader(); return parent; } - - private static Method getDefineClassMethod() throws Exception { + + static private Method getDefineClassMethod () throws Exception { // DCL on volatile - if (defineClassMethod==null) { - synchronized(accessClassLoaders) { - defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {String.class, byte[].class, int.class, - int.class, ProtectionDomain.class}); + if (defineClassMethod == null) { + synchronized (accessClassLoaders) { + defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", + new Class[] {String.class, byte[].class, int.class, int.class, ProtectionDomain.class}); try { defineClassMethod.setAccessible(true); - } - catch (Exception ignored) { + } catch (Exception ignored) { } } } return defineClassMethod; } + + static AccessClassLoader get (Class type) { + ClassLoader parent = getParentClassLoader(type); + // 1. fast-path: + if (selfContextParentClassLoader.equals(parent)) { + if (selfContextAccessClassLoader == null) { + synchronized (accessClassLoaders) { // DCL with volatile semantics + if (selfContextAccessClassLoader == null) + selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader); + } + } + return selfContextAccessClassLoader; + } + // 2. normal search: + synchronized (accessClassLoaders) { + WeakReference ref = accessClassLoaders.get(parent); + if (ref != null) { + AccessClassLoader accessClassLoader = ref.get(); + if (accessClassLoader != null) + return accessClassLoader; + else + accessClassLoaders.remove(parent); // the value has been GC-reclaimed, but still not the key (defensive sanity) + } + AccessClassLoader accessClassLoader = new AccessClassLoader(parent); + accessClassLoaders.put(parent, new WeakReference(accessClassLoader)); + return accessClassLoader; + } + } + + static public void remove (ClassLoader parent) { + // 1. fast-path: + if (selfContextParentClassLoader.equals(parent)) { + selfContextAccessClassLoader = null; + } else { + // 2. normal search: + synchronized (accessClassLoaders) { + accessClassLoaders.remove(parent); + } + } + } + + static public int activeAccessClassLoaders () { + int sz = accessClassLoaders.size(); + if (selfContextAccessClassLoader != null) sz++; + return sz; + } } diff --git a/src/com/esotericsoftware/reflectasm/ConstructorAccess.java b/src/com/esotericsoftware/reflectasm/ConstructorAccess.java index 778f80d..a1faa4a 100644 --- a/src/com/esotericsoftware/reflectasm/ConstructorAccess.java +++ b/src/com/esotericsoftware/reflectasm/ConstructorAccess.java @@ -16,13 +16,13 @@ import static com.esotericsoftware.asm.Opcodes.*; -import java.lang.reflect.Constructor; -import java.lang.reflect.Modifier; - import com.esotericsoftware.asm.ClassWriter; import com.esotericsoftware.asm.MethodVisitor; -public abstract class ConstructorAccess { +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; + +abstract public class ConstructorAccess { boolean isNonStaticMemberClass; public boolean isNonStaticMemberClass () { @@ -48,16 +48,13 @@ static public ConstructorAccess get (Class type) { String className = type.getName(); String accessClassName = className + "ConstructorAccess"; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; - Class accessClass; AccessClassLoader loader = AccessClassLoader.get(type); - try { - accessClass = loader.loadClass(accessClassName); - } catch (ClassNotFoundException ignored) { + Class accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { synchronized (loader) { - try { - accessClass = loader.loadClass(accessClassName); - } catch (ClassNotFoundException ignored2) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { String accessClassNameInternal = accessClassName.replace('.', '/'); String classNameInternal = className.replace('.', '/'); String enclosingClassNameInternal; @@ -80,17 +77,18 @@ static public ConstructorAccess get (Class type) { constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this. modifiers = constructor.getModifiers(); } catch (Exception ex) { - throw new RuntimeException("Non-static member class cannot be created (missing enclosing class constructor): " - + type.getName(), ex); + throw new RuntimeException( + "Non-static member class cannot be created (missing enclosing class constructor): " + type.getName(), ex); } if (Modifier.isPrivate(modifiers)) { throw new RuntimeException( - "Non-static member class cannot be created (the enclosing class constructor is private): " + type.getName()); + "Non-static member class cannot be created (the enclosing class constructor is private): " + + type.getName()); } } - String superclassNameInternal = Modifier.isPublic(modifiers) ? - "com/esotericsoftware/reflectasm/PublicConstructorAccess" : - "com/esotericsoftware/reflectasm/ConstructorAccess"; + String superclassNameInternal = Modifier.isPublic(modifiers) + ? "com/esotericsoftware/reflectasm/PublicConstructorAccess" + : "com/esotericsoftware/reflectasm/ConstructorAccess"; ClassWriter cw = new ClassWriter(0); cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null); @@ -100,7 +98,7 @@ static public ConstructorAccess get (Class type) { insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal); cw.visitEnd(); - accessClass = loader.defineClass(accessClassName, cw.toByteArray()); + accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); } } } @@ -110,14 +108,13 @@ static public ConstructorAccess get (Class type) { } catch (Throwable t) { throw new RuntimeException("Exception constructing constructor access class: " + accessClassName, t); } - if (!(access instanceof PublicConstructorAccess) && !AccessClassLoader.areInSameRuntimeClassLoader(type, accessClass)) { + if (!(access instanceof PublicConstructorAccess) && !AccessClassLoader.areInSameRuntimeClassLoader(type, accessClass)) { // Must test this after the try-catch block, whether the class has been loaded as if has been defined. // Throw a Runtime exception here instead of an IllegalAccessError when invoking newInstance() - throw new RuntimeException( - (!isNonStaticMemberClass ? - "Class cannot be created (the no-arg constructor is protected or package-protected, and its ConstructorAccess could not be defined in the same class loader): " : - "Non-static member class cannot be created (the enclosing class constructor is protected or package-protected, and its ConstructorAccess could not be defined in the same class loader): ") - + type.getName()); + throw new RuntimeException((!isNonStaticMemberClass + ? "Class cannot be created (the no-arg constructor is protected or package-protected, and its ConstructorAccess could not be defined in the same class loader): " + : "Non-static member class cannot be created (the enclosing class constructor is protected or package-protected, and its ConstructorAccess could not be defined in the same class loader): ") + + type.getName()); } access.isNonStaticMemberClass = isNonStaticMemberClass; return access; diff --git a/src/com/esotericsoftware/reflectasm/FieldAccess.java b/src/com/esotericsoftware/reflectasm/FieldAccess.java index 1b917d1..8027a71 100644 --- a/src/com/esotericsoftware/reflectasm/FieldAccess.java +++ b/src/com/esotericsoftware/reflectasm/FieldAccess.java @@ -137,16 +137,13 @@ static public FieldAccess get (Class type) { String className = type.getName(); String accessClassName = className + "FieldAccess"; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; - Class accessClass = null; AccessClassLoader loader = AccessClassLoader.get(type); - try { - accessClass = loader.loadClass(accessClassName); - } catch (ClassNotFoundException ignored) { + Class accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { synchronized (loader) { - try { - accessClass = loader.loadClass(accessClassName); - } catch (ClassNotFoundException ignored2) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { String accessClassNameInternal = accessClassName.replace('.', '/'); String classNameInternal = className.replace('.', '/'); @@ -174,7 +171,7 @@ static public FieldAccess get (Class type) { insertSetPrimitive(cw, classNameInternal, fields, Type.CHAR_TYPE); insertGetString(cw, classNameInternal, fields); cw.visitEnd(); - accessClass = loader.defineClass(accessClassName, cw.toByteArray()); + accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray()); } } } diff --git a/src/com/esotericsoftware/reflectasm/MethodAccess.java b/src/com/esotericsoftware/reflectasm/MethodAccess.java index 53c7b45..2a87df6 100644 --- a/src/com/esotericsoftware/reflectasm/MethodAccess.java +++ b/src/com/esotericsoftware/reflectasm/MethodAccess.java @@ -16,17 +16,17 @@ import static com.esotericsoftware.asm.Opcodes.*; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; - import com.esotericsoftware.asm.ClassWriter; import com.esotericsoftware.asm.Label; import com.esotericsoftware.asm.MethodVisitor; import com.esotericsoftware.asm.Opcodes; import com.esotericsoftware.asm.Type; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; + public abstract class MethodAccess { private String[] methodNames; private Class[][] parameterTypes; @@ -78,22 +78,22 @@ public Class[] getReturnTypes () { return returnTypes; } - /** @param type Must not be the Object class, an interface, a primitive type, or void. */ + /** Creates a new MethodAccess for the specified type. + * @param type Must not be the Object class, a primitive type, or void. */ static public MethodAccess get (Class type) { - if (type.getSuperclass() == null) + boolean isInterface = type.isInterface(); + if (!isInterface && type.getSuperclass() == null) throw new IllegalArgumentException("The type must not be the Object class, an interface, a primitive type, or void."); ArrayList methods = new ArrayList(); - boolean isInterface = type.isInterface(); if (!isInterface) { Class nextClass = type; while (nextClass != Object.class) { addDeclaredMethodsToList(nextClass, methods); nextClass = nextClass.getSuperclass(); } - } else { + } else recursiveAddInterfaceMethodsToList(type, methods); - } int n = methods.size(); String[] methodNames = new String[n]; @@ -109,16 +109,13 @@ static public MethodAccess get (Class type) { String className = type.getName(); String accessClassName = className + "MethodAccess"; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; - Class accessClass; AccessClassLoader loader = AccessClassLoader.get(type); - try { - accessClass = loader.loadClass(accessClassName); - } catch (ClassNotFoundException ignored) { + Class accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { synchronized (loader) { - try { - accessClass = loader.loadClass(accessClassName); - } catch (ClassNotFoundException ignored2) { + accessClass = loader.loadAccessClass(accessClassName); + if (accessClass == null) { String accessClassNameInternal = accessClassName.replace('.', '/'); String classNameInternal = className.replace('.', '/'); @@ -277,7 +274,7 @@ else if (Modifier.isStatic(methods.get(i).getModifiers())) } cw.visitEnd(); byte[] data = cw.toByteArray(); - accessClass = loader.defineClass(accessClassName, data); + accessClass = loader.defineAccessClass(accessClassName, data); } } } @@ -292,7 +289,7 @@ else if (Modifier.isStatic(methods.get(i).getModifiers())) } } - private static void addDeclaredMethodsToList (Class type, ArrayList methods) { + static private void addDeclaredMethodsToList (Class type, ArrayList methods) { Method[] declaredMethods = type.getDeclaredMethods(); for (int i = 0, n = declaredMethods.length; i < n; i++) { Method method = declaredMethods[i]; @@ -303,10 +300,9 @@ private static void addDeclaredMethodsToList (Class type, ArrayList meth } } - private static void recursiveAddInterfaceMethodsToList (Class interfaceType, ArrayList methods) { + static private void recursiveAddInterfaceMethodsToList (Class interfaceType, ArrayList methods) { addDeclaredMethodsToList(interfaceType, methods); - for (Class nextInterface : interfaceType.getInterfaces()) { + for (Class nextInterface : interfaceType.getInterfaces()) recursiveAddInterfaceMethodsToList(nextInterface, methods); - } } } diff --git a/test/com/esotericsoftware/reflectasm/MethodAccessTest.java b/test/com/esotericsoftware/reflectasm/MethodAccessTest.java index 709cd8b..e45fa8b 100644 --- a/test/com/esotericsoftware/reflectasm/MethodAccessTest.java +++ b/test/com/esotericsoftware/reflectasm/MethodAccessTest.java @@ -91,6 +91,7 @@ public void testEmptyClass () { public void testInvokeInterface () { MethodAccess access = MethodAccess.get(ConcurrentMap.class); + access = MethodAccess.get(ConcurrentMap.class); ConcurrentHashMap someMap = new ConcurrentHashMap(); someMap.put("first", "one"); someMap.put("second", "two");