StackOverflowError with Hibernate Validator 6.0.13.Final

Description

when the process working for two days,StackOverflowError always happen,the stacktrace is below:

java.lang.StackOverflowError at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) ...... at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031) at org.hibernate.validator.internal.engine.valueextraction.ValueExtractorResolver.getRuntimeCompliantValueExtractors(ValueExtractorResolver.java:316) at org.hibernate.validator.internal.engine.valueextraction.ValueExtractorResolver.getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation(ValueExtractorResolver.java:177) at org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder.build(CascadingMetaDataBuilder.java:227) at org.hibernate.validator.internal.metadata.aggregated.FieldCascadable$Builder.build(FieldCascadable.java:83) at org.hibernate.validator.internal.metadata.aggregated.FieldCascadable$Builder.build(FieldCascadable.java:64) at org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData$Builder.lambda$build$1(PropertyMetaData.java:347) at org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData$Builder$$Lambda$62/1651906449.apply(Unknown Source) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1625) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) at org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData$Builder.build(PropertyMetaData.java:348) at org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData$Builder.build(PropertyMetaData.java:151) at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BuilderDelegate.build(BeanMetaDataImpl.java:784) at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl$BeanMetaDataBuilder.build(BeanMetaDataImpl.java:648) at org.hibernate.validator.internal.metadata.BeanMetaDataManager.createBeanMetaData(BeanMetaDataManager.java:192) at org.hibernate.validator.internal.metadata.BeanMetaDataManager.lambda$getBeanMetaData$0(BeanMetaDataManager.java:160) at org.hibernate.validator.internal.metadata.BeanMetaDataManager$$Lambda$53/253243426.apply(Unknown Source) at java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:324) at org.hibernate.validator.internal.metadata.BeanMetaDataManager.getBeanMetaData(BeanMetaDataManager.java:159) at org.hibernate.validator.internal.engine.ValidationContext$ValidationContextBuilder.forValidate(ValidationContext.java:566) at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:155) at com.test.ValidateParameters.validateObject(ValidateParameters.java:31)

The environment is :x86_64 GNU/Linux JRE: 1.8.0_181 hibernate-validator:6.0.13Final
And our code is easy,just like this,first get validator in a static block,and then validate paramter for every request param:

//get validator Validator validator = Validation.byDefaultProvider() .providerResolver(new OsgiValidationProviderResolver()) .configure().messageInterpolator(resouce) .buildValidatorFactory().getValidator(); //validate parameter,this parameter is an Object,(we think it contains map attribute may have the problem). Set<ConstraintViolation> validateResultSets = validator.validate(parameter);

Our team have view the code,we doubt the following code(ValueExtractorResolver.java, getRuntimeCompliantValueExtractors) may has some problem in Multithreading,why it always happens for a long time?
When in thread one,the attribute "valueExtractorDescriptors" is not empty ,and it's type is java.util.map,then it will change to ImmutableSet,then a second thread run "valueExtractorDescriptors.isEmpty()",it will goes to the UnmodifiableCollection.isEmpty,this will recursion,at last it will throw SO Exception.

private Set<ValueExtractorDescriptor> getRuntimeCompliantValueExtractors(Class<?> runtimeType, Set<ValueExtractorDescriptor> potentialValueExtractorDescriptors) { if ( nonContainerTypes.contains( runtimeType ) ) { return Collections.emptySet(); } Set<ValueExtractorDescriptor> valueExtractorDescriptors = possibleValueExtractorsByRuntimeType.get( runtimeType ); if ( valueExtractorDescriptors == null ) { //otherwise we just look for maximally specific extractors for the runtime type Set<ValueExtractorDescriptor> possibleValueExtractors = potentialValueExtractorDescriptors .stream() .filter( e -> TypeHelper.isAssignable( e.getContainerType(), runtimeType ) ) .collect( Collectors.toSet() ); valueExtractorDescriptors = getMaximallySpecificValueExtractors( possibleValueExtractors ); } if ( valueExtractorDescriptors.isEmpty() ) { nonContainerTypes.put( runtimeType, NON_CONTAINER_VALUE ); } else { Set<ValueExtractorDescriptor> extractorDescriptorsToCache = CollectionHelper.toImmutableSet( valueExtractorDescriptors ); possibleValueExtractorsByRuntimeType.put( runtimeType, extractorDescriptorsToCache ); return extractorDescriptorsToCache; } return valueExtractorDescriptors; }

Environment

The environment is :x86_64 GNU/Linux JRE: 1.8.0_181 hibernate-validator:6.0.13Final

Activity

Show:

Guillaume Smet January 7, 2019 at 3:57 PM

OK, thanks again for reporting the issue, very helpful!

tp123 January 7, 2019 at 3:52 PM

Thank you for your reply,and I have debug the code, I've known about it now. And in my project, I have just only use Hibernate Validator as a library.

Guillaume Smet January 7, 2019 at 3:13 PM

btw, quick question: in which environment did you discover the issue? Do you use WildFly, JBoss EAP or Hibernate Validator as a library in another framework?

Thanks!

Guillaume Smet January 7, 2019 at 8:08 AM

"has special cases if the map is empty or has only one element." should be read "has special cases if the SET is empty or has only one element."

Guillaume Smet January 7, 2019 at 8:03 AM

The issue was the following:

In the end, you end up with an immutable set of an immutable of an immutable set... of the original set. After a certain period of time, you have so many immutable wrappers than just calling isEmpty() causes a stack overflow.

It only happens with Maps because CollectionHelper#toImmutableSet() has special cases if the map is empty or has only one element.

You can reproduce it by validating what Gunnar presented above a few times and check what is in the cache. The elements in the cache should be wrapped only once, even after a few rounds of validation.

Fixed

Details

Assignee

Reporter

Labels

Bug Testcase Reminder (view)

Bug reports should generally be accompanied by a test case!

Bug Testcase Reminder (edit)

Bug reports should generally be accompanied by a test case!

Feedback Requested

Participants

Guillaume Smet
Gunnar Morling
leedong
tp123

Components

Affects versions

Priority

Created January 3, 2019 at 12:42 PM
Updated January 7, 2019 at 3:57 PM
Resolved January 4, 2019 at 1:29 PM