Skip to content

Bean validation fails with ArrayIndexOutOfBoundsException for Kotlin suspending function #29793

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

Closed
cjdxhjj opened this issue Jan 10, 2023 · 11 comments
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: duplicate A duplicate of another issue theme: kotlin An issue related to Kotlin support

Comments

@cjdxhjj
Copy link

cjdxhjj commented Jan 10, 2023

Affects: Spring Framework 6.0.3


I put the @Validated on the controller, when i call any action on that controller, it fail with flowing exception

java.lang.ArrayIndexOutOfBoundsException: Index 9 out of bounds for length 9
        at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4165)
        Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
        *__checkpoint ⇢ com.nxin.passport.application.support.filter.FormDataParserFilter [DefaultWebFilterChain]
        *__checkpoint ⇢ com.nxin.passport.application.support.filter.UrlRewriteFilter [DefaultWebFilterChain]
        *__checkpoint ⇢ org.jasig.cas.client.webflux.authentication.AssertionFilter [DefaultWebFilterChain]
        *__checkpoint ⇢ org.jasig.cas.client.webflux.authentication.AuthenticationFilter [DefaultWebFilterChain]
        *__checkpoint ⇢ org.jasig.cas.client.webflux.authentication.SingleSignOutFilter [DefaultWebFilterChain]
        *__checkpoint ⇢ HTTP POST "/sec/user-info" [ExceptionHandlingWebHandler]
Original Stack Trace:
                at java.base/java.util.Arrays$ArrayList.get(Arrays.java:4165)
                at org.hibernate.validator.internal.properties.javabean.JavaBeanExecutable.getParameterName(JavaBeanExecutable.java:86)
                at org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData$Builder.build(ParameterMetaData.java:165)
                at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.findParameterMetaData(ExecutableMetaData.java:436)
                at org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData$Builder.build(ExecutableMetaData.java:391)
                at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder$BuilderDelegate.build(BeanMetaDataBuilder.java:260)
                at org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder.build(BeanMetaDataBuilder.java:133)
                at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.createBeanMetaData(BeanMetaDataManagerImpl.java:206)
                at org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl.getBeanMetaData(BeanMetaDataManagerImpl.java:165)
                at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:267)
                at org.hibernate.validator.internal.engine.ValidatorImpl.validateParameters(ValidatorImpl.java:235)
                at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:121)
                at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
                at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
                at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
                at com.nxin.passport.account.controllers.SecController$$SpringCGLIB$$0.getUserInfo(<generated>)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base/java.lang.reflect.Method.invoke(Method.java:568)
                at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:145)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:132)
                at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:293)
                at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:474)
                at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180)
                at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:122)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:158)
                at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
                at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:151)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
                at reactor.core.publisher.MonoZip$ZipCoordinator.request(MonoZip.java:216)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.request(MonoFlatMap.java:194)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onSubscribe(MonoIgnoreThen.java:134)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:117)
                at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:125)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
                at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:189)
                at reactor.core.publisher.Operators.complete(Operators.java:137)
                at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:121)
                at reactor.core.publisher.Mono.subscribe(Mono.java:4444)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263)
                at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
                at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
                at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:74)
                at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
                at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:258)
                at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:863)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
                at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180)
                at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2545)
                at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:171)
                at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2305)
                at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:338)
                at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108)
                at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2341)
                at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2215)
                at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70)
                at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164)
                at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
                at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
                at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
                at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
                at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:151)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:57)
                at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
                at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:240)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.onComplete(MonoIgnoreThen.java:203)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:155)
                at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
                at reactor.core.publisher.MonoCacheTime.subscribeOrReturn(MonoCacheTime.java:151)
                at reactor.core.publisher.Mono.subscribe(Mono.java:4429)
                at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.subscribeNext(MonoIgnoreThen.java:263)
                at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:51)
                at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
                at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
                at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
                at reactor.core.publisher.FluxHandleFuseable$HandleFuseableSubscriber.onNext(FluxHandleFuseable.java:193)
                at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:245)
                at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:305)
                at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:129)
                at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:299)
                at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
                at reactor.core.publisher.Operators$BaseFluxToMonoOperator.completePossiblyEmpty(Operators.java:2071)
                at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:145)
                at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)
                at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
                at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:144)
                at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:413)
                at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:426)
                at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:480)
                at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:702)
                at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:113)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
                at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
                at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
                at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
                at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
                at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
                at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1373)
                at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1236)
                at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1285)
                at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:529)
                at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:468)
                at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
                at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
                at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
                at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800)
                at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:499)
                at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:397)
                at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
                at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
                at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
                at java.base/java.lang.Thread.run(Thread.java:833)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 10, 2023
@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 10, 2023

i have dig into the exception, and found out the problem
image
hibenate replay1
hibenate replay2
the hibenate dosn't adapt kotlin, and consider it was the kotlin term problem, hibernate get the method parameter from the ExecutableParameterNameProvider, which can be configurable by spring. the fact spring special the KotlinReflectionParameterNameDiscoverer to resolve the paramter name, the hibernate get the paramter count at runtime by java reflect api, the count is source parmater count add one , the spring team ignore the continuation parameter, so the paramter is the less one than hibernate, hibernate get all the parameter by index, that cause the problem.
it may solved by add one string to getParameterNames() result when detect the method is suspend function. i have wrote a class here.

class KtReflectionParameterNameDiscoverer: KotlinReflectionParameterNameDiscoverer() {
    override fun getParameterNames(method: Method): Array<String>? {
        return super.getParameterNames(method)?.run {
            if (KotlinDetector.isSuspendingFunction(method)) this.plus("continuation") else this
        }
    }
}

i have found that configuration at ValidationAutoConfiguration

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean(Validator.class)
public static LocalValidatorFactoryBean defaultValidator(ApplicationContext applicationContext,
	ObjectProvider<ValidationConfigurationCustomizer> customizers) {
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    factoryBean.setConfigurationInitializer((configuration) -> customizers.orderedStream()
		.forEach((customizer) -> customizer.customize(configuration)));
    MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory(applicationContext);
    factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
    return factoryBean;
}

i'm try to custom a ValidationConfigurationCustomizer to special the KtReflectionParameterNameDiscoverer, but i found i can't special the parameterNameDiscoverer property of LocalValidatorFactoryBean, i have to configuration the LocalValidatorFactoryBean instance

  @Bean
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  fun defaultValidator(applicationContext: ApplicationContext): LocalValidatorFactoryBean {
      val factoryBean = LocalValidatorFactoryBean()
      factoryBean.setParameterNameDiscoverer(KtReflectionParameterNameDiscoverer())
      val interpolatorFactory = MessageInterpolatorFactory(applicationContext)
      factoryBean.messageInterpolator = interpolatorFactory.getObject()
      return factoryBean
  }

when i done that things, it works.

@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 10, 2023

@sbrannen could you fix that problem?

@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 10, 2023

the KotlinReflectionParameterNameDiscoverer class may used by other place, you can provide a new one, and override the getParameterNames function and replace it in LocalValidatorFactoryBean

public LocalValidatorFactoryBean() {
	if (KotlinDetector.isKotlinReflectPresent()) {
		this.parameterNameDiscoverer = new KotlinReflectionParameterNameDiscoverer();
	}
}

@sbrannen sbrannen added in: core Issues in core modules (aop, beans, core, context, expression) theme: kotlin An issue related to Kotlin support labels Jan 11, 2023
@sbrannen sbrannen changed the title bean validation with kotlin fail with ArrayIndexOutOfrangeException Bean validation fails with ArrayIndexOutOfBoundsException for Kotlin suspending function Jan 11, 2023
@sbrannen sbrannen added this to the Triage Queue milestone Jan 11, 2023
@sdeleuze
Copy link
Contributor

Duplicate of #23499.

@sdeleuze sdeleuze closed this as not planned Won't fix, can't repro, duplicate, stale Jan 17, 2023
@sdeleuze sdeleuze removed this from the Triage Queue milestone Jan 17, 2023
@sdeleuze sdeleuze added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 17, 2023
@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 17, 2023

@sdeleuze the issure exist for many years, and dosn't solved

@bclozel
Copy link
Member

bclozel commented Jan 17, 2023

@cjdxhjj it's not solved yet. This issue is just a duplicate so it's been closed in favor of the other one.

@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 17, 2023

@sdeleuze @bclozel i have found my solution, that works.

@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 17, 2023

you may add one switch at KotlinReflectionParameterNameDiscoverer to perform one result for hibernate validate and one for others

@hantsy
Copy link
Contributor

hantsy commented Jan 18, 2023

the KotlinReflectionParameterNameDiscoverer class may used by other place, you can provide a new one, and override the getParameterNames function and replace it in LocalValidatorFactoryBean

public LocalValidatorFactoryBean() {
	if (KotlinDetector.isKotlinReflectPresent()) {
		this.parameterNameDiscoverer = new KotlinReflectionParameterNameDiscoverer();
	}
}

The exception only occurs in KotlinCoroutines which will append an extra Continutation to the fun at runtime.

If you are using pure Kotlin, I do not think there is a problem.

So here, you have to make sure it is a Kotlin coroutine case.

@cjdxhjj
Copy link
Author

cjdxhjj commented Jan 18, 2023

@hantsy yes, the simple way is to extend that class or add one switch for other case

@sdeleuze
Copy link
Contributor

Let's continue the discussion on #23499 (issue) and #29840 (PR).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: duplicate A duplicate of another issue theme: kotlin An issue related to Kotlin support
Projects
None yet
Development

No branches or pull requests

6 participants