Use kotlin.Metadata instead of old annotations in IDE, reflection and tests

This commit is contained in:
Alexander Udalov
2016-01-26 17:20:25 +03:00
parent 7da981e12c
commit ba80e8ba81
6 changed files with 59 additions and 48 deletions
@@ -30,8 +30,7 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.* import java.util.*
object InlineTestUtil { object InlineTestUtil {
private val KOTLIN_MULTIFILE_CLASS_DESC = private val KOTLIN_METADATA_DESC = AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.METADATA)
AsmUtil.asmDescByFqNameWithoutInnerClasses(JvmAnnotationNames.KOTLIN_MULTIFILE_CLASS)
fun checkNoCallsToInline(files: Iterable<OutputFile>, sourceFiles: List<KtFile>) { fun checkNoCallsToInline(files: Iterable<OutputFile>, sourceFiles: List<KtFile>) {
val inlineInfo = obtainInlineInfo(files) val inlineInfo = obtainInlineInfo(files)
@@ -87,14 +86,20 @@ object InlineTestUtil {
val notInlined = ArrayList<NotInlinedCall>() val notInlined = ArrayList<NotInlinedCall>()
files.forEach { file -> files.forEach { file ->
val cr = ClassReader(file.asByteArray()) ClassReader(file.asByteArray()).accept(object : ClassVisitorWithName() {
cr.accept(object : ClassVisitorWithName() {
private var skipMethodsOfThisClass = false private var skipMethodsOfThisClass = false
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
if (desc == KOTLIN_MULTIFILE_CLASS_DESC) { if (desc == KOTLIN_METADATA_DESC) {
skipMethodsOfThisClass = true return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String?, value: Any) {
if (name == JvmAnnotationNames.KIND_FIELD_NAME && value == KotlinClassHeader.Kind.MULTIFILE_CLASS.id) {
skipMethodsOfThisClass = true
}
}
}
} }
return null return null
} }
@@ -29,7 +29,8 @@ import java.lang.annotation.Annotation;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.*; import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.METADATA;
import static org.jetbrains.kotlin.load.java.JvmAnnotationNames.METADATA_VERSION_FIELD_NAME;
public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase { public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
public static final FqName PACKAGE_NAME = new FqName("test"); public static final FqName PACKAGE_NAME = new FqName("test");
@@ -112,14 +113,14 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
} }
private void doTestKotlinSyntheticClass(@NotNull String code, @NotNull String classFilePart) { private void doTestKotlinSyntheticClass(@NotNull String code, @NotNull String classFilePart) {
doTest(code, classFilePart, KOTLIN_SYNTHETIC_CLASS); doTest(code, classFilePart);
} }
private void doTestKotlinClass(@NotNull String code, @NotNull String classFilePart) { private void doTestKotlinClass(@NotNull String code, @NotNull String classFilePart) {
doTest(code, classFilePart, KOTLIN_CLASS); doTest(code, classFilePart);
} }
private void doTest(@NotNull String code, @NotNull final String classFilePart, @NotNull FqName annotationFqName) { private void doTest(@NotNull String code, @NotNull final String classFilePart) {
loadText("package " + PACKAGE_NAME + "\n\n" + code); loadText("package " + PACKAGE_NAME + "\n\n" + code);
List<OutputFile> output = generateClassesInFile().asList(); List<OutputFile> output = generateClassesInFile().asList();
Collection<OutputFile> files = Collections2.filter(output, new Predicate<OutputFile>() { Collection<OutputFile> files = Collections2.filter(output, new Predicate<OutputFile>() {
@@ -134,16 +135,17 @@ public class KotlinSyntheticClassAnnotationTest extends CodegenTestCase {
String path = files.iterator().next().getRelativePath(); String path = files.iterator().next().getRelativePath();
String fqName = path.substring(0, path.length() - ".class".length()).replace('/', '.'); String fqName = path.substring(0, path.length() - ".class".length()).replace('/', '.');
Class<?> aClass = generateClass(fqName); Class<?> aClass = generateClass(fqName);
assertAnnotatedWith(aClass, annotationFqName.asString()); assertAnnotatedWithMetadata(aClass);
} }
private void assertAnnotatedWith(@NotNull Class<?> aClass, @NotNull String annotationFqName) { private void assertAnnotatedWithMetadata(@NotNull Class<?> aClass) {
String annotationFqName = METADATA.asString();
Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(annotationFqName); Class<? extends Annotation> annotationClass = loadAnnotationClassQuietly(annotationFqName);
assertTrue("No annotation " + annotationFqName + " found in " + aClass, aClass.isAnnotationPresent(annotationClass)); assertTrue("No annotation " + annotationFqName + " found in " + aClass, aClass.isAnnotationPresent(annotationClass));
Annotation annotation = aClass.getAnnotation(annotationClass); Annotation annotation = aClass.getAnnotation(annotationClass);
int[] version = (int[]) CodegenTestUtil.getAnnotationAttribute(annotation, VERSION_FIELD_NAME); int[] version = (int[]) CodegenTestUtil.getAnnotationAttribute(annotation, METADATA_VERSION_FIELD_NAME);
assertNotNull(version); assertNotNull(version);
assertTrue("Annotation " + annotationFqName + " is written with an unsupported format", assertTrue("Annotation " + annotationFqName + " is written with an unsupported format",
new JvmMetadataVersion(version).isCompatible()); new JvmMetadataVersion(version).isCompatible());
@@ -79,7 +79,7 @@ abstract class AbstractLocalClassProtoTest : TestCaseWithTmpdir() {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private fun assertHasAnnotationData(clazz: Class<*>) { private fun assertHasAnnotationData(clazz: Class<*>) {
checkNotNull(clazz.getAnnotation( checkNotNull(clazz.getAnnotation(
clazz.classLoader.loadClass(JvmAnnotationNames.KOTLIN_CLASS.asString()) as Class<Annotation> clazz.classLoader.loadClass(JvmAnnotationNames.METADATA.asString()) as Class<Annotation>
)) { "KotlinClass annotation is not found for class $clazz" } )) { "Metadata annotation is not found for class $clazz" }
} }
} }
@@ -24,7 +24,6 @@ import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil import org.jetbrains.kotlin.serialization.jvm.JvmProtoBufUtil
import java.lang.reflect.* import java.lang.reflect.*
import java.util.* import java.util.*
import kotlin.jvm.internal.KotlinFileFacade
import kotlin.jvm.internal.Reflection import kotlin.jvm.internal.Reflection
import kotlin.reflect.* import kotlin.reflect.*
import kotlin.reflect.jvm.internal.KTypeImpl import kotlin.reflect.jvm.internal.KTypeImpl
@@ -104,20 +103,18 @@ val Field.kotlinProperty: KProperty<*>?
private fun Member.getKPackage(): KDeclarationContainer? { private fun Member.getKPackage(): KDeclarationContainer? {
// TODO: support multifile classes // TODO: support multifile classes
return if (declaringClass.getAnnotation(KotlinFileFacade::class.java) != null) { val header = ReflectKotlinClass.create(declaringClass)?.classHeader
val header = ReflectKotlinClass.create(declaringClass)?.classHeader if (header != null && header.kind == KotlinClassHeader.Kind.FILE_FACADE && header.metadataVersion.isCompatible()) {
if (header != null && header.kind == KotlinClassHeader.Kind.FILE_FACADE && header.metadataVersion.isCompatible()) { // TODO: avoid reading and parsing metadata twice (here and later in KPackageImpl#descriptor)
// TODO: avoid reading and parsing metadata twice (here and later in KPackageImpl#descriptor) val (nameResolver, proto) = JvmProtoBufUtil.readPackageDataFrom(header.data!!, header.strings!!)
val (nameResolver, proto) = JvmProtoBufUtil.readPackageDataFrom(header.data!!, header.strings!!) val moduleName =
val moduleName = if (proto.hasExtension(JvmProtoBuf.packageModuleName))
if (proto.hasExtension(JvmProtoBuf.packageModuleName)) nameResolver.getString(proto.getExtension(JvmProtoBuf.packageModuleName))
nameResolver.getString(proto.getExtension(JvmProtoBuf.packageModuleName)) else JvmAbi.DEFAULT_MODULE_NAME
else JvmAbi.DEFAULT_MODULE_NAME return Reflection.getOrCreateKotlinPackage(declaringClass, moduleName)
Reflection.getOrCreateKotlinPackage(declaringClass, moduleName)
}
else null
} }
else null
return null
} }
/** /**
@@ -94,12 +94,7 @@ class AllClassesCompletion(private val parameters: CompletionParameters,
private fun PsiClass.isSyntheticKotlinClass(): Boolean { private fun PsiClass.isSyntheticKotlinClass(): Boolean {
if ('$' !in name!!) return false // optimization to not analyze annotations of all classes if ('$' !in name!!) return false // optimization to not analyze annotations of all classes
val metadata = modifierList?.findAnnotation(JvmAnnotationNames.METADATA.asString()) val metadata = modifierList?.findAnnotation(JvmAnnotationNames.METADATA.asString())
if (metadata != null && return (metadata?.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value ==
(metadata.findAttributeValue(JvmAnnotationNames.KIND_FIELD_NAME) as? PsiLiteral)?.value == KotlinClassHeader.Kind.SYNTHETIC_CLASS.id
KotlinClassHeader.Kind.SYNTHETIC_CLASS.id) {
return true
}
return modifierList?.findAnnotation(JvmAnnotationNames.KOTLIN_SYNTHETIC_CLASS.asString()) != null
} }
} }
@@ -23,6 +23,7 @@ import com.intellij.util.indexing.FileContent
import org.jetbrains.kotlin.codegen.AsmUtil.asmDescByFqNameWithoutInnerClasses import org.jetbrains.kotlin.codegen.AsmUtil.asmDescByFqNameWithoutInnerClasses
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.* import org.jetbrains.kotlin.load.java.JvmAnnotationNames.*
import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion import org.jetbrains.kotlin.load.kotlin.JvmMetadataVersion
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion import org.jetbrains.kotlin.serialization.deserialization.BinaryVersion
import org.jetbrains.org.objectweb.asm.AnnotationVisitor import org.jetbrains.org.objectweb.asm.AnnotationVisitor
import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.ClassReader
@@ -38,30 +39,37 @@ object KotlinMetadataVersionIndex : KotlinAbiVersionIndexBase<KotlinMetadataVers
override fun getVersion() = VERSION override fun getVersion() = VERSION
private val VERSION = 3 private val VERSION = 4
private val kotlinAnnotationsDesc = setOf( private val METADATA_DESC = asmDescByFqNameWithoutInnerClasses(METADATA)
KOTLIN_CLASS,
KOTLIN_FILE_FACADE, private val kindsToIndex = setOf(
KOTLIN_MULTIFILE_CLASS KotlinClassHeader.Kind.CLASS,
).map { asmDescByFqNameWithoutInnerClasses(it) } KotlinClassHeader.Kind.FILE_FACADE,
KotlinClassHeader.Kind.MULTIFILE_CLASS
)
private val INDEXER = DataIndexer<BinaryVersion, Void, FileContent>() { inputData: FileContent -> private val INDEXER = DataIndexer<BinaryVersion, Void, FileContent>() { inputData: FileContent ->
var version: BinaryVersion? = null var version: BinaryVersion? = null
var annotationPresent = false var annotationPresent = false
var kind: KotlinClassHeader.Kind? = null
tryBlock(inputData) { tryBlock(inputData) {
val classReader = ClassReader(inputData.content) val classReader = ClassReader(inputData.content)
classReader.accept(object : ClassVisitor(Opcodes.ASM5) { classReader.accept(object : ClassVisitor(Opcodes.ASM5) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? { override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
if (!kotlinAnnotationsDesc.contains(desc)) { if (desc != METADATA_DESC) return null
return null
}
annotationPresent = true annotationPresent = true
return object : AnnotationVisitor(Opcodes.ASM5) { return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String, value: Any) { override fun visit(name: String, value: Any) {
if (name == VERSION_FIELD_NAME && value is IntArray) { when (name) {
version = JvmMetadataVersion(*value) METADATA_VERSION_FIELD_NAME -> if (value is IntArray) {
version = JvmMetadataVersion(*value)
}
KIND_FIELD_NAME -> if (value is Int) {
kind = KotlinClassHeader.Kind.getById(value)
}
} }
} }
} }
@@ -69,7 +77,11 @@ object KotlinMetadataVersionIndex : KotlinAbiVersionIndexBase<KotlinMetadataVers
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES) }, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
} }
if (annotationPresent && version == null) { if (kind !in kindsToIndex) {
// Do not index metadata version for synthetic classes
version = null
}
else if (annotationPresent && version == null) {
// No version at all because the class is too old, or version is set to something weird // No version at all because the class is too old, or version is set to something weird
version = JvmMetadataVersion.INVALID_VERSION version = JvmMetadataVersion.INVALID_VERSION
} }