diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java index 28fe6091cc..65b898c8c5 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/EnhancerImpl.java @@ -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.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.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.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.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 synchronized byte[] enhance(String className, byte[] originalBytes) throw //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 ); diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java index 4933987969..46ba636b43 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BasicProxyFactoryImpl.java @@ -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.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 Object getProxy() { public boolean isInstance(Object object) { return proxyClass.isInstance( object ); } + + private TypeCache.SimpleKey getCacheKey(Class superClass, Class[] interfaces) { + Set> key = new HashSet>(); + if ( superClass != null ) { + key.add( superClass ); + } + if ( interfaces != null ) { + key.addAll( Arrays.>asList( interfaces ) ); + } + + return new TypeCache.SimpleKey( key ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java index 56d6eaca1b..9b14b8fe01 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/ByteBuddyState.java @@ -8,57 +8,129 @@ 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; - - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private final TypeCache proxyCache; + private final TypeCache basicProxyCache; ByteBuddyState() { this.byteBuddy = new ByteBuddy().with( TypeValidation.DISABLED ); - this.typeCache = new TypeCache.WithInlineExpunction( TypeCache.Sort.WEAK ); + + 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; + } } /** - * 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> makeProxyFunction) { + return load( referenceClass, proxyCache, cacheKey, makeProxyFunction ); } - public TypeCache 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> 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. + * + * @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> rewriteClassFunction) { + DynamicType.Builder builder = rewriteClassFunction.apply( byteBuddy ); + if ( builder == null ) { + return originalBytes; + } + + return make( builder ).getBytes(); } /** @@ -70,9 +142,46 @@ public TypeCache getCacheForProxies() { * of re-creating the small helpers should be negligible. */ void clearState() { - typeCache.clear(); + 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) { + 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 resolveClassLoadingStrategy(Class originalClass) { if ( ClassInjector.UsingLookup.isAvailable() ) { // This is only enabled for JDK 9+ @@ -111,4 +220,47 @@ public static ClassLoadingStrategy resolveClassLoadingStrategy(Clas } } + 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 { + 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 ); + } + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java index d1c589e51b..15f4c1ecc8 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/BytecodeProviderImpl.java @@ -70,15 +70,13 @@ public ReflectionOptimizer getReflectionOptimizer( // 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 ReflectionOptimizer getReflectionOptimizer( 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 ReflectionOptimizer getReflectionOptimizer( .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( diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java new file mode 100644 index 0000000000..bb3b287f6a --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcher.java @@ -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 . + */ +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> 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 authorizedClasses = ConcurrentHashMap.newKeySet(); + + public static Method getDeclaredMethod(Class type, String name, Class[] parameters) { + PrivilegedAction getDeclaredMethodAction = new PrivilegedAction() { + + @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 getMethodAction = new PrivilegedAction() { + + @Override + public Method run() { + try { + return type.getMethod( name, parameters ); + } + catch (NoSuchMethodException | SecurityException e) { + return null; + } + } + }; + + return doPrivilegedAction( getMethodAction ); + } + + private static Method doPrivilegedAction(PrivilegedAction 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 initializeGetCallerClassRequirementsAction = new PrivilegedAction() { + + @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>() { + @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> getCallerClassAction = new PrivilegedAction>() { + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Class run() { + try { + if ( stackWalker != null ) { + Optional> clazzOptional = (Optional>) stackWalkerWalkMethod.invoke( stackWalker, new Function() { + @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]; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java index b453d76d4d..f94cf4b943 100644 --- a/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/proxy/pojo/bytebuddy/ByteBuddyProxyHelper.java @@ -58,6 +58,7 @@ public ByteBuddyProxyHelper(ByteBuddyState byteBuddyState) { this.byteBuddyState = byteBuddyState; } + @SuppressWarnings({ "unchecked", "rawtypes" }) public Class buildProxy( final Class persistentClass, final Class[] interfaces) { @@ -67,24 +68,19 @@ public Class buildProxy( } key.addAll( Arrays.>asList( interfaces ) ); - final TypeCache 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) { diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java new file mode 100644 index 0000000000..3c016a2e94 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/GenerateProxiesTest.java @@ -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" ) ) ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java new file mode 100644 index 0000000000..184eca1a6b --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/HibernateMethodLookupDispatcherTest.java @@ -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 . + */ +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) { + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java new file mode 100644 index 0000000000..4b886db308 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/bytecode/internal/bytebuddy/SimpleEntity.java @@ -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 . + */ +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; + } +}