Fix deserialization of enum annotation arguments at runtime
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
|||||||
|
//ALLOW_AST_ACCESS
|
||||||
|
package test;
|
||||||
|
|
||||||
|
// This test checks that we don't accidentally call toString() on an enum value
|
||||||
|
// to determine which enum entry appears in the annotation, and call name() instead
|
||||||
|
|
||||||
|
public class EnumArgumentWithCustomToString {
|
||||||
|
public enum E {
|
||||||
|
CAKE {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LIE";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public @interface EnumAnno {
|
||||||
|
E value();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @interface EnumArrayAnno {
|
||||||
|
E[] value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@EnumAnno(E.CAKE)
|
||||||
|
@EnumArrayAnno({E.CAKE, E.CAKE})
|
||||||
|
void annotated() {}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
public open class EnumArgumentWithCustomToString {
|
||||||
|
public constructor EnumArgumentWithCustomToString()
|
||||||
|
test.EnumArgumentWithCustomToString.EnumAnno(value = E.CAKE: test.EnumArgumentWithCustomToString.E) test.EnumArgumentWithCustomToString.EnumArrayAnno(value = {E.CAKE, E.CAKE}: kotlin.Array<out test.EnumArgumentWithCustomToString.E>) public/*package*/ open fun annotated(): kotlin.Unit
|
||||||
|
|
||||||
|
public open enum class E : kotlin.Enum<test.EnumArgumentWithCustomToString.E!> {
|
||||||
|
public enum entry CAKE : test.EnumArgumentWithCustomToString.E {
|
||||||
|
private constructor CAKE()
|
||||||
|
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.EnumArgumentWithCustomToString.E!): kotlin.Int
|
||||||
|
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||||
|
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor E()
|
||||||
|
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.EnumArgumentWithCustomToString.E!): kotlin.Int
|
||||||
|
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||||
|
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||||
|
|
||||||
|
// Static members
|
||||||
|
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): test.EnumArgumentWithCustomToString.E
|
||||||
|
public final /*synthesized*/ fun values(): kotlin.Array<test.EnumArgumentWithCustomToString.E>
|
||||||
|
}
|
||||||
|
|
||||||
|
public final annotation class EnumAnno : kotlin.Annotation {
|
||||||
|
public constructor EnumAnno(/*0*/ value: test.EnumArgumentWithCustomToString.E)
|
||||||
|
public abstract fun value(): test.EnumArgumentWithCustomToString.E
|
||||||
|
}
|
||||||
|
|
||||||
|
public final annotation class EnumArrayAnno : kotlin.Annotation {
|
||||||
|
public constructor EnumArrayAnno(/*0*/ vararg value: test.EnumArgumentWithCustomToString.E /*kotlin.Array<out test.EnumArgumentWithCustomToString.E>*/)
|
||||||
|
public abstract fun value(): kotlin.Array<test.EnumArgumentWithCustomToString.E>
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
//ALLOW_AST_ACCESS
|
||||||
|
package test
|
||||||
|
|
||||||
|
// This test checks that we don't accidentally call toString() on an enum value
|
||||||
|
// to determine which enum entry appears in the annotation, and call name() instead
|
||||||
|
|
||||||
|
enum class E {
|
||||||
|
CAKE {
|
||||||
|
override fun toString() = "LIE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotation class EnumAnno(val value: E)
|
||||||
|
annotation class EnumArrayAnno(vararg val value: E)
|
||||||
|
|
||||||
|
public class EnumArgumentWithCustomToString {
|
||||||
|
EnumAnno(E.CAKE)
|
||||||
|
EnumArrayAnno(E.CAKE, E.CAKE)
|
||||||
|
fun annotated() {}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
internal final enum class E : kotlin.Enum<test.E> {
|
||||||
|
public enum entry CAKE : test.E {
|
||||||
|
/*primary*/ private constructor CAKE()
|
||||||
|
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.E): kotlin.Int
|
||||||
|
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||||
|
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
/*primary*/ private constructor E()
|
||||||
|
public final override /*1*/ /*fake_override*/ fun compareTo(/*0*/ other: test.E): kotlin.Int
|
||||||
|
public final override /*1*/ /*fake_override*/ fun name(): kotlin.String
|
||||||
|
public final override /*1*/ /*fake_override*/ fun ordinal(): kotlin.Int
|
||||||
|
|
||||||
|
// Static members
|
||||||
|
public final /*synthesized*/ fun valueOf(/*0*/ value: kotlin.String): test.E
|
||||||
|
public final /*synthesized*/ fun values(): kotlin.Array<test.E>
|
||||||
|
}
|
||||||
|
|
||||||
|
internal final annotation class EnumAnno : kotlin.Annotation {
|
||||||
|
/*primary*/ public constructor EnumAnno(/*0*/ value: test.E)
|
||||||
|
internal final val value: test.E
|
||||||
|
internal final fun <get-value>(): test.E
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class EnumArgumentWithCustomToString {
|
||||||
|
/*primary*/ public constructor EnumArgumentWithCustomToString()
|
||||||
|
test.EnumAnno(value = E.CAKE: test.E) test.EnumArrayAnno(value = {E.CAKE, E.CAKE}: kotlin.Array<out test.E>) internal final fun annotated(): kotlin.Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
internal final annotation class EnumArrayAnno : kotlin.Annotation {
|
||||||
|
/*primary*/ public constructor EnumArrayAnno(/*0*/ vararg value: test.E /*kotlin.Array<out test.E>*/)
|
||||||
|
internal final val value: kotlin.Array<out test.E>
|
||||||
|
internal final fun <get-value>(): kotlin.Array<out test.E>
|
||||||
|
}
|
||||||
@@ -409,6 +409,12 @@ public class LoadJavaTestGenerated extends AbstractLoadJavaTest {
|
|||||||
doTestCompiledJava(fileName);
|
doTestCompiledJava(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EnumArgumentWithCustomToString.java")
|
||||||
|
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java");
|
||||||
|
doTestCompiledJava(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("EnumInParam.java")
|
@TestMetadata("EnumInParam.java")
|
||||||
public void testEnumInParam() throws Exception {
|
public void testEnumInParam() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
|
||||||
@@ -1907,6 +1913,12 @@ public class LoadJavaTestGenerated extends AbstractLoadJavaTest {
|
|||||||
doTestCompiledKotlin(fileName);
|
doTestCompiledKotlin(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EnumArgumentWithCustomToString.kt")
|
||||||
|
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt");
|
||||||
|
doTestCompiledKotlin(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("SimpleAnnotation.kt")
|
@TestMetadata("SimpleAnnotation.kt")
|
||||||
public void testSimpleAnnotation() throws Exception {
|
public void testSimpleAnnotation() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
||||||
|
|||||||
+6
-3
@@ -149,9 +149,12 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi
|
|||||||
else if (header == null ||
|
else if (header == null ||
|
||||||
(header.kind == KotlinClassHeader.Kind.CLASS && header.classKind == JvmAnnotationNames.KotlinClass.Kind.CLASS)) {
|
(header.kind == KotlinClassHeader.Kind.CLASS && header.classKind == JvmAnnotationNames.KotlinClass.Kind.CLASS)) {
|
||||||
// Either a normal Kotlin class or a Java class
|
// Either a normal Kotlin class or a Java class
|
||||||
val classDescriptor = module.findClassAcrossModuleDependencies(klass.classId).sure("Couldn't resolve class $className")
|
val classId = klass.classId
|
||||||
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
|
if (!classId.isLocal()) {
|
||||||
scope.addClassifierDescriptor(classDescriptor)
|
val classDescriptor = module.findClassAcrossModuleDependencies(classId).sure("Couldn't resolve class $className")
|
||||||
|
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
|
||||||
|
scope.addClassifierDescriptor(classDescriptor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
@@ -87,6 +87,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EnumArgumentWithCustomToString.kt")
|
||||||
|
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("SimpleAnnotation.kt")
|
@TestMetadata("SimpleAnnotation.kt")
|
||||||
public void testSimpleAnnotation() throws Exception {
|
public void testSimpleAnnotation() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
||||||
@@ -3433,6 +3439,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EnumArgumentWithCustomToString.java")
|
||||||
|
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("EnumInParam.java")
|
@TestMetadata("EnumInParam.java")
|
||||||
public void testEnumInParam() throws Exception {
|
public void testEnumInParam() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
|
||||||
|
|||||||
+8
-4
@@ -22,10 +22,10 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
abstract class ReflectJavaAnnotationArgument(
|
abstract class ReflectJavaAnnotationArgument(
|
||||||
override val name: Name?
|
override val name: Name?
|
||||||
) : JavaAnnotationArgument {
|
) : JavaAnnotationArgument {
|
||||||
class object {
|
default object Factory {
|
||||||
fun create(value: Any, name: Name?): ReflectJavaAnnotationArgument {
|
fun create(value: Any, name: Name?): ReflectJavaAnnotationArgument {
|
||||||
return when {
|
return when {
|
||||||
value.javaClass.isEnum() -> ReflectJavaEnumValueAnnotationArgument(name, value)
|
value.javaClass.isEnumClassOrSpecializedEnumEntryClass() -> ReflectJavaEnumValueAnnotationArgument(name, value as Enum<*>)
|
||||||
value is Annotation -> ReflectJavaAnnotationAsAnnotationArgument(name, value)
|
value is Annotation -> ReflectJavaAnnotationAsAnnotationArgument(name, value)
|
||||||
value is Array<Any> -> ReflectJavaArrayAnnotationArgument(name, value)
|
value is Array<Any> -> ReflectJavaArrayAnnotationArgument(name, value)
|
||||||
value is Class<*> -> ReflectJavaClassObjectAnnotationArgument(name, value)
|
value is Class<*> -> ReflectJavaClassObjectAnnotationArgument(name, value)
|
||||||
@@ -49,9 +49,13 @@ class ReflectJavaArrayAnnotationArgument(
|
|||||||
|
|
||||||
class ReflectJavaEnumValueAnnotationArgument(
|
class ReflectJavaEnumValueAnnotationArgument(
|
||||||
name: Name?,
|
name: Name?,
|
||||||
private val value: Any
|
private val value: Enum<*>
|
||||||
) : ReflectJavaAnnotationArgument(name), JavaEnumValueAnnotationArgument {
|
) : ReflectJavaAnnotationArgument(name), JavaEnumValueAnnotationArgument {
|
||||||
override fun resolve() = ReflectJavaField(value.javaClass.getDeclaredField(value.toString()))
|
override fun resolve(): ReflectJavaField {
|
||||||
|
val clazz = value.javaClass
|
||||||
|
val enumClass = if (clazz.isEnum()) clazz else clazz.getEnclosingClass()
|
||||||
|
return ReflectJavaField(enumClass.getDeclaredField(value.name()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ReflectJavaClassObjectAnnotationArgument(
|
class ReflectJavaClassObjectAnnotationArgument(
|
||||||
|
|||||||
+5
-1
@@ -80,7 +80,11 @@ public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(
|
|||||||
.map(::ReflectJavaField)
|
.map(::ReflectJavaField)
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
override fun getConstructors() = klass.getDeclaredConstructors().map(::ReflectJavaConstructor)
|
override fun getConstructors() = klass.getDeclaredConstructors()
|
||||||
|
.stream()
|
||||||
|
.filter { constructor -> !constructor.isSynthetic() }
|
||||||
|
.map(::ReflectJavaConstructor)
|
||||||
|
.toList()
|
||||||
|
|
||||||
override fun getDefaultType(): ReflectJavaClassifierType = throw UnsupportedOperationException()
|
override fun getDefaultType(): ReflectJavaClassifierType = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
|||||||
+3
@@ -34,6 +34,9 @@ private fun calculateVisibility(modifiers: Int): Visibility {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean =
|
||||||
|
javaClass<Enum<*>>().isAssignableFrom(this)
|
||||||
|
|
||||||
public val Class<*>.fqName: FqName
|
public val Class<*>.fqName: FqName
|
||||||
get() = classId.asSingleFqName().toSafe()
|
get() = classId.asSingleFqName().toSafe()
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -159,19 +159,21 @@ private object ReflectClassStructure {
|
|||||||
visitor.visit(name, value)
|
visitor.visit(name, value)
|
||||||
}
|
}
|
||||||
javaClass<Enum<*>>().isAssignableFrom(clazz) -> {
|
javaClass<Enum<*>>().isAssignableFrom(clazz) -> {
|
||||||
visitor.visitEnum(name, clazz.classId, Name.identifier(value.toString()))
|
// isEnum returns false for specialized enum constants (enum entries which are subclasses)
|
||||||
|
val classId = (if (clazz.isEnum()) clazz else clazz.getEnclosingClass()).classId
|
||||||
|
visitor.visitEnum(name, classId, Name.identifier((value as Enum<*>).name()))
|
||||||
}
|
}
|
||||||
javaClass<Annotation>().isAssignableFrom(clazz) -> {
|
clazz.isAnnotation() -> {
|
||||||
// TODO: support values of annotation types
|
// TODO: support values of annotation types
|
||||||
throw UnsupportedOperationException("Values of annotation types are not yet supported in Kotlin reflection: $value")
|
throw UnsupportedOperationException("Values of annotation types are not yet supported in Kotlin reflection: $value")
|
||||||
}
|
}
|
||||||
clazz.isArray() -> {
|
clazz.isArray() -> {
|
||||||
val elementVisitor = visitor.visitArray(name) ?: return
|
val elementVisitor = visitor.visitArray(name) ?: return
|
||||||
val componentType = clazz.getComponentType()
|
val componentType = clazz.getComponentType()
|
||||||
if (javaClass<Enum<*>>().isAssignableFrom(componentType)) {
|
if (componentType.isEnum()) {
|
||||||
val componentClassName = componentType.classId
|
val enumClassId = componentType.classId
|
||||||
for (element in value as Array<*>) {
|
for (element in value as Array<*>) {
|
||||||
elementVisitor.visitEnum(componentClassName, Name.identifier(element.toString()))
|
elementVisitor.visitEnum(enumClassId, Name.identifier((element as Enum<*>).name()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ public class ResolveByStubTestGenerated extends AbstractResolveByStubTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("EnumArgumentWithCustomToString.kt")
|
||||||
|
public void testEnumArgumentWithCustomToString() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/EnumArgumentWithCustomToString.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("SimpleAnnotation.kt")
|
@TestMetadata("SimpleAnnotation.kt")
|
||||||
public void testSimpleAnnotation() throws Exception {
|
public void testSimpleAnnotation() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user