blob: 699cf8c8d98d132611967ac71eb551089583ed9e [file] [log] [blame]
/*
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package kotlinx.metadata.test
import kotlinx.metadata.*
import kotlinx.metadata.jvm.*
import org.jetbrains.org.objectweb.asm.ClassWriter
import org.jetbrains.org.objectweb.asm.Opcodes
import org.junit.Assert.assertEquals
import org.junit.Test
import java.net.URLClassLoader
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.full.primaryConstructor
class MetadataSmokeTest {
private fun Class<*>.readMetadata(): KotlinClassHeader {
return getAnnotation(Metadata::class.java).run {
KotlinClassHeader(kind, metadataVersion, bytecodeVersion, data1, data2, extraString, packageName, extraInt)
}
}
@Test
fun listInlineFunctions() {
@Suppress("unused")
class L {
val x: Int inline get() = 42
inline fun foo(f: () -> String) = f()
fun bar() {}
}
val classMetadata = KotlinClassMetadata.read(L::class.java.readMetadata()) as KotlinClassMetadata.Class
val inlineFunctions = classMetadata.toKmClass().functions
.filter { Flag.Function.IS_INLINE(it.flags) }
.mapNotNull { it.signature?.asString() }
assertEquals(
listOf("foo(Lkotlin/jvm/functions/Function0;)Ljava/lang/String;"),
inlineFunctions
)
}
@Test
fun produceKotlinClassFile() {
// First, produce a KotlinMetadata instance with the kotlinx-metadata API, for the following class:
// class Hello {
// fun hello(): String = "Hello, world!"
// }
val klass = KmClass().apply {
name = "Hello"
flags = flagsOf(Flag.IS_PUBLIC)
constructors += KmConstructor(flagsOf(Flag.IS_PUBLIC, Flag.Constructor.IS_PRIMARY)).apply {
signature = JvmMethodSignature("<init>", "()V")
}
functions += KmFunction(flagsOf(Flag.IS_PUBLIC, Flag.Function.IS_DECLARATION), "hello").apply {
returnType = KmType(flagsOf()).apply {
classifier = KmClassifier.Class("kotlin/String")
}
signature = JvmMethodSignature("hello", "()Ljava/lang/String;")
}
}
val header = KotlinClassMetadata.Class.Writer().apply(klass::accept).write().header
// Then, produce the bytecode of a .class file with ASM
val bytes = ClassWriter(0).run {
visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, "Hello", null, "java/lang/Object", null)
// Use the created KotlinMetadata instance to write @kotlin.Metadata annotation on the class file
visitAnnotation("Lkotlin/Metadata;", true).run {
visit("mv", header.metadataVersion)
visit("bv", header.bytecodeVersion)
visit("k", header.kind)
visitArray("d1").run {
header.data1.forEach { visit(null, it) }
visitEnd()
}
visitArray("d2").run {
header.data2.forEach { visit(null, it) }
visitEnd()
}
visitEnd()
}
visitMethod(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, "hello", "()Ljava/lang/String;", null, null).run {
visitAnnotation("Lorg/jetbrains/annotations/NotNull;", false).visitEnd()
visitCode()
visitLdcInsn("Hello, world!")
visitInsn(Opcodes.ARETURN)
visitMaxs(1, 1)
visitEnd()
}
visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null).run {
visitCode()
visitVarInsn(Opcodes.ALOAD, 0)
visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
visitInsn(Opcodes.RETURN)
visitMaxs(1, 1)
visitEnd()
}
visitEnd()
toByteArray()
}
// Finally, load the generated class, create its instance and invoke the method via Kotlin reflection.
// Kotlin reflection loads the metadata and builds a mapping from Kotlin symbols to JVM, so if the call succeeds,
// we can be sure that the metadata is consistent
val classLoader = object : URLClassLoader(emptyArray()) {
override fun findClass(name: String): Class<*> =
if (name == "Hello") defineClass(name, bytes, 0, bytes.size) else super.findClass(name)
}
val kClass = classLoader.loadClass("Hello").kotlin
val hello = kClass.primaryConstructor!!.call()
val result = kClass.members.single { it.name == "hello" }.call(hello) as String
assertEquals("Hello, world!", result)
}
@Test
fun jvmInternalName() {
class ClassNameReader : KmClassVisitor() {
lateinit var className: ClassName
override fun visit(flags: Flags, name: ClassName) {
className = name
}
}
class L
val l = ClassNameReader().run {
(KotlinClassMetadata.read(L::class.java.readMetadata()) as KotlinClassMetadata.Class).accept(this)
className
}
assertEquals(".kotlinx/metadata/test/MetadataSmokeTest\$jvmInternalName\$L", l)
assertEquals("kotlinx/metadata/test/MetadataSmokeTest\$jvmInternalName\$L", l.jvmInternalName)
val coroutineContextKey = ClassNameReader().run {
(KotlinClassMetadata.read(CoroutineContext.Key::class.java.readMetadata()) as KotlinClassMetadata.Class).accept(this)
className
}
assertEquals("kotlin/coroutines/CoroutineContext.Key", coroutineContextKey)
assertEquals("kotlin/coroutines/CoroutineContext\$Key", coroutineContextKey.jvmInternalName)
}
@Test
fun lambdaVersionRequirement() {
val x: suspend Int.(String, String) -> Unit = { _, _ -> }
val annotation = x::class.java.getAnnotation(Metadata::class.java)!!
val metadata = KotlinClassMetadata.read(
KotlinClassHeader(
kind = annotation.kind,
metadataVersion = annotation.metadataVersion,
bytecodeVersion = annotation.bytecodeVersion,
data1 = annotation.data1,
data2 = annotation.data2,
extraInt = annotation.extraInt,
extraString = annotation.extraString,
packageName = annotation.packageName
)
) as KotlinClassMetadata.SyntheticClass
metadata.accept(KmLambda())
}
}