Skip to content

Commit

Permalink
Simplify parsing of default values in JVM binaries
Browse files Browse the repository at this point in the history
  • Loading branch information
ting-yuan committed Oct 9, 2024
1 parent 834cd5d commit cdc89da
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import com.google.devtools.ksp.impl.symbol.kotlin.resolved.KSAnnotationResolvedI
import com.google.devtools.ksp.impl.symbol.kotlin.resolved.KSClassifierParameterImpl
import com.google.devtools.ksp.impl.symbol.kotlin.resolved.KSClassifierReferenceResolvedImpl
import com.google.devtools.ksp.impl.symbol.util.getDocString
import com.google.devtools.ksp.impl.symbol.util.getFileContent
import com.google.devtools.ksp.symbol.*
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiJavaFile
Expand Down Expand Up @@ -66,9 +65,6 @@ import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.load.java.structure.JavaAnnotationArgument
import org.jetbrains.kotlin.load.java.structure.impl.JavaUnknownAnnotationArgumentImpl
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaAnnotationVisitor
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.name.ClassId
Expand All @@ -80,7 +76,6 @@ import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.getEffectiveVariance
import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments
import org.jetbrains.org.objectweb.asm.*

internal val ktSymbolOriginToOrigin = mapOf(
KaSymbolOrigin.JAVA_SOURCE to Origin.JAVA,
Expand Down Expand Up @@ -555,34 +550,12 @@ internal fun KaValueParameterSymbol.getDefaultValue(): KaAnnotationValue? {
val parentClass = this.getContainingKSSymbol()!!.findParentOfType<KSClassDeclaration>()
val classId = (parentClass as KSClassDeclarationImpl).ktClassOrObjectSymbol.classId
?: return@let null
val fileContent = classId.getFileContent(fileManager) ?: return@let null
var defaultValue: JavaAnnotationArgument? = null
ClassReader(fileContent).accept(
object : ClassVisitor(Opcodes.API_VERSION) {
override fun visitMethod(
access: Int,
name: String?,
desc: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
return if (name == this@getDefaultValue.name.asString()) {
object : MethodVisitor(Opcodes.API_VERSION) {
override fun visitAnnotationDefault(): AnnotationVisitor =
BinaryJavaAnnotationVisitor(
ClassifierResolutionContext { null },
BinaryClassSignatureParser()
) {
defaultValue = it
}
}
} else {
object : MethodVisitor(Opcodes.API_VERSION) {}
}
}
},
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
)

val defaultValue: JavaAnnotationArgument? = analyze {
val jc = fileManager.findClass(classId, analysisScope) ?: return@analyze null
jc.methods.firstOrNull { it.name == name }?.annotationParameterDefaultValue
}

(this as? KaFirValueParameterSymbol)?.let {
val firSession = it.firSymbol.fir.moduleData.session
val symbolBuilder = it.builder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,16 @@ class KSPAATest : AbstractKSPAATest() {
runTest("../test-utils/testData/api/annotationWithArrayValue.kt")
}

@Disabled
@TestMetadata("annotationWithDefault.kt")
@Test
fun testAnnotationWithDefault() {
runTest("../test-utils/testData/api/annotationWithDefault.kt")
runTest("../kotlin-analysis-api/testData/annotationWithDefault.kt")
}

@Disabled
@TestMetadata("annotationWithDefaultValues.kt")
@Test
fun testAnnotationWithDefaultValues() {
runTest("../test-utils/testData/api/annotationWithDefaultValues.kt")
runTest("../kotlin-analysis-api/testData/annotationWithDefaultValues.kt")
}

@TestMetadata("annotationWithJavaTypeValue.kt")
Expand Down Expand Up @@ -283,7 +281,6 @@ class KSPAATest : AbstractKSPAATest() {
runTest("../test-utils/testData/api/functionTypes.kt")
}

@Disabled
@TestMetadata("getAnnotationByTypeWithInnerDefault.kt")
@Test
fun testGetAnnotationByTypeWithInnerDefault() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// TEST PROCESSOR: DefaultKClassValueProcessor
// EXPECTED:
// java.lang.String
// kotlin.String
// java.lang.String
// kotlin.String
// kotlin.String
// kotlin.Int
// END
// MODULE: lib1
Expand Down
155 changes: 155 additions & 0 deletions kotlin-analysis-api/testData/annotationWithDefault.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2020 Google LLC
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// TEST PROCESSOR: AnnotationDefaultValueProcessor
// EXPECTED:
// KotlinAnnotation -> a:debugKt:false,b:default:true,kClassValue:Array<Array<InnerObj>>:true,topLevelProp:foo:true,companionProp:companion:true
// JavaAnnotation -> debug:debug:false,withDefaultValue:OK:true,nested:@Nested:true
// JavaAnnotation2 -> y:y-kotlin:false,x:x-kotlin:false,z:z-default:true
// KotlinAnnotation2 -> y:y-kotlin:false,x:x-kotlin:false,z:z-default:true,kotlinEnumVal:KotlinEnum.VALUE_1:true
// KotlinAnnotationLib -> a:debugLibKt:false,b:defaultInLib:true,kClassValue:OtherKotlinAnnotation:true,topLevelProp:bar:true
// JavaAnnotationWithDefaults -> stringVal:foo:true,stringArrayVal:[x, y]:true,typeVal:HashMap<(Any..Any?), (Any..Any?)>:true,typeArrayVal:[LinkedHashMap<(Any..Any?), (Any..Any?)>]:true,intVal:3:true,intArrayVal:[1, 3, 5]:true,enumVal:JavaEnum.DEFAULT:true,enumArrayVal:[JavaEnum.VAL1, JavaEnum.VAL2]:true,localEnumVal:LocalEnum.LOCAL1:true,otherAnnotationVal:@OtherAnnotation:true,otherAnnotationArrayVal:[@OtherAnnotation]:true,kotlinAnnotationLibVal:@OtherKotlinAnnotation:true
// KotlinAnnotationWithDefaults -> stringVal:foo:false,stringArrayVal:[x, y]:true,typeVal:HashMap<(Any..Any?), (Any..Any?)>:true,typeArrayVal:[LinkedHashMap<(Any..Any?), (Any..Any?)>]:true,intVal:3:true,intArrayVal:[1, 3, 5]:true,enumVal:JavaEnum.DEFAULT:true,enumArrayVal:[JavaEnum.VAL1, JavaEnum.VAL2]:true,otherAnnotationVal:@OtherAnnotation:true,otherAnnotationArrayVal:[@OtherAnnotation]:true,kotlinAnnotationLibVal:@OtherKotlinAnnotation:true
// KotlinAnnotation -> a:debugKt:false,b:default:true,kClassValue:Array<Array<InnerObj>>:true,topLevelProp:foo:true,companionProp:companion:true
// JavaAnnotation -> debug:debugJava2:false,withDefaultValue:OK:true,nested:@Nested:true
// JavaAnnotation2 -> y:y-java:false,x:x-java:false,z:z-default:true
// KotlinAnnotation2 -> y:y-java:false,x:x-java:false,z:z-default:true,kotlinEnumVal:KotlinEnum.VALUE_1:true
// END
// MODULE: lib
// FILE: Default.kt
const val Bar = "bar"
annotation class KotlinAnnotationLib(val a: String, val b: String = "defaultInLib", val kClassValue: kotlin.reflect.KClass<*> = OtherKotlinAnnotation::class, val topLevelProp:String = Bar)

annotation class OtherKotlinAnnotation(val b: String = "otherKotlinAnnotationDefault")

// FILE: JavaEnum.java
public enum JavaEnum {
VAL1,
VAL2,
DEFAULT
}

// FILE: OtherAnnotation.java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface OtherAnnotation {
String value();
}

// FILE: JavaAnnotationWithDefaults.java
import java.util.HashMap;
import java.util.LinkedHashMap;
public @interface JavaAnnotationWithDefaults {
String stringVal() default "foo";
String[] stringArrayVal() default {"x", "y"};
Class<?> typeVal() default HashMap.class;
Class[] typeArrayVal() default {LinkedHashMap.class};
int intVal() default 3;
int[] intArrayVal() default {1, 3, 5};
JavaEnum enumVal() default JavaEnum.DEFAULT;
JavaEnum[] enumArrayVal() default {JavaEnum.VAL1, JavaEnum.VAL2};
LocalEnum localEnumVal() default LocalEnum.LOCAL1;
OtherAnnotation otherAnnotationVal() default @OtherAnnotation("def");
OtherAnnotation[] otherAnnotationArrayVal() default {@OtherAnnotation("v1")};
OtherKotlinAnnotation kotlinAnnotationLibVal() default @OtherKotlinAnnotation(b = "JavaAnnotationWithDefaults");
enum LocalEnum {
LOCAL1,
LOCAL2
}
}

// FILE: KotlinAnnotationWithDefaults.kt
import kotlin.reflect.KClass

annotation class KotlinAnnotationWithDefaults(
val stringVal: String = "foo",
val stringArrayVal: Array<String> = ["x", "y"],
val typeVal: KClass<*> = java.util.HashMap::class,
val typeArrayVal: Array<KClass<*>> = [java.util.LinkedHashMap::class],
val intVal: Int = 3,
val intArrayVal: IntArray = [1, 3, 5],
val enumVal: JavaEnum = JavaEnum.DEFAULT,
val enumArrayVal: Array<JavaEnum> = [JavaEnum.VAL1, JavaEnum.VAL2],
val otherAnnotationVal: OtherAnnotation = OtherAnnotation("def"),
val otherAnnotationArrayVal: Array<OtherAnnotation> = [OtherAnnotation("v1")],
val kotlinAnnotationLibVal: OtherKotlinAnnotation = OtherKotlinAnnotation("1")
)
// MODULE: main(lib)
// FILE: Const.kt
const val Foo = "foo"
const val DebugKt = "debugKt"

class Container {
companion object {
const val comp = "companion"
}
}

// FILE: a.kt
import test.KotlinEnum
import Container.Companion.comp


annotation class KotlinAnnotation(val a: String, val b:String = "default", val kClassValue: kotlin.reflect.KClass<*> = Array<Array<InnerObj>>::class, val topLevelProp: String = Foo, val companionProp: String = comp) {
object InnerObj
}
annotation class KotlinAnnotation2(val x: String, val y:String = "y-default", val z:String = "z-default", val kotlinEnumVal: KotlinEnum = KotlinEnum.VALUE_1)

@KotlinAnnotation(DebugKt)
@JavaAnnotation("debug")
@JavaAnnotation2(y="y-kotlin", x="x-kotlin")
@KotlinAnnotation2(y="y-kotlin", x="x-kotlin")
@KotlinAnnotationLib("debugLibKt")
@JavaAnnotationWithDefaults
@KotlinAnnotationWithDefaults(stringVal="foo") //set the value to the same as the default
class A

// FILE: test.kt
package test

enum class KotlinEnum {
VALUE_1,
VALUE2
}

// FILE: JavaAnnotation.java
public @interface JavaAnnotation {
String debug();
String withDefaultValue() default "OK";
@interface Nested {
String nestedX() default "nested";
}
Nested nested() default @Nested();
}

// FILE: JavaAnnotation2.java
public @interface JavaAnnotation2 {
String x() default "x-default";
String y() default "y-default";
String z() default "z-default";
}

// FILE: JavaAnnotated.java

@KotlinAnnotation(ConstKt.DebugKt)
@JavaAnnotation("debugJava2")
@JavaAnnotation2(y="y-java", x="x-java")
@KotlinAnnotation2(y="y-java", x="x-java")
public class JavaAnnotated {

}
Loading

0 comments on commit cdc89da

Please sign in to comment.