HHH-12857 Rewrite getDeclaredMethod() calls in static initializers

We rewrite them to run them as privileged blocks.
This commit is contained in:
Guillaume Smet 2018-07-25 15:44:13 +02:00
parent d95b36ffb6
commit dff202ded9
9 changed files with 559 additions and 72 deletions

View File

@ -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 );

View File

@ -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,29 +33,29 @@ 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()
.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 )
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
.method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) )
.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.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 )
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
.method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) )
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
.implement( ProxyConfiguration.class )
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
);
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 );
}
}

View File

@ -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 );
}
}
}
}

View File

@ -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(

View File

@ -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];
}
}
}

View File

@ -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,24 +68,19 @@ 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()
.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 )
.implement( (Type[]) interfaces )
.method( isVirtual().and( not( isFinalizer() ) ) )
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
.method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) )
.intercept( SuperMethodCall.INSTANCE )
.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 );
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 )
.implement( (Type[]) interfaces )
.method( isVirtual().and( not( isFinalizer() ) ) )
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
.method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) )
.intercept( SuperMethodCall.INSTANCE )
.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 ) )
);
}
public HibernateProxy deserializeProxy(SerializableProxy serializableProxy) {

View File

@ -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" ) ) );
}
}

View File

@ -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) {
}
}
}

View File

@ -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;
}
}