mirror of
https://github.com/EsotericSoftware/reflectasm.git
synced 2025-03-23 07:40:16 +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.
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.esotericsoftware.reflectasm;
|
||||||
|
|
||||||
|
public abstract class PublicConstructorAccess extends ConstructorAccess {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user