Fix deserialization of enum annotation arguments at runtime

This commit is contained in:
Alexander Udalov
2015-02-27 17:22:02 +03:00
parent f36ef6f546
commit 4fb420f3f1
12 changed files with 177 additions and 13 deletions
@@ -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() {}
}
@@ -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>
}
}
@@ -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() {}
}
@@ -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);
}
@TestMetadata("EnumArgumentWithCustomToString.java")
public void testEnumArgumentWithCustomToString() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumArgumentWithCustomToString.java");
doTestCompiledJava(fileName);
}
@TestMetadata("EnumInParam.java")
public void testEnumInParam() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
@@ -1907,6 +1913,12 @@ public class LoadJavaTestGenerated extends AbstractLoadJavaTest {
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")
public void testSimpleAnnotation() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
@@ -149,9 +149,12 @@ public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdi
else if (header == null ||
(header.kind == KotlinClassHeader.Kind.CLASS && header.classKind == JvmAnnotationNames.KotlinClass.Kind.CLASS)) {
// Either a normal Kotlin class or a Java class
val classDescriptor = module.findClassAcrossModuleDependencies(klass.classId).sure("Couldn't resolve class $className")
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
scope.addClassifierDescriptor(classDescriptor)
val classId = klass.classId
if (!classId.isLocal()) {
val classDescriptor = module.findClassAcrossModuleDependencies(classId).sure("Couldn't resolve class $className")
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
scope.addClassifierDescriptor(classDescriptor)
}
}
}
}
@@ -87,6 +87,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
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")
public void testSimpleAnnotation() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");
@@ -3433,6 +3439,12 @@ public class JvmRuntimeDescriptorLoaderTestGenerated extends AbstractJvmRuntimeD
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")
public void testEnumInParam() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledJava/annotations/EnumInParam.java");
@@ -22,10 +22,10 @@ import org.jetbrains.kotlin.name.Name
abstract class ReflectJavaAnnotationArgument(
override val name: Name?
) : JavaAnnotationArgument {
class object {
default object Factory {
fun create(value: Any, name: Name?): ReflectJavaAnnotationArgument {
return when {
value.javaClass.isEnum() -> ReflectJavaEnumValueAnnotationArgument(name, value)
value.javaClass.isEnumClassOrSpecializedEnumEntryClass() -> ReflectJavaEnumValueAnnotationArgument(name, value as Enum<*>)
value is Annotation -> ReflectJavaAnnotationAsAnnotationArgument(name, value)
value is Array<Any> -> ReflectJavaArrayAnnotationArgument(name, value)
value is Class<*> -> ReflectJavaClassObjectAnnotationArgument(name, value)
@@ -49,9 +49,13 @@ class ReflectJavaArrayAnnotationArgument(
class ReflectJavaEnumValueAnnotationArgument(
name: Name?,
private val value: Any
private val value: Enum<*>
) : 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(
@@ -80,7 +80,11 @@ public class ReflectJavaClass(private val klass: Class<*>) : ReflectJavaElement(
.map(::ReflectJavaField)
.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()
@@ -34,6 +34,9 @@ private fun calculateVisibility(modifiers: Int): Visibility {
}
}
public fun Class<*>.isEnumClassOrSpecializedEnumEntryClass(): Boolean =
javaClass<Enum<*>>().isAssignableFrom(this)
public val Class<*>.fqName: FqName
get() = classId.asSingleFqName().toSafe()
@@ -159,19 +159,21 @@ private object ReflectClassStructure {
visitor.visit(name, value)
}
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
throw UnsupportedOperationException("Values of annotation types are not yet supported in Kotlin reflection: $value")
}
clazz.isArray() -> {
val elementVisitor = visitor.visitArray(name) ?: return
val componentType = clazz.getComponentType()
if (javaClass<Enum<*>>().isAssignableFrom(componentType)) {
val componentClassName = componentType.classId
if (componentType.isEnum()) {
val enumClassId = componentType.classId
for (element in value as Array<*>) {
elementVisitor.visitEnum(componentClassName, Name.identifier(element.toString()))
elementVisitor.visitEnum(enumClassId, Name.identifier((element as Enum<*>).name()))
}
}
else {
@@ -81,6 +81,12 @@ public class ResolveByStubTestGenerated extends AbstractResolveByStubTest {
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")
public void testSimpleAnnotation() throws Exception {
String fileName = JetTestUtils.navigationMetadata("compiler/testData/loadJava/compiledKotlin/annotations/SimpleAnnotation.kt");