Skip to content

Commit

Permalink
Avoid asking parent classloader for access class that has not yet bee…
Browse files Browse the repository at this point in the history
…n generated, clean up.

closes #52
closes #51
  • Loading branch information
NathanSweet committed Jun 11, 2018
1 parent 54f4534 commit da80699
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 120 deletions.
150 changes: 84 additions & 66 deletions src/com/esotericsoftware/reflectasm/AccessClassLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<AccessClassLoader> 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>(accessClassLoader));
return accessClassLoader;
}
private final HashSet<String> 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;
Expand All @@ -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<AccessClassLoader> 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>(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;
}
}
45 changes: 21 additions & 24 deletions src/com/esotericsoftware/reflectasm/ConstructorAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

abstract public class ConstructorAccess<T> {
boolean isNonStaticMemberClass;

public boolean isNonStaticMemberClass () {
Expand All @@ -48,16 +48,13 @@ static public <T> ConstructorAccess<T> get (Class<T> 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;
Expand All @@ -80,17 +77,18 @@ static public <T> ConstructorAccess<T> get (Class<T> 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);
Expand All @@ -100,7 +98,7 @@ static public <T> ConstructorAccess<T> get (Class<T> type) {
insertNewInstanceInner(cw, classNameInternal, enclosingClassNameInternal);

cw.visitEnd();
accessClass = loader.defineClass(accessClassName, cw.toByteArray());
accessClass = loader.defineAccessClass(accessClassName, cw.toByteArray());
}
}
}
Expand All @@ -110,14 +108,13 @@ static public <T> ConstructorAccess<T> get (Class<T> 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;
Expand Down
13 changes: 5 additions & 8 deletions src/com/esotericsoftware/reflectasm/FieldAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -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('.', '/');

Expand Down Expand Up @@ -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());
}
}
}
Expand Down
Loading

0 comments on commit da80699

Please sign in to comment.