org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState Maven / Gradle / Ivy
Show all versions of hibernate-core Show documentation
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.bytecode.internal.bytebuddy;
import static net.bytebuddy.matcher.ElementMatchers.isFinalizer;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.isVirtual;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static org.hibernate.internal.CoreLogging.messageLogger;
import java.io.File;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.bytecode.spi.BasicProxyFactory;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.proxy.ProxyConfiguration;
import org.hibernate.proxy.ProxyFactory;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.TypeCache;
import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods;
import net.bytebuddy.asm.MemberSubstitution;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.DynamicType.Unloaded;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
/**
* A utility to hold all ByteBuddy related state, as in the current version of
* Hibernate the Bytecode Provider state is held in a static field, yet ByteBuddy
* is able to benefit from some caching and general state reuse.
*/
public final class ByteBuddyState {
private static final CoreMessageLogger LOG = messageLogger( ByteBuddyState.class );
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final boolean DEBUG = false;
private final ByteBuddy byteBuddy;
private final ForDeclaredMethods getDeclaredMethodMemberSubstitution;
private final ForDeclaredMethods getMethodMemberSubstitution;
private final ProxyDefinitionHelpers proxyDefinitionHelpers;
/**
* It will be easier to maintain the cache and its state when it will no longer be static
* in Hibernate ORM 6+.
* Opted for WEAK keys to avoid leaking the classloader in case the SessionFactory isn't closed.
* Avoiding Soft keys as they are prone to cause issues with unstable performance.
*/
private final TypeCache proxyCache;
private final TypeCache basicProxyCache;
ByteBuddyState() {
this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED );
this.proxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK );
this.basicProxyCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK );
if ( System.getSecurityManager() != null ) {
this.getDeclaredMethodMemberSubstitution = getDeclaredMethodMemberSubstitution();
this.getMethodMemberSubstitution = getMethodMemberSubstitution();
}
else {
this.getDeclaredMethodMemberSubstitution = null;
this.getMethodMemberSubstitution = null;
}
this.proxyDefinitionHelpers = new ProxyDefinitionHelpers();
}
/**
* Load a proxy as generated by the {@link ProxyFactory}.
*
* @param referenceClass The main class to proxy - might be an interface.
* @param cacheKey The cache key.
* @param makeProxyFunction A function building the proxy.
* @return The loaded proxy class.
*/
public Class loadProxy(Class referenceClass, TypeCache.SimpleKey cacheKey,
Function> makeProxyFunction) {
return load( referenceClass, proxyCache, cacheKey, makeProxyFunction );
}
/**
* Load a proxy as generated by the {@link BasicProxyFactory}.
*
* @param referenceClass The main class to proxy - might be an interface.
* @param cacheKey The cache key.
* @param makeProxyFunction A function building the proxy.
* @return The loaded proxy class.
*/
Class loadBasicProxy(Class referenceClass, TypeCache.SimpleKey cacheKey,
Function> makeProxyFunction) {
return load( referenceClass, basicProxyCache, cacheKey, makeProxyFunction );
}
/**
* Load a class generated by ByteBuddy.
*
* @param referenceClass The main class to proxy - might be an interface.
* @param makeClassFunction A function building the class.
* @return The loaded generated class.
*/
public Class load(Class referenceClass, Function> makeClassFunction) {
return make( makeClassFunction.apply( byteBuddy ) )
.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) )
.getLoaded();
}
/**
* Rewrite a class, used by the enhancer.
*
* WARNING: Returns null if rewriteClassFunction returns a null builder. Do not use if you expect the original
* content.
*
* @param typePool the ByteBuddy TypePool
* @param className The original class name.
* @param rewriteClassFunction The function used to rewrite the class.
* @return The rewritten content of the class or null if rewriteClassFunction returns a null builder.
*/
public byte[] rewrite(TypePool typePool, String className,
Function> rewriteClassFunction) {
DynamicType.Builder builder = rewriteClassFunction.apply( byteBuddy );
if ( builder == null ) {
return null;
}
return make( typePool, builder ).getBytes();
}
/**
* Returns the proxy definition helpers to reuse when defining proxies.
*
* These elements are shared as they are immutable.
*
* @return The proxy definition helpers.
*/
public ProxyDefinitionHelpers getProxyDefinitionHelpers() {
return proxyDefinitionHelpers;
}
/**
* Wipes out all known caches used by ByteBuddy. This implies it might trigger the need
* to re-create some helpers if used at runtime, especially as this state is shared by
* multiple SessionFactory instances, but at least ensures we cleanup anything which is no
* longer needed after a SessionFactory close.
* The assumption is that closing SessionFactories is a rare event; in this perspective the cost
* of re-creating the small helpers should be negligible.
*/
void clearState() {
proxyCache.clear();
basicProxyCache.clear();
}
private Class load(Class referenceClass, TypeCache cache,
TypeCache.SimpleKey cacheKey, Function> makeProxyFunction) {
return cache.findOrInsert(
referenceClass.getClassLoader(),
cacheKey,
() -> make( makeProxyFunction.apply( byteBuddy ) )
.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) )
.getLoaded(),
cache );
}
private Unloaded make(DynamicType.Builder builder) {
return make( null, builder );
}
private Unloaded make(TypePool typePool, DynamicType.Builder builder) {
if ( System.getSecurityManager() != null ) {
builder = builder.visit( getDeclaredMethodMemberSubstitution );
builder = builder.visit( getMethodMemberSubstitution );
}
Unloaded unloadedClass;
if ( typePool != null ) {
unloadedClass = builder.make( typePool );
}
else {
unloadedClass = builder.make();
}
if ( DEBUG ) {
try {
unloadedClass.saveIn( new File( System.getProperty( "java.io.tmpdir" ) + "/bytebuddy/" ) );
}
catch (IOException e) {
LOG.warn( "Unable to save generated class %1$s", unloadedClass.getTypeDescription().getName(), e );
}
}
if ( System.getSecurityManager() != null ) {
// we authorize the proxy class to access the method lookup dispatcher
HibernateMethodLookupDispatcher.registerAuthorizedClass( unloadedClass.getTypeDescription().getName() );
}
return unloadedClass;
}
// This method is kept public static as it is also required by a test.
public static ClassLoadingStrategy resolveClassLoadingStrategy(Class originalClass) {
if ( ClassInjector.UsingLookup.isAvailable() ) {
// This is only enabled for JDK 9+
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod( "privateLookupIn", Class.class, MethodHandles.Lookup.class );
}
catch (Exception e) {
throw new HibernateException( LOG.bytecodeEnhancementFailed( originalClass.getName() ), e );
}
try {
Object privateLookup;
try {
privateLookup = privateLookupIn.invoke( null, originalClass, LOOKUP );
}
catch (InvocationTargetException exception) {
if ( exception.getCause() instanceof IllegalAccessException ) {
return new ClassLoadingStrategy.ForUnsafeInjection( originalClass.getProtectionDomain() );
}
else {
throw new HibernateException( LOG.bytecodeEnhancementFailed( originalClass.getName() ), exception.getCause() );
}
}
return ClassLoadingStrategy.UsingLookup.of( privateLookup );
}
catch (Throwable e) {
throw new HibernateException( LOG.bytecodeEnhancementFailedUnableToGetPrivateLookupFor( originalClass.getName() ), e );
}
}
else {
return new ClassLoadingStrategy.ForUnsafeInjection( originalClass.getProtectionDomain() );
}
}
private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() {
// this should only be called if the security manager is enabled, thus the privileged calls
return MemberSubstitution.relaxed()
.method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class,
"getDeclaredMethod", String.class, Class[].class ) ) ) )
.replaceWith(
AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class,
"getDeclaredMethod", Class.class, String.class, Class[].class ) ) )
.on( ElementMatchers.isTypeInitializer() );
}
private static ForDeclaredMethods getMethodMemberSubstitution() {
// this should only be called if the security manager is enabled, thus the privileged calls
return MemberSubstitution.relaxed()
.method( ElementMatchers.is( AccessController.doPrivileged( new GetDeclaredMethodAction( Class.class,
"getMethod", String.class, Class[].class ) ) ) )
.replaceWith(
AccessController.doPrivileged( new GetDeclaredMethodAction( HibernateMethodLookupDispatcher.class,
"getMethod", Class.class, String.class, Class[].class ) ) )
.on( ElementMatchers.isTypeInitializer() );
}
private static class GetDeclaredMethodAction implements PrivilegedAction {
private final Class clazz;
private final String methodName;
private final Class[] parameterTypes;
private GetDeclaredMethodAction(Class clazz, String methodName, Class... parameterTypes) {
this.clazz = clazz;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
}
@Override
public Method run() {
try {
Method method = clazz.getDeclaredMethod( methodName, parameterTypes );
return method;
}
catch (NoSuchMethodException e) {
throw new HibernateException( "Unable to prepare getDeclaredMethod()/getMethod() substitution", e );
}
}
}
/**
* Shared proxy definition helpers. They are immutable so we can safely share them.
*/
public static class ProxyDefinitionHelpers {
private final ElementMatcher groovyGetMetaClassFilter;
private final ElementMatcher virtualNotFinalizerFilter;
private final ElementMatcher hibernateGeneratedMethodFilter;
private final MethodDelegation delegateToInterceptorDispatcherMethodDelegation;
private final FieldAccessor.PropertyConfigurable interceptorFieldAccessor;
private ProxyDefinitionHelpers() {
this.groovyGetMetaClassFilter = isSynthetic().and( named( "getMetaClass" )
.and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) );
this.virtualNotFinalizerFilter = isVirtual().and( not( isFinalizer() ) );
this.hibernateGeneratedMethodFilter = nameStartsWith( "$$_hibernate_" ).and( isVirtual() );
PrivilegedAction delegateToInterceptorDispatcherMethodDelegationPrivilegedAction =
new PrivilegedAction() {
@Override
public MethodDelegation run() {
return MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class );
}
};
this.delegateToInterceptorDispatcherMethodDelegation = System.getSecurityManager() != null
? AccessController.doPrivileged( delegateToInterceptorDispatcherMethodDelegationPrivilegedAction )
: delegateToInterceptorDispatcherMethodDelegationPrivilegedAction.run();
PrivilegedAction interceptorFieldAccessorPrivilegedAction =
new PrivilegedAction() {
@Override
public FieldAccessor.PropertyConfigurable run() {
return FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME )
.withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC );
}
};
this.interceptorFieldAccessor = System.getSecurityManager() != null
? AccessController.doPrivileged( interceptorFieldAccessorPrivilegedAction )
: interceptorFieldAccessorPrivilegedAction.run();
}
public ElementMatcher getGroovyGetMetaClassFilter() {
return groovyGetMetaClassFilter;
}
public ElementMatcher getVirtualNotFinalizerFilter() {
return virtualNotFinalizerFilter;
}
public ElementMatcher getHibernateGeneratedMethodFilter() {
return hibernateGeneratedMethodFilter;
}
public MethodDelegation getDelegateToInterceptorDispatcherMethodDelegation() {
return delegateToInterceptorDispatcherMethodDelegation;
}
public FieldAccessor.PropertyConfigurable getInterceptorFieldAccessor() {
return interceptorFieldAccessor;
}
}
}