mirror of
https://github.com/EsotericSoftware/reflectasm.git
synced 2025-03-14 03:10:31 +08:00
Fixes issue #217 in Kryo (https://github.com/EsotericSoftware/kryo/issues/217)
This commit is contained in:
parent
fb4e9d9b7c
commit
32d4420a02
@ -16,6 +16,8 @@ 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);
|
||||
@ -72,6 +74,7 @@ class AccessClassLoader extends ClassLoader {
|
||||
if (name.equals(FieldAccess.class.getName())) return FieldAccess.class;
|
||||
if (name.equals(MethodAccess.class.getName())) return MethodAccess.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.
|
||||
return super.loadClass(name, resolve);
|
||||
}
|
||||
@ -79,19 +82,52 @@ class AccessClassLoader extends ClassLoader {
|
||||
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.
|
||||
Method method = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {String.class, byte[].class, int.class,
|
||||
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),
|
||||
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()) {
|
||||
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) {
|
||||
ClassLoader parent = type.getClassLoader();
|
||||
if (parent == null) parent = ClassLoader.getSystemClassLoader();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,8 @@ public abstract class ConstructorAccess<T> {
|
||||
String className = type.getName();
|
||||
String accessClassName = className + "ConstructorAccess";
|
||||
if (accessClassName.startsWith("java.")) accessClassName = "reflectasm." + accessClassName;
|
||||
Class accessClass = null;
|
||||
|
||||
Class accessClass;
|
||||
|
||||
AccessClassLoader loader = AccessClassLoader.get(type);
|
||||
synchronized (loader) {
|
||||
try {
|
||||
@ -45,39 +45,41 @@ public abstract class ConstructorAccess<T> {
|
||||
String accessClassNameInternal = accessClassName.replace('.', '/');
|
||||
String classNameInternal = className.replace('.', '/');
|
||||
String enclosingClassNameInternal;
|
||||
|
||||
boolean isPrivate = false;
|
||||
Constructor<T> constructor = null;
|
||||
int modifiers = 0;
|
||||
if (!isNonStaticMemberClass) {
|
||||
enclosingClassNameInternal = null;
|
||||
try {
|
||||
Constructor<T> constructor = type.getDeclaredConstructor((Class[])null);
|
||||
isPrivate = Modifier.isPrivate(constructor.getModifiers());
|
||||
constructor = type.getDeclaredConstructor((Class[])null);
|
||||
modifiers = constructor.getModifiers();
|
||||
} catch (Exception 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());
|
||||
}
|
||||
} else {
|
||||
enclosingClassNameInternal = enclosingType.getName().replace('.', '/');
|
||||
try {
|
||||
Constructor<T> constructor = type.getDeclaredConstructor(enclosingType); // Inner classes should have this.
|
||||
isPrivate = Modifier.isPrivate(constructor.getModifiers());
|
||||
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);
|
||||
}
|
||||
if (isPrivate) {
|
||||
if (Modifier.isPrivate(modifiers)) {
|
||||
throw new RuntimeException(
|
||||
"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);
|
||||
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null,
|
||||
"com/esotericsoftware/reflectasm/ConstructorAccess", null);
|
||||
cw.visit(V1_1, ACC_PUBLIC + ACC_SUPER, accessClassNameInternal, null, superclassNameInternal, null);
|
||||
|
||||
insertConstructor(cw);
|
||||
insertConstructor(cw, superclassNameInternal);
|
||||
insertNewInstance(cw, classNameInternal);
|
||||
insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal);
|
||||
|
||||
@ -85,20 +87,30 @@ public abstract class ConstructorAccess<T> {
|
||||
accessClass = loader.defineClass(accessClassName, cw.toByteArray());
|
||||
}
|
||||
}
|
||||
ConstructorAccess<T> access;
|
||||
try {
|
||||
ConstructorAccess<T> access = (ConstructorAccess<T>)accessClass.newInstance();
|
||||
access.isNonStaticMemberClass = isNonStaticMemberClass;
|
||||
return access;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error constructing constructor access class: " + accessClassName, ex);
|
||||
access = (ConstructorAccess<T>)accessClass.newInstance();
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Exception constructing constructor access class: " + accessClassName, t);
|
||||
}
|
||||
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);
|
||||
mv.visitCode();
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, "com/esotericsoftware/reflectasm/ConstructorAccess", "<init>", "()V");
|
||||
mv.visitMethodInsn(INVOKESPECIAL, superclassNameInternal, "<init>", "()V");
|
||||
mv.visitInsn(RETURN);
|
||||
mv.visitMaxs(1, 1);
|
||||
mv.visitEnd();
|
||||
|
@ -19,7 +19,7 @@ public abstract class FieldAccess {
|
||||
public int getIndex (String fieldName) {
|
||||
for (int i = 0, n = fieldNames.length; i < n; 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) {
|
||||
@ -147,8 +147,8 @@ public abstract class FieldAccess {
|
||||
access.fieldNames = fieldNames;
|
||||
access.fieldTypes = fieldTypes;
|
||||
return access;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error constructing field access class: " + accessClassName, ex);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Error constructing field access class: " + accessClassName, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,21 +35,21 @@ public abstract class MethodAccess {
|
||||
public int getIndex (String methodName) {
|
||||
for (int i = 0, n = methodNames.length; i < n; 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. */
|
||||
public int getIndex (String methodName, Class... paramTypes) {
|
||||
for (int i = 0, n = methodNames.length; i < n; 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. */
|
||||
public int getIndex (String methodName, int paramsCount) {
|
||||
for (int i = 0, n = methodNames.length; i < n; 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 () {
|
||||
@ -260,8 +260,8 @@ public abstract class MethodAccess {
|
||||
access.parameterTypes = parameterTypes;
|
||||
access.returnTypes = returnTypes;
|
||||
return access;
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Error constructing method access class: " + accessClassName, ex);
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException("Error constructing method access class: " + accessClassName, t);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.esotericsoftware.reflectasm;
|
||||
|
||||
public abstract class PublicConstructorAccess extends ConstructorAccess {
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user