Skip to content
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

Get AnnotationByType's invocation handler supports java array value #1330

Conversation

larryxiao625
Copy link
Contributor

To fix #1329

@larryxiao625
Copy link
Contributor Author

Meanwhile, seems test-utils not support java type support, so I am not sure how to make a UT for this

@neetopia
Copy link
Contributor

I am not sure if this fix is right. It looks to me that the cause of the bug is that we are expecting an array type in the code which is not the case for single value, therefore we should instead handle single value case. Have you tested your fix since you do not have a test in this PR? Regarding the test though, our test suite should support Java types, there are lots of test cases in our code that targets specifically for Java, can you elaborate your difficulties in writing a test so we can figure it out?

@larryxiao625
Copy link
Contributor Author

Yes, you can follow the example in this #1329 to reproduce this issue, because for java annotation, we can pass single value as an array to anntation’s value, which will make KSAnnotation argument value return single value for it and ClassCast issue occurs, so it is a workaround for java type.
For test case, I have checked it in test-utils and testData, seems all of them are kotlin type. I would appreciate it if you could provide some example of java type, to reproduce this issue, the annotated class also should be java, so I am not sure can testData also be java type. Thanks for your help~

@neetopia
Copy link
Contributor

here is an example that tests annotations declared in Java source.

@larryxiao625
Copy link
Contributor Author

I got it, I will try to follow this example to add some test case, thanks for your help

@larryxiao625
Copy link
Contributor Author

@neetopia Thanks for your help, I have added one test case for this situation and it will throw as follow now

info: [ksp] loaded provider(s): [com.google.devtools.ksp.processor.AnnotatedUtilProcessor]
error: error occurred in KSP, check log for detail
error: [ksp] java.lang.ClassCastException: class java.lang.Boolean cannot be cast to class [Z (java.lang.Boolean and [Z are in module java.base of loader 'bootstrap')
	at com.sun.proxy.$Proxy40.booleanArrayValue(Unknown Source)
	at com.google.devtools.ksp.processor.AnnotatedUtilProcessorKt.asString(AnnotatedUtilProcessor.kt:95)
	at com.google.devtools.ksp.processor.AnnotatedUtilProcessorKt.asString(AnnotatedUtilProcessor.kt:145)
	at com.google.devtools.ksp.processor.AnnotatedUtilProcessor$GetAnnotationsByTypeVisitor.visitAnnotated(AnnotatedUtilProcessor.kt:69)
	at com.google.devtools.ksp.processor.AnnotatedUtilProcessor$GetAnnotationsByTypeVisitor.visitAnnotated(AnnotatedUtilProcessor.kt:65)
	at com.google.devtools.ksp.visitor.KSDefaultVisitor.visitDeclaration(KSDefaultVisitor.kt:116)
	at com.google.devtools.ksp.visitor.KSTopDownVisitor.visitDeclaration(KSTopDownVisitor.kt:56)
	at com.google.devtools.ksp.visitor.KSDefaultVisitor.visitClassDeclaration(KSDefaultVisitor.kt:79)
	at com.google.devtools.ksp.visitor.KSTopDownVisitor.visitClassDeclaration(KSTopDownVisitor.kt:51)
	at com.google.devtools.ksp.symbol.impl.java.KSClassDeclarationJavaImpl.accept(KSClassDeclarationJavaImpl.kt:171)
	at com.google.devtools.ksp.processor.AnnotatedUtilProcessor.process(AnnotatedUtilProcessor.kt:42)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:287)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$6$1.invoke(KotlinSymbolProcessingExtension.kt:285)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:390)
	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:285)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:123)
	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:99)
	at org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil.analyze(JvmResolveUtil.kt:99)
	at org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil.analyzeAndCheckForErrors(JvmResolveUtil.kt:66)
	at org.jetbrains.kotlin.codegen.GenerationUtils.compileFilesUsingStandardMode(GenerationUtils.kt:179)
	at org.jetbrains.kotlin.codegen.GenerationUtils.compileFiles(GenerationUtils.kt:82)
	at org.jetbrains.kotlin.codegen.GenerationUtils.compileFiles(GenerationUtils.kt:67)
	at org.jetbrains.kotlin.codegen.GenerationUtils.compileFiles$default(GenerationUtils.kt:61)
	at org.jetbrains.kotlin.codegen.GenerationUtils.compileFilesTo(GenerationUtils.kt:55)
	at com.google.devtools.ksp.test.AbstractKSPCompilerPluginTest.runTest(AbstractKSPCompilerPluginTest.kt:73)
	at com.google.devtools.ksp.testutils.AbstractKSPTest.runTest(AbstractKSPTest.kt:225)
	at com.google.devtools.ksp.test.KSPCompilerPluginTest.testJavaAnnotatedUtil(KSPCompilerPluginTest.kt:38)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:564)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService$ExclusiveTask.compute(ForkJoinPoolHierarchicalTestExecutorService.java:185)
	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService.executeNonConcurrentTasks(ForkJoinPoolHierarchicalTestExecutorService.java:155)
	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService.invokeAll(ForkJoinPoolHierarchicalTestExecutorService.java:135)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService$ExclusiveTask.compute(ForkJoinPoolHierarchicalTestExecutorService.java:185)
	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService.invokeAll(ForkJoinPoolHierarchicalTestExecutorService.java:129)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.ForkJoinPoolHierarchicalTestExecutorService$ExclusiveTask.compute(ForkJoinPoolHierarchicalTestExecutorService.java:185)
	at java.base/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:189)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1016)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1665)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1598)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)

I have checked that this MR can fix this issue and pass all the test case locally

@neetopia
Copy link
Contributor

neetopia commented Mar 2, 2023

Looks like there is a lint error. Make sure at least lint passes, you can check by running ./gradlew ktlint prior to pushing. Ideally, a full run of ./gradlew build will include all necessary checks.

@larryxiao625 larryxiao625 force-pushed the p/xiaozihan.larryxiao/fix_invocation_handler_for_java_type branch from 15df9ab to ae19967 Compare March 2, 2023 07:52
@larryxiao625
Copy link
Contributor Author

Thanks for you remind, I have correct all the lint issue and pass all necessary checks locally~

@neetopia neetopia merged commit 6f7c046 into google:main Mar 3, 2023
@neetopia
Copy link
Contributor

neetopia commented Mar 3, 2023

Thanks for your PR!

@lukellmann
Copy link
Contributor

This will now actually fail in a case like this (only for mpp projects though, see below):

annotation class Anno(val array: Array<String> = [])

@Anno
class Annotated

// processor code:
val clazz: KSClassDeclaration = TODO("get KSClassDeclaration for Annotated")
val array = clazz.getAnnotationsByType(Anno::class)
    .first()
    .array // fails here
println(array.contentToString())

this fails with the following:

e: [ksp] java.lang.IllegalStateException: unhandled value type, please file a bug at https://github.com/google/ksp/issues/new
        at com.google.devtools.ksp.UtilsKt.createInvocationHandler$lambda$8(utils.kt:381)
        at jdk.proxy6/jdk.proxy6.$Proxy32.array(Unknown Source)
        at processor.Processor.process(Processor.kt:22)
        ...

The reason this fails is this code:

// argument.value returns null for default values in mpp
// because of https://github.com/google/ksp/issues/885
//                           |||
//                           |||       fall back to jvm reflection
//                           |||       which returns the array default
//                           vvv                vvv
when (val result = argument.value ?: method.defaultValue) {
    is Proxy -> result
    is List<*> -> {
        // ...
    }
    // result is now of type Array<String>, so we end up in this else
    else -> {
        when {
            // this is the case, we end up in this branch
            method.returnType.isArray -> {
                // but now this check returns false, result is of type Array<String>
                if (result !is Array<*>) {
                    // ...
                } else {
                    // now this exception get's thrown, while we should just return result
                    throw IllegalStateException("unhandled value type, $ExceptionMessage")
                }
            }
            // ...

@larryxiao625
Copy link
Contributor Author

Let me check this issue~

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Java annotation array type value in java will break getAnnotationsByType
3 participants