This commit is contained in:
Tumi 2014-07-17 16:38:36 +02:00
parent fb4e9d9b7c
commit 32d4420a02
5 changed files with 85 additions and 32 deletions

View File

@ -16,6 +16,8 @@ class AccessClassLoader extends ClassLoader {
// Fast-path for classes loaded in the same ClassLoader as this class. // Fast-path for classes loaded in the same ClassLoader as this class.
static private final ClassLoader selfContextParentClassLoader = getParentClassLoader(AccessClassLoader.class); static private final ClassLoader selfContextParentClassLoader = getParentClassLoader(AccessClassLoader.class);
static private volatile AccessClassLoader selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader); static private volatile AccessClassLoader selfContextAccessClassLoader = new AccessClassLoader(selfContextParentClassLoader);
static private volatile Method defineClassMethod;
static AccessClassLoader get (Class type) { static AccessClassLoader get (Class type) {
ClassLoader parent = getParentClassLoader(type); ClassLoader parent = getParentClassLoader(type);
@ -72,6 +74,7 @@ class AccessClassLoader extends ClassLoader {
if (name.equals(FieldAccess.class.getName())) return FieldAccess.class; if (name.equals(FieldAccess.class.getName())) return FieldAccess.class;
if (name.equals(MethodAccess.class.getName())) return MethodAccess.class; if (name.equals(MethodAccess.class.getName())) return MethodAccess.class;
if (name.equals(ConstructorAccess.class.getName())) return ConstructorAccess.class; if (name.equals(ConstructorAccess.class.getName())) return ConstructorAccess.class;
if (name.equals(PublicConstructorAccess.class.getName())) return PublicConstructorAccess.class;
// All other classes come from the classloader that loaded the type we are accessing. // All other classes come from the classloader that loaded the type we are accessing.
return super.loadClass(name, resolve); return super.loadClass(name, resolve);
} }
@ -79,19 +82,52 @@ class AccessClassLoader extends ClassLoader {
Class<?> defineClass (String name, byte[] bytes) throws ClassFormatError { Class<?> defineClass (String name, byte[] bytes) throws ClassFormatError {
try { try {
// Attempt to load the access class in the same loader, which makes protected and default access members accessible. // Attempt to load the access class in the same loader, which makes protected and default access members accessible.
Method method = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {String.class, byte[].class, int.class, return (Class<?>)getDefineClassMethod().invoke(getParent(), new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length),
int.class, ProtectionDomain.class});
if (!method.isAccessible()) method.setAccessible(true);
return (Class)method.invoke(getParent(), new Object[] {name, bytes, Integer.valueOf(0), Integer.valueOf(bytes.length),
getClass().getProtectionDomain()}); getClass().getProtectionDomain()});
} catch (Exception ignored) { } 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()); 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()) {
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;
}
return loader1==loader2;
}
private static ClassLoader getParentClassLoader (Class type) { private static ClassLoader getParentClassLoader (Class type) {
ClassLoader parent = type.getClassLoader(); ClassLoader parent = type.getClassLoader();
if (parent == null) parent = ClassLoader.getSystemClassLoader(); if (parent == null) parent = ClassLoader.getSystemClassLoader();
return parent; return parent;
} }
private static 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});
try {
defineClassMethod.setAccessible(true);
}
catch (Exception ignored) {
}
}
}
return defineClassMethod;
}
} }

View File

@ -35,8 +35,8 @@ public abstract class ConstructorAccess<T> {
String className = type.getName(); String className = type.getName();
String accessClassName = className + "ConstructorAccess"; String accessClassName = className + "ConstructorAccess";
if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName; if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName;
Class accessClass = null; Class accessClass;
AccessClassLoader loader = AccessClassLoader.get(type); AccessClassLoader loader = AccessClassLoader.get(type);
synchronized (loader) { synchronized (loader) {
try { try {
@ -45,39 +45,41 @@ public abstract class ConstructorAccess<T> {
String accessClassNameInternal = accessClassName.replace('.', '/'); String accessClassNameInternal = accessClassName.replace('.', '/');
String classNameInternal = className.replace('.', '/'); String classNameInternal = className.replace('.', '/');
String enclosingClassNameInternal; String enclosingClassNameInternal;
Constructor<T> constructor = null;
boolean isPrivate = false; int modifiers = 0;
if (!isNonStaticMemberClass) { if (!isNonStaticMemberClass) {
enclosingClassNameInternal = null; enclosingClassNameInternal = null;
try { try {
Constructor<T> constructor = type.getDeclaredConstructor((Class[])null); constructor = type.getDeclaredConstructor((Class[])null);
isPrivate = Modifier.isPrivate(constructor.getModifiers()); modifiers = constructor.getModifiers();
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException("Class cannot be created (missing no-arg constructor): " + type.getName(), ex); throw new RuntimeException("Class cannot be created (missing no-arg constructor): " + type.getName(), ex);
} }
if (isPrivate) { if (Modifier.isPrivate(modifiers)) {
throw new RuntimeException("Class cannot be created (the no-arg constructor is private): " + type.getName()); throw new RuntimeException("Class cannot be created (the no-arg constructor is private): " + type.getName());
} }
} else { } else {
enclosingClassNameInternal = enclosingType.getName().replace('.', '/'); enclosingClassNameInternal = enclosingType.getName().replace('.', '/');
try { try {
Constructor<T> constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this. constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this.
isPrivate = Modifier.isPrivate(constructor.getModifiers()); modifiers = constructor.getModifiers();
} catch (Exception ex) { } catch (Exception ex) {
throw new RuntimeException("Non-static member class cannot be created (missing enclosing class constructor): " throw new RuntimeException("Non-static member class cannot be created (missing enclosing class constructor): "
+ type.getName(), ex); + type.getName(), ex);
} }
if (isPrivate) { if (Modifier.isPrivate(modifiers)) {
throw new RuntimeException( 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";
ClassWriter cw = new ClassWriter(0); ClassWriter cw = new ClassWriter(0);
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null);
"com/esotericsoftware/reflectasm/ConstructorAccess", null);
insertConstructor(cw); insertConstructor(cw, superclassNameInternal);
insertNewInstance(cw, classNameInternal); insertNewInstance(cw, classNameInternal);
insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal); insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal);
@ -85,20 +87,30 @@ public abstract class ConstructorAccess<T> {
accessClass = loader.defineClass(accessClassName, cw.toByteArray()); accessClass = loader.defineClass(accessClassName, cw.toByteArray());
} }
} }
ConstructorAccess<T> access;
try { try {
ConstructorAccess<T> access = (ConstructorAccess<T>)accessClass.newInstance(); access = (ConstructorAccess<T>)accessClass.newInstance();
access.isNonStaticMemberClass = isNonStaticMemberClass; } catch (Throwable t) {
return access; throw new RuntimeException("Exception constructing constructor access class: " + accessClassName, t);
} catch (Exception ex) {
throw new RuntimeException("Error constructing constructor access class: " + accessClassName, ex);
} }
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());
}
access.isNonStaticMemberClass = isNonStaticMemberClass;
return access;
} }
static private void insertConstructor (ClassWriter cw) { static private void insertConstructor (ClassWriter cw, String superclassNameInternal) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode(); mv.visitCode();
mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "com/esotericsoftware/reflectasm/ConstructorAccess", "<init>", "()V"); mv.visitMethodInsn(INVOKESPECIAL, superclassNameInternal, "<init>", "()V");
mv.visitInsn(RETURN); mv.visitInsn(RETURN);
mv.visitMaxs(1, 1); mv.visitMaxs(1, 1);
mv.visitEnd(); mv.visitEnd();

View File

@ -19,7 +19,7 @@ public abstract class FieldAccess {
public int getIndex (String fieldName) { public int getIndex (String fieldName) {
for (int i = 0, n = fieldNames.length; i < n; i++) for (int i = 0, n = fieldNames.length; i < n; i++)
if (fieldNames[i].equals(fieldName)) return i; if (fieldNames[i].equals(fieldName)) return i;
throw new IllegalArgumentException("Unable to find public field: " + fieldName); throw new IllegalArgumentException("Unable to find non-private field: " + fieldName);
} }
public void set (Object instance, String fieldName, Object value) { public void set (Object instance, String fieldName, Object value) {
@ -147,8 +147,8 @@ public abstract class FieldAccess {
access.fieldNames = fieldNames; access.fieldNames = fieldNames;
access.fieldTypes = fieldTypes; access.fieldTypes = fieldTypes;
return access; return access;
} catch (Exception ex) { } catch (Throwable t) {
throw new RuntimeException("Error constructing field access class: " + accessClassName, ex); throw new RuntimeException("Error constructing field access class: " + accessClassName, t);
} }
} }

View File

@ -35,21 +35,21 @@ public abstract class MethodAccess {
public int getIndex (String methodName) { public int getIndex (String methodName) {
for (int i = 0, n = methodNames.length; i < n; i++) for (int i = 0, n = methodNames.length; i < n; i++)
if (methodNames[i].equals(methodName)) return i; if (methodNames[i].equals(methodName)) return i;
throw new IllegalArgumentException("Unable to find public method: " + methodName); throw new IllegalArgumentException("Unable to find non-private method: " + methodName);
} }
/** Returns the index of the first method with the specified name and param types. */ /** Returns the index of the first method with the specified name and param types. */
public int getIndex (String methodName, Class... paramTypes) { public int getIndex (String methodName, Class... paramTypes) {
for (int i = 0, n = methodNames.length; i < n; i++) for (int i = 0, n = methodNames.length; i < n; i++)
if (methodNames[i].equals(methodName) && Arrays.equals(paramTypes, parameterTypes[i])) return i; if (methodNames[i].equals(methodName) && Arrays.equals(paramTypes, parameterTypes[i])) return i;
throw new IllegalArgumentException("Unable to find public method: " + methodName + " " + Arrays.toString(paramTypes)); throw new IllegalArgumentException("Unable to find non-private method: " + methodName + " " + Arrays.toString(paramTypes));
} }
/** Returns the index of the first method with the specified name and the specified number of arguments. */ /** Returns the index of the first method with the specified name and the specified number of arguments. */
public int getIndex (String methodName, int paramsCount) { public int getIndex (String methodName, int paramsCount) {
for (int i = 0, n = methodNames.length; i < n; i++) for (int i = 0, n = methodNames.length; i < n; i++)
if (methodNames[i].equals(methodName) && parameterTypes[i].length == paramsCount) return i; if (methodNames[i].equals(methodName) && parameterTypes[i].length == paramsCount) return i;
throw new IllegalArgumentException("Unable to find public method: " + methodName + " with " + paramsCount + " params."); throw new IllegalArgumentException("Unable to find non-private method: " + methodName + " with " + paramsCount + " params.");
} }
public String[] getMethodNames () { public String[] getMethodNames () {
@ -260,8 +260,8 @@ public abstract class MethodAccess {
access.parameterTypes = parameterTypes; access.parameterTypes = parameterTypes;
access.returnTypes = returnTypes; access.returnTypes = returnTypes;
return access; return access;
} catch (Exception ex) { } catch (Throwable t) {
throw new RuntimeException("Error constructing method access class: " + accessClassName, ex); throw new RuntimeException("Error constructing method access class: " + accessClassName, t);
} }
} }

View File

@ -0,0 +1,5 @@
package com.esotericsoftware.reflectasm;
public abstract class PublicConstructorAccess extends ConstructorAccess {
}