-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HHH-18976 Avoid usage of Array.newInstance #9589
Conversation
b581178
to
4340eab
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added comments to the main areas of changes -- the rest is mostly dumb find and replace.
@sebersole if you can (please!) confirm those commented sections look fine by you, we can probably merge and solve the upgrade issue in Quarkus?
rules/forbidden-apis.txt
Outdated
# Reflection-related | ||
@defaultMessage Use 'new Object[]' instead if possible. This forbidden method requires reflection and may not work in natively compiled applications. If you really must use this forbidden method, annotate the calling method with @AllowReflection. | ||
|
||
java.lang.reflect.Array#newInstance(java.lang.Class, int) | ||
java.lang.reflect.Array#newInstance(java.lang.Class, int[]) | ||
org.hibernate.internal.util.collections.ArrayHelper#newInstance(java.lang.Class, int) | ||
org.hibernate.internal.util.collections.ArrayHelper#filledArray(java.lang.Object, java.lang.Class, int) | ||
org.hibernate.internal.util.collections.ArrayHelper#join(java.lang.Object[], java.lang.Object[]) | ||
|
||
@defaultMessage Use '.clone()' or a type-specific 'ArrayHelper.copyOf' instead if possible. This forbidden method requires reflection and may not work in natively compiled applications. If you really must use this forbidden method, annotate the calling method with @AllowReflection. | ||
|
||
java.util.Arrays#copyOf(java.lang.Object[], int) | ||
java.util.Arrays#copyOf(java.lang.Object[], int, java.lang.Class) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe these constraints are reasonable -- especially since we can bypass them where necessary. Essentially this gives a heads-up to not introduce new uses of these methods by mistake, but doesn't prevent it if we need to.
private final ConcurrentHashMap<Type, JavaType<?>> descriptorsByType = new ConcurrentHashMap<>(); | ||
private final ConcurrentHashMap<String, JavaType<?>> descriptorsByTypeName = new ConcurrentHashMap<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and other changes in the same file) avoids the need to use reflection to create an array Type
, just for the purpose of retrieving the corresponding JavaType
.
Regardless of the original intent, I believe using type names (strings) as keys would be more consistent with BasicTypeRegistry
? (see for example org.hibernate.type.BasicTypeRegistry#getRegisteredType(java.lang.reflect.Type)
)
ids.toArray( LoaderHelper.createTypedArray( ids.get( 0 ).getClass(), ids.size() ) ), | ||
ids.toArray( new Object[0] ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note this is still done, but deeper within the multiLoad
call, and only when necessary (see MultiIdEntityLoaderArrayParam
).
|
||
public AbstractMultiIdEntityLoader(EntityMappingType entityDescriptor, SessionFactoryImplementor sessionFactory) { | ||
this.entityDescriptor = entityDescriptor; | ||
this.sessionFactory = sessionFactory; | ||
identifierMapping = getLoadable().getIdentifierMapping(); | ||
idArray = (Object[]) Array.newInstance( identifierMapping.getJavaType().getJavaTypeClass(), 0 ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the main problem affecting Quarkus, FWIW.
This being done in the abstract class meant it was done for MultiIdEntityLoaderArrayParam
-- where it's needed, and generally innocuous since it handles things like Integer[]
-- but also MultiIdEntityLoaderStandardParam
-- where it's not needed, and possibly problematic as it could be handling things like MyIdClass[]
, requiring MyIdClass[]
(the array type) to be registered for reflection in native images.
It's not trivial to detect all ID types in Quarkus (think of orm.xml
using property access, or IDs defined in mappedsuperclasses with generics), so this need for registering MyIdClass[]
for reflection was a problem.
hibernate-core/src/main/java/org/hibernate/type/descriptor/java/spi/JavaTypeRegistry.java
Outdated
Show resolved
Hide resolved
@yrodiere I would ask the JDK folks, because |
@franz1981 Okay, I must admit the last part about |
I can tell you when it could matter: the reason why is an intrinsics is because the zeroing of the array "could" be avoided between the creation of the array and the actual copy - while we loose this advantage by performing ourselves the array creation + copy (in theory). |
I can have the EDIT: well we would potentially do reflection with GraalVM, but just for |
I'll also double check in Quarkus that |
…Array#newInstance
…lans It's more consistent, and happens to get rid of ArrayMutabilityPlan, which involved an unnecessary use of Array.newInstance. I've also seen claims that clone() performs better than Array.newInstance() due to not having to zero-out the allocated memory, but I doubt that's relevant here.
No functional impact, it's just less redundant.
…e type descriptors
…Standard MultiEntityLoaderStandard is used for arbitrary ID types, including IdClass, making it very problematic to instantiate T[] where T is the ID type: in native images, it requires registering T[] for reflection for every T that can possibly be used as an ID type. Fortunately, MultiEntityLoaderStandard does not, in fact, need concrete-type arrays: Object[] works perfectly well with this implementation, and only the other implementation, MultiIdEntityLoaderArrayParam, actually needs concrete-type arrays. We're truly in a lucky streak, because MultiIdEntityLoaderArrayParam is only used for well-known, basic types such as Integer, which can easily be registered for reflection in native images, and likely will be for other reasons anyway. Some of the dynamic instantiations were originally introduced to fix the following issue: * HHH-17201 -- tested in MultiIdEntityLoadTests The corresponding tests still pass after removing these dynamic array instantiations.
It does not, for some reason. I removed the commits about I also checked (locally) that this changeset solves the specific reflection-related problem we had in my PR to upgrade Quarkus to ORM 7.0 (quarkusio/quarkus#41310). |
Steve is likely busy, but others have reviewed and I addressed objections. Let's merge :) |
https://hibernate.atlassian.net/browse/HHH-18976
A simpler solution than #9569, without any attempt to solve HHH-16809 (on contrary to #9576), because that makes things complicated.
Two things in here:
Array.newInstance
(and util methods known to use it) by default. This is mostly as a heads-up to not use it "just because", since the check can be bypassed if necessary by annotating the method with@AllowReflection
.Array.newInstance
. E.g.:array.clone()
where relevant -- i.e. when we want the same array type with the same length.Array.newInstance
just to retrieve theClass
of an array type -- since we ultimately just look use thatClass
to look up a descriptor in a registry.new Object[...]
instead where possible -- e.g. when the array ends up being used internally only, and we don't leverage array type support so we only needObject[]
.Reworks a few implementations to avoid=> Not necessary, see conversation below.Arrays.copyOf
as well (because it relies onArray.newInstance
).This should get rid of most problematic uses of reflection when compiling to native binaries (see Jira issue).
There are still a few uses of
Array.newInstance
but as far as I can see they are completely justified and cannot easily be worked around: array mapping, JSON/XML serialization, instantiation of query result types, ...