HHH-12857 Rewrite getDeclaredMethod() calls in static initializers
We rewrite them to run them as privileged blocks.
This commit is contained in:
parent
d95b36ffb6
commit
dff202ded9
|
@ -6,6 +6,9 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.enhance.internal.bytebuddy;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isGetter;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
|
@ -13,6 +16,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Transient;
|
||||
|
@ -26,10 +30,10 @@ import org.hibernate.bytecode.enhance.spi.Enhancer;
|
|||
import org.hibernate.bytecode.enhance.spi.EnhancerConstants;
|
||||
import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
|
||||
import org.hibernate.bytecode.internal.bytebuddy.ByteBuddyState;
|
||||
import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.CompositeOwner;
|
||||
import org.hibernate.engine.spi.CompositeTracker;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.Managed;
|
||||
import org.hibernate.engine.spi.ManagedComposite;
|
||||
import org.hibernate.engine.spi.ManagedEntity;
|
||||
|
@ -40,7 +44,6 @@ import org.hibernate.engine.spi.SelfDirtinessTracker;
|
|||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.asm.Advice;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.field.FieldDescription;
|
||||
|
@ -53,16 +56,12 @@ import net.bytebuddy.description.type.TypeDescription;
|
|||
import net.bytebuddy.dynamic.ClassFileLocator;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.scaffold.MethodGraph;
|
||||
import net.bytebuddy.dynamic.scaffold.TypeValidation;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
import net.bytebuddy.implementation.FixedValue;
|
||||
import net.bytebuddy.implementation.Implementation;
|
||||
import net.bytebuddy.implementation.StubMethod;
|
||||
import net.bytebuddy.pool.TypePool;
|
||||
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isDefaultFinalizer;
|
||||
import static net.bytebuddy.matcher.ElementMatchers.isGetter;
|
||||
|
||||
public class EnhancerImpl implements Enhancer {
|
||||
|
||||
private static final CoreMessageLogger log = CoreLogging.messageLogger( Enhancer.class );
|
||||
|
@ -101,17 +100,12 @@ public class EnhancerImpl implements Enhancer {
|
|||
//Classpool#describe does not accept '/' in the description name as it expects a class name. See HHH-12545
|
||||
final String safeClassName = className.replace( '/', '.' );
|
||||
try {
|
||||
final TypeDescription managedCtClass = classPool.describe( safeClassName ).resolve();
|
||||
DynamicType.Builder<?> builder = doEnhance(
|
||||
byteBuddyState.getCurrentByteBuddy().ignore( isDefaultFinalizer() ).redefine( managedCtClass, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ),
|
||||
managedCtClass
|
||||
);
|
||||
if ( builder == null ) {
|
||||
return originalBytes;
|
||||
}
|
||||
else {
|
||||
return builder.make().getBytes();
|
||||
}
|
||||
final TypeDescription typeDescription = classPool.describe( safeClassName ).resolve();
|
||||
|
||||
return byteBuddyState.rewrite( safeClassName, originalBytes, byteBuddy -> doEnhance(
|
||||
byteBuddy.ignore( isDefaultFinalizer() ).redefine( typeDescription, ClassFileLocator.Simple.of( safeClassName, originalBytes ) ),
|
||||
typeDescription
|
||||
) );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw new EnhancementException( "Failed to enhance class " + className, e );
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
*/
|
||||
package org.hibernate.bytecode.internal.bytebuddy;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.bytecode.spi.BasicProxyFactory;
|
||||
|
@ -13,6 +17,7 @@ import org.hibernate.cfg.Environment;
|
|||
import org.hibernate.proxy.ProxyConfiguration;
|
||||
|
||||
import net.bytebuddy.NamingStrategy;
|
||||
import net.bytebuddy.TypeCache;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
|
||||
import net.bytebuddy.implementation.FieldAccessor;
|
||||
|
@ -28,15 +33,16 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory {
|
|||
private final Class proxyClass;
|
||||
private final ProxyConfiguration.Interceptor interceptor;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState bytebuddy) {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public BasicProxyFactoryImpl(Class superClass, Class[] interfaces, ByteBuddyState byteBuddyState) {
|
||||
if ( superClass == null && ( interfaces == null || interfaces.length < 1 ) ) {
|
||||
throw new AssertionFailure( "attempting to build proxy without any superclass or interfaces" );
|
||||
}
|
||||
|
||||
final Class<?> superClassOrMainInterface = superClass != null ? superClass : interfaces[0];
|
||||
final TypeCache.SimpleKey cacheKey = getCacheKey( superClass, interfaces );
|
||||
|
||||
this.proxyClass = bytebuddy.getCurrentByteBuddy()
|
||||
this.proxyClass = byteBuddyState.loadBasicProxy( superClassOrMainInterface, cacheKey, byteBuddy -> byteBuddy
|
||||
.with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( superClassOrMainInterface.getName() ) ) )
|
||||
.subclass( superClass == null ? Object.class : superClass, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR )
|
||||
.implement( interfaces == null ? NO_INTERFACES : interfaces )
|
||||
|
@ -45,12 +51,11 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory {
|
|||
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
|
||||
.implement( ProxyConfiguration.class )
|
||||
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
||||
.make()
|
||||
.load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) )
|
||||
.getLoaded();
|
||||
);
|
||||
this.interceptor = new PassThroughInterceptor( proxyClass.getName() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getProxy() {
|
||||
try {
|
||||
final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.newInstance();
|
||||
|
@ -65,4 +70,16 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory {
|
|||
public boolean isInstance(Object object) {
|
||||
return proxyClass.isInstance( object );
|
||||
}
|
||||
|
||||
private TypeCache.SimpleKey getCacheKey(Class<?> superClass, Class<?>[] interfaces) {
|
||||
Set<Class<?>> key = new HashSet<Class<?>>();
|
||||
if ( superClass != null ) {
|
||||
key.add( superClass );
|
||||
}
|
||||
if ( interfaces != null ) {
|
||||
key.addAll( Arrays.<Class<?>>asList( interfaces ) );
|
||||
}
|
||||
|
||||
return new TypeCache.SimpleKey( key );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,57 +8,129 @@ package org.hibernate.bytecode.internal.bytebuddy;
|
|||
|
||||
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.pojo.bytebuddy.ByteBuddyProxyFactory;
|
||||
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.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.matcher.ElementMatchers;
|
||||
|
||||
/**
|
||||
* An utility to hold all ByteBuddy related state, as in the current version of
|
||||
* 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( ByteBuddyProxyFactory.class );
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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<TypeCache.SimpleKey> typeCache;
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
private final TypeCache<TypeCache.SimpleKey> proxyCache;
|
||||
private final TypeCache<TypeCache.SimpleKey> basicProxyCache;
|
||||
|
||||
ByteBuddyState() {
|
||||
this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED );
|
||||
this.typeCache = new TypeCache.WithInlineExpunction<TypeCache.SimpleKey>( TypeCache.Sort.WEAK );
|
||||
|
||||
this.proxyCache = new TypeCache.WithInlineExpunction<TypeCache.SimpleKey>( TypeCache.Sort.WEAK );
|
||||
this.basicProxyCache = new TypeCache.WithInlineExpunction<TypeCache.SimpleKey>( TypeCache.Sort.WEAK );
|
||||
|
||||
if ( System.getSecurityManager() != null ) {
|
||||
this.getDeclaredMethodMemberSubstitution = getDeclaredMethodMemberSubstitution();
|
||||
this.getMethodMemberSubstitution = getMethodMemberSubstitution();
|
||||
}
|
||||
else {
|
||||
this.getDeclaredMethodMemberSubstitution = null;
|
||||
this.getMethodMemberSubstitution = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to ByteBuddy. It's almost equivalent to creating a new ByteBuddy instance,
|
||||
* yet slightly preferrable so to be able to reuse the same instance.
|
||||
* @return
|
||||
* 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 ByteBuddy getCurrentByteBuddy() {
|
||||
return byteBuddy;
|
||||
public Class<?> loadProxy(Class<?> referenceClass, TypeCache.SimpleKey cacheKey,
|
||||
Function<ByteBuddy, DynamicType.Builder<?>> makeProxyFunction) {
|
||||
return load( referenceClass, proxyCache, cacheKey, makeProxyFunction );
|
||||
}
|
||||
|
||||
public TypeCache<TypeCache.SimpleKey> getCacheForProxies() {
|
||||
return typeCache;
|
||||
/**
|
||||
* 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<ByteBuddy, DynamicType.Builder<?>> 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<ByteBuddy, DynamicType.Builder<?>> makeClassFunction) {
|
||||
return make( makeClassFunction.apply( byteBuddy ) )
|
||||
.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) )
|
||||
.getLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a class, used by the enhancer.
|
||||
*
|
||||
* @param className The original class name.
|
||||
* @param originalBytes The original content of the class.
|
||||
* @param rewriteClassFunction The function used to rewrite the class.
|
||||
* @return The rewritten content of the class.
|
||||
*/
|
||||
public byte[] rewrite(String className, byte[] originalBytes,
|
||||
Function<ByteBuddy, DynamicType.Builder<?>> rewriteClassFunction) {
|
||||
DynamicType.Builder<?> builder = rewriteClassFunction.apply( byteBuddy );
|
||||
if ( builder == null ) {
|
||||
return originalBytes;
|
||||
}
|
||||
|
||||
return make( builder ).getBytes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,9 +142,46 @@ public final class ByteBuddyState {
|
|||
* of re-creating the small helpers should be negligible.
|
||||
*/
|
||||
void clearState() {
|
||||
typeCache.clear();
|
||||
proxyCache.clear();
|
||||
basicProxyCache.clear();
|
||||
}
|
||||
|
||||
private Class<?> load(Class<?> referenceClass, TypeCache<TypeCache.SimpleKey> cache,
|
||||
TypeCache.SimpleKey cacheKey, Function<ByteBuddy, DynamicType.Builder<?>> makeProxyFunction) {
|
||||
return cache.findOrInsert(
|
||||
referenceClass.getClassLoader(),
|
||||
cacheKey,
|
||||
() -> make( makeProxyFunction.apply( byteBuddy ) )
|
||||
.load( referenceClass.getClassLoader(), resolveClassLoadingStrategy( referenceClass ) )
|
||||
.getLoaded(),
|
||||
cache );
|
||||
}
|
||||
|
||||
private Unloaded<?> make(DynamicType.Builder<?> builder) {
|
||||
if ( System.getSecurityManager() != null ) {
|
||||
builder = builder.visit( getDeclaredMethodMemberSubstitution );
|
||||
builder = builder.visit( getMethodMemberSubstitution );
|
||||
}
|
||||
|
||||
Unloaded<?> 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<ClassLoader> resolveClassLoadingStrategy(Class<?> originalClass) {
|
||||
if ( ClassInjector.UsingLookup.isAvailable() ) {
|
||||
// This is only enabled for JDK 9+
|
||||
|
@ -111,4 +220,47 @@ public final class ByteBuddyState {
|
|||
}
|
||||
}
|
||||
|
||||
private static ForDeclaredMethods getDeclaredMethodMemberSubstitution() {
|
||||
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() {
|
||||
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<Method> {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,15 +70,13 @@ public class BytecodeProviderImpl implements BytecodeProvider {
|
|||
// we only provide a fast class instantiator if the class can be instantiated
|
||||
final Constructor<?> constructor = findConstructor( clazz );
|
||||
|
||||
fastClass = byteBuddyState.getCurrentByteBuddy()
|
||||
fastClass = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
|
||||
.with( new NamingStrategy.SuffixingRandom( INSTANTIATOR_PROXY_NAMING_SUFFIX,
|
||||
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
|
||||
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
|
||||
.method( newInstanceMethodName )
|
||||
.intercept( MethodCall.construct( constructor ) )
|
||||
.make()
|
||||
.load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) )
|
||||
.getLoaded();
|
||||
);
|
||||
}
|
||||
else {
|
||||
fastClass = null;
|
||||
|
@ -88,7 +86,7 @@ public class BytecodeProviderImpl implements BytecodeProvider {
|
|||
final Method[] setters = new Method[setterNames.length];
|
||||
findAccessors( clazz, getterNames, setterNames, types, getters, setters );
|
||||
|
||||
final Class bulkAccessor = byteBuddyState.getCurrentByteBuddy()
|
||||
final Class bulkAccessor = byteBuddyState.load( clazz, byteBuddy -> byteBuddy
|
||||
.with( new NamingStrategy.SuffixingRandom( OPTIMIZER_PROXY_NAMING_SUFFIX,
|
||||
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
|
||||
.subclass( ReflectionOptimizer.AccessOptimizer.class )
|
||||
|
@ -98,9 +96,7 @@ public class BytecodeProviderImpl implements BytecodeProvider {
|
|||
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
|
||||
.method( getPropertyNamesMethodName )
|
||||
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
|
||||
.make()
|
||||
.load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) )
|
||||
.getLoaded();
|
||||
);
|
||||
|
||||
try {
|
||||
return new ReflectionOptimizerImpl(
|
||||
|
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.bytebuddy;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
|
||||
public class HibernateMethodLookupDispatcher {
|
||||
|
||||
private static final SecurityActions SECURITY_ACTIONS = new SecurityActions();
|
||||
|
||||
private static final Function<Object, Class<?>> STACK_FRAME_GET_DECLARING_CLASS_FUNCTION;
|
||||
private static Object stackWalker;
|
||||
private static Method stackWalkerWalkMethod;
|
||||
private static Method stackFrameGetDeclaringClass;
|
||||
|
||||
// Currently, the bytecode provider is created statically and shared between all the session factories. Thus we
|
||||
// can't clear this set when we close a session factory as we might remove elements coming from another one.
|
||||
// Considering we can't clear these elements, we use the class names instead of the classes themselves to avoid
|
||||
// issues.
|
||||
private static Set<String> authorizedClasses = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public static Method getDeclaredMethod(Class<?> type, String name, Class<?>[] parameters) {
|
||||
PrivilegedAction<Method> getDeclaredMethodAction = new PrivilegedAction<Method>() {
|
||||
|
||||
@Override
|
||||
public Method run() {
|
||||
try {
|
||||
return type.getDeclaredMethod( name, parameters );
|
||||
}
|
||||
catch (NoSuchMethodException | SecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return doPrivilegedAction( getDeclaredMethodAction );
|
||||
}
|
||||
|
||||
public static Method getMethod(Class<?> type, String name, Class<?>[] parameters) {
|
||||
PrivilegedAction<Method> getMethodAction = new PrivilegedAction<Method>() {
|
||||
|
||||
@Override
|
||||
public Method run() {
|
||||
try {
|
||||
return type.getMethod( name, parameters );
|
||||
}
|
||||
catch (NoSuchMethodException | SecurityException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return doPrivilegedAction( getMethodAction );
|
||||
}
|
||||
|
||||
private static Method doPrivilegedAction(PrivilegedAction<Method> privilegedAction) {
|
||||
Class<?> callerClass = getCallerClass();
|
||||
|
||||
if ( !authorizedClasses.contains( callerClass.getName() ) ) {
|
||||
throw new SecurityException( "Unauthorized call by class " + callerClass );
|
||||
}
|
||||
|
||||
return System.getSecurityManager() != null ? AccessController.doPrivileged( privilegedAction ) :
|
||||
privilegedAction.run();
|
||||
}
|
||||
|
||||
static void registerAuthorizedClass(String className) {
|
||||
authorizedClasses.add( className );
|
||||
}
|
||||
|
||||
static {
|
||||
PrivilegedAction<Void> initializeGetCallerClassRequirementsAction = new PrivilegedAction<Void>() {
|
||||
|
||||
@Override
|
||||
public Void run() {
|
||||
Class<?> stackWalkerClass = null;
|
||||
try {
|
||||
stackWalkerClass = Class.forName( "java.lang.StackWalker" );
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
// ignore, we will deal with that later.
|
||||
}
|
||||
|
||||
if ( stackWalkerClass != null ) {
|
||||
try {
|
||||
Class<?> optionClass = Class.forName( "java.lang.StackWalker$Option" );
|
||||
stackWalker = stackWalkerClass.getMethod( "getInstance", optionClass )
|
||||
// The first one is RETAIN_CLASS_REFERENCE
|
||||
.invoke( null, optionClass.getEnumConstants()[0] );
|
||||
|
||||
stackWalkerWalkMethod = stackWalkerClass.getMethod( "walk", Function.class );
|
||||
stackFrameGetDeclaringClass = Class.forName( "java.lang.StackWalker$StackFrame" )
|
||||
.getMethod( "getDeclaringClass" );
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw new HibernateException( "Unable to initialize the stack walker", e );
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
if ( System.getSecurityManager() != null ) {
|
||||
AccessController.doPrivileged( initializeGetCallerClassRequirementsAction );
|
||||
}
|
||||
else {
|
||||
initializeGetCallerClassRequirementsAction.run();
|
||||
}
|
||||
|
||||
STACK_FRAME_GET_DECLARING_CLASS_FUNCTION = new Function<Object, Class<?>>() {
|
||||
@Override
|
||||
public Class<?> apply(Object t) {
|
||||
try {
|
||||
return (Class<?>) stackFrameGetDeclaringClass.invoke( t );
|
||||
}
|
||||
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new HibernateException( "Unable to get stack frame declaring class", e );
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Class<?> getCallerClass() {
|
||||
PrivilegedAction<Class<?>> getCallerClassAction = new PrivilegedAction<Class<?>>() {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public Class<?> run() {
|
||||
try {
|
||||
if ( stackWalker != null ) {
|
||||
Optional<Class<?>> clazzOptional = (Optional<Class<?>>) stackWalkerWalkMethod.invoke( stackWalker, new Function<Stream, Object>() {
|
||||
@Override
|
||||
public Object apply(Stream stream) {
|
||||
return stream.map( STACK_FRAME_GET_DECLARING_CLASS_FUNCTION )
|
||||
.skip( System.getSecurityManager() != null ? 6 : 5 )
|
||||
.findFirst();
|
||||
}
|
||||
});
|
||||
|
||||
if ( !clazzOptional.isPresent() ) {
|
||||
throw new HibernateException( "Unable to determine the caller class" );
|
||||
}
|
||||
|
||||
return clazzOptional.get();
|
||||
}
|
||||
else {
|
||||
return SECURITY_ACTIONS.getCallerClass();
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||
throw new SecurityException( "Unable to determine the caller class", e );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return System.getSecurityManager() != null ? AccessController.doPrivileged( getCallerClassAction ) :
|
||||
getCallerClassAction.run();
|
||||
}
|
||||
|
||||
private static class SecurityActions extends SecurityManager {
|
||||
|
||||
private Class<?> getCallerClass() {
|
||||
return getClassContext()[7];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,6 +58,7 @@ public class ByteBuddyProxyHelper implements Serializable {
|
|||
this.byteBuddyState = byteBuddyState;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public Class buildProxy(
|
||||
final Class persistentClass,
|
||||
final Class[] interfaces) {
|
||||
|
@ -67,10 +68,7 @@ public class ByteBuddyProxyHelper implements Serializable {
|
|||
}
|
||||
key.addAll( Arrays.<Class<?>>asList( interfaces ) );
|
||||
|
||||
final TypeCache<TypeCache.SimpleKey> cacheForProxies = byteBuddyState.getCacheForProxies();
|
||||
|
||||
return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () ->
|
||||
byteBuddyState.getCurrentByteBuddy()
|
||||
return byteBuddyState.loadProxy( persistentClass, new TypeCache.SimpleKey(key), byteBuddy -> byteBuddy
|
||||
.ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) )
|
||||
.with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) )
|
||||
.subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING )
|
||||
|
@ -82,9 +80,7 @@ public class ByteBuddyProxyHelper implements Serializable {
|
|||
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
||||
.implement( ProxyConfiguration.class )
|
||||
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
||||
.make()
|
||||
.load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) )
|
||||
.getLoaded(), cacheForProxies );
|
||||
);
|
||||
}
|
||||
|
||||
public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) {
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package org.hibernate.bytecode.internal.bytebuddy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import org.hibernate.bytecode.enhance.internal.bytebuddy.EnhancerImpl;
|
||||
import org.hibernate.bytecode.enhance.spi.DefaultEnhancementContext;
|
||||
import org.hibernate.bytecode.enhance.spi.EnhancementException;
|
||||
import org.hibernate.bytecode.enhance.spi.Enhancer;
|
||||
import org.hibernate.bytecode.spi.ByteCodeHelper;
|
||||
import org.hibernate.bytecode.spi.ReflectionOptimizer;
|
||||
import org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GenerateProxiesTest {
|
||||
|
||||
@Test
|
||||
public void generateBasicProxy() {
|
||||
BasicProxyFactoryImpl basicProxyFactory = new BasicProxyFactoryImpl( SimpleEntity.class, new Class<?>[0],
|
||||
new ByteBuddyState() );
|
||||
assertNotNull( basicProxyFactory.getProxy() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateProxy() throws InstantiationException, IllegalAccessException, IllegalArgumentException,
|
||||
InvocationTargetException, NoSuchMethodException, SecurityException {
|
||||
ByteBuddyProxyHelper byteBuddyProxyHelper = new ByteBuddyProxyHelper( new ByteBuddyState() );
|
||||
Class<?> proxyClass = byteBuddyProxyHelper.buildProxy( SimpleEntity.class, new Class<?>[0] );
|
||||
assertNotNull( proxyClass );
|
||||
assertNotNull( proxyClass.getConstructor().newInstance() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateFastClassAndReflectionOptimizer() {
|
||||
BytecodeProviderImpl bytecodeProvider = new BytecodeProviderImpl();
|
||||
ReflectionOptimizer reflectionOptimizer = bytecodeProvider.getReflectionOptimizer( SimpleEntity.class,
|
||||
new String[]{ "getId", "getName" }, new String[]{ "setId", "setName" },
|
||||
new Class<?>[]{ Long.class, String.class } );
|
||||
assertEquals( 2, reflectionOptimizer.getAccessOptimizer().getPropertyNames().length );
|
||||
assertNotNull( reflectionOptimizer.getInstantiationOptimizer().newInstance() );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateEnhancedClass() throws EnhancementException, IOException {
|
||||
Enhancer enhancer = new EnhancerImpl( new DefaultEnhancementContext(), new ByteBuddyState() );
|
||||
enhancer.enhance( SimpleEntity.class.getName(),
|
||||
ByteCodeHelper.readByteCode( SimpleEntity.class.getClassLoader()
|
||||
.getResourceAsStream( SimpleEntity.class.getName().replace( '.', '/' ) + ".class" ) ) );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.bytebuddy;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class HibernateMethodLookupDispatcherTest {
|
||||
|
||||
@Test
|
||||
public void testAuthorizedClass() {
|
||||
HibernateMethodLookupDispatcher.registerAuthorizedClass( AuthorizedClass.class.getName() );
|
||||
|
||||
AuthorizedClass authorizedClass = new AuthorizedClass();
|
||||
assertNotNull( authorizedClass.declaredMethod );
|
||||
assertEquals( "myMethod", authorizedClass.declaredMethod.getName() );
|
||||
}
|
||||
|
||||
@Test( expected = SecurityException.class )
|
||||
public void testUnauthorizedClass() {
|
||||
new UnauthorizedClass();
|
||||
}
|
||||
|
||||
public static class AuthorizedClass {
|
||||
|
||||
private Method declaredMethod;
|
||||
|
||||
public AuthorizedClass() {
|
||||
declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod",
|
||||
new Class<?>[]{ String.class } );
|
||||
}
|
||||
|
||||
public void myMethod(String myParameter) {
|
||||
}
|
||||
}
|
||||
|
||||
public static class UnauthorizedClass {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private Method declaredMethod;
|
||||
|
||||
public UnauthorizedClass() {
|
||||
declaredMethod = HibernateMethodLookupDispatcher.getDeclaredMethod( AuthorizedClass.class, "myMethod",
|
||||
new Class<?>[]{ String.class } );
|
||||
}
|
||||
|
||||
public void myMethod(String myParameter) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.bytecode.internal.bytebuddy;
|
||||
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity(name = "SimpleEntity")
|
||||
public class SimpleEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
private String name;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue