net.openhft.chronicle.values.BytecodeGen Maven / Gradle / Ivy
/*
* Copyright 2016-2021 chronicle.software
*
* https://chronicle.software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.openhft.chronicle.values;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.core.Jvm;
import net.openhft.chronicle.core.util.WeakIdentityHashMap;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Map;
/**
* Stripped down version of classes
* https://github.com/google/guice/blob/9867f9c2142355ae958f9eeb8fb96811082c8812/core/src/com/google/inject/internal/InternalFlags.java
* and
* https://github.com/google/guice/blob/09fec22916264e76d95333b81c69030b3b713e64/core/src/com/google/inject/internal/BytecodeGen.java
*
*
When loading classes, we need to be careful of:
*
* - Memory leaks. Generated classes need to be garbage collected in long-lived
* applications. Once an injector and any instances it created can be garbage collected, the
* corresponding generated classes should be collectable.
*
- Visibility. Containers like
OSGi
use class loader boundaries
* to enforce modularity at runtime.
*
*
*
For each generated class, there's multiple class loaders involved:
*
* - The related class's class loader. Every generated class services exactly
* one user-supplied class. This class loader must be used to access members with protected
* and package visibility.
*
- Values's class loader.
*
- Our bridge class loader. This is a child of the user's class loader. It
* selectively delegates to either the user's class loader (for user classes) or the Values
* class loader (for internal classes that are used by the generated classes). This class
* loader that owns the classes generated by Values.
*
*
* @author [email protected] (Stuart McCulloch)
* @author [email protected] (Jesse Wilson)
*/
final class BytecodeGen {
static final ClassLoader VALUES_CLASS_LOADER = canonicalize(BytecodeGen.class.getClassLoader());
/**
* ie. "net.openhft.chronicle.values"
*/
static final String VALUES_PACKAGE =
BytecodeGen.class.getName().replaceFirst("\\.values\\..*$", ".values");
/**
* ie. "net.openhft.chronicle.bytes"
*/
static final String BYTES_PACKAGE =
Bytes.class.getName().replaceFirst("\\.bytes\\..*$", ".bytes");
private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING =
parseCustomClassLoadingOption();
/**
* Weak cache of bridge class loaders that make the Chronicle Values implementation
* classes visible to various code-generated proxies of client classes.
*/
private static final Map> CLASS_LOADER_CACHE =
new WeakIdentityHashMap<>();
public static CustomClassLoadingOption getCustomClassLoadingOption() {
return CUSTOM_CLASS_LOADING;
}
private static CustomClassLoadingOption parseCustomClassLoadingOption() {
return getSystemOption("chronicle_values_custom_class_loading",
CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF);
}
/**
* Gets the system option indicated by the specified key; runs as a privileged action.
*
* @param name of the system option
* @param defaultValue if the option is not set
* @param secureValue if the security manager disallows access to the option
* @return value of the option, defaultValue if not set, secureValue if no access
*/
private static > T getSystemOption(final String name, T defaultValue,
T secureValue) {
Class enumType = defaultValue.getDeclaringClass();
String value = null;
try {
value = AccessController.doPrivileged(
(PrivilegedAction) () -> Jvm.getProperty(name));
return (value != null && value.length() > 0) ? Enum.valueOf(enumType, value) :
defaultValue;
} catch (SecurityException e) {
return secureValue;
} catch (IllegalArgumentException e) {
Jvm.warn().on(BytecodeGen.class,
value + " is not a valid flag value for " + name + ". "
+ " Values must be one of " + Arrays.asList(enumType.getEnumConstants()));
return defaultValue;
}
}
private static ClassLoader getFromClassLoaderCache(ClassLoader typeClassLoader) {
synchronized (CLASS_LOADER_CACHE) {
return CLASS_LOADER_CACHE.compute(typeClassLoader, (k, ref) -> {
if (ref == null || ref.get() == null) {
Jvm.debug().on(BytecodeGen.class,
"Creating a bridge ClassLoader for " + typeClassLoader);
return AccessController.doPrivileged(
(PrivilegedAction>) () ->
new WeakReference<>(new BridgeClassLoader(typeClassLoader)));
} else {
return ref;
}
}).get();
}
}
/**
* Attempts to canonicalize null references to the system class loader.
* May return null if for some reason the system loader is unavailable.
*/
private static ClassLoader canonicalize(ClassLoader classLoader) {
return classLoader != null ? classLoader : SystemBridgeHolder.SYSTEM_BRIDGE.getParent();
}
/**
* Returns the class loader to host generated classes for {@code type}.
*/
public static ClassLoader getClassLoader(Class> type) {
return getClassLoader(type, type.getClassLoader());
}
private static ClassLoader getClassLoader(Class> type, ClassLoader delegate) {
// simple case: do nothing!
if (getCustomClassLoadingOption() == CustomClassLoadingOption.OFF) {
return delegate;
}
// java.* types can be seen everywhere
if (type.getName().startsWith("java.")) {
return VALUES_CLASS_LOADER;
}
delegate = canonicalize(delegate);
// no need for a bridge if using same class loader, or it's already a bridge
if (delegate == VALUES_CLASS_LOADER || delegate instanceof BridgeClassLoader) {
return delegate;
}
// don't try bridging private types as it won't work
if (Visibility.forType(type) == Visibility.PUBLIC) {
if (delegate != SystemBridgeHolder.SYSTEM_BRIDGE.getParent()) {
// delegate guaranteed to be non-null here
return getFromClassLoaderCache(delegate);
}
// delegate may or may not be null here
return SystemBridgeHolder.SYSTEM_BRIDGE;
}
return delegate; // last-resort: do nothing!
}
/**
* The options for Values custom class loading.
*/
public enum CustomClassLoadingOption {
/**
* No custom class loading
*/
OFF,
/**
* Automatically bridge between class loaders (Default)
*/
BRIDGE
}
/**
* The required visibility of a user's class from a Values-generated class. Visibility of
* package-private members depends on the loading classloader: only if two classes were loaded
* by the same classloader can they see each other's package-private members. We need to be
* careful when choosing which classloader to use for generated classes. We prefer our bridge
* classloader, since it's OSGi-safe and doesn't leak permgen space. But often we cannot due to
* visibility.
*/
public enum Visibility {
/**
* Indicates that Values-generated classes only need to call and override public members of
* the target class. These generated classes may be loaded by our bridge classloader.
*/
PUBLIC {
@Override
public Visibility and(Visibility that) {
return that;
}
},
/**
* Indicates that Values-generated classes need to call or override package-private members.
* These generated classes must be loaded in the same classloader as the target class. They
* won't work with OSGi, and won't get garbage collected until the target class' classloader
* is garbage collected.
*/
SAME_PACKAGE {
@Override
public Visibility and(Visibility that) {
return this;
}
};
public static Visibility forType(Class> type) {
return (type.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) != 0
? PUBLIC
: SAME_PACKAGE;
}
public abstract Visibility and(Visibility that);
}
// initialization-on-demand...
private static class SystemBridgeHolder {
static final BridgeClassLoader SYSTEM_BRIDGE = new BridgeClassLoader();
}
/**
* Loader for Values-generated classes. For referenced classes, this delegates to either either
* the user's classloader (which is the parent of this classloader) or Values's class loader.
*/
static class BridgeClassLoader extends ClassLoader {
BridgeClassLoader() {
// use system loader as parent
}
BridgeClassLoader(ClassLoader usersClassLoader) {
super(usersClassLoader);
}
@Override
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (name.startsWith("sun.reflect")) {
// these reflection classes must be loaded from bootstrap class loader
return SystemBridgeHolder.SYSTEM_BRIDGE.classicLoadClass(name, resolve);
}
if (name.startsWith(VALUES_PACKAGE) || name.startsWith(BYTES_PACKAGE)) {
if (null == VALUES_CLASS_LOADER) {
// use special system bridge to load classes from bootstrap class loader
return SystemBridgeHolder.SYSTEM_BRIDGE.classicLoadClass(name, resolve);
}
try {
Class> clazz = VALUES_CLASS_LOADER.loadClass(name);
if (resolve) {
resolveClass(clazz);
}
return clazz;
} catch (Throwable e) {
// fall-back to classic delegation
}
}
return classicLoadClass(name, resolve);
}
// make the classic delegating loadClass method visible
Class> classicLoadClass(String name, boolean resolve)
throws ClassNotFoundException {
return super.loadClass(name, resolve);
}
}
}